Debugger: Call invokeMethod() safely, through the execution context (KT-30268, EA-131589)

This commit is contained in:
Yan Zhulanow
2019-03-05 01:11:13 +03:00
parent 9d176917e6
commit 3315c1c35b
5 changed files with 48 additions and 30 deletions
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.SuspendContextImpl
@@ -13,6 +14,7 @@ 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.jdi.VirtualMachineProxyImpl
import com.intellij.debugger.memory.utils.StackFrameItem
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.xdebugger.frame.XNamedValue
@@ -20,21 +22,21 @@ import com.sun.jdi.*
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
private companion object {
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
fun classByName(name: String, frame: StackFrameProxyImpl, threadReference: ThreadReference, invokePolicy: Int): ReferenceType? {
val virtualMachine = frame.virtualMachine
fun ContextBase.classByName(name: String): ReferenceType? {
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;" }
val forNameMethod = classClass.methodsByName("forName")
.firstOrNull { it.signature() == "(Ljava/lang/String;)Ljava/lang/Class;" }
?: return null
try {
val args = listOf(virtualMachine.mirrorOf(name))
val result = classClass.invokeMethod(threadReference, forNameMethod, args, invokePolicy)
val result = debugProcess.invokeMethod(evaluationContext, classClass, forNameMethod, args)
if (result is ClassObjectReference) {
return result.reflectedType()
@@ -64,11 +66,14 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
fun getAsyncStackTrace(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
val location = frameProxy.location()
val method = location.safeMethod() ?: return null
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended && it.isAtBreakpoint } ?: return null
val invokePolicy = suspendContext.getInvokePolicy()
val debugMetadataKtType = classByName(DEBUG_METADATA_KT, frameProxy, threadReference, invokePolicy) as? ClassType ?: return null
val currentThread = frameProxy.threadProxy().threadReference
if (currentThread == null || !currentThread.isSuspended || !currentThread.isAtBreakpoint) {
return null
}
val contextBase = ContextBase(suspendContext, frameProxy)
val debugMetadataKtType = contextBase.classByName(DEBUG_METADATA_KT) as? ClassType ?: return null
val context = Context(suspendContext, frameProxy, method, debugMetadataKtType, threadReference, invokePolicy)
val context = Context(suspendContext, frameProxy, method, debugMetadataKtType)
return context.getAsyncStackTraceForSuspendLambda() ?: context.getAsyncStackTraceForSuspendFunction()
}
@@ -120,16 +125,20 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
collectFramesRecursively(completion, consumer)
}
private class Context(
val suspendContext: SuspendContextImpl,
val frameProxy: StackFrameProxyImpl,
val method: Method,
private val debugMetadataKtType: ClassType,
private val threadReference: ThreadReference,
private val invokePolicy: Int
) {
val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) }
private open class ContextBase(val suspendContext: SuspendContextImpl, val frameProxy: StackFrameProxyImpl) {
val virtualMachine: VirtualMachineProxyImpl
get() = frameProxy.virtualMachine
val debugProcess: DebugProcessImpl
get() = suspendContext.debugProcess
val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) }
}
private class Context(
suspendContext: SuspendContextImpl, frameProxy: StackFrameProxyImpl,
val method: Method, private val debugMetadataKtType: ClassType
) : ContextBase(suspendContext, frameProxy) {
fun getLocation(continuation: ObjectReference): Location? {
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
"getStackTraceElement",
@@ -138,22 +147,23 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
val args = listOf(continuation)
val stackTraceElement = debugMetadataKtType
.invokeMethod(threadReference, getStackTraceElementMethod, args, invokePolicy) as? ObjectReference ?: return null
val stackTraceElement = debugProcess
.invokeMethod(evaluationContext, debugMetadataKtType, getStackTraceElementMethod, args) 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)
return debugProcess.invokeMethod(evaluationContext, stackTraceElement, method, emptyList())
}
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, threadReference, invokePolicy) ?: return null
val locationClass = classByName(className) ?: return null
return GeneratedLocation(suspendContext.debugProcess, locationClass, methodName, lineNumber)
}
@@ -165,8 +175,9 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP
val args = listOf(continuation)
val rawSpilledVariables = debugMetadataKtType
.invokeMethod(threadReference, getSpilledVariableFieldMappingMethod, args, invokePolicy) as? ArrayReference ?: return null
val rawSpilledVariables = debugProcess
.invokeMethod(evaluationContext, debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
?: return null
val length = rawSpilledVariables.length() / 2
val spilledVariables = ArrayList<XNamedValue>(length)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.sun.jdi.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -14,6 +15,9 @@ class ExecutionContext(val evaluationContext: EvaluationContextImpl, val thread:
val vm: VirtualMachine
get() = thread.virtualMachine()
val debugProcess: DebugProcessImpl
get() = evaluationContext.debugProcess
fun loadClassType(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? {
if (asmType.sort == Type.ARRAY) {
return loadClassType(asmType.elementType, classLoader)
@@ -43,7 +47,8 @@ class ExecutionContext(val evaluationContext: EvaluationContextImpl, val thread:
return null
}
return (classClass.invokeMethod(thread, method, args, invokePolicy) as? ClassObjectReference)?.reflectedType()
val result = evaluationContext.debugProcess.invokeMethod(evaluationContext, classClass, method, args)
return (result as? ClassObjectReference)?.reflectedType()
}
}
@@ -288,7 +288,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val mainClassType = context.loadClassType(Type.getObjectType(GENERATED_CLASS_NAME), classLoader) as? ClassType
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
val returnValue = mainClassType.invokeMethod(context.thread, mainMethod, args, context.invokePolicy)
val returnValue = context.debugProcess.invokeMethod(context.evaluationContext, mainClassType, mainMethod, args)
EvaluatorValueConverter(context).unref(returnValue)
}
} catch (e: Throwable) {
@@ -136,7 +136,9 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
return boxedTypeClass.invokeMethod(executionContext.thread, valueOfMethod, listOf(value), executionContext.invokePolicy)
val debugProcess = executionContext.evaluationContext.debugProcess
return debugProcess.invokeMethod(executionContext.evaluationContext, boxedTypeClass, valueOfMethod, listOf(value))
}
private fun unbox(value: Value?): Value? {
@@ -151,7 +153,7 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
val unboxingMethodName = UNBOXING_METHOD_NAMES.getValue(boxedType.internalName)
val methodDesc = AsmType.getMethodDescriptor(unboxedType)
val valueMethod = boxedTypeClass.methodsByName(unboxingMethodName, methodDesc).first()
return value.invokeMethod(executionContext.thread, valueMethod, emptyList(), executionContext.invokePolicy)
return executionContext.debugProcess.invokeMethod(executionContext.evaluationContext, value, valueMethod, emptyList())
}
private fun ref(value: Value?): Value? {
@@ -408,7 +408,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
?: return null
return continuation.invokeMethod(context.thread, getContextMethod, emptyList(), context.invokePolicy) as? ObjectReference
return context.debugProcess.invokeMethod(context.evaluationContext, continuation, getContextMethod, emptyList()) as? ObjectReference
}
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
@@ -482,7 +482,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
?: return rawValue
return delegateValue.invokeMethod(context.thread, getValueMethod, emptyList(), context.invokePolicy)
return context.debugProcess.invokeMethod(context.evaluationContext, delegateValue, getValueMethod, emptyList())
}
private fun isCapturedReceiverFieldName(name: String): Boolean {