Evaluator: Support when expressions (EA-119639)
This commit is contained in:
+2
-2
@@ -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? {
|
||||||
|
|||||||
+36
-24
@@ -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
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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 {
|
||||||
|
|||||||
+10
@@ -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;
|
||||||
+8
@@ -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
|
||||||
Generated
+5
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user