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];