[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:
+79
-28
@@ -631,21 +631,21 @@ 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 { _, flow ->
|
exitNode.mergeIncomingFlow { path, flow ->
|
||||||
processWhileLoopExit(flow, exitNode, conditionEnterNode)
|
processWhileLoopExit(path, flow, exitNode, conditionEnterNode)
|
||||||
processLoopExit(flow, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode)
|
processLoopExit(flow, exitNode, exitNode.firstPreviousNode as LoopConditionExitNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processWhileLoopExit(flow: MutableFlow, node: LoopExitNode, conditionEnterNode: LoopConditionEnterNode) {
|
private fun processWhileLoopExit(path: FlowPath, flow: MutableFlow, node: LoopExitNode, conditionEnterNode: LoopConditionEnterNode) {
|
||||||
val possiblyChangedVariables = exitRepeatableStatement(node.fir)
|
val possiblyChangedVariables = exitRepeatableStatement(node.fir)
|
||||||
if (possiblyChangedVariables.isNullOrEmpty()) return
|
if (possiblyChangedVariables.isNullOrEmpty()) return
|
||||||
// While analyzing the loop we might have added some backwards jumps to `conditionEnterNode` which weren't
|
// While analyzing the loop we might have added some backwards jumps to `conditionEnterNode` which weren't
|
||||||
// there at the time its flow was computed - which is why we erased all information about `possiblyChangedVariables`
|
// there at the time its flow was computed - which is why we erased all information about `possiblyChangedVariables`
|
||||||
// from it. Now that we have those edges, we can restore type information for the code after the loop.
|
// from it. Now that we have those edges, we can restore type information for the code after the loop.
|
||||||
val conditionEnterFlow = conditionEnterNode.flow
|
val conditionEnterFlow = conditionEnterNode.getFlow(path)
|
||||||
val loopEnterAndContinueFlows = conditionEnterNode.livePreviousFlows
|
val loopEnterAndContinueFlows = conditionEnterNode.previousLiveNodes.map { it.getFlow(path) }.toList()
|
||||||
val conditionExitAndBreakFlows = node.livePreviousFlows
|
val conditionExitAndBreakFlows = node.previousLiveNodes.map { it.getFlow(path) }.toList()
|
||||||
possiblyChangedVariables.forEach { variable ->
|
possiblyChangedVariables.forEach { variable ->
|
||||||
// The statement about `variable` in `conditionEnterFlow` should be empty, so to obtain the new statement
|
// The statement about `variable` in `conditionEnterFlow` should be empty, so to obtain the new statement
|
||||||
// we can simply add the now-known input to whatever was inferred from nothing so long as the value is the same.
|
// we can simply add the now-known input to whatever was inferred from nothing so long as the value is the same.
|
||||||
@@ -769,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 { path, 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
|
||||||
@@ -779,7 +779,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
// TODO? all new implications in previous node's flow are valid here if receiver != null
|
// TODO? all new implications in previous node's flow are valid here if receiver != null
|
||||||
// (that requires a second level of implications: receiver != null => condition => effect).
|
// (that requires a second level of implications: receiver != null => condition => effect).
|
||||||
// KT-59689
|
// KT-59689
|
||||||
flow.addAllConditionally(expressionVariable notEq null, node.lastPreviousNode.flow)
|
flow.addAllConditionally(expressionVariable notEq null, node.lastPreviousNode.getFlow(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1003,10 +1003,10 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
graphBuilder.exitBinaryLogicExpression(binaryLogicExpression).mergeBinaryLogicOperatorFlow()
|
graphBuilder.exitBinaryLogicExpression(binaryLogicExpression).mergeBinaryLogicOperatorFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AbstractBinaryExitNode<FirBinaryLogicExpression>.mergeBinaryLogicOperatorFlow() = mergeIncomingFlow { _, flow ->
|
private fun AbstractBinaryExitNode<FirBinaryLogicExpression>.mergeBinaryLogicOperatorFlow() = mergeIncomingFlow { path, flow ->
|
||||||
val isAnd = fir.kind == LogicOperationKind.AND
|
val isAnd = fir.kind == LogicOperationKind.AND
|
||||||
val flowFromLeft = leftOperandNode.flow
|
val flowFromLeft = leftOperandNode.getFlow(path)
|
||||||
val flowFromRight = rightOperandNode.flow
|
val flowFromRight = rightOperandNode.getFlow(path)
|
||||||
|
|
||||||
val leftVariable = variableStorage.get(flowFromLeft, fir.leftOperand)
|
val leftVariable = variableStorage.get(flowFromLeft, fir.leftOperand)
|
||||||
val leftIsBoolean = leftVariable != null && fir.leftOperand.coneType.isBoolean
|
val leftIsBoolean = leftVariable != null && fir.leftOperand.coneType.isBoolean
|
||||||
@@ -1105,19 +1105,21 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
|
val (lhsExitNode, lhsIsNotNullNode, rhsEnterNode) = graphBuilder.exitElvisLhs(elvisExpression)
|
||||||
lhsExitNode.mergeIncomingFlow()
|
lhsExitNode.mergeIncomingFlow()
|
||||||
|
|
||||||
val lhsVariable = variableStorage.getOrCreateIfReal(lhsExitNode.flow, elvisExpression.lhs)
|
fun getLhsVariable(path: FlowPath): DataFlowVariable? =
|
||||||
lhsIsNotNullNode.mergeIncomingFlow { _, flow ->
|
variableStorage.getOrCreateIfReal(lhsExitNode.getFlow(path), elvisExpression.lhs)
|
||||||
lhsVariable?.let { flow.commitOperationStatement(it notEq null) }
|
|
||||||
|
lhsIsNotNullNode.mergeIncomingFlow { path, flow ->
|
||||||
|
getLhsVariable(path)?.let { flow.commitOperationStatement(it notEq null) }
|
||||||
}
|
}
|
||||||
rhsEnterNode.mergeIncomingFlow { _, flow ->
|
rhsEnterNode.mergeIncomingFlow { path, flow ->
|
||||||
lhsVariable?.let { flow.commitOperationStatement(it eq null) }
|
getLhsVariable(path)?.let { flow.commitOperationStatement(it eq null) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 { path, 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
|
||||||
@@ -1144,7 +1146,7 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
// implications, but for constant v the logic system can handle some basic cases of P(x).
|
// implications, but for constant v the logic system can handle some basic cases of P(x).
|
||||||
val rhs = (elvisExpression.rhs as? FirConstExpression<*>)?.value as? Boolean
|
val rhs = (elvisExpression.rhs as? FirConstExpression<*>)?.value as? Boolean
|
||||||
if (rhs != null) {
|
if (rhs != null) {
|
||||||
flow.addAllConditionally(elvisVariable eq !rhs, node.firstPreviousNode.flow)
|
flow.addAllConditionally(elvisVariable eq !rhs, node.firstPreviousNode.getFlow(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1161,9 +1163,6 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
|
|
||||||
// ------------------------------------------------------ Utils ------------------------------------------------------
|
// ------------------------------------------------------ Utils ------------------------------------------------------
|
||||||
|
|
||||||
private val CFGNode<*>.livePreviousFlows: List<PersistentFlow>
|
|
||||||
get() = previousNodes.mapNotNull { it.takeIf { this.isDead || !it.isDead }?.flow }
|
|
||||||
|
|
||||||
// Smart cast information is taken from `graphBuilder.lastNode`, but the problem with receivers specifically
|
// Smart cast information is taken from `graphBuilder.lastNode`, but the problem with receivers specifically
|
||||||
// is that they also affect tower resolver's scope stack. To allow accessing members on smart casted receivers,
|
// is that they also affect tower resolver's scope stack. To allow accessing members on smart casted receivers,
|
||||||
// we explicitly patch up the stack by calling `receiverUpdated` in a way that maintains consistency with
|
// we explicitly patch up the stack by calling `receiverUpdated` in a way that maintains consistency with
|
||||||
@@ -1177,18 +1176,39 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
path: FlowPath,
|
path: FlowPath,
|
||||||
builder: (FlowPath, MutableFlow) -> Unit,
|
builder: (FlowPath, MutableFlow) -> Unit,
|
||||||
): MutableFlow {
|
): MutableFlow {
|
||||||
val previousFlows = previousNodes.mapNotNull {
|
val previousFlows = previousDfaNodes.mapNotNull { (edge, node) ->
|
||||||
val incomingEdgeKind = edgeFrom(it).kind
|
// For CFGNodes that cause alternate flow paths to be created, only edges with matching labels should be merged. However, when
|
||||||
if (if (isDead) !incomingEdgeKind.usedInDeadDfa else !incomingEdgeKind.usedInDfa) return@mapNotNull null
|
// an alternate flow is being propagated through one of these CFGNodes - i.e., when the FirElements do not match - only
|
||||||
|
// NormalPath edges should be merged.
|
||||||
|
if (this is AlternateFlowStartMarker && path is FlowPath.CfgEdge) {
|
||||||
|
if (path.fir == this.fir && edge.label != path.label) {
|
||||||
|
return@mapNotNull null
|
||||||
|
} else if (path.fir != this.fir && edge.label != NormalPath) {
|
||||||
|
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()
|
if (node is MergePostponedLambdaExitsNode && !node.flowInitialized) node.mergeIncomingFlow()
|
||||||
|
|
||||||
it.flow
|
when (path) {
|
||||||
}
|
FlowPath.Default -> {
|
||||||
|
// For CFGNodes that are the end of alternate flows, use the alternate flow associated with the edge label.
|
||||||
|
if (node is AlternateFlowEndMarker) {
|
||||||
|
val alternatePath = FlowPath.CfgEdge(edge.label, node.fir)
|
||||||
|
node.getAlternateFlow(alternatePath) ?: node.flow
|
||||||
|
} else {
|
||||||
|
node.flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
node.getAlternateFlow(path) ?: node.flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
val result = logicSystem.joinFlow(previousFlows, isUnion)
|
val result = logicSystem.joinFlow(previousFlows, isUnion)
|
||||||
if (graphBuilder.lastNodeOrNull == this) {
|
if (path == FlowPath.Default && graphBuilder.lastNodeOrNull == this) {
|
||||||
// Here it is, the new `lastNode`. If the previous state is the only predecessor, then there is actually
|
// Here it is, the new `lastNode`. If the previous state is the only predecessor, then there is actually
|
||||||
// nothing to update; `addTypeStatement` has already ensured we have the correct information.
|
// nothing to update; `addTypeStatement` has already ensured we have the correct information.
|
||||||
if (currentReceiverState == null || previousFlows.singleOrNull() != currentReceiverState) {
|
if (currentReceiverState == null || previousFlows.singleOrNull() != currentReceiverState) {
|
||||||
@@ -1203,11 +1223,42 @@ abstract class FirDataFlowAnalyzer(
|
|||||||
private fun CFGNode<*>.mergeIncomingFlow(
|
private fun CFGNode<*>.mergeIncomingFlow(
|
||||||
builder: (FlowPath, MutableFlow) -> Unit = { _, _ -> },
|
builder: (FlowPath, MutableFlow) -> Unit = { _, _ -> },
|
||||||
) {
|
) {
|
||||||
|
// Always build the default flow path for all nodes.
|
||||||
val mutableDefaultFlow = buildIncomingFlow(FlowPath.Default, builder)
|
val mutableDefaultFlow = buildIncomingFlow(FlowPath.Default, builder)
|
||||||
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
|
val defaultFlow = mutableDefaultFlow.freeze().also { this.flow = it }
|
||||||
if (currentReceiverState === mutableDefaultFlow) {
|
if (currentReceiverState === mutableDefaultFlow) {
|
||||||
currentReceiverState = defaultFlow
|
currentReceiverState = defaultFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Propagate alternate flows from previous nodes.
|
||||||
|
val propagatePaths = previousDfaNodes.flatMapTo(mutableSetOf()) { (edge, node) ->
|
||||||
|
when (node) {
|
||||||
|
// If the source node is the end of alternate flows, do not propagate the alternate flows which have ended.
|
||||||
|
is AlternateFlowEndMarker -> node.alternateFlowPaths.filter { it !is FlowPath.CfgEdge || it.fir != node.fir }
|
||||||
|
// Otherwise, only propagate alternate flows which originate along a normal path edge.
|
||||||
|
else -> node.alternateFlowPaths.takeIf { edge.label == NormalPath } ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (path in propagatePaths) {
|
||||||
|
addAlternateFlow(path, buildIncomingFlow(path, builder).freeze())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any new alternate flows that should be created.
|
||||||
|
if (this is AlternateFlowStartMarker) {
|
||||||
|
val additionalPaths = previousDfaNodes
|
||||||
|
.mapNotNullTo(mutableSetOf()) { (edge, _) -> edge.label.takeIf { it != UncaughtExceptionPath } }
|
||||||
|
.map { FlowPath.CfgEdge(it, this.fir) }
|
||||||
|
for (path in additionalPaths) {
|
||||||
|
addAlternateFlow(path, buildIncomingFlow(path, builder).freeze())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CFGNode<*>.getFlow(path: FlowPath): PersistentFlow {
|
||||||
|
return when (path) {
|
||||||
|
FlowPath.Default -> flow
|
||||||
|
else -> getAlternateFlow(path) ?: error("no alternate flow for $path")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.fir.FirElement
|
|||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel
|
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]
|
* Sealed class representing a type of path through a [Control Flow Graph (CFG) Node][org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode] used
|
||||||
* 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
|
* 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
|
* 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:
|
* 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
|
* 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.
|
* 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.
|
* 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
|
* 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
|
* 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.
|
* [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<*>.firstPreviousNode: CFGNode<*> get() = previousNodes[0]
|
||||||
val CFGNode<*>.lastPreviousNode: CFGNode<*> get() = previousNodes.last()
|
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 EnterNodeMarker
|
||||||
interface ExitNodeMarker
|
interface ExitNodeMarker
|
||||||
interface GraphEnterNodeMarker : EnterNodeMarker
|
interface GraphEnterNodeMarker : EnterNodeMarker
|
||||||
interface GraphExitNodeMarker : ExitNodeMarker
|
interface GraphExitNodeMarker : ExitNodeMarker
|
||||||
|
interface AlternateFlowStartMarker
|
||||||
|
interface AlternateFlowEndMarker
|
||||||
|
|
||||||
// ----------------------------------- EnterNode for declaration with CFG -----------------------------------
|
// ----------------------------------- 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) {
|
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 {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitMergePostponedLambdaExitsNode(this, data)
|
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),
|
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 {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitFinallyBlockEnterNode(this, data)
|
return visitor.visitFinallyBlockEnterNode(this, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class FinallyBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level),
|
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 {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitFinallyBlockExitNode(this, data)
|
return visitor.visitFinallyBlockExitNode(this, data)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// ISSUE: KT-51759
|
||||||
|
|
||||||
|
fun testBreak(b: Boolean, s: String?) {
|
||||||
|
while (b) {
|
||||||
|
val x: String?
|
||||||
|
try {
|
||||||
|
x = s ?: break
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
x<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testContinue(b: Boolean, s: String?) {
|
||||||
|
while (b) {
|
||||||
|
val x: String?
|
||||||
|
try {
|
||||||
|
x = s ?: continue
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
x<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.length
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// FIR_IDENTICAL
|
|
||||||
// ISSUE: KT-51759
|
// ISSUE: KT-51759
|
||||||
|
|
||||||
fun testBreak(b: Boolean, s: String?) {
|
fun testBreak(b: Boolean, s: String?) {
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ fun castInTryAndCatch(s: Any) {
|
|||||||
} finally {
|
} finally {
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // shouldn't be resolved
|
s.<!UNRESOLVED_REFERENCE!>length<!> // shouldn't be resolved
|
||||||
}
|
}
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // should be smartcast
|
s.length // should be smartcast
|
||||||
}
|
}
|
||||||
|
|
||||||
fun castAtAll(s: Any) {
|
fun castAtAll(s: Any) {
|
||||||
|
|||||||
+521
-485
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -26,13 +26,13 @@ fun breakInTry_withNestedFinally() {
|
|||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be ok
|
x.bbb() // should be ok
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be ok
|
x.bbb() // should be ok
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be ok
|
x.aaa() // should be ok
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be ok
|
x.bbb() // should be ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fun returnInCatch() {
|
fun returnInCatch() {
|
||||||
@@ -46,7 +46,7 @@ fun returnInCatch() {
|
|||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be ok
|
x.aaa() // should be ok
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,13 +65,13 @@ fun returnInCatch_insideFinally() {
|
|||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>ccc<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>ccc<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be ok
|
x.aaa() // should be ok
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>ccc<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>ccc<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be ok
|
x.aaa() // should be ok
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>ccc<!>() // should be ok
|
x.ccc() // should be ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fun breakInCatch() {
|
fun breakInCatch() {
|
||||||
@@ -86,11 +86,11 @@ fun breakInCatch() {
|
|||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be ok
|
x.aaa() // should be ok
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be error
|
||||||
}
|
}
|
||||||
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
x.<!UNRESOLVED_REFERENCE!>aaa<!>() // should be error
|
||||||
x.<!UNRESOLVED_REFERENCE!>bbb<!>() // should be ok
|
x.bbb() // should be ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fun returnInFinally_insideTry_nonLocal() {
|
fun returnInFinally_insideTry_nonLocal() {
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ fun castInTry(s: Any) {
|
|||||||
} finally {
|
} finally {
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
||||||
}
|
}
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
s.length // Should be smartcast
|
||||||
}
|
}
|
||||||
|
|
||||||
fun castInTryAndFinally(s: Any) {
|
fun castInTryAndFinally(s: Any) {
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ fun castInTry(s: Any) {
|
|||||||
} finally {
|
} finally {
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
||||||
}
|
}
|
||||||
s.<!UNRESOLVED_REFERENCE!>length<!> // Shouldn't be resolved
|
s.<!UNRESOLVED_REFERENCE!>length<!> // Should be smartcast
|
||||||
}
|
}
|
||||||
|
|
||||||
fun castInTryAndFinally(s: Any) {
|
fun castInTryAndFinally(s: Any) {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ fun conditionalThrowInTry_rethrow_smartcastAfterTryCatchFinally(a: A) {
|
|||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
throw e
|
throw e
|
||||||
} finally {}
|
} finally {}
|
||||||
takeB(<!ARGUMENT_TYPE_MISMATCH!>a<!>)
|
takeB(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun conditionalThrowInTry_rethrow_noSmartcastInFinally(a: A) {
|
fun conditionalThrowInTry_rethrow_noSmartcastInFinally(a: A) {
|
||||||
|
|||||||
+2
-2
@@ -82,7 +82,7 @@ fun test5() {
|
|||||||
s2<!UNSAFE_CALL!>.<!>length
|
s2<!UNSAFE_CALL!>.<!>length
|
||||||
}
|
}
|
||||||
s1.length
|
s1.length
|
||||||
s2<!UNSAFE_CALL!>.<!>length
|
s2.length
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test6(s1: String?, s2: String?) {
|
fun test6(s1: String?, s2: String?) {
|
||||||
@@ -100,6 +100,6 @@ fun test6(s1: String?, s2: String?) {
|
|||||||
requireNotNull(s2)
|
requireNotNull(s2)
|
||||||
}
|
}
|
||||||
s<!UNSAFE_CALL!>.<!>length
|
s<!UNSAFE_CALL!>.<!>length
|
||||||
s1<!UNSAFE_CALL!>.<!>length
|
s1.length
|
||||||
s2.length
|
s2.length
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -84,7 +84,7 @@ fun test5() {
|
|||||||
s2<!UNSAFE_CALL!>.<!>length
|
s2<!UNSAFE_CALL!>.<!>length
|
||||||
}
|
}
|
||||||
s1.length
|
s1.length
|
||||||
s2<!UNSAFE_CALL!>.<!>length
|
s2.length
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test6(s1: String?, s2: String?) {
|
fun test6(s1: String?, s2: String?) {
|
||||||
@@ -102,6 +102,6 @@ fun test6(s1: String?, s2: String?) {
|
|||||||
requireNotNull(s2)
|
requireNotNull(s2)
|
||||||
}
|
}
|
||||||
s<!UNSAFE_CALL!>.<!>length
|
s<!UNSAFE_CALL!>.<!>length
|
||||||
s1<!UNSAFE_CALL!>.<!>length
|
s1.length
|
||||||
s2.length
|
s2.length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ fun case_4() {
|
|||||||
*/
|
*/
|
||||||
fun case_5() {
|
fun case_5() {
|
||||||
var x: Int? = null
|
var x: Int? = null
|
||||||
if (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!> == try { x = 10; null } finally {} && x != null) {
|
if (<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>x<!> == try { x = 10; null } finally {} && <!SENSELESS_COMPARISON!>x != null<!>) {
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,5 +52,5 @@ fun case_4() {
|
|||||||
try {
|
try {
|
||||||
x = null
|
x = null
|
||||||
} finally { }
|
} finally { }
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>not()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fun case_3() {
|
|||||||
*/
|
*/
|
||||||
fun case_4() {
|
fun case_4() {
|
||||||
var x: Int? = null
|
var x: Int? = null
|
||||||
if (x == try { x = 10; null } finally {} && x != null) {
|
if (x == try { x = 10; null } finally {} && <!SENSELESS_COMPARISON!>x != null<!>) {
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Int")!>x<!>.inv()
|
||||||
println(1)
|
println(1)
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ fun case_2() {
|
|||||||
try {
|
try {
|
||||||
x = null
|
x = null
|
||||||
} finally { }
|
} finally { }
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!>
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing?")!>x<!>
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean?")!>x<!><!UNSAFE_CALL!>.<!>not()
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>not()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ fun case_2() {
|
|||||||
var x: String?
|
var x: String?
|
||||||
x = "Test"
|
x = "Test"
|
||||||
println("${try { x = null } finally { }}")
|
println("${try { x = null } finally { }}")
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String?")!>x<!>
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!>
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String?")!>x<!><!UNSAFE_CALL!>.<!>length
|
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>length
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user