FIR: thread control flow through anonymous object init blocks
^KT-39646 Fixed
This commit is contained in:
@@ -328,6 +328,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
return controlFlowGraph
|
||||
}
|
||||
|
||||
fun enterAnonymousObject(anonymousObject: FirAnonymousObject) {
|
||||
graphBuilder.enterAnonymousObject(anonymousObject).mergeIncomingFlow()
|
||||
}
|
||||
|
||||
fun exitAnonymousObject(anonymousObject: FirAnonymousObject): ControlFlowGraph {
|
||||
// TODO: support capturing of mutable properties, KT-44877
|
||||
val (node, controlFlowGraph) = graphBuilder.exitAnonymousObject(anonymousObject)
|
||||
|
||||
+31
-9
@@ -453,19 +453,41 @@ class ControlFlowGraphBuilder {
|
||||
return node to graph
|
||||
}
|
||||
|
||||
fun enterAnonymousObject(anonymousObject: FirAnonymousObject): AnonymousObjectEnterNode {
|
||||
val enterNode = createAnonymousObjectEnterNode(anonymousObject)
|
||||
// TODO: looks like there was some problem with enum initializers that causes `lastNodes` to be empty
|
||||
lastNodes.popOrNull()?.let { addEdge(it, enterNode, preferredKind = EdgeKind.Forward) }
|
||||
lastNodes.push(enterNode)
|
||||
enterClass()
|
||||
return enterNode
|
||||
}
|
||||
|
||||
fun exitAnonymousObject(anonymousObject: FirAnonymousObject): Pair<AnonymousObjectExitNode, ControlFlowGraph> {
|
||||
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)
|
||||
}
|
||||
val enterNode = lastNodes.popOrNull()
|
||||
if (enterNode !is AnonymousObjectEnterNode) {
|
||||
throw AssertionError("anonymous object exit should be preceded by anonymous object enter, but got $enterNode")
|
||||
}
|
||||
visitLocalClassFunctions(anonymousObject, node)
|
||||
addEdge(node, graph.enterNode, preferredKind = EdgeKind.CfgForward)
|
||||
return node to graph
|
||||
val exitNode = createAnonymousObjectExitNode(anonymousObject)
|
||||
// TODO: Intentionally not using anonymous object init blocks for data flow? Might've been a FE1.0 bug.
|
||||
addEdge(enterNode, graph.enterNode, preferredKind = EdgeKind.CfgForward)
|
||||
if (!graph.exitNode.isDead) {
|
||||
addEdge(graph.exitNode, exitNode, preferredKind = EdgeKind.CfgForward)
|
||||
}
|
||||
addEdge(enterNode, exitNode, preferredKind = EdgeKind.DfgForward)
|
||||
// TODO: Here we're assuming that the methods are called after the object is constructed, which is really not true
|
||||
// (init blocks can call them). But FE1.0 did so too, hence the following code compiles and prints 0:
|
||||
// val x: Int
|
||||
// object {
|
||||
// fun bar() = x
|
||||
// init { x = bar() }
|
||||
// }
|
||||
// println(x)
|
||||
visitLocalClassFunctions(anonymousObject, exitNode)
|
||||
lastNodes.push(exitNode)
|
||||
return exitNode to graph
|
||||
}
|
||||
|
||||
fun exitAnonymousObjectExpression(anonymousObjectExpression: FirAnonymousObjectExpression): AnonymousObjectExpressionExitNode {
|
||||
@@ -474,7 +496,7 @@ class ControlFlowGraphBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fun visitLocalClassFunctions(klass: FirClass, node: CFGNodeWithCfgOwner<*>) {
|
||||
private fun visitLocalClassFunctions(klass: FirClass, node: CFGNodeWithSubgraphs<*>) {
|
||||
klass.declarations.filterIsInstance<FirFunction>().forEach { function ->
|
||||
val functionGraph = function.controlFlowGraphReference?.controlFlowGraph
|
||||
if (functionGraph != null && functionGraph.owner == null) {
|
||||
|
||||
+3
@@ -232,6 +232,9 @@ fun ControlFlowGraphBuilder.createPostponedLambdaEnterNode(fir: FirAnonymousFunc
|
||||
fun ControlFlowGraphBuilder.createAnonymousFunctionExpressionExitNode(fir: FirAnonymousFunctionExpression): AnonymousFunctionExpressionExitNode =
|
||||
AnonymousFunctionExpressionExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createAnonymousObjectEnterNode(fir: FirAnonymousObject): AnonymousObjectEnterNode =
|
||||
AnonymousObjectEnterNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createAnonymousObjectExitNode(fir: FirAnonymousObject): AnonymousObjectExitNode =
|
||||
AnonymousObjectExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ class FirControlFlowGraphRenderVisitor(
|
||||
renderEdges(kind)
|
||||
}
|
||||
|
||||
if (node is CFGNodeWithCfgOwner<*>) {
|
||||
if (node is CFGNodeWithSubgraphs<*>) {
|
||||
val subNodes = node.subGraphs
|
||||
if (subNodes.isNotEmpty()) {
|
||||
print(
|
||||
|
||||
+5
-1
@@ -484,7 +484,11 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
|
||||
transformer.firProviderInterceptor
|
||||
)
|
||||
}
|
||||
dataFlowAnalyzer.enterClass()
|
||||
if (!implicitTypeOnly && anonymousObject.controlFlowGraphReference == null) {
|
||||
dataFlowAnalyzer.enterAnonymousObject(anonymousObject)
|
||||
} else {
|
||||
dataFlowAnalyzer.enterClass()
|
||||
}
|
||||
val result = context.withAnonymousObject(anonymousObject, components) {
|
||||
transformDeclarationContent(anonymousObject, data) as FirAnonymousObject
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user