FIR: only allow member val initialization through this@T
class C {
val x: Int
init {
// valid ways to initialize:
x = 1
this@C.x = 1
// invalid:
someOtherC.x = 1
run { /*this@run.*/x = 1 }
val self = this
self.x = 1
}
}
This commit is contained in:
+10
-1
@@ -22,7 +22,6 @@ FILE: reassignOfNonMemberProperty_lateInitialization.kt
|
||||
|
||||
public final val a: R|kotlin/String| = this@R|/Some|.R|kotlin/run|<R|Some|, R|kotlin/String|>(<L> = run@fun R|Some|.<anonymous>(): R|kotlin/String| <inline=Inline, kind=EXACTLY_ONCE> {
|
||||
this@R|special/anonymous|.R|/Some.x| = String(error)
|
||||
this@R|special/anonymous|.R|/Some.y| = String(ok)
|
||||
this@R|special/anonymous|.R|/Some.y| = String(error)
|
||||
this@R|special/anonymous|.R|/z| = String(error)
|
||||
^ String(hello)
|
||||
@@ -30,6 +29,16 @@ FILE: reassignOfNonMemberProperty_lateInitialization.kt
|
||||
)
|
||||
public get(): R|kotlin/String|
|
||||
|
||||
public final val b: R|kotlin/String| = Int(123).R|kotlin/run|<R|kotlin/Int|, R|kotlin/String|>(<L> = run@fun R|kotlin/Int|.<anonymous>(): R|kotlin/String| <inline=Inline, kind=EXACTLY_ONCE> {
|
||||
this@R|/Some|.R|/Some.x| = String(error)
|
||||
this@R|/Some|.R|/Some.y| = String(ok)
|
||||
this@R|/Some|.R|/Some.y| = String(error)
|
||||
this@R|/Some|.R|/z| = String(error)
|
||||
^ String(there)
|
||||
}
|
||||
)
|
||||
public get(): R|kotlin/String|
|
||||
|
||||
init {
|
||||
this@R|/Some|.R|/Some.x| = String(error)
|
||||
this@R|/Some|.R|/Some.y| = String(error)
|
||||
|
||||
Vendored
+10
-1
@@ -15,11 +15,20 @@ class Some {
|
||||
}
|
||||
|
||||
val a: String = run {
|
||||
// these are all on this@run, which is not guaranteed to be this@Some
|
||||
<!VAL_REASSIGNMENT!>x<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>y<!> = "error"
|
||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||
"hello"
|
||||
}
|
||||
|
||||
val b: String = 123.run {
|
||||
// now this@run is an Int, so these are on this@Some
|
||||
x = "error"
|
||||
y = "ok"
|
||||
y = "error"
|
||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||
"hello"
|
||||
"there"
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
+18
-25
@@ -7,8 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util
|
||||
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||
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) {
|
||||
@@ -23,8 +25,12 @@ class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, graph:
|
||||
|
||||
class PropertyInitializationInfoCollector(
|
||||
private val localProperties: Set<FirPropertySymbol>,
|
||||
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)
|
||||
}
|
||||
@@ -37,12 +43,11 @@ class PropertyInitializationInfoCollector(
|
||||
data: PathAwarePropertyInitializationInfo
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val dataForNode = visitNode(node, data)
|
||||
val receiver = (node.fir.dispatchReceiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol
|
||||
if (receiver != expectedReceiver) return dataForNode
|
||||
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: return dataForNode
|
||||
return if (symbol !in localProperties) {
|
||||
dataForNode
|
||||
} else {
|
||||
processVariableWithAssignment(dataForNode, symbol)
|
||||
}
|
||||
if (symbol !in localProperties) return dataForNode
|
||||
return addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE)
|
||||
}
|
||||
|
||||
override fun visitVariableDeclarationNode(
|
||||
@@ -50,26 +55,14 @@ class PropertyInitializationInfoCollector(
|
||||
data: PathAwarePropertyInitializationInfo
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val dataForNode = visitNode(node, data)
|
||||
return processVariableWithAssignment(
|
||||
dataForNode,
|
||||
node.fir.symbol,
|
||||
overwriteRange = node.fir.initializer == null && node.fir.delegate == null
|
||||
)
|
||||
}
|
||||
|
||||
fun getData(graph: ControlFlowGraph) =
|
||||
graph.collectDataForNode(TraverseDirection.Forward, this)
|
||||
|
||||
private fun processVariableWithAssignment(
|
||||
dataForNode: PathAwarePropertyInitializationInfo,
|
||||
symbol: FirPropertySymbol,
|
||||
overwriteRange: Boolean = false,
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
assert(dataForNode.keys.isNotEmpty())
|
||||
return if (overwriteRange)
|
||||
overwriteRange(dataForNode, symbol, EventOccurrencesRange.ZERO)
|
||||
else
|
||||
addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE)
|
||||
return when {
|
||||
expectedReceiver != null ->
|
||||
dataForNode
|
||||
node.fir.initializer == null && node.fir.delegate == null ->
|
||||
overwriteRange(dataForNode, node.fir.symbol, EventOccurrencesRange.ZERO)
|
||||
else ->
|
||||
overwriteRange(dataForNode, node.fir.symbol, EventOccurrencesRange.EXACTLY_ONCE)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
||||
}
|
||||
if (memberPropertySymbols.isEmpty()) return null
|
||||
// TODO: this also visits non-constructor member functions...
|
||||
return PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)[graph.exitNode]?.get(NormalPath)
|
||||
// TODO: also use FirPropertyInitializationAnalyzer.PropertyReporter to report reassignments or use-before-initialization
|
||||
return PropertyInitializationInfoCollector(memberPropertySymbols, symbol).getData(graph)[graph.exitNode]?.get(NormalPath)
|
||||
}
|
||||
|
||||
private fun checkProperty(
|
||||
|
||||
+11
-11
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
|
||||
import org.jetbrains.kotlin.fir.references.FirBackingFieldReference
|
||||
@@ -29,10 +30,10 @@ 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.ConeSimpleKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||
import org.jetbrains.kotlin.fir.visibilityChecker
|
||||
|
||||
@@ -133,23 +134,22 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
||||
) {
|
||||
val property = expression.lValue.toResolvedPropertySymbol() ?: return
|
||||
if (property.isLocal || property.isVar) return
|
||||
val assignIsIllegal = when (val dispatchReceiverType = property.dispatchReceiverType) {
|
||||
null -> true
|
||||
else -> when {
|
||||
property is FirSyntheticPropertySymbol -> true
|
||||
property.hasDelegate || !property.hasBackingField -> true
|
||||
property.hasInitializer -> true
|
||||
else -> !isInsideInitializationOfClass(dispatchReceiverType, context)
|
||||
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 (assignIsIllegal) {
|
||||
if (!isLegal) {
|
||||
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInsideInitializationOfClass(classType: ConeSimpleKotlinType, context: CheckerContext): Boolean {
|
||||
private fun isInsideInitializationOfClass(classSymbol: FirBasedSymbol<*>, context: CheckerContext): Boolean {
|
||||
val session = context.session
|
||||
val classSymbol = classType.toSymbol(session) ?: return false
|
||||
for (containingDeclaration in context.containingDeclarations) {
|
||||
val containingClassSymbol = when (containingDeclaration) {
|
||||
is FirProperty -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class A(val next: A? = null) {
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val x: String<!>
|
||||
init {
|
||||
next?.x = "a"
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,12 +1,12 @@
|
||||
class A(val next: A? = null) {
|
||||
val x: String
|
||||
init {
|
||||
next?.x = "a"
|
||||
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"
|
||||
|
||||
next?.x = "f"
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "f"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -15,7 +15,7 @@ class Outer {
|
||||
innerProp = "6" // do not repeat the same diagnostic with this receiver
|
||||
this@Inner.innerProp = "7"
|
||||
|
||||
inner.innerProp = "8"
|
||||
inner.<!VAL_REASSIGNMENT!>innerProp<!> = "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
class A(val next: A? = null) {
|
||||
val x: String
|
||||
init {
|
||||
next?.x = "a"
|
||||
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"
|
||||
|
||||
next?.x = "f"
|
||||
next?.<!VAL_REASSIGNMENT!>x<!> = "f"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user