diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt index 746e2464d0e..45774518c14 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt @@ -87,7 +87,8 @@ internal fun checkPropertyInitializer( modifierList: FirModifierList?, isInitialized: Boolean, reporter: DiagnosticReporter, - context: CheckerContext + context: CheckerContext, + reachable: Boolean = true ) { val inInterface = containingClass?.isInterface == true val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList @@ -147,7 +148,7 @@ internal fun checkPropertyInitializer( if (backingFieldRequired && !inInterface && !property.isLateInit && !isExpect && !isInitialized && !isExternal) { if (property.receiverTypeRef != null && !property.hasAccessorImplementation) { reporter.reportOn(propertySource, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context) - } else { // TODO: can be suppressed not to report diagnostics about no body + } else if (reachable) { // TODO: can be suppressed not to report diagnostics about no body if (containingClass == null || property.hasAccessorImplementation) { reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED, context) } else { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirMemberPropertiesChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirMemberPropertiesChecker.kt index b54f3ef8798..75273a6ed56 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirMemberPropertiesChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirMemberPropertiesChecker.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfoCollector @@ -20,6 +21,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.diagnostics.withSuppressedDiagnostics import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.BlockExitNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph @@ -40,13 +43,18 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() { collectPropertyInitialization(declaration, memberPropertySymbols, initializedInConstructor, initializedInInitOrOtherProperty) } - for (propertySymbol in memberPropertySymbols) { - val property = propertySymbol.fir - val isInitialized = - property.initializer != null || - initializedInConstructor.getValue(propertySymbol).isDefinitelyVisited() || - initializedInInitOrOtherProperty.getValue(propertySymbol).isDefinitelyVisited() - checkProperty(declaration, property, isInitialized, context, reporter) + val deadEnds = declaration.collectDeadEndDeclarations() + var reachedDeadEnd = false + for (innerDeclaration in declaration.declarations) { + if (innerDeclaration is FirProperty) { + val symbol = innerDeclaration.symbol + val isInitialized = + innerDeclaration.initializer != null || + initializedInConstructor.getValue(symbol).isDefinitelyVisited() || + initializedInInitOrOtherProperty.getValue(symbol).isDefinitelyVisited() + checkProperty(declaration, innerDeclaration, isInitialized, context, reporter, !reachedDeadEnd) + } + reachedDeadEnd = reachedDeadEnd || deadEnds.contains(innerDeclaration) } } @@ -140,7 +148,8 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() { property: FirProperty, isInitialized: Boolean, context: CheckerContext, - reporter: DiagnosticReporter + reporter: DiagnosticReporter, + reachable: Boolean ) { val source = property.source ?: return if (source.kind is FirFakeSourceElementKind) return @@ -155,7 +164,8 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() { modifierList, isInitialized, reporter, - context + context, + reachable ) checkExpectDeclarationVisibilityAndBody(property, source, reporter, context) @@ -205,4 +215,27 @@ object FirMemberPropertiesChecker : FirRegularClassChecker() { } } } + + private fun FirRegularClass.collectDeadEndDeclarations(): Set { + val cfg = controlFlowGraphReference?.controlFlowGraph ?: return emptySet() + return cfg.exitNode.incomingEdges.keys + .map { it.fir } + .filter { it.isDeadEnd() } + .toSet() + } + + /** + * The idea is to find presumably exit node is dead one + * 1. in the case of block expression, it is BlockExitNode + * 2. in other cases treat any dead node that leads to exitNode as evidence of deadness + * This is all a workaround because ideally exit node itself should be dead if it is unreachable + */ + private fun FirElement.isDeadEnd(): Boolean { + val cfg = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return false + cfg.exitNode.incomingEdges.keys.find { it is BlockExitNode }?.let { + return it.isDead + } + + return cfg.exitNode.incomingEdges.keys.any { it.isDead } + } } diff --git a/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt b/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt index 52cceeabd75..7376c78fb5c 100644 --- a/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt +++ b/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND: WASM // WASM_MUTE_REASON: EXCEPTIONS_NOT_IMPLEMENTED // WITH_RUNTIME @@ -12,10 +11,49 @@ class C { var uninitializedVar: String } -fun box(): String = +class Foo { + init { + TODO() + } + + val uninitializedVal: String + + var uninitializedVar: String +} + +class Bar { + val initializedVal = 43 + + init { + TODO() + } + + val uninitializedVal: String + + var uninitializedVar: String +} + +fun box(): String { try { C() - "Fail" + return "Fail" } catch (e: NotImplementedError) { - "OK" + //OK } + + try { + Foo() + return "Fail" + } catch (e: NotImplementedError) { + //OK + } + + try { + Bar() + return "Fail" + } catch (e: NotImplementedError) { + //OK + } + + return "OK" +}