[FIR][CFG] Partial support of postponed Nothing calls

In try blocks, last call won't be completed when building node for it
This is workaround to partially reconstruct nothing stub node for such
calls. Should work for non-local returns in try only.
#KT-48160 Fixed
This commit is contained in:
Andrey Zinovyev
2021-08-10 13:59:54 +03:00
committed by TeamCityServer
parent 445e5122c1
commit 3ec9599bc4
7 changed files with 98 additions and 2 deletions
@@ -5908,6 +5908,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/kt4034.kt");
}
@Test
@TestMetadata("lambdaInTryFalsePositive.kt")
public void testLambdaInTryFalsePositive() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/lambdaInTryFalsePositive.kt");
}
@Test
@TestMetadata("ReturnFromFunctionInObject.kt")
public void testReturnFromFunctionInObject() throws Exception {
@@ -5908,6 +5908,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/kt4034.kt");
}
@Test
@TestMetadata("lambdaInTryFalsePositive.kt")
public void testLambdaInTryFalsePositive() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/lambdaInTryFalsePositive.kt");
}
@Test
@TestMetadata("ReturnFromFunctionInObject.kt")
public void testReturnFromFunctionInObject() throws Exception {
@@ -104,6 +104,8 @@ class ControlFlowGraphBuilder {
private val exitElvisExpressionNodes: Stack<ElvisExitNode> = stackOf()
private val elvisRhsEnterNodes: Stack<ElvisRhsEnterNode> = stackOf()
private val notCompletedFunctionCalls: Stack<MutableList<FunctionCallNode>> = stackOf()
/*
* ignoredFunctionCalls is needed for resolve of += operator:
* we have two different calls for resolve, but we left only one of them,
@@ -875,7 +877,7 @@ class ControlFlowGraphBuilder {
finallyEnterNodes.push(finallyEnterNode)
finallyExitNodes.push(createFinallyBlockExitNode(tryExpression))
}
notCompletedFunctionCalls.push(mutableListOf())
return enterTryExpressionNode to enterTryNodeBlock
}
@@ -966,6 +968,9 @@ class ControlFlowGraphBuilder {
catchNodeStorages.pop()
val catchExitNodes = catchExitNodeStorages.pop()
val tryMainExitNode = tryMainExitNodes.pop()
notCompletedFunctionCalls.pop().forEach(::completeFunctionCall)
val node = tryExitNodes.pop()
node.updateDeadStatus()
lastNodes.push(node)
@@ -985,6 +990,29 @@ class ControlFlowGraphBuilder {
return node to unionNode
}
//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
val stub = withLevelOfNode(node) { createStubNode() }
val edges = node.followingNodes.map { it to node.outgoingEdges.getValue(it) }
CFGNode.removeAllOutgoingEdges(node)
addEdge(node, stub)
for ((to, edge) in edges) {
addEdge(
from = stub,
to = to,
isBack = edge.kind.isBack,
preferredKind = edge.kind,
label = edge.label
)
}
stub.followingNodes.forEach { propagateDeadnessForward(it, deep = true) }
}
// ----------------------------------- Resolvable call -----------------------------------
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression): QualifiedAccessNode {
@@ -1026,6 +1054,9 @@ class ControlFlowGraphBuilder {
} else {
addNewSimpleNode(node, preferredKind = kind)
}
if (!returnsNothing && !callCompleted) {
notCompletedFunctionCalls.topOrNull()?.add(node)
}
return node to unionNode
}
@@ -1474,13 +1505,16 @@ class ControlFlowGraphBuilder {
addEdge(from, to, propagateDeadness = false, isDead = isDead, isBack = true, preferredKind = EdgeKind.CfgBackward, label = label)
}
private fun propagateDeadnessForward(node: CFGNode<*>) {
private fun propagateDeadnessForward(node: CFGNode<*>, deep: Boolean = false) {
if (!node.isDead) return
node.followingNodes
.filter { node.outgoingEdges.getValue(it).kind == EdgeKind.Forward }
.forEach { target ->
CFGNode.addJustKindEdge(node, target, EdgeKind.DeadForward, false)
target.updateDeadStatus()
if (deep) {
propagateDeadnessForward(target, true)
}
}
}
@@ -1506,6 +1540,16 @@ class ControlFlowGraphBuilder {
return addNewSimpleNode(newNode, isDead)
}
private fun <R> withLevelOfNode(node: CFGNode<*>, f: () -> R): R {
val last = levelCounter
levelCounter = node.level
try {
return f()
} finally {
levelCounter = last
}
}
}
fun FirDeclaration?.isLocalClassOrAnonymousObject() = ((this as? FirRegularClass)?.isLocal == true) || this is FirAnonymousObject
@@ -0,0 +1,24 @@
//KT-48160
// FIR_IDENTICAL
// FULL_JDK
// WITH_STDLIB
import java.io.File
inline fun <T : AutoCloseable, R> T.use(block: (T) -> R): R {
return block(this)
}
fun foo(): Int? {
try {
File("123").bufferedWriter().use {
return 45
}
} catch (e: Exception) {
if (e.message?.startsWith("Remote does not have ") == true) {
return null
}
return null
}
}
@@ -0,0 +1,4 @@
package
public fun foo(): kotlin.Int?
public inline fun </*0*/ T : java.lang.AutoCloseable, /*1*/ R> T.use(/*0*/ block: (T) -> R): R
@@ -5914,6 +5914,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/kt4034.kt");
}
@Test
@TestMetadata("lambdaInTryFalsePositive.kt")
public void testLambdaInTryFalsePositive() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/lambdaInTryFalsePositive.kt");
}
@Test
@TestMetadata("ReturnFromFunctionInObject.kt")
public void testReturnFromFunctionInObject() throws Exception {
@@ -5908,6 +5908,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/kt4034.kt");
}
@Test
@TestMetadata("lambdaInTryFalsePositive.kt")
public void testLambdaInTryFalsePositive() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/definiteReturn/lambdaInTryFalsePositive.kt");
}
@Test
@TestMetadata("ReturnFromFunctionInObject.kt")
public void testReturnFromFunctionInObject() throws Exception {