FIR CFA: ensure on API level that graphs have enter & exit

What's going on with script graphs?..
This commit is contained in:
pyos
2022-12-10 20:59:26 +01:00
committed by Dmitriy Novozhilov
parent e710edbd77
commit 3887c80816
8 changed files with 104 additions and 193 deletions
@@ -19,11 +19,6 @@ fun ControlFlowGraph.getNodesInOrder(direction: TraverseDirection): List<CFGNode
TraverseDirection.Backward -> nodes.asReversed()
}
fun CFGNode<*>.isEnterNode(direction: TraverseDirection): Boolean = when (direction) {
TraverseDirection.Forward -> owner.enterNode == this
TraverseDirection.Backward -> owner.exitNode == this
}
val CFGNode<*>.previousCfgNodes: List<CFGNode<*>>
get() = previousNodes.filter {
val kind = edgeFrom(it).kind
@@ -231,12 +231,13 @@ abstract class FirDataFlowAnalyzer(
// ----------------------------------- Scripts ------------------------------------------
fun enterScript(script: FirScript) {
val res = graphBuilder.enterScript(script)
res.mergeIncomingFlow()
graphBuilder.enterScript(script).mergeIncomingFlow()
}
fun exitScript(script: FirScript) {
graphBuilder.exitScript(script)
fun exitScript(): ControlFlowGraph {
val (node, graph) = graphBuilder.exitScript()
node.mergeIncomingFlow()
return graph
}
// ----------------------------------- Value parameters (and it's defaults) -----------------------------------
@@ -113,13 +113,24 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Utils -----------------------------------
private fun pushGraph(graph: ControlFlowGraph) {
graphs.push(graph)
private inline fun <T, E : T, EnterNode, ExitNode> enterGraph(
fir: E,
name: String,
kind: ControlFlowGraph.Kind,
nodes: (E) -> Pair<EnterNode, ExitNode>
): Pair<EnterNode, ExitNode> where EnterNode : CFGNode<T>, EnterNode : GraphEnterNodeMarker, ExitNode : CFGNode<T>, ExitNode : GraphExitNodeMarker {
graphs.push(ControlFlowGraph(fir as? FirDeclaration, name, kind))
levelCounter++
return nodes(fir).also { (enterNode, exitNode) ->
currentGraph.enterNode = enterNode
currentGraph.exitNode = exitNode
lastNodes.push(enterNode)
}
}
private fun popGraph(): ControlFlowGraph {
levelCounter--
// TODO: count nodes per graph, validate the list after sorting
return graphs.pop().also { it.complete() }
}
@@ -137,24 +148,16 @@ class ControlFlowGraphBuilder {
val localFunctionNode = runIf(function.symbol.callableId.isLocal && currentGraph.kind.withBody) {
createLocalFunctionDeclarationNode(function).also { addNewSimpleNode(it) }
}
pushGraph(ControlFlowGraph(function, name, ControlFlowGraph.Kind.Function))
val enterNode = createFunctionEnterNode(function).also {
lastNodes.push(it)
val (enterNode, exitNode) = enterGraph(function, name, ControlFlowGraph.Kind.Function) {
createFunctionEnterNode(it) to createFunctionExitNode(it)
}
if (localFunctionNode != null) {
addEdge(localFunctionNode, enterNode)
} else {
enterToLocalClassesMembers.remove(function.symbol)?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
}
createFunctionExitNode(function).also {
exitTargetsForReturn.push(it)
exitTargetsForTry.push(it)
}
exitTargetsForReturn.push(exitNode)
exitTargetsForTry.push(exitNode)
return Pair(localFunctionNode, enterNode)
}
@@ -230,15 +233,14 @@ class ControlFlowGraphBuilder {
fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction): FunctionEnterNode {
val symbol = anonymousFunction.symbol
val flowSourceNode = postponedAnonymousFunctionNodes.getValue(symbol).first
pushGraph(ControlFlowGraph(anonymousFunction, "<anonymous>", ControlFlowGraph.Kind.AnonymousFunction))
val enterNode = createFunctionEnterNode(anonymousFunction)
val exitNode = createFunctionExitNode(anonymousFunction)
val (enterNode, exitNode) = enterGraph(anonymousFunction, "<anonymous>", ControlFlowGraph.Kind.AnonymousFunction) {
createFunctionEnterNode(it) to createFunctionExitNode(it)
}
exitTargetsForReturn.push(exitNode)
if (!anonymousFunction.invocationKind.isInPlace) {
exitTargetsForTry.push(exitNode)
}
addEdge(flowSourceNode, enterNode)
lastNodes.push(enterNode)
return enterNode
}
@@ -364,11 +366,12 @@ class ControlFlowGraphBuilder {
fun enterClass(klass: FirClass, buildGraph: Boolean): Pair<CFGNode<*>?, ClassEnterNode>? {
if (!buildGraph) {
pushGraph(ControlFlowGraph(null, "STUB_CLASS_GRAPH", ControlFlowGraph.Kind.Stub))
graphs.push(ControlFlowGraph(null, "<discarded class graph>", ControlFlowGraph.Kind.ClassInitializer))
levelCounter++
return null
}
val enterNode = when {
val localClassEnterNode = when {
// TODO: enum classes cannot be local so this is mostly fine, but it looks hacky. Maybe handle FirEnumEntry?
klass is FirAnonymousObject && klass.classKind != ClassKind.ENUM_ENTRY -> createAnonymousObjectEnterNode(klass)
// Local classes are only initialized on first use, so they look pretty much like named functions:
@@ -382,33 +385,34 @@ class ControlFlowGraphBuilder {
is FirRegularClass -> klass.name.asString()
else -> throw IllegalArgumentException("Unknown class kind: ${klass::class}")
}
pushGraph(ControlFlowGraph(klass, name, ControlFlowGraph.Kind.ClassInitializer))
val graphEnterNode = createClassEnterNode(klass)
lastNodes.push(graphEnterNode)
if (enterNode != null) {
addEdge(enterNode, graphEnterNode)
val (enterNode, _) = enterGraph(klass, name, ControlFlowGraph.Kind.ClassInitializer) {
createClassEnterNode(it) to createClassExitNode(it)
}
if (localClassEnterNode != null) {
addEdge(localClassEnterNode, enterNode)
} else {
enterToLocalClassesMembers.remove(klass.symbol)?.let { addEdge(it, graphEnterNode, preferredKind = EdgeKind.DfgForward) }
enterToLocalClassesMembers.remove(klass.symbol)?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
}
if (graphEnterNode.previousNodes.isNotEmpty()) {
if (enterNode.previousNodes.isNotEmpty()) {
for (member in klass.declarations) {
if (member is FirFunction || member is FirAnonymousInitializer || member is FirField || member is FirClass || member is FirProperty) {
enterToLocalClassesMembers[member.symbol] = graphEnterNode
enterToLocalClassesMembers[member.symbol] = enterNode
}
if (member is FirProperty) {
member.getter?.let { enterToLocalClassesMembers[it.symbol] = graphEnterNode }
member.setter?.let { enterToLocalClassesMembers[it.symbol] = graphEnterNode }
member.getter?.let { enterToLocalClassesMembers[it.symbol] = enterNode }
member.setter?.let { enterToLocalClassesMembers[it.symbol] = enterNode }
}
}
}
return enterNode to graphEnterNode
return localClassEnterNode to enterNode
}
fun exitClass(): Pair<AnonymousObjectExitNode?, ControlFlowGraph>? {
if (currentGraph.kind == ControlFlowGraph.Kind.Stub) {
popGraph()
if (currentGraph.declaration == null) {
levelCounter--
graphs.pop().also { assert(it.kind == ControlFlowGraph.Kind.ClassInitializer) }
return null
}
@@ -468,7 +472,7 @@ class ControlFlowGraphBuilder {
prevInitPartNode = partNode
}
val exitNode = createClassExitNode(klass)
val exitNode = currentGraph.exitNode as ClassExitNode
addEdge(node, exitNode, preferredKind = EdgeKind.CfgForward)
if (prevInitPartNode != null) {
addEdge(prevInitPartNode, exitNode, preferredKind = EdgeKind.DeadForward, propagateDeadness = false)
@@ -500,16 +504,19 @@ class ControlFlowGraphBuilder {
}
fun enterScript(script: FirScript): ScriptEnterNode {
pushGraph(ControlFlowGraph(null, "SCRIPT_GRAPH", ControlFlowGraph.Kind.Function))
val enterNode = createScriptEnterNode(script)
lastNodes.push(enterNode)
val (enterNode, exitNode) = enterGraph(script, "SCRIPT_GRAPH", ControlFlowGraph.Kind.Function) {
createScriptEnterNode(it) to createScriptExitNode(it)
}
exitTargetsForTry.push(exitNode)
return enterNode
}
fun exitScript(script: FirScript): ScriptExitNode {
return createScriptExitNode(script).also {
addNewSimpleNodeIfPossible(it)
}
fun exitScript(): Pair<ScriptExitNode, ControlFlowGraph> {
val exitNode = exitTargetsForTry.pop() as ScriptExitNode
addNewSimpleNode(exitNode)
val graph = popGraph()
assert(graph.exitNode == exitNode)
return exitNode to graph
}
// ----------------------------------- Value parameters (and it's defaults) -----------------------------------
@@ -518,14 +525,14 @@ class ControlFlowGraphBuilder {
if (valueParameter.defaultValue == null) return null
val outerEnterNode = createEnterValueParameterNode(valueParameter)
pushGraph(ControlFlowGraph(valueParameter, "default value of ${valueParameter.name}", ControlFlowGraph.Kind.DefaultArgument))
val innerExitNode = createExitDefaultArgumentsNode(valueParameter)
val innerEnterNode = createEnterDefaultArgumentsNode(valueParameter)
addNewSimpleNode(outerEnterNode)
addEdge(outerEnterNode, innerEnterNode)
lastNodes.push(innerEnterNode)
exitTargetsForTry.push(innerExitNode)
return outerEnterNode to innerEnterNode
val name = "default value of ${valueParameter.name}"
val (enterNode, exitNode) = enterGraph(valueParameter, name, ControlFlowGraph.Kind.DefaultArgument) {
createEnterDefaultArgumentsNode(it) to createExitDefaultArgumentsNode(it)
}
addEdge(outerEnterNode, enterNode)
exitTargetsForTry.push(exitNode)
return outerEnterNode to enterNode
}
fun exitValueParameter(valueParameter: FirValueParameter): Triple<ExitDefaultArgumentsNode, ExitValueParameterNode, ControlFlowGraph>? {
@@ -563,13 +570,11 @@ class ControlFlowGraphBuilder {
fun enterProperty(property: FirProperty): PropertyInitializerEnterNode? {
if (!property.hasInitialization) return null
pushGraph(ControlFlowGraph(property, "val ${property.name}", ControlFlowGraph.Kind.PropertyInitializer))
val enterNode = createPropertyInitializerEnterNode(property)
val exitNode = createPropertyInitializerExitNode(property)
val (enterNode, exitNode) = enterGraph(property, "val ${property.name}", ControlFlowGraph.Kind.PropertyInitializer) {
createPropertyInitializerEnterNode(it) to createPropertyInitializerExitNode(it)
}
exitTargetsForTry.push(exitNode)
enterToLocalClassesMembers.remove(property.symbol)?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
lastNodes.push(enterNode)
return enterNode
}
@@ -587,13 +592,11 @@ class ControlFlowGraphBuilder {
fun enterField(field: FirField): FieldInitializerEnterNode? {
if (field.initializer == null) return null
pushGraph(ControlFlowGraph(field, "val ${field.name}", ControlFlowGraph.Kind.FieldInitializer))
val enterNode = createFieldInitializerEnterNode(field)
val exitNode = createFieldInitializerExitNode(field)
val (enterNode, exitNode) = enterGraph(field, "val ${field.name}", ControlFlowGraph.Kind.FieldInitializer) {
createFieldInitializerEnterNode(it) to createFieldInitializerExitNode(it)
}
exitTargetsForTry.push(exitNode)
enterToLocalClassesMembers.remove(field.symbol)?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
lastNodes.push(enterNode)
return enterNode
}
@@ -1192,7 +1195,8 @@ class ControlFlowGraphBuilder {
// Things like annotations and `contract { ... }` use normal call resolution, but aren't real expressions
// and are never evaluated. We'll push all nodes created in the process into a stub graph, then throw it away.
// TODO: don't waste time creating the nodes in the first place
pushGraph(ControlFlowGraph(null, "STUB_GRAPH_FOR_FAKE_EXPRESSION", ControlFlowGraph.Kind.FakeCall))
graphs.push(ControlFlowGraph(null, "<compile-time expression graph>", ControlFlowGraph.Kind.FakeCall))
levelCounter++
return createFakeExpressionEnterNode().also {
lastNodes.push(it)
exitTargetsForTry.push(it) // technically might create CFG loops, but the graph will never be visited anyway...
@@ -1200,9 +1204,10 @@ class ControlFlowGraphBuilder {
}
fun exitFakeExpression() {
levelCounter--
lastNodes.pop()
exitTargetsForTry.pop()
popGraph()
graphs.pop().also { assert(it.kind == ControlFlowGraph.Kind.FakeCall) }
}
// ----------------------------------- Callable references -----------------------------------
@@ -1219,13 +1224,11 @@ class ControlFlowGraphBuilder {
fun enterInitBlock(initBlock: FirAnonymousInitializer): InitBlockEnterNode {
// TODO: questionable moment that we should pass data flow from init to init
pushGraph(ControlFlowGraph(initBlock, "init block", ControlFlowGraph.Kind.Function))
val enterNode = createInitBlockEnterNode(initBlock)
val exitNode = createInitBlockExitNode(initBlock)
val (enterNode, exitNode) = enterGraph(initBlock, "init block", ControlFlowGraph.Kind.Function) {
createInitBlockEnterNode(it) to createInitBlockExitNode(it)
}
exitTargetsForTry.push(exitNode)
enterToLocalClassesMembers.remove(initBlock.symbol)?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
lastNodes.push(enterNode)
return enterNode
}
@@ -67,17 +67,11 @@ fun ControlFlowGraphBuilder.createFieldInitializerExitNode(fir: FirField): Field
fun ControlFlowGraphBuilder.createFieldInitializerEnterNode(fir: FirField): FieldInitializerEnterNode =
FieldInitializerEnterNode(currentGraph, fir, levelCounter, createId())
@OptIn(CfgInternals::class)
fun ControlFlowGraphBuilder.createFunctionEnterNode(fir: FirFunction): FunctionEnterNode =
FunctionEnterNode(currentGraph, fir, levelCounter, createId()).also {
currentGraph.enterNode = it
}
FunctionEnterNode(currentGraph, fir, levelCounter, createId())
@OptIn(CfgInternals::class)
fun ControlFlowGraphBuilder.createFunctionExitNode(fir: FirFunction): FunctionExitNode =
FunctionExitNode(currentGraph, fir, levelCounter, createId()).also {
currentGraph.exitNode = it
}
FunctionExitNode(currentGraph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createLocalFunctionDeclarationNode(fir: FirFunction): LocalFunctionDeclarationNode =
LocalFunctionDeclarationNode(currentGraph, fir, levelCounter, createId())
@@ -513,9 +513,7 @@ open class FirDeclarationsResolveTransformer(transformer: FirAbstractBodyResolve
val result = context.withScopesForScript(script, components) {
transformDeclarationContent(script, data) as FirScript
}
dataFlowAnalyzer.exitScript(script)
dataFlowAnalyzer.exitScript() // TODO: FirScript should be a FirControlFlowGraphOwner
return result
}
@@ -65,11 +65,6 @@ sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level:
}
}
init {
@Suppress("LeakingThis")
owner.addNode(this)
}
private val _previousNodes: MutableList<CFGNode<*>> = SmartList()
private val _followingNodes: MutableList<CFGNode<*>> = SmartList()
@@ -132,6 +127,8 @@ val CFGNode<*>.lastPreviousNode: CFGNode<*> get() = previousNodes.last()
interface EnterNodeMarker
interface ExitNodeMarker
interface GraphEnterNodeMarker : EnterNodeMarker
interface GraphExitNodeMarker : ExitNodeMarker
// a ---> b ---> d
// \-> c -/
@@ -153,24 +150,25 @@ sealed class CFGNodeWithCfgOwner<out E : FirControlFlowGraphOwner>(owner: Contro
// ----------------------------------- Named function -----------------------------------
class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNode<FirFunction>(owner, level, id),
EnterNodeMarker {
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFunctionEnterNode(this, data)
}
}
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNode<FirFunction>(owner, level, id),
ExitNodeMarker {
GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFunctionExitNode(this, data)
}
}
class LocalFunctionDeclarationNode(owner: ControlFlowGraph, override val fir: FirFunction, level: Int, id: Int) : CFGNodeWithCfgOwner<FirFunction>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitLocalFunctionDeclarationNode(this, data)
}
}
// ----------------------------------- Default arguments -----------------------------------
class EnterValueParameterNode(owner: ControlFlowGraph, override val fir: FirValueParameter, level: Int, id: Int) : CFGNodeWithCfgOwner<FirValueParameter>(owner, level, id) {
@@ -179,25 +177,15 @@ class EnterValueParameterNode(owner: ControlFlowGraph, override val fir: FirValu
}
}
@OptIn(CfgInternals::class)
class EnterDefaultArgumentsNode(owner: ControlFlowGraph, override val fir: FirValueParameter, level: Int, id: Int) : CFGNode<FirValueParameter>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterDefaultArgumentsNode(this, data)
}
}
@OptIn(CfgInternals::class)
class ExitDefaultArgumentsNode(owner: ControlFlowGraph, override val fir: FirValueParameter, level: Int, id: Int) : CFGNode<FirValueParameter>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitDefaultArgumentsNode(this, data)
}
@@ -245,24 +233,15 @@ class AnonymousFunctionExpressionNode(owner: ControlFlowGraph, override val fir:
// ----------------------------------- Classes -----------------------------------
@OptIn(CfgInternals::class)
class ClassEnterNode(owner: ControlFlowGraph, override val fir: FirClass, level: Int, id: Int) : CFGNode<FirClass>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitClassEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class ClassExitNode(owner: ControlFlowGraph, override val fir: FirClass, level: Int, id: Int) : CFGNodeWithSubgraphs<FirClass>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
GraphExitNodeMarker {
lateinit override var subGraphs: List<ControlFlowGraph>
@@ -297,13 +276,13 @@ class AnonymousObjectExpressionExitNode(owner: ControlFlowGraph, override val fi
// ----------------------------------- Scripts ------------------------------------------
class ScriptEnterNode(owner: ControlFlowGraph, override val fir: FirScript, level: Int, id: Int) : CFGNode<FirScript>(owner, level, id) {
class ScriptEnterNode(owner: ControlFlowGraph, override val fir: FirScript, level: Int, id: Int) : CFGNode<FirScript>(owner, level, id), GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitScriptEnterNode(this, data)
}
}
class ScriptExitNode(owner: ControlFlowGraph, override val fir: FirScript, level: Int, id: Int) : CFGNode<FirScript>(owner, level, id) {
class ScriptExitNode(owner: ControlFlowGraph, override val fir: FirScript, level: Int, id: Int) : CFGNode<FirScript>(owner, level, id), GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitScriptExitNode(this, data)
}
@@ -319,25 +298,15 @@ class PartOfClassInitializationNode(owner: ControlFlowGraph, override val fir: F
// ----------------------------------- Property -----------------------------------
@OptIn(CfgInternals::class)
class PropertyInitializerEnterNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode<FirProperty>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPropertyInitializerEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class PropertyInitializerExitNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int, id: Int) : CFGNode<FirProperty>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitPropertyInitializerExitNode(this, data)
}
@@ -353,25 +322,15 @@ class DelegateExpressionExitNode(owner: ControlFlowGraph, override val fir: FirE
// ----------------------------------- Field -----------------------------------
@OptIn(CfgInternals::class)
class FieldInitializerEnterNode(owner: ControlFlowGraph, override val fir: FirField, level: Int, id: Int) : CFGNode<FirField>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFieldInitializerEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class FieldInitializerExitNode(owner: ControlFlowGraph, override val fir: FirField, level: Int, id: Int) : CFGNode<FirField>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitFieldInitializerExitNode(this, data)
}
@@ -379,25 +338,15 @@ class FieldInitializerExitNode(owner: ControlFlowGraph, override val fir: FirFie
// ----------------------------------- Init -----------------------------------
@OptIn(CfgInternals::class)
class InitBlockEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int, id: Int) : CFGNode<FirAnonymousInitializer>(owner, level, id),
EnterNodeMarker {
init {
owner.enterNode = this
}
GraphEnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitInitBlockEnterNode(this, data)
}
}
@OptIn(CfgInternals::class)
class InitBlockExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int, id: Int) : CFGNode<FirAnonymousInitializer>(owner, level, id),
ExitNodeMarker {
init {
owner.exitNode = this
}
GraphExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitInitBlockExitNode(this, data)
}
@@ -10,15 +10,8 @@ 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) {
private var _nodes: MutableList<CFGNode<*>> = mutableListOf()
val nodes: List<CFGNode<*>>
get() = _nodes
internal fun addNode(node: CFGNode<*>) {
assertState(State.Building)
_nodes.add(node)
}
lateinit var nodes: List<CFGNode<*>>
private set
@set:CfgInternals
lateinit var enterNode: CFGNode<*>
@@ -29,36 +22,12 @@ class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val k
val isSubGraph: Boolean
get() = enterNode.previousNodes.isNotEmpty()
var state: State = State.Building
private set
val subGraphs: List<ControlFlowGraph>
get() = _nodes.flatMap { (it as? CFGNodeWithSubgraphs<*>)?.subGraphs ?: emptyList() }
get() = nodes.flatMap { (it as? CFGNodeWithSubgraphs<*>)?.subGraphs ?: emptyList() }
@CfgInternals
fun complete() {
assertState(State.Building)
state = State.Completed
if (kind == Kind.Stub) return
val sortedNodes = orderNodes()
// TODO Fix this
// assert(sortedNodes.size == _nodes.size)
// for (node in _nodes) {
// assert(node in sortedNodes)
// }
_nodes.clear()
_nodes.addAll(sortedNodes)
}
private fun assertState(state: State) {
assert(this.state == state) {
"This action can not be performed at $this state"
}
}
enum class State {
Building,
Completed;
nodes = orderNodes()
}
enum class Kind(val withBody: Boolean) {
@@ -70,7 +39,6 @@ class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val k
TopLevel(withBody = false),
FakeCall(withBody = true),
DefaultArgument(withBody = true),
Stub(withBody = false)
}
}
@@ -146,7 +114,7 @@ enum class EdgeKind(
DeadBackward(usedInDfa = false, usedInDeadDfa = false, usedInCfa = true, isBack = true, isDead = true)
}
private fun ControlFlowGraph.orderNodes(): LinkedHashSet<CFGNode<*>> {
private fun ControlFlowGraph.orderNodes(): List<CFGNode<*>> {
val visitedNodes = linkedSetOf<CFGNode<*>>()
/*
* [delayedNodes] is needed to accomplish next order contract:
@@ -173,5 +141,5 @@ private fun ControlFlowGraph.orderNodes(): LinkedHashSet<CFGNode<*>> {
}
}
}
return visitedNodes
return visitedNodes.toList()
}
@@ -9,6 +9,8 @@ import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.resolve.dfa.FirControlFlowGraphReferenceImpl
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.GraphEnterNodeMarker
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.GraphExitNodeMarker
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
import org.jetbrains.kotlin.test.Assertions
@@ -19,7 +21,8 @@ class FirCfgConsistencyChecker(private val assertions: Assertions) : FirVisitorV
override fun visitControlFlowGraphReference(controlFlowGraphReference: FirControlFlowGraphReference) {
val graph = (controlFlowGraphReference as? FirControlFlowGraphReferenceImpl)?.controlFlowGraph ?: return
assertions.assertEquals(ControlFlowGraph.State.Completed, graph.state)
assertions.assertEquals(graph.nodes.single { it is GraphEnterNodeMarker }, graph.enterNode)
assertions.assertEquals(graph.nodes.single { it is GraphExitNodeMarker }, graph.exitNode)
checkConsistency(graph)
checkOrder(graph)
}