diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index 6423acad68b..156e25f423d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -320,8 +320,7 @@ abstract class InlineCodegen( val splitBy = SimpleInterval(start.info as LabelNode, extension.finallyIntervalEnd) processor.tryBlocksMetaInfo.splitAndRemoveCurrentIntervals(splitBy, true) - - //processor.getLocalVarsMetaInfo().splitAndRemoveIntervalsFromCurrents(splitBy); + processor.localVarsMetaInfo.splitAndRemoveCurrentIntervals(splitBy, true); mark.dropTo() } @@ -330,8 +329,7 @@ abstract class InlineCodegen( } processor.substituteTryBlockNodes(intoNode) - - //processor.substituteLocalVarTable(intoNode); + processor.substituteLocalVarTable(intoNode); } protected abstract fun generateAssertFieldIfNeeded(info: RootInliningContext) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 722472a11aa..3ee31e14a47 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -87,9 +87,27 @@ class TryWithFinallyInfo(val onExit: IrExpression) : TryInfo() class BlockInfo(val parent: BlockInfo? = null) { val variables = mutableListOf() val infos: Stack = parent?.infos ?: Stack() + var activeLocalGaps = 0 fun hasFinallyBlocks(): Boolean = infos.firstIsInstanceOrNull() != null + internal inline fun forEachBlockUntil(tryWithFinallyInfo: TryWithFinallyInfo, onBlock: BlockInfo.() -> Unit) { + var current: BlockInfo? = this + while (current != null && current != tryWithFinallyInfo.blockInfo) { + current.onBlock() + current = current.parent + } + } + + internal inline fun localGapScope(tryWithFinallyInfo: TryWithFinallyInfo, block: () -> Unit) { + forEachBlockUntil(tryWithFinallyInfo) { ++activeLocalGaps } + try { + block() + } finally { + forEachBlockUntil(tryWithFinallyInfo) { --activeLocalGaps } + } + } + internal inline fun withBlock(info: T, f: (T) -> R): R { info.blockInfo = this infos.add(info) @@ -411,14 +429,15 @@ class ExpressionCodegen( gapStart: Label, restartLabel: Label ) { - var current: BlockInfo? = info - while (current != null && current != tryWithFinallyInfo.blockInfo) { - for (variable in current.variables) { - if (variable.declaration.isVisibleInLVT) { - variable.gaps.add(Gap(gapStart, restartLabel)) + info.forEachBlockUntil(tryWithFinallyInfo) { + // If we are already in a gap do not add a new one. + if (activeLocalGaps == 0) { + for (variable in variables) { + if (variable.declaration.isVisibleInLVT) { + variable.gaps.add(Gap(gapStart, restartLabel)) + } } } - current = current.parent } } @@ -1239,21 +1258,21 @@ class ExpressionCodegen( nestedTryWithoutFinally: MutableList = arrayListOf() ) { val gapStart = markNewLinkedLabel() - - finallyDepth++ - if (isFinallyMarkerRequired()) { - generateFinallyMarker(mv, finallyDepth, true) + data.localGapScope(tryWithFinallyInfo) { + finallyDepth++ + if (isFinallyMarkerRequired()) { + generateFinallyMarker(mv, finallyDepth, true) + } + tryWithFinallyInfo.onExit.accept(this, data).discard() + if (isFinallyMarkerRequired()) { + generateFinallyMarker(mv, finallyDepth, false) + } + finallyDepth-- + if (tryCatchBlockEnd != null) { + tryWithFinallyInfo.onExit.markLineNumber(startOffset = false) + mv.goTo(tryCatchBlockEnd) + } } - tryWithFinallyInfo.onExit.accept(this, data).discard() - if (isFinallyMarkerRequired()) { - generateFinallyMarker(mv, finallyDepth, false) - } - finallyDepth-- - if (tryCatchBlockEnd != null) { - tryWithFinallyInfo.onExit.markLineNumber(startOffset = false) - mv.goTo(tryCatchBlockEnd) - } - // Split the local variables for the blocks on the way to the finally. Variables introduced in these blocks do not // cover the finally block code. val endOfFinallyCode = markNewLinkedLabel() diff --git a/compiler/testData/debug/localVariables/tryFinally10.kt b/compiler/testData/debug/localVariables/tryFinally10.kt new file mode 100644 index 00000000000..1642bb64e8d --- /dev/null +++ b/compiler/testData/debug/localVariables/tryFinally10.kt @@ -0,0 +1,55 @@ +// FILE: test.kt + +inline fun f(block: () -> Unit) { + try { + val z = 32 + for (j in 0 until 1) { + return + } + } finally { + block() + } +} + +var x: String = "" + +fun compute(): String { + try { + val y = 42 + for (i in 0 until 1) { + f { + return "NON_LOCAL_RETURN" + } + } + } finally { + val s2 = "OK" + x = s2 // TODO: Why is `s2` not visible here? + } + return "FAIL" +} + +fun box() { + val result = compute() + val localX = x +} + +// The local variables `y` and `i` are visible in the finally block with old backend. +// IGNORE_BACKEND: JVM + +// LOCAL VARIABLES +// test.kt:32 box: +// test.kt:17 compute: +// test.kt:18 compute: +// test.kt:19 compute: y:int=42:int +// test.kt:20 compute: y:int=42:int, i:int=0:int +// test.kt:4 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:5 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:6 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, z$iv:int=32:int +// test.kt:7 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, z$iv:int=32:int, j$iv:int=0:int +// test.kt:10 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:21 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, $i$a$-f-TestKt$compute$1:int=0:int +// test.kt:25 compute: +// test.kt:26 compute: +// test.kt:32 box: +// test.kt:33 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String +// test.kt:34 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String, localX:java.lang.String="OK":java.lang.String diff --git a/compiler/testData/debug/localVariables/tryFinally5.kt b/compiler/testData/debug/localVariables/tryFinally5.kt index 5bd7503652a..b30b6bf458f 100644 --- a/compiler/testData/debug/localVariables/tryFinally5.kt +++ b/compiler/testData/debug/localVariables/tryFinally5.kt @@ -26,13 +26,9 @@ fun box() { val localX = x } -// JVM_IR backend correctly gets rid of the `a` local in the finally, but the inliner -// does not get rid of `b` or the inlining locals. +// JVM backend has the `a` local covering the finally block. It shouldn't. -// JVM backend does not get rid of `a` and also the inliner does not get rid of `b` -// or the inlining locals. - -// IGNORE_BACKEND: JVM_IR, JVM +// IGNORE_BACKEND: JVM // LOCAL VARIABLES // test.kt:25 box: diff --git a/compiler/testData/debug/localVariables/tryFinally6.kt b/compiler/testData/debug/localVariables/tryFinally6.kt new file mode 100644 index 00000000000..6af51b6a1c5 --- /dev/null +++ b/compiler/testData/debug/localVariables/tryFinally6.kt @@ -0,0 +1,64 @@ +// FILE: test.kt + +inline fun h(block: () -> Unit) { + try { + val hLocal = "hLocal" + block() + } finally { + val h = "h" + } +} + +inline fun g(block: () -> Unit) { + try { + val gLocal = "gLocal" + h(block) + } finally { + val g = "g" + } +} + +var x: String? = null + +fun compute(): String { + try { + for (a in listOf("a")) { + g { + for (b in listOf("b")) { + return b + } + } + } + } finally { + x = "OK" + } + return "FAIL" +} + +fun box() { + val result = compute() + val localX = x +} + +// JVM backend has `a` visible in the `compute` finally block. It shouldn't be. +// IGNORE_BACKEND: JVM + +// LOCAL VARIABLES +// test.kt:39 box: +// test.kt:24 compute: +// test.kt:25 compute: +// test.kt:26 compute: a:java.lang.String="a":java.lang.String +// test.kt:13 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int +// test.kt:14 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int +// test.kt:15 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String +// test.kt:4 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String, $i$f$h:int=0:int +// test.kt:5 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String, $i$f$h:int=0:int +// test.kt:6 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String, $i$f$h:int=0:int, hLocal$iv$iv:java.lang.String="hLocal":java.lang.String +// test.kt:27 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String, $i$f$h:int=0:int, hLocal$iv$iv:java.lang.String="hLocal":java.lang.String, $i$a$-g-TestKt$compute$1:int=0:int +// test.kt:28 compute: a:java.lang.String="a":java.lang.String, $i$f$g:int=0:int, gLocal$iv:java.lang.String="gLocal":java.lang.String, $i$f$h:int=0:int, hLocal$iv$iv:java.lang.String="hLocal":java.lang.String, $i$a$-g-TestKt$compute$1:int=0:int, b:java.lang.String="b":java.lang.String +// test.kt:8 compute: a:java.lang.String="a":java.lang.String +// test.kt:17 compute: a:java.lang.String="a":java.lang.String +// test.kt:33 compute: +// test.kt:39 box: +// test.kt:40 box: result:java.lang.String="b":java.lang.String +// test.kt:41 box: result:java.lang.String="b":java.lang.String, localX:java.lang.String="OK":java.lang.String diff --git a/compiler/testData/debug/localVariables/tryFinally7.kt b/compiler/testData/debug/localVariables/tryFinally7.kt new file mode 100644 index 00000000000..b887318c03c --- /dev/null +++ b/compiler/testData/debug/localVariables/tryFinally7.kt @@ -0,0 +1,51 @@ +// FILE: test.kt + +inline fun f(block: () -> Unit) { + block() +} + +var x: String = "" + +fun compute(): String { + try { + val y = 42 + for (i in 0 until 1) { + throw RuntimeException("$y $i") + } + } catch (e: Exception) { + val y = 32 + for (j in 0 until 1) { + f { + return "NON_LOCAL_RETURN" + } + } + } finally { + x = "OK" + } + return "FAIL" +} + +fun box() { + val result = compute() + val localX = x +} + +// The old backend has `y` and `j` visible on the finally block. +// IGNORE_BACKEND: JVM + +// LOCAL VARIABLES +// test.kt:29 box: +// test.kt:10 compute: +// test.kt:11 compute: +// test.kt:12 compute: y:int=42:int +// test.kt:13 compute: y:int=42:int, i:int=0:int +// test.kt:15 compute: +// test.kt:16 compute: e:java.lang.Exception=java.lang.RuntimeException +// test.kt:17 compute: e:java.lang.Exception=java.lang.RuntimeException, y:int=32:int +// test.kt:18 compute: e:java.lang.Exception=java.lang.RuntimeException, y:int=32:int, j:int=0:int +// test.kt:4 compute: e:java.lang.Exception=java.lang.RuntimeException, y:int=32:int, j:int=0:int, $i$f$f:int=0:int +// test.kt:19 compute: e:java.lang.Exception=java.lang.RuntimeException, y:int=32:int, j:int=0:int, $i$f$f:int=0:int, $i$a$-f-TestKt$compute$1:int=0:int +// test.kt:23 compute: e:java.lang.Exception=java.lang.RuntimeException +// test.kt:29 box: +// test.kt:30 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String +// test.kt:31 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String, localX:java.lang.String="OK":java.lang.String diff --git a/compiler/testData/debug/localVariables/tryFinally8.kt b/compiler/testData/debug/localVariables/tryFinally8.kt new file mode 100644 index 00000000000..a34fc9bb6b2 --- /dev/null +++ b/compiler/testData/debug/localVariables/tryFinally8.kt @@ -0,0 +1,54 @@ +// FILE: test.kt + +inline fun f(block: () -> Unit) { + try { + val z = 32 + for (j in 0 until 1) { + throw RuntimeException("$z $j") + } + } catch (e: Exception) { + block() + } +} + +var x: String = "" + +fun compute(): String { + try { + val y = 42 + for (i in 0 until 1) { + f { + return "NON_LOCAL_RETURN" + } + } + } finally { + x = "OK" + } + return "FAIL" +} + +fun box() { + val result = compute() + val localX = x +} + +// The old backend has `y` and `i` visible on the finally block. +// IGNORE_BACKEND: JVM + +// LOCAL VARIABLES +// test.kt:31 box: +// test.kt:17 compute: +// test.kt:18 compute: +// test.kt:19 compute: y:int=42:int +// test.kt:20 compute: y:int=42:int, i:int=0:int +// test.kt:4 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:5 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:6 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, z$iv:int=32:int +// test.kt:7 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, z$iv:int=32:int, j$iv:int=0:int +// test.kt:9 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int +// test.kt:10 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, e$iv:java.lang.Exception=java.lang.RuntimeException +// test.kt:21 compute: y:int=42:int, i:int=0:int, $i$f$f:int=0:int, e$iv:java.lang.Exception=java.lang.RuntimeException, $i$a$-f-TestKt$compute$1:int=0:int +// test.kt:25 compute: +// test.kt:31 box: +// test.kt:32 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String +// test.kt:33 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String, localX:java.lang.String="OK":java.lang.String diff --git a/compiler/testData/debug/localVariables/tryFinally9.kt b/compiler/testData/debug/localVariables/tryFinally9.kt new file mode 100644 index 00000000000..ae6bf6fddb2 --- /dev/null +++ b/compiler/testData/debug/localVariables/tryFinally9.kt @@ -0,0 +1,54 @@ +// FILE: test.kt + +inline fun f(block: () -> Unit) { + block() +} + +var x: String = "" + +fun compute(): String { + try { + try { + val y = 42 + for (i in 0 until 1) { + return "NORMAL_RETURN" + } + } finally { + var s = "NOPE" + x = s + f { + return "NON_LOCAL_RETURN" + } + } + } finally { + val s2 = "OK" + x = s2 // TODO: Why is `s2` not visible here? + } + return "FAIL" +} + +fun box() { + val result = compute() + val localX = x +} + +// The local variables in the try and finally blocks are not removed for the finally block with the old backend. +// IGNORE_BACKEND: JVM + +// LOCAL VARIABLES +// test.kt:31 box: +// test.kt:10 compute: +// test.kt:11 compute: +// test.kt:12 compute: +// test.kt:13 compute: y:int=42:int +// test.kt:14 compute: y:int=42:int, i:int=0:int +// test.kt:17 compute: +// test.kt:18 compute: s:java.lang.String="NOPE":java.lang.String +// test.kt:19 compute: s:java.lang.String="NOPE":java.lang.String +// test.kt:4 compute: s:java.lang.String="NOPE":java.lang.String, $i$f$f:int=0:int +// test.kt:20 compute: s:java.lang.String="NOPE":java.lang.String, $i$f$f:int=0:int, $i$a$-f-TestKt$compute$1:int=0:int +// test.kt:24 compute: +// test.kt:25 compute: +// test.kt:31 box: +// test.kt:32 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String +// test.kt:33 box: result:java.lang.String="NON_LOCAL_RETURN":java.lang.String, localX:java.lang.String="OK":java.lang.String diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java index 7b981161b68..f4e9de550c8 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/IrLocalVariableTestGenerated.java @@ -92,6 +92,12 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest { runTest("compiler/testData/debug/localVariables/tryFinally.kt"); } + @Test + @TestMetadata("tryFinally10.kt") + public void testTryFinally10() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally10.kt"); + } + @Test @TestMetadata("tryFinally2.kt") public void testTryFinally2() throws Exception { @@ -116,6 +122,30 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest { runTest("compiler/testData/debug/localVariables/tryFinally5.kt"); } + @Test + @TestMetadata("tryFinally6.kt") + public void testTryFinally6() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally6.kt"); + } + + @Test + @TestMetadata("tryFinally7.kt") + public void testTryFinally7() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally7.kt"); + } + + @Test + @TestMetadata("tryFinally8.kt") + public void testTryFinally8() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally8.kt"); + } + + @Test + @TestMetadata("tryFinally9.kt") + public void testTryFinally9() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally9.kt"); + } + @Test @TestMetadata("underscoreNames.kt") public void testUnderscoreNames() throws Exception { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java index 0c84a8ea521..2e2010a460b 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/debugInformation/LocalVariableTestGenerated.java @@ -92,6 +92,12 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest { runTest("compiler/testData/debug/localVariables/tryFinally.kt"); } + @Test + @TestMetadata("tryFinally10.kt") + public void testTryFinally10() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally10.kt"); + } + @Test @TestMetadata("tryFinally2.kt") public void testTryFinally2() throws Exception { @@ -116,6 +122,30 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest { runTest("compiler/testData/debug/localVariables/tryFinally5.kt"); } + @Test + @TestMetadata("tryFinally6.kt") + public void testTryFinally6() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally6.kt"); + } + + @Test + @TestMetadata("tryFinally7.kt") + public void testTryFinally7() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally7.kt"); + } + + @Test + @TestMetadata("tryFinally8.kt") + public void testTryFinally8() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally8.kt"); + } + + @Test + @TestMetadata("tryFinally9.kt") + public void testTryFinally9() throws Exception { + runTest("compiler/testData/debug/localVariables/tryFinally9.kt"); + } + @Test @TestMetadata("underscoreNames.kt") public void testUnderscoreNames() throws Exception {