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