From 069d99c5eab0ecf2065b0b05e70ec461d04d0d00 Mon Sep 17 00:00:00 2001 From: pyos Date: Sun, 27 Nov 2022 21:27:37 +0100 Subject: [PATCH] FIR CFA: rewrite handling of try-catch-finally The result is the same, but it should now be much clearer what the shortcomings of the current implementation are. --- .../resolve/cfg/annotatedLocalClass.dot | 10 +- .../testData/resolve/cfg/jumps.dot | 36 +- .../resolve/cfg/localClassesWithImplicit.dot | 10 +- .../testData/resolve/cfg/loops.dot | 42 +- .../testData/resolve/cfg/tryCatch.dot | 80 ++-- .../cfg/variableInitializedInTryBlock.dot | 2 +- .../resolve/smartcasts/loops/endlessLoops.dot | 176 ++++---- .../resolve/smartcasts/smartcastToNothing.dot | 72 +-- .../dfa/cfg/ControlFlowGraphBuilder.kt | 414 ++++++++---------- .../fir/resolve/dfa/cfg/ControlFlowGraph.kt | 14 +- 10 files changed, 390 insertions(+), 466 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/annotatedLocalClass.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/annotatedLocalClass.dot index 14e9cd540b3..2bf107e6bdc 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/annotatedLocalClass.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/annotatedLocalClass.dot @@ -50,13 +50,13 @@ digraph annotatedLocalClass_kt { 20 [label="Function call: R|/bar|()" style="filled" fillcolor=yellow]; 21 [label="Exit block"]; } - subgraph cluster_7 { - color=blue - 23 [label="Enter class Local" style="filled" fillcolor=red]; - 24 [label="Exit class Local" style="filled" fillcolor=red]; - } 22 [label="Exit function foo" style="filled" fillcolor=red]; } + subgraph cluster_7 { + color=blue + 23 [label="Enter class Local" style="filled" fillcolor=red]; + 24 [label="Exit class Local" style="filled" fillcolor=red]; + } 5 -> {6}; 6 -> {7}; 7 -> {8}; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/jumps.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/jumps.dot index 9b37d0dc49f..2b7597f1808 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/jumps.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/jumps.dot @@ -190,19 +190,19 @@ digraph jumps_kt { 68 [label="Access variable R|/x|"]; 69 [label="Type operator: (R|/x| as R|kotlin/Int|)"]; 70 [label="Jump: break@@@[Boolean(true)] "]; - 71 [label="Stub" style="filled" fillcolor=gray]; - 72 [label="Exit block" style="filled" fillcolor=gray]; + 77 [label="Stub" style="filled" fillcolor=gray]; + 78 [label="Exit block" style="filled" fillcolor=gray]; } - 73 [label="Exit loop block" style="filled" fillcolor=gray]; + 79 [label="Exit loop block" style="filled" fillcolor=gray]; } - 74 [label="Exit whileloop"]; + 71 [label="Exit whileloop"]; } - 75 [label="Access variable R|/x|"]; - 76 [label="Smart cast: R|/x|"]; - 77 [label="Function call: R|/x|.R|kotlin/Int.inc|()" style="filled" fillcolor=yellow]; - 78 [label="Exit block"]; + 72 [label="Access variable R|/x|"]; + 73 [label="Smart cast: R|/x|"]; + 74 [label="Function call: R|/x|.R|kotlin/Int.inc|()" style="filled" fillcolor=yellow]; + 75 [label="Exit block"]; } - 79 [label="Exit function test_3" style="filled" fillcolor=red]; + 76 [label="Exit function test_3" style="filled" fillcolor=red]; } 60 -> {61}; 61 -> {62}; @@ -210,21 +210,21 @@ digraph jumps_kt { 63 -> {64}; 64 -> {65}; 65 -> {66}; - 65 -> {74} [style=dotted]; + 65 -> {71} [style=dotted]; 66 -> {67}; 67 -> {68}; 68 -> {69}; 69 -> {70}; - 70 -> {74}; - 70 -> {71} [style=dotted]; - 71 -> {72} [style=dotted]; - 72 -> {73} [style=dotted]; - 73 -> {63} [color=green style=dotted]; + 70 -> {71}; + 70 -> {77} [style=dotted]; + 71 -> {72}; + 72 -> {73}; + 73 -> {74}; 74 -> {75}; 75 -> {76}; - 76 -> {77}; - 77 -> {78}; - 78 -> {79}; + 77 -> {78} [style=dotted]; + 78 -> {79} [style=dotted]; + 79 -> {63} [color=green style=dotted]; subgraph cluster_20 { color=red diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/localClassesWithImplicit.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/localClassesWithImplicit.dot index 83e6ddee313..a21ddd0fce2 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/localClassesWithImplicit.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/localClassesWithImplicit.dot @@ -64,13 +64,13 @@ digraph localClassesWithImplicit_kt { 26 [label="Variable declaration: lval x: R||"]; 27 [label="Exit block"]; } - subgraph cluster_8 { - color=blue - 29 [label="Enter class A" style="filled" fillcolor=red]; - 30 [label="Exit class A" style="filled" fillcolor=red]; - } 28 [label="Exit function test" style="filled" fillcolor=red]; } + subgraph cluster_8 { + color=blue + 29 [label="Enter class A" style="filled" fillcolor=red]; + 30 [label="Exit class A" style="filled" fillcolor=red]; + } 7 -> {8}; 8 -> {9}; 9 -> {10}; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/loops.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/loops.dot index 4649336e823..d60e1cff6ad 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/loops.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/loops.dot @@ -276,22 +276,22 @@ digraph loops_kt { color=blue 94 [label="Enter block"]; 95 [label="Jump: break@@@[Boolean(true)] "]; - 96 [label="Stub" style="filled" fillcolor=gray]; - 97 [label="Exit block" style="filled" fillcolor=gray]; + 100 [label="Stub" style="filled" fillcolor=gray]; + 101 [label="Exit block" style="filled" fillcolor=gray]; } - 98 [label="Exit when branch result" style="filled" fillcolor=gray]; - 99 [label="Exit when"]; + 102 [label="Exit when branch result" style="filled" fillcolor=gray]; + 103 [label="Exit when"]; } - 100 [label="Exit block"]; + 104 [label="Exit block"]; } - 101 [label="Exit loop block"]; + 105 [label="Exit loop block"]; } - 102 [label="Exit whileloop"]; + 96 [label="Exit whileloop"]; } - 103 [label="Const: Int(1)"]; - 104 [label="Exit block"]; + 97 [label="Const: Int(1)"]; + 98 [label="Exit block"]; } - 105 [label="Exit function testWhileTrueWithBreak" style="filled" fillcolor=red]; + 99 [label="Exit function testWhileTrueWithBreak" style="filled" fillcolor=red]; } 80 -> {81}; 81 -> {82}; @@ -299,27 +299,27 @@ digraph loops_kt { 83 -> {84}; 84 -> {85}; 85 -> {86}; - 85 -> {102} [style=dotted]; + 85 -> {96} [style=dotted]; 86 -> {87}; 87 -> {88}; 88 -> {89}; 89 -> {90}; 90 -> {91}; 91 -> {92 93}; - 92 -> {99}; + 92 -> {103}; 93 -> {94}; 94 -> {95}; - 95 -> {102}; - 95 -> {96} [style=dotted]; - 96 -> {97} [style=dotted]; - 97 -> {98} [style=dotted]; - 98 -> {99} [style=dotted]; - 99 -> {100}; - 100 -> {101}; - 101 -> {83} [color=green style=dashed]; - 102 -> {103}; + 95 -> {96}; + 95 -> {100} [style=dotted]; + 96 -> {97}; + 97 -> {98}; + 98 -> {99}; + 100 -> {101} [style=dotted]; + 101 -> {102} [style=dotted]; + 102 -> {103} [style=dotted]; 103 -> {104}; 104 -> {105}; + 105 -> {83} [color=green style=dashed]; subgraph cluster_34 { color=red diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot index b7c3052e3e4..a89841a6536 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot @@ -27,12 +27,12 @@ digraph tryCatch_kt { subgraph cluster_5 { color=blue 9 [label="Catch enter"]; - 10 [label="Variable declaration: e: R|kotlin/Exception|"]; + 10 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; subgraph cluster_6 { color=blue 11 [label="Enter block"]; - 12 [label="Const: Int(3)"]; - 13 [label="Variable declaration: lval z: R|kotlin/Int|"]; + 12 [label="Const: Int(2)"]; + 13 [label="Variable declaration: lval y: R|kotlin/Int|"]; 14 [label="Exit block"]; } 15 [label="Catch exit"]; @@ -40,12 +40,12 @@ digraph tryCatch_kt { subgraph cluster_7 { color=blue 16 [label="Catch enter"]; - 17 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; + 17 [label="Variable declaration: e: R|kotlin/Exception|"]; subgraph cluster_8 { color=blue 18 [label="Enter block"]; - 19 [label="Const: Int(2)"]; - 20 [label="Variable declaration: lval y: R|kotlin/Int|"]; + 19 [label="Const: Int(3)"]; + 20 [label="Variable declaration: lval z: R|kotlin/Int|"]; 21 [label="Exit block"]; } 22 [label="Catch exit"]; @@ -224,11 +224,11 @@ digraph tryCatch_kt { subgraph cluster_31 { color=blue 84 [label="Catch enter"]; - 85 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; + 85 [label="Variable declaration: e: R|kotlin/Exception|"]; subgraph cluster_32 { color=blue 86 [label="Enter block"]; - 87 [label="Jump: break@@@[Boolean(true)] "]; + 87 [label="Jump: continue@@@[Boolean(true)] "]; 88 [label="Stub" style="filled" fillcolor=gray]; 89 [label="Exit block" style="filled" fillcolor=gray]; } @@ -236,32 +236,32 @@ digraph tryCatch_kt { } subgraph cluster_33 { color=blue - 95 [label="Catch enter"]; - 96 [label="Variable declaration: e: R|kotlin/Exception|"]; + 91 [label="Catch enter"]; + 92 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; subgraph cluster_34 { color=blue - 97 [label="Enter block"]; - 98 [label="Jump: continue@@@[Boolean(true)] "]; - 99 [label="Stub" style="filled" fillcolor=gray]; - 100 [label="Exit block" style="filled" fillcolor=gray]; + 93 [label="Enter block"]; + 94 [label="Jump: break@@@[Boolean(true)] "]; + 100 [label="Stub" style="filled" fillcolor=gray]; + 101 [label="Exit block" style="filled" fillcolor=gray]; } - 101 [label="Catch exit" style="filled" fillcolor=gray]; + 102 [label="Catch exit" style="filled" fillcolor=gray]; } - 102 [label="Try expression exit"]; + 103 [label="Try expression exit"]; } - 103 [label="Const: Int(2)"]; - 104 [label="Variable declaration: lval y: R|kotlin/Int|"]; - 105 [label="Exit block"]; + 104 [label="Const: Int(2)"]; + 105 [label="Variable declaration: lval y: R|kotlin/Int|"]; + 106 [label="Exit block"]; } - 106 [label="Exit loop block"]; + 107 [label="Exit loop block"]; } - 91 [label="Exit whileloop"]; + 95 [label="Exit whileloop"]; } - 92 [label="Const: Int(3)"]; - 93 [label="Variable declaration: lval z: R|kotlin/Int|"]; - 94 [label="Exit block"]; + 96 [label="Const: Int(3)"]; + 97 [label="Variable declaration: lval z: R|kotlin/Int|"]; + 98 [label="Exit block"]; } - 107 [label="Exit function test_3" style="filled" fillcolor=red]; + 99 [label="Exit function test_3" style="filled" fillcolor=red]; } 44 -> {45}; 45 -> {46}; @@ -269,10 +269,10 @@ digraph tryCatch_kt { 47 -> {48}; 48 -> {49}; 49 -> {50}; - 49 -> {91} [style=dotted]; + 49 -> {95} [style=dotted]; 50 -> {51}; 51 -> {52}; - 52 -> {53 84 95}; + 52 -> {53 84 91}; 53 -> {54}; 54 -> {55}; 55 -> {56}; @@ -282,7 +282,7 @@ digraph tryCatch_kt { 59 -> {66}; 60 -> {61}; 61 -> {62}; - 62 -> {107}; + 62 -> {99}; 62 -> {63} [style=dotted]; 63 -> {64} [style=dotted]; 64 -> {65} [style=dotted]; @@ -298,40 +298,40 @@ digraph tryCatch_kt { 74 -> {81}; 75 -> {76}; 76 -> {77}; - 77 -> {91}; + 77 -> {95}; 77 -> {78} [style=dotted]; 78 -> {79} [style=dotted]; 79 -> {80} [style=dotted]; 80 -> {81} [style=dotted]; 81 -> {82}; 82 -> {83}; - 83 -> {84 95 102}; + 83 -> {84 91 103}; 84 -> {85}; - 84 -> {107} [label="onUncaughtException"]; + 84 -> {99} [label="onUncaughtException"]; 85 -> {86}; 86 -> {87}; - 87 -> {91}; + 87 -> {47} [color=green style=dashed]; 87 -> {88} [style=dotted]; 88 -> {89} [style=dotted]; 89 -> {90} [style=dotted]; - 90 -> {102} [style=dotted]; + 90 -> {103} [style=dotted]; 91 -> {92}; + 91 -> {99} [label="onUncaughtException"]; 92 -> {93}; 93 -> {94}; - 94 -> {107}; + 94 -> {95}; + 94 -> {100} [style=dotted]; 95 -> {96}; - 95 -> {107} [label="onUncaughtException"]; 96 -> {97}; 97 -> {98}; - 98 -> {47} [color=green style=dashed]; - 98 -> {99} [style=dotted]; - 99 -> {100} [style=dotted]; + 98 -> {99}; 100 -> {101} [style=dotted]; 101 -> {102} [style=dotted]; - 102 -> {103}; + 102 -> {103} [style=dotted]; 103 -> {104}; 104 -> {105}; 105 -> {106}; - 106 -> {47} [color=green style=dashed]; + 106 -> {107}; + 107 -> {47} [color=green style=dashed]; } diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot index 7e6c7291fcb..fed57d116cb 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot @@ -83,7 +83,7 @@ digraph variableInitializedInTryBlock_kt { 23 -> {24}; 24 -> {25}; 25 -> {26}; - 25 -> {30} [label="return@/test"]; + 25 -> {30} [label="onUncaughtException"]; 26 -> {27}; 27 -> {28}; 28 -> {29}; diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/loops/endlessLoops.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/loops/endlessLoops.dot index 868c9eb5683..f6951f8c031 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/loops/endlessLoops.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/loops/endlessLoops.dot @@ -55,24 +55,24 @@ digraph endlessLoops_kt { color=blue 20 [label="Enter block"]; 21 [label="Jump: break@@@[Boolean(true)] "]; - 22 [label="Stub" style="filled" fillcolor=gray]; - 23 [label="Exit block" style="filled" fillcolor=gray]; + 28 [label="Stub" style="filled" fillcolor=gray]; + 29 [label="Exit block" style="filled" fillcolor=gray]; } - 24 [label="Exit when branch result" style="filled" fillcolor=gray]; - 25 [label="Exit when"]; + 30 [label="Exit when branch result" style="filled" fillcolor=gray]; + 31 [label="Exit when"]; } - 26 [label="Exit block"]; + 32 [label="Exit block"]; } - 27 [label="Exit loop block"]; + 33 [label="Exit loop block"]; } - 28 [label="Exit whileloop"]; + 22 [label="Exit whileloop"]; } - 29 [label="Access variable R|/x|"]; - 30 [label="Smart cast: R|/x|"]; - 31 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; - 32 [label="Exit block"]; + 23 [label="Access variable R|/x|"]; + 24 [label="Smart cast: R|/x|"]; + 25 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; + 26 [label="Exit block"]; } - 33 [label="Exit function test_1" style="filled" fillcolor=red]; + 27 [label="Exit function test_1" style="filled" fillcolor=red]; } 4 -> {5}; 5 -> {6}; @@ -80,7 +80,7 @@ digraph endlessLoops_kt { 7 -> {8}; 8 -> {9}; 9 -> {10}; - 9 -> {28} [style=dotted]; + 9 -> {22} [style=dotted]; 10 -> {11}; 11 -> {12}; 12 -> {13}; @@ -89,22 +89,22 @@ digraph endlessLoops_kt { 15 -> {16}; 16 -> {17}; 17 -> {18 19}; - 18 -> {25}; + 18 -> {31}; 19 -> {20}; 20 -> {21}; - 21 -> {28}; - 21 -> {22} [style=dotted]; - 22 -> {23} [style=dotted]; - 23 -> {24} [style=dotted]; - 24 -> {25} [style=dotted]; + 21 -> {22}; + 21 -> {28} [style=dotted]; + 22 -> {23}; + 23 -> {24}; + 24 -> {25}; 25 -> {26}; 26 -> {27}; - 27 -> {7} [color=green style=dashed]; - 28 -> {29}; - 29 -> {30}; - 30 -> {31}; + 28 -> {29} [style=dotted]; + 29 -> {30} [style=dotted]; + 30 -> {31} [style=dotted]; 31 -> {32}; 32 -> {33}; + 33 -> {7} [color=green style=dashed]; subgraph cluster_11 { color=red @@ -144,24 +144,24 @@ digraph endlessLoops_kt { 49 [label="Access variable R|/x|"]; 50 [label="Type operator: (R|/x| as R|A|)"]; 51 [label="Jump: break@@@[Boolean(true)] "]; - 52 [label="Stub" style="filled" fillcolor=gray]; - 53 [label="Exit block" style="filled" fillcolor=gray]; + 58 [label="Stub" style="filled" fillcolor=gray]; + 59 [label="Exit block" style="filled" fillcolor=gray]; } - 54 [label="Exit when branch result" style="filled" fillcolor=gray]; - 55 [label="Exit when"]; + 60 [label="Exit when branch result" style="filled" fillcolor=gray]; + 61 [label="Exit when"]; } - 56 [label="Exit block"]; + 62 [label="Exit block"]; } - 57 [label="Exit loop block"]; + 63 [label="Exit loop block"]; } - 58 [label="Exit whileloop"]; + 52 [label="Exit whileloop"]; } - 59 [label="Access variable R|/x|"]; - 60 [label="Smart cast: R|/x|"]; - 61 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; - 62 [label="Exit block"]; + 53 [label="Access variable R|/x|"]; + 54 [label="Smart cast: R|/x|"]; + 55 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; + 56 [label="Exit block"]; } - 63 [label="Exit function test_2" style="filled" fillcolor=red]; + 57 [label="Exit function test_2" style="filled" fillcolor=red]; } 34 -> {35}; 35 -> {36}; @@ -169,31 +169,31 @@ digraph endlessLoops_kt { 37 -> {38}; 38 -> {39}; 39 -> {40}; - 39 -> {58} [style=dotted]; + 39 -> {52} [style=dotted]; 40 -> {41}; 41 -> {42}; 42 -> {43}; 43 -> {44}; 44 -> {45}; 45 -> {46 47}; - 46 -> {55}; + 46 -> {61}; 47 -> {48}; 48 -> {49}; 49 -> {50}; 50 -> {51}; - 51 -> {58}; - 51 -> {52} [style=dotted]; - 52 -> {53} [style=dotted]; - 53 -> {54} [style=dotted]; - 54 -> {55} [style=dotted]; + 51 -> {52}; + 51 -> {58} [style=dotted]; + 52 -> {53}; + 53 -> {54}; + 54 -> {55}; 55 -> {56}; 56 -> {57}; - 57 -> {37} [color=green style=dashed]; - 58 -> {59}; - 59 -> {60}; - 60 -> {61}; + 58 -> {59} [style=dotted]; + 59 -> {60} [style=dotted]; + 60 -> {61} [style=dotted]; 61 -> {62}; 62 -> {63}; + 63 -> {37} [color=green style=dashed]; subgraph cluster_20 { color=red @@ -254,24 +254,24 @@ digraph endlessLoops_kt { color=blue 92 [label="Enter block"]; 93 [label="Jump: break@@@[Boolean(true)] "]; - 94 [label="Stub" style="filled" fillcolor=gray]; - 95 [label="Exit block" style="filled" fillcolor=gray]; + 100 [label="Stub" style="filled" fillcolor=gray]; + 101 [label="Exit block" style="filled" fillcolor=gray]; } - 96 [label="Exit when branch result" style="filled" fillcolor=gray]; - 97 [label="Exit when"]; + 102 [label="Exit when branch result" style="filled" fillcolor=gray]; + 103 [label="Exit when"]; } - 98 [label="Exit block"]; + 104 [label="Exit block"]; } - 99 [label="Exit loop block"]; + 105 [label="Exit loop block"]; } - 100 [label="Exit whileloop"]; + 94 [label="Exit whileloop"]; } - 101 [label="Access variable R|/x|"]; - 102 [label="Smart cast: R|/x|"]; - 103 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; - 104 [label="Exit block"]; + 95 [label="Access variable R|/x|"]; + 96 [label="Smart cast: R|/x|"]; + 97 [label="Function call: R|/x|.R|/A.foo|()" style="filled" fillcolor=yellow]; + 98 [label="Exit block"]; } - 105 [label="Exit function test_3" style="filled" fillcolor=red]; + 99 [label="Exit function test_3" style="filled" fillcolor=red]; } 64 -> {65}; 65 -> {66}; @@ -279,7 +279,7 @@ digraph endlessLoops_kt { 67 -> {68}; 68 -> {69}; 69 -> {70}; - 69 -> {100} [style=dotted]; + 69 -> {94} [style=dotted]; 70 -> {71}; 71 -> {72}; 72 -> {73}; @@ -291,7 +291,7 @@ digraph endlessLoops_kt { 78 -> {85}; 79 -> {80}; 80 -> {81}; - 81 -> {100}; + 81 -> {94}; 81 -> {82} [style=dotted]; 82 -> {83} [style=dotted]; 83 -> {84} [style=dotted]; @@ -301,22 +301,22 @@ digraph endlessLoops_kt { 87 -> {88}; 88 -> {89}; 89 -> {90 91}; - 90 -> {97}; + 90 -> {103}; 91 -> {92}; 92 -> {93}; - 93 -> {100}; - 93 -> {94} [style=dotted]; - 94 -> {95} [style=dotted]; - 95 -> {96} [style=dotted]; - 96 -> {97} [style=dotted]; + 93 -> {94}; + 93 -> {100} [style=dotted]; + 94 -> {95}; + 95 -> {96}; + 96 -> {97}; 97 -> {98}; 98 -> {99}; - 99 -> {67} [color=green style=dashed]; - 100 -> {101}; - 101 -> {102}; - 102 -> {103}; + 100 -> {101} [style=dotted]; + 101 -> {102} [style=dotted]; + 102 -> {103} [style=dotted]; 103 -> {104}; 104 -> {105}; + 105 -> {67} [color=green style=dashed]; subgraph cluster_32 { color=red @@ -363,18 +363,18 @@ digraph endlessLoops_kt { 127 [label="Exit when"]; } 128 [label="Jump: break@@@[Boolean(true)] "]; - 129 [label="Stub" style="filled" fillcolor=gray]; - 130 [label="Exit block" style="filled" fillcolor=gray]; + 134 [label="Stub" style="filled" fillcolor=gray]; + 135 [label="Exit block" style="filled" fillcolor=gray]; } - 131 [label="Exit loop block" style="filled" fillcolor=gray]; + 136 [label="Exit loop block" style="filled" fillcolor=gray]; } - 132 [label="Exit whileloop"]; + 129 [label="Exit whileloop"]; } - 133 [label="Access variable R|/x|"]; - 134 [label="Function call: R|/x|.#()" style="filled" fillcolor=yellow]; - 135 [label="Exit block"]; + 130 [label="Access variable R|/x|"]; + 131 [label="Function call: R|/x|.#()" style="filled" fillcolor=yellow]; + 132 [label="Exit block"]; } - 136 [label="Exit function test_4" style="filled" fillcolor=red]; + 133 [label="Exit function test_4" style="filled" fillcolor=red]; } 106 -> {107}; 107 -> {108}; @@ -382,7 +382,7 @@ digraph endlessLoops_kt { 109 -> {110}; 110 -> {111}; 111 -> {112}; - 111 -> {132} [style=dotted]; + 111 -> {129} [style=dotted]; 112 -> {113}; 113 -> {114}; 114 -> {115}; @@ -394,21 +394,21 @@ digraph endlessLoops_kt { 120 -> {121}; 121 -> {122}; 122 -> {123}; - 123 -> {132}; + 123 -> {129}; 123 -> {124} [style=dotted]; 124 -> {125} [style=dotted]; 125 -> {126} [style=dotted]; 126 -> {127} [style=dotted]; 127 -> {128}; - 128 -> {132}; - 128 -> {129} [style=dotted]; - 129 -> {130} [style=dotted]; - 130 -> {131} [style=dotted]; - 131 -> {109} [color=green style=dotted]; + 128 -> {129}; + 128 -> {134} [style=dotted]; + 129 -> {130}; + 130 -> {131}; + 131 -> {132}; 132 -> {133}; - 133 -> {134}; - 134 -> {135}; - 135 -> {136}; + 134 -> {135} [style=dotted]; + 135 -> {136} [style=dotted]; + 136 -> {109} [color=green style=dotted]; subgraph cluster_41 { color=red diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastToNothing.dot b/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastToNothing.dot index 2bedbc0c1f0..407309caa15 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastToNothing.dot +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/smartcastToNothing.dot @@ -214,23 +214,23 @@ digraph smartcastToNothing_kt { color=blue 82 [label="Enter block" style="filled" fillcolor=gray]; 83 [label="Jump: break@@@[R|/|.R|SubstitutionOverride|()] " style="filled" fillcolor=gray]; - 84 [label="Stub" style="filled" fillcolor=gray]; - 85 [label="Exit block" style="filled" fillcolor=gray]; + 94 [label="Stub" style="filled" fillcolor=gray]; + 95 [label="Exit block" style="filled" fillcolor=gray]; } - 86 [label="Exit when branch result" style="filled" fillcolor=gray]; - 87 [label="Exit when" style="filled" fillcolor=gray]; + 96 [label="Exit when branch result" style="filled" fillcolor=gray]; + 97 [label="Exit when" style="filled" fillcolor=gray]; } - 88 [label="Exit block" style="filled" fillcolor=gray]; + 98 [label="Exit block" style="filled" fillcolor=gray]; } - 89 [label="Exit loop block" style="filled" fillcolor=gray]; + 99 [label="Exit loop block" style="filled" fillcolor=gray]; } - 90 [label="Exit whileloop"]; + 84 [label="Exit whileloop"]; } - 91 [label="Exit block"]; + 85 [label="Exit block"]; } - 92 [label="Access variable R|/s|"]; - 93 [label="Enter safe call"]; - 94 [label="Postponed enter to lambda"]; + 86 [label="Access variable R|/s|"]; + 87 [label="Enter safe call"]; + 88 [label="Postponed enter to lambda"]; subgraph cluster_24 { color=blue 100 [label="Enter function anonymousFunction" style="filled" fillcolor=red]; @@ -243,12 +243,12 @@ digraph smartcastToNothing_kt { } 105 [label="Exit function anonymousFunction" style="filled" fillcolor=red]; } - 95 [label="Postponed exit from lambda"]; - 96 [label="Function call: $subj$.R|kotlin/let|(...)" style="filled" fillcolor=yellow]; - 97 [label="Exit safe call"]; - 98 [label="Exit block"]; + 89 [label="Postponed exit from lambda"]; + 90 [label="Function call: $subj$.R|kotlin/let|(...)" style="filled" fillcolor=yellow]; + 91 [label="Exit safe call"]; + 92 [label="Exit block"]; } - 99 [label="Exit function test_0" style="filled" fillcolor=red]; + 93 [label="Exit function test_0" style="filled" fillcolor=red]; } 52 -> {53}; 53 -> {54}; @@ -262,52 +262,52 @@ digraph smartcastToNothing_kt { 61 -> {62}; 62 -> {63}; 63 -> {64}; - 64 -> {65 90}; + 64 -> {65 84}; 65 -> {66}; 66 -> {67}; 67 -> {68}; - 68 -> {99} [label="onUncaughtException"]; + 68 -> {93} [label="onUncaughtException"]; 68 -> {69} [style=dotted]; 69 -> {70} [style=dotted]; 70 -> {71} [style=dotted]; - 71 -> {99} [style=dotted label="onUncaughtException"]; + 71 -> {93} [style=dotted label="onUncaughtException"]; 71 -> {72} [style=dotted]; 72 -> {73} [style=dotted]; 73 -> {74} [style=dotted]; 74 -> {75} [style=dotted]; 75 -> {76} [style=dotted]; - 76 -> {99} [style=dotted label="onUncaughtException"]; + 76 -> {93} [style=dotted label="onUncaughtException"]; 76 -> {77} [style=dotted]; 77 -> {78} [style=dotted]; 78 -> {79} [style=dotted]; 79 -> {80 81} [style=dotted]; - 80 -> {87} [style=dotted]; + 80 -> {97} [style=dotted]; 81 -> {82} [style=dotted]; 82 -> {83} [style=dotted]; - 83 -> {84 90} [style=dotted]; - 84 -> {85} [style=dotted]; - 85 -> {86} [style=dotted]; - 86 -> {87} [style=dotted]; - 87 -> {88} [style=dotted]; + 83 -> {84 94} [style=dotted]; + 84 -> {85}; + 85 -> {86}; + 86 -> {87 91}; + 87 -> {88}; + 88 -> {90 100}; 88 -> {89} [style=dotted]; - 89 -> {61} [color=green style=dotted]; + 88 -> {100} [style=dashed]; + 89 -> {90}; 90 -> {91}; 91 -> {92}; - 92 -> {93 97}; - 93 -> {94}; - 94 -> {96 100}; + 92 -> {93}; 94 -> {95} [style=dotted]; - 94 -> {100} [style=dashed]; - 95 -> {96}; - 96 -> {97}; - 97 -> {98}; - 98 -> {99}; + 95 -> {96} [style=dotted]; + 96 -> {97} [style=dotted]; + 97 -> {98} [style=dotted]; + 98 -> {99} [style=dotted]; + 99 -> {61} [color=green style=dotted]; 100 -> {101}; 101 -> {102}; 102 -> {103}; 103 -> {104}; 104 -> {105}; - 105 -> {95}; + 105 -> {89}; subgraph cluster_26 { color=red 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 b87e2e99ec2..30447355615 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 @@ -75,12 +75,10 @@ class ControlFlowGraphBuilder { private val binaryLogicExpressionExitNodes: Stack> = stackOf() private val tryExitNodes: NodeStorage = NodeStorage() - private val tryMainExitNodes: NodeStorage = NodeStorage() - private val catchNodeStorages: Stack> = stackOf() - private val catchNodeStorage: NodeStorage get() = catchNodeStorages.top() - private val catchExitNodeStorages: Stack> = stackOf() + private val catchNodes: Stack> = stackOf() + private val catchBlocksInProgress: Stack = stackOf() private val finallyEnterNodes: Stack = stackOf() - private val finallyExitNodes: NodeStorage = NodeStorage() + private val finallyBlocksInProgress: Stack = stackOf() private val initBlockExitNodes: Stack = stackOf() @@ -684,12 +682,7 @@ class ControlFlowGraphBuilder { fun exitJump(jump: FirJump<*>): JumpNode { val node = createJumpNode(jump) - val nextNode = when (jump) { - is FirReturnExpression -> exitTargetsForReturn[jump.target.labeledElement.symbol] - is FirContinueExpression -> loopConditionEnterNodes[jump.target.labeledElement.condition] - is FirBreakExpression -> loopExitNodes[jump.target.labeledElement] - else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}") - } + addNonTerminatingNode(node) if (jump is FirReturnExpression && jump.target.labeledElement is FirAnonymousFunction) { // TODO: these should be DFA-only edges; they should be pointed into the postponed function exit node? @@ -698,33 +691,41 @@ class ControlFlowGraphBuilder { postponedLambdaExits.pop() } - val labelForFinallyBlock = when (jump) { - is FirReturnExpression -> ReturnPath(jump.target.labeledElement.symbol) - is FirLoopJump -> LoopPath(jump) - else -> NormalPath + val nextNode = when (jump) { + is FirReturnExpression -> exitTargetsForReturn[jump.target.labeledElement.symbol] + is FirContinueExpression -> loopConditionEnterNodes[jump.target.labeledElement.condition] + is FirBreakExpression -> loopExitNodes[jump.target.labeledElement] + else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}") + } ?: return node + val nextFinally = finallyEnterNodes.topOrNull()?.takeIf { it.level > nextNode.level } + if (nextFinally != null) { + addEdge(node, nextFinally, propagateDeadness = false, label = nextNode.returnPathLabel) + nonDirectJumps.put(nextNode, node) + } else if (nextNode.returnPathIsBackwards) { + addBackEdge(node, nextNode) + } else { + addEdge(node, nextNode, propagateDeadness = false) } - - // while (...) continue // <- jump back - // do continue while (...) // <- jump forward (block exit node not created yet) - // do ... while (continue) // <- jump back (to itself), believe it or not - val isBack = nextNode is LoopConditionEnterNode && - (nextNode.loop !is FirDoWhileLoop || nextNode.previousNodes.any { it is LoopBlockExitNode }) - popAndAddEdge(node) - if (nextNode != null) { - if (isBack) { - addBackEdge(node, nextNode) // TODO: shouldn't this go through finally too? - } else { - val hadFinally = addEdgeThroughFinally(node, nextNode, labelForFinallyBlock = labelForFinallyBlock) - if (hadFinally && jump is FirReturnExpression) { - //actually we can store all returns like this, but not sure if it makes anything better - nonDirectJumps.put(nextNode, node) - } - } - } - addWithStub(node) return node } + private val CFGNode<*>.returnPathLabel: EdgeLabel + get() = when (this) { + is FunctionExitNode -> ReturnPath(fir.symbol) + is LoopConditionEnterNode -> LoopContinuePath(loop) + is LoopExitNode -> LoopBreakPath(fir) + else -> throw IllegalStateException("not a labeled jump target: $this") + } + + // while (x) { continue } + // ^------------/ back + // do { continue } while (x) + // \---------^ forward + // do { x } while (continue) + // ^-------/ back + private val CFGNode<*>.returnPathIsBackwards: Boolean + get() = this is LoopConditionEnterNode && (loop !is FirDoWhileLoop || previousNodes.any { it is LoopBlockExitNode }) + // ----------------------------------- When ----------------------------------- fun enterWhenExpression(whenExpression: FirWhenExpression): WhenEnterNode { @@ -923,146 +924,161 @@ class ControlFlowGraphBuilder { // ----------------------------------- Try-catch-finally ----------------------------------- fun enterTryExpression(tryExpression: FirTryExpression): Pair { - catchNodeStorages.push(NodeStorage()) - catchExitNodeStorages.push(NodeStorage()) val enterTryExpressionNode = createTryExpressionEnterNode(tryExpression) + val exitTryExpressionNode = createTryExpressionExitNode(tryExpression) addNewSimpleNode(enterTryExpressionNode) - val tryExitNode = createTryExpressionExitNode(tryExpression) - tryExitNodes.push(tryExitNode) - levelCounter++ - val enterTryNodeBlock = createTryMainBlockEnterNode(tryExpression) - addNewSimpleNode(enterTryNodeBlock) - - val exitTryNodeBlock = createTryMainBlockExitNode(tryExpression) - tryMainExitNodes.push(exitTryNodeBlock) - - for (catch in tryExpression.catches) { - val catchNode = createCatchClauseEnterNode(catch) - catchNodeStorage.push(catchNode) - // a flow where an exception of interest is thrown and caught before executing any of try-main block. - addEdge(enterTryExpressionNode, catchNode) - } + tryExitNodes.push(exitTryExpressionNode) levelCounter++ + val enterTryMainBlockNode = createTryMainBlockEnterNode(tryExpression) + addNewSimpleNode(enterTryMainBlockNode) + + catchNodes.push(tryExpression.catches.map { createCatchClauseEnterNode(it) }) if (tryExpression.finallyBlock != null) { - val finallyEnterNode = createFinallyBlockEnterNode(tryExpression) - // a flow where an uncaught exception is thrown before executing any of try-main block. - addEdge(enterTryExpressionNode, finallyEnterNode, propagateDeadness = false, label = UncaughtExceptionPath) - finallyEnterNodes.push(finallyEnterNode) - finallyExitNodes.push(createFinallyBlockExitNode(tryExpression)) + finallyEnterNodes.push(createFinallyBlockEnterNode(tryExpression)) } + + for (catchEnterNode in catchNodes.top()) { + addEdge(enterTryExpressionNode, catchEnterNode) + } + if (tryExpression.finallyBlock != null) { + addEdge(enterTryExpressionNode, finallyEnterNodes.top(), label = UncaughtExceptionPath) + } + notCompletedFunctionCalls.push(mutableListOf()) splitDataFlowForPostponedLambdas() - return enterTryExpressionNode to enterTryNodeBlock + levelCounter++ + return enterTryExpressionNode to enterTryMainBlockNode } fun exitTryMainBlock(): TryMainBlockExitNode { levelCounter-- - val node = tryMainExitNodes.top() + val exitTryExpressionNode = tryExitNodes.top() + val node = createTryMainBlockExitNode(exitTryExpressionNode.fir) popAndAddEdge(node) - node.updateDeadStatus() - val finallyEnterNode = finallyEnterNodes.topOrNull() - // NB: Check the level to avoid adding an edge to the finally block at an upper level. - if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) { - // TODO: in case of return/continue/break in try main block, we need a unique label. - addEdge(node, finallyEnterNode) - //in case try exit is dead, but there is other edges to finally (eg return) - // actually finallyEnterNode can't be dead, except for the case when the whole try is dead - finallyEnterNode.updateDeadStatus() - } else { - addEdge(node, tryExitNodes.top(), propagateDeadness = false) + // try { a } catch (e) { b } [finally { c }] + // \-----------------^ + val nextNode = if (node.fir.finallyBlock != null) finallyEnterNodes.top() else exitTryExpressionNode + addEdge(node, nextNode, propagateDeadness = false) + for (catchEnterNode in catchNodes.pop().asReversed()) { + catchBlocksInProgress.push(catchEnterNode) + // At least merge the data flow from enter + exit...but this doesn't really help, + // see the comment for `addExceptionEdgesFrom`. + addEdge(node, catchEnterNode, propagateDeadness = false) } return node } fun enterCatchClause(catch: FirCatch): CatchClauseEnterNode { - return catchNodeStorage[catch]!!.also { - val tryMainExitNode = tryMainExitNodes.top() - // a flow where an exception of interest is thrown and caught after executing all of try-main block. - addEdge(tryMainExitNode, it) - //tryMainExitNode might be dead (eg main block contains return), but it doesn't mean catch block is also dead - it.updateDeadStatus() - val finallyEnterNode = finallyEnterNodes.topOrNull() - // a flow where an uncaught exception is thrown before executing any of catch clause. - // NB: Check the level to avoid adding an edge to the finally block at an upper level. - if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) { - addEdge(it, finallyEnterNode, propagateDeadness = false, label = UncaughtExceptionPath) - } else { - addEdge(it, exitTargetsForTry.top(), label = UncaughtExceptionPath) - } - lastNodes.push(it) - levelCounter++ + val catchEnterNode = catchBlocksInProgress.pop() + assert(catchEnterNode.fir == catch) + if (tryExitNodes.top().fir.finallyBlock != null) { + // TODO: not sure this does anything? + addEdge(catchEnterNode, finallyEnterNodes.top(), propagateDeadness = false, label = UncaughtExceptionPath) + } else { + // TODO: this DEFINITELY does nothing, and is in fact incorrect if there are outer try-finally. + addEdge(catchEnterNode, exitTargetsForTry.top(), propagateDeadness = false, label = UncaughtExceptionPath) } + lastNodes.push(catchEnterNode) + levelCounter++ + return catchEnterNode } fun exitCatchClause(catch: FirCatch): CatchClauseExitNode { levelCounter-- - return createCatchClauseExitNode(catch).also { - popAndAddEdge(it) - val finallyEnterNode = finallyEnterNodes.topOrNull() - // NB: Check the level to avoid adding an edge to the finally block at an upper level. - if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) { - // TODO: in case of return/continue/break in catch clause, we need a unique label. - addEdge(it, finallyEnterNode, propagateDeadness = false) - } else { - addEdge(it, tryExitNodes.top(), propagateDeadness = false) - } - catchExitNodeStorages.top().push(it) - } + val exitTryExpressionNode = tryExitNodes.top() + val catchExitNode = createCatchClauseExitNode(catch) + popAndAddEdge(catchExitNode) + // try { a } catch (e1) { b } catch (e2) { c } [finally { d }] + // \------------------^ + val nextNode = if (exitTryExpressionNode.fir.finallyBlock != null) finallyEnterNodes.top() else exitTryExpressionNode + addEdge(catchExitNode, nextNode, propagateDeadness = false) + return catchExitNode } fun enterFinallyBlock(): FinallyBlockEnterNode { - val enterNode = finallyEnterNodes.pop() - lastNodes.push(enterNode) - return enterNode + return finallyEnterNodes.pop().also { + lastNodes.push(it) + finallyBlocksInProgress.push(it) + } } fun exitFinallyBlock(): FinallyBlockExitNode { - return finallyExitNodes.pop().also { finallyExit -> - popAndAddEdge(finallyExit) - val tryExitNode = tryExitNodes.top() - // a flow where either there wasn't any exception or caught if any. - addEdge(finallyExit, tryExitNode) - if (finallyExit.isDead) { - //refresh forward links, which were created before finalizing try expression (eg created by `break`) - propagateDeadnessForward(finallyExit) + val enterNode = finallyBlocksInProgress.pop() + val tryExitNode = tryExitNodes.top() + val exitNode = createFinallyBlockExitNode(enterNode.fir) + popAndAddEdge(exitNode) + val allNormalInputsAreDead = enterNode.previousNodes.all { + val edge = enterNode.incomingEdges.getValue(it) + edge.kind.isDead || edge.label != NormalPath + } + addEdge(exitNode, tryExitNode, isDead = allNormalInputsAreDead) + val nextExitLevel = addUncaughtExceptionEdgeFrom(exitNode) + // /-----------v + // f@ { try { return@f } finally { b }; c } + // \-----^ + exitNode.addReturnEdges(exitTargetsForReturn, nextExitLevel) + // /-----------v + // f@ while (x) { try { continue@f } finally { b }; c } + // ^------------------------------------/ + exitNode.addReturnEdges(loopConditionEnterNodes, nextExitLevel) + // /-----------v + // f@ while (x) { try { break@f } finally { b }; c } + // \-----^ + exitNode.addReturnEdges(loopExitNodes, nextExitLevel) + return exitNode + } + + private fun > CFGNode<*>.addReturnEdges(nodes: Stack, minLevel: Int) { + for (node in nodes.all()) { + when { + node.level <= minLevel -> break + node !in nonDirectJumps -> continue + node.returnPathIsBackwards -> addBackEdge(this, node, label = node.returnPathLabel) + else -> addEdge(this, node, propagateDeadness = false, label = node.returnPathLabel) } - // a flow that exits to the exit target while there was an uncaught exception. - //todo this edge might exist already if try has jump outside, so we effectively lose labeled edge here - addEdgeIfNotExist(finallyExit, exitTargetsForTry.top(), propagateDeadness = false, label = UncaughtExceptionPath) - // TODO: differentiate flows that return/(re)throw in try main block and/or catch clauses - // To do so, we need mappings from such distinct label to original exit target (fun exit or loop) - // Also, CFG should support multiple edges towards the same destination node } } fun exitTryExpression(callCompleted: Boolean): TryExpressionExitNode { levelCounter-- - catchNodeStorages.pop() - val catchExitNodes = catchExitNodeStorages.pop() - val tryMainExitNode = tryMainExitNodes.pop() - notCompletedFunctionCalls.pop().forEach(::completeFunctionCall) - val node = tryExitNodes.pop() mergeDataFlowFromPostponedLambdas(node, callCompleted) node.updateDeadStatus() lastNodes.push(node) - - val allCatchesAreDead = tryMainExitNode.fir.catches.all { catch -> catchExitNodes[catch]!!.isDead } - val tryMainBlockIsDead = tryMainExitNode.previousNodes.all { prev -> - prev.isDead || tryMainExitNode.incomingEdges.getValue(prev).label != NormalPath - } - if (tryMainBlockIsDead && allCatchesAreDead) { - //if try expression doesn't have any regular non-dead exits, we add stub to make everything after dead - val stub = createStubNode() - popAndAddEdge(stub) - lastNodes.push(stub) - } - return node } + private fun addUncaughtExceptionEdgeFrom(node: CFGNode<*>): Int { + val nextExit = exitTargetsForTry.top() + val nextFinally = finallyEnterNodes.topOrNull() + // TODO: this edge is probably redundant if chose `nextExit` + val nextNode = if (nextFinally != null && nextFinally.level > nextExit.level) nextFinally else nextExit + addEdge(node, nextNode, propagateDeadness = false, label = UncaughtExceptionPath) + return nextNode.level + } + + // TODO: these edges are true for literally any node in the graph. Their existence for *some* nodes + // might lead to a false sense of security, but things are broken. This should be some sort of implicit knowledge + // instead of requiring a ton of edges? + // x = "" + // try { + // x = null + // f() // throws + // x = "" + // } catch (e: Throwable) { x.length } // oops + // Just look at `enterTryExpression` - the edges it adds are literally `addExceptionEdgesFrom(tryExpressionEnterNode)`. + private fun addExceptionEdgesFrom(node: CFGNode<*>) { + val nextCatch = catchNodes.topOrNull() + if (nextCatch != null) { + for (catchEnterNode in nextCatch) { + addEdge(node, catchEnterNode, propagateDeadness = false) + } + } + addUncaughtExceptionEdgeFrom(node) + } + //this is a workaround to make function call dead when call is completed _after_ building its node in the graph //this happens when completing the last call in try/catch blocks //todo this doesn't make fully 'right' Nothing node (doesn't support going to catch and pass through finally) @@ -1076,10 +1092,9 @@ class ControlFlowGraphBuilder { CFGNode.addEdge(node, stub, EdgeKind.DeadForward, propagateDeadness = false) for ((to, edge) in edges) { val kind = if (edge.kind.isBack) EdgeKind.DeadBackward else EdgeKind.DeadForward - // TODO: `propagateDeadness` should be redundant if `propagateDeadnessForward` was correct, but it's not. CFGNode.addEdge(stub, to, kind, propagateDeadness = true, label = edge.label) + propagateDeadnessForward(to) } - stub.followingNodes.forEach { propagateDeadnessForward(it, deep = true) } } // ----------------------------------- Resolvable call ----------------------------------- @@ -1088,7 +1103,7 @@ class ControlFlowGraphBuilder { val returnsNothing = qualifiedAccessExpression.resultType.isNothing val node = createQualifiedAccessNode(qualifiedAccessExpression) if (returnsNothing) { - addNodeThatReturnsNothing(node) + addNonSuccessfullyTerminatingNode(node) } else { addNewSimpleNode(node) } @@ -1099,7 +1114,7 @@ class ControlFlowGraphBuilder { val returnsNothing = smartCastExpression.resultType.isNothing val node = createSmartCastExitNode(smartCastExpression) if (returnsNothing) { - addNodeThatReturnsNothing(node) + addNonSuccessfullyTerminatingNode(node) } else { addNewSimpleNode(node) } @@ -1135,7 +1150,7 @@ class ControlFlowGraphBuilder { val node = createFunctionCallNode(functionCall) unifyDataFlowFromPostponedLambdas(node, callCompleted) if (returnsNothing) { - addNodeThatReturnsNothing(node) + addNonSuccessfullyTerminatingNode(node) } else { addNewSimpleNode(node) } @@ -1174,7 +1189,7 @@ class ControlFlowGraphBuilder { } fun exitThrowExceptionNode(throwExpression: FirThrowExpression): ThrowExceptionNode { - return createThrowExceptionNode(throwExpression).also { addNodeThatReturnsNothing(it) } + return createThrowExceptionNode(throwExpression).also { addNonSuccessfullyTerminatingNode(it) } } fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall, callCompleted: Boolean): CheckNotNullCallNode { @@ -1182,7 +1197,7 @@ class ControlFlowGraphBuilder { val node = createCheckNotNullCallNode(checkNotNullCall) unifyDataFlowFromPostponedLambdas(node, callCompleted) if (checkNotNullCall.resultType.isNothing) { - addNodeThatReturnsNothing(node) + addNonSuccessfullyTerminatingNode(node) } else { addNewSimpleNode(node) } @@ -1395,111 +1410,22 @@ class ControlFlowGraphBuilder { addNewSimpleNode(newNode) } - private fun addWithStub(node: CFGNode<*>) { + private fun addNonTerminatingNode(node: CFGNode<*>) { + popAndAddEdge(node) val stub = createStubNode() addEdge(node, stub) lastNodes.push(stub) } - private fun addNodeThatReturnsNothing(node: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Forward) { - // If an expression, which returns Nothing, ...(1) - val targetNode = when { - tryExitNodes.isEmpty -> { - // (1)... isn't inside a try expression, that is an uncaught exception path. - exitTargetsForTry.top() - } - // (1)... inside a try expression...(2) - finallyEnterNodes.topOrNull()?.level == levelCounter -> { - // (2)... with finally - // Either in try-main or catch. Route to `finally` - // TODO: that doesn't seem right? - // fun bar(): Nothing = throw RuntimeException() - // fun main() { - // val x: Int - // try { - // try { - // x = 1 - // println(x) - // bar() // addNodeThatReturnsNothing - // } finally {} // to this finally? - // } finally { // but what about this one? - // x = 2 - // println(x) - // } - // } - // -> compiles and prints 1 then 2 - // Guessing that there should already be an UncaughtExceptionPath edge from finally 1 to finally 2 - // regardless of `bar()`, but there isn't. - finallyEnterNodes.top() - } - // (2)... without finally or within finally ...(3) - tryExitNodes.top().fir.finallyBlock == null -> { - // (3)... without finally ...(4) - // Either in try-main or catch. - val tryMainExitNode = tryMainExitNodes.top() - if (tryMainExitNode.followingNodes.isNotEmpty()) { - // (4)... in catch, i.e., re-throw. - exitTargetsForTry.top() - } else { - // (4)... in try-main. Route to exit of try main block. - // We already have edges from the exit of try main block to each catch clause. - // This edge makes the remaining part of try main block, e.g., following `when` branches, marked as dead. - tryMainExitNode - } - } - // (3)... within finally. - else -> exitTargetsForTry.top() - } - popAndAddEdge(node, preferredKind) - if (targetNode is TryMainBlockExitNode) { - val catches = targetNode.fir.catches - if (catches.isEmpty()) { - addEdgeThroughFinally(node, exitTargetsForTry.top(), label = UncaughtExceptionPath) - } else { - for (catch in catches) { - addEdge(node, catchNodeStorage[catch]!!, propagateDeadness = false, label = UncaughtExceptionPath) - } - } - } else { - addEdgeThroughFinally(node, targetNode, label = UncaughtExceptionPath) - } - addWithStub(node) - } - - private fun addEdgeThroughFinally( - node: CFGNode<*>, - targetNode: CFGNode<*>, - label: EdgeLabel = NormalPath, - labelForFinallyBlock: EdgeLabel = label - ): Boolean { - var next = node - for (finallyEnter in finallyEnterNodes.all()) { - if (finallyEnter.level <= targetNode.level) { - break - } - addEdgeIfNotExist(next, finallyEnter, propagateDeadness = false, label = labelForFinallyBlock) - next = finallyExitNodes[finallyEnter.fir]!! - } - addEdgeIfNotExist(next, targetNode, propagateDeadness = false, label = if (next == node) label else labelForFinallyBlock) - return next != node + private fun addNonSuccessfullyTerminatingNode(node: CFGNode<*>) { + addNonTerminatingNode(node) + addExceptionEdgesFrom(node) } private fun popAndAddEdge(to: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Forward) { addEdge(lastNodes.pop(), to, preferredKind = preferredKind) } - private fun addEdgeIfNotExist( - from: CFGNode<*>, - to: CFGNode<*>, - propagateDeadness: Boolean = true, - preferredKind: EdgeKind = EdgeKind.Forward, - label: EdgeLabel = NormalPath - ) { - if (!from.followingNodes.contains(to)) { - addEdge(from, to, propagateDeadness, preferredKind = preferredKind, label = label) - } - } - private fun addEdge( from: CFGNode<*>, to: CFGNode<*>, @@ -1514,22 +1440,26 @@ class ControlFlowGraphBuilder { CFGNode.addEdge(from, to, kind, propagateDeadness, label) } - private fun addBackEdge(from: CFGNode<*>, to: CFGNode<*>, isDead: Boolean = false) { + private fun addBackEdge(from: CFGNode<*>, to: CFGNode<*>, isDead: Boolean = false, label: EdgeLabel = LoopBackPath) { val kind = if (isDead || from.isDead || to.isDead) EdgeKind.DeadBackward else EdgeKind.CfgBackward - CFGNode.addEdge(from, to, kind, propagateDeadness = false, label = LoopBackPath) + CFGNode.addEdge(from, to, kind, propagateDeadness = false, label = label) } - private fun propagateDeadnessForward(node: CFGNode<*>, deep: Boolean = false) { + private fun propagateDeadnessForward(node: CFGNode<*>) { if (!node.isDead) return - node.followingNodes - .filter { node.outgoingEdges.getValue(it).kind == EdgeKind.Forward } - .forEach { target -> - CFGNode.addJustKindEdge(node, target, EdgeKind.DeadForward, false) - target.updateDeadStatus() - if (deep) { - propagateDeadnessForward(target, true) + for (next in node.followingNodes) { + val kind = node.outgoingEdges.getValue(next).kind + if (kind.isDead) continue + if (kind.isBack) { + CFGNode.addJustKindEdge(node, next, EdgeKind.DeadBackward, propagateDeadness = false) + } else { + CFGNode.addJustKindEdge(node, next, EdgeKind.DeadForward, propagateDeadness = false) + if (kind.usedInCfa) { + next.updateDeadStatus() + propagateDeadnessForward(next) } } + } } // ----------------------------------- Utils ----------------------------------- diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt index 40c89bac434..98de238fdea 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -6,8 +6,7 @@ package org.jetbrains.kotlin.fir.resolve.dfa.cfg import org.jetbrains.kotlin.fir.declarations.FirDeclaration -import org.jetbrains.kotlin.fir.expressions.FirBreakExpression -import org.jetbrains.kotlin.fir.expressions.FirLoopJump +import org.jetbrains.kotlin.fir.expressions.FirLoop import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val kind: Kind) { @@ -146,15 +145,10 @@ object LoopBackPath : EdgeLabel(label = null) { object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException") -class LoopPath( - firLoopJump: FirLoopJump -) : EdgeLabel((if (firLoopJump is FirBreakExpression) "break" else "continue") + - (firLoopJump.target.labeledElement.label?.let { "@${it.name}" } ?: "")) - // TODO: Label `return`ing edge with this. -class ReturnPath( - returnTargetSymbol: FirFunctionSymbol<*> -) : EdgeLabel(label = "return@${returnTargetSymbol.callableId}") +data class ReturnPath(val target: FirFunctionSymbol<*>) : EdgeLabel(label = "return@${target.callableId}") +data class LoopBreakPath(val loop: FirLoop) : EdgeLabel(loop.label?.let { "break@${it.name}" } ?: "break") +data class LoopContinuePath(val loop: FirLoop) : EdgeLabel(loop.label?.let { "continue@${it.name}" } ?: "continue") enum class EdgeKind( val usedInDfa: Boolean, // propagate flow to alive nodes