From 3176dd1341442fe87f911f1d34c37edc13dc1e09 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Wed, 25 Aug 2021 11:00:56 +0300 Subject: [PATCH] [FIR] Properly create edges from return expression to finally block ^KT-48376 Fixed --- ...TouchedTilContractsPhaseTestGenerated.java | 5 + .../cfg/variableInitializedInTryBlock.dot | 127 ++++++++++++++++++ .../cfg/variableInitializedInTryBlock.fir.txt | 18 +++ .../cfg/variableInitializedInTryBlock.kt | 19 +++ .../runners/FirDiagnosticTestGenerated.java | 6 + ...DiagnosticsWithLightTreeTestGenerated.java | 6 + .../dfa/cfg/ControlFlowGraphBuilder.kt | 28 +++- ...nosisCompilerFirTestdataTestGenerated.java | 6 + 8 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot create mode 100644 compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.fir.txt create mode 100644 compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt diff --git a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java index 5bd57a22985..a8c596b504b 100644 --- a/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java +++ b/compiler/fir/analysis-tests/legacy-fir-tests/tests-gen/org/jetbrains/kotlin/fir/LazyBodyIsNotTouchedTilContractsPhaseTestGenerated.java @@ -958,6 +958,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract runTest("compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.kt"); } + @TestMetadata("variableInitializedInTryBlock.kt") + public void testVariableInitializedInTryBlock() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt"); + } + @TestMetadata("when.kt") public void testWhen() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/cfg/when.kt"); diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot new file mode 100644 index 00000000000..3d52d66515b --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot @@ -0,0 +1,127 @@ +digraph variableInitializedInTryBlock_kt { + graph [nodesep=3] + node [shape=box penwidth=2] + edge [penwidth=2] + + subgraph cluster_0 { + color=red + 0 [label="Enter function test" style="filled" fillcolor=red]; + subgraph cluster_1 { + color=blue + 1 [label="Enter block"]; + 2 [label="Variable declaration: lval b: R|kotlin/Boolean|"]; + subgraph cluster_2 { + color=blue + 3 [label="Try expression enter"]; + subgraph cluster_3 { + color=blue + 4 [label="Try main block enter"]; + subgraph cluster_4 { + color=blue + 5 [label="Enter block"]; + 6 [label="Function call: R|/getStringOrNull|()"]; + 7 [label="Exit lhs of ?:"]; + 8 [label="Enter rhs of ?:"]; + 9 [label="Jump: ^test Unit"]; + 10 [label="Stub" style="filled" fillcolor=gray]; + 11 [label="Lhs of ?: is not null"]; + 12 [label="Exit ?:"]; + 13 [label="Variable declaration: lval s: R|kotlin/String|"]; + 14 [label="Access variable R|/s|"]; + 15 [label="Access variable R|kotlin/String.length|"]; + 16 [label="Const: Int(0)"]; + 17 [label="Equality operator !="]; + 18 [label="Assignment: R|/b|"]; + 19 [label="Exit block"]; + } + 20 [label="Try main block exit"]; + } + subgraph cluster_5 { + color=blue + 21 [label="Enter finally"]; + subgraph cluster_6 { + color=blue + 22 [label="Enter block"]; + 23 [label="Function call: R|/test|()"]; + 24 [label="Exit block"]; + } + 25 [label="Exit finally"]; + } + 26 [label="Try expression exit"]; + } + 27 [label="Access variable R|/b|"]; + 28 [label="Function call: R|/takeBoolean|(...)"]; + 29 [label="Exit block"]; + } + 30 [label="Exit function test" style="filled" fillcolor=red]; + } + 0 -> {1}; + 1 -> {2}; + 2 -> {3}; + 3 -> {4}; + 3 -> {21} [label=onUncaughtException]; + 4 -> {5}; + 5 -> {6}; + 6 -> {7}; + 7 -> {11 8}; + 8 -> {9}; + 9 -> {21} [label="return@/test"]; + 9 -> {10} [style=dotted]; + 10 -> {12} [style=dotted]; + 11 -> {12}; + 12 -> {13}; + 13 -> {14}; + 14 -> {15}; + 15 -> {16}; + 16 -> {17}; + 17 -> {18}; + 18 -> {19}; + 19 -> {20}; + 20 -> {21}; + 21 -> {22}; + 22 -> {23}; + 23 -> {24}; + 24 -> {25}; + 25 -> {26}; + 25 -> {30} [label="return@/test"]; + 26 -> {27}; + 27 -> {28}; + 28 -> {29}; + 29 -> {30}; + + subgraph cluster_7 { + color=red + 31 [label="Enter function takeBoolean" style="filled" fillcolor=red]; + subgraph cluster_8 { + color=blue + 32 [label="Enter block"]; + 33 [label="Exit block"]; + } + 34 [label="Exit function takeBoolean" style="filled" fillcolor=red]; + } + 31 -> {32}; + 32 -> {33}; + 33 -> {34}; + + subgraph cluster_9 { + color=red + 35 [label="Enter function getStringOrNull" style="filled" fillcolor=red]; + subgraph cluster_10 { + color=blue + 36 [label="Enter block"]; + 37 [label="Const: String(hello)"]; + 38 [label="Jump: ^getStringOrNull String(hello)"]; + 39 [label="Stub" style="filled" fillcolor=gray]; + 40 [label="Exit block" style="filled" fillcolor=gray]; + } + 41 [label="Exit function getStringOrNull" style="filled" fillcolor=red]; + } + 35 -> {36}; + 36 -> {37}; + 37 -> {38}; + 38 -> {41}; + 38 -> {39} [style=dotted]; + 39 -> {40} [style=dotted]; + 40 -> {41} [style=dotted]; + +} diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.fir.txt b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.fir.txt new file mode 100644 index 00000000000..5fed9ea96b5 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.fir.txt @@ -0,0 +1,18 @@ +FILE: variableInitializedInTryBlock.kt + public final fun test(): R|kotlin/Unit| { + lval b: R|kotlin/Boolean| + try { + lval s: R|kotlin/String| = R|/getStringOrNull|() ?: ^test Unit + R|/b| = !=(R|/s|.R|kotlin/String.length|, Int(0)) + } + finally { + R|/test|() + } + + R|/takeBoolean|(R|/b|) + } + public final fun takeBoolean(b: R|kotlin/Boolean|): R|kotlin/Unit| { + } + public final fun getStringOrNull(): R|kotlin/String?| { + ^getStringOrNull String(hello) + } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt new file mode 100644 index 00000000000..4bba0e8b30d --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt @@ -0,0 +1,19 @@ +// DUMP_CFG +// ISSUE: KT-48376 + +fun test() { + val b: Boolean + try { + val s = getStringOrNull() ?: return + b = s.length != 0 + } finally { + test() + } + takeBoolean(b) +} + +fun takeBoolean(b: Boolean) {} + +fun getStringOrNull(): String? { + return "hello" +} diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java index 2400e4913d2..01cc3e2f555 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticTestGenerated.java @@ -1113,6 +1113,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest { runTest("compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.kt"); } + @Test + @TestMetadata("variableInitializedInTryBlock.kt") + public void testVariableInitializedInTryBlock() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt"); + } + @Test @TestMetadata("when.kt") public void testWhen() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java index d71de277631..7e743d10820 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirDiagnosticsWithLightTreeTestGenerated.java @@ -1113,6 +1113,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.kt"); } + @Test + @TestMetadata("variableInitializedInTryBlock.kt") + public void testVariableInitializedInTryBlock() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt"); + } + @Test @TestMetadata("when.kt") public void testWhen() throws Exception { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index 41d8349952f..ffe858f7261 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -613,7 +613,21 @@ class ControlFlowGraphBuilder { is FirBreakExpression -> loopExitNodes[jump.target.labeledElement] else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}") } - addNodeWithJump(node, nextNode, isBack = jump is FirContinueExpression, trackJump = jump is FirReturnExpression) + + val labelForFinallyBLock = if (jump is FirReturnExpression) { + ReturnPath(jump.target.labeledElement.symbol) + } else { + NormalPath + } + + addNodeWithJump( + node, + nextNode, + isBack = jump is FirContinueExpression, + trackJump = jump is FirReturnExpression, + label = NormalPath, + labelForFinallyBLock = labelForFinallyBLock + ) return node } @@ -1411,6 +1425,7 @@ class ControlFlowGraphBuilder { preferredKind: EdgeKind = EdgeKind.Forward, isBack: Boolean = false, label: EdgeLabel = NormalPath, + labelForFinallyBLock: EdgeLabel = label, trackJump: Boolean = false ) { popAndAddEdge(node, preferredKind) @@ -1423,13 +1438,18 @@ class ControlFlowGraphBuilder { addBackEdge(node, targetNode, label = label) } } else { - //go through all final nodes between node and target + // go through all final nodes between node and target val finallyNodes = finallyBefore(targetNode) val finalFrom = finallyNodes.fold(node) { from, (finallyEnter, tryExit) -> - addEdgeIfNotExist(from, finallyEnter, propagateDeadness = false, label = label) + addEdgeIfNotExist(from, finallyEnter, propagateDeadness = false, label = labelForFinallyBLock) tryExit } - addEdgeIfNotExist(finalFrom, targetNode, propagateDeadness = false, label = label) + addEdgeIfNotExist( + finalFrom, + targetNode, + propagateDeadness = false, + label = if (finallyNodes.isEmpty()) label else labelForFinallyBLock + ) if (trackJump && finallyNodes.isNotEmpty()) { //actually we can store all returns like this, but not sure if it makes anything better nonDirectJumps.put(targetNode, node) diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java index 3a756892d05..ae00db6bb32 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/diagnostic/compiler/based/DiagnosisCompilerFirTestdataTestGenerated.java @@ -1113,6 +1113,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis runTest("compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.kt"); } + @Test + @TestMetadata("variableInitializedInTryBlock.kt") + public void testVariableInitializedInTryBlock() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.kt"); + } + @Test @TestMetadata("when.kt") public void testWhen() throws Exception {