Evaluator: Support when expressions (EA-119639)

This commit is contained in:
Yan Zhulanow
2018-07-02 16:26:20 +03:00
parent c749a222eb
commit a2fdf7d47d
6 changed files with 63 additions and 28 deletions
@@ -24,8 +24,8 @@ import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.isDexDebug import org.jetbrains.kotlin.idea.debugger.isDexDebug
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() { class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
override fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean { override fun isApplicable(context: EvaluationContextImpl, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
return (hasAdditionalClasses || hasLoops) && context.debugProcess.isDexDebug() isCompilingEvaluatorPreferred && context.debugProcess.isDexDebug()
} }
private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? { private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
@@ -25,9 +25,7 @@ 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
import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
import org.jetbrains.org.objectweb.asm.tree.LabelNode
import kotlin.math.min import kotlin.math.min
interface ClassLoadingAdapter { interface ClassLoadingAdapter {
@@ -40,15 +38,15 @@ interface ClassLoadingAdapter {
) )
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? { fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
val hasAdditionalClasses = classes.size > 1 val mainClass = classes.firstOrNull { it.isMainClass() } ?: return null
val hasLoops = classes.isNotEmpty() && doesContainLoops(classes.first { it.isMainClass() }.bytes)
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
if (!info.containsAdditionalClasses) {
info = analyzeClass(mainClass, info)
}
for (adapter in ADAPTERS) { for (adapter in ADAPTERS) {
if (adapter.isApplicable( if (adapter.isApplicable(context, info)) {
context,
hasAdditionalClasses = hasAdditionalClasses,
hasLoops = hasLoops
)) {
return adapter.loadClasses(context, classes) return adapter.loadClasses(context, classes)
} }
} }
@@ -56,30 +54,44 @@ interface ClassLoadingAdapter {
return null return null
} }
private fun doesContainLoops(clazz: ByteArray): Boolean { data class ClassInfoForEvaluator(
val classNode = ClassNode().apply { ClassReader(clazz).accept(this, ClassReader.EXPAND_FRAMES) } val containsLoops: Boolean = false,
val containsCodeUnsupportedInEval4J: Boolean = false,
val containsAdditionalClasses: Boolean = false
) {
val isCompilingEvaluatorPreferred: Boolean
get() = containsLoops || containsCodeUnsupportedInEval4J || containsAdditionalClasses
}
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, ClassReader.EXPAND_FRAMES) }
val methodToRun = classNode.methods.single() val methodToRun = classNode.methods.single()
val labelsVisited = hashSetOf<Label>() val visitedLabels = hashSetOf<Label>()
var currentInsn = methodToRun.instructions.first
while (currentInsn != null) { tailrec fun analyzeInsn(insn: AbstractInsnNode, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
if (currentInsn is LabelNode) { when (insn) {
labelsVisited += currentInsn.label is LabelNode -> visitedLabels += insn.label
} is JumpInsnNode -> {
else if (currentInsn is JumpInsnNode) { if (insn.label.label in visitedLabels) {
if (currentInsn.label.label in labelsVisited) { return info.copy(containsLoops = true)
return true }
}
is TableSwitchInsnNode, is LookupSwitchInsnNode -> {
return info.copy(containsCodeUnsupportedInEval4J = true)
} }
} }
currentInsn = currentInsn.next val nextInsn = insn.next ?: return info
return analyzeInsn(nextInsn, info)
} }
return false val firstInsn = methodToRun.instructions?.first ?: return info
return analyzeInsn(firstInsn, info)
} }
} }
fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean fun isApplicable(context: EvaluationContextImpl, info: ClassInfoForEvaluator): Boolean
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference
@@ -37,8 +37,8 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda")) private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
} }
override fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean { override fun isApplicable(context: EvaluationContextImpl, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
return (hasAdditionalClasses || hasLoops) && context.classLoader != null && !context.debugProcess.isDexDebug() isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
} }
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference { override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference {
@@ -0,0 +1,10 @@
package whenEvaluation
fun main(args: Array<String>) {
val a = "x"
//Breakpoint!
args.size
}
// EXPRESSION: when (a) { "a" -> "A"; "b" -> "B"; else -> "C" }
// RESULT: "C": Ljava/lang/String;
@@ -0,0 +1,8 @@
LineBreakpoint created at whenEvaluation.kt:6
Run Java
Connected to the target VM
whenEvaluation.kt:6
Compile bytecode for when (a) { "a" -> "A"; "b" -> "B"; else -> "C" }
Disconnected from the target VM
Process finished with exit code 0
@@ -331,6 +331,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt");
} }
@TestMetadata("whenEvaluation.kt")
public void testWhenEvaluation() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/whenEvaluation.kt");
}
@TestMetadata(".kt.kt") @TestMetadata(".kt.kt")
public void test_kt() throws Exception { public void test_kt() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/.kt.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/.kt.kt");