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(
direction: TraverseDirection,
visitor: PathAwareControlFlowGraphVisitor<I>,
visitSubGraphs: Boolean = true
): Map<CFGNode<*>, PathAwareControlFlowInfo<I>> {
val nodeMap = LinkedHashMap<CFGNode<*>, PathAwareControlFlowInfo<I>>()
val startNode = getEnterNode(direction)
nodeMap[startNode] = visitor.emptyInfo
val nodeMap = HashMap<CFGNode<*>, PathAwareControlFlowInfo<I>>()
var shouldContinue: Boolean
do {
shouldContinue = collectDataForNodeInternal(direction, visitor, nodeMap, visitSubGraphs)
shouldContinue = collectDataForNodeInternal(direction, visitor, nodeMap)
} while (shouldContinue)
return nodeMap
}
@@ -32,38 +27,36 @@ private fun <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNodeIn
direction: TraverseDirection,
visitor: PathAwareControlFlowGraphVisitor<I>,
nodeMap: MutableMap<CFGNode<*>, PathAwareControlFlowInfo<I>>,
visitSubGraphs: Boolean = true
): Boolean {
var changed = false
val nodes = getNodesInOrder(direction)
for (node in nodes) {
if (visitSubGraphs && direction == TraverseDirection.Backward && node is CFGNodeWithSubgraphs<*>) {
node.subGraphs.forEach { changed = changed or it.collectDataForNodeInternal(direction, visitor, nodeMap) }
}
val previousNodes = when (direction) {
TraverseDirection.Forward -> node.previousCfgNodes
TraverseDirection.Backward -> node.followingCfgNodes
for (node in getNodesInOrder(direction)) {
if (direction == TraverseDirection.Backward && node is CFGNodeWithSubgraphs<*>) {
node.subGraphs.forEach {
changed = changed or (visitor.visitSubGraph(node, it) && it.collectDataForNodeInternal(direction, visitor, nodeMap))
}
}
// TODO: if data for previousNodes hasn't changed, then should be no need to recompute data for this one
val union = node.isUnion
val previousData =
previousNodes.mapNotNull {
val k = when (direction) {
TraverseDirection.Forward -> node.edgeFrom(it)
TraverseDirection.Backward -> node.edgeTo(it)
val previousData = when (direction) {
TraverseDirection.Forward -> node.previousCfgNodes
TraverseDirection.Backward -> node.followingCfgNodes
}.mapNotNull { source ->
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(it, node, k, v)
}.reduceOrNull { a, b -> a.join(b, union) }
val data = nodeMap[node]
visitor.visitEdge(source, node, edge, it)
}
}.reduceOrNull { a, b -> a.join(b, union) }
val newData = node.accept(visitor, previousData ?: visitor.emptyInfo)
val hasChanged = newData != data
changed = changed or hasChanged
if (hasChanged) {
nodeMap[node] = newData
if (newData != nodeMap.put(node, newData)) {
changed = true
}
if (visitSubGraphs && direction == TraverseDirection.Forward && node is CFGNodeWithSubgraphs<*>) {
node.subGraphs.forEach { changed = changed or it.collectDataForNodeInternal(direction, visitor, nodeMap) }
if (direction == TraverseDirection.Forward && node is CFGNodeWithSubgraphs<*>) {
node.subGraphs.forEach {
changed = changed or (visitor.visitSubGraph(node, it) && it.collectDataForNodeInternal(direction, visitor, nodeMap))
}
}
}
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.ControlFlowGraph
fun ControlFlowGraph.getEnterNode(direction: TraverseDirection): CFGNode<*> = when (direction) {
TraverseDirection.Forward -> enterNode
TraverseDirection.Backward -> exitNode
}
fun ControlFlowGraph.getNodesInOrder(direction: TraverseDirection): List<CFGNode<*>> = when (direction) {
TraverseDirection.Forward -> nodes
TraverseDirection.Backward -> nodes.asReversed()
@@ -31,6 +31,9 @@ abstract class PathAwareControlFlowGraphVisitor<I : ControlFlowInfo<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> {
val label = metadata.label
return when {
@@ -39,6 +39,11 @@ class PropertyInitializationInfoCollector(
override val emptyInfo: PathAwarePropertyInitializationInfo
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(
node: VariableAssignmentNode,
data: PathAwarePropertyInitializationInfo
@@ -50,7 +50,6 @@ object FirMemberPropertiesChecker : FirClassChecker() {
(it.symbol as? FirPropertySymbol)?.takeIf { symbol -> symbol.requiresInitialization }
}
if (memberPropertySymbols.isEmpty()) return null
// TODO: this also visits non-constructor member functions...
// TODO: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
val data = PropertyInitializationInfoData(memberPropertySymbols, symbol, graph)
data.checkPropertyAccesses(context, reporter)