FIR CFA: add edges from maybe-throwing statements to catch/finally
This commit is contained in:
@@ -13,7 +13,9 @@ import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.PersistentFlow
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.impl.FirImplicitNothingTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.isNothing
|
||||
import org.jetbrains.kotlin.fir.visitors.FirTransformer
|
||||
@@ -27,6 +29,8 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
|
||||
@OptIn(CfgInternals::class)
|
||||
val id = owner.nodeCount++
|
||||
|
||||
open val canThrow: Boolean get() = false
|
||||
|
||||
companion object {
|
||||
@CfgInternals
|
||||
fun addEdge(
|
||||
@@ -151,7 +155,10 @@ class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction,
|
||||
}
|
||||
|
||||
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int) : CFGNode<FirFunction>(owner, level),
|
||||
GraphExitNodeMarker {
|
||||
GraphExitNodeMarker, EdgeLabel {
|
||||
override val label: String
|
||||
get() = "return@${fir.symbol.callableId}"
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitFunctionExitNode(this, data)
|
||||
}
|
||||
@@ -418,7 +425,10 @@ class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, leve
|
||||
}
|
||||
}
|
||||
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int) : CFGNode<FirExpression>(owner, level),
|
||||
EnterNodeMarker {
|
||||
EnterNodeMarker, EdgeLabel {
|
||||
override val label: String
|
||||
get() = loop.label?.let { "continue@${it.name}" } ?: "continue"
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitLoopConditionEnterNode(this, data)
|
||||
}
|
||||
@@ -430,7 +440,10 @@ class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirExpres
|
||||
}
|
||||
}
|
||||
class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level),
|
||||
ExitNodeMarker {
|
||||
ExitNodeMarker, EdgeLabel {
|
||||
override val label: String
|
||||
get() = fir.label?.let { "break@${it.name}" } ?: "break"
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitLoopExitNode(this, data)
|
||||
}
|
||||
@@ -555,18 +568,27 @@ class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogic
|
||||
// ----------------------------------- Operator call -----------------------------------
|
||||
|
||||
class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode<FirTypeOperatorCall>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = fir.operation == FirOperation.AS
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitTypeOperatorCallNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
class ComparisonExpressionNode(owner: ControlFlowGraph, override val fir: FirComparisonExpression, level: Int) : CFGNode<FirComparisonExpression>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = true // TODO? only overridden compareTo
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitComparisonExpressionNode(this, data)
|
||||
}
|
||||
}
|
||||
|
||||
class EqualityOperatorCallNode(owner: ControlFlowGraph, override val fir: FirEqualityOperatorCall, level: Int) : AbstractBinaryExitNode<FirEqualityOperatorCall>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = true // TODO? only overridden equals
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitEqualityOperatorCallNode(this, data)
|
||||
}
|
||||
@@ -589,6 +611,9 @@ class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExp
|
||||
|
||||
class CheckNotNullCallNode(owner: ControlFlowGraph, override val fir: FirCheckNotNullCall, level: Int)
|
||||
: CFGNode<FirCheckNotNullCall>(owner, level), UnionNodeMarker {
|
||||
override val canThrow: Boolean
|
||||
get() = true
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitCheckNotNullCallNode(this, data)
|
||||
}
|
||||
@@ -601,6 +626,9 @@ class QualifiedAccessNode(
|
||||
override val fir: FirQualifiedAccessExpression,
|
||||
level: Int
|
||||
) : CFGNode<FirQualifiedAccessExpression>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = fir.toResolvedCallableSymbol() is FirPropertySymbol
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitQualifiedAccessNode(this, data)
|
||||
}
|
||||
@@ -618,6 +646,9 @@ class ResolvedQualifierNode(
|
||||
|
||||
class FunctionCallNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int)
|
||||
: CFGNode<FirFunctionCall>(owner, level), UnionNodeMarker {
|
||||
override val canThrow: Boolean
|
||||
get() = true
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitFunctionCallNode(this, data)
|
||||
}
|
||||
@@ -641,6 +672,9 @@ class GetClassCallNode(owner: ControlFlowGraph, override val fir: FirGetClassCal
|
||||
|
||||
class DelegatedConstructorCallNode(owner: ControlFlowGraph, override val fir: FirDelegatedConstructorCall, level: Int)
|
||||
: CFGNode<FirDelegatedConstructorCall>(owner, level), UnionNodeMarker {
|
||||
override val canThrow: Boolean
|
||||
get() = true // shouldn't matter since delegated constructor calls aren't wrapped in try-finally, but still
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitDelegatedConstructorCallNode(this, data)
|
||||
}
|
||||
@@ -658,6 +692,9 @@ class ThrowExceptionNode(
|
||||
override val fir: FirThrowExpression,
|
||||
level: Int
|
||||
) : CFGNode<FirThrowExpression>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = true
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitThrowExceptionNode(this, data)
|
||||
}
|
||||
@@ -761,6 +798,9 @@ class FakeExpressionEnterNode(owner: ControlFlowGraph, level: Int) : CFGNode<Fir
|
||||
// ----------------------------------- Smart-cast node -----------------------------------
|
||||
|
||||
class SmartCastExpressionExitNode(owner: ControlFlowGraph, override val fir: FirSmartCastExpression, level: Int) : CFGNode<FirSmartCastExpression>(owner, level) {
|
||||
override val canThrow: Boolean
|
||||
get() = fir.typeRef.coneType.isNothing
|
||||
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitSmartCastExpressionExitNode(this, data)
|
||||
}
|
||||
|
||||
+8
-12
@@ -6,8 +6,6 @@
|
||||
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
||||
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.expressions.FirLoop
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
|
||||
class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val kind: Kind) {
|
||||
@set:CfgInternals
|
||||
@@ -91,19 +89,17 @@ data class Edge(
|
||||
}
|
||||
}
|
||||
|
||||
sealed class EdgeLabel(val label: String?) {
|
||||
override fun toString(): String {
|
||||
return label ?: ""
|
||||
}
|
||||
sealed interface EdgeLabel {
|
||||
val label: String?
|
||||
}
|
||||
|
||||
object NormalPath : EdgeLabel(label = null)
|
||||
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
|
||||
object NormalPath : EdgeLabel {
|
||||
override val label: String? get() = null
|
||||
}
|
||||
|
||||
// TODO: Label `return`ing edge with this.
|
||||
data class ReturnPath(val target: FirFunctionSymbol<*>) : EdgeLabel(label = "return@${target.callableId}")
|
||||
data class LoopBreakPath(val loop: FirLoop) : EdgeLabel(loop.label?.let { "break@${it.name}" } ?: "break")
|
||||
data class LoopContinuePath(val loop: FirLoop) : EdgeLabel(loop.label?.let { "continue@${it.name}" } ?: "continue")
|
||||
object UncaughtExceptionPath : EdgeLabel {
|
||||
override val label: String get() = "onUncaughtException"
|
||||
}
|
||||
|
||||
enum class EdgeKind(
|
||||
val usedInDfa: Boolean, // propagate flow to alive nodes
|
||||
|
||||
Reference in New Issue
Block a user