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