FIR: Report CAPTURED_VAL_INITIALIZATION if initialization is done

outside of constructor.

In other words, when EXACTLY_ONCE lambda initializes non-static final
field of a class, the lambda has to be inlined, otherwise, backend
generated separate function of separate class for the lambda,
which lead to exception at runtime.

 #KT-64854 Fixed
 #KT-59906 Fixed
This commit is contained in:
Ilmir Usmanov
2024-03-05 22:00:19 +01:00
committed by Space Team
parent 5dec87eba8
commit 1c4023fda5
7 changed files with 149 additions and 10 deletions
@@ -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)
}
}
@@ -47,6 +47,8 @@ class PropertyInitializationInfo(
val EMPTY = PropertyInitializationInfo()
}
val initializedInsideConstructor: MutableSet<FirPropertySymbol> = mutableSetOf()
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
::PropertyInitializationInfo
}
@@ -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<ControlFlowGraph>()
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<PropertyInitializationInfo>
): PathAwareControlFlowInfo<PropertyInitializationInfo> {
if (node.owner.declaration is FirAnonymousFunction) {
scopeStack.add(node.owner)
}
return super.visitFunctionEnterNode(node, data)
}
override fun visitFunctionExitNode(
node: FunctionExitNode,
data: PathAwareControlFlowInfo<PropertyInitializationInfo>
): PathAwareControlFlowInfo<PropertyInitializationInfo> {
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<PropertyInitializationInfo>
): PathAwareControlFlowInfo<PropertyInitializationInfo> {
scopeStack.add(node.owner)
return super.visitAnonymousObjectEnterNode(node, data)
}
override fun visitAnonymousObjectExpressionExitNode(
node: AnonymousObjectExpressionExitNode,
data: PathAwareControlFlowInfo<PropertyInitializationInfo>
): PathAwareControlFlowInfo<PropertyInitializationInfo> {
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 <S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> addRange(
@@ -42,19 +42,53 @@ class Test {
val b: String
val c: String
val d: String
val e: String
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val f: String<!>
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val g: String<!>
val h: String
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val i: String<!>
init {
inlineMe {
a = "allowed"
}
crossinlineMe {
b = "not allowed"
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "not allowed"
}
noinlineMe {
c = "not allowed"
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "not allowed"
}
notinline {
d = "not allowed"
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
}
crossinlineMe {
inlineMe {
<!CAPTURED_VAL_INITIALIZATION!>e<!> = "not allowed"
}
}
fun localFun() {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>f<!> = "not allowed"
}
val localLambda = {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>g<!> = "not allowed"
}
object {
val o: String
init {
<!CAPTURED_VAL_INITIALIZATION!>h<!> = "not allowed"
o = "allowed"
}
}
class Local {
init {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>i<!> = "not allowed"
}
}
}
}
@@ -141,12 +175,12 @@ class Test5 {
a = "OK"
}
val bInit = crossinlineMe {
b = "OK"
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "not allowed"
}
val cInit = noinlineMe {
c = "OK"
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "not allowed"
}
val dInit = notinline {
d = "OK"
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
}
}
@@ -42,6 +42,11 @@ class Test {
val b: String
val c: String
val d: String
val e: String
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val f: String<!>
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val g: String<!>
val h: String
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val i: String<!>
init {
inlineMe {
@@ -56,6 +61,35 @@ class Test {
notinline {
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
}
crossinlineMe {
inlineMe {
e = "not allowed"
}
}
fun localFun() {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>f<!> = "not allowed"
}
val <!UNUSED_VARIABLE!>localLambda<!> = {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>g<!> = "not allowed"
}
object {
val o: String
init {
h = "not allowed"
o = "allowed"
}
}
class Local {
init {
<!CAPTURED_MEMBER_VAL_INITIALIZATION!>i<!> = "not allowed"
}
}
}
}
@@ -141,12 +175,12 @@ class Test5 {
a = "OK"
}
val bInit = crossinlineMe {
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "OK"
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "not allowed"
}
val cInit = noinlineMe {
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "OK"
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "not allowed"
}
val dInit = notinline {
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "OK"
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
}
}
@@ -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
@@ -86,7 +86,7 @@ class DefiniteInitializationInInitSection {
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y: Int<!>
init {
myRun { x = 42 }
myRun { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 }
unknownRun { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>y<!> = 239 }
}
}