Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphBuilder.kt Maven / Gradle / Ivy
/*
* 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.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirControlFlowGraphReference
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.dfa.*
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.types.isNothing
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import kotlin.random.Random
@RequiresOptIn
private annotation class CfgBuilderInternals
class ControlFlowGraphBuilder {
@CfgBuilderInternals
private val graphs: Stack = stackOf(ControlFlowGraph(null, "", ControlFlowGraph.Kind.TopLevel))
@get:OptIn(CfgBuilderInternals::class)
val currentGraph: ControlFlowGraph
get() = graphs.top()
private val lastNodes: Stack> = stackOf()
val lastNode: CFGNode<*>
get() = lastNodes.top()
var levelCounter: Int = 0
private set
private val modes: Stack = stackOf(Mode.TopLevel)
private val mode: Mode get() = modes.top()
/*
* TODO: it's temporary hack for anonymous functions resolved twice in delegate expressions
* Example: val x: Boolean by lazy { true }
*
* Note that this hack breaks passing data flow from inplace lambdas inside lambdas of delegates:
* val x: Boolean by lazy {
* val b: Any = ...
* run {
* require(b is Boolean)
* }
* b // there will be no smartcast, but it should be
* }
*/
private val shouldPassFlowFromInplaceLambda: Stack = stackOf(true)
private enum class Mode {
Function, TopLevel, Body, ClassInitializer, PropertyInitializer
}
// ----------------------------------- Node caches -----------------------------------
private val exitTargetsForReturn: SymbolBasedNodeStorage, FunctionExitNode> = SymbolBasedNodeStorage()
private val exitTargetsForTry: Stack> = stackOf()
private val exitsOfAnonymousFunctions: MutableMap, FunctionExitNode> = mutableMapOf()
private val enterToLocalClassesMembers: MutableMap, CFGNode<*>?> = mutableMapOf()
private val postponedLambdas: MutableSet> = mutableSetOf()
private val entersToPostponedAnonymousFunctions: MutableMap, PostponedLambdaEnterNode> = mutableMapOf()
private val exitsFromPostponedAnonymousFunctions: MutableMap, PostponedLambdaExitNode> = mutableMapOf()
private val parentGraphForAnonymousFunctions: MutableMap, ControlFlowGraph> = mutableMapOf()
private val loopEnterNodes: NodeStorage> = NodeStorage()
private val loopExitNodes: NodeStorage = NodeStorage()
private val exitsFromCompletedPostponedAnonymousFunctions: MutableList = mutableListOf()
private val whenExitNodes: NodeStorage = NodeStorage()
private val binaryAndExitNodes: Stack = stackOf()
private val binaryOrExitNodes: Stack = stackOf()
private val tryExitNodes: NodeStorage = NodeStorage()
private val tryMainExitNodes: NodeStorage = NodeStorage()
private val catchNodeStorages: Stack> = stackOf()
private val catchNodeStorage: NodeStorage get() = catchNodeStorages.top()
private val finallyEnterNodes: Stack = stackOf()
private val initBlockExitNodes: Stack = stackOf()
private val exitSafeCallNodes: Stack = stackOf()
private val exitElvisExpressionNodes: Stack = stackOf()
/*
* ignoredFunctionCalls is needed for resolve of += operator:
* we have two different calls for resolve, but we left only one of them,
* so we twice call `enterCall` and twice increase `levelCounter`, but
* `exitFunctionCall` we call only once.
*
* So workflow looks like that:
* Calls:
* - a.plus(b) // (1)
* - a.plusAssign(b) // (2)
*
* enterCall(a.plus(b)), increase counter
* exitIgnoredCall(a.plus(b)) // decrease counter
* enterCall(a.plusAssign(b)) // increase counter
* exitIgnoredCall(a.plusAssign(b)) // decrease counter
* exitFunctionCall(a.plus(b) | a.plusAssign(b)) // don't touch counter
*/
private val ignoredFunctionCalls: MutableSet = mutableSetOf()
// ----------------------------------- API for node builders -----------------------------------
private var idCounter: Int = Random.nextInt()
fun createId(): Int = idCounter++
// ----------------------------------- Public API -----------------------------------
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): Collection {
fun FirElement.extractArgument(): FirElement = when {
this is FirReturnExpression && target.labeledElement.symbol == function.symbol -> result.extractArgument()
else -> this
}
fun CFGNode<*>.extractArgument(): FirElement? = when (this) {
is FunctionEnterNode, is TryMainBlockEnterNode, is CatchClauseEnterNode -> null
is ExitSafeCallNode -> lastPreviousNode.extractArgument()
is StubNode, is BlockExitNode -> firstPreviousNode.extractArgument()
else -> fir.extractArgument()
}
val exitNode = function.controlFlowGraphReference?.controlFlowGraph?.exitNode
?: exitsOfAnonymousFunctions.getValue(function.symbol)
return exitNode.previousNodes.mapNotNullTo(mutableSetOf()) {
it.extractArgument() as FirStatement?
}
}
@OptIn(CfgBuilderInternals::class)
fun isTopLevel(): Boolean = graphs.size == 1
// ----------------------------------- Utils -----------------------------------
@OptIn(CfgBuilderInternals::class)
private fun pushGraph(graph: ControlFlowGraph, mode: Mode) {
graphs.push(graph)
modes.push(mode)
levelCounter++
}
@OptIn(CfgBuilderInternals::class)
private fun popGraph(): ControlFlowGraph {
levelCounter--
modes.pop()
return graphs.pop().also { it.complete() }
}
// ----------------------------------- Regular function -----------------------------------
fun enterFunction(function: FirFunction<*>): Triple?> {
require(function !is FirAnonymousFunction)
val name = when (function) {
is FirSimpleFunction -> function.name.asString()
is FirPropertyAccessor -> if (function.isGetter) "" else ""
is FirConstructor -> ""
else -> throw IllegalArgumentException("Unknown function: ${function.render()}")
}
val graph = ControlFlowGraph(function, name, ControlFlowGraph.Kind.Function)
// function is local
val localFunctionNode = runIf(mode == Mode.Body) {
assert(currentGraph.kind.withBody)
currentGraph.addSubGraph(graph)
createLocalFunctionDeclarationNode(function).also {
addNewSimpleNode(it)
}
}
pushGraph(
graph = graph,
mode = Mode.Body
)
val previousNode = enterToLocalClassesMembers[function.symbol]
?: (function as? FirSimpleFunction)?.takeIf { it.isLocal }?.let { lastNode }
val enterNode = createFunctionEnterNode(function).also {
lastNodes.push(it)
}
if (previousNode != null) {
if (localFunctionNode == previousNode) {
addEdge(localFunctionNode, enterNode, preferredKind = EdgeKind.Forward)
} else {
addEdge(previousNode, enterNode, preferredKind = EdgeKind.DfgForward)
}
}
createFunctionExitNode(function).also {
exitTargetsForReturn.push(it)
exitTargetsForTry.push(it)
}
return Triple(enterNode, localFunctionNode, previousNode)
}
fun exitFunction(function: FirFunction<*>): Pair {
require(function !is FirAnonymousFunction)
val exitNode = exitTargetsForReturn.pop()
popAndAddEdge(exitNode)
val graph = popGraph()
assert(exitNode == graph.exitNode)
exitTargetsForTry.pop().also {
assert(it == graph.exitNode)
}
graph.exitNode.updateDeadStatus()
return graph.exitNode as FunctionExitNode to graph
}
// ----------------------------------- Anonymous function -----------------------------------
fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction): Pair {
val enterNode = createPostponedLambdaEnterNode(anonymousFunction)
val exitNode = createPostponedLambdaExitNode(anonymousFunction)
val symbol = anonymousFunction.symbol
postponedLambdas += symbol
entersToPostponedAnonymousFunctions[symbol] = enterNode
exitsFromPostponedAnonymousFunctions[symbol] = exitNode
parentGraphForAnonymousFunctions[symbol] = currentGraph
popAndAddEdge(enterNode, preferredKind = EdgeKind.Forward)
addEdge(enterNode, exitNode, preferredKind = EdgeKind.DfgForward)
lastNodes.push(exitNode)
return enterNode to exitNode
}
fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction): Pair {
val invocationKind = anonymousFunction.invocationKind
var previousNodeIsNew = false
val symbol = anonymousFunction.symbol
val previousNode = entersToPostponedAnonymousFunctions[symbol]
?: createPostponedLambdaEnterNode(anonymousFunction).also {
addNewSimpleNode(it)
entersToPostponedAnonymousFunctions[symbol] = it
previousNodeIsNew = true
}
if (previousNodeIsNew) {
assert(symbol !in exitsFromPostponedAnonymousFunctions)
val exitFromLambda = createPostponedLambdaExitNode(anonymousFunction).also {
exitsFromPostponedAnonymousFunctions[symbol] = it
}
addEdge(previousNode, exitFromLambda)
}
pushGraph(ControlFlowGraph(anonymousFunction, "", ControlFlowGraph.Kind.AnonymousFunction), Mode.Function)
val enterNode = createFunctionEnterNode(anonymousFunction).also {
if (previousNodeIsNew) {
addNewSimpleNode(it)
} else {
addEdge(previousNode, it)
lastNodes.push(it)
}
}
val exitNode = createFunctionExitNode(anonymousFunction).also {
exitsOfAnonymousFunctions[symbol] = it
exitTargetsForReturn.push(it)
if (!invocationKind.isInPlace) {
exitTargetsForTry.push(it)
}
}
if (invocationKind.hasTowardEdge) {
addEdge(enterNode, exitNode)
}
if (invocationKind.hasBackEdge) {
addBackEdge(exitNode, enterNode)
}
return if (previousNodeIsNew) {
previousNode to enterNode
} else {
null to enterNode
}
}
private val EventOccurrencesRange?.hasTowardEdge: Boolean
get() = when (this) {
EventOccurrencesRange.AT_MOST_ONCE, EventOccurrencesRange.UNKNOWN -> true
else -> false
}
private val EventOccurrencesRange?.hasBackEdge: Boolean
get() = when (this) {
EventOccurrencesRange.AT_LEAST_ONCE, EventOccurrencesRange.UNKNOWN -> true
else -> false
}
private val EventOccurrencesRange?.isInPlace: Boolean
get() = this != null
fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple {
val symbol = anonymousFunction.symbol
val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also {
require(it == exitTargetsForReturn.pop())
if (!anonymousFunction.invocationKind.isInPlace) {
require(it == exitTargetsForTry.pop())
}
}
popAndAddEdge(exitNode)
exitNode.updateDeadStatus()
val graph = popGraph().also { graph ->
assert(graph.declaration == anonymousFunction)
assert(graph.exitNode == exitNode)
exitsFromCompletedPostponedAnonymousFunctions.removeAll { it.owner == graph }
}
val postponedEnterNode = entersToPostponedAnonymousFunctions.remove(symbol)!!
val postponedExitNode = exitsFromPostponedAnonymousFunctions.remove(symbol)!!
val lambdaIsPostponedFromCall = postponedLambdas.remove(symbol)
if (!lambdaIsPostponedFromCall) {
lastNodes.push(postponedExitNode)
}
val invocationKind = anonymousFunction.invocationKind
if (invocationKind != null) {
addEdge(exitNode, postponedExitNode, preferredKind = EdgeKind.CfgForward)
} else {
val kind = if (postponedExitNode.isDead) EdgeKind.DeadForward else EdgeKind.CfgForward
CFGNode.addJustKindEdge(postponedEnterNode, postponedExitNode, kind, propagateDeadness = true)
}
if (invocationKind == EventOccurrencesRange.EXACTLY_ONCE && shouldPassFlowFromInplaceLambda.top()) {
exitsFromCompletedPostponedAnonymousFunctions += postponedExitNode
}
val containingGraph = parentGraphForAnonymousFunctions.remove(symbol) ?: currentGraph
containingGraph.addSubGraph(graph)
return if (lambdaIsPostponedFromCall) {
Triple(exitNode, null, graph)
} else {
Triple(exitNode, postponedExitNode, graph)
}
}
// ----------------------------------- Classes -----------------------------------
fun enterClass() {
pushGraph(
ControlFlowGraph(null, "STUB_CLASS_GRAPH", ControlFlowGraph.Kind.Stub),
mode = Mode.ClassInitializer
)
}
fun exitClass() {
popGraph()
}
fun exitClass(klass: FirClass<*>): ControlFlowGraph {
exitClass()
val name = when (klass) {
is FirAnonymousObject -> ""
is FirRegularClass -> klass.name.asString()
else -> throw IllegalArgumentException("Unknown class kind: ${klass::class}")
}
val classGraph = ControlFlowGraph(klass, name, ControlFlowGraph.Kind.ClassInitializer)
pushGraph(classGraph, Mode.ClassInitializer)
val exitNode = createClassExitNode(klass)
var node: CFGNode<*> = createClassEnterNode(klass)
var prevInitPartNode: CFGNode<*>? = null
for (declaration in klass.declarations) {
val graph = when (declaration) {
is FirProperty -> declaration.controlFlowGraphReference?.controlFlowGraph
is FirAnonymousInitializer -> declaration.controlFlowGraphReference?.controlFlowGraph
else -> null
} ?: continue
createPartOfClassInitializationNode(declaration as FirControlFlowGraphOwner).also {
addEdge(node, it, preferredKind = EdgeKind.CfgForward)
addEdge(it, graph.enterNode, preferredKind = EdgeKind.CfgForward)
node = graph.exitNode
if (prevInitPartNode != null) addEdge(prevInitPartNode!!, it, preferredKind = EdgeKind.DeadForward)
it.updateDeadStatus()
prevInitPartNode = it
}
}
addEdge(node, exitNode, preferredKind = EdgeKind.CfgForward)
if (prevInitPartNode != null) addEdge(prevInitPartNode!!, exitNode, preferredKind = EdgeKind.DeadForward)
exitNode.updateDeadStatus()
return popGraph()
}
fun prepareForLocalClassMembers(members: Collection>) {
members.forEachMember {
enterToLocalClassesMembers[it.symbol] = lastNodes.topOrNull()
}
}
fun cleanAfterForLocalClassMembers(members: Collection>) {
members.forEachMember {
enterToLocalClassesMembers.remove(it.symbol)
}
}
fun exitLocalClass(klass: FirRegularClass): Pair {
val graph = exitClass(klass).also {
currentGraph.addSubGraph(it)
}
val node = createLocalClassExitNode(klass).also {
addNewSimpleNodeIfPossible(it)
}
visitLocalClassFunctions(klass, node)
addEdge(node, graph.enterNode, preferredKind = EdgeKind.CfgForward)
return node to graph
}
fun exitAnonymousObject(anonymousObject: FirAnonymousObject): Pair {
val graph = exitClass(anonymousObject).also {
currentGraph.addSubGraph(it)
}
val node = createAnonymousObjectExitNode(anonymousObject).also {
// TODO: looks like there was some problem with enum initializers
if (lastNodes.isNotEmpty) {
addNewSimpleNode(it)
}
}
visitLocalClassFunctions(anonymousObject, node)
addEdge(node, graph.enterNode, preferredKind = EdgeKind.CfgForward)
return node to graph
}
fun visitLocalClassFunctions(klass: FirClass<*>, node: CFGNodeWithCfgOwner<*>) {
klass.declarations.filterIsInstance>().forEach { function ->
val functionGraph = function.controlFlowGraphReference?.controlFlowGraph
if (functionGraph != null && functionGraph.owner == null) {
addEdge(node, functionGraph.enterNode, preferredKind = EdgeKind.CfgForward)
node.addSubGraph(functionGraph)
}
}
}
// ----------------------------------- Value parameters (and it's defaults) -----------------------------------
fun enterValueParameter(valueParameter: FirValueParameter): EnterDefaultArgumentsNode? {
if (valueParameter.defaultValue == null) return null
val graph = ControlFlowGraph(valueParameter, "default value of ${valueParameter.name}", ControlFlowGraph.Kind.DefaultArgument)
currentGraph.addSubGraph(graph)
pushGraph(graph, Mode.Body)
createExitDefaultArgumentsNode(valueParameter).also {
exitTargetsForTry.push(it)
}
return createEnterDefaultArgumentsNode(valueParameter).also {
addEdge(lastNode, it)
lastNodes.push(it)
}
}
fun exitValueParameter(valueParameter: FirValueParameter): Pair? {
if (valueParameter.defaultValue == null) return null
val exitNode = exitTargetsForTry.pop() as ExitDefaultArgumentsNode
popAndAddEdge(exitNode)
val graph = popGraph()
require(exitNode == graph.exitNode)
return exitNode to graph
}
// ----------------------------------- Block -----------------------------------
fun enterBlock(block: FirBlock): BlockEnterNode {
return createBlockEnterNode(block).also {
addNewSimpleNode(it)
}
}
fun exitBlock(block: FirBlock): CFGNode<*> {
return createBlockExitNode(block).also {
addNewSimpleNode(it)
}
}
// ----------------------------------- Property -----------------------------------
fun enterProperty(property: FirProperty): PropertyInitializerEnterNode? {
if (property.initializer == null && property.delegate == null) return null
val graph = ControlFlowGraph(property, "val ${property.name}", ControlFlowGraph.Kind.PropertyInitializer)
pushGraph(graph, Mode.PropertyInitializer)
val enterNode = createPropertyInitializerEnterNode(property)
val exitNode = createPropertyInitializerExitNode(property)
exitTargetsForTry.push(exitNode)
enterToLocalClassesMembers[property.symbol]?.let {
addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward)
}
lastNodes.push(enterNode)
return enterNode
}
fun exitProperty(property: FirProperty): Pair? {
if (property.initializer == null && property.delegate == null) return null
val exitNode = exitTargetsForTry.pop() as PropertyInitializerExitNode
popAndAddEdge(exitNode)
val graph = popGraph()
assert(exitNode == graph.exitNode)
return exitNode to graph
}
// ----------------------------------- Delegate -----------------------------------
fun enterDelegateExpression() {
shouldPassFlowFromInplaceLambda.push(false)
}
fun exitDelegateExpression() {
shouldPassFlowFromInplaceLambda.pop()
}
// ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall): TypeOperatorCallNode {
return createTypeOperatorCallNode(typeOperatorCall).also { addNewSimpleNode(it) }
}
fun exitComparisonExpression(comparisonExpression: FirComparisonExpression): ComparisonExpressionNode {
return createComparisonExpressionNode(comparisonExpression).also { addNewSimpleNode(it) }
}
fun exitEqualityOperatorCall(equalityOperatorCall: FirEqualityOperatorCall): EqualityOperatorCallNode {
return createEqualityOperatorCallNode(equalityOperatorCall).also { addNewSimpleNode(it) }
}
// ----------------------------------- Jump -----------------------------------
fun exitJump(jump: FirJump<*>): JumpNode {
val node = createJumpNode(jump)
// TODO: if within `try` with `finally`, don't go to the target directly.
val nextNode = when (jump) {
is FirReturnExpression -> exitTargetsForReturn[jump.target.labeledElement.symbol]
is FirContinueExpression -> loopEnterNodes[jump.target.labeledElement]
is FirBreakExpression -> loopExitNodes[jump.target.labeledElement]
else -> throw IllegalArgumentException("Unknown jump type: ${jump.render()}")
}
addNodeWithJump(node, nextNode, isBack = jump is FirContinueExpression)
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): Pair {
levelCounter--
val conditionExitNode = createWhenBranchConditionExitNode(whenBranch).also {
addNewSimpleNode(it)
}.also { levelCounter++ }
val branchEnterNode = createWhenBranchResultEnterNode(whenBranch).also {
lastNodes.push(it)
addEdge(conditionExitNode, it)
}
return conditionExitNode to branchEnterNode
}
fun exitWhenBranchResult(whenBranch: FirWhenBranch): WhenBranchResultExitNode {
levelCounter--
val node = createWhenBranchResultExitNode(whenBranch)
popAndAddEdge(node)
val whenExitNode = whenExitNodes.top()
addEdge(node, whenExitNode, propagateDeadness = false)
return node
}
fun exitWhenExpression(whenExpression: FirWhenExpression): Pair {
val whenExitNode = whenExitNodes.pop()
// exit from last condition node still on stack
// we should remove it
val lastWhenConditionExit = lastNodes.pop()
val syntheticElseBranchNode = if (!whenExpression.isExhaustive) {
createWhenSyntheticElseBranchNode(whenExpression).apply {
addEdge(lastWhenConditionExit, this)
addEdge(this, whenExitNode)
}
} else null
whenExitNode.updateDeadStatus()
lastNodes.push(whenExitNode)
dropPostponedLambdasForNonDeterministicCalls()
levelCounter--
return whenExitNode to syntheticElseBranchNode
}
// ----------------------------------- While Loop -----------------------------------
fun enterWhileLoop(loop: FirLoop): Pair {
val loopEnterNode = createLoopEnterNode(loop).also {
addNewSimpleNode(it)
loopEnterNodes.push(it)
}
loopExitNodes.push(createLoopExitNode(loop))
levelCounter++
val conditionEnterNode = createLoopConditionEnterNode(loop.condition).also {
addNewSimpleNode(it)
// put conditional node twice so we can refer it after exit from loop block
lastNodes.push(it)
}
levelCounter++
return loopEnterNode to conditionEnterNode
}
fun exitWhileLoopCondition(loop: FirLoop): Pair {
levelCounter--
val conditionExitNode = createLoopConditionExitNode(loop.condition)
addNewSimpleNode(conditionExitNode)
val conditionConstBooleanValue = conditionExitNode.booleanConstValue
addEdge(conditionExitNode, loopExitNodes.top(), propagateDeadness = false, isDead = conditionConstBooleanValue == true)
val loopBlockEnterNode = createLoopBlockEnterNode(loop)
addNewSimpleNode(loopBlockEnterNode, conditionConstBooleanValue == false)
levelCounter++
return conditionExitNode to loopBlockEnterNode
}
fun exitWhileLoop(loop: FirLoop): Pair {
loopEnterNodes.pop()
levelCounter--
val loopBlockExitNode = createLoopBlockExitNode(loop)
popAndAddEdge(loopBlockExitNode)
if (lastNodes.isNotEmpty) {
val conditionEnterNode = lastNodes.pop()
require(conditionEnterNode is LoopConditionEnterNode) { loop.render() }
addBackEdge(loopBlockExitNode, conditionEnterNode)
}
val loopExitNode = loopExitNodes.pop()
loopExitNode.updateDeadStatus()
lastNodes.push(loopExitNode)
levelCounter--
return loopBlockExitNode to loopExitNode
}
// ----------------------------------- Do while Loop -----------------------------------
fun enterDoWhileLoop(loop: FirLoop): Pair {
val loopEnterNode = createLoopEnterNode(loop)
addNewSimpleNode(loopEnterNode)
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 loopEnterNode to blockEnterNode
}
fun enterDoWhileLoopCondition(loop: FirLoop): Pair {
levelCounter--
val blockExitNode = createLoopBlockExitNode(loop).also { addNewSimpleNode(it) }
val conditionEnterNode = createLoopConditionEnterNode(loop.condition).also { addNewSimpleNode(it) }
levelCounter++
return blockExitNode to conditionEnterNode
}
fun exitDoWhileLoop(loop: FirLoop): Pair {
loopEnterNodes.pop()
levelCounter--
val conditionExitNode = createLoopConditionExitNode(loop.condition)
val conditionBooleanValue = conditionExitNode.booleanConstValue
popAndAddEdge(conditionExitNode)
val blockEnterNode = lastNodes.pop()
require(blockEnterNode is LoopBlockEnterNode)
addBackEdge(conditionExitNode, blockEnterNode, isDead = conditionBooleanValue == false)
val loopExit = loopExitNodes.pop()
addEdge(conditionExitNode, loopExit, propagateDeadness = false, isDead = conditionBooleanValue == true)
loopExit.updateDeadStatus()
lastNodes.push(loopExit)
levelCounter--
return conditionExitNode to loopExit
}
// ----------------------------------- Boolean operators -----------------------------------
fun enterBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndEnterNode {
assert(binaryLogicExpression.kind == LogicOperationKind.AND)
binaryAndExitNodes.push(createBinaryAndExitNode(binaryLogicExpression))
return createBinaryAndEnterNode(binaryLogicExpression).also { addNewSimpleNode(it) }.also { levelCounter++ }
}
fun exitLeftBinaryAndArgument(binaryLogicExpression: FirBinaryLogicExpression): Pair {
assert(binaryLogicExpression.kind == LogicOperationKind.AND)
val lastNode = lastNodes.pop()
val leftBooleanConstValue = lastNode.booleanConstValue
val leftExitNode = createBinaryAndExitLeftOperandNode(binaryLogicExpression).also {
addEdge(lastNode, it)
addEdge(it, binaryAndExitNodes.top(), propagateDeadness = false, isDead = leftBooleanConstValue == true)
}
val rightEnterNode = createBinaryAndEnterRightOperandNode(binaryLogicExpression).also {
addEdge(leftExitNode, it, isDead = leftBooleanConstValue == false)
lastNodes.push(it)
}
return leftExitNode to rightEnterNode
}
fun exitBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression): BinaryAndExitNode {
levelCounter--
assert(binaryLogicExpression.kind == LogicOperationKind.AND)
return binaryAndExitNodes.pop().also {
val rightNode = lastNodes.pop()
addEdge(rightNode, it, propagateDeadness = false, isDead = it.leftOperandNode.booleanConstValue == false)
it.updateDeadStatus()
lastNodes.push(it)
}
}
fun enterBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrEnterNode {
assert(binaryLogicExpression.kind == LogicOperationKind.OR)
binaryOrExitNodes.push(createBinaryOrExitNode(binaryLogicExpression))
return createBinaryOrEnterNode(binaryLogicExpression).also {
addNewSimpleNode(it)
}.also { levelCounter++ }
}
fun exitLeftBinaryOrArgument(binaryLogicExpression: FirBinaryLogicExpression): Pair {
levelCounter--
assert(binaryLogicExpression.kind == LogicOperationKind.OR)
val previousNode = lastNodes.pop()
val leftBooleanValue = previousNode.booleanConstValue
val leftExitNode = createBinaryOrExitLeftOperandNode(binaryLogicExpression).also {
addEdge(previousNode, it)
addEdge(it, binaryOrExitNodes.top(), propagateDeadness = false, isDead = leftBooleanValue == false)
}
val rightExitNode = createBinaryOrEnterRightOperandNode(binaryLogicExpression).also {
addEdge(leftExitNode, it, propagateDeadness = true, isDead = leftBooleanValue == true)
lastNodes.push(it)
levelCounter++
}
return leftExitNode to rightExitNode
}
fun enterContract(qualifiedAccess: FirQualifiedAccess): EnterContractNode {
return createEnterContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
}
fun exitContract(qualifiedAccess: FirQualifiedAccess): ExitContractNode {
return createExitContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
}
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
assert(binaryLogicExpression.kind == LogicOperationKind.OR)
levelCounter--
return binaryOrExitNodes.pop().also {
val rightNode = lastNodes.pop()
addEdge(rightNode, it, propagateDeadness = false)
it.updateDeadStatus()
lastNodes.push(it)
}
}
private val CFGNode<*>.booleanConstValue: Boolean? get() = (fir as? FirConstExpression<*>)?.value as? Boolean?
// ----------------------------------- Try-catch-finally -----------------------------------
fun enterTryExpression(tryExpression: FirTryExpression): Pair {
catchNodeStorages.push(NodeStorage())
val enterTryExpressionNode = createTryExpressionEnterNode(tryExpression)
addNewSimpleNode(enterTryExpressionNode)
val tryExitNode = createTryExpressionExitNode(tryExpression)
tryExitNodes.push(tryExitNode)
levelCounter++
val enterTryNodeBlock = createTryMainBlockEnterNode(tryExpression)
addNewSimpleNode(enterTryNodeBlock)
for (catch in tryExpression.catches) {
val catchNode = createCatchClauseEnterNode(catch)
catchNodeStorage.push(catchNode)
// a flow where an exception of interest is thrown and caught before executing any of try-main block.
addEdge(enterTryExpressionNode, catchNode)
}
levelCounter++
if (tryExpression.finallyBlock != null) {
val finallyEnterNode = createFinallyBlockEnterNode(tryExpression)
// a flow where an uncaught exception is thrown before executing any of try-main block.
addEdge(enterTryExpressionNode, finallyEnterNode, label = UncaughtExceptionPath)
finallyEnterNodes.push(finallyEnterNode)
}
return enterTryExpressionNode to enterTryNodeBlock
}
fun exitTryMainBlock(tryExpression: FirTryExpression): TryMainBlockExitNode {
levelCounter--
val node = createTryMainBlockExitNode(tryExpression)
tryMainExitNodes.push(node)
popAndAddEdge(node)
val finallyEnterNode = finallyEnterNodes.topOrNull()
// NB: Check the level to avoid adding an edge to the finally block at an upper level.
if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) {
// TODO: in case of return/throw in try main block, we need a unique label.
addEdge(node, finallyEnterNode)
} else {
addEdge(node, tryExitNodes.top())
}
return node
}
fun enterCatchClause(catch: FirCatch): CatchClauseEnterNode {
return catchNodeStorage[catch]!!.also {
val tryMainExitNode = tryMainExitNodes.top()
// a flow where an exception of interest is thrown and caught after executing all of try-main block.
addEdge(tryMainExitNode, it)
val finallyEnterNode = finallyEnterNodes.topOrNull()
// a flow where an uncaught exception is thrown before executing any of catch clause.
// NB: Check the level to avoid adding an edge to the finally block at an upper level.
if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) {
addEdge(it, finallyEnterNode, label = UncaughtExceptionPath)
} else {
addEdge(it, exitTargetsForTry.top(), label = UncaughtExceptionPath)
}
lastNodes.push(it)
levelCounter++
}
}
fun exitCatchClause(catch: FirCatch): CatchClauseExitNode {
levelCounter--
return createCatchClauseExitNode(catch).also {
popAndAddEdge(it)
val finallyEnterNode = finallyEnterNodes.topOrNull()
// NB: Check the level to avoid adding an edge to the finally block at an upper level.
if (finallyEnterNode != null && finallyEnterNode.level == levelCounter + 1) {
// TODO: in case of return/rethrow in catch clause, we need a unique label.
addEdge(it, finallyEnterNode, propagateDeadness = false)
} else {
addEdge(it, tryExitNodes.top(), propagateDeadness = false)
}
}
}
fun enterFinallyBlock(): FinallyBlockEnterNode {
val enterNode = finallyEnterNodes.pop()
lastNodes.push(enterNode)
return enterNode
}
fun exitFinallyBlock(tryExpression: FirTryExpression): FinallyBlockExitNode {
return createFinallyBlockExitNode(tryExpression).also {
popAndAddEdge(it)
val tryExitNode = tryExitNodes.top()
// a flow where either there wasn't any exception or caught if any.
addEdge(it, tryExitNode)
// a flow that exits to the exit target while there was an uncaught exception.
addEdge(it, exitTargetsForTry.top(), label = UncaughtExceptionPath)
// TODO: differentiate flows that return/(re)throw in try main block or catch clauses.
}
}
fun exitTryExpression(
callCompleted: Boolean
): Pair {
levelCounter--
catchNodeStorages.pop()
tryMainExitNodes.pop()
val node = tryExitNodes.pop()
node.updateDeadStatus()
lastNodes.push(node)
val (_, unionNode) = processUnionOfArguments(node, callCompleted)
return node to unionNode
}
// ----------------------------------- Resolvable call -----------------------------------
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression): QualifiedAccessNode {
val returnsNothing = qualifiedAccessExpression.resultType.isNothing
val node = createQualifiedAccessNode(qualifiedAccessExpression)
if (returnsNothing) {
addNodeThatReturnsNothing(node)
} else {
addNewSimpleNode(node)
}
return node
}
fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier): ResolvedQualifierNode {
return createResolvedQualifierNode(resolvedQualifier).also(this::addNewSimpleNode)
}
fun enterCall() {
levelCounter++
}
fun exitIgnoredCall(functionCall: FirFunctionCall) {
levelCounter--
ignoredFunctionCalls += functionCall
}
fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean): Pair {
val callWasIgnored = ignoredFunctionCalls.remove(functionCall)
if (!callWasIgnored) {
levelCounter--
} else {
ignoredFunctionCalls.clear()
}
val returnsNothing = functionCall.resultType.isNothing
val node = createFunctionCallNode(functionCall)
val (kind, unionNode) = processUnionOfArguments(node, callCompleted)
if (returnsNothing) {
addNodeThatReturnsNothing(node, preferredKind = kind)
} else {
addNewSimpleNode(node, preferredKind = kind)
}
return node to unionNode
}
fun exitDelegatedConstructorCall(
call: FirDelegatedConstructorCall,
callCompleted: Boolean
): Pair {
levelCounter--
val node = createDelegatedConstructorCallNode(call)
val (kind, unionNode) = processUnionOfArguments(node, callCompleted)
addNewSimpleNode(node, preferredKind = kind)
return node to unionNode
}
fun exitConstExpression(constExpression: FirConstExpression<*>): ConstExpressionNode {
return createConstExpressionNode(constExpression).also { addNewSimpleNode(it) }
}
fun exitVariableDeclaration(variable: FirProperty): 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) }
}
fun exitCheckNotNullCall(
checkNotNullCall: FirCheckNotNullCall,
callCompleted: Boolean
): Pair {
val node = createCheckNotNullCallNode(checkNotNullCall).also { addNewSimpleNode(it) }
val unionNode = processUnionOfArguments(node, callCompleted).second
return node to unionNode
}
/*
* This is needed for some control flow constructions which are resolved as calls (when and elvis)
* For usual call we have invariant that all arguments will be called before function call, but for
* when and elvis only one of arguments will be actually called, so it's illegal to pass data flow info
* from lambda in one of branches
*/
private fun dropPostponedLambdasForNonDeterministicCalls() {
exitsFromCompletedPostponedAnonymousFunctions.clear()
}
private fun processUnionOfArguments(
node: CFGNode<*>,
callCompleted: Boolean
): Pair {
if (!shouldPassFlowFromInplaceLambda.top()) return EdgeKind.Forward to null
var kind = EdgeKind.Forward
if (!callCompleted || exitsFromCompletedPostponedAnonymousFunctions.isEmpty()) {
return EdgeKind.Forward to null
}
val unionNode by lazy { createUnionFunctionCallArgumentsNode(node.fir) }
var hasDirectPreviousNode = false
var hasPostponedLambdas = false
val iterator = exitsFromCompletedPostponedAnonymousFunctions.iterator()
val lastPostponedLambdaExitNode = lastNode
while (iterator.hasNext()) {
val exitNode = iterator.next()
if (node.level >= exitNode.level) continue
hasPostponedLambdas = true
if (exitNode == lastPostponedLambdaExitNode) {
popAndAddEdge(node, preferredKind = EdgeKind.CfgForward)
kind = EdgeKind.DfgForward
hasDirectPreviousNode = true
}
addEdge(exitNode.lastPreviousNode, unionNode, preferredKind = EdgeKind.DfgForward)
iterator.remove()
}
if (hasPostponedLambdas) {
if (hasDirectPreviousNode) {
lastNodes.push(unionNode)
} else {
addNewSimpleNode(unionNode)
}
} else {
return EdgeKind.Forward to null
}
return Pair(kind, unionNode)
}
// ----------------------------------- Annotations -----------------------------------
fun enterAnnotationCall(annotationCall: FirAnnotationCall): AnnotationEnterNode {
val graph = ControlFlowGraph(null, "STUB_GRAPH_FOR_ANNOTATION_CALL", ControlFlowGraph.Kind.AnnotationCall)
pushGraph(graph, Mode.Body)
return createAnnotationEnterNode(annotationCall).also {
lastNodes.push(it)
}
}
fun exitAnnotationCall(annotationCall: FirAnnotationCall): AnnotationExitNode {
val node = createAnnotationExitNode(annotationCall)
popAndAddEdge(node)
popGraph()
return node
}
// ----------------------------------- Block -----------------------------------
fun enterInitBlock(initBlock: FirAnonymousInitializer): Pair?> {
// TODO: questionable moment that we should pass data flow from init to init
val graph = ControlFlowGraph(initBlock, "init block", ControlFlowGraph.Kind.Function)
pushGraph(graph, Mode.Body)
val enterNode = createInitBlockEnterNode(initBlock).also {
lastNodes.push(it)
}
val lastNode = runIf(lastNode is InitBlockExitNode) { lastNodes.pop() } ?: enterToLocalClassesMembers[initBlock.symbol]
lastNode?.let { addEdge(it, enterNode, preferredKind = EdgeKind.DfgForward) }
createInitBlockExitNode(initBlock).also {
initBlockExitNodes.push(it)
exitTargetsForTry.push(it)
}
return enterNode to lastNode
}
fun exitInitBlock(initBlock: FirAnonymousInitializer): Pair {
val exitNode = initBlockExitNodes.pop()
require(exitNode == exitTargetsForTry.pop())
popAndAddEdge(exitNode)
val graph = popGraph()
assert(graph.declaration == initBlock)
exitNode.updateDeadStatus()
return exitNode to graph
}
// ----------------------------------- Safe calls -----------------------------------
fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode {
/*
* We create
* lastNode -> enterNode
* lastNode -> exitNode
* instead of
* lastNode -> enterNode -> exitNode
* because of we need to fork flow on `enterNode`, so `exitNode`
* will have unchanged flow from `lastNode`
*/
val lastNode = lastNodes.pop()
val enterNode = createEnterSafeCallNode(safeCall)
lastNodes.push(enterNode)
val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode)
addEdge(lastNode, exitNode)
return enterNode
}
fun exitSafeCall(): ExitSafeCallNode {
return exitSafeCallNodes.pop().also {
addNewSimpleNode(it)
it.updateDeadStatus()
}
}
// ----------------------------------- Elvis -----------------------------------
fun exitElvisLhs(elvisExpression: FirElvisExpression): Triple {
val exitNode = createElvisExitNode(elvisExpression).also {
exitElvisExpressionNodes.push(it)
}
val lhsExitNode = createElvisLhsExitNode(elvisExpression).also {
popAndAddEdge(it)
}
val lhsIsNotNullNode = createElvisLhsIsNotNullNode(elvisExpression).also {
addEdge(lhsExitNode, it)
addEdge(it, exitNode)
}
val rhsEnterNode = createElvisRhsEnterNode(elvisExpression).also {
addEdge(lhsExitNode, it)
}
lastNodes.push(rhsEnterNode)
return Triple(lhsExitNode, lhsIsNotNullNode, rhsEnterNode)
}
fun exitElvis(): ElvisExitNode {
val exitNode = exitElvisExpressionNodes.pop()
addNewSimpleNode(exitNode)
exitNode.updateDeadStatus()
dropPostponedLambdasForNonDeterministicCalls()
return exitNode
}
// ----------------------------------- Contract description -----------------------------------
fun enterContractDescription(): CFGNode<*> {
pushGraph(ControlFlowGraph(null, "contract description", ControlFlowGraph.Kind.TopLevel), Mode.Body)
return createContractDescriptionEnterNode().also {
lastNodes.push(it)
exitTargetsForTry.push(it)
}
}
fun exitContractDescription() {
lastNodes.pop()
exitTargetsForTry.pop()
popGraph()
}
// -------------------------------------------------------------------------------------------------------------------------
fun reset() {
exitsOfAnonymousFunctions.clear()
exitsFromCompletedPostponedAnonymousFunctions.clear()
lastNodes.reset()
}
fun dropSubgraphFromCall(call: FirFunctionCall) {
val graphs = mutableListOf()
call.acceptChildren(object : FirDefaultVisitorVoid() {
override fun visitElement(element: FirElement) {
element.acceptChildren(this)
}
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
anonymousFunction.controlFlowGraphReference?.accept(this)
}
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject) {
anonymousObject.controlFlowGraphReference?.accept(this)
}
override fun visitControlFlowGraphReference(controlFlowGraphReference: FirControlFlowGraphReference) {
val graph = controlFlowGraphReference.controlFlowGraph ?: return
if (graph.owner == null) return
graphs += graph
}
}, null)
for (graph in graphs) {
currentGraph.removeSubGraph(graph)
}
}
// ----------------------------------- Edge utils -----------------------------------
private fun addNewSimpleNode(
node: CFGNode<*>,
isDead: Boolean = false,
preferredKind: EdgeKind = EdgeKind.Forward
): CFGNode<*> {
val lastNode = lastNodes.pop()
addEdge(lastNode, node, isDead = isDead, preferredKind = preferredKind)
lastNodes.push(node)
return lastNode
}
private fun addNodeThatReturnsNothing(node: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Forward) {
val exitNode: CFGNode<*> = exitTargetsForTry.top()
addNodeWithJump(node, exitNode, preferredKind)
}
private fun addNodeWithJump(
node: CFGNode<*>,
targetNode: CFGNode<*>?,
preferredKind: EdgeKind = EdgeKind.Forward,
isBack: Boolean = false
) {
popAndAddEdge(node, preferredKind)
if (targetNode != null) {
if (isBack) {
addBackEdge(node, targetNode)
} else {
addEdge(node, targetNode, propagateDeadness = false)
}
}
val stub = createStubNode()
addEdge(node, stub)
lastNodes.push(stub)
}
private fun popAndAddEdge(to: CFGNode<*>, preferredKind: EdgeKind = EdgeKind.Forward) {
addEdge(lastNodes.pop(), to, preferredKind = preferredKind)
}
private fun addEdge(
from: CFGNode<*>,
to: CFGNode<*>,
propagateDeadness: Boolean = true,
isDead: Boolean = false,
isBack: Boolean = false,
preferredKind: EdgeKind = EdgeKind.Forward,
label: EdgeLabel = NormalPath
) {
val kind = if (isDead || from.isDead || to.isDead) {
if (isBack) EdgeKind.DeadBackward else EdgeKind.DeadForward
} else preferredKind
CFGNode.addEdge(from, to, kind, propagateDeadness, label)
}
private fun addBackEdge(
from: CFGNode<*>,
to: CFGNode<*>,
isDead: Boolean = false,
label: EdgeLabel = NormalPath
) {
addEdge(from, to, propagateDeadness = false, isDead = isDead, isBack = true, preferredKind = EdgeKind.CfgBackward, label = label)
}
// ----------------------------------- Utils -----------------------------------
private inline fun Collection>.forEachMember(block: (FirSymbolOwner<*>) -> Unit) {
for (member in this) {
for (callableDeclaration in member.unwrap()) {
block(callableDeclaration)
}
}
}
private fun FirSymbolOwner<*>.unwrap(): List> =
when (this) {
is FirFunction<*>, is FirAnonymousInitializer -> listOf(this)
is FirProperty -> listOfNotNull(this.getter, this.setter, this)
else -> emptyList()
}
private fun addNewSimpleNodeIfPossible(newNode: CFGNode<*>, isDead: Boolean = false): CFGNode<*>? {
if (lastNodes.isEmpty) return null
return addNewSimpleNode(newNode, isDead)
}
}
fun FirDeclaration?.isLocalClassOrAnonymousObject() = ((this as? FirRegularClass)?.isLocal == true) || this is FirAnonymousObject