FIR: thread control flow through anonymous object init blocks

^KT-39646 Fixed
This commit is contained in:
pyos
2021-11-18 14:31:22 +01:00
committed by TeamCityServer
parent 19031c88d9
commit a1be855d17
17 changed files with 506 additions and 431 deletions
@@ -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)
@@ -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) {
@@ -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())
@@ -156,7 +156,7 @@ class FirControlFlowGraphRenderVisitor(
renderEdges(kind)
}
if (node is CFGNodeWithCfgOwner<*>) {
if (node is CFGNodeWithSubgraphs<*>) {
val subNodes = node.subGraphs
if (subNodes.isNotEmpty()) {
print(
@@ -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
}