FIR CFA: remove direction from ControlFlowGraph.traverse.

If you care about direction, then you also care about labels.

3 out of 6 places that use `traverse` are incorrect, by the way.
Node order does not correspond to scoping boundaries.
This commit is contained in:
pyos
2022-12-16 16:42:16 +01:00
committed by Dmitriy Novozhilov
parent de105e602d
commit 5d4b44500a
8 changed files with 25 additions and 36 deletions
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList import org.jetbrains.kotlin.fir.expressions.impl.FirResolvedArgumentList
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol 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.FirReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.references.FirThisReference
@@ -71,7 +70,6 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() {
val leakedSymbols = mutableMapOf<FirBasedSymbol<*>, MutableList<KtSourceElement>>() val leakedSymbols = mutableMapOf<FirBasedSymbol<*>, MutableList<KtSourceElement>>()
graph.traverse( graph.traverse(
TraverseDirection.Forward,
CapturedLambdaFinder(function), CapturedLambdaFinder(function),
IllegalScopeContext(functionalTypeEffects.keys, leakedSymbols) IllegalScopeContext(functionalTypeEffects.keys, leakedSymbols)
) )
@@ -163,6 +161,8 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() {
override fun <T> visitUnionNode(node: T, data: IllegalScopeContext) where T : CFGNode<*>, T : UnionNodeMarker {} override fun <T> visitUnionNode(node: T, data: IllegalScopeContext) where T : CFGNode<*>, T : UnionNodeMarker {}
override fun visitFunctionEnterNode(node: FunctionEnterNode, data: IllegalScopeContext) { 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()) data.enterScope(node.fir === rootFunction || node.fir.isInPlaceLambda())
} }
@@ -9,10 +9,7 @@ import org.jetbrains.kotlin.contracts.description.canBeRevisited
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn 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.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.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.utils.isLateInit import org.jetbrains.kotlin.fir.declarations.utils.isLateInit
@@ -38,7 +35,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
) { ) {
val localProperties = properties.filterNotTo(mutableSetOf()) { it.isInitialized() } val localProperties = properties.filterNotTo(mutableSetOf()) { it.isInitialized() }
val reporterVisitor = PropertyReporter(data, localProperties, capturedWrites, reporter, context) val reporterVisitor = PropertyReporter(data, localProperties, capturedWrites, reporter, context)
graph.traverse(TraverseDirection.Forward, reporterVisitor) graph.traverse(reporterVisitor)
} }
private fun FirVariableSymbol<*>.isInitialized(): Boolean { private fun FirVariableSymbol<*>.isInitialized(): Boolean {
@@ -7,32 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
// ------------------------------ Graph Traversal ------------------------------
enum class TraverseDirection { enum class TraverseDirection {
Forward, Backward Forward, Backward
} }
fun <D> 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 <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNode( fun <I : ControlFlowInfo<I, *, *>> ControlFlowGraph.collectDataForNode(
direction: TraverseDirection, direction: TraverseDirection,
visitor: PathAwareControlFlowGraphVisitor<I>, visitor: PathAwareControlFlowGraphVisitor<I>,
@@ -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.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol 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() { class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
companion object { companion object {
fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> { fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
val collector = LocalPropertyAndCapturedWriteCollector() val collector = LocalPropertyAndCapturedWriteCollector()
graph.traverse(TraverseDirection.Forward, collector) graph.traverse(collector)
return collector.symbols.keys to collector.capturedWrites return collector.symbols.keys to collector.capturedWrites
} }
} }
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.descriptors.Visibilities 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.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -34,11 +32,12 @@ object FirTailrecFunctionChecker : FirSimpleFunctionChecker() {
} }
val graph = declaration.controlFlowGraphReference?.controlFlowGraph ?: return 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 tryScopeCount = 0
var catchScopeCount = 0 var catchScopeCount = 0
var finallyScopeCount = 0 var finallyScopeCount = 0
var tailrecCount = 0 var tailrecCount = 0
graph.traverse(TraverseDirection.Forward, object : ControlFlowGraphVisitorVoid() { graph.traverse(object : ControlFlowGraphVisitorVoid() {
override fun visitNode(node: CFGNode<*>) {} override fun visitNode(node: CFGNode<*>) {}
override fun <T> visitUnionNode(node: T) where T : CFGNode<*>, T : UnionNodeMarker {} override fun <T> visitUnionNode(node: T) where T : CFGNode<*>, T : UnionNodeMarker {}
@@ -12,8 +12,6 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker 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.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getChildren import org.jetbrains.kotlin.fir.analysis.checkers.getChildren
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -36,7 +34,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>() val propertiesCharacteristics = mutableMapOf<FirPropertySymbol, EventOccurrencesRange>()
val reporterVisitor = UninitializedPropertyReporter(data, properties, unprocessedProperties, propertiesCharacteristics) val reporterVisitor = UninitializedPropertyReporter(data, properties, unprocessedProperties, propertiesCharacteristics)
graph.traverse(TraverseDirection.Forward, reporterVisitor) graph.traverse(reporterVisitor)
for (property in unprocessedProperties) { for (property in unprocessedProperties) {
val source = property.source val source = property.source
@@ -40,7 +40,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() {
context: CheckerContext context: CheckerContext
) { ) {
val ownData = ValueWritesWithoutReading(context.session, properties).getData(graph) val ownData = ValueWritesWithoutReading(context.session, properties).getData(graph)
graph.traverse(TraverseDirection.Backward, CfaVisitor(ownData, reporter, context)) graph.traverse(CfaVisitor(ownData, reporter, context))
} }
class CfaVisitor( class CfaVisitor(
@@ -72,6 +72,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() {
if (node.fir.source == null) return if (node.fir.source == null) return
if (variableSymbol.isLoopIterator) return if (variableSymbol.isLoopIterator) return
val dataPerNode = data[node] ?: return val dataPerNode = data[node] ?: return
// TODO: merge values for labels, otherwise diagnostics are inconsistent
for (dataPerLabel in dataPerNode.values) { for (dataPerLabel in dataPerNode.values) {
val data = dataPerLabel[variableSymbol] ?: continue val data = dataPerLabel[variableSymbol] ?: continue
@@ -43,6 +43,21 @@ class ControlFlowGraph(val declaration: FirDeclaration?, val name: String, val k
FakeCall, FakeCall,
DefaultArgument, 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 <D> 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( data class Edge(