[FIR] Allow to access uninitialized member properties in non-inPlace lambdas in class initialization

^KT-56696 Fixed
^KT-56408
This commit is contained in:
Dmitriy Novozhilov
2023-02-15 19:02:18 +02:00
committed by Space Team
parent 914acd841f
commit 86af01439c
13 changed files with 200 additions and 96 deletions
@@ -5672,6 +5672,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/breakOrContinueInLoopCondition.kt");
}
@Test
@TestMetadata("capturingUninitializedVariableInNonInPlaceLambda.kt")
public void testCapturingUninitializedVariableInNonInPlaceLambda() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt");
}
@Test
@TestMetadata("checkInnerLocalDeclarations.kt")
public void testCheckInnerLocalDeclarations() throws Exception {
@@ -5678,6 +5678,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/breakOrContinueInLoopCondition.kt");
}
@Test
@TestMetadata("capturingUninitializedVariableInNonInPlaceLambda.kt")
public void testCapturingUninitializedVariableInNonInPlaceLambda() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt");
}
@Test
@TestMetadata("checkInnerLocalDeclarations.kt")
public void testCheckInnerLocalDeclarations() throws Exception {
@@ -5672,6 +5672,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/breakOrContinueInLoopCondition.kt");
}
@Test
@TestMetadata("capturingUninitializedVariableInNonInPlaceLambda.kt")
public void testCapturingUninitializedVariableInNonInPlaceLambda() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt");
}
@Test
@TestMetadata("checkInnerLocalDeclarations.kt")
public void testCheckInnerLocalDeclarations() throws Exception {
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.fir.expressions.unwrapLValue
import org.jetbrains.kotlin.fir.isCatchParameter
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph.Kind
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
@@ -76,7 +77,10 @@ fun PropertyInitializationInfoData.checkPropertyAccesses(
val filtered = properties.filterTo(mutableSetOf()) { it.requiresInitialization(isForClassInitialization) }
if (filtered.isEmpty()) return
checkPropertyAccesses(graph, filtered, context, reporter, null, mutableMapOf())
checkPropertyAccesses(
graph, filtered, context, reporter, scope = null,
isForClassInitialization, doNotReportUninitializedVariable = false, mutableMapOf()
)
}
@OptIn(SymbolInternals::class)
@@ -86,7 +90,9 @@ private fun PropertyInitializationInfoData.checkPropertyAccesses(
context: CheckerContext,
reporter: DiagnosticReporter,
scope: FirDeclaration?,
scopes: MutableMap<FirPropertySymbol, FirDeclaration?>,
isForClassInitialization: Boolean,
doNotReportUninitializedVariable: Boolean,
scopes: MutableMap<FirPropertySymbol, FirDeclaration?>
) {
fun FirQualifiedAccessExpression.hasCorrectReceiver() =
(dispatchReceiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol == receiver
@@ -121,6 +127,7 @@ private fun PropertyInitializationInfoData.checkPropertyAccesses(
}
node is QualifiedAccessNode -> {
if (doNotReportUninitializedVariable) continue
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: continue
if (!symbol.isLateInit && node.fir.hasCorrectReceiver() && symbol in properties &&
getValue(node).values.any { it[symbol]?.isDefinitelyVisited() != true }
@@ -134,10 +141,27 @@ private fun PropertyInitializationInfoData.checkPropertyAccesses(
// needed. The errors on reassignments will be emitted by `FirReassignmentAndInvisibleSetterChecker`.
node is CFGNodeWithSubgraphs<*> && (receiver == null || node !== graph.exitNode) -> {
for (subGraph in node.subGraphs) {
/*
* For class initialization graph we allow to read properties in non-in-place lambdas
* even if they may be not initialized at this point, because if lambda is not in-place,
* then it most likely will be called after object will be initialized
*/
val doNotReportForSubGraph = doNotReportUninitializedVariable ||
(isForClassInitialization && subGraph.kind.doNotReportUninitializedVariableForClassInitialization)
val newScope = subGraph.declaration?.takeIf { !it.evaluatedInPlace } ?: scope
checkPropertyAccesses(subGraph, properties, context, reporter, newScope, scopes)
checkPropertyAccesses(
subGraph, properties, context, reporter, newScope,
isForClassInitialization, doNotReportForSubGraph, scopes
)
}
}
}
}
}
private val Kind.doNotReportUninitializedVariableForClassInitialization: Boolean
get() = when (this) {
Kind.AnonymousFunction, Kind.LocalFunction -> true
else -> false
}
@@ -0,0 +1,71 @@
// DIAGNOSTICS: -UNUSED_PARAMETER
// WITH_STDLIB
import kotlin.contracts.*
fun capture(block: () -> Unit): String = ""
@OptIn(ExperimentalContracts::class)
inline fun inPlace(block: () -> Unit): String {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return ""
}
fun consume(x: Any?) {}
class A {
val a = capture { consume(x) }
val b = inPlace {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
inPlace {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
}
}
val c = object {
fun foo() {
consume(x) // ok
capture { consume(x) } // ok
inPlace {
consume(x) // ok
capture { consume(x) } // ok
}
}
init {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
inPlace {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
}
}
val objectProp = inPlace {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
inPlace {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
}
}
}
val d = inPlace {
fun localFun() {
consume(x) // ok
}
capture {
localFun()
}
}
val x = 10
}
@@ -0,0 +1,71 @@
// DIAGNOSTICS: -UNUSED_PARAMETER
// WITH_STDLIB
import kotlin.contracts.*
fun capture(block: () -> Unit): String = ""
@OptIn(ExperimentalContracts::class)
inline fun inPlace(block: () -> Unit): String {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return ""
}
fun consume(x: Any?) {}
class A {
val a = capture { consume(x) }
val b = inPlace {
consume(x) // error
capture { consume(x) } // ok
inPlace {
consume(x) // error
capture { consume(x) } // ok
}
}
val c = object {
fun foo() {
consume(x) // ok
capture { consume(x) } // ok
inPlace {
consume(x) // ok
capture { consume(x) } // ok
}
}
init {
consume(<!UNINITIALIZED_VARIABLE!>x<!>) // error
capture { consume(x) } // ok
inPlace {
consume(x) // error
capture { consume(x) } // ok
}
}
val objectProp = inPlace {
consume(x) // error
capture { consume(x) } // ok
inPlace {
consume(x) // error
capture { consume(x) } // ok
}
}
}
val d = inPlace {
fun localFun() {
consume(x) // ok
}
capture {
localFun()
}
}
val x = 10
}
@@ -1,87 +0,0 @@
fun println(obj: Any?) = obj
class Demo0 {
private val some = object {
fun foo() {
println(state)
}
}
private var state: Boolean = true
}
class Demo1 {
private val some = object {
fun foo() {
if (state)
state = true
println(state)
}
}
private var state: Boolean = true
}
class Demo1A {
fun foo() {
if (state)
state = true
println(state)
}
private var state: Boolean = true
}
class Demo2 {
private val some = object {
fun foo() {
if (state)
state = true
else
state = false
println(state)
}
}
private var state: Boolean = true
}
class Demo3 {
private val some = run {
if (state)
state = true
println(state)
}
private var state: Boolean = true
}
fun <T> exec(f: () -> T): T = f()
class Demo4 {
private val some = exec {
if (<!UNINITIALIZED_VARIABLE!>state<!>)
state = true
println(<!UNINITIALIZED_VARIABLE!>state<!>)
}
private var state: Boolean = true
}
class Demo5 {
private var state: Boolean = true
private val some = object {
fun foo() {
if (state)
state = true
println(state)
}
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun println(obj: Any?) = obj
class Demo0 {
@@ -4,7 +4,7 @@
object DelegateTest {
var result = ""
val f by lazy {
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>f<!>.toString() // Compiler crash
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>f<!>.toString() // Compiler crash
"hello"
}
}
@@ -12,7 +12,7 @@ object DelegateTest {
object DelegateTest2 {
var result = ""
val f by lazy {
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>f<!>
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>f<!>
"hello"
}
}
@@ -5,7 +5,7 @@
object DelegateTest {
var result = ""
val f by lazy {
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>f<!>.toString() // Compiler crash
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>f<!>.toString() // Compiler crash
"hello"
}
}
@@ -13,7 +13,7 @@ object DelegateTest {
object DelegateTest2 {
var result = ""
val f by lazy {
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>f<!>
result += <!DEBUG_INFO_EXPRESSION_TYPE("ERROR CLASS: cycle"), TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>f<!>
"hello"
}
}
@@ -3,7 +3,7 @@
object DelegateTest {
var result = ""
val f by lazy {
result += <!TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>f<!>.toString() // Compiler crash
result += <!TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>f<!>.toString() // Compiler crash
"hello"
}
}
@@ -12,7 +12,7 @@ fun bar2() = {
//properties
//in a class
class A() {
val x = { <!TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, UNINITIALIZED_VARIABLE!>x<!> }
val x = { <!TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM, TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM!>x<!> }
}
//in a package
@@ -5678,6 +5678,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/breakOrContinueInLoopCondition.kt");
}
@Test
@TestMetadata("capturingUninitializedVariableInNonInPlaceLambda.kt")
public void testCapturingUninitializedVariableInNonInPlaceLambda() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt");
}
@Test
@TestMetadata("checkInnerLocalDeclarations.kt")
public void testCheckInnerLocalDeclarations() throws Exception {