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:
pyos
2023-01-18 13:25:16 +01:00
committed by teamcity
parent 166ddd9ea9
commit 123b211fc4
@@ -6,11 +6,9 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
import org.jetbrains.kotlin.descriptors.Visibilities
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.PropertyInitializationInfoCollector
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.utils.*
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.controlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.lexer.KtTokens
// See old FE's [DeclarationsChecker]
object FirMemberPropertiesChecker : FirClassChecker() {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val memberPropertySymbols = declaration.declarations.filterIsInstance<FirProperty>().map { it.symbol }.toSet()
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 info = declaration.collectInitializationInfo()
val deadEnds = declaration.collectDeadEndDeclarations()
var reachedDeadEnd = false
for (innerDeclaration in declaration.declarations) {
if (innerDeclaration is FirProperty) {
val symbol = innerDeclaration.symbol
val isInitialized =
innerDeclaration.initializer != null ||
initializedInConstructor.getValue(symbol).isDefinitelyVisited() ||
initializedInInitOrOtherProperty.getValue(symbol).isDefinitelyVisited()
val isInitialized = innerDeclaration.initializer != null || info?.get(symbol)?.isDefinitelyVisited() == true
checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd)
}
reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration)
}
}
private fun collectPropertyInitialization(
klass: FirClass,
session: FirSession,
memberPropertySymbols: Set<FirPropertySymbol>,
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)
private fun FirClass.collectInitializationInfo(): PropertyInitializationInfo? {
val graph = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return null
val memberPropertySymbols = declarations.mapNotNullTo(mutableSetOf()) {
(it as? FirProperty)?.takeIf { fir -> fir.initializer == null }?.symbol
}
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(