FIR CFA: skip member function graphs when looking for member assignments

Shouldn't affect the result, since in member functions all properties
are assumed to be initialized so the CFG is not needed for reporting
VAL_REASSIGNMENT.
This commit is contained in:
pyos
2023-01-23 11:19:45 +01:00
committed by teamcity
parent f118c94110
commit 1d9a1e979a
5 changed files with 32 additions and 37 deletions
@@ -14,17 +14,12 @@ enum class TraverseDirection {
fun <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNode( fun <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNode(
direction: TraverseDirection, direction: TraverseDirection,
visitor: PathAwareControlFlowGraphVisitor<I>, visitor: PathAwareControlFlowGraphVisitor<I>,
visitSubGraphs: Boolean = true
): Map<CFGNode<*>, PathAwareControlFlowInfo<I>> { ): Map<CFGNode<*>, PathAwareControlFlowInfo<I>> {
val nodeMap = LinkedHashMap<CFGNode<*>, PathAwareControlFlowInfo<I>>() val nodeMap = HashMap<CFGNode<*>, PathAwareControlFlowInfo<I>>()
val startNode = getEnterNode(direction)
nodeMap[startNode] = visitor.emptyInfo
var shouldContinue: Boolean var shouldContinue: Boolean
do { do {
shouldContinue = collectDataForNodeInternal(direction, visitor, nodeMap, visitSubGraphs) shouldContinue = collectDataForNodeInternal(direction, visitor, nodeMap)
} while (shouldContinue) } while (shouldContinue)
return nodeMap return nodeMap
} }
@@ -32,38 +27,36 @@ private fun <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNodeIn
direction: TraverseDirection, direction: TraverseDirection,
visitor: PathAwareControlFlowGraphVisitor<I>, visitor: PathAwareControlFlowGraphVisitor<I>,
nodeMap: MutableMap<CFGNode<*>, PathAwareControlFlowInfo<I>>, nodeMap: MutableMap<CFGNode<*>, PathAwareControlFlowInfo<I>>,
visitSubGraphs: Boolean = true
): Boolean { ): Boolean {
var changed = false var changed = false
val nodes = getNodesInOrder(direction) for (node in getNodesInOrder(direction)) {
for (node in nodes) { if (direction == TraverseDirection.Backward && node is CFGNodeWithSubgraphs<*>) {
if (visitSubGraphs && direction == TraverseDirection.Backward && node is CFGNodeWithSubgraphs<*>) { node.subGraphs.forEach {
node.subGraphs.forEach { changed = changed or it.collectDataForNodeInternal(direction, visitor, nodeMap) } changed = changed or (visitor.visitSubGraph(node, it) && it.collectDataForNodeInternal(direction, visitor, nodeMap))
} }
val previousNodes = when (direction) {
TraverseDirection.Forward -> node.previousCfgNodes
TraverseDirection.Backward -> node.followingCfgNodes
} }
// TODO: if data for previousNodes hasn't changed, then should be no need to recompute data for this one // TODO: if data for previousNodes hasn't changed, then should be no need to recompute data for this one
val union = node.isUnion val union = node.isUnion
val previousData = val previousData = when (direction) {
previousNodes.mapNotNull { TraverseDirection.Forward -> node.previousCfgNodes
val k = when (direction) { TraverseDirection.Backward -> node.followingCfgNodes
TraverseDirection.Forward -> node.edgeFrom(it) }.mapNotNull { source ->
TraverseDirection.Backward -> node.edgeTo(it) nodeMap[source]?.let {
val edge = when (direction) {
TraverseDirection.Forward -> node.edgeFrom(source)
TraverseDirection.Backward -> node.edgeTo(source)
} }
val v = nodeMap[it] ?: return@mapNotNull null visitor.visitEdge(source, node, edge, it)
visitor.visitEdge(it, node, k, v) }
}.reduceOrNull { a, b -> a.join(b, union) } }.reduceOrNull { a, b -> a.join(b, union) }
val data = nodeMap[node]
val newData = node.accept(visitor, previousData ?: visitor.emptyInfo) val newData = node.accept(visitor, previousData ?: visitor.emptyInfo)
val hasChanged = newData != data if (newData != nodeMap.put(node, newData)) {
changed = changed or hasChanged changed = true
if (hasChanged) {
nodeMap[node] = newData
} }
if (visitSubGraphs && direction == TraverseDirection.Forward && node is CFGNodeWithSubgraphs<*>) { if (direction == TraverseDirection.Forward && node is CFGNodeWithSubgraphs<*>) {
node.subGraphs.forEach { changed = changed or it.collectDataForNodeInternal(direction, visitor, nodeMap) } node.subGraphs.forEach {
changed = changed or (visitor.visitSubGraph(node, it) && it.collectDataForNodeInternal(direction, visitor, nodeMap))
}
} }
} }
return changed return changed
@@ -8,11 +8,6 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
fun ControlFlowGraph.getEnterNode(direction: TraverseDirection): CFGNode<*> = when (direction) {
TraverseDirection.Forward -> enterNode
TraverseDirection.Backward -> exitNode
}
fun ControlFlowGraph.getNodesInOrder(direction: TraverseDirection): List<CFGNode<*>> = when (direction) { fun ControlFlowGraph.getNodesInOrder(direction: TraverseDirection): List<CFGNode<*>> = when (direction) {
TraverseDirection.Forward -> nodes TraverseDirection.Forward -> nodes
TraverseDirection.Backward -> nodes.asReversed() TraverseDirection.Backward -> nodes.asReversed()
@@ -31,6 +31,9 @@ abstract class PathAwareControlFlowGraphVisitor<I : ControlFlowInfo<I, *, *>> :
abstract val emptyInfo: PathAwareControlFlowInfo<I> abstract val emptyInfo: PathAwareControlFlowInfo<I>
open fun visitSubGraph(node: CFGNodeWithSubgraphs<*>, graph: ControlFlowGraph): Boolean =
true // false to skip
open fun visitEdge(from: CFGNode<*>, to: CFGNode<*>, metadata: Edge, data: PathAwareControlFlowInfo<I>): PathAwareControlFlowInfo<I> { open fun visitEdge(from: CFGNode<*>, to: CFGNode<*>, metadata: Edge, data: PathAwareControlFlowInfo<I>): PathAwareControlFlowInfo<I> {
val label = metadata.label val label = metadata.label
return when { return when {
@@ -39,6 +39,11 @@ class PropertyInitializationInfoCollector(
override val emptyInfo: PathAwarePropertyInitializationInfo override val emptyInfo: PathAwarePropertyInitializationInfo
get() = EMPTY_INFO get() = EMPTY_INFO
// When looking for initializations of member properties, skip subgraphs of member functions;
// all properties are assumed to be initialized there.
override fun visitSubGraph(node: CFGNodeWithSubgraphs<*>, graph: ControlFlowGraph): Boolean =
expectedReceiver == null || node !is ClassExitNode || node !== node.owner.exitNode
override fun visitVariableAssignmentNode( override fun visitVariableAssignmentNode(
node: VariableAssignmentNode, node: VariableAssignmentNode,
data: PathAwarePropertyInitializationInfo data: PathAwarePropertyInitializationInfo
@@ -50,7 +50,6 @@ object FirMemberPropertiesChecker : FirClassChecker() {
(it.symbol as? FirPropertySymbol)?.takeIf { symbol -> symbol.requiresInitialization } (it.symbol as? FirPropertySymbol)?.takeIf { symbol -> symbol.requiresInitialization }
} }
if (memberPropertySymbols.isEmpty()) return null if (memberPropertySymbols.isEmpty()) return null
// TODO: this also visits non-constructor member functions...
// TODO: merge with `FirPropertyInitializationAnalyzer` for fewer passes. // TODO: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
val data = PropertyInitializationInfoData(memberPropertySymbols, symbol, graph) val data = PropertyInitializationInfoData(memberPropertySymbols, symbol, graph)
data.checkPropertyAccesses(context, reporter) data.checkPropertyAccesses(context, reporter)