Debugger: Display async stack trace elements for suspend callers (KT-28728)
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
+201
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user