Refactoring: Incapsulate method calls into ExecutionContext

This commit is contained in:
Yan Zhulanow
2019-03-07 17:31:09 +03:00
parent 5d156c2edb
commit 6d90e850ff
11 changed files with 179 additions and 165 deletions
@@ -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)
}
}
@@ -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? {
@@ -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
}
@@ -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
}
}
@@ -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
@@ -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)
}
@@ -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
@@ -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)
}
@@ -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)
@@ -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 {