[FIR] Properties defined in a do-while may not always be initialized
Local properties defined within the body of a do-while loop can be used in the condition of the loop. However, use of a `continue` may mean the property isn't always initialized, even if it is initialized when it is defined. So while a local property may be within scope and has an initializer, this doesn't always mean that the property is initialized. As such, properties that are defined within a do-while loop and also used in the condition of the same do-while loop should be tracked. Then, these properties should still be checked for proper initialization even if they have an initializer. ^KT-64872 Fixed
This commit is contained in:
+3
-1
@@ -74,7 +74,9 @@ fun PropertyInitializationInfoData.checkPropertyAccesses(
|
|||||||
) {
|
) {
|
||||||
// If a property has an initializer (or does not need one), then any reads are OK while any writes are OK
|
// If a property has an initializer (or does not need one), then any reads are OK while any writes are OK
|
||||||
// if it's a `var` and bad if it's a `val`. `FirReassignmentAndInvisibleSetterChecker` does this without a CFG.
|
// if it's a `var` and bad if it's a `val`. `FirReassignmentAndInvisibleSetterChecker` does this without a CFG.
|
||||||
val filtered = properties.filterTo(mutableSetOf()) { it.requiresInitialization(isForInitialization) }
|
val filtered = properties.filterTo(mutableSetOf()) {
|
||||||
|
it.requiresInitialization(isForInitialization) || it in conditionallyInitializedProperties
|
||||||
|
}
|
||||||
if (filtered.isEmpty()) return
|
if (filtered.isEmpty()) return
|
||||||
|
|
||||||
checkPropertyAccesses(
|
checkPropertyAccesses(
|
||||||
|
|||||||
+1
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
|||||||
|
|
||||||
class PropertyInitializationInfoData(
|
class PropertyInitializationInfoData(
|
||||||
val properties: Set<FirPropertySymbol>,
|
val properties: Set<FirPropertySymbol>,
|
||||||
|
val conditionallyInitializedProperties: Set<FirPropertySymbol>,
|
||||||
val receiver: FirBasedSymbol<*>?,
|
val receiver: FirBasedSymbol<*>?,
|
||||||
val graph: ControlFlowGraph,
|
val graph: ControlFlowGraph,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@ object FirMemberPropertiesChecker : FirClassChecker(MppCheckerKind.Common) {
|
|||||||
}
|
}
|
||||||
if (memberPropertySymbols.isEmpty()) return null
|
if (memberPropertySymbols.isEmpty()) return null
|
||||||
// TODO, KT-59803: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
// TODO, KT-59803: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
||||||
val data = PropertyInitializationInfoData(memberPropertySymbols, symbol, graph)
|
val data = PropertyInitializationInfoData(memberPropertySymbols, conditionallyInitializedProperties = emptySet(), symbol, graph)
|
||||||
data.checkPropertyAccesses(isForInitialization = true, context, reporter)
|
data.checkPropertyAccesses(isForInitialization = true, context, reporter)
|
||||||
return data.getValue(graph.exitNode)[NormalPath]
|
return data.getValue(graph.exitNode)[NormalPath]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@ private fun FirDeclaration.collectionInitializationInfo(
|
|||||||
if (propertySymbols.isEmpty()) return null
|
if (propertySymbols.isEmpty()) return null
|
||||||
|
|
||||||
// TODO, KT-59803: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
// TODO, KT-59803: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
||||||
val data = PropertyInitializationInfoData(propertySymbols, receiver = null, graph)
|
val data = PropertyInitializationInfoData(propertySymbols, conditionallyInitializedProperties = emptySet(), receiver = null, graph)
|
||||||
data.checkPropertyAccesses(isForInitialization = true, context, reporter)
|
data.checkPropertyAccesses(isForInitialization = true, context, reporter)
|
||||||
return data.getValue(graph.exitNode)[NormalPath]
|
return data.getValue(graph.exitNode)[NormalPath]
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-7
@@ -13,9 +13,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
|||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkersComponent
|
import org.jetbrains.kotlin.fir.analysis.checkersComponent
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
|
import org.jetbrains.kotlin.fir.expressions.FirLoop
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.VariableDeclarationNode
|
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
|
|
||||||
@@ -41,9 +42,10 @@ class ControlFlowAnalysisDiagnosticComponent(
|
|||||||
if (graph.isSubGraph) return
|
if (graph.isSubGraph) return
|
||||||
cfaCheckers.forEach { it.analyze(graph, reporter, context) }
|
cfaCheckers.forEach { it.analyze(graph, reporter, context) }
|
||||||
|
|
||||||
val properties = mutableSetOf<FirPropertySymbol>().apply { graph.traverse(LocalPropertyCollector(this)) }
|
val collector = LocalPropertyCollector().apply { graph.traverse(this) }
|
||||||
|
val properties = collector.properties
|
||||||
if (properties.isNotEmpty()) {
|
if (properties.isNotEmpty()) {
|
||||||
val data = PropertyInitializationInfoData(properties, receiver = null, graph)
|
val data = PropertyInitializationInfoData(properties, collector.conditionallyInitializedProperties, receiver = null, graph)
|
||||||
variableAssignmentCheckers.forEach { it.analyze(data, reporter, context) }
|
variableAssignmentCheckers.forEach { it.analyze(data, reporter, context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,11 +96,60 @@ class ControlFlowAnalysisDiagnosticComponent(
|
|||||||
analyze(constructor, data)
|
analyze(constructor, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LocalPropertyCollector(private val result: MutableSet<FirPropertySymbol>) : ControlFlowGraphVisitorVoid() {
|
private class LocalPropertyCollector : ControlFlowGraphVisitorVoid() {
|
||||||
|
val properties = mutableSetOf<FirPropertySymbol>()
|
||||||
|
|
||||||
|
// Properties which may not be initialized when accessed, even if they have an initializer.
|
||||||
|
val conditionallyInitializedProperties = mutableSetOf<FirPropertySymbol>()
|
||||||
|
|
||||||
|
// Properties defined within do-while loops, and used within the condition of that same do-while loop, are considered conditionally
|
||||||
|
// initialized. It is possible they may not even be defined by the loop condition due to a `continue` in the do-while loop. Track
|
||||||
|
// do-while loop properties so those used in the condition can be recorded.
|
||||||
|
private val doWhileLoopProperties = ArrayDeque<Pair<FirLoop, MutableSet<FirPropertySymbol>>>()
|
||||||
|
private val insideDoWhileConditions = mutableSetOf<FirLoop>()
|
||||||
|
|
||||||
override fun visitNode(node: CFGNode<*>) {}
|
override fun visitNode(node: CFGNode<*>) {}
|
||||||
|
|
||||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||||
result.add(node.fir.symbol)
|
val symbol = node.fir.symbol
|
||||||
|
properties.add(symbol)
|
||||||
|
doWhileLoopProperties.lastOrNull()?.second?.add(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
|
||||||
|
if (insideDoWhileConditions.isNotEmpty()) {
|
||||||
|
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: return
|
||||||
|
|
||||||
|
// It is possible to nest do-while loops within do-while loop conditions via in-place lambda functions. Make sure to check
|
||||||
|
// all properties for all loop conditions.
|
||||||
|
if (doWhileLoopProperties.any { it.first in insideDoWhileConditions && symbol in it.second }) {
|
||||||
|
conditionallyInitializedProperties.add(symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLoopEnterNode(node: LoopEnterNode) {
|
||||||
|
if (node.fir is FirDoWhileLoop) {
|
||||||
|
doWhileLoopProperties.addLast(node.fir to mutableSetOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLoopExitNode(node: LoopExitNode) {
|
||||||
|
if (node.fir is FirDoWhileLoop) {
|
||||||
|
doWhileLoopProperties.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLoopConditionEnterNode(node: LoopConditionEnterNode) {
|
||||||
|
if (node.loop is FirDoWhileLoop) {
|
||||||
|
insideDoWhileConditions.add(node.loop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLoopConditionExitNode(node: LoopConditionExitNode) {
|
||||||
|
if (node.loop is FirDoWhileLoop) {
|
||||||
|
insideDoWhileConditions.remove(node.loop)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -965,7 +965,7 @@ class ControlFlowGraphBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun exitWhileLoopCondition(loop: FirLoop): Pair<LoopConditionExitNode, LoopBlockEnterNode> {
|
fun exitWhileLoopCondition(loop: FirLoop): Pair<LoopConditionExitNode, LoopBlockEnterNode> {
|
||||||
val conditionExitNode = createLoopConditionExitNode(loop.condition).also { addNewSimpleNode(it) }
|
val conditionExitNode = createLoopConditionExitNode(loop.condition, loop).also { addNewSimpleNode(it) }
|
||||||
val conditionConstBooleanValue = loop.condition.booleanLiteralValue
|
val conditionConstBooleanValue = loop.condition.booleanLiteralValue
|
||||||
addEdge(conditionExitNode, loopExitNodes.getValue(loop), propagateDeadness = false, isDead = conditionConstBooleanValue == true)
|
addEdge(conditionExitNode, loopExitNodes.getValue(loop), propagateDeadness = false, isDead = conditionConstBooleanValue == true)
|
||||||
val loopBlockEnterNode = createLoopBlockEnterNode(loop)
|
val loopBlockEnterNode = createLoopBlockEnterNode(loop)
|
||||||
@@ -1007,7 +1007,7 @@ class ControlFlowGraphBuilder {
|
|||||||
|
|
||||||
fun exitDoWhileLoop(loop: FirLoop): Pair<LoopConditionExitNode, LoopExitNode> {
|
fun exitDoWhileLoop(loop: FirLoop): Pair<LoopConditionExitNode, LoopExitNode> {
|
||||||
loopConditionEnterNodes.remove(loop)
|
loopConditionEnterNodes.remove(loop)
|
||||||
val conditionExitNode = createLoopConditionExitNode(loop.condition)
|
val conditionExitNode = createLoopConditionExitNode(loop.condition, loop)
|
||||||
val conditionBooleanValue = loop.condition.booleanLiteralValue
|
val conditionBooleanValue = loop.condition.booleanLiteralValue
|
||||||
popAndAddEdge(conditionExitNode)
|
popAndAddEdge(conditionExitNode)
|
||||||
val blockEnterNode = lastNodes.pop()
|
val blockEnterNode = lastNodes.pop()
|
||||||
|
|||||||
+2
-2
@@ -106,8 +106,8 @@ fun ControlFlowGraphBuilder.createWhenSyntheticElseBranchNode(fir: FirWhenExpres
|
|||||||
fun ControlFlowGraphBuilder.createWhenBranchResultEnterNode(fir: FirWhenBranch): WhenBranchResultEnterNode =
|
fun ControlFlowGraphBuilder.createWhenBranchResultEnterNode(fir: FirWhenBranch): WhenBranchResultEnterNode =
|
||||||
WhenBranchResultEnterNode(currentGraph, fir, levelCounter)
|
WhenBranchResultEnterNode(currentGraph, fir, levelCounter)
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createLoopConditionExitNode(fir: FirExpression): LoopConditionExitNode =
|
fun ControlFlowGraphBuilder.createLoopConditionExitNode(fir: FirExpression, loop: FirLoop): LoopConditionExitNode =
|
||||||
LoopConditionExitNode(currentGraph, fir, levelCounter)
|
LoopConditionExitNode(currentGraph, fir, loop, levelCounter)
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression, loop: FirLoop): LoopConditionEnterNode =
|
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression, loop: FirLoop): LoopConditionEnterNode =
|
||||||
LoopConditionEnterNode(currentGraph, fir, loop, levelCounter)
|
LoopConditionEnterNode(currentGraph, fir, loop, levelCounter)
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpre
|
|||||||
return visitor.visitLoopConditionEnterNode(this, data)
|
return visitor.visitLoopConditionEnterNode(this, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirExpression, level: Int) : CFGNode<FirExpression>(owner, level),
|
class LoopConditionExitNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int) : CFGNode<FirExpression>(owner, level),
|
||||||
ExitNodeMarker {
|
ExitNodeMarker {
|
||||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitLoopConditionExitNode(this, data)
|
return visitor.visitLoopConditionExitNode(this, data)
|
||||||
|
|||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
fun test(cond1: Boolean) {
|
|
||||||
do {
|
|
||||||
if (cond1) continue
|
|
||||||
val cond2 = false
|
|
||||||
} while (cond2) // cond2 may be not defined here
|
|
||||||
}
|
|
||||||
+37
-1
@@ -1,6 +1,42 @@
|
|||||||
fun test(cond1: Boolean) {
|
// FIR_IDENTICAL
|
||||||
|
// SKIP_TXT
|
||||||
|
|
||||||
|
fun test1(cond1: Boolean) {
|
||||||
do {
|
do {
|
||||||
if (cond1) continue
|
if (cond1) continue
|
||||||
val cond2 = false
|
val cond2 = false
|
||||||
} while (<!UNINITIALIZED_VARIABLE!>cond2<!>) // cond2 may be not defined here
|
} while (<!UNINITIALIZED_VARIABLE!>cond2<!>) // cond2 may be not defined here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test2(cond1: Boolean, cond3: Boolean) {
|
||||||
|
do {
|
||||||
|
if (cond1) continue
|
||||||
|
val cond2 = false
|
||||||
|
} while (
|
||||||
|
run {
|
||||||
|
do {
|
||||||
|
if (cond3) continue
|
||||||
|
val cond4 = false
|
||||||
|
} while (<!UNINITIALIZED_VARIABLE!>cond4<!>) // cond4 may be not defined here
|
||||||
|
|
||||||
|
<!UNINITIALIZED_VARIABLE!>cond2<!> // cond2 may be not defined here
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun test3(cond1: Boolean, cond3: Boolean) {
|
||||||
|
do {
|
||||||
|
if (cond1) continue
|
||||||
|
val cond2 = false
|
||||||
|
} while (
|
||||||
|
run {
|
||||||
|
do {
|
||||||
|
if (cond3) continue
|
||||||
|
val cond4 = false
|
||||||
|
} while (<!UNINITIALIZED_VARIABLE!>cond2<!> && <!UNINITIALIZED_VARIABLE!>cond4<!>) // cond2 and cond4 may be not defined here
|
||||||
|
|
||||||
|
cond3
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package
|
|
||||||
|
|
||||||
public fun test(/*0*/ cond1: kotlin.Boolean): kotlin.Unit
|
|
||||||
Reference in New Issue
Block a user