FIR CFA: add edges from maybe-throwing statements to catch/finally

This commit is contained in:
pyos
2023-01-16 12:15:52 +01:00
committed by teamcity
parent ef208d9eac
commit 8aa50e9446
17 changed files with 156 additions and 64 deletions
@@ -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)
}
@@ -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