diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index 35a67fec48a..915902c8e61 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -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 { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index edac921e509..f5902c8209b 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -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 { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 998256817f9..2327bf95741 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -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 { 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 23a51ddf5e6..3f032cb2f6d 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 @@ -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, + isForClassInitialization: Boolean, + doNotReportUninitializedVariable: Boolean, + scopes: MutableMap ) { 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 + } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.fir.kt new file mode 100644 index 00000000000..ddcf0912129 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.fir.kt @@ -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(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 +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt new file mode 100644 index 00000000000..dca37563d4f --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/capturingUninitializedVariableInNonInPlaceLambda.kt @@ -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(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 +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.fir.kt deleted file mode 100644 index 2f049b0656b..00000000000 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.fir.kt +++ /dev/null @@ -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 exec(f: () -> T): T = f() - -class Demo4 { - private val some = exec { - if (state) - state = true - - println(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) - } - } -} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.kt index 3cb2bd13754..72518ea0657 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/localObjectInConstructor.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL fun println(obj: Any?) = obj class Demo0 { diff --git a/compiler/testData/diagnostics/tests/delegatedProperty/kt48546.fir.kt b/compiler/testData/diagnostics/tests/delegatedProperty/kt48546.fir.kt index 19138f21642..81e341b1d4f 100644 --- a/compiler/testData/diagnostics/tests/delegatedProperty/kt48546.fir.kt +++ b/compiler/testData/diagnostics/tests/delegatedProperty/kt48546.fir.kt @@ -4,7 +4,7 @@ object DelegateTest { var result = "" val f by lazy { - result += f.toString() // Compiler crash + result += f.toString() // Compiler crash "hello" } } @@ -12,7 +12,7 @@ object DelegateTest { object DelegateTest2 { var result = "" val f by lazy { - result += f + result += f "hello" } } diff --git a/compiler/testData/diagnostics/tests/delegatedProperty/kt48546Strict.fir.kt b/compiler/testData/diagnostics/tests/delegatedProperty/kt48546Strict.fir.kt index 82d551054b1..9d84d24a098 100644 --- a/compiler/testData/diagnostics/tests/delegatedProperty/kt48546Strict.fir.kt +++ b/compiler/testData/diagnostics/tests/delegatedProperty/kt48546Strict.fir.kt @@ -5,7 +5,7 @@ object DelegateTest { var result = "" val f by lazy { - result += f.toString() // Compiler crash + result += f.toString() // Compiler crash "hello" } } @@ -13,7 +13,7 @@ object DelegateTest { object DelegateTest2 { var result = "" val f by lazy { - result += f + result += f "hello" } } diff --git a/compiler/testData/diagnostics/tests/delegation/kt48546.fir.kt b/compiler/testData/diagnostics/tests/delegation/kt48546.fir.kt index 0c28d00b081..b04a5fa33ab 100644 --- a/compiler/testData/diagnostics/tests/delegation/kt48546.fir.kt +++ b/compiler/testData/diagnostics/tests/delegation/kt48546.fir.kt @@ -3,7 +3,7 @@ object DelegateTest { var result = "" val f by lazy { - result += f.toString() // Compiler crash + result += f.toString() // Compiler crash "hello" } } diff --git a/compiler/testData/diagnostics/tests/regressions/kt328.fir.kt b/compiler/testData/diagnostics/tests/regressions/kt328.fir.kt index 9632b69d7d9..5755cafeedb 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt328.fir.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt328.fir.kt @@ -12,7 +12,7 @@ fun bar2() = { //properties //in a class class A() { - val x = { x } + val x = { x } } //in a package diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index fb3739549bd..69ad5d92f5f 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -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 {