[FIR] Properly propagate deadness from try main block throw finally block
^KT-56476 Fixed
This commit is contained in:
committed by
Space Team
parent
cecf22e035
commit
2a022ca9e0
+6
@@ -6410,6 +6410,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
|
||||
public void testSimpleClass() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/simpleClass.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("singleReturnFromTry.kt")
|
||||
public void testSingleReturnFromTry() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
+6
@@ -6416,6 +6416,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
|
||||
public void testSimpleClass() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/simpleClass.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("singleReturnFromTry.kt")
|
||||
public void testSingleReturnFromTry() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/singleReturnFromTry.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
||||
+33
-14
@@ -923,15 +923,11 @@ class ControlFlowGraphBuilder {
|
||||
}
|
||||
|
||||
fun exitFinallyBlock(): FinallyBlockExitNode {
|
||||
val enterNode = finallyBlocksInProgress.pop()
|
||||
val enterNode = finallyBlocksInProgress.top()
|
||||
val tryExitNode = tryExitNodes.top()
|
||||
val exitNode = createFinallyBlockExitNode(enterNode.fir)
|
||||
popAndAddEdge(exitNode)
|
||||
val allNormalInputsAreDead = enterNode.previousNodes.all {
|
||||
val edge = enterNode.edgeFrom(it)
|
||||
edge.kind.isDead || edge.label != NormalPath
|
||||
}
|
||||
addEdge(exitNode, tryExitNode, isDead = allNormalInputsAreDead)
|
||||
addEdge(exitNode, tryExitNode, isDead = enterNode.allNormalInputsAreDead)
|
||||
// TODO: there should also be edges to outer catch blocks? Control flow can go like this:
|
||||
// try { try { throw E2() } catch (e: E1) { } finally { } } catch (e: E2) { }
|
||||
// \-----------------------------^ \-----------------^
|
||||
@@ -960,6 +956,12 @@ class ControlFlowGraphBuilder {
|
||||
return exitNode
|
||||
}
|
||||
|
||||
private val FinallyBlockEnterNode.allNormalInputsAreDead: Boolean
|
||||
get() = previousNodes.all {
|
||||
val edge = edgeFrom(it)
|
||||
edge.kind.isDead || edge.label != NormalPath
|
||||
}
|
||||
|
||||
private fun <T> CFGNode<*>.addReturnEdges(nodes: Iterable<T>, minLevel: Int) where T : CFGNode<*>, T : EdgeLabel {
|
||||
for (node in nodes) {
|
||||
when {
|
||||
@@ -974,8 +976,23 @@ class ControlFlowGraphBuilder {
|
||||
}
|
||||
|
||||
fun exitTryExpression(callCompleted: Boolean): TryExpressionExitNode {
|
||||
notCompletedFunctionCalls.pop().forEach(::completeFunctionCall)
|
||||
var haveNothingReturnCall = false
|
||||
notCompletedFunctionCalls.pop().forEach { haveNothingReturnCall = completeFunctionCall(it) || haveNothingReturnCall }
|
||||
val node = tryExitNodes.pop()
|
||||
if (node.fir.finallyBlock != null) {
|
||||
val enterFinallyNode = finallyBlocksInProgress.pop()
|
||||
/**
|
||||
* If it appears that after completion try main expression returns nothing and try has finally block,
|
||||
* we should make edge from finally exist to try exit a dead (and it may be not dead originally
|
||||
* before completion)
|
||||
*/
|
||||
if (haveNothingReturnCall && enterFinallyNode.allNormalInputsAreDead) {
|
||||
val exitFinallyNode = node.previousNodes.single()
|
||||
assert(exitFinallyNode is FinallyBlockExitNode)
|
||||
CFGNode.removeAllIncomingEdges(node)
|
||||
addEdge(exitFinallyNode, node, isDead = true)
|
||||
}
|
||||
}
|
||||
mergeDataFlowFromPostponedLambdas(node, callCompleted)
|
||||
node.updateDeadStatus()
|
||||
lastNodes.push(node)
|
||||
@@ -1001,13 +1018,14 @@ class ControlFlowGraphBuilder {
|
||||
private fun levelOfNextExceptionCatchingGraph(): Int =
|
||||
graphs.all().first { it.kind != ControlFlowGraph.Kind.AnonymousFunctionCalledInPlace }.exitNode.level
|
||||
|
||||
//this is a workaround to make function call dead when call is completed _after_ building its node in the graph
|
||||
//this happens when completing the last call in try/catch blocks
|
||||
//todo this doesn't make fully 'right' Nothing node (doesn't support going to catch and pass through finally)
|
||||
// because doing those afterwards is quite challenging
|
||||
// it would be much easier if we could build calls after full completion only, at least for Nothing calls
|
||||
private fun completeFunctionCall(node: FunctionCallNode) {
|
||||
if (!node.fir.resultType.isNothing) return
|
||||
// this is a workaround to make function call dead when call is completed _after_ building its node in the graph
|
||||
// this happens when completing the last call in try/catch blocks
|
||||
// todo this doesn't make fully 'right' Nothing node (doesn't support going to catch and pass through finally)
|
||||
// because doing those afterwards is quite challenging
|
||||
// it would be much easier if we could build calls after full completion only, at least for Nothing calls
|
||||
// @returns `true` if node actually returned Nothing
|
||||
private fun completeFunctionCall(node: FunctionCallNode): Boolean {
|
||||
if (!node.fir.resultType.isNothing) return false
|
||||
val stub = StubNode(node.owner, node.level)
|
||||
val edges = node.followingNodes.map { it to node.edgeTo(it) }
|
||||
CFGNode.removeAllOutgoingEdges(node)
|
||||
@@ -1018,6 +1036,7 @@ class ControlFlowGraphBuilder {
|
||||
to.updateDeadStatus()
|
||||
propagateDeadnessForward(to)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ----------------------------------- Resolvable call -----------------------------------
|
||||
|
||||
@@ -77,6 +77,15 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
}
|
||||
from._followingNodes.clear()
|
||||
}
|
||||
|
||||
@CfgInternals
|
||||
fun removeAllIncomingEdges(to: CFGNode<*>) {
|
||||
for (from in to._previousNodes) {
|
||||
from._followingNodes.remove(to)
|
||||
}
|
||||
to._previousNodes.clear()
|
||||
to._incomingEdges?.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private val _previousNodes: MutableList<CFGNode<*>> = SmartList()
|
||||
|
||||
Reference in New Issue
Block a user