From 2c9bf952272728491d95851745dcdb0dda31b180 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersen Date: Mon, 5 Oct 2020 22:22:38 +0200 Subject: [PATCH] [JVM+IR] New LVT debugging test harness improvements This is the first step in a push to improve the test coverage of the IR debugging experience. This commit improves on the sketch of local variable table tests built on the new debugger stepping infrastructure. As improvements on the existing checkLocalVariableTableTest they: - don't overspecify codegen strategy: no hard requirements on slots. - test the observed lifespans of locals by stepping through code. Ultimately this should enable us to bring over all existing tests for improved coverage of both old and new JVM backends. WIP list: - Gracefully handle absent LVT: treat as empty Add type of local and - type of value stored there to expectations Print values in local - slots if primitive or java.lang.String Enable specifying - expectations per backend Gracefully handle absent box methods - Gracefully handle null values in slots of reference type - Port a first LVT test --- .../checkLocalVariablesTable/catchClause.kt | 16 --- .../debug/localVariables/catchClause.kt | 19 ++++ .../debug/localVariables/copyFunction.kt | 12 +-- .../localVariables/destructuringInFor.kt | 10 +- .../localVariables/destructuringInLambdas.kt | 8 +- .../testData/debug/localVariables/localFun.kt | 2 +- .../debug/localVariables/underscoreNames.kt | 24 ++--- .../debugInformation/AbstractDebugTest.kt | 3 + .../AbstractLocalVariableTest.kt | 102 ++++++++++++++---- ...CheckLocalVariablesTableTestGenerated.java | 5 - .../IrLocalVariableTestGenerated.java | 6 ++ .../LocalVariableTestGenerated.java | 6 ++ ...CheckLocalVariablesTableTestGenerated.java | 5 - 13 files changed, 145 insertions(+), 73 deletions(-) delete mode 100644 compiler/testData/checkLocalVariablesTable/catchClause.kt create mode 100644 compiler/testData/debug/localVariables/catchClause.kt diff --git a/compiler/testData/checkLocalVariablesTable/catchClause.kt b/compiler/testData/checkLocalVariablesTable/catchClause.kt deleted file mode 100644 index 66901447d3e..00000000000 --- a/compiler/testData/checkLocalVariablesTable/catchClause.kt +++ /dev/null @@ -1,16 +0,0 @@ -class A { - fun foo() { - try { - var a = 1 - a++ - } - catch(e : Throwable) { - e.printStackTrace() - } - } -} - -// METHOD : A.foo()V -// VARIABLE : NAME=a TYPE=I INDEX=1 -// VARIABLE : NAME=e TYPE=Ljava/lang/Throwable; INDEX=1 -// VARIABLE : NAME=this TYPE=LA; INDEX=0 diff --git a/compiler/testData/debug/localVariables/catchClause.kt b/compiler/testData/debug/localVariables/catchClause.kt new file mode 100644 index 00000000000..5ca09e84bd9 --- /dev/null +++ b/compiler/testData/debug/localVariables/catchClause.kt @@ -0,0 +1,19 @@ +// FILE: test.kt +fun box() { + try { + var a = 1 + a -- + a /= a + } catch(e : Throwable) { + e.printStackTrace() + } +} + +// LOCAL VARIABLES +// TestKt:3: +// TestKt:4: +// TestKt:5: a:int=1:int +// TestKt:6: a:int=0:int +// TestKt:7: +// TestKt:8: e:java.lang.Throwable=java.lang.ArithmeticException +// TestKt:10: diff --git a/compiler/testData/debug/localVariables/copyFunction.kt b/compiler/testData/debug/localVariables/copyFunction.kt index 3ec0742aaab..76184a8121d 100644 --- a/compiler/testData/debug/localVariables/copyFunction.kt +++ b/compiler/testData/debug/localVariables/copyFunction.kt @@ -9,11 +9,11 @@ fun box() { // LOCAL VARIABLES // TestKt:6: -// someClass:3: a:double, b:double +// someClass:3: a:double=1.0:double, b:double=2.0:double // TestKt:6: -// TestKt:7: a:someClass -// someClass:3: a:double, b:double -// someClass.copy(double, double)+9: a:double, b:double +// TestKt:7: a:someClass=someClass +// someClass:3: a:double=1.0:double, b:double=3.0:double +// someClass.copy(double, double)+9: a:double=1.0:double, b:double=3.0:double // someClass.copy$default(someClass, double, double, int, java.lang.Object)+30: -// TestKt:7: a:someClass -// TestKt:8: a:someClass, b:someClass \ No newline at end of file +// TestKt:7: a:someClass=someClass +// TestKt:8: a:someClass=someClass, b:someClass=someClass \ No newline at end of file diff --git a/compiler/testData/debug/localVariables/destructuringInFor.kt b/compiler/testData/debug/localVariables/destructuringInFor.kt index 85e091411b9..ed6bdaefe7a 100644 --- a/compiler/testData/debug/localVariables/destructuringInFor.kt +++ b/compiler/testData/debug/localVariables/destructuringInFor.kt @@ -12,8 +12,8 @@ fun box() { // IGNORE_BACKEND: JVM_IR // LOCAL VARIABLES // TestKt:6: -// TestKt:8: map:java.util.Collections$SingletonMap -// TestKt:7: map:java.util.Collections$SingletonMap -// TestKt:9: map:java.util.Collections$SingletonMap, a:java.lang.String, b:java.lang.String -// TestKt:7: map:java.util.Collections$SingletonMap -// TestKt:11: map:java.util.Collections$SingletonMap +// TestKt:8: map:java.util.Map=java.util.Collections$SingletonMap +// TestKt:7: map:java.util.Map=java.util.Collections$SingletonMap +// TestKt:9: map:java.util.Map=java.util.Collections$SingletonMap, a:java.lang.String="1":java.lang.String, b:java.lang.String="23":java.lang.String +// TestKt:7: map:java.util.Map=java.util.Collections$SingletonMap +// TestKt:11: map:java.util.Map=java.util.Collections$SingletonMap \ No newline at end of file diff --git a/compiler/testData/debug/localVariables/destructuringInLambdas.kt b/compiler/testData/debug/localVariables/destructuringInLambdas.kt index 16a619004d7..6d680bf8def 100644 --- a/compiler/testData/debug/localVariables/destructuringInLambdas.kt +++ b/compiler/testData/debug/localVariables/destructuringInLambdas.kt @@ -10,11 +10,11 @@ fun box() { // IGNORE_BACKEND: JVM_IR // LOCAL VARIABLES // TestKt:8: -// A:3: x:java.lang.String, y:int +// A:3: x:java.lang.String="O":java.lang.String, y:int=123:int // TestKt:8: -// TestKt:5: a:A, block:TestKt$box$1 -// TestKt$box$1:8: $dstr$x$y:A +// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function1=TestKt$box$1 +// TestKt$box$1:8: $dstr$x$y:A=A // TestKt$box$1.invoke(java.lang.Object)+8: -// TestKt:5: a:A, block:TestKt$box$1 +// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function1=TestKt$box$1 // TestKt:8: // TestKt:9: \ No newline at end of file diff --git a/compiler/testData/debug/localVariables/localFun.kt b/compiler/testData/debug/localVariables/localFun.kt index 2e8c7ba7997..7cdccc8a8db 100644 --- a/compiler/testData/debug/localVariables/localFun.kt +++ b/compiler/testData/debug/localVariables/localFun.kt @@ -12,5 +12,5 @@ fun box() { // LOCAL VARIABLES // TestKt:9: // TestKt:4: -// TestKt:6: $fun$bar$1:TestKt$foo$1 +// TestKt:6: $fun$bar$1:TestKt$foo$1=TestKt$foo$1 // TestKt:10: \ No newline at end of file diff --git a/compiler/testData/debug/localVariables/underscoreNames.kt b/compiler/testData/debug/localVariables/underscoreNames.kt index 95853ee7baf..9dce211a2d1 100644 --- a/compiler/testData/debug/localVariables/underscoreNames.kt +++ b/compiler/testData/debug/localVariables/underscoreNames.kt @@ -25,25 +25,25 @@ fun box() { // LOCAL VARIABLES // TestKt:11: // A:3: -// A:3: x:double, y:java.lang.String, z:char +// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char // A:3: // TestKt:11: -// TestKt:5: a:A, block:TestKt$box$1 -// TestKt$box$1:14: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int +// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function3=TestKt$box$1 +// TestKt$box$1:14: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int // A:3: -// A:3: x:double, y:java.lang.String, z:char +// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char // A:3: -// TestKt$box$1:14: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char -// TestKt$box$1:15: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char +// TestKt$box$1:14: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char +// TestKt$box$1:15: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char // A:3: -// A:3: x:double, y:java.lang.String, z:char +// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char // A:3: -// TestKt$box$1:15: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char -// TestKt$box$1:17: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char +// TestKt$box$1:15: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char +// TestKt$box$1:17: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char // TestKt:7: -// TestKt$box$1:17: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char -// TestKt$box$1:21: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char +// TestKt$box$1:17: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char +// TestKt$box$1:21: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char // TestKt$box$1.invoke(java.lang.Object, java.lang.Object, java.lang.Object)+19: -// TestKt:5: a:A, block:TestKt$box$1 +// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function3=TestKt$box$1 // TestKt:11: // TestKt:23: \ No newline at end of file diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractDebugTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractDebugTest.kt index 5fef33f3a81..0a086afbe79 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractDebugTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractDebugTest.kt @@ -171,6 +171,9 @@ abstract class AbstractDebugTest : CodegenTestCase() { val classLoader = createGeneratedClassLoader(classesDir) val aClass = getGeneratedClass(classLoader, TEST_CLASS) + assert(aClass.declaredMethods.any { it.name == BOX_METHOD }) { + "Test method $BOX_METHOD not present on test class $TEST_CLASS" + } if (virtualMachine.allThreads().any { it.isSuspended }) { virtualMachine.resume() } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractLocalVariableTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractLocalVariableTest.kt index ec6e5704545..52c90fd2d19 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractLocalVariableTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/debugInformation/AbstractLocalVariableTest.kt @@ -1,11 +1,10 @@ package org.jetbrains.kotlin.codegen.debugInformation -import com.sun.jdi.LocalVariable -import com.sun.jdi.StackFrame -import com.sun.jdi.VirtualMachine +import com.sun.jdi.* import com.sun.jdi.event.Event import com.sun.jdi.event.LocatableEvent import junit.framework.TestCase +import org.jetbrains.kotlin.test.TargetBackend import org.junit.AfterClass import org.junit.BeforeClass import java.io.File @@ -15,7 +14,41 @@ abstract class AbstractLocalVariableTest : AbstractDebugTest() { override val virtualMachine: VirtualMachine = Companion.virtualMachine override val proxyPort: Int = Companion.proxyPort + interface LocalValue + + class LocalPrimitive(val value: String, val valueType: String) : LocalValue { + override fun toString(): String { + return "$value:$valueType" + } + } + + class LocalReference(val id: String, val referenceType: String) : LocalValue { + override fun toString(): String { + return "$referenceType" + } + } + + class LocalNullValue : LocalValue { + override fun toString(): String { + return "null" + } + } + + class LocalVariableRecord( + val variable: String, + val variableType: String, + val value: LocalValue + ) { + override fun toString(): String { + return "$variable:$variableType=$value" + } + } + companion object { + const val LOCAL_VARIABLES_MARKER = "// LOCAL VARIABLES" + const val JVM_LOCAL_VARIABLES_MARKER = "$LOCAL_VARIABLES_MARKER JVM" + const val JVM_IR_LOCAL_VARIABLES_MARKER = "$LOCAL_VARIABLES_MARKER JVM_IR" + const val LOCAL_VARIABLES = "// LOCAL VARIABLES" var proxyPort = 0 lateinit var process: Process @@ -41,29 +74,60 @@ abstract class AbstractLocalVariableTest : AbstractDebugTest() { override fun storeStep(loggedItems: ArrayList, event: Event) { waitUntil { (event as LocatableEvent).thread().isSuspended } - val visibleVars = (event as LocatableEvent) - .thread() - .frame(0) - .visibleVariables() - .map { variable -> toRecord(event.thread().frame(0), variable) } - .joinToString(", ") - loggedItems.add("${event.location()}: $visibleVars".trim()) + val frame = (event as LocatableEvent).thread().frame(0) + try { + val visibleVars = frame + .visibleVariables() + .map { variable -> toRecord(frame, variable) } + .joinToString(", ") + loggedItems.add("${event.location()}: $visibleVars".trim()) + } catch (e: AbsentInformationException) { + // LVT Completely absent - not distinguished from an empty table + loggedItems.add("${event.location()}:".trim()) + } } override fun checkResult(wholeFile: File, loggedItems: List) { - val expectedLocalVariables = wholeFile - .readLines() - .dropWhile { !it.startsWith(LOCAL_VARIABLES) } - .drop(1) - .map { it.drop(3) } - .joinToString("\n") + val lines = wholeFile.readLines() + + val expectedLocalVariables = mutableListOf() + val lineIterator = lines.iterator() + for (line in lineIterator) { + if (line.startsWith(LOCAL_VARIABLES_MARKER)) break + } + + var currentBackend = TargetBackend.ANY + for (line in lineIterator) { + if (line.trim() == "") continue + if (line.startsWith(LOCAL_VARIABLES_MARKER)) { + currentBackend = when (line) { + LOCAL_VARIABLES_MARKER -> TargetBackend.ANY + JVM_LOCAL_VARIABLES_MARKER -> TargetBackend.JVM + JVM_IR_LOCAL_VARIABLES_MARKER -> TargetBackend.JVM_IR + else -> error("Expected JVM backend: $line") + } + continue + } + if (currentBackend == TargetBackend.ANY || currentBackend == backend) { + expectedLocalVariables.add(line.drop(3)) + } + } + val actualLocalVariables = loggedItems.joinToString("\n") - TestCase.assertEquals(expectedLocalVariables, actualLocalVariables) + TestCase.assertEquals(expectedLocalVariables.joinToString("\n"), actualLocalVariables) } - private fun toRecord(frame: StackFrame, variable: LocalVariable): String { - return "${variable.name()}:${frame.getValue(variable)?.type()?.name() ?: "null"}" + private fun toRecord(frame: StackFrame, variable: LocalVariable): LocalVariableRecord { + val value = frame.getValue(variable) + val valueRecord = if (value == null) { + LocalNullValue() + } else if (value is ObjectReference && value.referenceType().name() != "java.lang.String") { + LocalReference(value.uniqueID().toString(), value.referenceType().name()) + } else { + LocalPrimitive(value.toString(), value.type().name()) + } + return LocalVariableRecord(variable.name(), variable.typeName(), valueRecord) } private fun waitUntil(condition: () -> Boolean) { diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java index 60a5b764022..397fab7b8b7 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CheckLocalVariablesTableTestGenerated.java @@ -29,11 +29,6 @@ public class CheckLocalVariablesTableTestGenerated extends AbstractCheckLocalVar KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/checkLocalVariablesTable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } - @TestMetadata("catchClause.kt") - public void testCatchClause() throws Exception { - runTest("compiler/testData/checkLocalVariablesTable/catchClause.kt"); - } - @TestMetadata("copyFunction.kt") public void testCopyFunction() throws Exception { runTest("compiler/testData/checkLocalVariablesTable/copyFunction.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java index 40e14135ed1..bc52c5cb9f9 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java @@ -31,6 +31,12 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @Test + @TestMetadata("catchClause.kt") + public void testCatchClause() throws Exception { + runTest("compiler/testData/debug/localVariables/catchClause.kt"); + } + @Test @TestMetadata("copyFunction.kt") public void testCopyFunction() throws Exception { diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java index d9d9e7f011b..bdef17af544 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java @@ -31,6 +31,12 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } + @Test + @TestMetadata("catchClause.kt") + public void testCatchClause() throws Exception { + runTest("compiler/testData/debug/localVariables/catchClause.kt"); + } + @Test @TestMetadata("copyFunction.kt") public void testCopyFunction() throws Exception { diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java index 5fe679c7190..9919c31b77c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCheckLocalVariablesTableTestGenerated.java @@ -29,11 +29,6 @@ public class IrCheckLocalVariablesTableTestGenerated extends AbstractIrCheckLoca KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/checkLocalVariablesTable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } - @TestMetadata("catchClause.kt") - public void testCatchClause() throws Exception { - runTest("compiler/testData/checkLocalVariablesTable/catchClause.kt"); - } - @TestMetadata("copyFunction.kt") public void testCopyFunction() throws Exception { runTest("compiler/testData/checkLocalVariablesTable/copyFunction.kt");