FIR DFA: use class enter node as data flow source for members

Also fix graphs for enums with specialized entries - since we don't
create property subgraphs for FirEnumEntry, there is no body to insert
AnonymousObjectEnterNode, AnonymousObjectExitNode, and
AnonymousObjectExpressionExitNode into.
This commit is contained in:
pyos
2022-12-10 19:18:15 +01:00
committed by Dmitriy Novozhilov
parent e6819e1295
commit 4bb7b1ac9f
12 changed files with 56 additions and 86 deletions
@@ -79,13 +79,12 @@ digraph annotatedLocalClass_kt {
16 -> {17} [style=dotted];
17 -> {18} [style=dotted];
18 -> {19};
18 -> {25} [color=red];
19 -> {20};
19 -> {23} [color=green];
19 -> {20 23};
19 -> {23} [style=dashed];
20 -> {21};
21 -> {22};
23 -> {24} [color=green];
23 -> {25} [color=red];
24 -> {25} [color=green];
24 -> {25} [style=dashed];
25 -> {26};
@@ -44,18 +44,19 @@ digraph innerClassInAnonymousObject_kt {
2 [label="Exit function <init>" style="filled" fillcolor=red];
}
14 -> {15};
14 -> {0 3 6} [color=red];
15 -> {12} [color=green];
15 -> {12};
15 -> {16} [color=red];
15 -> {12} [style=dashed];
16 -> {17};
17 -> {18};
12 -> {13} [color=green];
12 -> {0 10} [color=red];
13 -> {0 10 16} [color=green];
13 -> {0 10} [style=dashed];
0 -> {1};
1 -> {2};
10 -> {11} [color=green];
10 -> {3 6} [color=red];
11 -> {3 6} [color=green];
11 -> {3 6} [style=dashed];
3 -> {4};
@@ -351,12 +351,9 @@ digraph localClassesWithImplicit_kt {
19 -> {20} [style=dotted];
20 -> {21} [style=dotted];
21 -> {22};
21 -> {31 34 71 90} [color=red];
22 -> {23};
22 -> {29} [color=green];
22 -> {99 102 139 158} [color=red];
22 -> {23 29};
22 -> {29} [style=dashed];
23 -> {97} [color=green];
23 -> {97};
23 -> {24} [color=red];
23 -> {97} [style=dashed];
24 -> {25};
@@ -364,6 +361,7 @@ digraph localClassesWithImplicit_kt {
26 -> {27};
27 -> {28};
29 -> {30} [color=green];
29 -> {31 34 71 90} [color=red];
30 -> {31 34 71 90} [color=green];
30 -> {31 34 71 90} [style=dashed];
31 -> {32};
@@ -436,6 +434,7 @@ digraph localClassesWithImplicit_kt {
94 -> {95} [style=dotted];
95 -> {96} [style=dotted];
97 -> {98} [color=green];
97 -> {99 102 139 158} [color=red];
98 -> {24 99 102 139 158} [color=green];
98 -> {99 102 139 158} [style=dashed];
99 -> {100};
@@ -114,12 +114,11 @@ digraph propertiesAndInitBlocks_kt {
}
58 -> {59};
59 -> {60};
59 -> {66 73} [color=red];
60 -> {61};
60 -> {63} [color=green];
60 -> {61 63};
60 -> {63} [style=dashed];
61 -> {62};
63 -> {64} [color=green];
63 -> {66 73} [color=red];
64 -> {66} [color=green];
64 -> {65} [style=dotted];
64 -> {66} [style=dashed];
@@ -210,10 +209,8 @@ digraph propertiesAndInitBlocks_kt {
24 -> {25};
25 -> {26};
26 -> {27 33};
26 -> {47 55} [color=red];
26 -> {33} [style=dashed];
27 -> {28};
27 -> {44} [color=green];
27 -> {28 44};
27 -> {44} [style=dashed];
28 -> {29};
29 -> {30} [style=dotted];
@@ -231,6 +228,7 @@ digraph propertiesAndInitBlocks_kt {
41 -> {42} [style=dotted];
42 -> {43} [style=dotted];
44 -> {45} [color=green];
44 -> {47 55} [color=red];
45 -> {47} [color=green];
45 -> {46} [style=dotted];
45 -> {47} [style=dashed];
@@ -192,8 +192,7 @@ digraph smartcastInByClause_kt {
39 -> {40};
40 -> {41};
41 -> {42};
41 -> {53 59 64 67} [color=red];
42 -> {49} [color=green];
42 -> {49};
42 -> {43} [color=red];
42 -> {49} [style=dashed];
43 -> {44};
@@ -203,6 +202,7 @@ digraph smartcastInByClause_kt {
46 -> {47} [style=dotted];
47 -> {48} [style=dotted];
49 -> {50} [color=green];
49 -> {53 59 64 67} [color=red];
50 -> {53} [color=green];
50 -> {51} [style=dotted];
50 -> {53} [style=dashed];
@@ -71,8 +71,7 @@ digraph inAnonymousObject_kt {
1 -> {2};
2 -> {3};
3 -> {4};
3 -> {17 20 26 29} [color=red];
4 -> {13} [color=green];
4 -> {13};
4 -> {5} [color=red];
4 -> {13} [style=dashed];
5 -> {6};
@@ -83,6 +82,7 @@ digraph inAnonymousObject_kt {
10 -> {11};
11 -> {12};
13 -> {14} [color=green];
13 -> {17 20 26 29} [color=red];
14 -> {17} [color=green];
14 -> {15} [style=dotted];
14 -> {17} [style=dashed];
@@ -74,15 +74,14 @@ digraph inLocalClass_kt {
1 -> {2};
2 -> {3};
3 -> {4};
3 -> {14 17 23 29} [color=red];
4 -> {5};
4 -> {10} [color=green];
4 -> {5 10};
4 -> {10} [style=dashed];
5 -> {6};
6 -> {7};
7 -> {8};
8 -> {9};
10 -> {11} [color=green];
10 -> {14 17 23 29} [color=red];
11 -> {14} [color=green];
11 -> {12} [style=dotted];
11 -> {14} [style=dashed];
@@ -196,14 +196,14 @@ digraph delegateWithAnonymousObject_kt {
78 -> {81} [color=green];
26 -> {27};
27 -> {28};
27 -> {35 38 45} [color=red];
28 -> {33} [color=green];
28 -> {33};
28 -> {29} [color=red];
28 -> {33} [style=dashed];
29 -> {30};
30 -> {31};
31 -> {32};
33 -> {34} [color=green];
33 -> {35 38 45} [color=red];
34 -> {29 35 38 45} [color=green];
34 -> {35 38 45} [style=dashed];
35 -> {36};
@@ -204,7 +204,9 @@ abstract class FirDataFlowAnalyzer(
// ----------------------------------- Classes -----------------------------------
fun enterClass(klass: FirClass, buildGraph: Boolean) {
graphBuilder.enterClass(klass, buildGraph)?.mergeIncomingFlow()
val (outerNode, enterNode) = graphBuilder.enterClass(klass, buildGraph) ?: return
outerNode?.mergeIncomingFlow()
enterNode.mergeIncomingFlow()
}
fun exitClass(): ControlFlowGraph? {
@@ -223,7 +225,7 @@ abstract class FirDataFlowAnalyzer(
}
fun exitAnonymousObjectExpression(anonymousObjectExpression: FirAnonymousObjectExpression) {
graphBuilder.exitAnonymousObjectExpression(anonymousObjectExpression).mergeIncomingFlow()
graphBuilder.exitAnonymousObjectExpression(anonymousObjectExpression)?.mergeIncomingFlow()
}
// ----------------------------------- Scripts ------------------------------------------
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
import org.jetbrains.kotlin.contracts.description.*
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField
@@ -43,7 +44,7 @@ class ControlFlowGraphBuilder {
private val exitTargetsForReturn: SymbolBasedNodeStorage<FirFunction, FunctionExitNode> = SymbolBasedNodeStorage()
private val exitTargetsForTry: Stack<CFGNode<*>> = stackOf()
private val enterToLocalClassesMembers: MutableMap<FirBasedSymbol<*>, CFGNode<*>?> = mutableMapOf()
private val enterToLocalClassesMembers: MutableMap<FirBasedSymbol<*>, CFGNode<*>> = mutableMapOf()
//return jumps via finally blocks, target -> jumps
private val nonDirectJumps: ListMultimap<CFGNode<*>, CFGNode<*>> = listMultimapOf()
@@ -361,19 +362,20 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Classes -----------------------------------
fun enterClass(klass: FirClass, buildGraph: Boolean): CFGNode<*>? {
fun enterClass(klass: FirClass, buildGraph: Boolean): Pair<CFGNode<*>?, ClassEnterNode>? {
if (!buildGraph) {
pushGraph(ControlFlowGraph(null, "STUB_CLASS_GRAPH", ControlFlowGraph.Kind.Stub))
return null
}
val enterNode = when {
klass is FirAnonymousObject -> createAnonymousObjectEnterNode(klass)
// 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:
// control flow enters here and never leaves, and assignments invalidate smart casts.
klass is FirRegularClass && klass.isLocal && currentGraph.kind.withBody -> createLocalClassExitNode(klass)
else -> null
}
}?.also { addNewSimpleNode(it) }
val name = when (klass) {
is FirAnonymousObject -> "<anonymous object>"
@@ -383,14 +385,25 @@ class ControlFlowGraphBuilder {
pushGraph(ControlFlowGraph(klass, name, ControlFlowGraph.Kind.ClassInitializer))
val graphEnterNode = createClassEnterNode(klass)
lastNodes.push(graphEnterNode)
if (enterNode != null) {
// TODO: anonymous objects are used to represent enum entries - check what happens there
// (likely `exitClass` leaves a node on the stack that will never be consumed)
lastNodes.popOrNull()?.let { addEdge(it, enterNode) }
lastNodes.push(enterNode)
addEdge(enterNode, graphEnterNode, preferredKind = EdgeKind.CfgForward)
addEdge(enterNode, graphEnterNode)
} else {
enterToLocalClassesMembers.remove(klass.symbol)?.let { addEdge(it, graphEnterNode, preferredKind = EdgeKind.DfgForward) }
}
return enterNode
if (graphEnterNode.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
}
if (member is FirProperty) {
member.getter?.let { enterToLocalClassesMembers[it.symbol] = graphEnterNode }
member.setter?.let { enterToLocalClassesMembers[it.symbol] = graphEnterNode }
}
}
}
return enterNode to graphEnterNode
}
fun exitClass(): Pair<AnonymousObjectExitNode?, ControlFlowGraph>? {
@@ -400,13 +413,14 @@ class ControlFlowGraphBuilder {
}
val graph = popClassGraph()
if (graph.declaration !is FirAnonymousObject) {
val anonymousObject = graph.declaration as? FirAnonymousObject
if (anonymousObject == null || anonymousObject.classKind == ClassKind.ENUM_ENTRY) {
return null to graph
}
val lastNode = lastNodes.pop() as AnonymousObjectEnterNode
val exitNode = createAnonymousObjectExitNode(lastNode.fir).also { lastNodes.push(it) }
// TODO: should merge data flow from members into the exit node.
// TODO: should merge data flow from initializer parts into the exit node.
// TODO: the reason for this liveness trickery is that if control flow is dead, the CFG-only edge from
// `graph.exitNode` gets magically transformed into a CFG+DFG dead edge, and `graph.exitNode` has no data
// flow information attached to it. This should be fixed as soon as data flow is made to go through the object.
@@ -439,7 +453,7 @@ class ControlFlowGraphBuilder {
}
}
var node: CFGNode<*> = currentGraph.enterNode as ClassEnterNode
var node: CFGNode<*> = lastNodes.pop() as ClassEnterNode
var prevInitPartNode: CFGNode<*>? = null
for (graph in calledInPlace) {
val partNode = createPartOfClassInitializationNode(graph.declaration as FirControlFlowGraphOwner)
@@ -476,22 +490,9 @@ class ControlFlowGraphBuilder {
return popGraph()
}
fun prepareForLocalClassMembers(members: Collection<FirDeclaration>) {
// TODO: this is called before `enterClass` so the data flow source for objects and local classes
// is not the enter node, but whichever node happens to be before it. This technically works,
// but is ugly.
members.forEachMember {
enterToLocalClassesMembers[it.symbol] = lastNodes.topOrNull()
}
}
fun exitAnonymousObjectExpression(anonymousObjectExpression: FirAnonymousObjectExpression): AnonymousObjectExpressionExitNode? {
if (anonymousObjectExpression.anonymousObject.classKind == ClassKind.ENUM_ENTRY) return null
fun cleanAfterForLocalClassMembers(members: Collection<FirDeclaration>) {
members.forEachMember {
enterToLocalClassesMembers.remove(it.symbol)
}
}
fun exitAnonymousObjectExpression(anonymousObjectExpression: FirAnonymousObjectExpression): AnonymousObjectExpressionExitNode {
// TODO: what's AnonymousObjectExitNode for then?
return createAnonymousObjectExpressionExitNode(anonymousObjectExpression).also {
addNewSimpleNodeIfPossible(it)
@@ -1402,21 +1403,6 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Utils -----------------------------------
private inline fun Collection<FirDeclaration>.forEachMember(block: (FirDeclaration) -> Unit) {
for (member in this) {
for (callableDeclaration in member.unwrap()) {
block(callableDeclaration)
}
}
}
private fun FirDeclaration.unwrap(): List<FirDeclaration> =
when (this) {
is FirFunction, is FirAnonymousInitializer, is FirField -> listOf(this)
is FirProperty -> listOfNotNull(this.getter, this.setter, this)
else -> emptyList()
}
private fun <R> withLevelOfNode(node: CFGNode<*>, f: () -> R): R {
val last = levelCounter
levelCounter = node.level
@@ -102,14 +102,7 @@ fun <F : FirClassLikeDeclaration> F.runContractAndBodiesResolutionForLocalClass(
outerBodyResolveContext = newContext,
firTowerDataContextCollector = firTowerDataContextCollector
)
val graphBuilder = components.context.dataFlowAnalyzerContext.graphBuilder
val members = localClassesNavigationInfo.allMembers
graphBuilder.prepareForLocalClassMembers(members)
return this.transform<F, ResolutionMode>(transformer, resolutionMode).also {
graphBuilder.cleanAfterForLocalClassMembers(members)
}
return this.transform(transformer, resolutionMode)
}
private fun ReturnTypeCalculator.getTransformerCreator() = when (this) {
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.utils.keysToMap
class LocalClassesNavigationInfo(
val parentForClass: Map<FirClassLikeDeclaration, FirClassLikeDeclaration?>,
private val parentClassForFunction: Map<FirCallableDeclaration, FirClassLikeDeclaration>,
val allMembers: List<FirDeclaration>
) {
val designationMap: Map<FirCallableDeclaration, List<FirClassLikeDeclaration>> by lazy {
parentClassForFunction.keys.keysToMap {
@@ -39,13 +38,12 @@ fun FirClassLikeDeclaration.collectLocalClassesNavigationInfo(): LocalClassesNav
NavigationInfoVisitor().run {
this@collectLocalClassesNavigationInfo.accept(this@run, null)
LocalClassesNavigationInfo(parentForClass, resultingMap, allMembers)
LocalClassesNavigationInfo(parentForClass, resultingMap)
}
private class NavigationInfoVisitor : FirDefaultVisitor<Unit, Any?>() {
val resultingMap: MutableMap<FirCallableDeclaration, FirClassLikeDeclaration> = mutableMapOf()
val parentForClass: MutableMap<FirClassLikeDeclaration, FirClassLikeDeclaration?> = mutableMapOf()
val allMembers: MutableList<FirDeclaration> = mutableListOf()
private val currentPath: MutableList<FirClassLikeDeclaration> = mutableListOf()
override fun visitElement(element: FirElement, data: Any?) {}
@@ -84,12 +82,7 @@ private class NavigationInfoVisitor : FirDefaultVisitor<Unit, Any?>() {
}
override fun visitCallableDeclaration(callableDeclaration: FirCallableDeclaration, data: Any?) {
allMembers += callableDeclaration
if (callableDeclaration.returnTypeRef !is FirImplicitTypeRef) return
resultingMap[callableDeclaration] = currentPath.last()
}
override fun visitAnonymousInitializer(anonymousInitializer: FirAnonymousInitializer, data: Any?) {
allMembers += anonymousInitializer
}
}