diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt index 995d74f52a3..d7a748ad602 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt @@ -131,6 +131,13 @@ private fun PropertyInitializationInfoData.checkPropertyAccesses( else FirErrors.CAPTURED_VAL_INITIALIZATION reporter.reportOn(node.fir.lValue.source, error, symbol, context) + } else if (!symbol.isLocal && getValue(node).values.any { symbol !in it.initializedInsideConstructor }) { + /* If the assignment is inside INVOKE_ONCE lambda and the lambda is not inlined, + backend generates either separate function or separate class for the lambda. + If we try to initialize non-static final field there, we will get exception at + runtime, since we can initialize such fields only inside constructors. + */ + reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_VAL_INITIALIZATION, symbol, context) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt index 3364047052a..c10c1ffc6b1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfo.kt @@ -47,6 +47,8 @@ class PropertyInitializationInfo( val EMPTY = PropertyInitializationInfo() } + val initializedInsideConstructor: MutableSet = mutableSetOf() + override val constructor: (PersistentMap) -> PropertyInitializationInfo = ::PropertyInitializationInfo } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt index e7e96989ef0..2fb2d77a6e4 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/util/PropertyInitializationInfoCollector.kt @@ -7,6 +7,8 @@ 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.declarations.FirAnonymousFunction +import org.jetbrains.kotlin.fir.declarations.InlineStatus import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression import org.jetbrains.kotlin.fir.expressions.calleeReference import org.jetbrains.kotlin.fir.expressions.dispatchReceiver @@ -40,6 +42,8 @@ class PropertyInitializationInfoCollector( private val EMPTY_INFO: PathAwarePropertyInitializationInfo = persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY) } + private val scopeStack = arrayListOf() + override val emptyInfo: PathAwarePropertyInitializationInfo get() = EMPTY_INFO @@ -58,6 +62,17 @@ class PropertyInitializationInfoCollector( val symbol = node.fir.calleeReference?.toResolvedPropertySymbol() ?: return dataForNode if (symbol !in localProperties) return dataForNode return addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE) + .also { it.setPropertyAsInitializedInsideConstructorIfNeeded(symbol) } + } + + private fun PathAwarePropertyInitializationInfo.setPropertyAsInitializedInsideConstructorIfNeeded( + symbol: FirPropertySymbol, + ) { + if (scopeStack.all { (it.declaration as? FirAnonymousFunction)?.inlineStatus == InlineStatus.Inline }) { + for (info in values) { + info.initializedInsideConstructor.add(symbol) + } + } } override fun visitVariableDeclarationNode( @@ -122,6 +137,48 @@ class PropertyInitializationInfoCollector( declaredVariableCollector.exitCapturingStatement(node.fir) return visitNode(node, data) } + + override fun visitFunctionEnterNode( + node: FunctionEnterNode, + data: PathAwareControlFlowInfo + ): PathAwareControlFlowInfo { + if (node.owner.declaration is FirAnonymousFunction) { + scopeStack.add(node.owner) + } + return super.visitFunctionEnterNode(node, data) + } + + override fun visitFunctionExitNode( + node: FunctionExitNode, + data: PathAwareControlFlowInfo + ): PathAwareControlFlowInfo { + if (node.owner.declaration is FirAnonymousFunction) { + assert(scopeStack.isNotEmpty() && node.owner == scopeStack.last()) { + "Unexpected scopeStack's top. Expected: ${node.owner.name}, but got: ${if (scopeStack.isEmpty()) "null" else scopeStack.last().name}" + } + scopeStack.removeLast() + } + return super.visitFunctionExitNode(node, data) + } + + override fun visitAnonymousObjectEnterNode( + node: AnonymousObjectEnterNode, + data: PathAwareControlFlowInfo + ): PathAwareControlFlowInfo { + scopeStack.add(node.owner) + return super.visitAnonymousObjectEnterNode(node, data) + } + + override fun visitAnonymousObjectExpressionExitNode( + node: AnonymousObjectExpressionExitNode, + data: PathAwareControlFlowInfo + ): PathAwareControlFlowInfo { + assert(scopeStack.isNotEmpty() && node.owner == scopeStack.last()) { + "Unexpected scopeStack's top. Expected: ${node.owner.name}, but got: ${if (scopeStack.isEmpty()) "null" else scopeStack.last().name}" + } + scopeStack.removeLast() + return super.visitAnonymousObjectExpressionExitNode(node, data) + } } internal fun , K : Any> addRange( diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.fir.kt index 2efb4954808..11762189c12 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.fir.kt @@ -42,19 +42,53 @@ class Test { val b: String val c: String val d: String + val e: String + val f: String + val g: String + val h: String + val i: String init { inlineMe { a = "allowed" } crossinlineMe { - b = "not allowed" + b = "not allowed" } noinlineMe { - c = "not allowed" + c = "not allowed" } notinline { - d = "not allowed" + d = "not allowed" + } + + crossinlineMe { + inlineMe { + e = "not allowed" + } + } + + fun localFun() { + f = "not allowed" + } + + val localLambda = { + g = "not allowed" + } + + object { + val o: String + + init { + h = "not allowed" + o = "allowed" + } + } + + class Local { + init { + i = "not allowed" + } } } } @@ -141,12 +175,12 @@ class Test5 { a = "OK" } val bInit = crossinlineMe { - b = "OK" + b = "not allowed" } val cInit = noinlineMe { - c = "OK" + c = "not allowed" } val dInit = notinline { - d = "OK" + d = "not allowed" } } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt index 9038dcaa5ba..040748c8210 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt @@ -42,6 +42,11 @@ class Test { val b: String val c: String val d: String + val e: String + val f: String + val g: String + val h: String + val i: String init { inlineMe { @@ -56,6 +61,35 @@ class Test { notinline { d = "not allowed" } + + crossinlineMe { + inlineMe { + e = "not allowed" + } + } + + fun localFun() { + f = "not allowed" + } + + val localLambda = { + g = "not allowed" + } + + object { + val o: String + + init { + h = "not allowed" + o = "allowed" + } + } + + class Local { + init { + i = "not allowed" + } + } } } @@ -141,12 +175,12 @@ class Test5 { a = "OK" } val bInit = crossinlineMe { - b = "OK" + b = "not allowed" } val cInit = noinlineMe { - c = "OK" + c = "not allowed" } val dInit = notinline { - d = "OK" + d = "not allowed" } } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.txt index 012c3fbc21e..94ebc65dc81 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.txt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.txt @@ -18,6 +18,11 @@ package public final val b: kotlin.String public final val c: kotlin.String public final val d: kotlin.String + public final val e: kotlin.String + public final val f: kotlin.String + public final val g: kotlin.String + public final val h: kotlin.String + public final val i: kotlin.String public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valDefiniteInitialization.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valDefiniteInitialization.fir.kt index 79b8359396e..5736a9d69d6 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valDefiniteInitialization.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valDefiniteInitialization.fir.kt @@ -86,7 +86,7 @@ class DefiniteInitializationInInitSection { val y: Int init { - myRun { x = 42 } + myRun { x = 42 } unknownRun { y = 239 } } }