diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirCallsEffectAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirCallsEffectAnalyzer.kt index bea9035c80d..7ae7fbb65ac 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirCallsEffectAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirCallsEffectAnalyzer.kt @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol -import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirThisReference @@ -71,7 +70,6 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() { val leakedSymbols = mutableMapOf, MutableList>() graph.traverse( - TraverseDirection.Forward, CapturedLambdaFinder(function), IllegalScopeContext(functionalTypeEffects.keys, leakedSymbols) ) @@ -163,6 +161,8 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() { override fun visitUnionNode(node: T, data: IllegalScopeContext) where T : CFGNode<*>, T : UnionNodeMarker {} override fun visitFunctionEnterNode(node: FunctionEnterNode, data: IllegalScopeContext) { + // TODO: this is not how CFG works, this should be done by FIR tree traversal. Especially considering that + // none of these methods use anything from the CFG other than `node.fir`, which should've been a hint. data.enterScope(node.fir === rootFunction || node.fir.isInPlaceLambda()) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt index 465ad7dd74b..ad63991fab7 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt @@ -9,10 +9,7 @@ import org.jetbrains.kotlin.contracts.description.canBeRevisited import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn -import org.jetbrains.kotlin.fir.analysis.cfa.util.PathAwarePropertyInitializationInfo import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoData -import org.jetbrains.kotlin.fir.analysis.cfa.util.TraverseDirection -import org.jetbrains.kotlin.fir.analysis.cfa.util.traverse import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.utils.isLateInit @@ -38,7 +35,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec ) { val localProperties = properties.filterNotTo(mutableSetOf()) { it.isInitialized() } val reporterVisitor = PropertyReporter(data, localProperties, capturedWrites, reporter, context) - graph.traverse(TraverseDirection.Forward, reporterVisitor) + graph.traverse(reporterVisitor) } private fun FirVariableSymbol<*>.isInitialized(): Boolean { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/CfgTraverser.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/CfgTraverser.kt index f43f14b51c7..e398d7faee3 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/CfgTraverser.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/CfgTraverser.kt @@ -7,32 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* -// ------------------------------ Graph Traversal ------------------------------ - enum class TraverseDirection { Forward, Backward } -fun ControlFlowGraph.traverse( - direction: TraverseDirection, - visitor: ControlFlowGraphVisitor<*, D>, - data: D -) { - for (node in getNodesInOrder(direction)) { - node.accept(visitor, data) - (node as? CFGNodeWithSubgraphs<*>)?.subGraphs?.forEach { it.traverse(direction, visitor, data) } - } -} - -fun ControlFlowGraph.traverse( - direction: TraverseDirection, - visitor: ControlFlowGraphVisitorVoid -) { - traverse(direction, visitor, null) -} - -// ---------------------- Path-sensitive data collection ----------------------- - fun > ControlFlowGraph.collectDataForNode( direction: TraverseDirection, visitor: PathAwareControlFlowGraphVisitor, diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/LocalPropertyAndCapturedWriteCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/LocalPropertyAndCapturedWriteCollector.kt index 6ed37e6c7a5..e183336de84 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/LocalPropertyAndCapturedWriteCollector.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/LocalPropertyAndCapturedWriteCollector.kt @@ -14,11 +14,12 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol +// TODO: this should be a FIR tree visitor, not a control flow graph visitor. class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() { companion object { fun collect(graph: ControlFlowGraph): Pair, Set> { val collector = LocalPropertyAndCapturedWriteCollector() - graph.traverse(TraverseDirection.Forward, collector) + graph.traverse(collector) return collector.symbols.keys to collector.capturedWrites } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt index 5d8be267c21..845cbf8e63d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt @@ -6,8 +6,6 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.fir.analysis.cfa.util.TraverseDirection -import org.jetbrains.kotlin.fir.analysis.cfa.util.traverse import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors @@ -34,11 +32,12 @@ object FirTailrecFunctionChecker : FirSimpleFunctionChecker() { } val graph = declaration.controlFlowGraphReference?.controlFlowGraph ?: return + // TODO: this is not how CFG works, tail calls inside try-catch should be detected by FIR tree traversal. var tryScopeCount = 0 var catchScopeCount = 0 var finallyScopeCount = 0 var tailrecCount = 0 - graph.traverse(TraverseDirection.Forward, object : ControlFlowGraphVisitorVoid() { + graph.traverse(object : ControlFlowGraphVisitorVoid() { override fun visitNode(node: CFGNode<*>) {} override fun visitUnionNode(node: T) where T : CFGNode<*>, T : UnionNodeMarker {} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt index 5a9de929438..66545d0e2cf 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt @@ -12,8 +12,6 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker -import org.jetbrains.kotlin.fir.analysis.cfa.util.TraverseDirection -import org.jetbrains.kotlin.fir.analysis.cfa.util.traverse import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.getChildren import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors @@ -36,7 +34,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() { val propertiesCharacteristics = mutableMapOf() val reporterVisitor = UninitializedPropertyReporter(data, properties, unprocessedProperties, propertiesCharacteristics) - graph.traverse(TraverseDirection.Forward, reporterVisitor) + graph.traverse(reporterVisitor) for (property in unprocessedProperties) { val source = property.source diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt index 6a719b79ffa..cc685956ec9 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt @@ -40,7 +40,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { context: CheckerContext ) { val ownData = ValueWritesWithoutReading(context.session, properties).getData(graph) - graph.traverse(TraverseDirection.Backward, CfaVisitor(ownData, reporter, context)) + graph.traverse(CfaVisitor(ownData, reporter, context)) } class CfaVisitor( @@ -72,6 +72,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { if (node.fir.source == null) return if (variableSymbol.isLoopIterator) return val dataPerNode = data[node] ?: return + // TODO: merge values for labels, otherwise diagnostics are inconsistent for (dataPerLabel in dataPerNode.values) { val data = dataPerLabel[variableSymbol] ?: continue diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt index f8aa63689c4..d186d2f4569 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraph.kt @@ -43,6 +43,21 @@ class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val k FakeCall, DefaultArgument, } + + // NOTE: this is only for dynamic dispatch on node types. If you're collecting data from predecessors, + // use `collectDataForNode` instead to account for `finally` block deduplication. If you don't need that, + // then you probably don't need this either. Hint: if the only thing you need from nodes is the corresponding + // FIR structure, then traverse the `FirFile` instead. + fun traverse(visitor: ControlFlowGraphVisitor<*, D>, data: D) { + for (node in nodes) { + node.accept(visitor, data) + (node as? CFGNodeWithSubgraphs<*>)?.subGraphs?.forEach { it.traverse(visitor, data) } + } + } + + fun traverse(visitor: ControlFlowGraphVisitorVoid) { + traverse(visitor, null) + } } data class Edge(