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> {
|
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.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|/Some.y| = String(error)
|
||||||
this@R|special/anonymous|.R|/z| = String(error)
|
this@R|special/anonymous|.R|/z| = String(error)
|
||||||
^ String(hello)
|
^ String(hello)
|
||||||
@@ -30,6 +29,16 @@ FILE: reassignOfNonMemberProperty_lateInitialization.kt
|
|||||||
)
|
)
|
||||||
public get(): R|kotlin/String|
|
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 {
|
init {
|
||||||
this@R|/Some|.R|/Some.x| = String(error)
|
this@R|/Some|.R|/Some.x| = String(error)
|
||||||
this@R|/Some|.R|/Some.y| = String(error)
|
this@R|/Some|.R|/Some.y| = String(error)
|
||||||
|
|||||||
Vendored
+10
-1
@@ -15,11 +15,20 @@ class Some {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val a: String = run {
|
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"
|
x = "error"
|
||||||
y = "ok"
|
y = "ok"
|
||||||
y = "error"
|
y = "error"
|
||||||
<!VAL_REASSIGNMENT!>z<!> = "error"
|
<!VAL_REASSIGNMENT!>z<!> = "error"
|
||||||
"hello"
|
"there"
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
+18
-25
@@ -7,8 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa.util
|
|||||||
|
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
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.references.toResolvedPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
|
||||||
class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, graph: ControlFlowGraph) {
|
class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, graph: ControlFlowGraph) {
|
||||||
@@ -23,8 +25,12 @@ class PropertyInitializationInfoData(properties: Set<FirPropertySymbol>, graph:
|
|||||||
|
|
||||||
class PropertyInitializationInfoCollector(
|
class PropertyInitializationInfoCollector(
|
||||||
private val localProperties: Set<FirPropertySymbol>,
|
private val localProperties: Set<FirPropertySymbol>,
|
||||||
|
private val expectedReceiver: FirBasedSymbol<*>? = null,
|
||||||
private val declaredVariableCollector: DeclaredVariableCollector = DeclaredVariableCollector(),
|
private val declaredVariableCollector: DeclaredVariableCollector = DeclaredVariableCollector(),
|
||||||
) : PathAwareControlFlowGraphVisitor<PropertyInitializationInfo>() {
|
) : PathAwareControlFlowGraphVisitor<PropertyInitializationInfo>() {
|
||||||
|
fun getData(graph: ControlFlowGraph) =
|
||||||
|
graph.collectDataForNode(TraverseDirection.Forward, this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val EMPTY_INFO: PathAwarePropertyInitializationInfo = persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY)
|
private val EMPTY_INFO: PathAwarePropertyInitializationInfo = persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY)
|
||||||
}
|
}
|
||||||
@@ -37,12 +43,11 @@ class PropertyInitializationInfoCollector(
|
|||||||
data: PathAwarePropertyInitializationInfo
|
data: PathAwarePropertyInitializationInfo
|
||||||
): PathAwarePropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val dataForNode = visitNode(node, data)
|
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
|
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: return dataForNode
|
||||||
return if (symbol !in localProperties) {
|
if (symbol !in localProperties) return dataForNode
|
||||||
dataForNode
|
return addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE)
|
||||||
} else {
|
|
||||||
processVariableWithAssignment(dataForNode, symbol)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitVariableDeclarationNode(
|
override fun visitVariableDeclarationNode(
|
||||||
@@ -50,26 +55,14 @@ class PropertyInitializationInfoCollector(
|
|||||||
data: PathAwarePropertyInitializationInfo
|
data: PathAwarePropertyInitializationInfo
|
||||||
): PathAwarePropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val dataForNode = visitNode(node, data)
|
val dataForNode = visitNode(node, data)
|
||||||
return processVariableWithAssignment(
|
return when {
|
||||||
dataForNode,
|
expectedReceiver != null ->
|
||||||
node.fir.symbol,
|
dataForNode
|
||||||
overwriteRange = node.fir.initializer == null && node.fir.delegate == null
|
node.fir.initializer == null && node.fir.delegate == null ->
|
||||||
)
|
overwriteRange(dataForNode, node.fir.symbol, EventOccurrencesRange.ZERO)
|
||||||
}
|
else ->
|
||||||
|
overwriteRange(dataForNode, node.fir.symbol, EventOccurrencesRange.EXACTLY_ONCE)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|||||||
+2
-1
@@ -48,7 +48,8 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
|||||||
}
|
}
|
||||||
if (memberPropertySymbols.isEmpty()) return null
|
if (memberPropertySymbols.isEmpty()) return null
|
||||||
// TODO: this also visits non-constructor member functions...
|
// 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(
|
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.hasBackingField
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||||
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
|
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.expressions.FirVariableAssignment
|
||||||
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
|
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
|
||||||
import org.jetbrains.kotlin.fir.references.FirBackingFieldReference
|
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.references.toResolvedValueParameterSymbol
|
||||||
import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue
|
import org.jetbrains.kotlin.fir.resolve.calls.ExpressionReceiverValue
|
||||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
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.SymbolInternals
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
|
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.types.toSymbol
|
||||||
import org.jetbrains.kotlin.fir.visibilityChecker
|
import org.jetbrains.kotlin.fir.visibilityChecker
|
||||||
|
|
||||||
@@ -133,23 +134,22 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
|||||||
) {
|
) {
|
||||||
val property = expression.lValue.toResolvedPropertySymbol() ?: return
|
val property = expression.lValue.toResolvedPropertySymbol() ?: return
|
||||||
if (property.isLocal || property.isVar) return
|
if (property.isLocal || property.isVar) return
|
||||||
val assignIsIllegal = when (val dispatchReceiverType = property.dispatchReceiverType) {
|
val isLegal = when {
|
||||||
null -> true
|
property is FirSyntheticPropertySymbol -> false
|
||||||
else -> when {
|
property.hasDelegate || !property.hasBackingField -> false
|
||||||
property is FirSyntheticPropertySymbol -> true
|
property.hasInitializer -> false
|
||||||
property.hasDelegate || !property.hasBackingField -> true
|
else -> {
|
||||||
property.hasInitializer -> true
|
val thisReceiverSymbol = (expression.dispatchReceiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol
|
||||||
else -> !isInsideInitializationOfClass(dispatchReceiverType, context)
|
thisReceiverSymbol != null && isInsideInitializationOfClass(thisReceiverSymbol, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (assignIsIllegal) {
|
if (!isLegal) {
|
||||||
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
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 session = context.session
|
||||||
val classSymbol = classType.toSymbol(session) ?: return false
|
|
||||||
for (containingDeclaration in context.containingDeclarations) {
|
for (containingDeclaration in context.containingDeclarations) {
|
||||||
val containingClassSymbol = when (containingDeclaration) {
|
val containingClassSymbol = when (containingDeclaration) {
|
||||||
is FirProperty -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
|
is FirProperty -> containingDeclaration.containingClassLookupTag()?.toSymbol(session)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class A(val next: A? = null) {
|
class A(val next: A? = null) {
|
||||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val x: String<!>
|
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val x: String<!>
|
||||||
init {
|
init {
|
||||||
next?.x = "a"
|
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
class A(val next: A? = null) {
|
class A(val next: A? = null) {
|
||||||
val x: String
|
val x: String
|
||||||
init {
|
init {
|
||||||
next?.x = "a"
|
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||||
x = "b"
|
x = "b"
|
||||||
this.x = "c"
|
this.x = "c"
|
||||||
x = "d" // don't repeat the same diagnostic again with this receiver
|
x = "d" // don't repeat the same diagnostic again with this receiver
|
||||||
this.x = "e"
|
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
|
innerProp = "6" // do not repeat the same diagnostic with this receiver
|
||||||
this@Inner.innerProp = "7"
|
this@Inner.innerProp = "7"
|
||||||
|
|
||||||
inner.innerProp = "8"
|
inner.<!VAL_REASSIGNMENT!>innerProp<!> = "8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
class A(val next: A? = null) {
|
class A(val next: A? = null) {
|
||||||
val x: String
|
val x: String
|
||||||
init {
|
init {
|
||||||
next?.x = "a"
|
next?.<!VAL_REASSIGNMENT!>x<!> = "a"
|
||||||
this@A.x = "b"
|
this@A.x = "b"
|
||||||
this.x = "c"
|
this.x = "c"
|
||||||
x = "d" // don't repeat the same diagnostic again with this receiver
|
x = "d" // don't repeat the same diagnostic again with this receiver
|
||||||
this@A.x = "e"
|
this@A.x = "e"
|
||||||
|
|
||||||
next?.x = "f"
|
next?.<!VAL_REASSIGNMENT!>x<!> = "f"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user