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 1dff1665bc3..e86d6fa4e3b 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 @@ -222,9 +222,6 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() { override val constructor: (PersistentMap, EventOccurrencesRange>) -> LambdaInvocationInfo = ::LambdaInvocationInfo - - override val empty: () -> LambdaInvocationInfo = - ::EMPTY } class PathAwareLambdaInvocationInfo( @@ -236,9 +233,6 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() { override val constructor: (PersistentMap) -> PathAwareLambdaInvocationInfo = ::PathAwareLambdaInvocationInfo - - override val empty: () -> PathAwareLambdaInvocationInfo = - ::EMPTY } private class InvocationDataCollector( @@ -249,7 +243,7 @@ object FirCallsEffectAnalyzer : FirControlFlowChecker() { override fun visitFunctionCallNode( node: FunctionCallNode, - data: Collection> + data: PathAwareLambdaInvocationInfo ): PathAwareLambdaInvocationInfo { var dataForNode = visitUnionNode(node, data) 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 774966c0375..0ffa1a5423a 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 @@ -33,10 +33,10 @@ fun ControlFlowGraph.traverse( // ---------------------- Path-sensitive data collection ----------------------- -fun ControlFlowGraph.collectDataForNode( +fun > ControlFlowGraph.collectDataForNode( direction: TraverseDirection, initialInfo: I, - visitor: ControlFlowGraphVisitor>>, + visitor: PathAwareControlFlowGraphVisitor, visitSubGraphs: Boolean = true ): Map, I> { val nodeMap = LinkedHashMap, I>() @@ -51,10 +51,10 @@ fun ControlFlowGraph.collectDataForNode( return nodeMap } -private fun ControlFlowGraph.collectDataForNodeInternal( +private fun > ControlFlowGraph.collectDataForNodeInternal( direction: TraverseDirection, initialInfo: I, - visitor: ControlFlowGraphVisitor>>, + visitor: PathAwareControlFlowGraphVisitor, nodeMap: MutableMap, I>, visitSubGraphs: Boolean = true ): Boolean { @@ -68,18 +68,21 @@ private fun ControlFlowGraph.collectDataForNodeInternal( TraverseDirection.Forward -> node.previousCfgNodes TraverseDirection.Backward -> node.followingCfgNodes } - // One noticeable different against the path-unaware version is, here, we pair the control-flow info with the label. val previousData = previousNodes.mapNotNull { val k = when (direction) { - TraverseDirection.Forward -> node.edgeFrom(it).label - TraverseDirection.Backward -> node.edgeTo(it).label + TraverseDirection.Forward -> node.edgeFrom(it) + TraverseDirection.Backward -> node.edgeTo(it) } val v = nodeMap[it] ?: return@mapNotNull null - k to v + visitor.visitEdge(it, node, k, v) } + val reduced = if (node is UnionNodeMarker) + previousData.reduceOrNull { a, b -> a.plus(b) } + else + previousData.reduceOrNull { a, b -> a.merge(b) } val data = nodeMap[node] - val newData = node.accept(visitor, previousData) + val newData = node.accept(visitor, reduced ?: visitor.emptyInfo) val hasChanged = newData != data changed = changed or hasChanged if (hasChanged) { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/ControlFlowInfo.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/ControlFlowInfo.kt index 1a495d77948..a556d24a9c3 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/ControlFlowInfo.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/ControlFlowInfo.kt @@ -13,8 +13,6 @@ abstract class ControlFlowInfo, K : Any, V : Any> p protected abstract val constructor: (PersistentMap) -> S - protected abstract val empty: () -> S - override fun equals(other: Any?): Boolean { return map == (other as? ControlFlowInfo<*, *, *>)?.map } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowGraphVisitor.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowGraphVisitor.kt index 9aa4aa1f823..9b622778256 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowGraphVisitor.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowGraphVisitor.kt @@ -7,22 +7,16 @@ 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.ControlFlowGraphVisitor -import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.Edge import org.jetbrains.kotlin.fir.resolve.dfa.cfg.UnionNodeMarker -import org.jetbrains.kotlin.utils.addToStdlib.foldMap -abstract class PathAwareControlFlowGraphVisitor

> - : ControlFlowGraphVisitor>>() { +abstract class PathAwareControlFlowGraphVisitor

> : ControlFlowGraphVisitor() { + abstract val emptyInfo: P - protected abstract val emptyInfo: P + open fun visitEdge(from: CFGNode<*>, to: CFGNode<*>, metadata: Edge, data: P): P = + data.applyLabel(to, metadata.label) ?: emptyInfo - override fun visitNode(node: CFGNode<*>, data: Collection>): P { - if (data.isEmpty()) return emptyInfo - return data.foldMap({ (label, info) -> info.applyLabel(node, label) }) { a, b -> a.merge(b) } - } + override fun visitNode(node: CFGNode<*>, data: P): P = data - override fun visitUnionNode(node: T, data: Collection>): P where T : CFGNode<*>, T : UnionNodeMarker { - if (data.isEmpty()) return emptyInfo - return data.foldMap({ (label, info) -> info.applyLabel(node, label) }) { a, b -> a.plus(b) } - } + override fun visitUnionNode(node: T, data: P): P where T : CFGNode<*>, T : UnionNodeMarker = data } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowInfo.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowInfo.kt index 335ea95ed55..6216182865a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowInfo.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PathAwareControlFlowInfo.kt @@ -16,7 +16,7 @@ abstract class PathAwareControlFlowInfo

, S : internal val infoAtNormalPath: S get() = map.getValue(NormalPath) - fun applyLabel(node: CFGNode<*>, label: EdgeLabel): P { + fun applyLabel(node: CFGNode<*>, label: EdgeLabel): P? { if (label.isNormal) { // Special case: when we exit the try expression, null label means a normal path. // Filter out any info bound to non-null label @@ -28,7 +28,7 @@ abstract class PathAwareControlFlowInfo

, S : constructor(persistentMapOf(NormalPath to infoAtNormalPath)) } else { /* This means no info for normal path. */ - empty() + null } } // In general, null label means no additional path info, hence return `this` as-is. @@ -58,7 +58,7 @@ abstract class PathAwareControlFlowInfo

, S : } } else { /* This means no info for the specific label. */ - empty() + null } } else { // { |-> ... } // empty path info diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt index 9fa733b1935..b5046c65be1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt @@ -49,9 +49,6 @@ class PropertyInitializationInfo( override val constructor: (PersistentMap) -> PropertyInitializationInfo = ::PropertyInitializationInfo - - override val empty: () -> PropertyInitializationInfo = - ::EMPTY } class PathAwarePropertyInitializationInfo( @@ -63,7 +60,4 @@ class PathAwarePropertyInitializationInfo( override val constructor: (PersistentMap) -> PathAwarePropertyInitializationInfo = ::PathAwarePropertyInitializationInfo - - override val empty: () -> PathAwarePropertyInitializationInfo = - ::EMPTY } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt index 5b9e23f3d37..7902515a926 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange -import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol @@ -32,7 +31,7 @@ class PropertyInitializationInfoCollector( override fun visitVariableAssignmentNode( node: VariableAssignmentNode, - data: Collection> + data: PathAwarePropertyInitializationInfo ): PathAwarePropertyInitializationInfo { val dataForNode = visitNode(node, data) val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: return dataForNode @@ -45,7 +44,7 @@ class PropertyInitializationInfoCollector( override fun visitVariableDeclarationNode( node: VariableDeclarationNode, - data: Collection> + data: PathAwarePropertyInitializationInfo ): PathAwarePropertyInitializationInfo { val dataForNode = visitNode(node, data) return processVariableWithAssignment( @@ -78,72 +77,35 @@ class PropertyInitializationInfoCollector( // Data flows of declared/assigned variables in loops // -------------------------------------------------- - private fun enterCapturingStatement(statement: FirStatement): Set = - declaredVariableCollector.enterCapturingStatement(statement) - - private fun exitCapturingStatement(statement: FirStatement) { - declaredVariableCollector.exitCapturingStatement(statement) - } - - // A merge point for a loop with `continue` - override fun visitLoopEnterNode( - node: LoopEnterNode, - data: Collection> + override fun visitEdge( + from: CFGNode<*>, + to: CFGNode<*>, + metadata: Edge, + data: PathAwarePropertyInitializationInfo ): PathAwarePropertyInitializationInfo { - val declaredVariableSymbolsInLoop = enterCapturingStatement(node.fir) - if (declaredVariableSymbolsInLoop.isEmpty()) - return visitNode(node, data) - - return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data) - } - - // A merge point for while loop - override fun visitLoopConditionEnterNode( - node: LoopConditionEnterNode, - data: Collection> - ): PathAwarePropertyInitializationInfo { - val declaredVariableSymbolsInLoop = declaredVariableCollector.declaredVariablesPerElement[node.loop] - if (declaredVariableSymbolsInLoop.isEmpty()) - return visitNode(node, data) - - return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data) - } - - // A merge point for do-while loop - override fun visitLoopBlockEnterNode( - node: LoopBlockEnterNode, - data: Collection> - ): PathAwarePropertyInitializationInfo { - val declaredVariableSymbolsInLoop = declaredVariableCollector.declaredVariablesPerElement[node.fir] - if (declaredVariableSymbolsInLoop.isEmpty()) - return visitNode(node, data) - - return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data) - } - - private fun filterDeclaredVariableSymbolsInCapturedScope( - node: CFGNode<*>, - declaredVariableSymbolsInCapturedScope: Collection, - data: Collection> - ): PathAwarePropertyInitializationInfo { - var filteredData = data - for (variableSymbol in declaredVariableSymbolsInCapturedScope) { - filteredData = filteredData.map { (label, pathAwareInfo) -> - label to if (label is LoopBackPath) { - removeRange(pathAwareInfo, variableSymbol, ::PathAwarePropertyInitializationInfo) - } else { - pathAwareInfo - } - } + val result = super.visitEdge(from, to, metadata, data) + if (metadata.label != LoopBackPath) return result + val declaredVariableSymbolsInCapturedScope = when (to) { + is LoopEnterNode -> declaredVariableCollector.declaredVariablesPerElement[to.fir] + is LoopBlockEnterNode -> declaredVariableCollector.declaredVariablesPerElement[to.fir] + is LoopConditionEnterNode -> declaredVariableCollector.declaredVariablesPerElement[to.loop] + else -> return result } - return visitNode(node, filteredData) + return declaredVariableSymbolsInCapturedScope.fold(data) { filteredData, variableSymbol -> + removeRange(filteredData, variableSymbol, ::PathAwarePropertyInitializationInfo) + } + } + + override fun visitLoopEnterNode(node: LoopEnterNode, data: PathAwarePropertyInitializationInfo): PathAwarePropertyInitializationInfo { + declaredVariableCollector.enterCapturingStatement(node.fir) + return visitNode(node, data) } override fun visitLoopExitNode( node: LoopExitNode, - data: Collection> + data: PathAwarePropertyInitializationInfo ): PathAwarePropertyInitializationInfo { - exitCapturingStatement(node.fir) + declaredVariableCollector.exitCapturingStatement(node.fir) return visitNode(node, data) } } 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 a5e40472d82..4527fd2a7af 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 @@ -136,9 +136,6 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override val constructor: (PersistentMap) -> VariableStatusInfo = ::VariableStatusInfo - override val empty: () -> VariableStatusInfo = - ::EMPTY - override fun merge(other: VariableStatusInfo): VariableStatusInfo { var result = this for (symbol in keys.union(other.keys)) { @@ -163,9 +160,6 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override val constructor: (PersistentMap) -> PathAwareVariableStatusInfo = ::PathAwareVariableStatusInfo - - override val empty: () -> PathAwareVariableStatusInfo = - ::EMPTY } private class ValueWritesWithoutReading( @@ -184,19 +178,19 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override fun visitNode( node: CFGNode<*>, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo = super.visitNode(node, data).withAnnotationsFrom(node) override fun visitUnionNode( node: T, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo where T : CFGNode<*>, T : UnionNodeMarker = super.visitUnionNode(node, data).withAnnotationsFrom(node) override fun visitVariableDeclarationNode( node: VariableDeclarationNode, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo { val dataForNode = visitNode(node, data) if (node.fir.source?.kind is KtFakeSourceElementKind) return dataForNode @@ -228,7 +222,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override fun visitVariableAssignmentNode( node: VariableAssignmentNode, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo { val dataForNode = visitNode(node, data) val symbol = node.fir.lValue.toResolvedPropertySymbol() ?: return dataForNode @@ -257,7 +251,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override fun visitQualifiedAccessNode( node: QualifiedAccessNode, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo { val dataForNode = visitNode(node, data) return visitQualifiedAccesses(dataForNode, node.fir) @@ -293,7 +287,7 @@ object UnusedChecker : AbstractFirPropertyInitializationChecker() { override fun visitFunctionCallNode( node: FunctionCallNode, - data: Collection> + data: PathAwareVariableStatusInfo ): PathAwareVariableStatusInfo { val dataForNode = visitUnionNode(node, data) val reference = node.fir.calleeReference.resolved ?: return dataForNode