FIR: simplify FirMemberPropertiesChecker
Now that there are correct control flow edges between constructors, collecting the data is as simple as going over the entire initialization graph.
This commit is contained in:
+9
-112
@@ -6,11 +6,9 @@
|
|||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
|
||||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.fir.FirElement
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfo
|
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfo
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoCollector
|
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoCollector
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
||||||
@@ -23,135 +21,34 @@ import org.jetbrains.kotlin.fir.declarations.*
|
|||||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
|
||||||
// See old FE's [DeclarationsChecker]
|
// See old FE's [DeclarationsChecker]
|
||||||
object FirMemberPropertiesChecker : FirClassChecker() {
|
object FirMemberPropertiesChecker : FirClassChecker() {
|
||||||
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
val memberPropertySymbols = declaration.declarations.filterIsInstance<FirProperty>().map { it.symbol }.toSet()
|
val info = declaration.collectInitializationInfo()
|
||||||
val initializedInConstructor =
|
|
||||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
|
||||||
val initializedInInitOrOtherProperty =
|
|
||||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
|
||||||
|
|
||||||
// If all member properties have its own initializer, we don't need to collect property initialization info at all.
|
|
||||||
if (memberPropertySymbols.any { !it.hasInitializer }) {
|
|
||||||
collectPropertyInitialization(
|
|
||||||
declaration,
|
|
||||||
context.session,
|
|
||||||
memberPropertySymbols,
|
|
||||||
initializedInConstructor,
|
|
||||||
initializedInInitOrOtherProperty
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val deadEnds = declaration.collectDeadEndDeclarations()
|
val deadEnds = declaration.collectDeadEndDeclarations()
|
||||||
var reachedDeadEnd = false
|
var reachedDeadEnd = false
|
||||||
for (innerDeclaration in declaration.declarations) {
|
for (innerDeclaration in declaration.declarations) {
|
||||||
if (innerDeclaration is FirProperty) {
|
if (innerDeclaration is FirProperty) {
|
||||||
val symbol = innerDeclaration.symbol
|
val symbol = innerDeclaration.symbol
|
||||||
val isInitialized =
|
val isInitialized = innerDeclaration.initializer != null || info?.get(symbol)?.isDefinitelyVisited() == true
|
||||||
innerDeclaration.initializer != null ||
|
|
||||||
initializedInConstructor.getValue(symbol).isDefinitelyVisited() ||
|
|
||||||
initializedInInitOrOtherProperty.getValue(symbol).isDefinitelyVisited()
|
|
||||||
checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd)
|
checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd)
|
||||||
}
|
}
|
||||||
reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration)
|
reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectPropertyInitialization(
|
private fun FirClass.collectInitializationInfo(): PropertyInitializationInfo? {
|
||||||
klass: FirClass,
|
val graph = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return null
|
||||||
session: FirSession,
|
val memberPropertySymbols = declarations.mapNotNullTo(mutableSetOf()) {
|
||||||
memberPropertySymbols: Set<FirPropertySymbol>,
|
(it as? FirProperty)?.takeIf { fir -> fir.initializer == null }?.symbol
|
||||||
initializedInConstructor: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
|
||||||
initializedInInitOrOtherProperty: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
|
||||||
) {
|
|
||||||
// A property is known to be initialized only if it is initialized
|
|
||||||
// 1) with its own initializing expression;
|
|
||||||
// 2) at every class constructor;
|
|
||||||
// 3) at any of class's anonymous initializers; or
|
|
||||||
// 4) at other property's initializing expression
|
|
||||||
|
|
||||||
// 2) Property can be initialized at constructors. Since it's unknown what constructor will be used, the property can be determined
|
|
||||||
// as initialized only if it is initialized at every constructor. We should consider a delegated constructor, e.g.,
|
|
||||||
// constructor() { x = ... }
|
|
||||||
// constructor(...): this() { ... } // x will be initialized via this() delegation
|
|
||||||
// We need to topologically sort constructors so that we can process delegated ones before the use sites.
|
|
||||||
|
|
||||||
// 3) Property can be initialized at any of class's anonymous initializers (all of initializers will be executed), e.g.,
|
|
||||||
// init { x = ... }
|
|
||||||
// ...
|
|
||||||
// init { y = ... }
|
|
||||||
|
|
||||||
// 4) Property can be initialized at other property's initializing expression too, e.g.,
|
|
||||||
// val initX = inlineMe { x = ... } // where inlineMe returns the value of the last expression of the lambda
|
|
||||||
|
|
||||||
// To handle the delegated constructor call, we need a cache from constructor to (analyzed) property init info.
|
|
||||||
val constructorToData =
|
|
||||||
mutableMapOf<FirConstructorSymbol, PropertyInitializationInfo>().withDefault { PropertyInitializationInfo.EMPTY }
|
|
||||||
|
|
||||||
fun collectInfoFromGraph(
|
|
||||||
graph: ControlFlowGraph,
|
|
||||||
map: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
|
||||||
acc: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange,
|
|
||||||
delegatedConstructor: FirConstructorSymbol? = null,
|
|
||||||
) {
|
|
||||||
val delegatedInfo = delegatedConstructor?.let { constructorToData.getValue(it) } ?: PropertyInitializationInfo.EMPTY
|
|
||||||
|
|
||||||
val data = PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)
|
|
||||||
val infoAtExitNode = data[graph.exitNode]?.get(NormalPath) ?: PropertyInitializationInfo.EMPTY
|
|
||||||
|
|
||||||
// NB: it's not [merge], which is conducted at merging points, such as loop condition or when conditions.
|
|
||||||
// Rather, delegated constructor call is the predecessor of the current constructor call, so we should accumulate.
|
|
||||||
val info = delegatedInfo.plus(infoAtExitNode)
|
|
||||||
|
|
||||||
if (graph.declaration is FirConstructor) {
|
|
||||||
constructorToData.putIfAbsent((graph.declaration as FirConstructor).symbol, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (propertySymbol in memberPropertySymbols) {
|
|
||||||
val item = map[propertySymbol]
|
|
||||||
if (item != null) {
|
|
||||||
// Accumulation:
|
|
||||||
// range join for class constructors, range plus for class's anonymous initializers and property initializations
|
|
||||||
map[propertySymbol] = acc.invoke(item, info[propertySymbol] ?: EventOccurrencesRange.ZERO)
|
|
||||||
} else {
|
|
||||||
// Initial assignment.
|
|
||||||
// NB: we should not use `acc` here to not weaken ranges. For example, if we visit one and only constructor where
|
|
||||||
// a property of interest is correctly initialized (a.k.a. [EXACTLY_ONCE]), and if `acc` is ...Range::or,
|
|
||||||
// merging with the default [ZERO] makes the result [AT_MOST_ONCE], which will be regarded as uninitialized.
|
|
||||||
map[propertySymbol] = info[propertySymbol] ?: EventOccurrencesRange.ZERO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val constructorGraphs = klass.constructorsSortedByDelegation(session).mapNotNull {
|
|
||||||
it.resolvedControlFlowGraphReference?.controlFlowGraph
|
|
||||||
}
|
|
||||||
for (graph in constructorGraphs) {
|
|
||||||
collectInfoFromGraph(
|
|
||||||
graph,
|
|
||||||
initializedInConstructor,
|
|
||||||
EventOccurrencesRange::or,
|
|
||||||
(graph.declaration as? FirConstructor)?.symbol?.delegatedThisConstructor
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val initGraphs = klass.anonymousInitializers.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
|
||||||
for (graph in initGraphs) {
|
|
||||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
|
||||||
}
|
|
||||||
|
|
||||||
val propertyInitGraphs = memberPropertySymbols.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
|
||||||
for (graph in propertyInitGraphs) {
|
|
||||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
|
||||||
}
|
}
|
||||||
|
if (memberPropertySymbols.isEmpty()) return null
|
||||||
|
// TODO: this also visits non-constructor member functions...
|
||||||
|
return PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)[graph.exitNode]?.get(NormalPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkProperty(
|
private fun checkProperty(
|
||||||
|
|||||||
Reference in New Issue
Block a user