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
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+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
|
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) {
|
||||||
|
|||||||
+4
-2
@@ -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? {
|
||||||
|
|||||||
+2
-2
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user