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:
Yan Zhulanow
2018-01-22 04:17:15 +03:00
parent da74bcbcc0
commit e38a41d2f2
6 changed files with 29 additions and 62 deletions
@@ -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)
@@ -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
} }
} }
@@ -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() {}
}
@@ -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
@@ -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(
@@ -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)
return null null
} }
} }
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? { fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
if (classes.isEmpty()) return ClassLoaderHandler(evaluationContext.classLoader) if (classes.isEmpty()) {
return null
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)?.apply {
evaluationContext.classLoader = this.reference
} }
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)
} }