FIR: check assignments and references to members in constructors
I.e. emit VAL_REASSIGNMENT on repeated assignments to `this.something`, UNINITIALIZED_VARIABLE on reads of it before any assignment if there is no initializer, and CAPTURED_MEMBER_VAL_INITIALIZATION on assignments inside non-called-in-place functions and named classes. ^KT-55528 Fixed
This commit is contained in:
Vendored
+5
-6
@@ -10,7 +10,7 @@ class Some {
|
||||
|
||||
init {
|
||||
x = "ok"
|
||||
x = "error"
|
||||
<!VAL_REASSIGNMENT!>x<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||
}
|
||||
|
||||
@@ -24,17 +24,16 @@ class Some {
|
||||
|
||||
val b: String = 123.run {
|
||||
// now this@run is an Int, so these are on this@Some
|
||||
x = "error"
|
||||
<!VAL_REASSIGNMENT!>x<!> = "error"
|
||||
y = "ok"
|
||||
y = "error"
|
||||
<!VAL_REASSIGNMENT!>y<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||
"there"
|
||||
}
|
||||
|
||||
init {
|
||||
x = "error"
|
||||
y = "error"
|
||||
<!VAL_REASSIGNMENT!>x<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>y<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ fun test2() {
|
||||
}
|
||||
|
||||
fun localFunc() {
|
||||
y = 0
|
||||
<!CAPTURED_VAL_INITIALIZATION!>y<!> = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ fun test3() {
|
||||
|
||||
class A {
|
||||
init {
|
||||
x = 0
|
||||
<!CAPTURED_VAL_INITIALIZATION!>x<!> = 0
|
||||
}
|
||||
|
||||
fun localFunc() {
|
||||
y = 0
|
||||
<!CAPTURED_VAL_INITIALIZATION!>y<!> = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-64
@@ -5,25 +5,15 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.cfa
|
||||
|
||||
import org.jetbrains.kotlin.contracts.description.canBeRevisited
|
||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoData
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isLateInit
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.checkPropertyAccesses
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.requiresInitialization
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.isCatchParameter
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChecker() {
|
||||
override fun analyze(
|
||||
graph: ControlFlowGraph,
|
||||
@@ -32,55 +22,5 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
properties: Set<FirPropertySymbol>,
|
||||
capturedWrites: Set<FirVariableAssignment>,
|
||||
context: CheckerContext
|
||||
) {
|
||||
val localProperties = properties.filterNotTo(mutableSetOf()) { it.isInitialized() }
|
||||
val reporterVisitor = PropertyReporter(data, localProperties, capturedWrites, reporter, context)
|
||||
graph.traverse(reporterVisitor)
|
||||
}
|
||||
|
||||
private fun FirVariableSymbol<*>.isInitialized(): Boolean {
|
||||
return fir.initializer != null
|
||||
|| fir.delegate != null
|
||||
|| this is FirPropertySymbol && fir.isCatchParameter == true
|
||||
}
|
||||
|
||||
private class PropertyReporter(
|
||||
val data: PropertyInitializationInfoData,
|
||||
val localProperties: Set<FirPropertySymbol>,
|
||||
val capturedWrites: Set<FirVariableAssignment>,
|
||||
val reporter: DiagnosticReporter,
|
||||
val context: CheckerContext
|
||||
) : ControlFlowGraphVisitorVoid() {
|
||||
override fun visitNode(node: CFGNode<*>) {
|
||||
// TODO: `node.isUnion` - f({ x = 1 }, { x = 2 }) - which to report?
|
||||
// Also this is currently indistinguishable from x = 1; f({}, {}).
|
||||
}
|
||||
|
||||
private val CFGNode<*>.propertySymbol: FirPropertySymbol?
|
||||
get() = (fir as? FirQualifiedAccess)?.calleeReference?.toResolvedPropertySymbol()
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
val symbol = node.propertySymbol ?: return
|
||||
if (!symbol.fir.isVal) return
|
||||
|
||||
if (node.fir in capturedWrites) {
|
||||
if (symbol.fir.isLocal) {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_VAL_INITIALIZATION, symbol, context)
|
||||
} else {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION, symbol, context)
|
||||
}
|
||||
} else if (data.getValue(node).values.any { it[symbol]?.canBeRevisited() == true }) {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.VAL_REASSIGNMENT, symbol, context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
|
||||
val symbol = node.propertySymbol ?: return
|
||||
if (symbol in localProperties && !symbol.fir.isLateInit && symbol !is FirSyntheticPropertySymbol &&
|
||||
!data.getValue(node).values.all { it[symbol]?.isDefinitelyVisited() == true }
|
||||
) {
|
||||
reporter.reportOn(node.fir.source, FirErrors.UNINITIALIZED_VARIABLE, symbol, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
) = graph.checkPropertyAccesses(properties.filterTo(mutableSetOf()) { it.requiresInitialization }, null, context, reporter, data)
|
||||
}
|
||||
|
||||
+2
-5
@@ -13,9 +13,9 @@ import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
|
||||
class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, graph: ControlFlowGraph) {
|
||||
class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, receiver: FirBasedSymbol<*>?, graph: ControlFlowGraph) {
|
||||
private val data by lazy(LazyThreadSafetyMode.NONE) {
|
||||
PropertyInitializationInfoCollector(properties).getData(graph)
|
||||
graph.collectDataForNode(TraverseDirection.Forward, PropertyInitializationInfoCollector(properties, receiver))
|
||||
}
|
||||
|
||||
fun getValue(node: CFGNode<*>): PathAwarePropertyInitializationInfo {
|
||||
@@ -28,9 +28,6 @@ class PropertyInitializationInfoCollector(
|
||||
private val expectedReceiver: FirBasedSymbol<*>? = null,
|
||||
private val declaredVariableCollector: DeclaredVariableCollector = DeclaredVariableCollector(),
|
||||
) : PathAwareControlFlowGraphVisitor<PropertyInitializationInfo>() {
|
||||
fun getData(graph: ControlFlowGraph) =
|
||||
graph.collectDataForNode(TraverseDirection.Forward, this)
|
||||
|
||||
companion object {
|
||||
private val EMPTY_INFO: PathAwarePropertyInitializationInfo = persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY)
|
||||
}
|
||||
|
||||
+113
-8
@@ -6,28 +6,38 @@
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.contracts.description.canBeRevisited
|
||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||
import org.jetbrains.kotlin.contracts.description.isInPlace
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
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
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoData
|
||||
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.NormalPath
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
|
||||
import org.jetbrains.kotlin.fir.isCatchParameter
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
// See old FE's [DeclarationsChecker]
|
||||
object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val info = declaration.collectInitializationInfo()
|
||||
val info = declaration.collectInitializationInfo(context, reporter)
|
||||
var reachedDeadEnd =
|
||||
(declaration as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph?.enterNode?.isDead == true
|
||||
for (innerDeclaration in declaration.declarations) {
|
||||
@@ -42,15 +52,17 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirClass.collectInitializationInfo(): PropertyInitializationInfo? {
|
||||
private fun FirClass.collectInitializationInfo(context: CheckerContext, reporter: DiagnosticReporter): PropertyInitializationInfo? {
|
||||
val graph = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return null
|
||||
val memberPropertySymbols = declarations.mapNotNullTo(mutableSetOf()) {
|
||||
(it as? FirProperty)?.takeIf { fir -> fir.initializer == null }?.symbol
|
||||
(it.symbol as? FirPropertySymbol)?.takeIf { symbol -> symbol.requiresInitialization }
|
||||
}
|
||||
if (memberPropertySymbols.isEmpty()) return null
|
||||
// TODO: this also visits non-constructor member functions...
|
||||
// TODO: also use FirPropertyInitializationAnalyzer.PropertyReporter to report reassignments or use-before-initialization
|
||||
return PropertyInitializationInfoCollector(memberPropertySymbols, symbol).getData(graph)[graph.exitNode]?.get(NormalPath)
|
||||
// TODO: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
||||
val data = PropertyInitializationInfoData(memberPropertySymbols, symbol, graph)
|
||||
graph.checkPropertyAccesses(memberPropertySymbols, symbol, context, reporter, data)
|
||||
return data.getValue(graph.exitNode)[NormalPath]
|
||||
}
|
||||
|
||||
private fun checkProperty(
|
||||
@@ -124,3 +136,96 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val FirDeclaration.evaluatedInPlace: Boolean
|
||||
get() = when (this) {
|
||||
is FirAnonymousFunction -> invocationKind.isInPlace
|
||||
is FirAnonymousObject -> classKind != ClassKind.ENUM_ENTRY
|
||||
is FirConstructor -> true // child of class initialization graph
|
||||
is FirFunction, is FirClass -> false
|
||||
else -> true // property initializer, etc.
|
||||
}
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
val FirPropertySymbol.requiresInitialization: Boolean
|
||||
get() = this !is FirSyntheticPropertySymbol && !hasInitializer && !hasExplicitBackingField &&
|
||||
hasBackingField && fir.isCatchParameter != true
|
||||
|
||||
fun ControlFlowGraph.checkPropertyAccesses(
|
||||
properties: Set<FirPropertySymbol>,
|
||||
receiver: FirBasedSymbol<*>?,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter,
|
||||
data: PropertyInitializationInfoData
|
||||
) {
|
||||
// NOTE: assert(properties.all { it.requiresInitialization })
|
||||
// If a property has an initializer (or does not need one), then any reads are OK while any writes are OK
|
||||
// if it's a `var` and bad if it's a `val`. `FirReassignmentAndInvisibleSetterChecker` does this without a CFG.
|
||||
if (properties.isEmpty()) return
|
||||
|
||||
checkPropertyAccesses(properties, receiver, context, reporter, data, null, mutableMapOf())
|
||||
}
|
||||
|
||||
@OptIn(SymbolInternals::class)
|
||||
private fun ControlFlowGraph.checkPropertyAccesses(
|
||||
properties: Set<FirPropertySymbol>,
|
||||
receiver: FirBasedSymbol<*>?,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter,
|
||||
data: PropertyInitializationInfoData,
|
||||
scope: FirDeclaration?,
|
||||
scopes: MutableMap<FirPropertySymbol, FirDeclaration?>,
|
||||
) {
|
||||
fun FirQualifiedAccess.hasCorrectReceiver() =
|
||||
(dispatchReceiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol == receiver
|
||||
|
||||
for (node in nodes) {
|
||||
when {
|
||||
// TODO: `node.isUnion` - f({ x = 1 }, { x = 2 }) - which to report?
|
||||
// Also this is currently indistinguishable from x = 1; f({}, {}).
|
||||
|
||||
node is VariableDeclarationNode -> {
|
||||
val symbol = node.fir.symbol
|
||||
if (scope != null && receiver == null && node.fir.isVal && symbol in properties) {
|
||||
// It's OK to initialize this variable from a nested called-in-place function, but not from
|
||||
// a non-called-in-place function or a non-anonymous-object class initializer.
|
||||
scopes[symbol] = scope
|
||||
}
|
||||
}
|
||||
|
||||
node is VariableAssignmentNode -> {
|
||||
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: continue
|
||||
if (!symbol.fir.isVal || !node.fir.hasCorrectReceiver() || symbol !in properties) continue
|
||||
|
||||
if (scope != scopes[symbol]) {
|
||||
val error = if (receiver != null)
|
||||
FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION
|
||||
else
|
||||
FirErrors.CAPTURED_VAL_INITIALIZATION
|
||||
reporter.reportOn(node.fir.lValue.source, error, symbol, context)
|
||||
} else if (data.getValue(node).values.any { it[symbol]?.canBeRevisited() == true }) {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.VAL_REASSIGNMENT, symbol, context)
|
||||
}
|
||||
}
|
||||
|
||||
node is QualifiedAccessNode -> {
|
||||
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: continue
|
||||
if (!symbol.isLateInit && node.fir.hasCorrectReceiver() && symbol in properties &&
|
||||
data.getValue(node).values.any { it[symbol]?.isDefinitelyVisited() != true }
|
||||
) {
|
||||
reporter.reportOn(node.fir.source, FirErrors.UNINITIALIZED_VARIABLE, symbol, context)
|
||||
}
|
||||
}
|
||||
|
||||
// In the class case, subgraphs of the exit node are member functions, which are considered to not
|
||||
// be part of initialization, so any val is considered to be initialized there and the CFG is not
|
||||
// needed. The errors on reassignments will be emitted by `FirReassignmentAndInvisibleSetterChecker`.
|
||||
node is CFGNodeWithSubgraphs<*> && (receiver == null || node !== exitNode) -> {
|
||||
for (subGraph in node.subGraphs) {
|
||||
val newScope = subGraph.declaration?.takeIf { !it.evaluatedInPlace } ?: scope
|
||||
subGraph.checkPropertyAccesses(properties, receiver, context, reporter, data, newScope, scopes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-42
@@ -10,15 +10,13 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.evaluatedInPlace
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.requiresInitialization
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.containingClassLookupTag
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
|
||||
import org.jetbrains.kotlin.fir.declarations.FirConstructor
|
||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
@@ -29,12 +27,8 @@ import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedValueParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||
import org.jetbrains.kotlin.fir.visibilityChecker
|
||||
|
||||
object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker() {
|
||||
@@ -43,7 +37,7 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
||||
checkValReassignmentViaBackingField(expression, context, reporter)
|
||||
checkValReassignmentOnValueParameter(expression, context, reporter)
|
||||
checkAssignmentToThis(expression, context, reporter)
|
||||
checkValReassignmentOfNonLocalProperty(expression, context, reporter)
|
||||
checkValReassignment(expression, context, reporter)
|
||||
}
|
||||
|
||||
private fun checkInvisibleSetter(
|
||||
@@ -127,39 +121,30 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValReassignmentOfNonLocalProperty(
|
||||
expression: FirVariableAssignment,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
private fun checkValReassignment(expression: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val property = expression.lValue.toResolvedPropertySymbol() ?: return
|
||||
if (property.isLocal || property.isVar) return
|
||||
val isLegal = when {
|
||||
property is FirSyntheticPropertySymbol -> false
|
||||
property.hasDelegate || !property.hasBackingField -> false
|
||||
property.hasInitializer -> false
|
||||
else -> {
|
||||
val thisReceiverSymbol = (expression.dispatchReceiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol
|
||||
thisReceiverSymbol != null && isInsideInitializationOfClass(thisReceiverSymbol, context)
|
||||
if (property.isVar) return
|
||||
// Assignments of uninitialized `val`s must be checked via CFG, since the first one is OK.
|
||||
// See `FirPropertyInitializationAnalyzer` for locals and `FirMemberPropertiesChecker` for backing fields in initializers.
|
||||
if (property.requiresInitialization && (property.isLocal || isInOwnersInitializer(expression.dispatchReceiver, context))) return
|
||||
|
||||
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
||||
}
|
||||
|
||||
private fun isInOwnersInitializer(receiver: FirExpression, context: CheckerContext): Boolean {
|
||||
val uninitializedThisSymbol = (receiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol ?: return false
|
||||
var foundInitializer = false
|
||||
for ((i, declaration) in context.containingDeclarations.withIndex()) {
|
||||
if (declaration is FirClass) {
|
||||
foundInitializer = if (context.containingDeclarations.getOrNull(i + 1)?.evaluatedInPlace == false) {
|
||||
// In member function of a class, assume all outer classes are already initialized
|
||||
// by the time this function is called.
|
||||
false
|
||||
} else {
|
||||
foundInitializer || declaration.symbol == uninitializedThisSymbol
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isLegal) {
|
||||
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInsideInitializationOfClass(classSymbol: FirBasedSymbol<*>, context: CheckerContext): Boolean {
|
||||
val session = context.session
|
||||
for (containingDeclaration in context.containingDeclarations) {
|
||||
val containingClassSymbol = when (containingDeclaration) {
|
||||
is FirProperty -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
|
||||
is FirAnonymousInitializer -> containingDeclaration.dispatchReceiverType?.toSymbol(session)
|
||||
is FirConstructor -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
|
||||
else -> null
|
||||
} ?: continue
|
||||
|
||||
if (containingClassSymbol == classSymbol) return true
|
||||
}
|
||||
return false
|
||||
return foundInitializer
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ class ControlFlowAnalysisDiagnosticComponent(
|
||||
|
||||
val (properties, capturedWrites) = LocalPropertyAndCapturedWriteCollector.collect(graph)
|
||||
if (properties.isNotEmpty()) {
|
||||
val data = PropertyInitializationInfoData(properties, graph)
|
||||
val data = PropertyInitializationInfoData(properties, receiver = null, graph)
|
||||
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties, capturedWrites, context) }
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -134,7 +134,7 @@ class AnonymousInitializers(var a: String, val b: String) {
|
||||
get() = 20
|
||||
|
||||
init {
|
||||
i = 13
|
||||
<!VAL_REASSIGNMENT!>i<!> = 13
|
||||
<!VAL_REASSIGNMENT!>j<!> = 34
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class AnonymousInitializers(var a: String, val b: String) {
|
||||
val n: Int
|
||||
|
||||
init {
|
||||
while (n == 0) {
|
||||
while (<!UNINITIALIZED_VARIABLE!>n<!> == 0) {
|
||||
}
|
||||
n = 10
|
||||
while (n == 0) {
|
||||
|
||||
Vendored
+3
-3
@@ -7,7 +7,7 @@ class Test {
|
||||
val t = object {
|
||||
fun some() {
|
||||
// See KT-13597
|
||||
a = "12"
|
||||
<!VAL_REASSIGNMENT!>a<!> = "12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class Test2 {
|
||||
init {
|
||||
val t = object {
|
||||
fun some() {
|
||||
a = "12"
|
||||
<!VAL_REASSIGNMENT!>a<!> = "12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class Test4 {
|
||||
init {
|
||||
exec {
|
||||
// See KT-14381
|
||||
a = "12"
|
||||
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>a<!> = "12"
|
||||
}
|
||||
a = "34"
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ fun gav() {
|
||||
class B {
|
||||
init {
|
||||
// Error! See KT-10445
|
||||
x = ""
|
||||
<!CAPTURED_VAL_INITIALIZATION!>x<!> = ""
|
||||
}
|
||||
}
|
||||
// Error! See KT-10042
|
||||
@@ -36,7 +36,7 @@ fun gav() {
|
||||
class C(val s: String) {
|
||||
constructor(): this("") {
|
||||
// Error!
|
||||
y = s
|
||||
<!CAPTURED_VAL_INITIALIZATION!>y<!> = s
|
||||
}
|
||||
}
|
||||
<!UNINITIALIZED_VARIABLE!>y<!>.length
|
||||
@@ -77,7 +77,7 @@ class My {
|
||||
class Your {
|
||||
init {
|
||||
// Error! See KT-10445
|
||||
x = ""
|
||||
<!CAPTURED_VAL_INITIALIZATION!>x<!> = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ class Test {
|
||||
|
||||
run {
|
||||
// Not sure do we need diagnostic also here
|
||||
this@Test.str = "B"
|
||||
this@Test.<!VAL_REASSIGNMENT!>str<!> = "B"
|
||||
}
|
||||
|
||||
str = "C"
|
||||
<!VAL_REASSIGNMENT!>str<!> = "C"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ class Test {
|
||||
|
||||
run {
|
||||
// Not sure do we need diagnostic also here
|
||||
this@Test.str = "B"
|
||||
this@Test.<!VAL_REASSIGNMENT!>str<!> = "B"
|
||||
}
|
||||
|
||||
str = "C"
|
||||
<!VAL_REASSIGNMENT!>str<!> = "C"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -3,9 +3,9 @@ class A(val next: A? = null) {
|
||||
init {
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||
x = "b"
|
||||
this.x = "c"
|
||||
x = "d" // don't repeat the same diagnostic again with this receiver
|
||||
this.x = "e"
|
||||
this.<!VAL_REASSIGNMENT!>x<!> = "c"
|
||||
<!VAL_REASSIGNMENT!>x<!> = "d" // don't repeat the same diagnostic again with this receiver
|
||||
this.<!VAL_REASSIGNMENT!>x<!> = "e"
|
||||
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "f"
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -11,9 +11,9 @@ class Outer {
|
||||
outer.<!VAL_REASSIGNMENT!>outerProp<!> = "3"
|
||||
|
||||
innerProp = "4" + inner.innerProp
|
||||
this@Inner.innerProp = "5"
|
||||
innerProp = "6" // do not repeat the same diagnostic with this receiver
|
||||
this@Inner.innerProp = "7"
|
||||
this@Inner.<!VAL_REASSIGNMENT!>innerProp<!> = "5"
|
||||
<!VAL_REASSIGNMENT!>innerProp<!> = "6" // do not repeat the same diagnostic with this receiver
|
||||
this@Inner.<!VAL_REASSIGNMENT!>innerProp<!> = "7"
|
||||
|
||||
inner.<!VAL_REASSIGNMENT!>innerProp<!> = "8"
|
||||
}
|
||||
|
||||
+3
-3
@@ -3,9 +3,9 @@ class A(val next: A? = null) {
|
||||
init {
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||
this@A.x = "b"
|
||||
this.x = "c"
|
||||
x = "d" // don't repeat the same diagnostic again with this receiver
|
||||
this@A.x = "e"
|
||||
this.<!VAL_REASSIGNMENT!>x<!> = "c"
|
||||
<!VAL_REASSIGNMENT!>x<!> = "d" // don't repeat the same diagnostic again with this receiver
|
||||
this@A.<!VAL_REASSIGNMENT!>x<!> = "e"
|
||||
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "f"
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ class A(val w: Char) {
|
||||
constructor(): this('a') {
|
||||
y = 1
|
||||
|
||||
overinitialized = 2
|
||||
<!VAL_REASSIGNMENT!>overinitialized<!> = 2
|
||||
uninitialized = 3
|
||||
}
|
||||
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
class A {
|
||||
val x: Int
|
||||
var y: Int
|
||||
val z: Int
|
||||
val v = -1
|
||||
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val uninitialized: Int<!>
|
||||
val overinitialized: Int
|
||||
|
||||
constructor() {
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
overinitialized = 3
|
||||
uninitialized = 4
|
||||
}
|
||||
|
||||
constructor(a: Int): super() {
|
||||
x = 5
|
||||
y = 6
|
||||
}
|
||||
|
||||
constructor(x: String): this() {
|
||||
y = 7
|
||||
uninitialized = 8
|
||||
}
|
||||
|
||||
//anonymous
|
||||
init {
|
||||
z = 9
|
||||
overinitialized = 10
|
||||
}
|
||||
|
||||
// anonymous
|
||||
init {
|
||||
y = 12
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
class A {
|
||||
val x: Int
|
||||
|
||||
Vendored
+7
-7
@@ -1,8 +1,8 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
class A(val w: Int) {
|
||||
val x: Int
|
||||
val useUnitialized = x +
|
||||
y +
|
||||
val useUnitialized = <!UNINITIALIZED_VARIABLE!>x<!> +
|
||||
<!UNINITIALIZED_VARIABLE!>y<!> +
|
||||
v
|
||||
var y: Int
|
||||
val v = -1
|
||||
@@ -11,20 +11,20 @@ class A(val w: Int) {
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val uninitialized: Int<!>
|
||||
|
||||
constructor(): this(1) {
|
||||
x + y + v + uninitialized + w
|
||||
x + y + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!> + w
|
||||
}
|
||||
|
||||
// anonymous
|
||||
init {
|
||||
x + y + v + uninitialized + w
|
||||
<!UNINITIALIZED_VARIABLE!>x<!> + <!UNINITIALIZED_VARIABLE!>y<!> + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!> + w
|
||||
x = 1
|
||||
x + y + v + uninitialized + w
|
||||
x + <!UNINITIALIZED_VARIABLE!>y<!> + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!> + w
|
||||
}
|
||||
|
||||
// anonymous
|
||||
init {
|
||||
x + y + v + uninitialized + w
|
||||
x + <!UNINITIALIZED_VARIABLE!>y<!> + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!> + w
|
||||
y = 7
|
||||
x + y + v + uninitialized + w
|
||||
x + y + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!> + w
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+6
-6
@@ -1,8 +1,8 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
class A {
|
||||
val x: Int
|
||||
val useUnitialized = x + // reported on each secondary constructor
|
||||
y +
|
||||
val useUnitialized = <!UNINITIALIZED_VARIABLE!>x<!> + // reported on each secondary constructor
|
||||
<!UNINITIALIZED_VARIABLE!>y<!> +
|
||||
v
|
||||
var y: Int
|
||||
val v = -1
|
||||
@@ -19,7 +19,7 @@ class A {
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
x + y + v + uninitialized
|
||||
x + y + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!>
|
||||
|
||||
uninitialized = 3
|
||||
|
||||
@@ -27,16 +27,16 @@ class A {
|
||||
}
|
||||
|
||||
constructor(a: Int): super() {
|
||||
x + y + v + uninitialized
|
||||
<!UNINITIALIZED_VARIABLE!>x<!> + y + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!>
|
||||
x = 4
|
||||
y = 5
|
||||
|
||||
x + y + v + uninitialized
|
||||
x + y + v + <!UNINITIALIZED_VARIABLE!>uninitialized<!>
|
||||
}
|
||||
|
||||
//anonymous
|
||||
init {
|
||||
y
|
||||
<!UNINITIALIZED_VARIABLE!>y<!>
|
||||
}
|
||||
|
||||
// anonymous
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ class DefiniteInitializationInInitSection {
|
||||
|
||||
init {
|
||||
myRun { x = 42 }
|
||||
unknownRun { y = 239 }
|
||||
unknownRun { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>y<!> = 239 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ class case_5 {
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var value_5: Int<!>
|
||||
init {
|
||||
funWithAtMostOnceCallsInPlace { value_1 = 1 }
|
||||
funWithUnknownCallsInPlace { value_2 = 1 }
|
||||
funWithAtLeastOnceCallsInPlace { value_3 = 1 }
|
||||
funWithUnknownCallsInPlace { <!VAL_REASSIGNMENT!>value_2<!> = 1 }
|
||||
funWithAtLeastOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_3<!> = 1 }
|
||||
funWithAtMostOnceCallsInPlace { value_4 = 2 }
|
||||
funWithUnknownCallsInPlace { value_5 = 3 }
|
||||
}
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@ class case_3(value_1: Any?) {
|
||||
init {
|
||||
if (value_1 is String) {
|
||||
funWithUnknownCallsInPlace { value_2 = 0 }
|
||||
value_2.div(10)
|
||||
<!UNINITIALIZED_VARIABLE!>value_2<!>.div(10)
|
||||
} else if (value_1 == null) {
|
||||
funWithAtLeastOnceCallsInPlace { value_2 = 1 }
|
||||
value_2.div(10)
|
||||
@@ -47,7 +47,7 @@ class case_3(value_1: Any?) {
|
||||
value_2 = 2
|
||||
}
|
||||
|
||||
value_2.div(10)
|
||||
<!UNINITIALIZED_VARIABLE!>value_2<!>.div(10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ fun case_4() {
|
||||
fun case_5() {
|
||||
val value_1: Int
|
||||
val o = object {
|
||||
fun l() { value_1 = 10 }
|
||||
fun l() { <!CAPTURED_VAL_INITIALIZATION!>value_1<!> = 10 }
|
||||
}
|
||||
funWithExactlyOnceCallsInPlace(o::l)
|
||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.inc()
|
||||
|
||||
Reference in New Issue
Block a user