[FIR] Create FlowPath interface and add alternate flows to CFGNodes

Preliminary refactor in preparation for the implementation details to
fix KT-56888. This change makes it so alternate data flows can be
created and maintained through CFGNodes. This change was separated and
created first to aid in the code review process.
This commit is contained in:
Brian Norman
2023-07-19 08:37:15 -05:00
committed by Space Team
parent 1acdb3c143
commit f3847de1b9
3 changed files with 170 additions and 43 deletions
@@ -154,9 +154,9 @@ abstract class FirDataFlowAnalyzer(
graphBuilder.enterFunction(function) graphBuilder.enterFunction(function)
} }
localFunctionNode?.mergeIncomingFlow() localFunctionNode?.mergeIncomingFlow()
functionEnterNode.mergeIncomingFlow { functionEnterNode.mergeIncomingFlow { _, flow ->
if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() == true) { if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() == true) {
enterRepeatableStatement(it, function) enterRepeatableStatement(flow, function)
} }
} }
context.variableAssignmentAnalyzer.enterFunction(function) context.variableAssignmentAnalyzer.enterFunction(function)
@@ -239,7 +239,7 @@ abstract class FirDataFlowAnalyzer(
// ----------------------------------- Code Fragment ------------------------------------------ // ----------------------------------- Code Fragment ------------------------------------------
fun enterCodeFragment(codeFragment: FirCodeFragment) { fun enterCodeFragment(codeFragment: FirCodeFragment) {
graphBuilder.enterCodeFragment(codeFragment).mergeIncomingFlow { flow -> graphBuilder.enterCodeFragment(codeFragment).mergeIncomingFlow { _, flow ->
val realVariablesFromContext = codeFragment.codeFragmentContext?.variables.orEmpty() val realVariablesFromContext = codeFragment.codeFragmentContext?.variables.orEmpty()
for ((symbol, exactTypes) in realVariablesFromContext) { for ((symbol, exactTypes) in realVariablesFromContext) {
val realVariable = variableStorage.getOrCreateIfReal(flow, symbol.fir) as? RealVariable ?: continue val realVariable = variableStorage.getOrCreateIfReal(flow, symbol.fir) as? RealVariable ?: continue
@@ -316,9 +316,9 @@ abstract class FirDataFlowAnalyzer(
// ----------------------------------- Operator call ----------------------------------- // ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) { fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) {
graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow { graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow { _, flow ->
if (typeOperatorCall.operation !in FirOperation.TYPES) return@mergeIncomingFlow if (typeOperatorCall.operation !in FirOperation.TYPES) return@mergeIncomingFlow
addTypeOperatorStatements(it, typeOperatorCall) addTypeOperatorStatements(flow, typeOperatorCall)
} }
} }
@@ -404,7 +404,7 @@ abstract class FirDataFlowAnalyzer(
val leftIsNull = leftIsNullConst || leftOperand.coneType.isNullableNothing && !rightIsNullConst val leftIsNull = leftIsNullConst || leftOperand.coneType.isNullableNothing && !rightIsNullConst
val rightIsNull = rightIsNullConst || rightOperand.coneType.isNullableNothing && !leftIsNullConst val rightIsNull = rightIsNullConst || rightOperand.coneType.isNullableNothing && !leftIsNullConst
node.mergeIncomingFlow { flow -> node.mergeIncomingFlow { _, flow ->
when { when {
leftConst != null && rightConst != null -> return@mergeIncomingFlow leftConst != null && rightConst != null -> return@mergeIncomingFlow
leftIsNull -> processEqNull(flow, equalityOperatorCall, rightOperand, operation.isEq()) leftIsNull -> processEqNull(flow, equalityOperatorCall, rightOperand, operation.isEq())
@@ -559,7 +559,7 @@ abstract class FirDataFlowAnalyzer(
} }
fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall, callCompleted: Boolean) { fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall, callCompleted: Boolean) {
graphBuilder.exitCheckNotNullCall(checkNotNullCall, callCompleted).mergeIncomingFlow { flow -> graphBuilder.exitCheckNotNullCall(checkNotNullCall, callCompleted).mergeIncomingFlow { _, flow ->
val argumentVariable = variableStorage.getOrCreateIfReal(flow, checkNotNullCall.argument) ?: return@mergeIncomingFlow val argumentVariable = variableStorage.getOrCreateIfReal(flow, checkNotNullCall.argument) ?: return@mergeIncomingFlow
flow.commitOperationStatement(argumentVariable notEq null) flow.commitOperationStatement(argumentVariable notEq null)
} }
@@ -575,7 +575,7 @@ abstract class FirDataFlowAnalyzer(
graphBuilder.enterWhenBranchCondition(whenBranch).mergeWhenBranchEntryFlow() graphBuilder.enterWhenBranchCondition(whenBranch).mergeWhenBranchEntryFlow()
} }
private fun CFGNode<*>.mergeWhenBranchEntryFlow() = mergeIncomingFlow { flow -> private fun CFGNode<*>.mergeWhenBranchEntryFlow() = mergeIncomingFlow { _, flow ->
val previousConditionExitNode = previousNodes.singleOrNull() as? WhenBranchConditionExitNode ?: return@mergeIncomingFlow val previousConditionExitNode = previousNodes.singleOrNull() as? WhenBranchConditionExitNode ?: return@mergeIncomingFlow
val previousCondition = previousConditionExitNode.fir.condition val previousCondition = previousConditionExitNode.fir.condition
if (!previousCondition.coneType.isBoolean) return@mergeIncomingFlow if (!previousCondition.coneType.isBoolean) return@mergeIncomingFlow
@@ -586,7 +586,7 @@ abstract class FirDataFlowAnalyzer(
fun exitWhenBranchCondition(whenBranch: FirWhenBranch) { fun exitWhenBranchCondition(whenBranch: FirWhenBranch) {
val (conditionExitNode, resultEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch) val (conditionExitNode, resultEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch)
conditionExitNode.mergeIncomingFlow() conditionExitNode.mergeIncomingFlow()
resultEnterNode.mergeIncomingFlow { flow -> resultEnterNode.mergeIncomingFlow { _, flow ->
// If the condition is invalid, don't generate smart casts to Any or Boolean. // If the condition is invalid, don't generate smart casts to Any or Boolean.
if (whenBranch.condition.coneType.isBoolean) { if (whenBranch.condition.coneType.isBoolean) {
val conditionVariable = variableStorage.get(flow, whenBranch.condition) ?: return@mergeIncomingFlow val conditionVariable = variableStorage.get(flow, whenBranch.condition) ?: return@mergeIncomingFlow
@@ -614,13 +614,13 @@ abstract class FirDataFlowAnalyzer(
fun enterWhileLoop(loop: FirLoop) { fun enterWhileLoop(loop: FirLoop) {
val (loopEnterNode, loopConditionEnterNode) = graphBuilder.enterWhileLoop(loop) val (loopEnterNode, loopConditionEnterNode) = graphBuilder.enterWhileLoop(loop)
loopEnterNode.mergeIncomingFlow() loopEnterNode.mergeIncomingFlow()
loopConditionEnterNode.mergeIncomingFlow { flow -> enterRepeatableStatement(flow, loop) } loopConditionEnterNode.mergeIncomingFlow { _, flow -> enterRepeatableStatement(flow, loop) }
} }
fun exitWhileLoopCondition(loop: FirLoop) { fun exitWhileLoopCondition(loop: FirLoop) {
val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop) val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop)
loopConditionExitNode.mergeIncomingFlow() loopConditionExitNode.mergeIncomingFlow()
loopBlockEnterNode.mergeIncomingFlow { flow -> loopBlockEnterNode.mergeIncomingFlow { _, flow ->
if (loop.condition.coneType.isBoolean) { if (loop.condition.coneType.isBoolean) {
val conditionVariable = variableStorage.get(flow, loop.condition) ?: return@mergeIncomingFlow val conditionVariable = variableStorage.get(flow, loop.condition) ?: return@mergeIncomingFlow
flow.commitOperationStatement(conditionVariable eq true) flow.commitOperationStatement(conditionVariable eq true)
@@ -631,9 +631,9 @@ abstract class FirDataFlowAnalyzer(
fun exitWhileLoop(loop: FirLoop) { fun exitWhileLoop(loop: FirLoop) {
val (conditionEnterNode, blockExitNode, exitNode) = graphBuilder.exitWhileLoop(loop) val (conditionEnterNode, blockExitNode, exitNode) = graphBuilder.exitWhileLoop(loop)
blockExitNode.mergeIncomingFlow() blockExitNode.mergeIncomingFlow()
exitNode.mergeIncomingFlow { exitNode.mergeIncomingFlow { _, flow ->
processWhileLoopExit(it, exitNode, conditionEnterNode) processWhileLoopExit(flow, exitNode, conditionEnterNode)
processLoopExit(it, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode) processLoopExit(flow, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode)
} }
} }
@@ -697,7 +697,7 @@ abstract class FirDataFlowAnalyzer(
fun enterDoWhileLoop(loop: FirLoop) { fun enterDoWhileLoop(loop: FirLoop) {
val (loopEnterNode, loopBlockEnterNode) = graphBuilder.enterDoWhileLoop(loop) val (loopEnterNode, loopBlockEnterNode) = graphBuilder.enterDoWhileLoop(loop)
loopEnterNode.mergeIncomingFlow { flow -> enterRepeatableStatement(flow, loop) } loopEnterNode.mergeIncomingFlow { _, flow -> enterRepeatableStatement(flow, loop) }
loopBlockEnterNode.mergeIncomingFlow() loopBlockEnterNode.mergeIncomingFlow()
} }
@@ -710,7 +710,9 @@ abstract class FirDataFlowAnalyzer(
fun exitDoWhileLoop(loop: FirLoop) { fun exitDoWhileLoop(loop: FirLoop) {
val (loopConditionExitNode, loopExitNode) = graphBuilder.exitDoWhileLoop(loop) val (loopConditionExitNode, loopExitNode) = graphBuilder.exitDoWhileLoop(loop)
loopConditionExitNode.mergeIncomingFlow() loopConditionExitNode.mergeIncomingFlow()
loopExitNode.mergeIncomingFlow { processLoopExit(it, loopExitNode, loopConditionExitNode) } loopExitNode.mergeIncomingFlow { _, flow ->
processLoopExit(flow, loopExitNode, loopConditionExitNode)
}
exitRepeatableStatement(loop) exitRepeatableStatement(loop)
} }
@@ -749,7 +751,7 @@ abstract class FirDataFlowAnalyzer(
// ----------------------------------- Resolvable call ----------------------------------- // ----------------------------------- Resolvable call -----------------------------------
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow { flow -> graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow { _, flow ->
processConditionalContract(flow, qualifiedAccessExpression) processConditionalContract(flow, qualifiedAccessExpression)
} }
} }
@@ -759,7 +761,7 @@ abstract class FirDataFlowAnalyzer(
} }
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) { fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow { flow -> graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow { _, flow ->
val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return@mergeIncomingFlow val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return@mergeIncomingFlow
flow.commitOperationStatement(receiverVariable notEq null) flow.commitOperationStatement(receiverVariable notEq null)
} }
@@ -767,7 +769,7 @@ abstract class FirDataFlowAnalyzer(
fun exitSafeCall(safeCall: FirSafeCallExpression) { fun exitSafeCall(safeCall: FirSafeCallExpression) {
val node = graphBuilder.exitSafeCall() val node = graphBuilder.exitSafeCall()
node.mergeIncomingFlow { flow -> node.mergeIncomingFlow { _, flow ->
// If there is only 1 previous node, then this is LHS of `a?.b ?: c`; then the null-case // If there is only 1 previous node, then this is LHS of `a?.b ?: c`; then the null-case
// edge from `a` goes directly to `c` and this node's flow already assumes `b` executed. // edge from `a` goes directly to `c` and this node's flow already assumes `b` executed.
if (node.previousNodes.size < 2) return@mergeIncomingFlow if (node.previousNodes.size < 2) return@mergeIncomingFlow
@@ -798,8 +800,8 @@ abstract class FirDataFlowAnalyzer(
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) { fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
context.variableAssignmentAnalyzer.exitFunctionCall(callCompleted) context.variableAssignmentAnalyzer.exitFunctionCall(callCompleted)
graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { _, flow ->
processConditionalContract(it, functionCall) processConditionalContract(flow, functionCall)
} }
} }
@@ -907,14 +909,14 @@ abstract class FirDataFlowAnalyzer(
} }
fun exitLocalVariableDeclaration(variable: FirProperty, hadExplicitType: Boolean) { fun exitLocalVariableDeclaration(variable: FirProperty, hadExplicitType: Boolean) {
graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow { flow -> graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow { _, flow ->
val initializer = variable.initializer ?: return@mergeIncomingFlow val initializer = variable.initializer ?: return@mergeIncomingFlow
exitVariableInitialization(flow, initializer, variable, assignmentLhs = null, hadExplicitType) exitVariableInitialization(flow, initializer, variable, assignmentLhs = null, hadExplicitType)
} }
} }
fun exitVariableAssignment(assignment: FirVariableAssignment) { fun exitVariableAssignment(assignment: FirVariableAssignment) {
graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow { flow -> graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow { _, flow ->
val property = assignment.calleeReference?.toResolvedPropertySymbol()?.fir ?: return@mergeIncomingFlow val property = assignment.calleeReference?.toResolvedPropertySymbol()?.fir ?: return@mergeIncomingFlow
if (property.isLocal || property.isVal) { if (property.isLocal || property.isVal) {
exitVariableInitialization(flow, assignment.rValue, property, assignment.lValue, hasExplicitType = false) exitVariableInitialization(flow, assignment.rValue, property, assignment.lValue, hasExplicitType = false)
@@ -990,7 +992,7 @@ abstract class FirDataFlowAnalyzer(
fun exitLeftBinaryLogicExpressionArgument(binaryLogicExpression: FirBinaryLogicExpression) { fun exitLeftBinaryLogicExpressionArgument(binaryLogicExpression: FirBinaryLogicExpression) {
val (leftExitNode, rightEnterNode) = graphBuilder.exitLeftBinaryLogicExpressionArgument(binaryLogicExpression) val (leftExitNode, rightEnterNode) = graphBuilder.exitLeftBinaryLogicExpressionArgument(binaryLogicExpression)
leftExitNode.mergeIncomingFlow() leftExitNode.mergeIncomingFlow()
rightEnterNode.mergeIncomingFlow { flow -> rightEnterNode.mergeIncomingFlow { _, flow ->
val leftOperandVariable = variableStorage.get(flow, binaryLogicExpression.leftOperand) ?: return@mergeIncomingFlow val leftOperandVariable = variableStorage.get(flow, binaryLogicExpression.leftOperand) ?: return@mergeIncomingFlow
val isAnd = binaryLogicExpression.kind == LogicOperationKind.AND val isAnd = binaryLogicExpression.kind == LogicOperationKind.AND
flow.commitOperationStatement(leftOperandVariable eq isAnd) flow.commitOperationStatement(leftOperandVariable eq isAnd)
@@ -1001,7 +1003,7 @@ abstract class FirDataFlowAnalyzer(
graphBuilder.exitBinaryLogicExpression(binaryLogicExpression).mergeBinaryLogicOperatorFlow() graphBuilder.exitBinaryLogicExpression(binaryLogicExpression).mergeBinaryLogicOperatorFlow()
} }
private fun AbstractBinaryExitNode<FirBinaryLogicExpression>.mergeBinaryLogicOperatorFlow() = mergeIncomingFlow { flow -> private fun AbstractBinaryExitNode<FirBinaryLogicExpression>.mergeBinaryLogicOperatorFlow() = mergeIncomingFlow { _, flow ->
val isAnd = fir.kind == LogicOperationKind.AND val isAnd = fir.kind == LogicOperationKind.AND
val flowFromLeft = leftOperandNode.flow val flowFromLeft = leftOperandNode.flow
val flowFromRight = rightOperandNode.flow val flowFromRight = rightOperandNode.flow
@@ -1101,11 +1103,13 @@ abstract class FirDataFlowAnalyzer(
fun exitElvisLhs(elvisExpression: FirElvisExpression) { fun exitElvisLhs(elvisExpression: FirElvisExpression) {
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression) val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
val lhsVariable = variableStorage.getOrCreateIfReal(lhsExitNode.mergeIncomingFlow(), elvisExpression.lhs) lhsExitNode.mergeIncomingFlow()
lhsIsNotNullNode.mergeIncomingFlow { flow ->
val lhsVariable = variableStorage.getOrCreateIfReal(lhsExitNode.flow, elvisExpression.lhs)
lhsIsNotNullNode.mergeIncomingFlow { _, flow ->
lhsVariable?.let { flow.commitOperationStatement(it notEq null) } lhsVariable?.let { flow.commitOperationStatement(it notEq null) }
} }
rhsEnterNode.mergeIncomingFlow { flow -> rhsEnterNode.mergeIncomingFlow { _, flow ->
lhsVariable?.let { flow.commitOperationStatement(it eq null) } lhsVariable?.let { flow.commitOperationStatement(it eq null) }
} }
} }
@@ -1113,7 +1117,7 @@ abstract class FirDataFlowAnalyzer(
@OptIn(UnexpandedTypeCheck::class) @OptIn(UnexpandedTypeCheck::class)
fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean, callCompleted: Boolean) { fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean, callCompleted: Boolean) {
val node = graphBuilder.exitElvis(isLhsNotNull, callCompleted) val node = graphBuilder.exitElvis(isLhsNotNull, callCompleted)
node.mergeIncomingFlow { flow -> node.mergeIncomingFlow { _, flow ->
// If LHS is never null, then the edge from RHS is dead and this node's flow already contains // If LHS is never null, then the edge from RHS is dead and this node's flow already contains
// all statements from LHS unconditionally. // all statements from LHS unconditionally.
if (isLhsNotNull) return@mergeIncomingFlow if (isLhsNotNull) return@mergeIncomingFlow
@@ -1169,13 +1173,19 @@ abstract class FirDataFlowAnalyzer(
// Generally when calling some method on `graphBuilder`, one of the nodes it returns is the new `lastNode`. // Generally when calling some method on `graphBuilder`, one of the nodes it returns is the new `lastNode`.
// In that case `mergeIncomingFlow` will automatically ensure consistency once called on that node. // In that case `mergeIncomingFlow` will automatically ensure consistency once called on that node.
private fun CFGNode<*>.buildIncomingFlow(): MutableFlow { private fun CFGNode<*>.buildIncomingFlow(
path: FlowPath,
builder: (FlowPath, MutableFlow) -> Unit,
): MutableFlow {
val previousFlows = previousNodes.mapNotNull { val previousFlows = previousNodes.mapNotNull {
val incomingEdgeKind = edgeFrom(it).kind val incomingEdgeKind = edgeFrom(it).kind
if (if (isDead) !incomingEdgeKind.usedInDeadDfa else !incomingEdgeKind.usedInDfa) return@mapNotNull null if (if (isDead) !incomingEdgeKind.usedInDeadDfa else !incomingEdgeKind.usedInDfa) return@mapNotNull null
// `MergePostponedLambdaExitsNode` nodes form a parallel data flow graph. We never compute // `MergePostponedLambdaExitsNode` nodes form a parallel data flow graph. We never compute
// data flow for any of them until reaching a completed call. // data flow for any of them until reaching a completed call.
if (it is MergePostponedLambdaExitsNode) it.mergeIncomingFlow() else it.flow if (it is MergePostponedLambdaExitsNode) it.mergeIncomingFlow()
it.flow
} }
val result = logicSystem.joinFlow(previousFlows, isUnion) val result = logicSystem.joinFlow(previousFlows, isUnion)
if (graphBuilder.lastNodeOrNull == this) { if (graphBuilder.lastNodeOrNull == this) {
@@ -1186,24 +1196,20 @@ abstract class FirDataFlowAnalyzer(
} }
currentReceiverState = result currentReceiverState = result
} }
return result return result.also { builder(path, it) }
} }
@OptIn(CfgInternals::class) @OptIn(CfgInternals::class)
private fun CFGNode<*>.setFlow(builder: MutableFlow): PersistentFlow { private fun CFGNode<*>.mergeIncomingFlow(
val flow = builder.freeze().also { this.flow = it } builder: (FlowPath, MutableFlow) -> Unit = { _, _ -> },
if (currentReceiverState === builder) { ) {
currentReceiverState = flow val mutableDefaultFlow = buildIncomingFlow(FlowPath.Default, builder)
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
if (currentReceiverState === mutableDefaultFlow) {
currentReceiverState = defaultFlow
} }
return flow
} }
private inline fun CFGNode<*>.mergeIncomingFlow(crossinline builder: (MutableFlow) -> Unit): PersistentFlow =
setFlow(buildIncomingFlow().also(builder))
private fun CFGNode<*>.mergeIncomingFlow() =
setFlow(buildIncomingFlow())
// In rare cases (like after exiting functions) after adding more nodes `graphBuilder` will revert the current // In rare cases (like after exiting functions) after adding more nodes `graphBuilder` will revert the current
// state to a previously created node, so none of the nodes it returned are `lastNode` and `mergeIncomingFlow` // state to a previously created node, so none of the nodes it returned are `lastNode` and `mergeIncomingFlow`
// will not ensure consistency. In that case an explicit call to `resetReceivers` is needed to roll back the stack // will not ensure consistency. In that case an explicit call to `resetReceivers` is needed to roll back the stack
@@ -1251,4 +1257,10 @@ abstract class FirDataFlowAnalyzer(
private fun MutableFlow.commitOperationStatement(statement: OperationStatement) = private fun MutableFlow.commitOperationStatement(statement: OperationStatement) =
addAllStatements(logicSystem.approveOperationStatement(this, statement, removeApprovedOrImpossible = true)) addAllStatements(logicSystem.approveOperationStatement(this, statement, removeApprovedOrImpossible = true))
private enum class FlowPathOperation {
PROPAGATE,
ADDITIONAL,
DIVERGE,
TERMINATE,
}
} }
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel
/**
* Sealed interface representing a type of path through a [Control Flow Graph (CFG) Node][org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode]
* used for data flow analysis. Most CFG nodes only have a single data flow path through them, but there are times when a node may require
* multiple paths to be calculated. The most common use case is for `finally` code blocks, where multiple code paths may enter, but these
* paths diverge after exiting the code block. Consider the following (very) contrived example:
*
* ```kotlin
* fun test() {
* var x: Any? = null
* while (true) {
* try {
* x = "" // (1)
* doSomething()
* } catch (e: Exception) {
* x = 1 // (2)
* break // (3)
* } finally {
* x.inc() // (4) Should be error.
* x.length // (5) Should be error.
* }
* x.length // (6) Should be OK.
* }
* x.inc() // (7) Should be OK.
* }
* ```
*
* A few things to note here:
*
* 1. The statement at (1) implies all following code can smartcast `x` to be a `String`.
* 2. The statement at (2) implies all following code can smartcast `x` to be a `Int`.
* 3. The statement at (3) is the only way to exit the infinite `while` loop without throwing an exception.
* 4. Both of the statements at (4) and (5) should be considered errors because it is unknown through which path they will be reached. It is
* possible to reach them only through (1) or after an exception is caught and going through (2).
* 5. The statement at (6) should compile successfully since the only way to reach it is if the `try-catch-finally` expression completes
* without exception, therefore only executing the statement at (1) and ***not*** (2).
* 6. The statement at (7) should compile successfully since the only way to reach it is if an exception is caught and processed by the
* `catch` block, therefore executing the statement at (2).
*
* To achieve this behavior, multiple data flows must be maintained through the `finally` code block for use by each path that comes after.
* In the above example, 3 separate flows must be maintained:
*
* 1. A flow which will be used within the `finally` block itself. This data flow is a combination of all flows which lead into the
* `finally` block.
* 2. A flow which will be used when the entire `try` expression exits by jumping out of the while loop. This data flow is a continuation of
* the data flow leading into the `finally` block from the `break` statement.
* 3. A flow which will be used when the entire `try` expression exits without exception or jumping. This data flow is a continuation of the
* data flow leading into the `finally` block from the main `try` block.
*/
sealed interface FlowPath {
/**
* The [FlowPath] which represents the combination of all flows leading into a CFG Node.
*/
data object Default : FlowPath
/**
* The [FlowPath] which represents the combination of all flows leading into a CFG Node that follow an edge with the specified
* [edge label][EdgeLabel]. The edge label is also combined with an [FIR element][FirElement] as the same edge label can be used for
* multiple flows. For example, a `finally` block within another `finally` block will require multiple
* [normal paths][org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath] through each that diverge at different nodes.
*/
data class CfgEdge(val label: EdgeLabel, val fir: FirElement) : FlowPath
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.dfa.FlowPath
import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
@@ -113,6 +114,10 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
var isDead: Boolean = false var isDead: Boolean = false
protected set protected set
/**
* [Flow][org.jetbrains.kotlin.fir.resolve.dfa.Flow] representing the [default path][FlowPath.Default] for this node. This flow should
* be used for all type resolutions at this node.
*/
private var _flow: PersistentFlow? = null private var _flow: PersistentFlow? = null
open var flow: PersistentFlow open var flow: PersistentFlow
get() = _flow ?: throw IllegalStateException("flow for $this not initialized - traversing nodes in wrong order?") get() = _flow ?: throw IllegalStateException("flow for $this not initialized - traversing nodes in wrong order?")
@@ -122,6 +127,32 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
_flow = value _flow = value
} }
/**
* All other [flows][org.jetbrains.kotlin.fir.resolve.dfa.Flow] through this node which are not the [default][FlowPath.Default]. These
* flows should only be used by following nodes when the path through this node diverges (ex., following a `finally` code block).
*/
private var _alternateFlows: MutableMap<FlowPath, PersistentFlow>? = null
open val alternateFlowPaths: Set<FlowPath>
get() = _alternateFlows?.keys ?: emptySet()
open fun getAlternateFlow(path: FlowPath): PersistentFlow? {
return _alternateFlows?.get(path)
}
@CfgInternals
open fun addAlternateFlow(path: FlowPath, flow: PersistentFlow) {
assert(path !== FlowPath.Default) { "cannot add default flow path as alternate for $this" }
assert(_alternateFlows?.get(path) == null) { "reassigning $path flow for $this" }
var alternateFlows = _alternateFlows
if (alternateFlows == null) {
alternateFlows = mutableMapOf()
_alternateFlows = alternateFlows
}
alternateFlows[path] = flow
}
@CfgInternals @CfgInternals
fun updateDeadStatus() { fun updateDeadStatus() {
isDead = if (isUnion) isDead = if (isUnion)
@@ -746,6 +777,18 @@ class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode<FirStub>(owner, le
@CfgInternals @CfgInternals
set(_) = throw IllegalStateException("can't set flow for stub node") set(_) = throw IllegalStateException("can't set flow for stub node")
override val alternateFlowPaths: Set<FlowPath>
get() = firstPreviousNode.alternateFlowPaths
override fun getAlternateFlow(path: FlowPath): PersistentFlow? {
return firstPreviousNode.getAlternateFlow(path)
}
@CfgInternals
override fun addAlternateFlow(path: FlowPath, flow: PersistentFlow) {
firstPreviousNode.addAlternateFlow(path, flow)
}
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R { override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitStubNode(this, data) return visitor.visitStubNode(this, data)
} }