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:
+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)
|
||||
|
||||
Reference in New Issue
Block a user