From f51b7faa1bdf42869a0f92783aa27eb03469802a Mon Sep 17 00:00:00 2001 From: Brian Norman Date: Fri, 21 Jul 2023 10:10:07 -0500 Subject: [PATCH] [FIR] Only track assignment nodes for uncaught exception edges in CFG UncaughtExceptionPath edges are used to influence smart-casting within catch and finally blocks. Previously these edges were added from every node which could throw an exception. But only assignment nodes influence smart-casts by resetting inference back to some less specific type. Therefore, instead of tracking every possible node which could throw an exception - even though almost every statement node can - only add edges from assignment nodes to catch and finally blocks. This fixes many missing exception cases and also reduces the total number of incoming edges to catch and finally blocks. #KT-56872 Fixed --- .../resolve/cfa/initializationInTry.dot | 7 +- .../testData/resolve/cfg/complex.dot | 2 +- .../inplaceLambdaInControlFlowExpressions.dot | 4 +- .../resolve/cfg/postponedLambdaInReturn.dot | 2 +- .../testData/resolve/cfg/tryCatch.dot | 14 +- .../cfg/variableInitializedInTryBlock.dot | 5 +- .../smartcasts/tryWithLambdaInside.dot | 2 - .../dfa/cfg/ControlFlowGraphBuilder.kt | 37 +- .../kotlin/fir/resolve/dfa/cfg/CFGNode.kt | 30 -- .../definiteReturn/singleReturnFromTry.dot | 4 +- .../controlFlowAnalysis/smartCastInCatch.dot | 502 ++++++++++++++++++ .../smartCastInCatch.fir.kt | 54 ++ .../controlFlowAnalysis/smartCastInCatch.kt | 54 ++ .../castchecks/castInTryWithJump.dot | 38 +- 14 files changed, 650 insertions(+), 105 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.dot diff --git a/compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.dot b/compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.dot index 171d29aec89..ef97481b820 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfa/initializationInTry.dot @@ -97,13 +97,11 @@ digraph initializationInTry_kt { 17 -> {18}; 18 -> {19}; 19 -> {20}; - 19 -> {26} [label="onUncaughtException"]; 20 -> {21}; - 20 -> {26} [label="onUncaughtException"]; 21 -> {22}; 22 -> {23}; - 22 -> {26} [label="onUncaughtException"]; 23 -> {24}; + 23 -> {26} [label="onUncaughtException"]; 24 -> {25}; 25 -> {26}; 26 -> {27}; @@ -167,11 +165,10 @@ digraph initializationInTry_kt { 40 -> {41}; 41 -> {42}; 42 -> {43}; - 42 -> {48} [label="onUncaughtException"]; 43 -> {44}; 44 -> {45}; - 44 -> {48} [label="onUncaughtException"]; 45 -> {46}; + 45 -> {48} [label="onUncaughtException"]; 46 -> {47}; 47 -> {48}; 48 -> {49}; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/complex.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/complex.dot index 23805e3f642..ac049010f1a 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/complex.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/complex.dot @@ -151,7 +151,7 @@ digraph complex_kt { 23 -> {24 29}; 24 -> {25}; 25 -> {26}; - 26 -> {27 29}; + 26 -> {27}; 27 -> {28}; 28 -> {29 38}; 29 -> {30}; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/inplaceLambdaInControlFlowExpressions.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/inplaceLambdaInControlFlowExpressions.dot index b0bda7a1440..9782f5f9904 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/inplaceLambdaInControlFlowExpressions.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/inplaceLambdaInControlFlowExpressions.dot @@ -184,12 +184,12 @@ digraph inplaceLambdaInControlFlowExpressions_kt { 45 -> {46} [style=dashed]; 46 -> {47}; 47 -> {48}; - 48 -> {49 55}; + 48 -> {49}; 49 -> {50}; 50 -> {51}; 51 -> {52} [color=green]; 51 -> {61} [color=red]; - 52 -> {53 55}; + 52 -> {53}; 53 -> {54}; 54 -> {55 61}; 55 -> {56}; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdaInReturn.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdaInReturn.dot index 5dacd53e46d..78948324536 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdaInReturn.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/postponedLambdaInReturn.dot @@ -333,7 +333,7 @@ digraph postponedLambdaInReturn_kt { 88 -> {89}; 89 -> {90}; 90 -> {91} [color=green]; - 91 -> {92 96}; + 91 -> {92}; 92 -> {109}; 92 -> {93} [style=dotted]; 93 -> {94} [style=dotted]; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot index b7509d2895a..e54d58e83e1 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/tryCatch.dot @@ -228,11 +228,11 @@ digraph tryCatch_kt { subgraph cluster_32 { color=blue 86 [label="Catch enter"]; - 87 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; + 87 [label="Variable declaration: e: R|kotlin/Exception|"]; subgraph cluster_33 { color=blue 88 [label="Enter block"]; - 89 [label="Jump: break@@@[Boolean(true)] "]; + 89 [label="Jump: continue@@@[Boolean(true)] "]; 90 [label="Stub" style="filled" fillcolor=gray]; 91 [label="Exit block" style="filled" fillcolor=gray]; } @@ -241,11 +241,11 @@ digraph tryCatch_kt { subgraph cluster_34 { color=blue 93 [label="Catch enter"]; - 94 [label="Variable declaration: e: R|kotlin/Exception|"]; + 94 [label="Variable declaration: e: R|kotlin/RuntimeException|"]; subgraph cluster_35 { color=blue 95 [label="Enter block"]; - 96 [label="Jump: continue@@@[Boolean(true)] "]; + 96 [label="Jump: break@@@[Boolean(true)] "]; 97 [label="Stub" style="filled" fillcolor=gray]; 98 [label="Exit block" style="filled" fillcolor=gray]; } @@ -297,7 +297,7 @@ digraph tryCatch_kt { 71 -> {72}; 72 -> {73}; 73 -> {74}; - 74 -> {75 86 93}; + 74 -> {75}; 75 -> {76 77}; 76 -> {83}; 77 -> {78}; @@ -313,7 +313,7 @@ digraph tryCatch_kt { 86 -> {87}; 87 -> {88}; 88 -> {89}; - 89 -> {105}; + 89 -> {49} [color=green style=dashed]; 89 -> {90} [style=dotted]; 90 -> {91} [style=dotted]; 91 -> {92} [style=dotted]; @@ -321,7 +321,7 @@ digraph tryCatch_kt { 93 -> {94}; 94 -> {95}; 95 -> {96}; - 96 -> {49} [color=green style=dashed]; + 96 -> {105}; 96 -> {97} [style=dotted]; 97 -> {98} [style=dotted]; 98 -> {99} [style=dotted]; diff --git a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot index d391b09216a..3e2ea4262e6 100644 --- a/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot +++ b/compiler/fir/analysis-tests/testData/resolve/cfg/variableInitializedInTryBlock.dot @@ -70,7 +70,6 @@ digraph variableInitializedInTryBlock_kt { 6 -> {7}; 7 -> {8}; 8 -> {9}; - 8 -> {23} [label="onUncaughtException"]; 9 -> {10 13}; 10 -> {11}; 11 -> {23} [label="return@/test"]; @@ -80,13 +79,11 @@ digraph variableInitializedInTryBlock_kt { 14 -> {15}; 15 -> {16}; 16 -> {17}; - 16 -> {23} [label="onUncaughtException"]; 17 -> {18}; - 17 -> {23} [label="onUncaughtException"]; 18 -> {19}; 19 -> {20}; - 19 -> {23} [label="onUncaughtException"]; 20 -> {21}; + 20 -> {23} [label="onUncaughtException"]; 21 -> {22}; 22 -> {23}; 23 -> {24}; diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/smartcasts/tryWithLambdaInside.dot b/compiler/fir/analysis-tests/testData/resolveWithStdlib/smartcasts/tryWithLambdaInside.dot index 859c1128993..75de9b9536f 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/smartcasts/tryWithLambdaInside.dot +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/smartcasts/tryWithLambdaInside.dot @@ -123,7 +123,6 @@ finally { 25 -> {26} [color=green]; 25 -> {33} [color=red]; 26 -> {27}; - 26 -> {29} [label="onUncaughtException"]; 27 -> {28}; 28 -> {29}; 29 -> {30}; @@ -212,7 +211,6 @@ finally { 50 -> {51} [color=green]; 50 -> {58} [color=red]; 51 -> {52}; - 51 -> {54} [label="onUncaughtException"]; 52 -> {53}; 53 -> {54}; 54 -> {55}; 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 ee5b12a14fa..74a49281b7d 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 @@ -1103,21 +1103,6 @@ class ControlFlowGraphBuilder { return node } - private fun addExceptionEdgesFrom(node: CFGNode<*>) { - if (!node.canThrow) return - - val nextCatch = catchNodes.topOrNull() - if (!nextCatch.isNullOrEmpty() && nextCatch.first().level > levelOfNextExceptionCatchingGraph()) { - for (catchEnterNode in nextCatch) { - addEdge(node, catchEnterNode, propagateDeadness = false) - } - } - val nextFinally = finallyEnterNodes.topOrNull() - if (nextFinally != null && nextFinally.level > levelOfNextExceptionCatchingGraph()) { - addEdge(node, nextFinally, propagateDeadness = false, label = UncaughtExceptionPath) - } - } - // Called-in-place function graphs are effectively inlined, exceptions go to enclosing function. private fun levelOfNextExceptionCatchingGraph(): Int = graphs.all().first { it.kind != ControlFlowGraph.Kind.AnonymousFunctionCalledInPlace }.exitNode.level @@ -1228,7 +1213,25 @@ class ControlFlowGraphBuilder { } fun exitVariableAssignment(assignment: FirVariableAssignment): VariableAssignmentNode { - return createVariableAssignmentNode(assignment).also { addNewSimpleNode(it) } + val node = createVariableAssignmentNode(assignment).also { addNewSimpleNode(it) } + + // Create edges from each assignment to any catch and/or finally blocks. Assignment is the only thing which can downgrade + // smart-casting, and exceptions can be thrown almost anywhere, so it is easier to track assignment than exceptions. This makes sure + // that any smart-cast downgrade which happens in a try block is recognized in catch and finally blocks. + val nextCatch = catchNodes.topOrNull() + if (!nextCatch.isNullOrEmpty()) { + val kind = if (nextCatch.first().level > levelOfNextExceptionCatchingGraph()) EdgeKind.Forward else EdgeKind.DfgForward + for (catchEnterNode in nextCatch) { + addEdge(node, catchEnterNode, preferredKind = kind, propagateDeadness = false) + } + } + val nextFinally = finallyEnterNodes.topOrNull() + if (nextFinally != null) { + val kind = if (nextFinally.level > levelOfNextExceptionCatchingGraph()) EdgeKind.Forward else EdgeKind.DfgForward + addEdge(node, nextFinally, preferredKind = kind, propagateDeadness = false, label = UncaughtExceptionPath) + } + + return node } fun exitThrowExceptionNode(throwExpression: FirThrowExpression): ThrowExceptionNode { @@ -1380,7 +1383,6 @@ class ControlFlowGraphBuilder { private fun addNewSimpleNode(node: CFGNode<*>, isDead: Boolean = false) { addEdge(lastNodes.pop(), node, preferredKind = if (isDead) EdgeKind.DeadForward else EdgeKind.Forward) lastNodes.push(node) - addExceptionEdgesFrom(node) } private fun addNonSuccessfullyTerminatingNode(node: CFGNode<*>) { @@ -1388,7 +1390,6 @@ class ControlFlowGraphBuilder { val stub = createStubNode() addEdge(node, stub) lastNodes.push(stub) - addExceptionEdgesFrom(node) } private fun popAndAddEdge(to: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Forward) { diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt index 3f2fa96628f..b13ff919f27 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CFGNode.kt @@ -32,8 +32,6 @@ sealed class CFGNode(val owner: ControlFlowGraph, val level: @OptIn(CfgInternals::class) val id = owner.nodeCount++ - open val canThrow: Boolean get() = false - // a ---> b ---> d // \-> c -/ // Normal CFG semantics: a, then either b or c, then d @@ -653,27 +651,18 @@ class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogic // ----------------------------------- Operator call ----------------------------------- class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = fir.operation == FirOperation.AS - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitTypeOperatorCallNode(this, data) } } class ComparisonExpressionNode(owner: ControlFlowGraph, override val fir: FirComparisonExpression, level: Int) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = true // TODO? only overridden compareTo - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitComparisonExpressionNode(this, data) } } class EqualityOperatorCallNode(owner: ControlFlowGraph, override val fir: FirEqualityOperatorCall, level: Int) : AbstractBinaryExitNode(owner, level) { - override val canThrow: Boolean - get() = true // TODO? only overridden equals - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitEqualityOperatorCallNode(this, data) } @@ -696,9 +685,6 @@ class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExp class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = true - override val isUnion: Boolean get() = true @@ -714,9 +700,6 @@ class QualifiedAccessNode( override val fir: FirQualifiedAccessExpression, level: Int ) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = fir.toResolvedCallableSymbol() is FirPropertySymbol - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitQualifiedAccessNode(this, data) } @@ -734,10 +717,6 @@ class ResolvedQualifierNode( class FunctionCallNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int) : CFGNode(owner, level) { - - override val canThrow: Boolean - get() = true - override val isUnion: Boolean get() = true @@ -768,9 +747,6 @@ class DelegatedConstructorCallNode(owner: ControlFlowGraph, override val fir: Fi override val isUnion: Boolean get() = true - override val canThrow: Boolean - get() = true // shouldn't matter since delegated constructor calls aren't wrapped in try-finally, but still - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitDelegatedConstructorCallNode(this, data) } @@ -792,9 +768,6 @@ class ThrowExceptionNode( override val fir: FirThrowExpression, level: Int ) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = true - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitThrowExceptionNode(this, data) } @@ -910,9 +883,6 @@ class FakeExpressionEnterNode(owner: ControlFlowGraph, level: Int) : CFGNode(owner, level) { - override val canThrow: Boolean - get() = fir.resolvedType.isNothing - override fun accept(visitor: ControlFlowGraphVisitor, data: D): R { return visitor.visitSmartCastExpressionExitNode(this, data) } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.dot b/compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.dot index 984b8d40186..e33b43b2533 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.dot +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.dot @@ -206,7 +206,6 @@ digraph singleReturnFromTry_kt { 54 -> {55} [color=green]; 54 -> {63} [style=dotted]; 55 -> {56} [style=dotted]; - 56 -> {59} [style=dotted label="onUncaughtException"]; 56 -> {57} [style=dotted]; 57 -> {58} [style=dotted]; 58 -> {59} [style=dotted]; @@ -384,8 +383,7 @@ digraph singleReturnFromTry_kt { 104 -> {105} [color=green]; 104 -> {119} [color=red]; 105 -> {106} [style=dotted]; - 106 -> {115} [style=dotted label="onUncaughtException"]; - 106 -> {107 109} [style=dotted]; + 106 -> {107} [style=dotted]; 107 -> {108} [style=dotted]; 108 -> {109 115} [style=dotted]; 109 -> {110}; diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.dot b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.dot new file mode 100644 index 00000000000..36aaf7eff6b --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.dot @@ -0,0 +1,502 @@ +digraph smartCastInCatch_fir_kt { + graph [nodesep=3] + node [shape=box penwidth=2] + edge [penwidth=2] + + subgraph cluster_0 { + color=red + 0 [label="Enter file smartCastInCatch.fir.kt" style="filled" fillcolor=red]; + 1 [label="Exit file smartCastInCatch.fir.kt" style="filled" fillcolor=red]; + } + 0 -> {1} [color=green]; + + subgraph cluster_1 { + color=red + 2 [label="Enter function exc" style="filled" fillcolor=red]; + subgraph cluster_2 { + color=blue + 3 [label="Enter block"]; + subgraph cluster_3 { + color=blue + 4 [label="Enter when"]; + subgraph cluster_4 { + color=blue + 5 [label="Enter when branch condition "]; + 6 [label="Access variable R|/flag|"]; + 7 [label="Exit when branch condition"]; + } + 8 [label="Synthetic else branch"]; + 9 [label="Enter when branch result"]; + subgraph cluster_5 { + color=blue + 10 [label="Enter block"]; + 11 [label="Function call: R|java/lang/RuntimeException.RuntimeException|()" style="filled" fillcolor=yellow]; + 12 [label="Throw: throw R|java/lang/RuntimeException.RuntimeException|()"]; + 13 [label="Stub" style="filled" fillcolor=gray]; + 14 [label="Exit block" style="filled" fillcolor=gray]; + } + 15 [label="Exit when branch result" style="filled" fillcolor=gray]; + 16 [label="Exit when"]; + } + 17 [label="Exit block"]; + } + 18 [label="Exit function exc" style="filled" fillcolor=red]; + } + 2 -> {3}; + 3 -> {4}; + 4 -> {5}; + 5 -> {6}; + 6 -> {7}; + 7 -> {8 9}; + 8 -> {16}; + 9 -> {10}; + 10 -> {11}; + 11 -> {12}; + 12 -> {13} [style=dotted]; + 13 -> {14} [style=dotted]; + 14 -> {15} [style=dotted]; + 15 -> {16} [style=dotted]; + 16 -> {17}; + 17 -> {18}; + + subgraph cluster_6 { + color=red + 19 [label="Enter function notNull" style="filled" fillcolor=red]; + subgraph cluster_7 { + color=blue + 20 [label="Enter block"]; + 21 [label="Function call: this@R|/notNull|.R|kotlin/Any.toString|()" style="filled" fillcolor=yellow]; + 22 [label="Jump: ^notNull this@R|/notNull|.R|kotlin/Any.toString|()"]; + 23 [label="Stub" style="filled" fillcolor=gray]; + 24 [label="Exit block" style="filled" fillcolor=gray]; + } + 25 [label="Exit function notNull" style="filled" fillcolor=red]; + } + 19 -> {20}; + 20 -> {21}; + 21 -> {22}; + 22 -> {25}; + 22 -> {23} [style=dotted]; + 23 -> {24} [style=dotted]; + 24 -> {25} [style=dotted]; + + subgraph cluster_8 { + color=red + 26 [label="Enter function test" style="filled" fillcolor=red]; + subgraph cluster_9 { + color=blue + 27 [label="Enter block"]; + 28 [label="Variable declaration: lvar x: R|kotlin/Any?|"]; + 29 [label="Const: String()"]; + 30 [label="Assignment: R|/x|"]; + subgraph cluster_10 { + color=blue + 31 [label="Try expression enter"]; + subgraph cluster_11 { + color=blue + 32 [label="Try main block enter"]; + subgraph cluster_12 { + color=blue + 33 [label="Enter block"]; + 34 [label="Const: Null(null)"]; + 35 [label="Assignment: R|/x|"]; + 36 [label="Access variable R|/flag|"]; + 37 [label="Function call: R|/exc|(...)" style="filled" fillcolor=yellow]; + 38 [label="Const: Int(1)"]; + 39 [label="Assignment: R|/x|"]; + 40 [label="Access variable R|/flag|"]; + 41 [label="Function call: R|/flag|.R|kotlin/Boolean.not|()" style="filled" fillcolor=yellow]; + 42 [label="Function call: R|/exc|(...)" style="filled" fillcolor=yellow]; + 43 [label="Const: String()"]; + 44 [label="Assignment: R|/x|"]; + 45 [label="Exit block"]; + } + 46 [label="Try main block exit"]; + } + subgraph cluster_13 { + color=blue + 47 [label="Catch enter"]; + 48 [label="Variable declaration: e: R|kotlin/Throwable|"]; + subgraph cluster_14 { + color=blue + 49 [label="Enter block"]; + 50 [label="Access variable R|/x|"]; + 51 [label="Smart cast: R|/x|"]; + 52 [label="Access variable #"]; + 53 [label="Access variable R|/x|"]; + 54 [label="Smart cast: R|/x|"]; + 55 [label="Function call: R|/x|.#()" style="filled" fillcolor=yellow]; + 56 [label="Exit block"]; + } + 57 [label="Catch exit"]; + } + 58 [label="Try expression exit"]; + } + 59 [label="Exit block"]; + } + 60 [label="Exit function test" style="filled" fillcolor=red]; + } + 26 -> {27}; + 27 -> {28}; + 28 -> {29}; + 29 -> {30}; + 30 -> {31}; + 31 -> {32 47}; + 32 -> {33}; + 33 -> {34}; + 34 -> {35}; + 35 -> {36 47}; + 36 -> {37}; + 37 -> {38}; + 38 -> {39}; + 39 -> {40 47}; + 40 -> {41}; + 41 -> {42}; + 42 -> {43}; + 43 -> {44}; + 44 -> {45 47}; + 45 -> {46}; + 46 -> {47 58}; + 47 -> {48}; + 48 -> {49}; + 49 -> {50}; + 50 -> {51}; + 51 -> {52}; + 52 -> {53}; + 53 -> {54}; + 54 -> {55}; + 55 -> {56}; + 56 -> {57}; + 57 -> {58}; + 58 -> {59}; + 59 -> {60}; + + subgraph cluster_15 { + color=red + 61 [label="Enter function testGetClassThrows" style="filled" fillcolor=red]; + subgraph cluster_16 { + color=blue + 62 [label="Enter block"]; + 63 [label="Access qualifier kotlin/String"]; + 64 [label="::class call"]; + 65 [label="Variable declaration: lvar x: R|kotlin/reflect/KClass?|"]; + 66 [label="Access variable R|/x|"]; + 67 [label="Type operator: (R|/x| as R|kotlin/reflect/KClass|)"]; + subgraph cluster_17 { + color=blue + 68 [label="Try expression enter"]; + subgraph cluster_18 { + color=blue + 69 [label="Try main block enter"]; + subgraph cluster_19 { + color=blue + 70 [label="Enter block"]; + 71 [label="Const: Null(null)"]; + 72 [label="Assignment: R|/x|"]; + 73 [label="Access qualifier kotlin/String"]; + 74 [label="::class call"]; + 75 [label="Assignment: R|/x|"]; + 76 [label="Exit block"]; + } + 77 [label="Try main block exit"]; + } + subgraph cluster_20 { + color=blue + 78 [label="Catch enter"]; + 79 [label="Variable declaration: e: R|kotlin/Throwable|"]; + subgraph cluster_21 { + color=blue + 80 [label="Enter block"]; + 81 [label="Access variable R|/x|"]; + 82 [label="Function call: R|/x|.R|/notNull#|()" style="filled" fillcolor=yellow]; + 83 [label="Exit block"]; + } + 84 [label="Catch exit"]; + } + 85 [label="Try expression exit"]; + } + 86 [label="Exit block"]; + } + 87 [label="Exit function testGetClassThrows" style="filled" fillcolor=red]; + } + 61 -> {62}; + 62 -> {63}; + 63 -> {64}; + 64 -> {65}; + 65 -> {66}; + 66 -> {67}; + 67 -> {68}; + 68 -> {69 78}; + 69 -> {70}; + 70 -> {71}; + 71 -> {72}; + 72 -> {73 78}; + 73 -> {74}; + 74 -> {75}; + 75 -> {76 78}; + 76 -> {77}; + 77 -> {78 85}; + 78 -> {79}; + 79 -> {80}; + 80 -> {81}; + 81 -> {82}; + 82 -> {83}; + 83 -> {84}; + 84 -> {85}; + 85 -> {86}; + 86 -> {87}; + + subgraph cluster_22 { + color=red + 88 [label="Enter function testMemberReferenceThrows" style="filled" fillcolor=red]; + subgraph cluster_23 { + color=blue + 89 [label="Enter block"]; + 90 [label="Const: String()"]; + 91 [label="Variable declaration: lvar x: R|kotlin/Any?|"]; + 92 [label="Access variable R|/x|"]; + 93 [label="Type operator: (R|/x| as R|kotlin/Any|)"]; + subgraph cluster_24 { + color=blue + 94 [label="Try expression enter"]; + subgraph cluster_25 { + color=blue + 95 [label="Try main block enter"]; + subgraph cluster_26 { + color=blue + 96 [label="Enter block"]; + 97 [label="Const: Null(null)"]; + 98 [label="Assignment: R|/x|"]; + 99 [label="Access qualifier kotlin/String"]; + 100 [label="Callable reference: Q|kotlin/String|::R|kotlin/String.length|"]; + 101 [label="Assignment: R|/x|"]; + 102 [label="Exit block"]; + } + 103 [label="Try main block exit"]; + } + subgraph cluster_27 { + color=blue + 104 [label="Catch enter"]; + 105 [label="Variable declaration: ex: R|kotlin/Throwable|"]; + subgraph cluster_28 { + color=blue + 106 [label="Enter block"]; + 107 [label="Access variable R|/x|"]; + 108 [label="Function call: R|/x|.R|/notNull#|()" style="filled" fillcolor=yellow]; + 109 [label="Exit block"]; + } + 110 [label="Catch exit"]; + } + 111 [label="Try expression exit"]; + } + 112 [label="Exit block"]; + } + 113 [label="Exit function testMemberReferenceThrows" style="filled" fillcolor=red]; + } + 88 -> {89}; + 89 -> {90}; + 90 -> {91}; + 91 -> {92}; + 92 -> {93}; + 93 -> {94}; + 94 -> {95 104}; + 95 -> {96}; + 96 -> {97}; + 97 -> {98}; + 98 -> {99 104}; + 99 -> {100}; + 100 -> {101}; + 101 -> {102 104}; + 102 -> {103}; + 103 -> {104 111}; + 104 -> {105}; + 105 -> {106}; + 106 -> {107}; + 107 -> {108}; + 108 -> {109}; + 109 -> {110}; + 110 -> {111}; + 111 -> {112}; + 112 -> {113}; + + subgraph cluster_29 { + color=red + 114 [label="Enter function testExceptionBeforeLambda" style="filled" fillcolor=red]; + subgraph cluster_30 { + color=blue + 115 [label="Enter block"]; + 116 [label="Const: String()"]; + 117 [label="Variable declaration: lvar x: R|kotlin/String?|"]; + 118 [label="Access variable R|/x|"]; + 119 [label="Type operator: (R|/x| as R|kotlin/String|)"]; + subgraph cluster_31 { + color=blue + 120 [label="Try expression enter"]; + subgraph cluster_32 { + color=blue + 121 [label="Try main block enter"]; + subgraph cluster_33 { + color=blue + 122 [label="Enter block"]; + 123 [label="Const: Null(null)"]; + 124 [label="Assignment: R|/x|"]; + 125 [label="Postponed enter to lambda"]; + subgraph cluster_34 { + color=blue + 126 [label="Enter function " style="filled" fillcolor=red]; + subgraph cluster_35 { + color=blue + 127 [label="Enter block"]; + 128 [label="Const: String()"]; + 129 [label="Assignment: R|/x|"]; + 130 [label="Exit block"]; + } + 131 [label="Exit function " style="filled" fillcolor=red]; + } + 132 [label="Postponed exit from lambda"]; + 133 [label="Function call: R|kotlin/run|(...)" style="filled" fillcolor=yellow]; + 134 [label="Exit block"]; + } + 135 [label="Try main block exit"]; + } + subgraph cluster_36 { + color=blue + 136 [label="Catch enter"]; + 137 [label="Variable declaration: ex: R|kotlin/Throwable|"]; + subgraph cluster_37 { + color=blue + 138 [label="Enter block"]; + 139 [label="Access variable R|/x|"]; + 140 [label="Function call: R|/x|.R|/notNull#|()" style="filled" fillcolor=yellow]; + 141 [label="Exit block"]; + } + 142 [label="Catch exit"]; + } + 143 [label="Try expression exit"]; + } + 144 [label="Exit block"]; + } + 145 [label="Exit function testExceptionBeforeLambda" style="filled" fillcolor=red]; + } + 114 -> {115}; + 115 -> {116}; + 116 -> {117}; + 117 -> {118}; + 118 -> {119}; + 119 -> {120}; + 120 -> {121 136}; + 121 -> {122}; + 122 -> {123}; + 123 -> {124}; + 124 -> {125 136}; + 125 -> {126 133}; + 125 -> {132} [style=dotted]; + 125 -> {126} [style=dashed]; + 126 -> {127}; + 127 -> {128}; + 128 -> {129}; + 129 -> {130 136}; + 130 -> {131}; + 131 -> {132}; + 132 -> {133} [color=green]; + 132 -> {143} [color=red]; + 133 -> {134}; + 134 -> {135}; + 135 -> {136 143}; + 136 -> {137}; + 137 -> {138}; + 138 -> {139}; + 139 -> {140}; + 140 -> {141}; + 141 -> {142}; + 142 -> {143}; + 143 -> {144}; + 144 -> {145}; + + subgraph cluster_38 { + color=red + 146 [label="Enter function testExceptionWithinLocalFunction" style="filled" fillcolor=red]; + subgraph cluster_39 { + color=blue + 147 [label="Enter block"]; + 148 [label="Const: String()"]; + 149 [label="Variable declaration: lvar x: R|kotlin/Any|"]; + 150 [label="Access variable R|/x|"]; + 151 [label="Type operator: (R|/x| as R|kotlin/String|)"]; + subgraph cluster_40 { + color=blue + 152 [label="Try expression enter"]; + subgraph cluster_41 { + color=blue + 153 [label="Try main block enter"]; + subgraph cluster_42 { + color=blue + 154 [label="Enter block"]; + 155 [label="Local function declaration"]; + 156 [label="Exit block"]; + } + 157 [label="Try main block exit"]; + } + subgraph cluster_43 { + color=blue + 158 [label="Enter function local" style="filled" fillcolor=red]; + subgraph cluster_44 { + color=blue + 159 [label="Enter block"]; + 160 [label="Const: Int(1)"]; + 161 [label="Assignment: R|/x|"]; + 162 [label="Exit block"]; + } + 163 [label="Exit function local" style="filled" fillcolor=red]; + } + subgraph cluster_45 { + color=blue + 164 [label="Catch enter"]; + 165 [label="Variable declaration: e: R|kotlin/Exception|"]; + subgraph cluster_46 { + color=blue + 166 [label="Enter block"]; + 167 [label="Access variable R|/x|"]; + 168 [label="Smart cast: R|/x|"]; + 169 [label="Access variable #"]; + 170 [label="Exit block"]; + } + 171 [label="Catch exit"]; + } + 172 [label="Try expression exit"]; + } + 173 [label="Exit block"]; + } + 174 [label="Exit function testExceptionWithinLocalFunction" style="filled" fillcolor=red]; + } + 146 -> {147}; + 147 -> {148}; + 148 -> {149}; + 149 -> {150}; + 150 -> {151}; + 151 -> {152}; + 152 -> {153 164}; + 153 -> {154}; + 154 -> {155}; + 155 -> {156 158}; + 155 -> {158} [style=dashed]; + 156 -> {157}; + 157 -> {164 172}; + 158 -> {159}; + 159 -> {160}; + 160 -> {161}; + 161 -> {162}; + 161 -> {164} [color=red]; + 162 -> {163}; + 164 -> {165}; + 165 -> {166}; + 166 -> {167}; + 167 -> {168}; + 168 -> {169}; + 169 -> {170}; + 170 -> {171}; + 171 -> {172}; + 172 -> {173}; + 173 -> {174}; + +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.fir.kt index a91d3e91ff5..1d0695052e8 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.fir.kt @@ -1,9 +1,14 @@ // !SKIP_TXT +// DUMP_CFG + +import kotlin.reflect.KClass fun exc(flag: Boolean) { if (flag) throw RuntimeException() } +fun Any.notNull() = toString() + fun test(flag: Boolean) { var x: Any? x = "" @@ -19,3 +24,52 @@ fun test(flag: Boolean) { x.inc() } } + +fun testGetClassThrows() { + var x: KClass? = String::class + x as KClass + try { + x = null + x = String::class + } catch (e: Throwable) { + // bad - get class call can throw + x.notNull() + } +} + +fun testMemberReferenceThrows() { + var x: Any? = "" + x as Any + try { + x = null + x = String::length + } catch (ex: Throwable) { + // bad - get callable reference throw + x.notNull() + } +} + +fun testExceptionBeforeLambda() { + var x: String? = "" + x as String + try { + x = null + run { x = "" } + } catch (ex: Throwable) { + // bad - `run` could throw before running lambda + x.notNull() + } +} + +fun testExceptionWithinLocalFunction() { + var x: Any = "" + x as String + try { + fun local() { + x = 1 + } + } catch (e: Exception) { + // bad - `local` could be run and reset smartcasting + x.length + } +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt index 11c7ee13549..33531c0f5e6 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/smartCastInCatch.kt @@ -1,9 +1,14 @@ // !SKIP_TXT +// DUMP_CFG + +import kotlin.reflect.KClass fun exc(flag: Boolean) { if (flag) throw RuntimeException() } +fun Any.notNull() = toString() + fun test(flag: Boolean) { var x: Any? x = "" @@ -19,3 +24,52 @@ fun test(flag: Boolean) { x.inc() } } + +fun testGetClassThrows() { + var x: KClass? = String::class + x as KClass + try { + x = null + x = String::class + } catch (e: Throwable) { + // bad - get class call can throw + x.notNull() + } +} + +fun testMemberReferenceThrows() { + var x: Any? = "" + x as Any + try { + x = null + x = String::length + } catch (ex: Throwable) { + // bad - get callable reference throw + x.notNull() + } +} + +fun testExceptionBeforeLambda() { + var x: String? = "" + x as String + try { + x = null + run { x = "" } + } catch (ex: Throwable) { + // bad - `run` could throw before running lambda + x.notNull() + } +} + +fun testExceptionWithinLocalFunction() { + var x: Any = "" + x as String + try { + fun local() { + x = 1 + } + } catch (e: Exception) { + // bad - `local` could be run and reset smartcasting + x.length + } +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/castchecks/castInTryWithJump.dot b/compiler/testData/diagnostics/tests/smartCasts/castchecks/castInTryWithJump.dot index 759abf953f0..d6e83231d5f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/castchecks/castInTryWithJump.dot +++ b/compiler/testData/diagnostics/tests/smartCasts/castchecks/castInTryWithJump.dot @@ -179,9 +179,7 @@ digraph castInTryWithJump_fir_kt { 25 -> {26}; 26 -> {27}; 27 -> {28}; - 27 -> {33} [label="onUncaughtException"]; 28 -> {29}; - 28 -> {33} [label="onUncaughtException"]; 29 -> {33} [label="break"]; 29 -> {30} [style=dotted]; 30 -> {31} [style=dotted]; @@ -194,9 +192,7 @@ digraph castInTryWithJump_fir_kt { 36 -> {37}; 37 -> {38}; 38 -> {39}; - 38 -> {42} [label="onUncaughtException"]; 39 -> {40}; - 39 -> {42} [label="onUncaughtException"]; 40 -> {41}; 41 -> {42}; 42 -> {43}; @@ -307,10 +303,8 @@ digraph castInTryWithJump_fir_kt { 81 -> {97} [label="onUncaughtException"]; 82 -> {83}; 83 -> {84}; - 84 -> {85 88}; - 84 -> {97} [label="onUncaughtException"]; - 85 -> {86 88}; - 85 -> {97} [label="onUncaughtException"]; + 84 -> {85}; + 85 -> {86}; 86 -> {87}; 87 -> {88 97}; 88 -> {89}; @@ -318,9 +312,7 @@ digraph castInTryWithJump_fir_kt { 89 -> {90}; 90 -> {91}; 91 -> {92}; - 91 -> {97} [label="onUncaughtException"]; 92 -> {93}; - 92 -> {97} [label="onUncaughtException"]; 93 -> {97} [label="return@/returnInCatch"]; 93 -> {94} [style=dotted]; 94 -> {95} [style=dotted]; @@ -458,9 +450,7 @@ digraph castInTryWithJump_fir_kt { 119 -> {120}; 120 -> {121}; 121 -> {122}; - 121 -> {125} [label="onUncaughtException"]; 122 -> {123}; - 122 -> {125} [label="onUncaughtException"]; 123 -> {124}; 124 -> {125}; 125 -> {126}; @@ -469,10 +459,8 @@ digraph castInTryWithJump_fir_kt { 127 -> {143} [label="onUncaughtException"]; 128 -> {129}; 129 -> {130}; - 130 -> {131 134}; - 130 -> {143} [label="onUncaughtException"]; - 131 -> {132 134}; - 131 -> {143} [label="onUncaughtException"]; + 130 -> {131}; + 131 -> {132}; 132 -> {133}; 133 -> {134 143}; 134 -> {135}; @@ -480,9 +468,7 @@ digraph castInTryWithJump_fir_kt { 135 -> {136}; 136 -> {137}; 137 -> {138}; - 137 -> {143} [label="onUncaughtException"]; 138 -> {139}; - 138 -> {143} [label="onUncaughtException"]; 139 -> {143} [label="return@/returnInCatch_insideFinally"]; 139 -> {140} [style=dotted]; 140 -> {141} [style=dotted]; @@ -629,10 +615,8 @@ digraph castInTryWithJump_fir_kt { 187 -> {203} [label="onUncaughtException"]; 188 -> {189}; 189 -> {190}; - 190 -> {191 194}; - 190 -> {203} [label="onUncaughtException"]; - 191 -> {192 194}; - 191 -> {203} [label="onUncaughtException"]; + 190 -> {191}; + 191 -> {192}; 192 -> {193}; 193 -> {194 203}; 194 -> {195}; @@ -640,9 +624,7 @@ digraph castInTryWithJump_fir_kt { 195 -> {196}; 196 -> {197}; 197 -> {198}; - 197 -> {203} [label="onUncaughtException"]; 198 -> {199}; - 198 -> {203} [label="onUncaughtException"]; 199 -> {203} [label="break"]; 199 -> {200} [style=dotted]; 200 -> {201} [style=dotted]; @@ -789,32 +771,24 @@ digraph castInTryWithJump_fir_kt { 237 -> {238}; 238 -> {239}; 239 -> {240}; - 239 -> {264} [label="onUncaughtException"]; 240 -> {241}; - 240 -> {264} [label="onUncaughtException"]; 241 -> {242}; 241 -> {249} [label="onUncaughtException"]; 242 -> {243}; 243 -> {244}; 244 -> {245}; - 244 -> {249} [label="onUncaughtException"]; 245 -> {246}; 246 -> {247}; - 246 -> {249} [label="onUncaughtException"]; 247 -> {248}; 248 -> {249}; 249 -> {250}; 250 -> {251}; 251 -> {252}; - 251 -> {264} [label="onUncaughtException"]; 252 -> {253}; 253 -> {254}; - 253 -> {264} [label="onUncaughtException"]; 254 -> {255}; - 254 -> {264} [label="onUncaughtException"]; 255 -> {256}; 256 -> {257}; - 256 -> {264} [label="onUncaughtException"]; 257 -> {264} [label="return@/returnInFinally_insideTry_nonLocal"]; 257 -> {258} [style=dotted]; 258 -> {259} [style=dotted];