From f5ffbaa42a8623d0edb00501459b69d39dcb258b Mon Sep 17 00:00:00 2001 From: Maria Sokolova Date: Thu, 11 Apr 2024 14:44:10 +0200 Subject: [PATCH] [coroutine-debugger]: Removed kotlin-stdlib and coroutines dependencies from intellij.java.rt.iml. Use simple reflection method calls in the Helper class. GitOrigin-RevId: d260099c7d459e7afcbb544cd7771a49cb97e838 --- .../rt/debugger/CoroutinesDebugHelper.java | 27 ---------- .../ContinuationExtractorHelper.java | 16 ++++++ .../coroutines/CoroutinesDebugHelper.java | 54 +++++++++++++++++++ .../CoroutineStackFrameInterceptor.kt | 37 +++++++------ 4 files changed, 90 insertions(+), 44 deletions(-) delete mode 100644 java/java-runtime/src/com/intellij/rt/debugger/CoroutinesDebugHelper.java create mode 100644 java/java-runtime/src/com/intellij/rt/debugger/coroutines/ContinuationExtractorHelper.java create mode 100644 java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java diff --git a/java/java-runtime/src/com/intellij/rt/debugger/CoroutinesDebugHelper.java b/java/java-runtime/src/com/intellij/rt/debugger/CoroutinesDebugHelper.java deleted file mode 100644 index ced48e789d7e..000000000000 --- a/java/java-runtime/src/com/intellij/rt/debugger/CoroutinesDebugHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.rt.debugger; - -import java.util.ArrayList; -import java.util.List; - -public final class CoroutinesDebugHelper { - public static long[] getCoroutinesRunningOnCurrentThread(Object debugProbes) throws ReflectiveOperationException { - Thread currentThread = Thread.currentThread(); - List coroutinesIds = new ArrayList<>(); - List infos = (List)invoke(debugProbes, "dumpCoroutinesInfo"); - for (Object info : infos) { - if (invoke(info, "getLastObservedThread") == currentThread) { - coroutinesIds.add((Long)invoke(info, "getSequenceNumber")); - } - } - long[] res = new long[coroutinesIds.size()]; - for (int i = 0; i < res.length; i++) { - res[i] = coroutinesIds.get(i).longValue(); - } - return res; - } - - private static Object invoke(Object object, String methodName) throws ReflectiveOperationException { - return object.getClass().getMethod(methodName).invoke(object); - } -} \ No newline at end of file diff --git a/java/java-runtime/src/com/intellij/rt/debugger/coroutines/ContinuationExtractorHelper.java b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/ContinuationExtractorHelper.java new file mode 100644 index 000000000000..c0836bb1204f --- /dev/null +++ b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/ContinuationExtractorHelper.java @@ -0,0 +1,16 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.rt.debugger.coroutines; + + +public final class ContinuationExtractorHelper { + private static final String GET_CALLER_FRAME_METHOD = "getCallerFrame"; + + public static Object getRootContinuation(Object continuation) throws ReflectiveOperationException { + Object parentFrame = invoke(continuation, GET_CALLER_FRAME_METHOD); + return (parentFrame != null) ? getRootContinuation(parentFrame) : continuation; + } + + private static Object invoke(Object object, String methodName) throws ReflectiveOperationException { + return object.getClass().getMethod(methodName).invoke(object); + } +} \ No newline at end of file diff --git a/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java new file mode 100644 index 000000000000..6abbc55b2680 --- /dev/null +++ b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java @@ -0,0 +1,54 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.rt.debugger.coroutines; + +import java.util.ArrayList; +import java.util.List; + +public final class CoroutinesDebugHelper { + + private static final String GET_CALLER_FRAME_METHOD = "getCallerFrame"; + private static final String DUMP_COROUTINES_INFO_METHOD = "dumpCoroutinesInfo"; + private static final String GET_LAST_OBSERVED_THREAD_METHOD = "getLastObservedThread"; + private static final String GET_SEQUENCE_NUMBER_METHOD = "getSequenceNumber"; + private static final String COROUTINE_OWNER_CLASS = "CoroutineOwner"; + private static final String DEBUG_COROUTINE_INFO_FIELD = "info"; + private static final String SEQUENCE_NUMBER_FIELD = "sequenceNumber"; + + public static long[] getCoroutinesRunningOnCurrentThread(Object debugProbes) throws ReflectiveOperationException { + Thread currentThread = Thread.currentThread(); + List coroutinesIds = new ArrayList<>(); + List infos = (List)invoke(debugProbes, DUMP_COROUTINES_INFO_METHOD); + for (Object info : infos) { + if (invoke(info, GET_LAST_OBSERVED_THREAD_METHOD) == currentThread) { + coroutinesIds.add((Long)invoke(info, GET_SEQUENCE_NUMBER_METHOD)); + } + } + long[] res = new long[coroutinesIds.size()]; + for (int i = 0; i < res.length; i++) { + res[i] = coroutinesIds.get(i).longValue(); + } + return res; + } + + public static long tryGetContinuationId(Object continuation) throws ReflectiveOperationException { + Object rootContinuation = getCoroutineOwner(continuation); + Object debugCoroutineInfo = getField(rootContinuation, DEBUG_COROUTINE_INFO_FIELD); + return (long) getField(debugCoroutineInfo, SEQUENCE_NUMBER_FIELD); + } + + // This method tries to extract CoroutineOwner as a root coroutine frame, + // it is invoked when kotlinx-coroutines debug agent is enabled. + private static Object getCoroutineOwner(Object stackFrame) throws ReflectiveOperationException { + if (stackFrame.getClass().getSimpleName().equals(COROUTINE_OWNER_CLASS)) return stackFrame; + Object parentFrame = invoke(stackFrame, GET_CALLER_FRAME_METHOD); + return (parentFrame != null) ? getCoroutineOwner(parentFrame) : stackFrame; + } + + private static Object getField(Object object, String fieldName) throws ReflectiveOperationException { + return object.getClass().getField(fieldName).get(object); + } + + private static Object invoke(Object object, String methodName) throws ReflectiveOperationException { + return object.getClass().getMethod(methodName).invoke(object); + } +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt index 13e4b9093b4b..724a4bf5378e 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt @@ -16,7 +16,8 @@ import com.intellij.execution.ui.layout.impl.RunnerLayoutUiImpl import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.util.registry.Registry -import com.intellij.rt.debugger.CoroutinesDebugHelper +import com.intellij.rt.debugger.coroutines.ContinuationExtractorHelper +import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.impl.XDebugSessionImpl import com.sun.jdi.* @@ -66,13 +67,13 @@ class CoroutineStackFrameInterceptor : StackFrameInterceptor { // First try to extract Continuation filter val continuationFilter = tryComputeContinuationFilter(frameProxy, defaultExecutionContext) if (continuationFilter != null) return continuationFilter - // If continuation could not be extracted or the root continuation was not an instance of BaseContinuationImpl, + // If continuation could not be extracted or the root continuation was not an instance of BaseContinuationImpl, // dump coroutines running on the current thread and compute [CoroutineIdFilter]. val debugProbesImpl = DebugProbesImpl.instance(defaultExecutionContext) val coroutineIdFilter = if (debugProbesImpl != null && debugProbesImpl.isInstalled) { // first try the helper, it is the fastest way, then try the mirror - val currentCoroutines = getCoroutinesRunningOnCurrentThreadFromHelper(defaultExecutionContext, debugProbesImpl) ?: - debugProbesImpl.getCoroutinesRunningOnCurrentThread(defaultExecutionContext) + val currentCoroutines = getCoroutinesRunningOnCurrentThreadFromHelper(defaultExecutionContext, debugProbesImpl) + ?: debugProbesImpl.getCoroutinesRunningOnCurrentThread(defaultExecutionContext) CoroutineIdFilter(currentCoroutines) } else { //TODO: IDEA-341142 show nice notification about this @@ -160,32 +161,34 @@ class CoroutineStackFrameInterceptor : StackFrameInterceptor { context: DefaultExecutionContext ): CoroutineFilter? { if (debugProbesImpl != null && debugProbesImpl.isInstalled) { - val continuationId = (callMethodFromHelper(context, "tryGetContinuationId", listOf(currentContinuation)) as LongValue).value() - if (continuationId != -1L) return CoroutineIdFilter(setOf(continuationId)) + val continuationIdValue = callMethodFromHelper(CoroutinesDebugHelper::class.java, context, "tryGetContinuationId", listOf(currentContinuation)) + if (continuationIdValue != null) { + return CoroutineIdFilter(setOf((continuationIdValue as LongValue).value())) + } thisLogger().warn("[coroutine filter]: Could not extract continuation ID, location = ${context.frameProxy?.location()}") } - val rootContinuation = callMethodFromHelper(context, "getRootContinuation", listOf(currentContinuation)) + val rootContinuation = callMethodFromHelper(ContinuationExtractorHelper::class.java, context, "getRootContinuation", listOf(currentContinuation)) if (rootContinuation == null) thisLogger().warn("[coroutine filter]: Could not extract continuation instance") return rootContinuation?.let { ContinuationObjectFilter(it as ObjectReference) } } - + private fun getCoroutinesRunningOnCurrentThreadFromHelper( context: DefaultExecutionContext, debugProbesImpl: DebugProbesImpl ): Set? { - val result = callMethodFromHelper(context, "getCoroutinesRunningOnCurrentThread", listOf(debugProbesImpl.getObject())) + val result = callMethodFromHelper(CoroutinesDebugHelper::class.java, context, "getCoroutinesRunningOnCurrentThread", listOf(debugProbesImpl.getObject())) result ?: return null return (result as ArrayReference).values.asSequence().map { (it as LongValue).value() }.toHashSet() } - private fun callMethodFromHelper(context: DefaultExecutionContext, methodName: String, args: List): Value? { + private fun callMethodFromHelper(helperClass: Class<*>, context: DefaultExecutionContext, methodName: String, args: List): Value? { try { - val helperClass = ClassLoadingUtils.getHelperClass(CoroutinesDebugHelper::class.java, context.evaluationContext) - if (helperClass != null) { - val method = DebuggerUtils.findMethod(helperClass, methodName, null) + val helper = ClassLoadingUtils.getHelperClass(helperClass, context.evaluationContext) + if (helper != null) { + val method = DebuggerUtils.findMethod(helper, methodName, null) if (method != null) { return context.evaluationContext.computeAndKeep { - context.invokeMethod(helperClass, method, args) + context.invokeMethod(helper, method, args) } } } @@ -244,12 +247,12 @@ class CoroutineStackFrameInterceptor : StackFrameInterceptor { override fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean = (nextCoroutineFilter is CoroutineIdFilter && coroutinesRunningOnCurrentThread.intersect(nextCoroutineFilter.coroutinesRunningOnCurrentThread).isNotEmpty()) } - + /** * The coroutine filter which defines a coroutine by the instance of the Continuation corresponding to the root coroutine frame. */ private data class ContinuationObjectFilter(val reference: ObjectReference) : CoroutineFilter { - override fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean = + override fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean = this == nextCoroutineFilter } -} +} \ No newline at end of file