Evaluate: Fix compiling evaluator issues led to improper expression caching
1. Do not save a ClassLoader reference inside the context, as we don't use it anyway after evaluation. This is to avoid custom ClassLoader nesting. 2. Do not use 'findClass()' as it caches the loaded classes and always returns the first evaluated class if it's in cache.
This commit is contained in:
+12
-22
@@ -64,7 +64,6 @@ import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
|||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.CompiledDataDescriptor
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.CompiledDataDescriptor
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
|
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
|
||||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||||
@@ -142,14 +141,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
extractAndCompile(fragment, position, context)
|
extractAndCompile(fragment, position, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
val classLoaderHandler = loadClassesSafely(context, compiledData.classes)
|
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||||
|
|
||||||
val result = if (classLoaderHandler != null) {
|
val result = if (classLoaderRef != null) {
|
||||||
try {
|
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData)
|
||||||
evaluateWithCompilation(context, compiledData) ?: runEval4j(context, compiledData)
|
|
||||||
} finally {
|
|
||||||
classLoaderHandler.dispose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
runEval4j(context, compiledData)
|
runEval4j(context, compiledData)
|
||||||
@@ -157,14 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
|
|
||||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
// 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) {
|
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||||
// We need only lambda classes here cause we using only eval4j evaluation method
|
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
|
||||||
val classLoaderHandler = loadClasses(context, compiledData.classes.filter { !it.isMainClass() })
|
|
||||||
|
|
||||||
try {
|
|
||||||
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
|
|
||||||
} finally {
|
|
||||||
classLoaderHandler?.dispose()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (result is InterpreterResult) {
|
return if (result is InterpreterResult) {
|
||||||
@@ -262,28 +250,30 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
get() = classes.firstOrNull { it.isMainClass() } ?: error(
|
get() = classes.firstOrNull { it.isMainClass() } ?: error(
|
||||||
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false))
|
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false))
|
||||||
|
|
||||||
private fun evaluateWithCompilation(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): Any? {
|
private fun evaluateWithCompilation(
|
||||||
|
context: EvaluationContextImpl,
|
||||||
|
compiledData: CompiledDataDescriptor,
|
||||||
|
classLoader: ClassLoaderReference
|
||||||
|
): Any? {
|
||||||
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
||||||
val classLoader = context.classLoader ?: return null
|
|
||||||
val mainClassBytecode = compiledData.mainClass.bytes
|
val mainClassBytecode = compiledData.mainClass.bytes
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
|
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
|
||||||
val mainClassJdiName = mainClassAsmNode.name.replace('/', '.')
|
|
||||||
assert(mainClassAsmNode.methods.size == 1)
|
assert(mainClassAsmNode.methods.size == 1)
|
||||||
|
|
||||||
val methodToInvoke = mainClassAsmNode.methods[0]
|
val methodToInvoke = mainClassAsmNode.methods[0]
|
||||||
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
|
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
|
||||||
|
|
||||||
val mainClass = context.debugProcess.findClass(context, mainClassJdiName, classLoader) as ClassType
|
|
||||||
|
|
||||||
val thread = context.suspendContext.thread?.threadReference!!
|
val thread = context.suspendContext.thread?.threadReference!!
|
||||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||||
val eval = JDIEval(vm, classLoader, thread, invokePolicy)
|
val eval = JDIEval(vm, classLoader, thread, invokePolicy)
|
||||||
|
|
||||||
|
val mainClassValue = (eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader) as? ObjectValue)
|
||||||
|
val mainClass = (mainClassValue?.value as? ClassObjectReference)?.reflectedType() as? ClassType ?: return null
|
||||||
|
|
||||||
return vm.executeWithBreakpointsDisabled {
|
return vm.executeWithBreakpointsDisabled {
|
||||||
// Prepare the main class
|
// Prepare the main class
|
||||||
eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader)
|
|
||||||
|
|
||||||
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
|
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
|
||||||
val args = context.getArgumentsForEval4j(compiledData.parameters, argumentTypes)
|
val args = context.getArgumentsForEval4j(compiledData.parameters, argumentTypes)
|
||||||
|
|||||||
+2
-2
@@ -37,7 +37,7 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler {
|
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||||
val process = context.debugProcess
|
val process = context.debugProcess
|
||||||
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
||||||
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
||||||
@@ -52,6 +52,6 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
|||||||
|
|
||||||
DebuggerUtilsEx.keep(newClassLoader, context)
|
DebuggerUtilsEx.keep(newClassLoader, context)
|
||||||
|
|
||||||
return ClassLoaderHandler(newClassLoader as ClassLoaderReference)
|
return newClassLoader as ClassLoaderReference
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2017 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
|
||||||
import com.sun.jdi.ClassLoaderReference
|
|
||||||
|
|
||||||
open class ClassLoaderHandler(val reference: ClassLoaderReference?) : Disposable {
|
|
||||||
override fun dispose() {}
|
|
||||||
}
|
|
||||||
+3
-2
@@ -21,6 +21,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
|||||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
import com.sun.jdi.ArrayReference
|
import com.sun.jdi.ArrayReference
|
||||||
import com.sun.jdi.ArrayType
|
import com.sun.jdi.ArrayType
|
||||||
|
import com.sun.jdi.ClassLoaderReference
|
||||||
import com.sun.jdi.Value
|
import com.sun.jdi.Value
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
@@ -34,7 +35,7 @@ interface ClassLoadingAdapter {
|
|||||||
AndroidOClassLoadingAdapter(),
|
AndroidOClassLoadingAdapter(),
|
||||||
OrdinaryClassLoadingAdapter())
|
OrdinaryClassLoadingAdapter())
|
||||||
|
|
||||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
|
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||||
val hasAdditionalClasses = classes.size > 1
|
val hasAdditionalClasses = classes.size > 1
|
||||||
val hasLoops = classes.isNotEmpty() && doesContainLoops(classes.first { it.isMainClass() }.bytes)
|
val hasLoops = classes.isNotEmpty() && doesContainLoops(classes.first { it.isMainClass() }.bytes)
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ interface ClassLoadingAdapter {
|
|||||||
|
|
||||||
fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean
|
fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean
|
||||||
|
|
||||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler
|
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||||
|
|
||||||
fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
|
fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
|
||||||
val arrayClass = process.findClass(context, "byte[]", context.classLoader) as ArrayType
|
val arrayClass = process.findClass(context, "byte[]", context.classLoader) as ArrayType
|
||||||
|
|||||||
+2
-2
@@ -41,7 +41,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
|||||||
return (hasAdditionalClasses || hasLoops) && context.classLoader != null && !context.debugProcess.isDexDebug()
|
return (hasAdditionalClasses || hasLoops) && context.classLoader != null && !context.debugProcess.isDexDebug()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler {
|
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||||
val process = context.debugProcess
|
val process = context.debugProcess
|
||||||
|
|
||||||
val classLoader = try {
|
val classLoader = try {
|
||||||
@@ -68,7 +68,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
|||||||
throw EvaluateException("Error during classes definition " + e, e)
|
throw EvaluateException("Error during classes definition " + e, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClassLoaderHandler(classLoader)
|
return classLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defineClasses(
|
private fun defineClasses(
|
||||||
|
|||||||
+11
-11
@@ -17,24 +17,24 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
|
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
|
||||||
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
|
import com.sun.jdi.ClassLoaderReference
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoaderHandler
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
|
|
||||||
fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
|
fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||||
try {
|
return try {
|
||||||
return loadClasses(evaluationContext, classes)
|
loadClasses(evaluationContext, classes)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
LOG.debug("Failed to evaluate expression", e)
|
LOG.debug("Failed to evaluate expression", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||||
|
if (classes.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
|
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)
|
||||||
if (classes.isEmpty()) return ClassLoaderHandler(evaluationContext.classLoader)
|
|
||||||
|
|
||||||
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)?.apply {
|
|
||||||
evaluationContext.classLoader = this.reference
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user