Debugger: Call invokeMethod() safely, through the execution context (KT-30268, EA-131589)
This commit is contained in:
+35
-24
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+4
-2
@@ -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? {
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user