From e79ee1ba8ef5a702a0ad558b764527ea076f81bc Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 13 Mar 2019 18:42:38 +0300 Subject: [PATCH] Use caching 'findClass()' instead of raw 'Class.forName()' call in async stack trace provider (KT-30268) --- ...KotlinCoroutinesAsyncStackTraceProvider.kt | 168 +++++++----------- 1 file changed, 67 insertions(+), 101 deletions(-) diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt index 6400ab2df07..971a4922588 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt @@ -6,7 +6,6 @@ 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 @@ -14,42 +13,18 @@ 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 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.ExecutionContext import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase { private companion object { const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt" - fun ContextBase.classByName(name: String): ReferenceType? { - val classClass = virtualMachine.classesByName(Class::class.java.name).firstIsInstanceOrNull() ?: return null - 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 = debugProcess.invokeMethod(evaluationContext, classClass, forNameMethod, args) - - 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 @@ -73,19 +48,22 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP 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) - return context.getAsyncStackTraceForSuspendLambda() ?: context.getAsyncStackTraceForSuspendFunction() + val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy) + val context = ExecutionContext(evaluationContext, frameProxy) + + val debugMetadataKtType = context.findClass(DEBUG_METADATA_KT) as? ClassType ?: return null + + val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType) + return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction() } - private fun Context.getAsyncStackTraceForSuspendLambda(): List? { + private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List? { if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") { return null } - val thisObject = frameProxy.thisObject() ?: return null + val thisObject = context.frameProxy.thisObject() ?: return null val thisType = thisObject.referenceType() if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) { @@ -95,24 +73,25 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP return collectFrames(thisObject) } - private fun Context.getAsyncStackTraceForSuspendFunction(): List? { + private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List? { if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) { return null } + val frameProxy = context.frameProxy val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null return collectFrames(continuation) } - private fun Context.collectFrames(continuation: ObjectReference): List? { + private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List? { val frames = mutableListOf() collectFramesRecursively(continuation, frames) return frames } - private fun Context.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList) { + private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList) { val continuationType = continuation.referenceType() as? ClassType ?: return val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return @@ -128,87 +107,74 @@ class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceP collectFramesRecursively(completion, consumer) } - private open class ContextBase(val suspendContext: SuspendContextImpl, val frameProxy: StackFrameProxyImpl) { - val virtualMachine: VirtualMachineProxyImpl - get() = frameProxy.virtualMachine + private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? { + val getStackTraceElementMethod = debugMetadataKtType.methodsByName( + "getStackTraceElement", + "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;" + ).firstOrNull() ?: return null - val debugProcess: DebugProcessImpl - get() = suspendContext.debugProcess + val args = listOf(continuation) - val evaluationContext: EvaluationContextImpl by lazy { EvaluationContextImpl(suspendContext, frameProxy) } - } + val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference + ?: return null - 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", - "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;" - ).firstOrNull() ?: return null + val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name } + ?: return null - val args = listOf(continuation) - - 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 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) ?: return null - return GeneratedLocation(suspendContext.debugProcess, locationClass, methodName, lineNumber) + fun getValue(name: String, desc: String): Value? { + val method = stackTraceElementType.methodsByName(name, desc).single() + return context.invokeMethod(stackTraceElement, method, emptyList()) } - fun getSpilledVariables(continuation: ObjectReference): List? { - val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName( - "getSpilledVariableFieldMapping", - "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;" - ).firstOrNull() ?: 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 lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null - val args = listOf(continuation) + val locationClass = context.findClass(className) ?: return null + return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber) + } - val rawSpilledVariables = debugProcess - .invokeMethod(evaluationContext, debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference - ?: return null + private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List? { + val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName( + "getSpilledVariableFieldMapping", + "(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;" + ).firstOrNull() ?: return null - val length = rawSpilledVariables.length() / 2 - val spilledVariables = ArrayList(length) + val args = listOf(continuation) - 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 rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference + ?: return null - val project = suspendContext.debugProcess.project + val length = rawSpilledVariables.length() / 2 + val spilledVariables = ArrayList(length) - 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") - } + 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 - spilledVariables += JavaValue.create( - null, - valueDescriptor, - evaluationContext, - suspendContext.debugProcess.xdebugProcess!!.nodeManager, - false - ) + val valueDescriptor = object : ValueDescriptorImpl(context.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") } - return spilledVariables + spilledVariables += JavaValue.create( + null, + valueDescriptor, + context.evaluationContext, + context.debugProcess.xdebugProcess!!.nodeManager, + false + ) } + + return spilledVariables } + + private class AsyncStackTraceContext( + val context: ExecutionContext, + val method: Method, + val debugMetadataKtType: ClassType + ) } \ No newline at end of file