FIR CFA: add edges from maybe-throwing statements to catch/finally
This commit is contained in:
+6
@@ -6076,6 +6076,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
|
|||||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("smartCastInCatch.kt")
|
||||||
|
public void testSmartCastInCatch() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("throwInLambda.kt")
|
@TestMetadata("throwInLambda.kt")
|
||||||
public void testThrowInLambda() throws Exception {
|
public void testThrowInLambda() throws Exception {
|
||||||
|
|||||||
@@ -90,9 +90,12 @@ digraph initializationInTry_kt {
|
|||||||
15 -> {16};
|
15 -> {16};
|
||||||
16 -> {17};
|
16 -> {17};
|
||||||
17 -> {18};
|
17 -> {18};
|
||||||
|
17 -> {24} [label="onUncaughtException"];
|
||||||
18 -> {19};
|
18 -> {19};
|
||||||
|
18 -> {24} [label="onUncaughtException"];
|
||||||
19 -> {20};
|
19 -> {20};
|
||||||
20 -> {21};
|
20 -> {21};
|
||||||
|
20 -> {24} [label="onUncaughtException"];
|
||||||
21 -> {22};
|
21 -> {22};
|
||||||
22 -> {23};
|
22 -> {23};
|
||||||
23 -> {24};
|
23 -> {24};
|
||||||
@@ -157,8 +160,10 @@ digraph initializationInTry_kt {
|
|||||||
38 -> {39};
|
38 -> {39};
|
||||||
39 -> {40};
|
39 -> {40};
|
||||||
40 -> {41};
|
40 -> {41};
|
||||||
|
40 -> {46} [label="onUncaughtException"];
|
||||||
41 -> {42};
|
41 -> {42};
|
||||||
42 -> {43};
|
42 -> {43};
|
||||||
|
42 -> {46} [label="onUncaughtException"];
|
||||||
43 -> {44};
|
43 -> {44};
|
||||||
44 -> {45};
|
44 -> {45};
|
||||||
45 -> {46};
|
45 -> {46};
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ digraph complex_kt {
|
|||||||
21 -> {22 27};
|
21 -> {22 27};
|
||||||
22 -> {23};
|
22 -> {23};
|
||||||
23 -> {24};
|
23 -> {24};
|
||||||
24 -> {25};
|
24 -> {25 27};
|
||||||
25 -> {26};
|
25 -> {26};
|
||||||
26 -> {27 36};
|
26 -> {27 36};
|
||||||
27 -> {28};
|
27 -> {28};
|
||||||
|
|||||||
+2
-2
@@ -177,12 +177,12 @@ digraph inplaceLambdaInControlFlowExpressions_kt {
|
|||||||
43 -> {44} [style=dashed];
|
43 -> {44} [style=dashed];
|
||||||
44 -> {45};
|
44 -> {45};
|
||||||
45 -> {46};
|
45 -> {46};
|
||||||
46 -> {47};
|
46 -> {47 53};
|
||||||
47 -> {48};
|
47 -> {48};
|
||||||
48 -> {49};
|
48 -> {49};
|
||||||
49 -> {50} [color=green];
|
49 -> {50} [color=green];
|
||||||
49 -> {59} [color=red];
|
49 -> {59} [color=red];
|
||||||
50 -> {51};
|
50 -> {51 53};
|
||||||
51 -> {52};
|
51 -> {52};
|
||||||
52 -> {53 59};
|
52 -> {53 59};
|
||||||
53 -> {54};
|
53 -> {54};
|
||||||
|
|||||||
+1
-1
@@ -324,7 +324,7 @@ digraph postponedLambdaInReturn_kt {
|
|||||||
86 -> {87};
|
86 -> {87};
|
||||||
87 -> {88};
|
87 -> {88};
|
||||||
88 -> {89} [color=green];
|
88 -> {89} [color=green];
|
||||||
89 -> {90};
|
89 -> {90 94};
|
||||||
90 -> {107};
|
90 -> {107};
|
||||||
90 -> {91} [style=dotted];
|
90 -> {91} [style=dotted];
|
||||||
91 -> {92} [style=dotted];
|
91 -> {92} [style=dotted];
|
||||||
|
|||||||
@@ -221,11 +221,11 @@ digraph tryCatch_kt {
|
|||||||
subgraph cluster_31 {
|
subgraph cluster_31 {
|
||||||
color=blue
|
color=blue
|
||||||
84 [label="Catch enter"];
|
84 [label="Catch enter"];
|
||||||
85 [label="Variable declaration: e: R|kotlin/Exception|"];
|
85 [label="Variable declaration: e: R|kotlin/RuntimeException|"];
|
||||||
subgraph cluster_32 {
|
subgraph cluster_32 {
|
||||||
color=blue
|
color=blue
|
||||||
86 [label="Enter block"];
|
86 [label="Enter block"];
|
||||||
87 [label="Jump: continue@@@[Boolean(true)] "];
|
87 [label="Jump: break@@@[Boolean(true)] "];
|
||||||
88 [label="Stub" style="filled" fillcolor=gray];
|
88 [label="Stub" style="filled" fillcolor=gray];
|
||||||
89 [label="Exit block" style="filled" fillcolor=gray];
|
89 [label="Exit block" style="filled" fillcolor=gray];
|
||||||
}
|
}
|
||||||
@@ -234,11 +234,11 @@ digraph tryCatch_kt {
|
|||||||
subgraph cluster_33 {
|
subgraph cluster_33 {
|
||||||
color=blue
|
color=blue
|
||||||
91 [label="Catch enter"];
|
91 [label="Catch enter"];
|
||||||
92 [label="Variable declaration: e: R|kotlin/RuntimeException|"];
|
92 [label="Variable declaration: e: R|kotlin/Exception|"];
|
||||||
subgraph cluster_34 {
|
subgraph cluster_34 {
|
||||||
color=blue
|
color=blue
|
||||||
93 [label="Enter block"];
|
93 [label="Enter block"];
|
||||||
94 [label="Jump: break@@@[Boolean(true)] "];
|
94 [label="Jump: continue@@@[Boolean(true)] "];
|
||||||
95 [label="Stub" style="filled" fillcolor=gray];
|
95 [label="Stub" style="filled" fillcolor=gray];
|
||||||
96 [label="Exit block" style="filled" fillcolor=gray];
|
96 [label="Exit block" style="filled" fillcolor=gray];
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ digraph tryCatch_kt {
|
|||||||
69 -> {70};
|
69 -> {70};
|
||||||
70 -> {71};
|
70 -> {71};
|
||||||
71 -> {72};
|
71 -> {72};
|
||||||
72 -> {73};
|
72 -> {73 84 91};
|
||||||
73 -> {74 75};
|
73 -> {74 75};
|
||||||
74 -> {81};
|
74 -> {81};
|
||||||
75 -> {76};
|
75 -> {76};
|
||||||
@@ -306,7 +306,7 @@ digraph tryCatch_kt {
|
|||||||
84 -> {85};
|
84 -> {85};
|
||||||
85 -> {86};
|
85 -> {86};
|
||||||
86 -> {87};
|
86 -> {87};
|
||||||
87 -> {47} [color=green style=dashed];
|
87 -> {103};
|
||||||
87 -> {88} [style=dotted];
|
87 -> {88} [style=dotted];
|
||||||
88 -> {89} [style=dotted];
|
88 -> {89} [style=dotted];
|
||||||
89 -> {90} [style=dotted];
|
89 -> {90} [style=dotted];
|
||||||
@@ -314,7 +314,7 @@ digraph tryCatch_kt {
|
|||||||
91 -> {92};
|
91 -> {92};
|
||||||
92 -> {93};
|
92 -> {93};
|
||||||
93 -> {94};
|
93 -> {94};
|
||||||
94 -> {103};
|
94 -> {47} [color=green style=dashed];
|
||||||
94 -> {95} [style=dotted];
|
94 -> {95} [style=dotted];
|
||||||
95 -> {96} [style=dotted];
|
95 -> {96} [style=dotted];
|
||||||
96 -> {97} [style=dotted];
|
96 -> {97} [style=dotted];
|
||||||
|
|||||||
+4
@@ -63,6 +63,7 @@ digraph variableInitializedInTryBlock_kt {
|
|||||||
4 -> {5};
|
4 -> {5};
|
||||||
5 -> {6};
|
5 -> {6};
|
||||||
6 -> {7};
|
6 -> {7};
|
||||||
|
6 -> {21} [label="onUncaughtException"];
|
||||||
7 -> {8 11};
|
7 -> {8 11};
|
||||||
8 -> {9};
|
8 -> {9};
|
||||||
9 -> {21} [label="return@/test"];
|
9 -> {21} [label="return@/test"];
|
||||||
@@ -72,9 +73,12 @@ digraph variableInitializedInTryBlock_kt {
|
|||||||
12 -> {13};
|
12 -> {13};
|
||||||
13 -> {14};
|
13 -> {14};
|
||||||
14 -> {15};
|
14 -> {15};
|
||||||
|
14 -> {21} [label="onUncaughtException"];
|
||||||
15 -> {16};
|
15 -> {16};
|
||||||
|
15 -> {21} [label="onUncaughtException"];
|
||||||
16 -> {17};
|
16 -> {17};
|
||||||
17 -> {18};
|
17 -> {18};
|
||||||
|
17 -> {21} [label="onUncaughtException"];
|
||||||
18 -> {19};
|
18 -> {19};
|
||||||
19 -> {20};
|
19 -> {20};
|
||||||
20 -> {21};
|
20 -> {21};
|
||||||
|
|||||||
+2
@@ -115,6 +115,7 @@ finally {
|
|||||||
23 -> {24};
|
23 -> {24};
|
||||||
23 -> {17} [color=green style=dashed];
|
23 -> {17} [color=green style=dashed];
|
||||||
24 -> {25};
|
24 -> {25};
|
||||||
|
24 -> {27} [label="onUncaughtException"];
|
||||||
25 -> {26};
|
25 -> {26};
|
||||||
26 -> {27};
|
26 -> {27};
|
||||||
27 -> {28};
|
27 -> {28};
|
||||||
@@ -202,6 +203,7 @@ finally {
|
|||||||
46 -> {47};
|
46 -> {47};
|
||||||
48 -> {49};
|
48 -> {49};
|
||||||
49 -> {50};
|
49 -> {50};
|
||||||
|
49 -> {52} [label="onUncaughtException"];
|
||||||
50 -> {51};
|
50 -> {51};
|
||||||
51 -> {52};
|
51 -> {52};
|
||||||
52 -> {53};
|
52 -> {53};
|
||||||
|
|||||||
+6
@@ -6082,6 +6082,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
|
|||||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("smartCastInCatch.kt")
|
||||||
|
public void testSmartCastInCatch() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("throwInLambda.kt")
|
@TestMetadata("throwInLambda.kt")
|
||||||
public void testThrowInLambda() throws Exception {
|
public void testThrowInLambda() throws Exception {
|
||||||
|
|||||||
+6
@@ -6076,6 +6076,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
|
|||||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("smartCastInCatch.kt")
|
||||||
|
public void testSmartCastInCatch() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("throwInLambda.kt")
|
@TestMetadata("throwInLambda.kt")
|
||||||
public void testThrowInLambda() throws Exception {
|
public void testThrowInLambda() throws Exception {
|
||||||
|
|||||||
+14
-35
@@ -101,8 +101,8 @@ class ControlFlowGraphBuilder {
|
|||||||
val edge = exitNode.edgeFrom(it)
|
val edge = exitNode.edgeFrom(it)
|
||||||
// * NormalPath: last expression = return value
|
// * NormalPath: last expression = return value
|
||||||
// * UncaughtExceptionPath: last expression = whatever threw, *not* a return value
|
// * UncaughtExceptionPath: last expression = whatever threw, *not* a return value
|
||||||
// * ReturnPath(this lambda): these go from `finally` blocks, so that's not the return value;
|
// * Other labels can only originate from finally block exits - look in nonDirectJumps to find the last node
|
||||||
// look in `nonDirectJumps` instead.
|
// before the block was entered
|
||||||
it.takeIf { edge.kind.usedInCfa && edge.label == NormalPath }?.returnExpression()
|
it.takeIf { edge.kind.usedInCfa && edge.label == NormalPath }?.returnExpression()
|
||||||
}
|
}
|
||||||
return nonDirectJumps[exitNode].mapNotNullTo(returnValues) { it.returnExpression() }
|
return nonDirectJumps[exitNode].mapNotNullTo(returnValues) { it.returnExpression() }
|
||||||
@@ -620,7 +620,7 @@ class ControlFlowGraphBuilder {
|
|||||||
|
|
||||||
fun exitJump(jump: FirJump<*>): JumpNode {
|
fun exitJump(jump: FirJump<*>): JumpNode {
|
||||||
val node = createJumpNode(jump)
|
val node = createJumpNode(jump)
|
||||||
addNonTerminatingNode(node)
|
addNonSuccessfullyTerminatingNode(node)
|
||||||
|
|
||||||
if (jump is FirReturnExpression && jump.target.labeledElement is FirAnonymousFunction) {
|
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?
|
// TODO: these should be DFA-only edges; they should be pointed into the postponed function exit node?
|
||||||
@@ -637,7 +637,7 @@ class ControlFlowGraphBuilder {
|
|||||||
} ?: return node
|
} ?: return node
|
||||||
val nextFinally = finallyEnterNodes.topOrNull()?.takeIf { it.level > nextNode.level }
|
val nextFinally = finallyEnterNodes.topOrNull()?.takeIf { it.level > nextNode.level }
|
||||||
if (nextFinally != null) {
|
if (nextFinally != null) {
|
||||||
addEdge(node, nextFinally, propagateDeadness = false, label = nextNode.returnPathLabel)
|
addEdge(node, nextFinally, propagateDeadness = false, label = nextNode)
|
||||||
nonDirectJumps.put(nextNode, node)
|
nonDirectJumps.put(nextNode, node)
|
||||||
} else if (nextNode.returnPathIsBackwards) {
|
} else if (nextNode.returnPathIsBackwards) {
|
||||||
addBackEdge(node, nextNode)
|
addBackEdge(node, nextNode)
|
||||||
@@ -647,14 +647,6 @@ class ControlFlowGraphBuilder {
|
|||||||
return 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 }
|
// while (x) { continue }
|
||||||
// ^------------/ back
|
// ^------------/ back
|
||||||
// do { continue } while (x)
|
// do { continue } while (x)
|
||||||
@@ -867,9 +859,8 @@ class ControlFlowGraphBuilder {
|
|||||||
addEdge(node, nextNode, propagateDeadness = false)
|
addEdge(node, nextNode, propagateDeadness = false)
|
||||||
for (catchEnterNode in catchNodes.pop().asReversed()) {
|
for (catchEnterNode in catchNodes.pop().asReversed()) {
|
||||||
catchBlocksInProgress.push(catchEnterNode)
|
catchBlocksInProgress.push(catchEnterNode)
|
||||||
// At least merge the data flow from enter + exit...but this doesn't really help,
|
// TODO: figure out if this edge is correct.
|
||||||
// see the comment for `addExceptionEdgesFrom`. Better than nothing, though.
|
// try { x = /* something non-throwing like variable read */ } catch (...) { /* can assume assignment didn't happen? */ }
|
||||||
// Like `finally`, `catch` nodes are only dead if the entire try-catch is dead.
|
|
||||||
addEdge(node, catchEnterNode, propagateDeadness = false)
|
addEdge(node, catchEnterNode, propagateDeadness = false)
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
@@ -942,15 +933,15 @@ class ControlFlowGraphBuilder {
|
|||||||
return exitNode
|
return exitNode
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CFGNode<*>.addReturnEdges(nodes: Iterable<CFGNode<*>>, minLevel: Int) {
|
private fun <T> CFGNode<*>.addReturnEdges(nodes: Iterable<T>, minLevel: Int) where T : CFGNode<*>, T : EdgeLabel {
|
||||||
for (node in nodes) {
|
for (node in nodes) {
|
||||||
when {
|
when {
|
||||||
// TODO: this check is imprecise and can add redundant edges:
|
// TODO: this check is imprecise and can add redundant edges:
|
||||||
// x@{ try { return@x } finally {}; try {} finally { /* return@x target is in nonDirectJumps */ }
|
// x@{ try { return@x } finally {}; try {} finally { /* return@x target is in nonDirectJumps */ }
|
||||||
node.level < minLevel || node !in nonDirectJumps -> continue
|
node.level < minLevel || node !in nonDirectJumps -> continue
|
||||||
// TODO: if the input to finally with that label is dead, then so should be the exit probably
|
// TODO: if the input to finally with that label is dead, then so should be the exit probably
|
||||||
node.returnPathIsBackwards -> addBackEdge(this, node, label = node.returnPathLabel)
|
node.returnPathIsBackwards -> addBackEdge(this, node, label = node)
|
||||||
else -> addEdge(this, node, propagateDeadness = false, label = node.returnPathLabel)
|
else -> addEdge(this, node, propagateDeadness = false, label = node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -964,20 +955,11 @@ class ControlFlowGraphBuilder {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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? (Some nodes never throw, but calls are never safe, and most useful stuff is calls.)
|
|
||||||
// var x: Any?
|
|
||||||
// x = ""
|
|
||||||
// try {
|
|
||||||
// x = null
|
|
||||||
// listOf(1, 2, 3).single()
|
|
||||||
// x = ""
|
|
||||||
// } catch (e: Throwable) { x.length } // oops
|
|
||||||
// R8 devs say they tried the "implicit knowledge" way but failed and decided to add all the edges - bad sign...
|
|
||||||
private fun addExceptionEdgesFrom(node: CFGNode<*>) {
|
private fun addExceptionEdgesFrom(node: CFGNode<*>) {
|
||||||
|
if (!node.canThrow) return
|
||||||
|
|
||||||
val nextCatch = catchNodes.topOrNull()
|
val nextCatch = catchNodes.topOrNull()
|
||||||
if (nextCatch != null) {
|
if (!nextCatch.isNullOrEmpty() && nextCatch.first().level > levelOfNextExceptionCatchingGraph()) {
|
||||||
for (catchEnterNode in nextCatch) {
|
for (catchEnterNode in nextCatch) {
|
||||||
addEdge(node, catchEnterNode, propagateDeadness = false)
|
addEdge(node, catchEnterNode, propagateDeadness = false)
|
||||||
}
|
}
|
||||||
@@ -1249,17 +1231,14 @@ class ControlFlowGraphBuilder {
|
|||||||
private fun addNewSimpleNode(node: CFGNode<*>, isDead: Boolean = false) {
|
private fun addNewSimpleNode(node: CFGNode<*>, isDead: Boolean = false) {
|
||||||
addEdge(lastNodes.pop(), node, preferredKind = if (isDead) EdgeKind.DeadForward else EdgeKind.Forward)
|
addEdge(lastNodes.pop(), node, preferredKind = if (isDead) EdgeKind.DeadForward else EdgeKind.Forward)
|
||||||
lastNodes.push(node)
|
lastNodes.push(node)
|
||||||
|
addExceptionEdgesFrom(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNonTerminatingNode(node: CFGNode<*>) {
|
private fun addNonSuccessfullyTerminatingNode(node: CFGNode<*>) {
|
||||||
popAndAddEdge(node)
|
popAndAddEdge(node)
|
||||||
val stub = createStubNode()
|
val stub = createStubNode()
|
||||||
addEdge(node, stub)
|
addEdge(node, stub)
|
||||||
lastNodes.push(stub)
|
lastNodes.push(stub)
|
||||||
}
|
|
||||||
|
|
||||||
private fun addNonSuccessfullyTerminatingNode(node: CFGNode<*>) {
|
|
||||||
addNonTerminatingNode(node)
|
|
||||||
addExceptionEdgesFrom(node)
|
addExceptionEdgesFrom(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import org.jetbrains.kotlin.fir.declarations.*
|
|||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow
|
import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||||
|
import org.jetbrains.kotlin.fir.types.coneType
|
||||||
import org.jetbrains.kotlin.fir.types.impl.FirImplicitNothingTypeRef
|
import org.jetbrains.kotlin.fir.types.impl.FirImplicitNothingTypeRef
|
||||||
import org.jetbrains.kotlin.fir.types.isNothing
|
import org.jetbrains.kotlin.fir.types.isNothing
|
||||||
import org.jetbrains.kotlin.fir.visitors.FirTransformer
|
import org.jetbrains.kotlin.fir.visitors.FirTransformer
|
||||||
@@ -27,6 +29,8 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
|||||||
@OptIn(CfgInternals::class)
|
@OptIn(CfgInternals::class)
|
||||||
val id = owner.nodeCount++
|
val id = owner.nodeCount++
|
||||||
|
|
||||||
|
open val canThrow: Boolean get() = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@CfgInternals
|
@CfgInternals
|
||||||
fun addEdge(
|
fun addEdge(
|
||||||
@@ -151,7 +155,10 @@ class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction,
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int) : CFGNode<FirFunction>(owner, level),
|
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int) : CFGNode<FirFunction>(owner, level),
|
||||||
GraphExitNodeMarker {
|
GraphExitNodeMarker, EdgeLabel {
|
||||||
|
override val label: String
|
||||||
|
get() = "return@${fir.symbol.callableId}"
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitFunctionExitNode(this, data)
|
return visitor.visitFunctionExitNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -418,7 +425,10 @@ class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, leve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int) : CFGNode<FirExpression>(owner, level),
|
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int) : CFGNode<FirExpression>(owner, level),
|
||||||
EnterNodeMarker {
|
EnterNodeMarker, EdgeLabel {
|
||||||
|
override val label: String
|
||||||
|
get() = loop.label?.let { "continue@${it.name}" } ?: "continue"
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitLoopConditionEnterNode(this, data)
|
return visitor.visitLoopConditionEnterNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -430,7 +440,10 @@ class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirExpres
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level),
|
class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level),
|
||||||
ExitNodeMarker {
|
ExitNodeMarker, EdgeLabel {
|
||||||
|
override val label: String
|
||||||
|
get() = fir.label?.let { "break@${it.name}" } ?: "break"
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitLoopExitNode(this, data)
|
return visitor.visitLoopExitNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -555,18 +568,27 @@ class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogic
|
|||||||
// ----------------------------------- Operator call -----------------------------------
|
// ----------------------------------- Operator call -----------------------------------
|
||||||
|
|
||||||
class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode<FirTypeOperatorCall>(owner, level) {
|
class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode<FirTypeOperatorCall>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = fir.operation == FirOperation.AS
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitTypeOperatorCallNode(this, data)
|
return visitor.visitTypeOperatorCallNode(this, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComparisonExpressionNode(owner: ControlFlowGraph, override val fir: FirComparisonExpression, level: Int) : CFGNode<FirComparisonExpression>(owner, level) {
|
class ComparisonExpressionNode(owner: ControlFlowGraph, override val fir: FirComparisonExpression, level: Int) : CFGNode<FirComparisonExpression>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true // TODO? only overridden compareTo
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitComparisonExpressionNode(this, data)
|
return visitor.visitComparisonExpressionNode(this, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EqualityOperatorCallNode(owner: ControlFlowGraph, override val fir: FirEqualityOperatorCall, level: Int) : AbstractBinaryExitNode<FirEqualityOperatorCall>(owner, level) {
|
class EqualityOperatorCallNode(owner: ControlFlowGraph, override val fir: FirEqualityOperatorCall, level: Int) : AbstractBinaryExitNode<FirEqualityOperatorCall>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true // TODO? only overridden equals
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitEqualityOperatorCallNode(this, data)
|
return visitor.visitEqualityOperatorCallNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -589,6 +611,9 @@ class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExp
|
|||||||
|
|
||||||
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int)
|
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int)
|
||||||
: CFGNode<FirCheckNotNullCall>(owner, level), UnionNodeMarker {
|
: CFGNode<FirCheckNotNullCall>(owner, level), UnionNodeMarker {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitCheckNotNullCallNode(this, data)
|
return visitor.visitCheckNotNullCallNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -601,6 +626,9 @@ class QualifiedAccessNode(
|
|||||||
override val fir: FirQualifiedAccessExpression,
|
override val fir: FirQualifiedAccessExpression,
|
||||||
level: Int
|
level: Int
|
||||||
) : CFGNode<FirQualifiedAccessExpression>(owner, level) {
|
) : CFGNode<FirQualifiedAccessExpression>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = fir.toResolvedCallableSymbol() is FirPropertySymbol
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitQualifiedAccessNode(this, data)
|
return visitor.visitQualifiedAccessNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -618,6 +646,9 @@ class ResolvedQualifierNode(
|
|||||||
|
|
||||||
class FunctionCallNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int)
|
class FunctionCallNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int)
|
||||||
: CFGNode<FirFunctionCall>(owner, level), UnionNodeMarker {
|
: CFGNode<FirFunctionCall>(owner, level), UnionNodeMarker {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitFunctionCallNode(this, data)
|
return visitor.visitFunctionCallNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -641,6 +672,9 @@ class GetClassCallNode(owner: ControlFlowGraph, override val fir: FirGetClassCal
|
|||||||
|
|
||||||
class DelegatedConstructorCallNode(owner: ControlFlowGraph, override val fir: FirDelegatedConstructorCall, level: Int)
|
class DelegatedConstructorCallNode(owner: ControlFlowGraph, override val fir: FirDelegatedConstructorCall, level: Int)
|
||||||
: CFGNode<FirDelegatedConstructorCall>(owner, level), UnionNodeMarker {
|
: CFGNode<FirDelegatedConstructorCall>(owner, level), UnionNodeMarker {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true // shouldn't matter since delegated constructor calls aren't wrapped in try-finally, but still
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitDelegatedConstructorCallNode(this, data)
|
return visitor.visitDelegatedConstructorCallNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -658,6 +692,9 @@ class ThrowExceptionNode(
|
|||||||
override val fir: FirThrowExpression,
|
override val fir: FirThrowExpression,
|
||||||
level: Int
|
level: Int
|
||||||
) : CFGNode<FirThrowExpression>(owner, level) {
|
) : CFGNode<FirThrowExpression>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitThrowExceptionNode(this, data)
|
return visitor.visitThrowExceptionNode(this, data)
|
||||||
}
|
}
|
||||||
@@ -761,6 +798,9 @@ class FakeExpressionEnterNode(owner: ControlFlowGraph, level: Int) : CFGNode<Fir
|
|||||||
// ----------------------------------- Smart-cast node -----------------------------------
|
// ----------------------------------- Smart-cast node -----------------------------------
|
||||||
|
|
||||||
class SmartCastExpressionExitNode(owner: ControlFlowGraph, override val fir: FirSmartCastExpression, level: Int) : CFGNode<FirSmartCastExpression>(owner, level) {
|
class SmartCastExpressionExitNode(owner: ControlFlowGraph, override val fir: FirSmartCastExpression, level: Int) : CFGNode<FirSmartCastExpression>(owner, level) {
|
||||||
|
override val canThrow: Boolean
|
||||||
|
get() = fir.typeRef.coneType.isNothing
|
||||||
|
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitSmartCastExpressionExitNode(this, data)
|
return visitor.visitSmartCastExpressionExitNode(this, data)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-12
@@ -6,8 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||||
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) {
|
class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val kind: Kind) {
|
||||||
@set:CfgInternals
|
@set:CfgInternals
|
||||||
@@ -91,19 +89,17 @@ data class Edge(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class EdgeLabel(val label: String?) {
|
sealed interface EdgeLabel {
|
||||||
override fun toString(): String {
|
val label: String?
|
||||||
return label ?: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object NormalPath : EdgeLabel(label = null)
|
object NormalPath : EdgeLabel {
|
||||||
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
|
override val label: String? get() = null
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Label `return`ing edge with this.
|
object UncaughtExceptionPath : EdgeLabel {
|
||||||
data class ReturnPath(val target: FirFunctionSymbol<*>) : EdgeLabel(label = "return@${target.callableId}")
|
override val label: String get() = "onUncaughtException"
|
||||||
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(
|
enum class EdgeKind(
|
||||||
val usedInDfa: Boolean, // propagate flow to alive nodes
|
val usedInDfa: Boolean, // propagate flow to alive nodes
|
||||||
|
|||||||
Vendored
+3
-3
@@ -13,7 +13,7 @@ fun f1(flag: Boolean) {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: reassignment
|
// KT-13612: reassignment
|
||||||
n = 3
|
<!VAL_REASSIGNMENT!>n<!> = 3
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ fun f2(flag: Boolean) {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: reassignment
|
// KT-13612: reassignment
|
||||||
n = 3
|
<!VAL_REASSIGNMENT!>n<!> = 3
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ fun f3(flag: Boolean) {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: reassignment
|
// KT-13612: reassignment
|
||||||
n = 3
|
<!VAL_REASSIGNMENT!>n<!> = 3
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
<!UNINITIALIZED_VARIABLE!>n<!>.hashCode()
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// !SKIP_TXT
|
||||||
|
|
||||||
|
fun exc(flag: Boolean) {
|
||||||
|
if (flag) throw RuntimeException()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(flag: Boolean) {
|
||||||
|
var x: Any?
|
||||||
|
x = ""
|
||||||
|
try {
|
||||||
|
x = null
|
||||||
|
exc(flag)
|
||||||
|
x = 1
|
||||||
|
exc(!flag)
|
||||||
|
x = ""
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// all bad - could come here from either call
|
||||||
|
x.<!UNRESOLVED_REFERENCE!>length<!>
|
||||||
|
x.<!UNRESOLVED_REFERENCE!>inc<!>()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// !SKIP_TXT
|
||||||
|
|
||||||
|
fun exc(flag: Boolean) {
|
||||||
|
if (flag) throw RuntimeException()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(flag: Boolean) {
|
||||||
|
var x: Any?
|
||||||
|
x = ""
|
||||||
|
try {
|
||||||
|
<!UNUSED_VALUE!>x =<!> null
|
||||||
|
exc(flag)
|
||||||
|
<!UNUSED_VALUE!>x =<!> 1
|
||||||
|
exc(!flag)
|
||||||
|
x = ""
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// all bad - could come here from either call
|
||||||
|
<!DEBUG_INFO_SMARTCAST!>x<!>.length
|
||||||
|
x.<!UNRESOLVED_REFERENCE!>inc<!>()
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+6
@@ -6082,6 +6082,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
|||||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/scopeOfAnonymousInitializer.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("smartCastInCatch.kt")
|
||||||
|
public void testSmartCastInCatch() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("throwInLambda.kt")
|
@TestMetadata("throwInLambda.kt")
|
||||||
public void testThrowInLambda() throws Exception {
|
public void testThrowInLambda() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user