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:
+24
-31
@@ -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()
|
||||
|
||||
+3
@@ -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 {
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user