[FIR] Create alternate DFA flows through finally blocks
Entering a `finally` block can happen from many different places:
through an exception, a jump, or normal exit from the `try` block. When
in the `finally` block, all DFA flows must be merged to have correct
smart casting. However, after the `finally` block, if exiting normally
or because of a jump, the combined flow from within the `finally` block
should not be used, but rather an alternate flow which combines the
correct flows from before the `finally` block.
```
try {
str as String // Potential cast exception
} finally {
str.length // Shouldn`t be resolved
}
str.length // Should be resolved
```
When building DFA flows, track the start of possible alternate flows,
and continue building them until they end. Both of these situations are
now marked on CFGNodes via interfaces.
When building the default DFA flow, and the source node is the end node
of alternate flows, attempt to use the alternate flow with the same edge
label instead of the default flow of the source node.
#KT-56888 Fixed
This commit is contained in:
@@ -9,8 +9,8 @@ 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
|
||||
* Sealed class 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:
|
||||
*
|
||||
@@ -56,11 +56,11 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel
|
||||
* 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 {
|
||||
sealed class FlowPath {
|
||||
/**
|
||||
* The [FlowPath] which represents the combination of all flows leading into a CFG Node.
|
||||
*/
|
||||
data object Default : FlowPath
|
||||
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
|
||||
@@ -68,5 +68,5 @@ sealed interface FlowPath {
|
||||
* 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
|
||||
data class CfgEdge(val label: EdgeLabel, val fir: FirElement) : FlowPath()
|
||||
}
|
||||
|
||||
@@ -170,11 +170,22 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
|
||||
val CFGNode<*>.firstPreviousNode: CFGNode<*> get() = previousNodes[0]
|
||||
val CFGNode<*>.lastPreviousNode: CFGNode<*> get() = previousNodes.last()
|
||||
val CFGNode<*>.previousDfaNodes: Sequence<Pair<Edge, CFGNode<*>>>
|
||||
get() = previousNodes.asSequence()
|
||||
.map { edgeFrom(it) to it }
|
||||
.filter { (edge, _) -> if (isDead) edge.kind.usedInDeadDfa else edge.kind.usedInDfa }
|
||||
val CFGNode<*>.previousLiveNodes: Sequence<CFGNode<*>>
|
||||
get() = when {
|
||||
this.isDead -> previousNodes.asSequence()
|
||||
else -> previousNodes.asSequence().mapNotNull { it.takeIf { !it.isDead } }
|
||||
}
|
||||
|
||||
interface EnterNodeMarker
|
||||
interface ExitNodeMarker
|
||||
interface GraphEnterNodeMarker : EnterNodeMarker
|
||||
interface GraphExitNodeMarker : ExitNodeMarker
|
||||
interface AlternateFlowStartMarker
|
||||
interface AlternateFlowEndMarker
|
||||
|
||||
// ----------------------------------- EnterNode for declaration with CFG -----------------------------------
|
||||
|
||||
@@ -260,6 +271,17 @@ class PostponedLambdaExitNode(owner: ControlFlowGraph, override val fir: FirAnon
|
||||
}
|
||||
|
||||
class MergePostponedLambdaExitsNode(owner: ControlFlowGraph, override val fir: FirElement, level: Int) : CFGNode<FirElement>(owner, level) {
|
||||
|
||||
private var _flowInitialized = false
|
||||
val flowInitialized: Boolean get() = _flowInitialized
|
||||
override var flow: PersistentFlow
|
||||
get() = super.flow
|
||||
@CfgInternals
|
||||
set(value) {
|
||||
super.flow = value
|
||||
_flowInitialized = true
|
||||
}
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitMergePostponedLambdaExitsNode(this, data)
|
||||
}
|
||||
@@ -543,13 +565,13 @@ class CatchClauseExitNode(owner: ControlFlowGraph, override val fir: FirCatch, l
|
||||
}
|
||||
}
|
||||
class FinallyBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level),
|
||||
EnterNodeMarker {
|
||||
EnterNodeMarker, AlternateFlowStartMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitFinallyBlockEnterNode(this, data)
|
||||
}
|
||||
}
|
||||
class FinallyBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level),
|
||||
ExitNodeMarker {
|
||||
ExitNodeMarker, AlternateFlowEndMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitFinallyBlockExitNode(this, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user