[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:
+55
-43
@@ -154,9 +154,9 @@ abstract class FirDataFlowAnalyzer(
|
||||
graphBuilder.enterFunction(function)
|
||||
}
|
||||
localFunctionNode?.mergeIncomingFlow()
|
||||
functionEnterNode.mergeIncomingFlow {
|
||||
functionEnterNode.mergeIncomingFlow { _, flow ->
|
||||
if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() == true) {
|
||||
enterRepeatableStatement(it, function)
|
||||
enterRepeatableStatement(flow, function)
|
||||
}
|
||||
}
|
||||
context.variableAssignmentAnalyzer.enterFunction(function)
|
||||
@@ -239,7 +239,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
// ----------------------------------- Code Fragment ------------------------------------------
|
||||
|
||||
fun enterCodeFragment(codeFragment: FirCodeFragment) {
|
||||
graphBuilder.enterCodeFragment(codeFragment).mergeIncomingFlow { flow ->
|
||||
graphBuilder.enterCodeFragment(codeFragment).mergeIncomingFlow { _, flow ->
|
||||
val realVariablesFromContext = codeFragment.codeFragmentContext?.variables.orEmpty()
|
||||
for ((symbol, exactTypes) in realVariablesFromContext) {
|
||||
val realVariable = variableStorage.getOrCreateIfReal(flow, symbol.fir) as? RealVariable ?: continue
|
||||
@@ -316,9 +316,9 @@ abstract class FirDataFlowAnalyzer(
|
||||
// ----------------------------------- Operator call -----------------------------------
|
||||
|
||||
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) {
|
||||
graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow {
|
||||
graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow { _, flow ->
|
||||
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 rightIsNull = rightIsNullConst || rightOperand.coneType.isNullableNothing && !leftIsNullConst
|
||||
|
||||
node.mergeIncomingFlow { flow ->
|
||||
node.mergeIncomingFlow { _, flow ->
|
||||
when {
|
||||
leftConst != null && rightConst != null -> return@mergeIncomingFlow
|
||||
leftIsNull -> processEqNull(flow, equalityOperatorCall, rightOperand, operation.isEq())
|
||||
@@ -559,7 +559,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
}
|
||||
|
||||
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
|
||||
flow.commitOperationStatement(argumentVariable notEq null)
|
||||
}
|
||||
@@ -575,7 +575,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
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 previousCondition = previousConditionExitNode.fir.condition
|
||||
if (!previousCondition.coneType.isBoolean) return@mergeIncomingFlow
|
||||
@@ -586,7 +586,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
fun exitWhenBranchCondition(whenBranch: FirWhenBranch) {
|
||||
val (conditionExitNode, resultEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch)
|
||||
conditionExitNode.mergeIncomingFlow()
|
||||
resultEnterNode.mergeIncomingFlow { flow ->
|
||||
resultEnterNode.mergeIncomingFlow { _, flow ->
|
||||
// If the condition is invalid, don't generate smart casts to Any or Boolean.
|
||||
if (whenBranch.condition.coneType.isBoolean) {
|
||||
val conditionVariable = variableStorage.get(flow, whenBranch.condition) ?: return@mergeIncomingFlow
|
||||
@@ -614,13 +614,13 @@ abstract class FirDataFlowAnalyzer(
|
||||
fun enterWhileLoop(loop: FirLoop) {
|
||||
val (loopEnterNode, loopConditionEnterNode) = graphBuilder.enterWhileLoop(loop)
|
||||
loopEnterNode.mergeIncomingFlow()
|
||||
loopConditionEnterNode.mergeIncomingFlow { flow -> enterRepeatableStatement(flow, loop) }
|
||||
loopConditionEnterNode.mergeIncomingFlow { _, flow -> enterRepeatableStatement(flow, loop) }
|
||||
}
|
||||
|
||||
fun exitWhileLoopCondition(loop: FirLoop) {
|
||||
val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop)
|
||||
loopConditionExitNode.mergeIncomingFlow()
|
||||
loopBlockEnterNode.mergeIncomingFlow { flow ->
|
||||
loopBlockEnterNode.mergeIncomingFlow { _, flow ->
|
||||
if (loop.condition.coneType.isBoolean) {
|
||||
val conditionVariable = variableStorage.get(flow, loop.condition) ?: return@mergeIncomingFlow
|
||||
flow.commitOperationStatement(conditionVariable eq true)
|
||||
@@ -631,9 +631,9 @@ abstract class FirDataFlowAnalyzer(
|
||||
fun exitWhileLoop(loop: FirLoop) {
|
||||
val (conditionEnterNode, blockExitNode, exitNode) = graphBuilder.exitWhileLoop(loop)
|
||||
blockExitNode.mergeIncomingFlow()
|
||||
exitNode.mergeIncomingFlow {
|
||||
processWhileLoopExit(it, exitNode, conditionEnterNode)
|
||||
processLoopExit(it, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode)
|
||||
exitNode.mergeIncomingFlow { _, flow ->
|
||||
processWhileLoopExit(flow, exitNode, conditionEnterNode)
|
||||
processLoopExit(flow, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +697,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
|
||||
fun enterDoWhileLoop(loop: FirLoop) {
|
||||
val (loopEnterNode, loopBlockEnterNode) = graphBuilder.enterDoWhileLoop(loop)
|
||||
loopEnterNode.mergeIncomingFlow { flow -> enterRepeatableStatement(flow, loop) }
|
||||
loopEnterNode.mergeIncomingFlow { _, flow -> enterRepeatableStatement(flow, loop) }
|
||||
loopBlockEnterNode.mergeIncomingFlow()
|
||||
}
|
||||
|
||||
@@ -710,7 +710,9 @@ abstract class FirDataFlowAnalyzer(
|
||||
fun exitDoWhileLoop(loop: FirLoop) {
|
||||
val (loopConditionExitNode, loopExitNode) = graphBuilder.exitDoWhileLoop(loop)
|
||||
loopConditionExitNode.mergeIncomingFlow()
|
||||
loopExitNode.mergeIncomingFlow { processLoopExit(it, loopExitNode, loopConditionExitNode) }
|
||||
loopExitNode.mergeIncomingFlow { _, flow ->
|
||||
processLoopExit(flow, loopExitNode, loopConditionExitNode)
|
||||
}
|
||||
exitRepeatableStatement(loop)
|
||||
}
|
||||
|
||||
@@ -749,7 +751,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
// ----------------------------------- Resolvable call -----------------------------------
|
||||
|
||||
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
|
||||
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow { flow ->
|
||||
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow { _, flow ->
|
||||
processConditionalContract(flow, qualifiedAccessExpression)
|
||||
}
|
||||
}
|
||||
@@ -759,7 +761,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
}
|
||||
|
||||
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
|
||||
graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow { flow ->
|
||||
graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow { _, flow ->
|
||||
val receiverVariable = variableStorage.getOrCreateIfReal(flow, safeCall.receiver) ?: return@mergeIncomingFlow
|
||||
flow.commitOperationStatement(receiverVariable notEq null)
|
||||
}
|
||||
@@ -767,7 +769,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
|
||||
fun exitSafeCall(safeCall: FirSafeCallExpression) {
|
||||
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
|
||||
// edge from `a` goes directly to `c` and this node's flow already assumes `b` executed.
|
||||
if (node.previousNodes.size < 2) return@mergeIncomingFlow
|
||||
@@ -798,8 +800,8 @@ abstract class FirDataFlowAnalyzer(
|
||||
|
||||
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) {
|
||||
context.variableAssignmentAnalyzer.exitFunctionCall(callCompleted)
|
||||
graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow {
|
||||
processConditionalContract(it, functionCall)
|
||||
graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { _, flow ->
|
||||
processConditionalContract(flow, functionCall)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,14 +909,14 @@ abstract class FirDataFlowAnalyzer(
|
||||
}
|
||||
|
||||
fun exitLocalVariableDeclaration(variable: FirProperty, hadExplicitType: Boolean) {
|
||||
graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow { flow ->
|
||||
graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow { _, flow ->
|
||||
val initializer = variable.initializer ?: return@mergeIncomingFlow
|
||||
exitVariableInitialization(flow, initializer, variable, assignmentLhs = null, hadExplicitType)
|
||||
}
|
||||
}
|
||||
|
||||
fun exitVariableAssignment(assignment: FirVariableAssignment) {
|
||||
graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow { flow ->
|
||||
graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow { _, flow ->
|
||||
val property = assignment.calleeReference?.toResolvedPropertySymbol()?.fir ?: return@mergeIncomingFlow
|
||||
if (property.isLocal || property.isVal) {
|
||||
exitVariableInitialization(flow, assignment.rValue, property, assignment.lValue, hasExplicitType = false)
|
||||
@@ -990,7 +992,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
fun exitLeftBinaryLogicExpressionArgument(binaryLogicExpression: FirBinaryLogicExpression) {
|
||||
val (leftExitNode, rightEnterNode) = graphBuilder.exitLeftBinaryLogicExpressionArgument(binaryLogicExpression)
|
||||
leftExitNode.mergeIncomingFlow()
|
||||
rightEnterNode.mergeIncomingFlow { flow ->
|
||||
rightEnterNode.mergeIncomingFlow { _, flow ->
|
||||
val leftOperandVariable = variableStorage.get(flow, binaryLogicExpression.leftOperand) ?: return@mergeIncomingFlow
|
||||
val isAnd = binaryLogicExpression.kind == LogicOperationKind.AND
|
||||
flow.commitOperationStatement(leftOperandVariable eq isAnd)
|
||||
@@ -1001,7 +1003,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
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 flowFromLeft = leftOperandNode.flow
|
||||
val flowFromRight = rightOperandNode.flow
|
||||
@@ -1101,11 +1103,13 @@ abstract class FirDataFlowAnalyzer(
|
||||
|
||||
fun exitElvisLhs(elvisExpression: FirElvisExpression) {
|
||||
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
|
||||
val lhsVariable = variableStorage.getOrCreateIfReal(lhsExitNode.mergeIncomingFlow(), elvisExpression.lhs)
|
||||
lhsIsNotNullNode.mergeIncomingFlow { flow ->
|
||||
lhsExitNode.mergeIncomingFlow()
|
||||
|
||||
val lhsVariable = variableStorage.getOrCreateIfReal(lhsExitNode.flow, elvisExpression.lhs)
|
||||
lhsIsNotNullNode.mergeIncomingFlow { _, flow ->
|
||||
lhsVariable?.let { flow.commitOperationStatement(it notEq null) }
|
||||
}
|
||||
rhsEnterNode.mergeIncomingFlow { flow ->
|
||||
rhsEnterNode.mergeIncomingFlow { _, flow ->
|
||||
lhsVariable?.let { flow.commitOperationStatement(it eq null) }
|
||||
}
|
||||
}
|
||||
@@ -1113,7 +1117,7 @@ abstract class FirDataFlowAnalyzer(
|
||||
@OptIn(UnexpandedTypeCheck::class)
|
||||
fun exitElvis(elvisExpression: FirElvisExpression, isLhsNotNull: Boolean, callCompleted: Boolean) {
|
||||
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
|
||||
// all statements from LHS unconditionally.
|
||||
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`.
|
||||
// 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 incomingEdgeKind = edgeFrom(it).kind
|
||||
if (if (isDead) !incomingEdgeKind.usedInDeadDfa else !incomingEdgeKind.usedInDfa) return@mapNotNull null
|
||||
|
||||
// `MergePostponedLambdaExitsNode` nodes form a parallel data flow graph. We never compute
|
||||
// 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)
|
||||
if (graphBuilder.lastNodeOrNull == this) {
|
||||
@@ -1186,24 +1196,20 @@ abstract class FirDataFlowAnalyzer(
|
||||
}
|
||||
currentReceiverState = result
|
||||
}
|
||||
return result
|
||||
return result.also { builder(path, it) }
|
||||
}
|
||||
|
||||
@OptIn(CfgInternals::class)
|
||||
private fun CFGNode<*>.setFlow(builder: MutableFlow): PersistentFlow {
|
||||
val flow = builder.freeze().also { this.flow = it }
|
||||
if (currentReceiverState === builder) {
|
||||
currentReceiverState = flow
|
||||
private fun CFGNode<*>.mergeIncomingFlow(
|
||||
builder: (FlowPath, MutableFlow) -> Unit = { _, _ -> },
|
||||
) {
|
||||
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
|
||||
// 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
|
||||
@@ -1251,4 +1257,10 @@ abstract class FirDataFlowAnalyzer(
|
||||
private fun MutableFlow.commitOperationStatement(statement: OperationStatement) =
|
||||
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.declarations.*
|
||||
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.controlFlowGraph
|
||||
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
|
||||
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
|
||||
open var flow: PersistentFlow
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
fun updateDeadStatus() {
|
||||
isDead = if (isUnion)
|
||||
@@ -746,6 +777,18 @@ class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode<FirStub>(owner, le
|
||||
@CfgInternals
|
||||
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 {
|
||||
return visitor.visitStubNode(this, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user