Debugger: Display async stack trace elements for suspend callers (KT-28728)

This commit is contained in:
Yan Zhulanow
2018-12-10 16:32:19 +09:00
parent e8c066605b
commit f6b2e673f7
15 changed files with 681 additions and 20 deletions
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.utils.kapt
import org.jetbrains.kotlin.utils.getSafe
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.util.*
import javax.annotation.processing.*
@@ -139,19 +139,4 @@ private fun ClassLoader.loadedClasses(): Vector<Class<*>> {
} catch (e: Throwable) {
return Vector()
}
}
private fun Field.getSafe(obj: Any?): Any? {
return try {
val oldIsAccessible = isAccessible
try {
isAccessible = true
get(obj)
} finally {
isAccessible = oldIsAccessible
}
} catch (e: Throwable) {
null
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.utils
import java.lang.reflect.Field
fun Field.getSafe(obj: Any?): Any? {
return try {
val oldIsAccessible = isAccessible
try {
isAccessible = true
get(obj)
} finally {
isAccessible = oldIsAccessible
}
} catch (e: Throwable) {
null
}
}
@@ -656,6 +656,10 @@ fun main(args: Array<String>) {
model("debugger/tinyApp/src/evaluate/multipleBreakpoints", testMethod = "doMultipleBreakpointsTest")
}
testClass<AbstractAsyncStackTraceTest> {
model("debugger/tinyApp/src/asyncStackTrace")
}
testClass<AbstractFileRankingTest> {
model("debugger/fileRanking")
}
@@ -67,10 +67,7 @@ import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralKotlinToKotlinCo
import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest
import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest
import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest
import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.evaluate.*
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
@@ -639,6 +636,10 @@ fun main(args: Array<String>) {
model("debugger/tinyApp/src/evaluate/multipleBreakpoints", testMethod = "doMultipleBreakpointsTest")
}
testClass<AbstractAsyncStackTraceTest> {
model("debugger/tinyApp/src/asyncStackTrace")
}
testClass<AbstractStubBuilderTest> {
model("stubs", extension = "kt")
}
@@ -0,0 +1,201 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.AsyncStackTraceProvider
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.GeneratedLocation
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.xdebugger.frame.XNamedValue
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class KotlinCoroutinesAsyncStackTraceProvider : AsyncStackTraceProvider {
private companion object {
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
fun classByName(name: String, frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): ReferenceType? {
val virtualMachine = frameProxy.virtualMachine
val classClass = virtualMachine.classesByName(Class::class.java.name).firstIsInstanceOrNull<ClassType>() ?: return null
val forNameMethod = classClass.methodsByName("forName").first { it.signature() == "(Ljava/lang/String;)Ljava/lang/Class;" }
try {
val threadReference = frameProxy.threadProxy().threadReference
val args = listOf(virtualMachine.mirrorOf(name))
val result = classClass.invokeMethod(threadReference, forNameMethod, args, suspendContext.getInvokePolicy())
if (result is ClassObjectReference) {
return result.reflectedType()
}
} catch (e: InvocationException) {
// Ignore ClassNotFoundException
} catch (e: Throwable) {
LOG.error(e)
}
return null
}
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
return type
}
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
}
}
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
return getAsyncStackTrace(stackFrame.stackFrameProxy, suspendContext)
}
fun getAsyncStackTrace(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
val location = frameProxy.location()
val method = location.safeMethod() ?: return null
val debugMetadataKtType = classByName(DEBUG_METADATA_KT, frameProxy, suspendContext) as? ClassType ?: return null
val context = Context(suspendContext, frameProxy, method, debugMetadataKtType)
return context.getAsyncStackTraceForSuspendLambda() ?: context.getAsyncStackTraceForSuspendFunction()
}
private fun Context.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
return null
}
val thisObject = frameProxy.thisObject() ?: return null
val thisType = thisObject.referenceType()
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
return null
}
return collectFrames(thisObject)
}
private fun Context.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
return null
}
val continuationVariable = frameProxy.visibleVariableByName("\$continuation") ?: return null
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
return collectFrames(continuation)
}
private fun Context.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
val frames = mutableListOf<StackFrameItem>()
collectFramesRecursively(continuation, frames)
return frames
}
private fun Context.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
val continuationType = continuation.referenceType() as? ClassType ?: return
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
val location = getLocation(continuation)
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
if (location != null) {
consumer += StackFrameItem(location, spilledVariables)
}
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
collectFramesRecursively(completion, consumer)
}
private class Context(
val suspendContext: SuspendContextImpl,
val frameProxy: StackFrameProxyImpl,
val method: Method,
private val debugMetadataKtType: ClassType
) {
val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) }
fun getLocation(continuation: ObjectReference): Location? {
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
"getStackTraceElement",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
).firstOrNull() ?: return null
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null
val args = listOf(continuation)
val invokePolicy = suspendContext.getInvokePolicy()
val stackTraceElement = debugMetadataKtType
.invokeMethod(threadReference, getStackTraceElementMethod, args, invokePolicy) as? ObjectReference ?: return null
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
?: return null
fun getValue(name: String, desc: String): Value? {
val method = stackTraceElementType.methodsByName(name, desc).single()
return stackTraceElement.invokeMethod(threadReference, method, emptyList(), invokePolicy)
}
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
val locationClass = classByName(className, frameProxy, suspendContext) ?: return null
return GeneratedLocation(suspendContext.debugProcess, locationClass, methodName, lineNumber)
}
fun getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
"getSpilledVariableFieldMapping",
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
).firstOrNull() ?: return null
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null
val args = listOf(continuation)
val invokePolicy = suspendContext.getInvokePolicy()
val rawSpilledVariables = debugMetadataKtType
.invokeMethod(threadReference, getSpilledVariableFieldMappingMethod, args, invokePolicy) as? ArrayReference ?: return null
val length = rawSpilledVariables.length() / 2
val spilledVariables = ArrayList<XNamedValue>(length)
for (index in 0 until length) {
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
val project = suspendContext.debugProcess.project
val valueDescriptor = object : ValueDescriptorImpl(project) {
override fun calcValueName() = variableName
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
override fun getDescriptorEvaluation(context: DebuggerContext?) =
throw EvaluateException("Spilled variable evaluation is not supported")
}
spilledVariables += JavaValue.create(
null,
valueDescriptor,
evaluationContext,
suspendContext.debugProcess.xdebugProcess!!.nodeManager,
false
)
}
return spilledVariables
}
}
}
+1
View File
@@ -90,6 +90,7 @@
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState"/>
<debugger.asyncStackTraceProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler"/>
<debugger.positionManagerFactory implementation="org.jetbrains.kotlin.idea.debugger.KotlinPositionManagerFactory"/>
<debugger.codeFragmentFactory implementation="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory"/>
+200
View File
@@ -0,0 +1,200 @@
<idea-plugin>
<extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.kotlin.platformGradleDetector"
interface="org.jetbrains.kotlin.idea.inspections.gradle.KotlinPlatformGradleDetector"/>
</extensionPoints>
<application-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.JvmPluginStartupComponent</implementation-class>
</component>
</application-components>
<project-components>
<component>
<implementation-class>org.jetbrains.kotlin.idea.compiler.KotlinCompilerManager</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ui.ScratchFileHook</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.scratch.ScratchFileModuleInfoProvider</implementation-class>
</component>
</project-components>
<actions>
<!-- Kotlin Console REPL-->
<action id="KotlinConsoleREPL" class="org.jetbrains.kotlin.console.actions.RunKotlinConsoleAction"
text="Kotlin REPL"
icon="/org/jetbrains/kotlin/idea/icons/kotlin_launch_configuration.png">
<add-to-group group-id="KotlinToolsGroup" anchor="last"/>
</action>
<action id="ConfigureKotlinInProject" class="org.jetbrains.kotlin.idea.actions.ConfigureKotlinJavaInProjectAction"
text="Configure Kotlin in Project">
<add-to-group group-id="KotlinToolsGroup"/>
</action>
<action id="ConfigureKotlinJsInProject" class="org.jetbrains.kotlin.idea.actions.ConfigureKotlinJsInProjectAction"
text="Configure Kotlin (JavaScript) in Project">
<add-to-group group-id="KotlinToolsGroup"/>
</action>
<action id="ShowKotlinBytecode" class="org.jetbrains.kotlin.idea.actions.ShowKotlinBytecodeAction"
text="Show Kotlin Bytecode">
<add-to-group group-id="KotlinToolsGroup"/>
</action>
<action id="CreateIncrementalCompilationBackup"
class="org.jetbrains.kotlin.idea.internal.makeBackup.CreateIncrementalCompilationBackup" internal="true">
<add-to-group group-id="KotlinInternalGroup"/>
</action>
<action id="ReactivePostOpenProjectActionsAction" class="org.jetbrains.kotlin.idea.actions.internal.ReactivePostOpenProjectActionsAction"
text="Kotlin Project Post-Open Activity" internal="true">
<add-to-group group-id="KotlinInternalGroup"/>
</action>
<action id="AddToProblemApiInspection" class="org.jetbrains.kotlin.idea.inspections.api.AddIncompatibleApiAction"
text="Report as incompatible API">
</action>
<group id="Kotlin.XDebugger.Actions">
<action id="Kotlin.XDebugger.ToggleKotlinVariableView"
class="org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesView"
icon="/org/jetbrains/kotlin/idea/icons/kotlin.png"/>
</group>
<group id="Kotlin.XDebugger.Watches.Tree.Toolbar">
<reference ref="Kotlin.XDebugger.ToggleKotlinVariableView"/>
<add-to-group group-id="XDebugger.Watches.Tree.Toolbar" relative-to-action="XDebugger.SwitchWatchesInVariables" anchor="after"/>
</group>
</actions>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceInterface="org.jetbrains.kotlin.console.KotlinConsoleKeeper"
serviceImplementation="org.jetbrains.kotlin.console.KotlinConsoleKeeper"/>
<buildProcess.parametersProvider implementation="org.jetbrains.kotlin.idea.compiler.configuration.KotlinBuildProcessParametersProvider"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches"
serviceImplementation="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches"/>
<projectService serviceImplementation="org.jetbrains.kotlin.idea.versions.SuppressNotificationState"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler"/>
<debugger.positionManagerFactory implementation="org.jetbrains.kotlin.idea.debugger.KotlinPositionManagerFactory"/>
<debugger.codeFragmentFactory implementation="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory"/>
<debuggerEditorTextProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider"/>
<debuggerClassFilterProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinDebuggerInternalClassesFilterProvider"/>
<debugger.nodeRenderer implementation="org.jetbrains.kotlin.idea.debugger.render.KotlinClassWithDelegatedPropertyRenderer"/>
<debugger.sourcePositionProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinSourcePositionProvider"/>
<debugger.sourcePositionHighlighter implementation="org.jetbrains.kotlin.idea.debugger.KotlinSourcePositionHighlighter"/>
<debugger.frameExtraVarsProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinFrameExtraVariablesProvider"/>
<debugger.extraSteppingFilter implementation="org.jetbrains.kotlin.idea.KotlinExtraSteppingFilter"/>
<xdebugger.settings implementation="org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType"/>
<xdebugger.breakpointType implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType" order="first"/>
<debugger.syntheticProvider implementation="org.jetbrains.kotlin.idea.debugger.filter.KotlinSyntheticTypeComponentProvider"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
<framework.type implementation="org.jetbrains.kotlin.idea.framework.JavaFrameworkType"/>
<projectTemplatesFactory implementation="org.jetbrains.kotlin.idea.framework.KotlinTemplatesFactory" />
<library.presentationProvider implementation="org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationType"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationProducer"/>
<library.type implementation="org.jetbrains.kotlin.idea.framework.JSLibraryType"/>
<library.javaSourceRootDetector implementation="org.jetbrains.kotlin.idea.configuration.KotlinSourceRootDetector"/>
<multipleRunLocationsProvider implementation="org.jetbrains.kotlin.idea.run.multiplatform.KotlinMultiplatformRunLocationsProvider"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.configuration.KotlinSetupEnvironmentNotificationProvider"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinAlternativeSourceNotificationProvider"/>
<consoleFilterProvider implementation="org.jetbrains.kotlin.idea.run.KotlinConsoleFilterProvider"/>
<exceptionFilter implementation="org.jetbrains.kotlin.idea.filters.KotlinExceptionFilterFactory" order="first"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.configuration.KotlinExternalSystemSyncListener"/>
<lang.surroundDescriptor language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.debugger.surroundWith.KotlinDebuggerExpressionSurroundDescriptor"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
<localInspection
groupName="Plugin DevKit"
shortName="IncompatibleAPI"
enabledByDefault="false"
level="ERROR"
displayName="Incompatible API usage"
implementationClass="org.jetbrains.kotlin.idea.inspections.api.IncompatibleAPIInspection"/>
<projectService serviceInterface="org.jetbrains.uast.kotlin.KotlinUastResolveProviderService"
serviceImplementation="org.jetbrains.uast.kotlin.internal.IdeaKotlinUastResolveProviderService"/>
<applicationService
serviceInterface="org.jetbrains.kotlin.platform.DefaultIdeTargetPlatformKindProvider"
serviceImplementation="org.jetbrains.kotlin.platform.impl.IdeaDefaultIdeTargetPlatformKindProvider"/>
<registryKey key="kotlin.use.ultra.light.classes"
description="Whether to use an experimental implementation of Kotlin-as-Java classes"
defaultValue="false"
restartRequired="false"/>
<registryKey key="kotlin.uast.multiresolve.enabled"
description="Whether to use muiti resolve for UAST in Kotlin provided by `Call.resolveCandidates`,
otherwise PsiPolyVariantReference-based multiResolve will be performed"
defaultValue="false"
restartRequired="false"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.uast">
<uastLanguagePlugin implementation="org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<diagnosticSuppressor implementation="org.jetbrains.kotlin.idea.debugger.DiagnosticSuppressorForDebugger"/>
<storageComponentContainerContributor implementation="org.jetbrains.kotlin.samWithReceiver.ide.IdeSamWithReceiverComponentContributor"/>
<declarationAttributeAltererExtension implementation="org.jetbrains.kotlin.allopen.ide.IdeAllOpenDeclarationAttributeAltererExtension"/>
<storageComponentContainerContributor implementation="org.jetbrains.kotlin.noarg.ide.IdeNoArgComponentContainerContributor"/>
<expressionCodegenExtension implementation="org.jetbrains.kotlin.noarg.NoArgExpressionCodegenExtension"/>
<defaultErrorMessages implementation="org.jetbrains.kotlin.noarg.diagnostic.DefaultErrorMessagesNoArg"/>
<completionExtension implementation="org.jetbrains.kotlin.idea.properties.PropertyKeyCompletion"/>
<newFileHook implementation="org.jetbrains.kotlin.idea.configuration.NewKotlinFileConfigurationHook"/>
<quickFixContributor implementation="org.jetbrains.kotlin.idea.quickfix.JvmQuickFixRegistrar"/>
<clearBuildState implementation="org.jetbrains.kotlin.idea.compiler.configuration.ClearBuildManagerState"/>
<facetValidatorCreator implementation="org.jetbrains.kotlin.idea.facet.KotlinLibraryValidatorCreator"/>
<gradleFrameworkSupport implementation="org.jetbrains.kotlin.gradle.kdsl.frameworkSupport.GradleJavaFrameworkSupportProvider" />
<gradleFrameworkSupport implementation="org.jetbrains.kotlin.gradle.kdsl.frameworkSupport.GradleKotlinDSLKotlinJavaFrameworkSupportProvider" />
<gradleFrameworkSupport implementation="org.jetbrains.kotlin.gradle.kdsl.frameworkSupport.GradleKotlinDSLKotlinJSFrameworkSupportProvider" />
<gradleFrameworkSupport implementation="org.jetbrains.kotlin.gradle.kdsl.frameworkSupport.GradleGroovyFrameworkSupportProvider" />
<idePlatformKind implementation="org.jetbrains.kotlin.platform.impl.JvmIdePlatformKind"/>
<idePlatformKind implementation="org.jetbrains.kotlin.platform.impl.JsIdePlatformKind"/>
<idePlatformKindTooling implementation="org.jetbrains.kotlin.idea.core.platform.impl.JvmIdePlatformKindTooling"/>
<idePlatformKindTooling implementation="org.jetbrains.kotlin.idea.core.platform.impl.JsIdePlatformKindTooling"/>
<syntheticScopeProviderExtension implementation="org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldSyntheticScopeProvider"/>
<expressionCodegenExtension implementation="org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldExpressionCodegenExtension"/>
<completionInformationProvider implementation="org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldCompletionInformationProvider" />
</extensions>
</idea-plugin>
@@ -0,0 +1,24 @@
package asyncFunctions
suspend fun main() {
val a = 5
val b = 7
none()
foo()
}
suspend fun foo() {
val x = "foo"
none()
bar()
}
suspend fun bar() {
var y = "zoo"
none()
val z = "bar"
//Breakpoint!
val a = 5
}
suspend fun none() {}
@@ -0,0 +1,15 @@
LineBreakpoint created at asyncFunctions.kt:21
Run Java
Connected to the target VM
asyncFunctions.kt:21
Async stack trace:
asyncFunctions.AsyncFunctionsKt:18
y = "zoo"
asyncFunctions.AsyncFunctionsKt:13
x = "foo"
asyncFunctions.AsyncFunctionsKt:7
a = 5
b = 7
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,20 @@
package asyncLambdas
suspend fun main() {
foo()
}
val foo: suspend () -> Unit = {
val a = 5
bar()
}
val bar: suspend () -> Unit = {
val b = 3
baz()
}
val baz: suspend () -> Unit = {
//Breakpoint!
val a = 5
}
@@ -0,0 +1,12 @@
LineBreakpoint created at asyncLambdas.kt:19
Run Java
Connected to the target VM
asyncLambdas.kt:19
Async stack trace:
asyncLambdas.AsyncLambdasKt$bar$1:14
b = 3
asyncLambdas.AsyncLambdasKt$foo$1:9
a = 5
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,10 @@
package asyncSimple
suspend fun main() {
val a = 5
foo()
//Breakpoint!
val b = a
}
suspend fun foo() {}
@@ -0,0 +1,10 @@
LineBreakpoint created at asyncSimple.kt:7
Run Java
Connected to the target VM
asyncSimple.kt:7
Async stack trace:
asyncSimple.AsyncSimpleKt:5
a = 5
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,109 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.execution.process.ProcessOutputTypes
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.getSafe
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.reflect.Modifier
abstract class AbstractAsyncStackTraceTest : KotlinDebuggerTestBase() {
private companion object {
const val MARGIN = " "
// Absent in 182, should be AsyncStackTraceProvider.EP.name
const val ASYNC_STACKTRACE_EP_NAME = "com.intellij.debugger.asyncStackTraceProvider"
}
protected fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path))
configureSettings(fileText)
createAdditionalBreakpoints(fileText)
createDebugProcess(path)
val area = Extensions.getArea(null)
if (!area.hasExtensionPoint(ASYNC_STACKTRACE_EP_NAME)) {
System.err.println("$ASYNC_STACKTRACE_EP_NAME extension point is not found (probably old IDE version)")
finish()
return
}
val extensionPoint = area.getExtensionPoint<Any>(ASYNC_STACKTRACE_EP_NAME)
val asyncStackTraceProvider = extensionPoint.extensions.firstIsInstanceOrNull<KotlinCoroutinesAsyncStackTraceProvider>()
?: run {
System.err.println("Kotlin coroutine async stack trace provider is not found")
finish()
return
}
doOnBreakpoint {
val frameProxy = this.frameProxy
if (frameProxy != null) {
try {
val stackTrace = asyncStackTraceProvider.getAsyncStackTrace(frameProxy, this)
if (stackTrace != null && stackTrace.isNotEmpty()) {
print(renderAsyncStackTrace(stackTrace), ProcessOutputTypes.SYSTEM)
} else {
println("No async stack trace available", ProcessOutputTypes.SYSTEM)
}
} catch (e: Throwable) {
val stackTrace = e.stackTraceAsString()
System.err.println("Exception occurred on calculating async stack traces: $stackTrace")
throw e
}
} else {
println("FrameProxy is 'null', can't calculate async stack trace", ProcessOutputTypes.SYSTEM)
}
resume(this)
}
}
private fun Throwable.stackTraceAsString(): String {
val writer = StringWriter()
printStackTrace(PrintWriter(writer))
return writer.toString()
}
private fun renderAsyncStackTrace(trace: List<StackFrameItem>) = buildString {
appendln("Async stack trace:")
for (item in trace) {
append(MARGIN).appendln(item.toString())
@Suppress("UNCHECKED_CAST")
val variablesField = item.javaClass.declaredFields.first { !Modifier.isStatic(it.modifiers) && it.type == List::class.java }
val variables = variablesField.getSafe(item) as? List<JavaValue>
if (variables != null) {
for (variable in variables) {
val descriptor = variable.descriptor
val name = descriptor.calcValueName()
val value = descriptor.calcValue(evaluationContext)
append(MARGIN).append(MARGIN).append(name).append(" = ").appendln(value)
}
}
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.debugger;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/debugger/tinyApp/src/asyncStackTrace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class AsyncStackTraceTestGenerated extends AbstractAsyncStackTraceTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInAsyncStackTrace() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/asyncStackTrace"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("asyncFunctions.kt")
public void testAsyncFunctions() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/asyncStackTrace/asyncFunctions.kt");
}
@TestMetadata("asyncLambdas.kt")
public void testAsyncLambdas() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/asyncStackTrace/asyncLambdas.kt");
}
@TestMetadata("asyncSimple.kt")
public void testAsyncSimple() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/asyncStackTrace/asyncSimple.kt");
}
}