[FIR] Add control flow graph and infrastructure for building it

For some of language constructions cfg is dummy (e.g. for finally blocks
  or anonymous initializers)
This commit is contained in:
Dmitriy Novozhilov
2019-08-22 11:38:10 +03:00
parent 198599f3c7
commit dcfc75a58f
5 changed files with 1078 additions and 0 deletions
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner
interface Stack<T> {
val size: Int
fun top(): T
fun pop(): T
fun push(value: T)
}
fun <T> stackOf(vararg values: T): Stack<T> = StackImpl(*values, pushCallback = null, popCallback = null)
fun <T> stackWithCallbacks(pushCallback: (T) -> Unit, popCallback: (T) -> Unit): Stack<T> =
StackImpl(pushCallback = pushCallback, popCallback = popCallback)
val Stack<*>.isEmpty: Boolean get() = size == 0
val Stack<*>.isNotEmpty: Boolean get() = size != 0
fun <T> Stack<T>.topOrNull(): T? = if (size == 0) null else top()
private class StackImpl<T>(
vararg values: T,
private val pushCallback: ((T) -> Unit)?,
private val popCallback: ((T) -> Unit)?
) : Stack<T> {
private val stack = mutableListOf(*values)
override fun top(): T = stack[stack.size - 1]
override fun pop(): T = stack.removeAt(stack.size - 1).also { element ->
popCallback?.let { it(element) }
}
override fun push(value: T) {
stack.add(value)
pushCallback?.let { it(value) }
}
override val size: Int get() = stack.size
}
class NodeStorage<T : FirElement, N : CFGNode<T>>(
pushCallback: ((N) -> Unit)? = null,
popCallback: ((N) -> Unit)? = null
) : Stack<N> {
private val stack: Stack<N> = StackImpl(pushCallback = pushCallback, popCallback = popCallback)
private val map: MutableMap<T, N> = mutableMapOf()
override val size: Int get() = stack.size
override fun top(): N = stack.top()
override fun pop(): N = stack.pop().also {
map.remove(it.fir)
}
override fun push(value: N) {
stack.push(value)
map[value.fir] = value
}
operator fun get(key: T): N? {
return map[key]
}
}
class SymbolBasedNodeStorage<T, N : CFGNode<T>> : Stack<N> where T : FirElement {
private val stack: Stack<N> = stackOf()
private val map: MutableMap<FirBasedSymbol<*>, N> = mutableMapOf()
override val size: Int get() = stack.size
override fun top(): N = stack.top()
override fun pop(): N = stack.pop().also {
map.remove((it.fir as FirSymbolOwner<*>).symbol)
}
override fun push(value: N) {
stack.push(value)
map[(value.fir as FirSymbolOwner<*>).symbol] = value
}
operator fun get(key: FirBasedSymbol<*>): N? {
return map[key]
}
}
@@ -0,0 +1,159 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.*
class ControlFlowGraph(val name: String) {
val nodes = mutableListOf<CFGNode<*>>()
lateinit var enterNode: CFGNode<*>
lateinit var exitNode: CFGNode<*>
}
sealed class CFGNode<out E : FirElement>(val owner: ControlFlowGraph, val level: Int) {
init {
owner.nodes += this
}
val previousNodes = mutableListOf<CFGNode<*>>()
val followingNodes = mutableListOf<CFGNode<*>>()
abstract val fir: E
var isDead: Boolean = false
}
val CFGNode<*>.usefulFollowingNodes: List<CFGNode<*>> get() = if (isDead) followingNodes else followingNodes.filterNot { it.isDead }
val CFGNode<*>.usefulPreviousNodes: List<CFGNode<*>> get() = if (isDead) previousNodes else previousNodes.filterNot { it.isDead }
interface ReturnableNothingNode {
val returnsNothing: Boolean
}
// ----------------------------------- Named function -----------------------------------
class FunctionEnterNode(owner: ControlFlowGraph, override val fir: FirFunction<*>, level: Int) : CFGNode<FirFunction<*>>(owner, level)
class FunctionExitNode(owner: ControlFlowGraph, override val fir: FirFunction<*>, level: Int) : CFGNode<FirFunction<*>>(owner, level)
// ----------------------------------- Property -----------------------------------
class PropertyEnterNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode<FirProperty>(owner, level)
class PropertyExitNode(owner: ControlFlowGraph, override val fir: FirProperty, level: Int) : CFGNode<FirProperty>(owner, level)
// ----------------------------------- Init -----------------------------------
class InitBlockEnterNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int) : CFGNode<FirAnonymousInitializer>(owner, level)
class InitBlockExitNode(owner: ControlFlowGraph, override val fir: FirAnonymousInitializer, level: Int) : CFGNode<FirAnonymousInitializer>(owner, level)
// ----------------------------------- Block -----------------------------------
class BlockEnterNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int) : CFGNode<FirBlock>(owner, level)
class BlockExitNode(owner: ControlFlowGraph, override val fir: FirBlock, level: Int) : CFGNode<FirBlock>(owner, level)
// ----------------------------------- When -----------------------------------
class WhenEnterNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int) : CFGNode<FirWhenExpression>(owner, level)
class WhenExitNode(owner: ControlFlowGraph, override val fir: FirWhenExpression, level: Int) : CFGNode<FirWhenExpression>(owner, level)
class WhenBranchConditionEnterNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode<FirWhenBranch>(owner, level)
class WhenBranchConditionExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode<FirWhenBranch>(owner, level)
class WhenBranchResultExitNode(owner: ControlFlowGraph, override val fir: FirWhenBranch, level: Int) : CFGNode<FirWhenBranch>(owner, level)
// ----------------------------------- Loop -----------------------------------
class LoopEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
class LoopBlockEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
class LoopExitNode(owner: ControlFlowGraph, override val fir: FirLoop, level: Int) : CFGNode<FirLoop>(owner, level)
// ----------------------------------- Try-catch-finally -----------------------------------
class TryExpressionEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
class TryMainBlockEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
class TryMainBlockExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
class CatchClauseEnterNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int) : CFGNode<FirCatch>(owner, level)
class CatchClauseExitNode(owner: ControlFlowGraph, override val fir: FirCatch, level: Int) : CFGNode<FirCatch>(owner, level)
class FinallyBlockEnterNode(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)
class FinallyProxyEnterNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
class FinallyProxyExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
class TryExpressionExitNode(owner: ControlFlowGraph, override val fir: FirTryExpression, level: Int) : CFGNode<FirTryExpression>(owner, level)
// ----------------------------------- Boolean operators -----------------------------------
abstract class AbstractBinaryExitNode<T : FirElement>(owner: ControlFlowGraph, level: Int) : CFGNode<T>(owner, level) {
val leftOperandNode: CFGNode<*> get() = previousNodes[0]
val rightOperandNode: CFGNode<*> get() = previousNodes[1]
}
class BinaryAndEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode<FirBinaryLogicExpression>(owner, level)
class BinaryAndExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : AbstractBinaryExitNode<FirBinaryLogicExpression>(owner, level)
class BinaryOrEnterNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode<FirBinaryLogicExpression>(owner, level)
// Exit left node pass after left argument in case if that argument is `false`
class BinaryOrExitLeftOperandNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : CFGNode<FirBinaryLogicExpression>(owner, level)
class BinaryOrExitNode(owner: ControlFlowGraph, override val fir: FirBinaryLogicExpression, level: Int) : AbstractBinaryExitNode<FirBinaryLogicExpression>(owner, level)
// ----------------------------------- Operator call -----------------------------------
class TypeOperatorCallNode(owner: ControlFlowGraph, override val fir: FirTypeOperatorCall, level: Int) : CFGNode<FirTypeOperatorCall>(owner, level)
class OperatorCallNode(owner: ControlFlowGraph, override val fir: FirOperatorCall, level: Int) : AbstractBinaryExitNode<FirOperatorCall>(owner, level)
// ----------------------------------- Jump -----------------------------------
class JumpNode(owner: ControlFlowGraph, override val fir: FirJump<*>, level: Int) : CFGNode<FirJump<*>>(owner, level)
class ConstExpressionNode(owner: ControlFlowGraph, override val fir: FirConstExpression<*>, level: Int) : CFGNode<FirConstExpression<*>>(owner, level)
// ----------------------------------- Resolvable call -----------------------------------
class QualifiedAccessNode(
owner: ControlFlowGraph,
override val fir: FirQualifiedAccessExpression,
override val returnsNothing: Boolean,
level: Int
) : CFGNode<FirQualifiedAccessExpression>(owner, level), ReturnableNothingNode
class FunctionCallNode(
owner: ControlFlowGraph,
override val fir: FirFunctionCall,
override val returnsNothing: Boolean,
level: Int
) : CFGNode<FirFunctionCall>(owner, level), ReturnableNothingNode
class ThrowExceptionNode(
owner: ControlFlowGraph,
override val fir: FirThrowExpression,
level: Int
) : CFGNode<FirThrowExpression>(owner, level), ReturnableNothingNode {
override val returnsNothing: Boolean get() = true
}
class StubNode(owner: ControlFlowGraph, level: Int) : CFGNode<FirStub>(owner, level) {
init {
isDead = true
}
override val fir: FirStub get() = FirStub
}
class VariableDeclarationNode(owner: ControlFlowGraph, override val fir: FirVariable<*>, level: Int) : CFGNode<FirVariable<*>>(owner, level)
class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVariableAssignment, level: Int) : CFGNode<FirVariableAssignment>(owner, level)
// ----------------------------------- Other -----------------------------------
class AnnotationEnterNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode<FirAnnotationCall>(owner, level)
class AnnotationExitNode(owner: ControlFlowGraph, override val fir: FirAnnotationCall, level: Int) : CFGNode<FirAnnotationCall>(owner, level)
// ----------------------------------- Stub -----------------------------------
object FirStub : FirElement {
override val psi: PsiElement? get() = null
}
@@ -0,0 +1,507 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirAbstractPropertyAccessor
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.dfa.*
import org.jetbrains.kotlin.fir.resolve.transformers.resultType
import org.jetbrains.kotlin.fir.types.isNothing
class ControlFlowGraphBuilder : ControlFlowGraphNodeBuilder() {
private val graphs: Stack<ControlFlowGraph> = stackOf(ControlFlowGraph("<DUMP_GRAPH_FOR_ENUMS>"))
override val graph: ControlFlowGraph get() = graphs.top()
private val lexicalScopes: Stack<Stack<CFGNode<*>>> = stackOf(stackOf())
private val lastNodes: Stack<CFGNode<*>> get() = lexicalScopes.top()
val lastNode: CFGNode<*> get() = lastNodes.top()
private val exitNodes: Stack<CFGNode<*>> = stackOf()
private val functionExitNodes: NodeStorage<FirFunction<*>, FunctionExitNode> = NodeStorage(
pushCallback = { exitNodes.push(it) },
popCallback = { exitNodes.pop() }
)
private val whenExitNodes: NodeStorage<FirWhenExpression, WhenExitNode> = NodeStorage()
private val loopEnterNodes: NodeStorage<FirElement, CFGNode<FirElement>> = NodeStorage()
private val loopExitNodes: NodeStorage<FirLoop, LoopExitNode> = NodeStorage()
private val tryExitNodes: NodeStorage<FirTryExpression, TryExpressionExitNode> = NodeStorage()
private val catchNodeStorages: Stack<NodeStorage<FirCatch, CatchClauseEnterNode>> = stackOf()
private val catchNodeStorage: NodeStorage<FirCatch, CatchClauseEnterNode> get() = catchNodeStorages.top()
private val binaryAndExitNodes: Stack<BinaryAndExitNode> = stackOf()
private val binaryOrExitNodes: Stack<BinaryOrExitNode> = stackOf()
private val topLevelVariableExitNodes: Stack<PropertyExitNode> = stackWithCallbacks(
pushCallback = { exitNodes.push(it) },
popCallback = { exitNodes.pop() }
)
private val initBlockExitNodes: Stack<InitBlockExitNode> = stackWithCallbacks(
pushCallback = { exitNodes.push(it) },
popCallback = { exitNodes.pop() }
)
override var levelCounter: Int = 0
fun isTopLevel(): Boolean = graphs.size == 1
// ----------------------------------- Named function -----------------------------------
fun enterFunction(function: FirFunction<*>): FunctionEnterNode {
val name = when (function) {
is FirNamedFunction -> function.name.asString()
is FirAbstractPropertyAccessor -> if (function.isGetter) "<getter>" else "<setter>"
is FirAnonymousFunction -> "<anonymous>" // TODO: add check to lambda or fun
is FirConstructor -> function.name.asString()
else -> throw IllegalArgumentException("Unknown function: ${function.render()}")
}
graphs.push(ControlFlowGraph(name))
functionExitNodes.push(createFunctionExitNode(function))
lexicalScopes.push(stackOf())
return createFunctionEnterNode(function).also { lastNodes.push(it) }.also { levelCounter++ }
}
fun exitFunction(function: FirFunction<*>): Pair<FunctionExitNode, ControlFlowGraph> {
levelCounter--
val exitNode = functionExitNodes.pop()
addEdge(lastNodes.pop(), exitNode)
lexicalScopes.pop()
return exitNode to graphs.pop()
}
// ----------------------------------- Block -----------------------------------
fun enterBlock(block: FirBlock): BlockEnterNode {
return createBlockEnterNode(block).also { addNewSimpleNode(it) }.also { levelCounter++ }
}
fun exitBlock(block: FirBlock): BlockExitNode {
levelCounter--
return createBlockExitNode(block).also { addNewSimpleNode(it) }
}
// ----------------------------------- Property -----------------------------------
fun enterProperty(property: FirProperty): PropertyEnterNode {
graphs.push(ControlFlowGraph("val ${property.name}"))
val enterNode = createPropertyEnterNode(property)
val exitNode = createPropertyExitNode(property)
topLevelVariableExitNodes.push(exitNode)
lexicalScopes.push(stackOf(enterNode))
graph.enterNode = enterNode
graph.exitNode = exitNode
levelCounter++
return enterNode
}
fun exitProperty(property: FirProperty): Pair<PropertyExitNode, ControlFlowGraph> {
val topLevelVariableExitNode = topLevelVariableExitNodes.pop()
addNewSimpleNode(topLevelVariableExitNode)
levelCounter--
lexicalScopes.pop()
return topLevelVariableExitNode to graphs.pop()
}
// ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall): TypeOperatorCallNode {
return createTypeOperatorCallNode(typeOperatorCall).also { addNewSimpleNode(it) }
}
fun exitOperatorCall(operatorCall: FirOperatorCall): OperatorCallNode {
return createOperatorCallNode(operatorCall).also { addNewSimpleNode(it) }
}
// ----------------------------------- Jump -----------------------------------
fun exitJump(jump: FirJump<*>): JumpNode {
val node = createJumpNode(jump)
val nextNode = when (jump) {
is FirReturnExpression -> functionExitNodes[jump.target.labeledElement]
is FirContinueExpression -> loopEnterNodes[jump.target.labeledElement]
is FirBreakExpression -> loopExitNodes[jump.target.labeledElement]
else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}")
}
addNodeWithJump(node, nextNode)
return node
}
// ----------------------------------- When -----------------------------------
fun enterWhenExpression(whenExpression: FirWhenExpression): WhenEnterNode {
val node = createWhenEnterNode(whenExpression)
addNewSimpleNode(node)
whenExitNodes.push(createWhenExitNode(whenExpression))
levelCounter++
return node
}
fun enterWhenBranchCondition(whenBranch: FirWhenBranch): WhenBranchConditionEnterNode {
return createWhenBranchConditionEnterNode(whenBranch).also { addNewSimpleNode(it) }.also { levelCounter++ }
}
fun exitWhenBranchCondition(whenBranch: FirWhenBranch): WhenBranchConditionExitNode {
levelCounter--
return createWhenBranchConditionExitNode(whenBranch).also {
addNewSimpleNode(it)
// put exit branch condition node twice so we can refer it after exit from when expression
lastNodes.push(it)
}.also { levelCounter++ }
}
fun exitWhenBranchResult(whenBranch: FirWhenBranch): WhenBranchResultExitNode {
levelCounter--
val node = createWhenBranchResultExitNode(whenBranch)
addEdge(lastNodes.pop(), node)
val whenExitNode = whenExitNodes.top()
addEdge(node, whenExitNode, propagateDeadness = false)
return node
}
fun exitWhenExpression(whenExpression: FirWhenExpression): WhenExitNode {
levelCounter--
// exit from last condition node still on stack
// we should remove it
require(lastNodes.pop() is WhenBranchConditionExitNode)
val whenExitNode = whenExitNodes.pop()
lastNodes.push(whenExitNode)
return whenExitNode
}
// ----------------------------------- While Loop -----------------------------------
fun enterWhileLoop(loop: FirLoop): LoopConditionEnterNode {
addNewSimpleNode(createLoopEnterNode(loop))
loopExitNodes.push(createLoopExitNode(loop))
levelCounter++
val node = createLoopConditionEnterNode(loop)
levelCounter++
addNewSimpleNode(node)
// put conditional node twice so we can refer it after exit from loop block
lastNodes.push(node)
loopEnterNodes.push(node)
return node
}
fun exitWhileLoopCondition(loop: FirLoop): LoopConditionExitNode {
levelCounter--
val conditionExitNode = createLoopConditionExitNode(loop)
addNewSimpleNode(conditionExitNode)
// TODO: here we can check that condition is always true
addEdge(conditionExitNode, loopExitNodes.top())
addNewSimpleNode(createLoopBlockEnterNode(loop))
levelCounter++
return conditionExitNode
}
fun exitWhileLoop(loop: FirLoop): Pair<LoopBlockExitNode, LoopExitNode> {
loopEnterNodes.pop()
levelCounter--
val loopBlockExitNode = createLoopBlockExitNode(loop)
addEdge(lastNodes.pop(), loopBlockExitNode)
if (lastNodes.isNotEmpty) {
val conditionEnterNode = lastNodes.pop()
require(conditionEnterNode is LoopConditionEnterNode) { loop.render() }
addEdge(loopBlockExitNode, conditionEnterNode, propagateDeadness = false)
}
val loopExitNode = loopExitNodes.pop()
lastNodes.push(loopExitNode)
levelCounter--
return loopBlockExitNode to loopExitNode
}
// ----------------------------------- Do while Loop -----------------------------------
fun enterDoWhileLoop(loop: FirLoop): LoopBlockEnterNode {
addNewSimpleNode(createLoopEnterNode(loop))
loopExitNodes.push(createLoopExitNode(loop))
levelCounter++
val blockEnterNode = createLoopBlockEnterNode(loop)
addNewSimpleNode(blockEnterNode)
// put block enter node twice so we can refer it after exit from loop condition
lastNodes.push(blockEnterNode)
loopEnterNodes.push(blockEnterNode)
levelCounter++
return blockEnterNode
}
fun enterDoWhileLoopCondition(loop: FirLoop): Pair<LoopBlockExitNode, LoopConditionEnterNode> {
levelCounter--
val blockExitNode = createLoopBlockExitNode(loop).also { addNewSimpleNode(it) }
val conditionEnterNode = createLoopConditionEnterNode(loop).also { addNewSimpleNode(it) }
levelCounter++
return blockExitNode to conditionEnterNode
}
fun exitDoWhileLoop(loop: FirLoop): LoopExitNode {
loopEnterNodes.pop()
levelCounter--
val conditionExitNode = createLoopConditionExitNode(loop)
// TODO: here we can check that condition is always false
addEdge(lastNodes.pop(), conditionExitNode)
val blockEnterNode = lastNodes.pop()
require(blockEnterNode is LoopBlockEnterNode)
addEdge(conditionExitNode, blockEnterNode, propagateDeadness = false)
val loopExit = loopExitNodes.pop()
addEdge(conditionExitNode, loopExit)
lastNodes.push(loopExit)
levelCounter--
return loopExit
}
// ----------------------------------- Boolean operators -----------------------------------
fun enterBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndEnterNode {
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND)
binaryAndExitNodes.push(createBinaryAndExitNode(binaryLogicExpression))
return createBinaryAndEnterNode(binaryLogicExpression).also { addNewSimpleNode(it) }.also { levelCounter++ }
}
fun exitLeftBinaryAndArgument(binaryLogicExpression: FirBinaryLogicExpression) {
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND)
addEdge(lastNode, binaryAndExitNodes.top())
}
fun exitBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndExitNode {
levelCounter--
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.AND)
return binaryAndExitNodes.pop().also { addNewSimpleNode(it) }
}
fun enterBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrEnterNode {
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR)
binaryOrExitNodes.push(createBinaryOrExitNode(binaryLogicExpression))
return createBinaryOrEnterNode(binaryLogicExpression).also {
addNewSimpleNode(it)
// put or enter node twice so we can refer it after exit from left argument
lastNodes.push(it)
}.also { levelCounter++ }
}
fun exitLeftBinaryOrArgument(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitLeftOperandNode {
levelCounter--
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR)
val previousNode = lastNodes.pop()
addEdge(previousNode, binaryOrExitNodes.top())
return createBinaryOrExitLeftOperandNode(binaryLogicExpression).also {
addEdge(previousNode, it)
lastNodes.push(it)
levelCounter++
}
}
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
assert(binaryLogicExpression.kind == FirBinaryLogicExpression.OperationKind.OR)
levelCounter--
return binaryOrExitNodes.pop().also {
addEdge(lastNodes.pop(), it)
addEdge(lastNodes.pop(), it)
lastNodes.push(it)
}
}
// ----------------------------------- Try-catch-finally -----------------------------------
private val finallyEnterNodes: Stack<FinallyBlockEnterNode> = stackOf()
fun enterTryExpression(tryExpression: FirTryExpression): TryMainBlockEnterNode {
catchNodeStorages.push(NodeStorage())
addNewSimpleNode(createTryExpressionEnterNode(tryExpression))
tryExitNodes.push(createTryExpressionExitNode(tryExpression))
levelCounter++
val tryNode = createTryMainBlockEnterNode(tryExpression)
addNewSimpleNode(tryNode)
addEdge(tryNode, exitNodes.top())
for (catch in tryExpression.catches) {
val catchNode = createCatchClauseEnterNode(catch)
catchNodeStorage.push(catchNode)
addEdge(tryNode, catchNode)
addEdge(catchNode, exitNodes.top())
}
levelCounter++
if (tryExpression.finallyBlock != null) {
val finallyEnterNode = createFinallyBlockEnterNode(tryExpression)
addEdge(tryNode, finallyEnterNode)
finallyEnterNodes.push(finallyEnterNode)
}
return tryNode
}
fun exitTryMainBlock(tryExpression: FirTryExpression): TryMainBlockExitNode {
levelCounter--
val node = createTryMainBlockExitNode(tryExpression)
addEdge(lastNodes.pop(), node)
addEdge(node, tryExitNodes.top())
return node
}
fun enterCatchClause(catch: FirCatch): CatchClauseEnterNode {
return catchNodeStorage[catch]!!.also { lastNodes.push(it) }.also { levelCounter++ }
}
fun exitCatchClause(catch: FirCatch): CatchClauseExitNode {
levelCounter--
return createCatchClauseExitNode(catch).also {
addEdge(lastNodes.pop(), it)
addEdge(it, tryExitNodes.top(), propagateDeadness = false)
}
}
fun enterFinallyBlock(tryExpression: FirTryExpression): FinallyBlockEnterNode {
val enterNode = finallyEnterNodes.pop()
lastNodes.push(enterNode)
return enterNode
}
fun exitFinallyBlock(tryExpression: FirTryExpression): FinallyBlockExitNode {
return createFinallyBlockExitNode(tryExpression).also {
addEdge(lastNodes.pop(), it)
addEdge(it, tryExitNodes.top())
}
}
fun exitTryExpression(tryExpression: FirTryExpression): TryExpressionExitNode {
levelCounter--
catchNodeStorages.pop()
val node = tryExitNodes.pop()
node.markAsDeadIfNecessary()
lastNodes.push(node)
return node
}
// ----------------------------------- Resolvable call -----------------------------------
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression): QualifiedAccessNode {
val returnsNothing = qualifiedAccessExpression.resultType.isNothing
val node = createQualifiedAccessNode(qualifiedAccessExpression, returnsNothing)
if (returnsNothing) {
addNodeThatReturnsNothing(node)
} else {
addNewSimpleNode(node)
}
return node
}
fun exitFunctionCall(functionCall: FirFunctionCall): FunctionCallNode {
val returnsNothing = functionCall.resultType.isNothing
val node = createFunctionCallNode(functionCall, returnsNothing)
if (returnsNothing) {
addNodeThatReturnsNothing(node)
} else {
addNewSimpleNode(node)
}
return node
}
fun exitConstExpresion(constExpression: FirConstExpression<*>): ConstExpressionNode {
return createConstExpressionNode(constExpression).also { addNewSimpleNode(it) }
}
fun exitVariableDeclaration(variable: FirVariable<*>): VariableDeclarationNode {
return createVariableDeclarationNode(variable).also { addNewSimpleNode(it) }
}
fun exitVariableAssignment(assignment: FirVariableAssignment): VariableAssignmentNode {
return createVariableAssignmentNode(assignment).also { addNewSimpleNode(it) }
}
fun exitThrowExceptionNode(throwExpression: FirThrowExpression): ThrowExceptionNode {
return createThrowExceptionNode(throwExpression).also { addNodeThatReturnsNothing(it) }
}
// ----------------------------------- Annotations -----------------------------------
fun enterAnnotationCall(annotationCall: FirAnnotationCall): AnnotationEnterNode {
return createAnnotationEnterNode(annotationCall).also {
if (graphs.size > 1) {
addNewSimpleNode(it)
} else {
lastNodes.push(it)
}
}
}
fun exitAnnotationCall(annotationCall: FirAnnotationCall): AnnotationExitNode {
return createAnnotationExitNode(annotationCall).also {
if (graphs.size > 1) {
addNewSimpleNode(it)
} else {
lastNodes.pop()
}
}
}
// ----------------------------------- Block -----------------------------------
fun enterInitBlock(initBlock: FirAnonymousInitializer): InitBlockEnterNode {
val enterNode = createInitBlockEnterNode(initBlock).also {
lexicalScopes.push(stackOf(it))
}
val exitNode = createInitBlockExitNode(initBlock)
initBlockExitNodes.push(exitNode)
levelCounter++
return enterNode
}
fun exitInitBlock(initBlock: FirAnonymousInitializer): InitBlockExitNode {
levelCounter--
return initBlockExitNodes.pop().also {
addNewSimpleNode(it)
lexicalScopes.pop()
}
}
// -------------------------------------------------------------------------------------------------------------------------
private fun CFGNode<*>.markAsDeadIfNecessary() {
isDead = previousNodes.all { it.isDead }
}
private fun addNodeThatReturnsNothing(node: CFGNode<*>) {
/*
* `return` is temporary solution that is needed for init block
* it will be replaced after correct implementation of CFG for class initialization
*/
val exitNode: CFGNode<*> = exitNodes.top()
addNodeWithJump(node, exitNode)
}
private fun addNodeWithJump(node: CFGNode<*>, targetNode: CFGNode<*>?) {
addEdge(lastNodes.pop(), node)
if (targetNode != null) {
addEdge(node, targetNode)
}
val stub = createStubNode()
addEdge(node, stub)
lastNodes.push(stub)
}
private fun addNewSimpleNode(newNode: CFGNode<*>): CFGNode<*> {
val oldNode = lastNodes.pop()
addEdge(oldNode, newNode)
lastNodes.push(newNode)
return oldNode
}
private fun addEdge(from: CFGNode<*>, to: CFGNode<*>, propagateDeadness: Boolean = true) {
if (propagateDeadness && from.isDead) {
to.isDead = true
}
from.followingNodes += to
to.previousNodes += from
}
}
@@ -0,0 +1,156 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.*
abstract class ControlFlowGraphNodeBuilder {
protected abstract val graph: ControlFlowGraph
protected abstract var levelCounter: Int
protected fun createStubNode(): StubNode = StubNode(
graph,
levelCounter
)
protected fun createLoopExitNode(fir: FirLoop): LoopExitNode = LoopExitNode(graph, fir, levelCounter)
protected fun createLoopEnterNode(fir: FirLoop): LoopEnterNode = LoopEnterNode(graph, fir, levelCounter)
protected fun createInitBlockExitNode(fir: FirAnonymousInitializer): InitBlockExitNode =
InitBlockExitNode(graph, fir, levelCounter)
protected fun createInitBlockEnterNode(fir: FirAnonymousInitializer): InitBlockEnterNode =
InitBlockEnterNode(graph, fir, levelCounter)
protected fun createTypeOperatorCallNode(fir: FirTypeOperatorCall): TypeOperatorCallNode =
TypeOperatorCallNode(graph, fir, levelCounter)
protected fun createOperatorCallNode(fir: FirOperatorCall): OperatorCallNode =
OperatorCallNode(graph, fir, levelCounter)
protected fun createWhenBranchConditionExitNode(fir: FirWhenBranch): WhenBranchConditionExitNode =
WhenBranchConditionExitNode(graph, fir, levelCounter)
protected fun createJumpNode(fir: FirJump<*>): JumpNode =
JumpNode(graph, fir, levelCounter)
protected fun createQualifiedAccessNode(
fir: FirQualifiedAccessExpression,
returnsNothing: Boolean
): QualifiedAccessNode = QualifiedAccessNode(graph, fir, returnsNothing, levelCounter)
protected fun createBlockEnterNode(fir: FirBlock): BlockEnterNode = BlockEnterNode(graph, fir, levelCounter)
protected fun createBlockExitNode(fir: FirBlock): BlockExitNode = BlockExitNode(graph, fir, levelCounter)
protected fun createPropertyExitNode(fir: FirProperty): PropertyExitNode = PropertyExitNode(graph, fir, levelCounter)
protected fun createPropertyEnterNode(fir: FirProperty): PropertyEnterNode = PropertyEnterNode(graph, fir, levelCounter)
protected fun createFunctionEnterNode(fir: FirFunction<*>): FunctionEnterNode =
FunctionEnterNode(graph, fir, levelCounter).also {
graph.enterNode = it
}
protected fun createFunctionExitNode(fir: FirFunction<*>): FunctionExitNode = FunctionExitNode(graph, fir, levelCounter).also {
graph.exitNode = it
}
protected fun createBinaryOrEnterNode(fir: FirBinaryLogicExpression): BinaryOrEnterNode =
BinaryOrEnterNode(graph, fir, levelCounter)
protected fun createBinaryOrExitLeftOperandNode(fir: FirBinaryLogicExpression): BinaryOrExitLeftOperandNode =
BinaryOrExitLeftOperandNode(graph, fir, levelCounter)
protected fun createBinaryOrExitNode(fir: FirBinaryLogicExpression): BinaryOrExitNode =
BinaryOrExitNode(graph, fir, levelCounter)
protected fun createBinaryAndExitNode(fir: FirBinaryLogicExpression): BinaryAndExitNode =
BinaryAndExitNode(graph, fir, levelCounter)
protected fun createBinaryAndEnterNode(fir: FirBinaryLogicExpression): BinaryAndEnterNode =
BinaryAndEnterNode(graph, fir, levelCounter)
protected fun createWhenBranchConditionEnterNode(fir: FirWhenBranch): WhenBranchConditionEnterNode =
WhenBranchConditionEnterNode(graph, fir, levelCounter)
protected fun createWhenEnterNode(fir: FirWhenExpression): WhenEnterNode =
WhenEnterNode(graph, fir, levelCounter)
protected fun createWhenExitNode(fir: FirWhenExpression): WhenExitNode =
WhenExitNode(graph, fir, levelCounter)
protected fun createWhenBranchResultExitNode(fir: FirWhenBranch): WhenBranchResultExitNode =
WhenBranchResultExitNode(graph, fir, levelCounter)
protected fun createLoopConditionExitNode(fir: FirLoop): LoopConditionExitNode =
LoopConditionExitNode(graph, fir, levelCounter)
protected fun createLoopConditionEnterNode(fir: FirLoop): LoopConditionEnterNode =
LoopConditionEnterNode(graph, fir, levelCounter)
protected fun createLoopBlockEnterNode(fir: FirLoop): LoopBlockEnterNode =
LoopBlockEnterNode(graph, fir, levelCounter)
protected fun createLoopBlockExitNode(fir: FirLoop): LoopBlockExitNode =
LoopBlockExitNode(graph, fir, levelCounter)
protected fun createFunctionCallNode(fir: FirFunctionCall, returnsNothing: Boolean): FunctionCallNode =
FunctionCallNode(graph, fir, returnsNothing, levelCounter)
protected fun createVariableAssignmentNode(fir: FirVariableAssignment): VariableAssignmentNode =
VariableAssignmentNode(graph, fir, levelCounter)
protected fun createAnnotationExitNode(fir: FirAnnotationCall): AnnotationExitNode =
AnnotationExitNode(graph, fir, levelCounter)
protected fun createAnnotationEnterNode(fir: FirAnnotationCall): AnnotationEnterNode =
AnnotationEnterNode(graph, fir, levelCounter)
protected fun createVariableDeclarationNode(fir: FirVariable<*>): VariableDeclarationNode =
VariableDeclarationNode(graph, fir, levelCounter)
protected fun createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode =
ConstExpressionNode(graph, fir, levelCounter)
protected fun createThrowExceptionNode(fir: FirThrowExpression): ThrowExceptionNode =
ThrowExceptionNode(graph, fir, levelCounter)
protected fun createFinallyProxyExitNode(fir: FirTryExpression): FinallyProxyExitNode =
FinallyProxyExitNode(graph, fir, levelCounter)
protected fun createFinallyProxyEnterNode(fir: FirTryExpression): FinallyProxyEnterNode =
FinallyProxyEnterNode(graph, fir, levelCounter)
protected fun createFinallyBlockExitNode(fir: FirTryExpression): FinallyBlockExitNode =
FinallyBlockExitNode(graph, fir, levelCounter)
protected fun createFinallyBlockEnterNode(fir: FirTryExpression): FinallyBlockEnterNode =
FinallyBlockEnterNode(graph, fir, levelCounter)
protected fun createCatchClauseExitNode(fir: FirCatch): CatchClauseExitNode =
CatchClauseExitNode(graph, fir, levelCounter)
protected fun createTryMainBlockExitNode(fir: FirTryExpression): TryMainBlockExitNode =
TryMainBlockExitNode(graph, fir, levelCounter)
protected fun createTryMainBlockEnterNode(fir: FirTryExpression): TryMainBlockEnterNode =
TryMainBlockEnterNode(graph, fir, levelCounter)
protected fun createCatchClauseEnterNode(fir: FirCatch): CatchClauseEnterNode =
CatchClauseEnterNode(graph, fir, levelCounter)
protected fun createTryExpressionEnterNode(fir: FirTryExpression): TryExpressionEnterNode =
TryExpressionEnterNode(graph, fir, levelCounter)
protected fun createTryExpressionExitNode(fir: FirTryExpression): TryExpressionExitNode =
TryExpressionExitNode(graph, fir, levelCounter)
}
@@ -0,0 +1,163 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirNamedFunction
import org.jetbrains.kotlin.fir.declarations.impl.FirAbstractPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.impl.FirErrorFunction
import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop
import org.jetbrains.kotlin.fir.expressions.FirLoop
import org.jetbrains.kotlin.fir.expressions.FirWhileLoop
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.utils.DFS
private const val INDENT = " "
private const val DEAD = "[DEAD]"
fun ControlFlowGraph.renderToStringBuilder(builder: StringBuilder) {
val sortedNodes: List<CFGNode<*>> = DFS.topologicalOrder(
nodes
) {
val result = if (it !is WhenBranchConditionExitNode || it.followingNodes.size < 2) {
it.followingNodes
} else {
it.followingNodes.sortedBy { node -> if (node is BlockEnterNode) 1 else 0 }
}
result
}
val indices = sortedNodes.mapIndexed { i, node -> node to i }.toMap()
val notVisited = sortedNodes.toMutableSet()
val maxLineNumberSize = sortedNodes.size.toString().length
fun List<CFGNode<*>>.renderEdges(nodeIsDead: Boolean): String = map {
indices.getValue(it) to it.isDead
}.sortedBy { it.first }.joinToString(", ") { (index, isDead) ->
index.toString() + if (isDead && !nodeIsDead) DEAD else ""
}
fun StringBuilder.renderNode(node: CFGNode<*>, index: Int) {
append(index.toString().padStart(maxLineNumberSize))
append(": ")
append(INDENT.repeat(node.level))
append(node.render())
append(" -> ")
append(node.followingNodes.renderEdges(node.isDead))
if (node.previousNodes.isNotEmpty()) {
append(" | <- ")
append(node.previousNodes.renderEdges(node.isDead))
}
appendln()
}
with(builder) {
sortedNodes.forEachIndexed { i, node ->
notVisited.remove(node)
renderNode(node, i)
}
if (notVisited.isNotEmpty()) {
appendln("Not visited nodes:")
notVisited.forEach { node ->
renderNode(node, indices.getValue(node))
}
}
appendln()
}
}
fun ControlFlowGraph.render(): String = buildString { renderToStringBuilder(this) }
fun CFGNode<*>.render(): String =
buildString {
append(
when (this@render) {
is FunctionEnterNode -> "Enter function \"${fir.name()}\""
is FunctionExitNode -> "Exit function \"${fir.name()}\""
is BlockEnterNode -> "Enter block"
is BlockExitNode -> "Exit block"
is WhenEnterNode -> "Enter when"
is WhenBranchConditionEnterNode -> "Enter when branch condition ${if (fir.condition is FirElseIfTrueCondition) "\"else\"" else ""}"
is WhenBranchConditionExitNode -> "Exit when branch condition"
is WhenBranchResultExitNode -> "Exit when branch result"
is WhenExitNode -> "Exit when"
is LoopEnterNode -> "Enter ${fir.type()} loop"
is LoopBlockEnterNode -> "Enter loop block"
is LoopBlockExitNode -> "Exit loop block"
is LoopConditionEnterNode -> "Enter loop condition"
is LoopConditionExitNode -> "Exit loop condition"
is LoopExitNode -> "Exit ${fir.type()}loop"
is QualifiedAccessNode -> "Access variable ${fir.calleeReference.render()}"
is OperatorCallNode -> "Operator ${fir.operation.operator}"
is TypeOperatorCallNode -> "Type operator: \"${fir.psi?.text?.toString() ?: fir.render()}\""
is JumpNode -> "Jump: ${fir.render()}"
is StubNode -> "Stub"
is ConstExpressionNode -> "Const: ${fir.render()}"
is VariableDeclarationNode ->
"Variable declaration: ${buildString { FirRenderer(this).visitCallableDeclaration(fir)} }"
is VariableAssignmentNode -> "Assignmenet: ${fir.lValue.render()}"
is FunctionCallNode -> "Function call: ${fir.render()}"
is ThrowExceptionNode -> "Throw: ${fir.render()}"
is TryExpressionEnterNode -> "Try expression enter"
is TryMainBlockEnterNode -> "Try main block enter"
is TryMainBlockExitNode -> "Try main block exit"
is CatchClauseEnterNode -> "Catch enter"
is CatchClauseExitNode -> "Catch exit"
is FinallyBlockEnterNode -> "Enter finally"
is FinallyBlockExitNode -> "Exit finally"
is FinallyProxyEnterNode -> TODO()
is FinallyProxyExitNode -> TODO()
is TryExpressionExitNode -> "Try expression exit"
is BinaryAndEnterNode -> "Enter &&"
is BinaryAndExitNode -> "Exit &&"
is BinaryOrEnterNode -> "Enter ||"
is BinaryOrExitLeftOperandNode -> "Exit left part of ||"
is BinaryOrExitNode -> "Exit ||"
is PropertyEnterNode -> "Enter property"
is PropertyExitNode -> "Exit property"
is InitBlockEnterNode -> "Enter init block"
is InitBlockExitNode -> "Exit init block"
is AnnotationEnterNode -> "Enter annotation"
is AnnotationExitNode -> "Exit annotation"
else -> TODO(this@render.toString())
}
)
if (isDead) {
append(DEAD)
}
}
private fun FirFunction<*>.name(): String = when (this) {
is FirNamedFunction -> name.asString()
is FirAnonymousFunction -> "anonymousFunction"
is FirConstructor -> name.asString()
is FirAbstractPropertyAccessor -> if (isGetter) "getter" else "setter"
is FirErrorFunction -> "errorFunction"
else -> TODO(toString())
}
private fun FirLoop.type(): String = when (this) {
is FirWhileLoop -> "while"
is FirDoWhileLoop -> "do-while"
else -> throw IllegalArgumentException()
}