From 26195d71085ec7165b263f592f507f849fc90229 Mon Sep 17 00:00:00 2001 From: Egor Ushakov Date: Fri, 30 Jan 2026 12:11:56 +0100 Subject: [PATCH] refactor [debugger]: reuse instanceOf method from the debugger utils GitOrigin-RevId: 5f9a7dc707604592b3ee5bb35bf9052eb41f8a7f --- .../debugger/engine/EvaluationUtils.kt | 44 ++----------------- .../com/intellij/debugger/impl/DebugUtils.kt | 2 + .../idea/debugger/base/util/JdiTypeUtils.kt | 16 +++++++ .../core/render/KotlinClassRenderer.kt | 6 +-- .../core/stackFrame/KotlinStackFrame.kt | 7 +-- .../debugger/coroutine/proxy/mirror/base.kt | 4 +- .../proxy/mirror/coroutinesDebugMirror.kt | 8 ++-- .../debugger/coroutine/util/CoroutineUtils.kt | 18 ++++---- .../variables/EvaluatorValueConverter.kt | 2 +- .../evaluate/variables/VariableFinder.kt | 4 +- .../stepping/packets/coroutineStepping.out | 2 +- .../packets/veryLongCoroutineStack.out | 4 +- 12 files changed, 49 insertions(+), 68 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/EvaluationUtils.kt b/java/debugger/impl/src/com/intellij/debugger/engine/EvaluationUtils.kt index 1c2b2d1da7fe..aeaca09943c2 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/EvaluationUtils.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/EvaluationUtils.kt @@ -4,6 +4,7 @@ package com.intellij.debugger.engine import com.intellij.debugger.engine.events.SuspendContextCommandImpl import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.impl.DebuggerUtilsAsync +import com.intellij.debugger.impl.instanceOf import com.intellij.debugger.settings.DebuggerSettings import com.intellij.debugger.ui.breakpoints.FilteredRequestor import com.intellij.openapi.diagnostic.logger @@ -14,9 +15,7 @@ import com.intellij.xdebugger.breakpoints.XBreakpointProperties import com.intellij.xdebugger.impl.breakpoints.BreakpointState import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil -import com.sun.jdi.ClassType import com.sun.jdi.ObjectReference -import com.sun.jdi.Type import com.sun.jdi.event.LocatableEvent import com.sun.jdi.request.EventRequest import kotlinx.coroutines.CompletableDeferred @@ -26,7 +25,6 @@ import kotlinx.coroutines.withTimeout import org.jetbrains.annotations.ApiStatus import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties import kotlin.time.Duration -import org.jetbrains.org.objectweb.asm.Type as AsmType /** * Find some suspend context in which evaluation is possible. @@ -166,40 +164,6 @@ fun , P : XBreakpointProperties<*>, S : BreakpointState> s return (isLoggingBp || isConditionalBp) && !(isLoggingBp && isConditionalBp) } -@ApiStatus.Internal -fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className)) - -@ApiStatus.Internal -fun Type.isSubTypeOrSame(className: String): Boolean = - name() == className || isSubtype(className) - -@ApiStatus.Internal -fun Type.isSubtype(type: AsmType): Boolean { - if (this.signature() == type.descriptor) { - return true - } - - if (type.sort != AsmType.OBJECT || this !is ClassType) { - return false - } - - val superTypeName = type.className - - if (allInterfaces().any { it.name() == superTypeName }) { - return true - } - - var superClass = superclass() - while (superClass != null) { - if (superClass.name() == superTypeName) { - return true - } - superClass = superClass.superclass() - } - - return false -} - @ApiStatus.Internal enum class ClientEvaluationExceptionType { USER_EXCEPTION, @@ -217,12 +181,12 @@ fun extractTypeFromClientException(exceptionFromCodeFragment: ObjectReference, h } } if (type.signature().startsWith("Ljava/lang/invoke/") - || type.isSubTypeOrSame("java.lang.ReflectiveOperationException") - || type.isSubTypeOrSame("java.lang.LinkageError") + || type.instanceOf("java.lang.ReflectiveOperationException") + || type.instanceOf("java.lang.LinkageError") ) { return ClientEvaluationExceptionType.MISCOMPILED } - if (type.isSubTypeOrSame("java.lang.ClassCastException")) { + if (type.instanceOf("java.lang.ClassCastException")) { return if (hasCast) ClientEvaluationExceptionType.USER_EXCEPTION else ClientEvaluationExceptionType.MISCOMPILED } return ClientEvaluationExceptionType.USER_EXCEPTION diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt index e817fbbcd014..f26703eeea46 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt @@ -138,3 +138,5 @@ internal fun runBlockingAssertNotInReadAction(block: suspend CoroutineScope. } return runBlockingMaybeCancellable(block) } + +fun Type.instanceOf(className: String): Boolean = DebuggerUtils.instanceOf(this, className) \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/JdiTypeUtils.kt b/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/JdiTypeUtils.kt index 4d36dc115c63..29e875b213b5 100644 --- a/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/JdiTypeUtils.kt +++ b/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/JdiTypeUtils.kt @@ -8,6 +8,22 @@ import com.intellij.debugger.engine.DebuggerUtils import com.sun.jdi.ClassType import com.sun.jdi.Method import com.sun.jdi.ReferenceType +import com.sun.jdi.Type +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.org.objectweb.asm.Type as AsmType + +@ApiStatus.Internal +fun Type.isSubtype(type: AsmType): Boolean { + if (this.signature() == type.descriptor) { + return true + } + + if (type.sort != AsmType.OBJECT || this !is ClassType) { + return false + } + + return DebuggerUtils.instanceOf(this, type.className) +} fun ClassType.hasSuperClass(jdiName: String): Boolean { var current = this diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/render/KotlinClassRenderer.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/render/KotlinClassRenderer.kt index 2fb6b1194018..97f1820b381f 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/render/KotlinClassRenderer.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/render/KotlinClassRenderer.kt @@ -5,10 +5,10 @@ package org.jetbrains.kotlin.idea.debugger import com.intellij.debugger.JavaDebuggerBundle import com.intellij.debugger.engine.DebuggerManagerThreadImpl -import com.intellij.debugger.engine.DebuggerUtils import com.intellij.debugger.engine.FieldVisibilityProvider import com.intellij.debugger.engine.JVMNameUtil import com.intellij.debugger.engine.evaluation.EvaluationContext +import com.intellij.debugger.impl.instanceOf import com.intellij.debugger.impl.DebuggerUtilsAsync import com.intellij.debugger.settings.NodeRendererSettings import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl @@ -21,6 +21,7 @@ import com.intellij.debugger.ui.tree.render.DescriptorLabelListener import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project import com.sun.jdi.* +import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.idea.debugger.base.util.isLateinitVariableGetter import org.jetbrains.kotlin.idea.debugger.base.util.isSimpleGetter import org.jetbrains.kotlin.idea.debugger.base.util.safeFields @@ -32,7 +33,6 @@ import org.jetbrains.kotlin.idea.debugger.core.isInKotlinSourcesAsync import org.jetbrains.kotlin.idea.debugger.core.render.GetterDescriptor import java.util.concurrent.CompletableFuture import java.util.function.Function -import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import kotlin.metadata.ExperimentalContextReceivers import kotlin.metadata.KmProperty import kotlin.metadata.isNotDefault @@ -228,7 +228,7 @@ class KotlinClassRenderer : ClassRenderer() { "java.util.Map.Entry", ) - return typesWithGoodDefaultRenderers.any { superType -> DebuggerUtils.instanceOf(this, superType) } + return typesWithGoodDefaultRenderers.any(this::instanceOf) } private fun Field.isInstanceFieldOfType(type: Type) = diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/stackFrame/KotlinStackFrame.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/stackFrame/KotlinStackFrame.kt index 603fffe6efb7..5a46b4d06644 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/stackFrame/KotlinStackFrame.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/stackFrame/KotlinStackFrame.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.idea.debugger.core.stackFrame import com.intellij.debugger.engine.JavaStackFrame import com.intellij.debugger.engine.JavaValue import com.intellij.debugger.engine.evaluation.EvaluationContextImpl -import com.intellij.debugger.engine.isSubtype +import com.intellij.debugger.impl.instanceOf import com.intellij.debugger.jdi.LocalVariableProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.INLI import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.INLINE_SCOPE_NUMBER_SEPARATOR import org.jetbrains.kotlin.idea.debugger.base.util.dropInlineSuffix import org.jetbrains.kotlin.idea.debugger.base.util.getInlineDepth +import org.jetbrains.kotlin.idea.debugger.base.util.isSubtype import org.jetbrains.kotlin.idea.debugger.base.util.safeVisibleVariables import org.jetbrains.kotlin.idea.debugger.core.ToggleKotlinVariablesState import org.jetbrains.kotlin.load.java.JvmAbi @@ -111,7 +112,7 @@ open class KotlinStackFrame( val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false val thisObjectType = thisObject.type() - if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) { + if (thisObjectType.instanceOf(Function::class.java.name) && '$' in thisObjectType.signature()) { val existingThis = ExistingInstanceThisRemapper.find(children) if (existingThis != null) { existingThis.remove() @@ -128,7 +129,7 @@ open class KotlinStackFrame( ExistingInstanceThisRemapper.find(children)?.remove() val dispatchReceiver = (evaluationContext.frameProxy as? KotlinStackFrameProxyImpl)?.dispatchReceiver() ?: return true val dispatchReceiverType = dispatchReceiver.type() - if (dispatchReceiverType.isSubtype(Function::class.java.name) && '$' in dispatchReceiverType.signature()) { + if (dispatchReceiverType.instanceOf(Function::class.java.name) && '$' in dispatchReceiverType.signature()) { attachCapturedValues(evaluationContext, children, dispatchReceiver, existingVariables) } return true diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/base.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/base.kt index ac85806bd577..51fedbdbff81 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/base.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/base.kt @@ -2,7 +2,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror -import com.intellij.debugger.engine.isSubTypeOrSame +import com.intellij.debugger.impl.instanceOf import com.sun.jdi.ObjectReference import com.sun.jdi.ReferenceType import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext @@ -15,7 +15,7 @@ abstract class BaseMirror(val name: String, context: Defa override fun getCls(): ReferenceType = cls override fun isCompatible(value: T?) = - value?.referenceType()?.isSubTypeOrSame(name) ?: false + value?.referenceType()?.instanceOf(name) ?: false override fun mirror(value: T?, context: DefaultExecutionContext): F? { if (value == null) return null diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt index a7ca0d35d549..c803651b7002 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/mirror/coroutinesDebugMirror.kt @@ -2,7 +2,7 @@ package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror -import com.intellij.debugger.engine.isSubTypeOrSame +import com.intellij.debugger.impl.instanceOf import com.sun.jdi.* import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger @@ -110,10 +110,10 @@ class DebugProbesImplCoroutineOwner(private val coroutineInfo: CoroutineInfo?, c } companion object { - const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner" + private const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner" - fun instanceOf(value: ObjectReference?) = - value?.referenceType()?.isSubTypeOrSame(COROUTINE_OWNER_CLASS_NAME) ?: false + internal fun instanceOf(value: ObjectReference?): Boolean = + value?.referenceType()?.instanceOf(COROUTINE_OWNER_CLASS_NAME) ?: false } } diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt index 2f8989012fab..8e41df191b82 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt @@ -6,7 +6,7 @@ import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.DebuggerManagerThreadImpl import com.intellij.debugger.engine.SuspendContextImpl -import com.intellij.debugger.engine.isSubtype +import com.intellij.debugger.impl.instanceOf import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl @@ -19,7 +19,6 @@ import org.jetbrains.kotlin.idea.debugger.base.util.* import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendExitMode import org.jetbrains.kotlin.idea.util.application.isUnitTestMode -import org.jetbrains.org.objectweb.asm.Type as AsmType const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines const val CREATION_CLASS_NAME = "_COROUTINE._CREATION" @@ -75,18 +74,17 @@ internal fun extractContinuation(frameProxy: StackFrameProxyImpl): ObjectReferen fun Location.safeCoroutineExitPointLineNumber() = (wrapIllegalArgumentException { DebuggerUtilsEx.getLineNumber(this, false) } ?: -2) == -1 -fun Type.isBaseContinuationImpl() = - isSubtype("kotlin.coroutines.jvm.internal.BaseContinuationImpl") - fun Type.isCoroutineScope() = - isSubtype("kotlinx.coroutines.CoroutineScope") - -@Deprecated("Use isSubTypeOrSame from com.intellij.debugger.engine.evaluation.expression instead") -fun Type.isSubTypeOrSame(className: String): Boolean = isSubtype(AsmType.getObjectType(className)) + instanceOf("kotlinx.coroutines.CoroutineScope") +@Deprecated( + "Use com.intellij.debugger.engine.DebuggerUtils.instanceOf instead", + ReplaceWith("com.intellij.debugger.engine.DebuggerUtils.instanceOf(this, className)") +) +fun Type.isSubTypeOrSame(className: String): Boolean = instanceOf(className) fun ReferenceType.isSuspendLambda() = - SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) } + SUSPEND_LAMBDA_CLASSES.any { instanceOf(it) } fun Location.isInvokeSuspend() = safeMethod()?.isInvokeSuspend() ?: false diff --git a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/EvaluatorValueConverter.kt b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/EvaluatorValueConverter.kt index 0cdf84c4c62d..341a09c652c4 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/EvaluatorValueConverter.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/EvaluatorValueConverter.kt @@ -3,11 +3,11 @@ package org.jetbrains.kotlin.idea.debugger.evaluate.variables import com.intellij.debugger.engine.DebuggerUtils -import com.intellij.debugger.engine.isSubtype import com.sun.jdi.* import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.ExecutionContext import org.jetbrains.kotlin.idea.debugger.base.util.findMethod +import org.jetbrains.kotlin.idea.debugger.base.util.isSubtype import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType import kotlin.jvm.internal.Ref diff --git a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt index 3f536d78a753..6d7c225f0dcf 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/VariableFinder.kt @@ -6,7 +6,7 @@ import com.intellij.debugger.engine.DebuggerUtils import com.intellij.debugger.engine.JavaValue import com.intellij.debugger.engine.evaluation.AdditionalContextProvider import com.intellij.debugger.engine.evaluation.EvaluationContextImpl -import com.intellij.debugger.engine.isSubtype +import com.intellij.debugger.impl.instanceOf import com.intellij.debugger.jdi.LocalVariableProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl import com.sun.jdi.* @@ -476,7 +476,7 @@ internal class VariableFinder(val context: ExecutionContext) { val continuation = frameProxy.continuation ?: return null val continuationType = continuation.referenceType() - if (SUSPEND_LAMBDA_CLASSES.none { continuationType.isSubtype(it) }) { + if (SUSPEND_LAMBDA_CLASSES.none { continuationType.instanceOf(it) }) { return null } diff --git a/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/coroutineStepping.out b/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/coroutineStepping.out index f2286c42f7e7..36bf99d9f19a 100644 --- a/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/coroutineStepping.out +++ b/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/coroutineStepping.out @@ -3,7 +3,7 @@ Run Java Connected to the target VM coroutineStepping.kt:18 coroutineStepping.kt:19 -Packets sent: 35, methods called: 4 +Packets sent: 36, methods called: 4 coroutineStepping.kt:20 Packets sent: 27, methods called: 4 coroutineStepping.kt:19 diff --git a/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/veryLongCoroutineStack.out b/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/veryLongCoroutineStack.out index 58db1a117ee2..05987bd7c8cb 100644 --- a/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/veryLongCoroutineStack.out +++ b/plugins/kotlin/jvm-debugger/test/testData/stepping/packets/veryLongCoroutineStack.out @@ -87,7 +87,7 @@ Packets sent: 49, methods called: 2 veryLongCoroutineStack.kt:12 Packets sent: 48, methods called: 2 veryLongCoroutineStack.kt:12 -Packets sent: 54, methods called: 3 +Packets sent: 57, methods called: 3 veryLongCoroutineStack.kt:16 Packets sent: 36, methods called: 2 veryLongCoroutineStack.kt:17 @@ -173,7 +173,7 @@ Packets sent: 16, methods called: 2 veryLongCoroutineStack.kt:17 Packets sent: 16, methods called: 2 veryLongCoroutineStack.kt:22 -Packets sent: 23, methods called: 4 +Packets sent: 29, methods called: 4 veryLongCoroutineStack.kt:19 Packets sent: 3, methods called: 1 veryLongCoroutineStack.kt:22