[FIR] Fix cfg for safe call inside elvis

This commit is contained in:
Andrey Zinovyev
2021-07-13 18:31:39 +03:00
committed by teamcityserver
parent c160511244
commit 7e9f27436a
10 changed files with 76 additions and 7 deletions
@@ -61,7 +61,9 @@ digraph elvis_kt {
5 -> {6}; 5 -> {6};
6 -> {7}; 6 -> {7};
7 -> {8}; 7 -> {8};
8 -> {9 11}; 8 -> {9};
8 -> {11} [color=red];
8 -> {13} [color=green];
9 -> {10}; 9 -> {10};
10 -> {11}; 10 -> {11};
11 -> {12}; 11 -> {12};
@@ -469,7 +469,9 @@ digraph returns_kt {
151 -> {152}; 151 -> {152};
152 -> {153}; 152 -> {153};
153 -> {154}; 153 -> {154};
154 -> {155 157}; 154 -> {155};
154 -> {157} [color=red];
154 -> {159} [color=green];
155 -> {156}; 155 -> {156};
156 -> {157}; 156 -> {157};
157 -> {158}; 157 -> {158};
@@ -583,7 +583,9 @@ digraph nullability_kt {
194 -> {195 197}; 194 -> {195 197};
195 -> {196}; 195 -> {196};
196 -> {197}; 196 -> {197};
197 -> {198 200}; 197 -> {198};
197 -> {200} [color=red];
197 -> {202} [color=green];
198 -> {199}; 198 -> {199};
199 -> {200}; 199 -> {200};
200 -> {201}; 200 -> {201};
@@ -1288,6 +1288,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Elvis ----------------------------------- // ----------------------------------- Elvis -----------------------------------
fun enterElvis(elvisExpression: FirElvisExpression) {
graphBuilder.enterElvis(elvisExpression)
}
fun exitElvisLhs(elvisExpression: FirElvisExpression) { fun exitElvisLhs(elvisExpression: FirElvisExpression) {
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression) val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
lhsExitNode.mergeIncomingFlow() lhsExitNode.mergeIncomingFlow()
@@ -119,7 +119,9 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
protected set protected set
internal fun updateDeadStatus() { internal fun updateDeadStatus() {
isDead = incomingEdges.size == previousNodes.size && incomingEdges.values.all { it.kind == EdgeKind.DeadForward } isDead = incomingEdges.size == previousNodes.size && incomingEdges.values.all {
it.kind == EdgeKind.DeadForward || !it.kind.usedInCfa
}
} }
abstract fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R abstract fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R
@@ -95,6 +95,7 @@ class ControlFlowGraphBuilder {
private val exitSafeCallNodes: Stack<ExitSafeCallNode> = stackOf() private val exitSafeCallNodes: Stack<ExitSafeCallNode> = stackOf()
private val exitElvisExpressionNodes: Stack<ElvisExitNode> = stackOf() private val exitElvisExpressionNodes: Stack<ElvisExitNode> = stackOf()
private val elvisRhsEnterNodes: Stack<ElvisRhsEnterNode> = stackOf()
/* /*
* ignoredFunctionCalls is needed for resolve of += operator: * ignoredFunctionCalls is needed for resolve of += operator:
@@ -1165,7 +1166,15 @@ class ControlFlowGraphBuilder {
val exitNode = createExitSafeCallNode(safeCall) val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode) exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode) addEdge(lastNode, enterNode)
addEdge(lastNode, exitNode) if (elvisRhsEnterNodes.topOrNull()?.fir?.lhs === safeCall) {
//if this is safe call in lhs of elvis, we make two edges
// 1. Df-only edge to exit node, to get not null implications there
// 2. Cf-only edge to elvis rhs
addEdge(lastNode, exitNode, preferredKind = EdgeKind.DfgForward)
addEdge(lastNode, elvisRhsEnterNodes.top(), preferredKind = EdgeKind.CfgForward)
} else {
addEdge(lastNode, exitNode)
}
return enterNode return enterNode
} }
@@ -1184,6 +1193,10 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Elvis ----------------------------------- // ----------------------------------- Elvis -----------------------------------
fun enterElvis(elvisExpression: FirElvisExpression) {
elvisRhsEnterNodes.push(createElvisRhsEnterNode(elvisExpression))
}
fun exitElvisLhs(elvisExpression: FirElvisExpression): Triple<ElvisLhsExitNode, ElvisLhsIsNotNullNode, ElvisRhsEnterNode> { fun exitElvisLhs(elvisExpression: FirElvisExpression): Triple<ElvisLhsExitNode, ElvisLhsIsNotNullNode, ElvisRhsEnterNode> {
val exitNode = createElvisExitNode(elvisExpression).also { val exitNode = createElvisExitNode(elvisExpression).also {
exitElvisExpressionNodes.push(it) exitElvisExpressionNodes.push(it)
@@ -1198,7 +1211,7 @@ class ControlFlowGraphBuilder {
addEdge(it, exitNode) addEdge(it, exitNode)
} }
val rhsEnterNode = createElvisRhsEnterNode(elvisExpression).also { val rhsEnterNode = elvisRhsEnterNodes.pop().also {
addEdge(lhsExitNode, it) addEdge(lhsExitNode, it)
} }
lastNodes.push(rhsEnterNode) lastNodes.push(rhsEnterNode)
@@ -224,6 +224,7 @@ class FirControlFlowStatementsResolveTransformer(transformer: FirBodyResolveTran
withExpectedType(expectedType, mayBeCoercionToUnitApplied = true) withExpectedType(expectedType, mayBeCoercionToUnitApplied = true)
else else
withExpectedType(expectedType?.withNullability(ConeNullability.NULLABLE, session.typeContext)) withExpectedType(expectedType?.withNullability(ConeNullability.NULLABLE, session.typeContext))
dataFlowAnalyzer.enterElvis(elvisExpression)
elvisExpression.transformLhs(transformer, resolutionModeForLhs) elvisExpression.transformLhs(transformer, resolutionModeForLhs)
dataFlowAnalyzer.exitElvisLhs(elvisExpression) dataFlowAnalyzer.exitElvisLhs(elvisExpression)
@@ -10,6 +10,13 @@ inline fun <T> Any?.myRun(block: () -> T): T {
return block() return block()
} }
inline fun <T> directRun(block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
fun bad(): String { fun bad(): String {
val x: String? = null val x: String? = null
@@ -20,4 +27,16 @@ fun ok(): String {
val x: String? = null val x: String? = null
x?.run { return "non-null" } ?: return "null" x?.run { return "non-null" } ?: return "null"
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!> }
fun ok2(): String {
directRun {
return "nonNull"
}
}
fun ok3(arg: Any?): String {
arg?.myRun {
return "nonNull"
} ?: error("null")
}
@@ -10,6 +10,13 @@ inline fun <T> Any?.myRun(block: () -> T): T {
return block() return block()
} }
inline fun <T> directRun(block: () -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
fun bad(): String { fun bad(): String {
val x: String? = null val x: String? = null
@@ -20,4 +27,16 @@ fun ok(): String {
val x: String? = null val x: String? = null
x?.run { return "non-null" } ?: return "null" x?.run { return "non-null" } ?: return "null"
}
fun ok2(): String {
directRun {
return "nonNull"
}
}
fun ok3(arg: Any?): String {
arg?.myRun {
return "nonNull"
} ?: error("null")
} }
@@ -1,7 +1,12 @@
package package
public fun bad(): kotlin.String public fun bad(): kotlin.String
public inline fun </*0*/ T> directRun(/*0*/ block: () -> T): T
CallsInPlace(block, EXACTLY_ONCE)
public fun ok(): kotlin.String public fun ok(): kotlin.String
public fun ok2(): kotlin.String
public fun ok3(/*0*/ arg: kotlin.Any?): kotlin.String
public inline fun </*0*/ T> kotlin.Any?.myRun(/*0*/ block: () -> T): T public inline fun </*0*/ T> kotlin.Any?.myRun(/*0*/ block: () -> T): T
CallsInPlace(block, EXACTLY_ONCE) CallsInPlace(block, EXACTLY_ONCE)