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.ParametersDescriptor
|
||||
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.refactoring.introduce.extractionEngine.ExtractionResult
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
@@ -142,14 +141,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
extractAndCompile(fragment, position, context)
|
||||
}
|
||||
|
||||
val classLoaderHandler = loadClassesSafely(context, compiledData.classes)
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val result = if (classLoaderHandler != null) {
|
||||
try {
|
||||
evaluateWithCompilation(context, compiledData) ?: runEval4j(context, compiledData)
|
||||
} finally {
|
||||
classLoaderHandler.dispose()
|
||||
}
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData)
|
||||
}
|
||||
else {
|
||||
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 (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
// We need only lambda classes here cause we using only eval4j evaluation method
|
||||
val classLoaderHandler = loadClasses(context, compiledData.classes.filter { !it.isMainClass() })
|
||||
|
||||
try {
|
||||
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
|
||||
} finally {
|
||||
classLoaderHandler?.dispose()
|
||||
}
|
||||
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
|
||||
}
|
||||
|
||||
return if (result is InterpreterResult) {
|
||||
@@ -262,28 +250,30 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
get() = classes.firstOrNull { it.isMainClass() } ?: error(
|
||||
"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 classLoader = context.classLoader ?: return null
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
|
||||
try {
|
||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
|
||||
val mainClassJdiName = mainClassAsmNode.name.replace('/', '.')
|
||||
assert(mainClassAsmNode.methods.size == 1)
|
||||
|
||||
val methodToInvoke = mainClassAsmNode.methods[0]
|
||||
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
|
||||
|
||||
val mainClass = context.debugProcess.findClass(context, mainClassJdiName, classLoader) as ClassType
|
||||
|
||||
val thread = context.suspendContext.thread?.threadReference!!
|
||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||
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 {
|
||||
// Prepare the main class
|
||||
eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader)
|
||||
|
||||
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
|
||||
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 inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
||||
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
||||
@@ -52,6 +52,6 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
||||
|
||||
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.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.ArrayType
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
@@ -34,7 +35,7 @@ interface ClassLoadingAdapter {
|
||||
AndroidOClassLoadingAdapter(),
|
||||
OrdinaryClassLoadingAdapter())
|
||||
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
val hasAdditionalClasses = classes.size > 1
|
||||
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 loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||
|
||||
fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler {
|
||||
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val process = context.debugProcess
|
||||
|
||||
val classLoader = try {
|
||||
@@ -68,7 +68,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
throw EvaluateException("Error during classes definition " + e, e)
|
||||
}
|
||||
|
||||
return ClassLoaderHandler(classLoader)
|
||||
return classLoader
|
||||
}
|
||||
|
||||
private fun defineClasses(
|
||||
|
||||
+10
-10
@@ -17,24 +17,24 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
|
||||
|
||||
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.classLoading.ClassLoaderHandler
|
||||
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>): ClassLoaderHandler? {
|
||||
try {
|
||||
return loadClasses(evaluationContext, classes)
|
||||
fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
return try {
|
||||
loadClasses(evaluationContext, classes)
|
||||
} catch (e: Throwable) {
|
||||
LOG.debug("Failed to evaluate expression", e)
|
||||
return null
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
|
||||
if (classes.isEmpty()) return ClassLoaderHandler(evaluationContext.classLoader)
|
||||
|
||||
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)?.apply {
|
||||
evaluationContext.classLoader = this.reference
|
||||
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
if (classes.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)
|
||||
}
|
||||
Reference in New Issue
Block a user