Refactoring: Incapsulate method calls into ExecutionContext
This commit is contained in:
@@ -6,49 +6,83 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class ExecutionContext(val evaluationContext: EvaluationContextImpl, val thread: ThreadReference, val invokePolicy: Int) {
|
||||
val vm: VirtualMachine
|
||||
get() = thread.virtualMachine()
|
||||
class ExecutionContext(val evaluationContext: EvaluationContextImpl, val frameProxy: StackFrameProxyImpl) {
|
||||
val vm: VirtualMachineProxyImpl
|
||||
get() = evaluationContext.debugProcess.virtualMachineProxy
|
||||
|
||||
val classLoader: ClassLoaderReference?
|
||||
get() = evaluationContext.classLoader
|
||||
|
||||
val suspendContext: SuspendContextImpl
|
||||
get() = evaluationContext.suspendContext
|
||||
|
||||
val debugProcess: DebugProcessImpl
|
||||
get() = evaluationContext.debugProcess
|
||||
|
||||
fun loadClassType(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
if (asmType.sort == Type.ARRAY) {
|
||||
return loadClassType(asmType.elementType, classLoader)
|
||||
}
|
||||
val project: Project
|
||||
get() = evaluationContext.project
|
||||
|
||||
if (asmType.sort != Type.OBJECT) {
|
||||
return null
|
||||
}
|
||||
val invokePolicy = evaluationContext.suspendContext.getInvokePolicy()
|
||||
|
||||
val vm = thread.virtualMachine()
|
||||
val className = asmType.className
|
||||
|
||||
val classClass = vm.classesByName(Class::class.java.name).firstIsInstanceOrNull<ClassType>() ?: return null
|
||||
|
||||
val method: Method?
|
||||
val args: List<Value?>
|
||||
|
||||
if (classLoader != null) {
|
||||
method = classClass.methodsByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;").firstOrNull()
|
||||
args = listOf(vm.mirrorOf(className), vm.mirrorOf(true), classLoader)
|
||||
} else {
|
||||
method = classClass.methodsByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;").firstOrNull()
|
||||
args = listOf(vm.mirrorOf(className))
|
||||
}
|
||||
|
||||
if (method == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val result = evaluationContext.debugProcess.invokeMethod(evaluationContext, classClass, method, args)
|
||||
return (result as? ClassObjectReference)?.reflectedType()
|
||||
@Throws(EvaluateException::class)
|
||||
fun invokeMethod(obj: ObjectReference, method: Method, args: List<Value?>): Value? {
|
||||
return debugProcess.invokeInstanceMethod(evaluationContext, obj, method, args, invokePolicy)
|
||||
}
|
||||
|
||||
fun invokeMethod(type: ClassType, method: Method, args: List<Value?>): Value? {
|
||||
return debugProcess.invokeMethod(evaluationContext, type, method, args)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun newInstance(type: ClassType, constructor: Method, args: List<Value?>): ObjectReference {
|
||||
return debugProcess.newInstance(evaluationContext, type, constructor, args)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun newInstance(arrayType: ArrayType, dimension: Int): ArrayReference {
|
||||
return debugProcess.newInstance(arrayType, dimension)
|
||||
}
|
||||
|
||||
@Throws(
|
||||
InvocationException::class,
|
||||
ClassNotLoadedException::class,
|
||||
IncompatibleThreadStateException::class,
|
||||
InvalidTypeException::class,
|
||||
EvaluateException::class
|
||||
)
|
||||
fun loadClass(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
return loadClass(asmType.className, classLoader)
|
||||
}
|
||||
|
||||
@Throws(
|
||||
InvocationException::class,
|
||||
ClassNotLoadedException::class,
|
||||
IncompatibleThreadStateException::class,
|
||||
InvalidTypeException::class,
|
||||
EvaluateException::class
|
||||
)
|
||||
fun loadClass(name: String, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
return debugProcess.loadClass(evaluationContext, name, classLoader)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun findClass(name: String, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
return debugProcess.findClass(evaluationContext, name, classLoader)
|
||||
}
|
||||
|
||||
fun keepReference(reference: ObjectReference) {
|
||||
// Not available in older IDEA versions
|
||||
@Suppress("DEPRECATION")
|
||||
DebuggerUtilsEx.keep(reference, evaluationContext)
|
||||
}
|
||||
}
|
||||
+38
-27
@@ -132,8 +132,19 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
?: evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
|
||||
|
||||
val operatingThread = context.suspendContext.thread
|
||||
?: evaluationException("Cannot evaluate a code fragment: thread is not available")
|
||||
|
||||
if (!operatingThread.isSuspended) {
|
||||
evaluationException("Evaluation is available only for the suspended threads")
|
||||
}
|
||||
|
||||
try {
|
||||
return evaluateSafe(context)
|
||||
val executionContext = ExecutionContext(context, frameProxy)
|
||||
return evaluateSafe(executionContext)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: ProcessCanceledException) {
|
||||
@@ -168,37 +179,33 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateSafe(context: EvaluationContextImpl): Any? {
|
||||
private fun evaluateSafe(context: ExecutionContext): Any? {
|
||||
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
|
||||
|
||||
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val thread = context.suspendContext.thread?.threadReference ?: error("Can not find a thread to run evaluation on")
|
||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||
val executionContext = ExecutionContext(context, thread, invokePolicy)
|
||||
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(executionContext, compiledData, classLoaderRef)
|
||||
?: evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
|
||||
evaluateWithCompilation(context, compiledData, classLoaderRef)
|
||||
?: evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
} else {
|
||||
evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
|
||||
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
}
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
|
||||
return evaluateWithEval4J(executionContext, recompiledData, classLoaderRef).toJdiValue(executionContext)
|
||||
return evaluateWithEval4J(context, recompiledData, classLoaderRef).toJdiValue(context)
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is InterpreterResult -> result.toJdiValue(executionContext)
|
||||
is InterpreterResult -> result.toJdiValue(context)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCodeFragment(evaluationContext: EvaluationContextImpl): CompiledDataDescriptor {
|
||||
val debugProcess = evaluationContext.debugProcess
|
||||
private fun compileCodeFragment(context: ExecutionContext): CompiledDataDescriptor {
|
||||
val debugProcess = context.debugProcess
|
||||
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
|
||||
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||
@@ -215,7 +222,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
|
||||
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||
|
||||
val result = CodeFragmentCompiler(evaluationContext).compile(codeFragment, bindingContext, moduleDescriptor)
|
||||
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
|
||||
return CompiledDataDescriptor.from(result, sourcePosition)
|
||||
}
|
||||
|
||||
@@ -285,10 +292,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
): Value? {
|
||||
return try {
|
||||
runEvaluation(context, compiledData, classLoader) { args ->
|
||||
val mainClassType = context.loadClassType(Type.getObjectType(GENERATED_CLASS_NAME), classLoader) as? ClassType
|
||||
val mainClassType = context.loadClass(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 = context.debugProcess.invokeMethod(context.evaluationContext, mainClassType, mainMethod, args)
|
||||
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
|
||||
EvaluatorValueConverter(context).unref(returnValue)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -307,7 +314,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
|
||||
val eval = JDIEval(context.vm, classLoader, context.thread, context.invokePolicy)
|
||||
val vm = context.vm.virtualMachine
|
||||
val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isAtBreakpoint }
|
||||
?: error("Can not find a thread to run evaluation on")
|
||||
|
||||
val eval = JDIEval(vm, classLoader, thread, context.invokePolicy)
|
||||
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
|
||||
}
|
||||
}
|
||||
@@ -321,24 +332,24 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
// Preload additional classes
|
||||
compiledData.classes
|
||||
.filter { !it.isMainClass }
|
||||
.forEach { context.loadClassType(Type.getObjectType(it.className), classLoader) }
|
||||
.forEach { context.loadClass(Type.getObjectType(it.className), classLoader) }
|
||||
|
||||
return context.vm.executeWithBreakpointsDisabled {
|
||||
return context.vm.virtualMachine.executeWithBreakpointsDisabled {
|
||||
for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
|
||||
context.loadClassType(parameterType, classLoader)
|
||||
context.loadClass(parameterType, classLoader)
|
||||
}
|
||||
val args = context.calculateMainMethodCallArguments(compiledData)
|
||||
val args = calculateMainMethodCallArguments(context, compiledData)
|
||||
block(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ExecutionContext.calculateMainMethodCallArguments(compiledData: CompiledDataDescriptor): List<Value?> {
|
||||
private fun calculateMainMethodCallArguments(context: ExecutionContext, compiledData: CompiledDataDescriptor): List<Value?> {
|
||||
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
|
||||
val valueParameters = compiledData.parameters
|
||||
require(asmValueParameters.size == valueParameters.size)
|
||||
|
||||
val args = valueParameters.zip(asmValueParameters)
|
||||
val variableFinder = VariableFinder.instance(this) ?: error("Frame map is not available")
|
||||
val variableFinder = VariableFinder(context)
|
||||
|
||||
return args.map { (parameter, asmType) ->
|
||||
val result = variableFinder.find(parameter, asmType)
|
||||
@@ -347,7 +358,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
val name = parameter.debugString
|
||||
|
||||
fun isInsideDefaultInterfaceMethod(): Boolean {
|
||||
val method = evaluationContext.frameProxy?.safeLocation()?.safeMethod() ?: return false
|
||||
val method = context.frameProxy.safeLocation()?.safeMethod() ?: return false
|
||||
val desc = method.signature()
|
||||
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
|
||||
}
|
||||
@@ -359,7 +370,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
|
||||
evaluationException("Parameter evaluation is not supported for '\$default' methods")
|
||||
} else {
|
||||
throw VariableFinder.variableNotFound(evaluationContext, buildString {
|
||||
throw VariableFinder.variableNotFound(context, buildString {
|
||||
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
|
||||
})
|
||||
}
|
||||
@@ -376,7 +387,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
|
||||
private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER)
|
||||
|
||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
|
||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): Value? {
|
||||
val jdiValue = when (this) {
|
||||
is ValueReturned -> result
|
||||
is ExceptionThrown -> {
|
||||
@@ -394,7 +405,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
}
|
||||
|
||||
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm, jdiValue.asmType)
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm.virtualMachine, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
|
||||
|
||||
+8
-13
@@ -16,31 +16,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
|
||||
protected fun dex(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ByteArray? {
|
||||
protected fun dex(context: ExecutionContext, classes: Collection<ClassToLoad>): ByteArray? {
|
||||
return AndroidDexer.getInstances(context.project).single().dex(classes)
|
||||
}
|
||||
|
||||
protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference {
|
||||
val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType
|
||||
protected fun wrapToByteBuffer(bytes: ArrayReference, context: ExecutionContext): ObjectReference {
|
||||
val classLoader = context.classLoader
|
||||
val byteBufferClass = context.findClass("java.nio.ByteBuffer", classLoader) as ClassType
|
||||
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
|
||||
?: error("'wrap' method not found")
|
||||
|
||||
return process.invokeMethod(context, byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
|
||||
return context.invokeMethod(byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
|
||||
}
|
||||
|
||||
protected fun DebugProcessImpl.tryLoadClass(
|
||||
context: EvaluationContextImpl,
|
||||
fqName: String,
|
||||
classLoader: ClassLoaderReference?
|
||||
): ReferenceType? {
|
||||
protected fun tryLoadClass(context: ExecutionContext, fqName: String, classLoader: ClassLoaderReference?): ReferenceType? {
|
||||
return try {
|
||||
loadClass(context, fqName, classLoader)
|
||||
context.loadClass(fqName, classLoader)
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
+13
-18
@@ -18,44 +18,39 @@ package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.JVMNameUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
||||
override fun isApplicable(context: EvaluationContextImpl, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
|
||||
isCompilingEvaluatorPreferred && context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
|
||||
private fun resolveClassLoaderClass(context: ExecutionContext): ClassType? {
|
||||
return try {
|
||||
context.debugProcess.tryLoadClass(
|
||||
context, "dalvik.system.InMemoryDexClassLoader", context.classLoader
|
||||
) as? ClassType
|
||||
val classLoader = context.classLoader
|
||||
tryLoadClass(context, "dalvik.system.InMemoryDexClassLoader", classLoader) as? ClassType
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val process = context.debugProcess
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
||||
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
||||
JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"
|
||||
) ?: error("Constructor method not found")
|
||||
|
||||
val dexBytes = dex(context, classes) ?: error("Can't dex classes")
|
||||
val dexBytesMirror = mirrorOfByteArray(dexBytes, context, process)
|
||||
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process)
|
||||
val dexBytesMirror = mirrorOfByteArray(dexBytes, context)
|
||||
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context)
|
||||
|
||||
val newClassLoader = process.newInstance(
|
||||
context, inMemoryClassLoaderClass, constructorMethod,
|
||||
listOf(dexByteBuffer, context.classLoader)
|
||||
)
|
||||
val classLoader = context.classLoader
|
||||
val args = listOf(dexByteBuffer, classLoader)
|
||||
val newClassLoader = context.newInstance(inMemoryClassLoaderClass, constructorMethod, args) as ClassLoaderReference
|
||||
context.keepReference(newClassLoader)
|
||||
|
||||
DebuggerUtilsEx.keep(newClassLoader, context)
|
||||
|
||||
return newClassLoader as ClassLoaderReference
|
||||
return newClassLoader
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -16,13 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.ArrayType
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_FUNCTION_NAME
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
@@ -38,7 +36,7 @@ interface ClassLoadingAdapter {
|
||||
OrdinaryClassLoadingAdapter()
|
||||
)
|
||||
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
|
||||
|
||||
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
|
||||
@@ -92,18 +90,19 @@ interface ClassLoadingAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
fun isApplicable(context: EvaluationContextImpl, info: ClassInfoForEvaluator): Boolean
|
||||
fun isApplicable(context: ExecutionContext, info: ClassInfoForEvaluator): Boolean
|
||||
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||
|
||||
fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
|
||||
val arrayClass = process.findClass(context, "byte[]", context.classLoader) as ArrayType
|
||||
val reference = process.newInstance(arrayClass, bytes.size)
|
||||
DebuggerUtilsEx.keep(reference, context)
|
||||
fun mirrorOfByteArray(bytes: ByteArray, context: ExecutionContext): ArrayReference {
|
||||
val classLoader = context.classLoader
|
||||
val arrayClass = context.findClass("byte[]", classLoader) as ArrayType
|
||||
val reference = context.newInstance(arrayClass, bytes.size)
|
||||
context.keepReference(reference)
|
||||
|
||||
val mirrors = ArrayList<Value>(bytes.size)
|
||||
for (byte in bytes) {
|
||||
mirrors += process.virtualMachineProxy.mirrorOf(byte)
|
||||
mirrors += context.vm.mirrorOf(byte)
|
||||
}
|
||||
|
||||
var loaded = 0
|
||||
|
||||
+14
-25
@@ -16,15 +16,12 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.ClassLoadingUtils
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.impl.watch.CompilingEvaluatorImpl
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.ClassType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
@@ -63,28 +60,28 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
return classWriter.toByteArray()
|
||||
}
|
||||
|
||||
fun useMagicAccessor(evaluationContext: EvaluationContextImpl): Boolean {
|
||||
val rawVersion = evaluationContext.debugProcess.virtualMachineProxy.version()?.substringBefore('_') ?: return false
|
||||
fun useMagicAccessor(context: ExecutionContext): Boolean {
|
||||
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
||||
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
||||
return !javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicable(context: EvaluationContextImpl, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
|
||||
isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator): Boolean {
|
||||
return info.isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val process = context.debugProcess
|
||||
|
||||
val classLoader = try {
|
||||
ClassLoadingUtils.getClassLoader(context, process)
|
||||
ClassLoadingUtils.getClassLoader(context.evaluationContext, process)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error creating evaluation class loader: $e", e)
|
||||
}
|
||||
|
||||
try {
|
||||
defineClasses(classes, context, process, classLoader)
|
||||
defineClasses(classes, context, classLoader)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during classes definition $e", e)
|
||||
}
|
||||
@@ -94,8 +91,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
|
||||
private fun defineClasses(
|
||||
classes: Collection<ClassToLoad>,
|
||||
context: EvaluationContextImpl,
|
||||
process: DebugProcessImpl,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
val classesToLoad = if (classes.size == 1) {
|
||||
@@ -110,31 +106,24 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
|
||||
for ((className, _, bytes) in classesToLoad) {
|
||||
val patchedBytes = if (useMagicAccessor(context)) changeSuperToMagicAccessor(bytes) else bytes
|
||||
defineClass(className, patchedBytes, context, process, classLoader)
|
||||
defineClass(className, patchedBytes, context, classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun defineClass(
|
||||
name: String,
|
||||
bytes: ByteArray,
|
||||
context: EvaluationContextImpl,
|
||||
process: DebugProcessImpl,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
try {
|
||||
val vm = process.virtualMachineProxy
|
||||
val vm = context.vm
|
||||
val classLoaderType = classLoader.referenceType() as ClassType
|
||||
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
|
||||
val nameObj = vm.mirrorOf(name)
|
||||
|
||||
// Still actual for older platform versions
|
||||
@Suppress("DEPRECATION")
|
||||
DebuggerUtilsEx.keep(nameObj, context)
|
||||
|
||||
process.invokeMethod(
|
||||
context, classLoader, defineMethod,
|
||||
listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
|
||||
)
|
||||
val args = listOf(nameObj, mirrorOfByteArray(bytes, context), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
|
||||
context.invokeMethod(classLoader, defineMethod, args)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during class $name definition: $e", e)
|
||||
}
|
||||
|
||||
+2
-3
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
@@ -43,7 +42,7 @@ import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class CodeFragmentCompiler(val evaluationContext: EvaluationContextImpl) {
|
||||
class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
|
||||
data class CompilationResult(
|
||||
val classes: List<ClassToLoad>,
|
||||
val parameterInfo: CodeFragmentParameterInfo,
|
||||
@@ -78,7 +77,7 @@ class CodeFragmentCompiler(val evaluationContext: EvaluationContextImpl) {
|
||||
bindingContext, listOf(codeFragment), compilerConfiguration
|
||||
).build()
|
||||
|
||||
val parameterInfo = CodeFragmentParameterAnalyzer(evaluationContext, codeFragment, bindingContext).analyze()
|
||||
val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze()
|
||||
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
|
||||
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
|
||||
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
|
||||
|
||||
+5
-5
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
||||
@@ -15,6 +14,7 @@ import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
@@ -57,8 +57,8 @@ interface CodeFragmentParameter {
|
||||
}
|
||||
|
||||
class CodeFragmentParameterInfo(
|
||||
val parameters: List<CodeFragmentParameter.Smart>,
|
||||
val crossingBounds: Set<CodeFragmentParameter.Dumb>
|
||||
val parameters: List<Smart>,
|
||||
val crossingBounds: Set<Dumb>
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -66,7 +66,7 @@ class CodeFragmentParameterInfo(
|
||||
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
|
||||
*/
|
||||
class CodeFragmentParameterAnalyzer(
|
||||
private val evaluationContext: EvaluationContextImpl,
|
||||
private val context: ExecutionContext,
|
||||
private val codeFragment: KtCodeFragment,
|
||||
private val bindingContext: BindingContext
|
||||
) {
|
||||
@@ -76,7 +76,7 @@ class CodeFragmentParameterAnalyzer(
|
||||
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
|
||||
|
||||
private val containingPrimaryConstructor: ConstructorDescriptor? by lazy {
|
||||
evaluationContext.frameProxy?.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
|
||||
context.frameProxy.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
|
||||
val constructor = codeFragment.context?.getParentOfType<KtPrimaryConstructor>(false) ?: return@lazy null
|
||||
bindingContext[BindingContext.CONSTRUCTOR, constructor]
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
|
||||
fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
return try {
|
||||
loadClasses(evaluationContext, classes)
|
||||
loadClasses(context, classes)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
@@ -34,10 +34,10 @@ fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collect
|
||||
}
|
||||
}
|
||||
|
||||
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
if (classes.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)
|
||||
return ClassLoadingAdapter.loadClasses(context, classes)
|
||||
}
|
||||
+9
-13
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
|
||||
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
@@ -18,7 +17,7 @@ import com.sun.jdi.Type as JdiType
|
||||
import kotlin.jvm.internal.Ref
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||
class EvaluatorValueConverter(private val context: ExecutionContext) {
|
||||
private companion object {
|
||||
private val UNBOXING_METHOD_NAMES = mapOf(
|
||||
"java/lang/Boolean" to "booleanValue",
|
||||
@@ -84,7 +83,7 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||
private fun coerceBoxing(value: Value?, type: AsmType): Result? {
|
||||
when {
|
||||
value == null -> return Result(value)
|
||||
type == AsmType.VOID_TYPE -> return Result(executionContext.vm.mirrorOfVoid())
|
||||
type == AsmType.VOID_TYPE -> return Result(context.vm.mirrorOfVoid())
|
||||
type.isBoxedType -> {
|
||||
if (value.asmType().isBoxedType) {
|
||||
return Result(value)
|
||||
@@ -131,14 +130,13 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||
val unboxedType = value.asmType()
|
||||
val boxedType = box(unboxedType)
|
||||
|
||||
val boxedTypeClass = (executionContext.loadClassType(boxedType) as ClassType?)
|
||||
val boxedTypeClass = (context.loadClass(boxedType) as ClassType?)
|
||||
?: error("Class $boxedType is not loaded")
|
||||
|
||||
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
|
||||
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
|
||||
|
||||
val debugProcess = executionContext.evaluationContext.debugProcess
|
||||
return debugProcess.invokeMethod(executionContext.evaluationContext, boxedTypeClass, valueOfMethod, listOf(value))
|
||||
return context.invokeMethod(boxedTypeClass, valueOfMethod, listOf(value))
|
||||
}
|
||||
|
||||
private fun unbox(value: Value?): Value? {
|
||||
@@ -153,7 +151,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 executionContext.debugProcess.invokeMethod(executionContext.evaluationContext, value, valueMethod, emptyList())
|
||||
return context.invokeMethod(value, valueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun ref(value: Value?): Value? {
|
||||
@@ -163,10 +161,8 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||
|
||||
fun wrapRef(value: Value?, refTypeClass: ClassType): Value? {
|
||||
val constructor = refTypeClass.methods().single { it.isConstructor }
|
||||
val ref = refTypeClass.newInstance(executionContext.thread, constructor, emptyList(), executionContext.invokePolicy)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
DebuggerUtilsEx.keep(ref, executionContext.evaluationContext)
|
||||
val ref = context.newInstance(refTypeClass, constructor, emptyList())
|
||||
context.keepReference(ref)
|
||||
|
||||
val elementField = refTypeClass.fieldByName("element") ?: error("'element' field not found")
|
||||
ref.setValue(elementField, value)
|
||||
@@ -177,13 +173,13 @@ class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||
val primitiveType = value.asmType()
|
||||
val refType = PRIMITIVE_TO_REF.getValue(primitiveType)
|
||||
|
||||
val refTypeClass = (executionContext.loadClassType(refType) as ClassType?)
|
||||
val refTypeClass = (context.loadClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
} else {
|
||||
val refType = AsmType.getType(Ref.ObjectRef::class.java)
|
||||
val refTypeClass = (executionContext.loadClassType(refType) as ClassType?)
|
||||
val refTypeClass = (context.loadClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
|
||||
+8
-12
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
@@ -33,7 +32,9 @@ import kotlin.coroutines.Continuation
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import com.sun.jdi.Type as JdiType
|
||||
|
||||
class VariableFinder private constructor(private val context: ExecutionContext, private val frameProxy: StackFrameProxyImpl) {
|
||||
class VariableFinder(private val context: ExecutionContext) {
|
||||
private val frameProxy = context.frameProxy
|
||||
|
||||
companion object {
|
||||
private const val USE_UNSAFE_FALLBACK = true
|
||||
|
||||
@@ -44,14 +45,9 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
||||
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
||||
)
|
||||
|
||||
fun instance(context: ExecutionContext): VariableFinder? {
|
||||
val frameProxy = context.evaluationContext.frameProxy ?: return null
|
||||
return VariableFinder(context, frameProxy)
|
||||
}
|
||||
|
||||
fun variableNotFound(context: EvaluationContextImpl, message: String): Exception {
|
||||
fun variableNotFound(context: ExecutionContext, message: String): Exception {
|
||||
val frameProxy = context.frameProxy
|
||||
val location = frameProxy?.safeLocation()
|
||||
val location = frameProxy.safeLocation()
|
||||
val scope = context.debugProcess.searchScope
|
||||
|
||||
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
|
||||
@@ -297,7 +293,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
||||
}
|
||||
|
||||
private fun findDebugLabel(name: String): Result? {
|
||||
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.evaluationContext.debugProcess)
|
||||
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.debugProcess)
|
||||
|
||||
for ((value, markup) in markupMap) {
|
||||
if (markup.text == name) {
|
||||
@@ -408,7 +404,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
||||
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||
?: return null
|
||||
|
||||
return context.debugProcess.invokeMethod(context.evaluationContext, continuation, getContextMethod, emptyList()) as? ObjectReference
|
||||
return context.invokeMethod(continuation, getContextMethod, emptyList()) as? ObjectReference
|
||||
}
|
||||
|
||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||
@@ -482,7 +478,7 @@ class VariableFinder private constructor(private val context: ExecutionContext,
|
||||
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
|
||||
?: return rawValue
|
||||
|
||||
return context.debugProcess.invokeMethod(context.evaluationContext, delegateValue, getValueMethod, emptyList())
|
||||
return context.invokeMethod(delegateValue, getValueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun isCapturedReceiverFieldName(name: String): Boolean {
|
||||
|
||||
Reference in New Issue
Block a user