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:
pyos
2023-01-19 10:57:34 +01:00
committed by teamcity
parent 123b211fc4
commit c42dd0848e
9 changed files with 57 additions and 45 deletions
@@ -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)
@@ -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 {
@@ -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)
}
}
// --------------------------------------------------
@@ -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(
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
}
@@ -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"
}
}