From 03000b963a3fcb06cd829e47d1e5b15781eb696e Mon Sep 17 00:00:00 2001 From: Maria Sokolova Date: Mon, 23 Jun 2025 16:56:37 +0200 Subject: [PATCH] [debugger] Fixed NPE in Coroutines View IDEA-374561 (cherry picked from commit 37474b08ca8a3c0751b36fa883756c1dbf8b94dc) IJ-CR-167001 GitOrigin-RevId: 5b92085c4b025cd28b0c572ea79648b493c19cbc --- .../coroutines/CoroutinesDebugHelper.java | 3 +- .../proxy/CoroutineDebugProbesProxy.kt | 4 +- .../coroutine/proxy/CoroutineInfoProvider.kt | 56 ++++++--------- ...actK2IdeK1CoroutineViewJobHierarchyTest.kt | 24 +++++++ ...oroutineViewJobHierarchyTestGenerated.java | 47 ++++++++++++ ...oroutineViewJobHierarchyTestGenerated.java | 47 ++++++++++++ .../AbstractCoroutineViewJobHierarchyTest.kt | 71 +++++++++++++++++++ ...oroutineViewJobHierarchyTestGenerated.java | 47 ++++++++++++ .../K1IdeK2CoroutineViewTestGenerated.java | 47 ++++++++++++ ...KotlinDescriptorTestCaseWithStackFrames.kt | 4 +- .../coroutinesView/coroutinesHierarchy2.kt | 14 ++++ .../coroutinesView/coroutinesHierarchy2.out | 9 +++ .../coroutinesView/coroutinesHierarchy3.kt | 57 +++++++++++++++ .../coroutinesView/coroutinesHierarchy3.out | 14 ++++ .../testData/coroutinesView/oneCoroutine.kt | 30 ++++++++ .../testData/coroutinesView/oneCoroutine.out | 9 +++ .../fe10/testGenerator/Fe10GenerateTests.kt | 6 ++ .../testGenerator/GenerateK2DebuggerTests.kt | 9 +++ 18 files changed, 458 insertions(+), 40 deletions(-) create mode 100644 plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/AbstractK2IdeK1CoroutineViewJobHierarchyTest.kt create mode 100644 plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK1CoroutineViewJobHierarchyTestGenerated.java create mode 100644 plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK2CoroutineViewJobHierarchyTestGenerated.java create mode 100644 plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractCoroutineViewJobHierarchyTest.kt create mode 100644 plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/CoroutineViewJobHierarchyTestGenerated.java create mode 100644 plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/K1IdeK2CoroutineViewTestGenerated.java create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.kt create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.out create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.kt create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.out create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.kt create mode 100644 plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.out 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 index 78b93007b9b1..d985c4118e05 100644 --- a/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java +++ b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java @@ -177,7 +177,8 @@ public final class CoroutinesDebugHelper { for (int i = 0; i < coroutineInfos.length; i++) { lastObservedStackTraces[i] = lastObservedStackTrace(coroutineInfos[i]); } - dump[3] = lastObservedStackTraces; + dump = Arrays.copyOf(dump, dump.length + 1); + dump[4] = lastObservedStackTraces; return dump; } catch (Throwable e) { return null; diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt index d8286e0eb3e3..96dcf312aa65 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineDebugProbesProxy.kt @@ -7,6 +7,7 @@ import com.intellij.debugger.impl.DebuggerUtilsImpl.logError import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper import com.sun.jdi.ArrayReference import com.sun.jdi.StringReference +import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext import org.jetbrains.kotlin.idea.debugger.coroutine.callMethodFromHelper import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoCache @@ -51,7 +52,8 @@ class CoroutineDebugProbesProxy(val suspendContext: SuspendContextImpl) { * * The corresponding properties [CoroutineInfoData.job] and [CoroutineInfoData.parentJob] are set to the obtained values. */ - internal fun fetchAndSetJobsAndParentsForCoroutines(infos: List): Boolean { + @ApiStatus.Internal + fun fetchAndSetJobsAndParentsForCoroutines(infos: List): Boolean { val executionContext = suspendContext.executionContext() ?: return false val debugCoroutineInfos = infos.map { it.debugCoroutineInfoRef } val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "getJobsAndParentsForCoroutines", debugCoroutineInfos) diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt index 6f85e7de6b7a..5e3affeed9b8 100644 --- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt +++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt @@ -40,13 +40,13 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( } val coroutinesInfoAsJsonString = arrayValues[0].safeAs()?.value() - ?: error("The first element of the result array must be a string") + ?: error("The 1st element of the result array must be a string") val lastObservedThreadRefs = arrayValues[1].safeAs()?.toTypedList() - ?: error("The second element of the result array must be an array") + ?: error("The 2nd element of the result array must be an array") val lastObservedFrameRefs = arrayValues[2].safeAs()?.toTypedList() - ?: error("The third element of the result array must be an array") + ?: error("The 3rd element of the result array must be an array") val coroutineInfoRefs = arrayValues[3].safeAs()?.toTypedList() - ?: error("The fourth element of the result array must be an array") + ?: error("The 4th element of the result array must be an array") val coroutinesInfo = Gson().fromJson(coroutinesInfoAsJsonString, Array::class.java) if (coroutineInfoRefs.size != lastObservedFrameRefs.size || @@ -55,7 +55,7 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( error("Arrays must have equal sizes") } - return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs) + return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs, null) } fun dumpCoroutinesWithStacktraces(): List? { @@ -63,18 +63,20 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( val arrayValues = (array as? ArrayReference)?.values ?: return null - if (arrayValues.size != 4) { - error("The result array of 'dumpCoroutinesWithStacktracesAsJson' should be of size 4") + if (arrayValues.size != 5) { + error("The result array of 'dumpCoroutinesWithStacktracesAsJson' should be of size 5") } val coroutinesInfoAsJsonString = arrayValues[0].safeAs()?.value() - ?: error("The first element of the result array must be a string") + ?: error("The 1st element of the result array must be a string") val lastObservedThreadRefs = arrayValues[1].safeAs()?.toTypedList() - ?: error("The second element of the result array must be an array") + ?: error("The 2nd element of the result array must be an array") val lastObservedFrameRefs = arrayValues[2].safeAs()?.toTypedList() - ?: error("The third element of the result array must be an array") - val lastObservedStackTraceJsons = arrayValues[3].safeAs()?.toTypedList() - ?: error("The fourth element of the result array must be an array") + ?: error("The 3rd element of the result array must be an array") + val coroutineInfoRefs = arrayValues[3].safeAs()?.toTypedList() + ?: error("The 4th element of the result array must be an array") + val lastObservedStackTraceJsons = arrayValues[4].safeAs()?.toTypedList() + ?: error("The 5th element of the result array must be an array") val coroutinesInfo = Gson().fromJson(coroutinesInfoAsJsonString, Array::class.java) val lastObservedStackTraces: List> = lastObservedStackTraceJsons.map { @@ -85,11 +87,12 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( if (lastObservedStackTraces.size != lastObservedFrameRefs.size || lastObservedFrameRefs.size != coroutinesInfo.size || + coroutineInfoRefs.size != coroutinesInfo.size || coroutinesInfo.size != lastObservedThreadRefs.size) { error("Arrays must have equal sizes") } - return calculateCoroutineInfoDataWithStacktraces(coroutinesInfo, lastObservedThreadRefs, lastObservedFrameRefs, lastObservedStackTraces) + return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs, lastObservedStackTraces) } private fun fallbackToOldMirrorDump(executionContext: DefaultExecutionContext): ArrayReference? { @@ -103,7 +106,8 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( coroutineInfos: Array, coroutineInfoRefs: List, lastObservedThreadRefs: List, - lastObservedFrameRefs: List + lastObservedFrameRefs: List, + lastObservedStackTraces: List>? ): List { return coroutineInfoRefs.mapIndexed { i, ref -> val info = coroutineInfos[i] @@ -115,28 +119,8 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider( lastObservedFrame = lastObservedFrameRefs[i], lastObservedThread = lastObservedThreadRefs[i], debugCoroutineInfoRef = ref, - stackFrameProvider = stackFramesProvider - ) - } - } - - private fun calculateCoroutineInfoDataWithStacktraces( - coroutineInfos: Array, - lastObservedThreadRefs: List, - lastObservedFrameRefs: List, - lastObservedStackTraces: List> - ): List { - return coroutineInfos.mapIndexed { i, info -> - CoroutineInfoData( - name = info.name, - id = info.sequenceNumber, - state = info.state, - dispatcher = info.dispatcher, - lastObservedFrame = lastObservedFrameRefs[i], - lastObservedThread = lastObservedThreadRefs[i], - debugCoroutineInfoRef = null, - stackFrameProvider = null, - lastObservedStackTrace = lastObservedStackTraces[i] + stackFrameProvider = stackFramesProvider, + lastObservedStackTrace = lastObservedStackTraces?.get(i) ?: emptyList() ) } } diff --git a/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/AbstractK2IdeK1CoroutineViewJobHierarchyTest.kt b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/AbstractK2IdeK1CoroutineViewJobHierarchyTest.kt new file mode 100644 index 000000000000..affaf7a49ead --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/AbstractK2IdeK1CoroutineViewJobHierarchyTest.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.k2.debugger.test.cases + +import org.jetbrains.kotlin.config.JvmClosureGenerationScheme +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.idea.debugger.test.* +import org.jetbrains.kotlin.idea.k2.debugger.test.K2DebuggerTestCompilerFacility + +abstract class AbstractK2IdeK1CoroutineViewJobHierarchyTest : AbstractCoroutineViewJobHierarchyTest() { + + override fun createDebuggerTestCompilerFacility( + testFiles: TestFiles, + jvmTarget: JvmTarget, + compileConfig: TestCompileConfiguration + ): DebuggerTestCompilerFacility { + return K2DebuggerTestCompilerFacility(project, testFiles, jvmTarget, compileConfig) + } +} + +abstract class AbstractK2IdeK2CoroutineViewJobHierarchyTest : AbstractK2IdeK1CoroutineViewJobHierarchyTest() { + + override val compileWithK2 = true + override fun lambdasGenerationScheme() = JvmClosureGenerationScheme.INDY +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK1CoroutineViewJobHierarchyTestGenerated.java b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK1CoroutineViewJobHierarchyTestGenerated.java new file mode 100644 index 000000000000..b6eef0a57560 --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK1CoroutineViewJobHierarchyTestGenerated.java @@ -0,0 +1,47 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.debugger.test.cases; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("jvm-debugger/test/k2") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("../testData/coroutinesView") +public class K2IdeK1CoroutineViewJobHierarchyTestGenerated extends AbstractK2IdeK1CoroutineViewJobHierarchyTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("coroutinesHierarchy2.kt") + public void testCoroutinesHierarchy2() throws Exception { + runTest("../testData/coroutinesView/coroutinesHierarchy2.kt"); + } + + @TestMetadata("coroutinesHierarchy3.kt") + public void testCoroutinesHierarchy3() throws Exception { + runTest("../testData/coroutinesView/coroutinesHierarchy3.kt"); + } + + @TestMetadata("oneCoroutine.kt") + public void testOneCoroutine() throws Exception { + runTest("../testData/coroutinesView/oneCoroutine.kt"); + } +} diff --git a/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK2CoroutineViewJobHierarchyTestGenerated.java b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK2CoroutineViewJobHierarchyTestGenerated.java new file mode 100644 index 000000000000..c16bd4f66699 --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/k2/test/org/jetbrains/kotlin/idea/k2/debugger/test/cases/K2IdeK2CoroutineViewJobHierarchyTestGenerated.java @@ -0,0 +1,47 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.k2.debugger.test.cases; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("jvm-debugger/test/k2") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("../testData/coroutinesView") +public class K2IdeK2CoroutineViewJobHierarchyTestGenerated extends AbstractK2IdeK2CoroutineViewJobHierarchyTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K2; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("coroutinesHierarchy2.kt") + public void testCoroutinesHierarchy2() throws Exception { + runTest("../testData/coroutinesView/coroutinesHierarchy2.kt"); + } + + @TestMetadata("coroutinesHierarchy3.kt") + public void testCoroutinesHierarchy3() throws Exception { + runTest("../testData/coroutinesView/coroutinesHierarchy3.kt"); + } + + @TestMetadata("oneCoroutine.kt") + public void testOneCoroutine() throws Exception { + runTest("../testData/coroutinesView/oneCoroutine.kt"); + } +} diff --git a/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractCoroutineViewJobHierarchyTest.kt b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractCoroutineViewJobHierarchyTest.kt new file mode 100644 index 000000000000..2d87d0faef8b --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/AbstractCoroutineViewJobHierarchyTest.kt @@ -0,0 +1,71 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.debugger.test + +import com.intellij.debugger.engine.executeOnDMT +import com.intellij.debugger.impl.PrioritizedTask +import org.jetbrains.kotlin.config.JvmClosureGenerationScheme +import org.jetbrains.kotlin.idea.debugger.coroutine.data.CoroutineInfoData +import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.CoroutineDebugProbesProxy +import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences + +abstract class AbstractCoroutineViewJobHierarchyTest : KotlinDescriptorTestCaseWithStackFrames() { + override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) { + doOnBreakpoint { + val suspendContext = this + try { + executeOnDMT(suspendContext, PrioritizedTask.Priority.NORMAL) { + val coroutineDebugProxy = CoroutineDebugProbesProxy(suspendContext) + val coroutineCache = coroutineDebugProxy.dumpCoroutines() + if (coroutineCache.isOk()) { + val cache = coroutineCache.cache + val isHierarchyBuilt = coroutineDebugProxy.fetchAndSetJobsAndParentsForCoroutines(cache) + if (isHierarchyBuilt) { + printCoroutinesJobHierarchy(cache) + } else { + printCoroutineInfos(cache) + } + } + } + } finally { + resume(suspendContext) + } + } + } + + private fun printCoroutineInfos(infos: List) { + for (info in infos) { + out(0, info.name + " " + info.state) + } + } + + private fun printCoroutinesJobHierarchy(infos: List) { + val parentJobToChildCoroutineInfos = infos.groupBy { it.parentJob } + val jobToCoroutineInfo = infos.associateBy { it.job } + val rootJobs = infos.filter { it.parentJob == null }.mapNotNull { it.job } + + out(0, "==== Hierarchy of coroutines =====") + for (root in rootJobs) { + val rootCoroutineInfo = jobToCoroutineInfo[root] + if (rootCoroutineInfo == null) { + out(0, root) + } else { + printInfo(rootCoroutineInfo, parentJobToChildCoroutineInfos, 0) + } + } + } + + private fun printInfo(info: CoroutineInfoData, jobToChildCoroutineInfos: Map>, indent: Int) { + out(indent, info.name + " " + info.state) + val children = jobToChildCoroutineInfos[info.job] ?: emptyList() + for (child in children) { + printInfo(child, jobToChildCoroutineInfos, indent + 1) + } + } +} + + +abstract class AbstractK1IdeK2CoroutineViewTest : AbstractCoroutineViewJobHierarchyTest() { + override val compileWithK2 = true + + override fun lambdasGenerationScheme() = JvmClosureGenerationScheme.INDY +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/CoroutineViewJobHierarchyTestGenerated.java b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/CoroutineViewJobHierarchyTestGenerated.java new file mode 100644 index 000000000000..7b596b41552a --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/CoroutineViewJobHierarchyTestGenerated.java @@ -0,0 +1,47 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.debugger.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("jvm-debugger/test") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("testData/coroutinesView") +public class CoroutineViewJobHierarchyTestGenerated extends AbstractCoroutineViewJobHierarchyTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K1; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("coroutinesHierarchy2.kt") + public void testCoroutinesHierarchy2() throws Exception { + runTest("testData/coroutinesView/coroutinesHierarchy2.kt"); + } + + @TestMetadata("coroutinesHierarchy3.kt") + public void testCoroutinesHierarchy3() throws Exception { + runTest("testData/coroutinesView/coroutinesHierarchy3.kt"); + } + + @TestMetadata("oneCoroutine.kt") + public void testOneCoroutine() throws Exception { + runTest("testData/coroutinesView/oneCoroutine.kt"); + } +} diff --git a/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/K1IdeK2CoroutineViewTestGenerated.java b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/K1IdeK2CoroutineViewTestGenerated.java new file mode 100644 index 000000000000..6154f50aae64 --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/K1IdeK2CoroutineViewTestGenerated.java @@ -0,0 +1,47 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.debugger.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode; +import org.jetbrains.kotlin.idea.base.test.TestRoot; +import org.jetbrains.kotlin.idea.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.idea.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +/** + * This class is generated by {@link org.jetbrains.kotlin.testGenerator.generator.TestGenerator}. + * DO NOT MODIFY MANUALLY. + */ +@SuppressWarnings("all") +@TestRoot("jvm-debugger/test") +@TestDataPath("$CONTENT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +@TestMetadata("testData/coroutinesView") +public class K1IdeK2CoroutineViewTestGenerated extends AbstractK1IdeK2CoroutineViewTest { + @java.lang.Override + @org.jetbrains.annotations.NotNull + public final KotlinPluginMode getPluginMode() { + return KotlinPluginMode.K1; + } + + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + @TestMetadata("coroutinesHierarchy2.kt") + public void testCoroutinesHierarchy2() throws Exception { + runTest("testData/coroutinesView/coroutinesHierarchy2.kt"); + } + + @TestMetadata("coroutinesHierarchy3.kt") + public void testCoroutinesHierarchy3() throws Exception { + runTest("testData/coroutinesView/coroutinesHierarchy3.kt"); + } + + @TestMetadata("oneCoroutine.kt") + public void testOneCoroutine() throws Exception { + runTest("testData/coroutinesView/oneCoroutine.kt"); + } +} diff --git a/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt index 5160de45cc1f..73de24bac9ef 100644 --- a/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt +++ b/plugins/kotlin/jvm-debugger/test/test/org/jetbrains/kotlin/idea/debugger/test/KotlinDescriptorTestCaseWithStackFrames.kt @@ -29,11 +29,11 @@ abstract class KotlinDescriptorTestCaseWithStackFrames : KotlinDescriptorTestCas out(INDENT_VARIABLES, "(${variables.joinToString()})") } - private fun out(text: String) { + protected fun out(text: String) { println(text, ProcessOutputTypes.SYSTEM) } - private fun out(indent: Int, text: String) { + protected fun out(indent: Int, text: String) { println("\t".repeat(indent) + text, ProcessOutputTypes.SYSTEM) } diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.kt b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.kt new file mode 100644 index 000000000000..3058f7c33123 --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.kt @@ -0,0 +1,14 @@ +package coroutinesView + +// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2)-javaagent + +import kotlinx.coroutines.* + +fun main() { + runBlocking(CoroutineName("root")) { + println() + //Breakpoint! + delay(11L) + println() + } +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.out b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.out new file mode 100644 index 000000000000..e39dc3b4e39c --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy2.out @@ -0,0 +1,9 @@ +LineBreakpoint created at coroutinesHierarchy2.kt:11 +Run Java +Connected to the target VM +coroutinesHierarchy2.kt:11 +==== Hierarchy of coroutines ===== +root RUNNING +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.kt b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.kt new file mode 100644 index 000000000000..3648c80a472f --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.kt @@ -0,0 +1,57 @@ +package coroutinesView + +// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2)-javaagent + +import kotlinx.coroutines.* + +fun main() { + runBlocking(CoroutineName("root")) { + test() + } +} + +suspend fun test() { + coroutineScope { + launch(CoroutineName("launch1")) { + delay(1) + println() + delay(1) + } + launch(CoroutineName("launch2")) { + coroutineScope { + val a = async(CoroutineName("childAsync1")) { + val coroutineVar = 1 + delay(100) + println("coroutineVar=$coroutineVar") + 6 + } + + val b = async(CoroutineName("childAsync2")) { + val coroutineVar = 2 + delay(100) + println("coroutineVar=$coroutineVar") + 7 + } + } + } + withContext(Dispatchers.Default) { + coroutineScope { + val a = async(CoroutineName("async1")) { + val coroutineVar = 1 + delay(100) + println("coroutineVar=$coroutineVar") // ← breakpoint + 6 + } + + val b = async(CoroutineName("async2")) { + val coroutineVar = 2 + delay(100) + println("coroutineVar=$coroutineVar") + 7 + } + //Breakpoint! + println("result: ${a.await() * b.await()}") + } + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.out b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.out new file mode 100644 index 000000000000..a554fc6ceedb --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/coroutinesHierarchy3.out @@ -0,0 +1,14 @@ +LineBreakpoint created at coroutinesHierarchy3.kt:53 +Run Java +Connected to the target VM +coroutinesHierarchy3.kt:53 +==== Hierarchy of coroutines ===== +root RUNNING + launch2 SUSPENDED + childAsync1 SUSPENDED + childAsync2 SUSPENDED + async1 SUSPENDED + async2 SUSPENDED +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.kt b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.kt new file mode 100644 index 000000000000..2d2ad20d3e31 --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.kt @@ -0,0 +1,30 @@ +package coroutinesView + +// ATTACH_LIBRARY: maven(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2)-javaagent + +import kotlinx.coroutines.* + +fun main() { + runBlocking(CoroutineName("root")) { + a() + } +} + +suspend fun a() { + val a = "a" + b(a) + val aLate = a +} + +suspend fun b(paramA: String) { + yield() + val b = "b" + c(b) + val dead = paramA +} + +suspend fun c(paramB: String) { + val c = "c" + //Breakpoint! + val dead = paramB +} \ No newline at end of file diff --git a/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.out b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.out new file mode 100644 index 000000000000..2d324d83282c --- /dev/null +++ b/plugins/kotlin/jvm-debugger/test/testData/coroutinesView/oneCoroutine.out @@ -0,0 +1,9 @@ +LineBreakpoint created at oneCoroutine.kt:29 +Run Java +Connected to the target VM +oneCoroutine.kt:29 +==== Hierarchy of coroutines ===== +root RUNNING +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/plugins/kotlin/util/test-generator-fe10/test/org/jetbrains/kotlin/fe10/testGenerator/Fe10GenerateTests.kt b/plugins/kotlin/util/test-generator-fe10/test/org/jetbrains/kotlin/fe10/testGenerator/Fe10GenerateTests.kt index dec357555815..2a4fa5bdce5d 100644 --- a/plugins/kotlin/util/test-generator-fe10/test/org/jetbrains/kotlin/fe10/testGenerator/Fe10GenerateTests.kt +++ b/plugins/kotlin/util/test-generator-fe10/test/org/jetbrains/kotlin/fe10/testGenerator/Fe10GenerateTests.kt @@ -299,6 +299,12 @@ private fun assembleWorkspace(): TWorkspace = workspace(KotlinPluginMode.K1) { } } + listOf(AbstractCoroutineViewJobHierarchyTest::class, AbstractK1IdeK2CoroutineViewTest::class).forEach { + testClass(it) { + model("coroutinesView") + } + } + testClass { // TODO: implement mapping logic for terminal operations model("sequence/streams/sequence", excludedDirectories = listOf("terminal")) } diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2DebuggerTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2DebuggerTests.kt index 35ebf710b94c..aaf7149a0b52 100644 --- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2DebuggerTests.kt +++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/GenerateK2DebuggerTests.kt @@ -98,6 +98,15 @@ internal fun MutableTWorkspace.generateK2DebuggerTests() { } } + listOf( + AbstractK2IdeK2CoroutineViewJobHierarchyTest::class, + AbstractK2IdeK1CoroutineViewJobHierarchyTest::class, + ).forEach { + testClass(it) { + model("coroutinesView") + } + } + //testClass { // TODO: implement mapping logic for terminal operations // model("sequence/streams/sequence", excludedDirectories = listOf("terminal")) //}