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:
+2
-2
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -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>,
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -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 {}
|
||||||
|
|||||||
+1
-3
@@ -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
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
|
|
||||||
|
|||||||
+15
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user