FIR checker: report val reassignment
This commit is contained in:
committed by
Dmitriy Novozhilov
parent
b128577508
commit
f1fa290d49
+1
-1
@@ -135,7 +135,7 @@ FILE: CanBeValChecker.kt
|
||||
}
|
||||
|
||||
lval b: R|kotlin/String|
|
||||
R|<local>/bool| = Boolean(false)
|
||||
R|<local>/b| = Boolean(false)
|
||||
}
|
||||
public final fun cycles(): R|kotlin/Unit| {
|
||||
lvar a: R|kotlin/Int| = Int(10)
|
||||
|
||||
+2
-2
@@ -105,9 +105,9 @@ fun foo() {
|
||||
<!VARIABLE_NEVER_READ{LT}!><!CAN_BE_VAL!>var<!> <!VARIABLE_NEVER_READ{PSI}!>a<!>: Int<!>
|
||||
val bool = true
|
||||
if (bool) <!ASSIGNED_VALUE_IS_NEVER_READ!>a<!> = 4 else <!ASSIGNED_VALUE_IS_NEVER_READ!>a<!> = 42
|
||||
<!UNUSED_VARIABLE{LT}!>val <!UNUSED_VARIABLE{PSI}!>b<!>: String<!>
|
||||
<!VARIABLE_NEVER_READ{LT}!>val <!VARIABLE_NEVER_READ{PSI}!>b<!>: String<!>
|
||||
|
||||
<!ASSIGNED_VALUE_IS_NEVER_READ!>bool<!> = false
|
||||
<!ASSIGNED_VALUE_IS_NEVER_READ!>b<!> = false
|
||||
}
|
||||
|
||||
fun cycles() {
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ fun myRun(block: () -> Unit) {
|
||||
fun test_1() {
|
||||
val x: Int
|
||||
inlineRun {
|
||||
x = 1
|
||||
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||
}
|
||||
x.inc()
|
||||
}
|
||||
@@ -27,7 +27,7 @@ fun test_1() {
|
||||
fun test_2() {
|
||||
val x: Int
|
||||
myRun {
|
||||
x = 1
|
||||
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||
}
|
||||
x.inc()
|
||||
}
|
||||
+2
-2
@@ -19,7 +19,7 @@ fun myRun(block: () -> Unit) {
|
||||
fun test_1() {
|
||||
val x: Int
|
||||
inlineRun {
|
||||
x = 1
|
||||
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||
}
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
@@ -27,7 +27,7 @@ fun test_1() {
|
||||
fun test_2() {
|
||||
val x: Int
|
||||
myRun {
|
||||
x = 1
|
||||
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||
}
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
+4
-1
@@ -355,7 +355,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
}
|
||||
|
||||
val CONTROL_FLOW by object : DiagnosticGroup("Control flow diagnostics") {
|
||||
val UNINITIALIZED_VARIABLE by error<FirSourceElement, PsiElement> {
|
||||
val UNINITIALIZED_VARIABLE by error<FirSourceElement, KtSimpleNameExpression> {
|
||||
parameter<FirPropertySymbol>("variable")
|
||||
}
|
||||
val VAL_REASSIGNMENT by error<FirSourceElement, KtExpression> {
|
||||
parameter<FirPropertySymbol>("variable")
|
||||
}
|
||||
val WRONG_INVOCATION_KIND by warning<FirSourceElement, PsiElement> {
|
||||
|
||||
+3
-1
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||
@@ -233,7 +234,8 @@ object FirErrors {
|
||||
val COMPONENT_FUNCTION_ON_NULLABLE by error1<FirSourceElement, KtExpression, Name>()
|
||||
|
||||
// Control flow diagnostics
|
||||
val UNINITIALIZED_VARIABLE by error1<FirSourceElement, PsiElement, FirPropertySymbol>()
|
||||
val UNINITIALIZED_VARIABLE by error1<FirSourceElement, KtSimpleNameExpression, FirPropertySymbol>()
|
||||
val VAL_REASSIGNMENT by error1<FirSourceElement, KtExpression, FirPropertySymbol>()
|
||||
val WRONG_INVOCATION_KIND by warning3<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>, EventOccurrencesRange, EventOccurrencesRange>()
|
||||
val LEAKED_IN_PLACE_LAMBDA by error1<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>>()
|
||||
val WRONG_IMPLIES_CONDITION by warning0<FirSourceElement, PsiElement>()
|
||||
|
||||
@@ -8,9 +8,15 @@ package org.jetbrains.kotlin.fir.analysis.cfa
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.util.SetMultimap
|
||||
import org.jetbrains.kotlin.fir.util.setMultimapOf
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
|
||||
abstract class EventOccurrencesRangeInfo<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
|
||||
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
||||
@@ -74,8 +80,10 @@ class PathAwarePropertyInitializationInfo(
|
||||
::EMPTY
|
||||
}
|
||||
|
||||
class PropertyInitializationInfoCollector(private val localProperties: Set<FirPropertySymbol>) :
|
||||
ControlFlowGraphVisitor<PathAwarePropertyInitializationInfo, Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>>() {
|
||||
class PropertyInitializationInfoCollector(
|
||||
private val localProperties: Set<FirPropertySymbol>,
|
||||
private val declaredVariableCollector: DeclaredVariableCollector = DeclaredVariableCollector(),
|
||||
) : ControlFlowGraphVisitor<PathAwarePropertyInitializationInfo, Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>>() {
|
||||
override fun visitNode(
|
||||
node: CFGNode<*>,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
@@ -104,11 +112,11 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val dataForNode = visitNode(node, data)
|
||||
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
||||
dataForNode
|
||||
} else {
|
||||
processVariableWithAssignment(dataForNode, node.fir.symbol)
|
||||
}
|
||||
return processVariableWithAssignment(
|
||||
dataForNode,
|
||||
node.fir.symbol,
|
||||
overwriteRange = node.fir.initializer == null && node.fir.delegate == null
|
||||
)
|
||||
}
|
||||
|
||||
fun getData(graph: ControlFlowGraph) =
|
||||
@@ -120,10 +128,160 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
||||
|
||||
private fun processVariableWithAssignment(
|
||||
dataForNode: PathAwarePropertyInitializationInfo,
|
||||
symbol: FirPropertySymbol
|
||||
symbol: FirPropertySymbol,
|
||||
overwriteRange: Boolean = false,
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
assert(dataForNode.keys.isNotEmpty())
|
||||
return addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE, ::PathAwarePropertyInitializationInfo)
|
||||
return if (overwriteRange)
|
||||
overwriteRange(dataForNode, symbol, EventOccurrencesRange.ZERO, ::PathAwarePropertyInitializationInfo)
|
||||
else
|
||||
addRange(dataForNode, symbol, EventOccurrencesRange.EXACTLY_ONCE, ::PathAwarePropertyInitializationInfo)
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Data flows of declared/assigned variables in loops
|
||||
// --------------------------------------------------
|
||||
|
||||
private fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> =
|
||||
declaredVariableCollector.enterCapturingStatement(statement)
|
||||
|
||||
private fun exitCapturingStatement(statement: FirStatement) {
|
||||
declaredVariableCollector.exitCapturingStatement(statement)
|
||||
}
|
||||
|
||||
// A merge point for a loop with `continue`
|
||||
override fun visitLoopEnterNode(
|
||||
node: LoopEnterNode,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val declaredVariableSymbolsInLoop = enterCapturingStatement(node.fir)
|
||||
if (declaredVariableSymbolsInLoop.isEmpty())
|
||||
return visitNode(node, data)
|
||||
|
||||
return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data)
|
||||
}
|
||||
|
||||
// A merge point for while loop
|
||||
override fun visitLoopConditionEnterNode(
|
||||
node: LoopConditionEnterNode,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val declaredVariableSymbolsInLoop = declaredVariableCollector.declaredVariablesPerElement[node.loop]
|
||||
if (declaredVariableSymbolsInLoop.isEmpty())
|
||||
return visitNode(node, data)
|
||||
|
||||
return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data)
|
||||
}
|
||||
|
||||
// A merge point for do-while loop
|
||||
override fun visitLoopBlockEnterNode(
|
||||
node: LoopBlockEnterNode,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val declaredVariableSymbolsInLoop = declaredVariableCollector.declaredVariablesPerElement[node.fir]
|
||||
if (declaredVariableSymbolsInLoop.isEmpty())
|
||||
return visitNode(node, data)
|
||||
|
||||
return filterDeclaredVariableSymbolsInCapturedScope(node, declaredVariableSymbolsInLoop, data)
|
||||
}
|
||||
|
||||
private fun filterDeclaredVariableSymbolsInCapturedScope(
|
||||
node: CFGNode<*>,
|
||||
declaredVariableSymbolsInCapturedScope: Collection<FirPropertySymbol>,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
var filteredData = data
|
||||
for (variableSymbol in declaredVariableSymbolsInCapturedScope) {
|
||||
filteredData = filteredData.map { (label, pathAwareInfo) ->
|
||||
label to if (label is LoopBackPath) {
|
||||
removeRange(pathAwareInfo, variableSymbol, ::PathAwarePropertyInitializationInfo)
|
||||
} else {
|
||||
pathAwareInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
return visitNode(node, filteredData)
|
||||
}
|
||||
|
||||
override fun visitLoopExitNode(
|
||||
node: LoopExitNode,
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
exitCapturingStatement(node.fir)
|
||||
return visitNode(node, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that [PreliminaryLoopVisitor] in FIR DFA collects assigned variable names.
|
||||
// This one collects declared variable symbols per capturing statements.
|
||||
class DeclaredVariableCollector {
|
||||
val declaredVariablesPerElement: SetMultimap<FirStatement, FirPropertySymbol> = setMultimapOf()
|
||||
|
||||
fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> {
|
||||
assert(statement is FirLoop || statement is FirClass<*> || statement is FirFunction<*>)
|
||||
if (statement !in declaredVariablesPerElement) {
|
||||
statement.accept(visitor, null)
|
||||
}
|
||||
return declaredVariablesPerElement[statement]
|
||||
}
|
||||
|
||||
fun exitCapturingStatement(statement: FirStatement) {
|
||||
assert(statement is FirLoop || statement is FirClass<*> || statement is FirFunction<*>)
|
||||
declaredVariablesPerElement.removeKey(statement)
|
||||
}
|
||||
|
||||
fun resetState() {
|
||||
declaredVariablesPerElement.clear()
|
||||
}
|
||||
|
||||
// FirStatement -- closest statement (loop/lambda/local declaration) which may contain reassignments
|
||||
private val visitor = object : FirVisitor<Unit, FirStatement?>() {
|
||||
override fun visitElement(element: FirElement, data: FirStatement?) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitProperty(property: FirProperty, data: FirStatement?) {
|
||||
if (property.isLocal) {
|
||||
requireNotNull(data)
|
||||
declaredVariablesPerElement.put(data, property.symbol)
|
||||
}
|
||||
visitElement(property, data)
|
||||
}
|
||||
|
||||
override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(whileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(doWhileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousFunction, data)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(simpleFunction, data)
|
||||
}
|
||||
|
||||
override fun <F : FirFunction<F>> visitFunction(function: FirFunction<F>, data: FirStatement?) {
|
||||
visitCapturingStatement(function, data)
|
||||
}
|
||||
|
||||
override fun visitRegularClass(regularClass: FirRegularClass, data: FirStatement?) {
|
||||
visitCapturingStatement(regularClass, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousObject, data)
|
||||
}
|
||||
|
||||
private fun visitCapturingStatement(statement: FirStatement, parent: FirStatement?) {
|
||||
visitElement(statement, statement)
|
||||
if (parent != null) {
|
||||
declaredVariablesPerElement.putAll(parent, declaredVariablesPerElement[statement])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,15 +290,54 @@ internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, Even
|
||||
key: K,
|
||||
range: EventOccurrencesRange,
|
||||
constructor: (PersistentMap<EdgeLabel, S>) -> P
|
||||
): P {
|
||||
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
||||
// after (if key is p1):
|
||||
// { |-> { p1 |-> PI1 + r }, l1 |-> { p1 |-> r, p2 |-> PI2 } }
|
||||
return updateRange(pathAwareInfo, key, { existingKind -> existingKind + range }, constructor)
|
||||
}
|
||||
|
||||
internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> overwriteRange(
|
||||
pathAwareInfo: P,
|
||||
key: K,
|
||||
range: EventOccurrencesRange,
|
||||
constructor: (PersistentMap<EdgeLabel, S>) -> P
|
||||
): P {
|
||||
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
||||
// after (if key is p1):
|
||||
// { |-> { p1 |-> r }, l1 |-> { p1 |-> r, p2 |-> PI2 } }
|
||||
return updateRange(pathAwareInfo, key, { range }, constructor)
|
||||
}
|
||||
|
||||
private inline fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> updateRange(
|
||||
pathAwareInfo: P,
|
||||
key: K,
|
||||
computeNewRange: (EventOccurrencesRange) -> EventOccurrencesRange,
|
||||
constructor: (PersistentMap<EdgeLabel, S>) -> P
|
||||
): P {
|
||||
var resultMap = persistentMapOf<EdgeLabel, S>()
|
||||
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
||||
for ((label, dataPerLabel) in pathAwareInfo) {
|
||||
val existingKind = dataPerLabel[key] ?: EventOccurrencesRange.ZERO
|
||||
val kind = existingKind + range
|
||||
val kind = computeNewRange.invoke(existingKind)
|
||||
resultMap = resultMap.put(label, dataPerLabel.put(key, kind))
|
||||
}
|
||||
// after (if key is p1):
|
||||
// { |-> { p1 |-> PI1 + r }, l1 |-> { p1 |-> r, p2 |-> PI2 } }
|
||||
// { |-> { p1 |-> computeNewRange(PI1) }, l1 |-> { p1 |-> r, p2 |-> PI2 } }
|
||||
return constructor(resultMap)
|
||||
}
|
||||
|
||||
private fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> removeRange(
|
||||
pathAwareInfo: P,
|
||||
key: K,
|
||||
constructor: (PersistentMap<EdgeLabel, S>) -> P
|
||||
): P {
|
||||
var resultMap = persistentMapOf<EdgeLabel, S>()
|
||||
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
||||
for ((label, dataPerLabel) in pathAwareInfo) {
|
||||
resultMap = resultMap.put(label, dataPerLabel.remove(key))
|
||||
}
|
||||
// after (if key is p1):
|
||||
// { |-> { }, l1 |-> { p2 |-> PI2 } }
|
||||
return constructor(resultMap)
|
||||
}
|
||||
|
||||
@@ -31,5 +31,9 @@ abstract class ControlFlowInfo<S : ControlFlowInfo<S, K, V>, K : Any, V : Any> p
|
||||
return constructor(map.put(key, value))
|
||||
}
|
||||
|
||||
override fun remove(key: K): S {
|
||||
return constructor(map.remove(key))
|
||||
}
|
||||
|
||||
abstract fun merge(other: S): S
|
||||
}
|
||||
|
||||
+43
-8
@@ -6,16 +6,15 @@
|
||||
package org.jetbrains.kotlin.fir.analysis.cfa
|
||||
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.contracts.description.canBeRevisited
|
||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.isLateInit
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitorVoid
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.QualifiedAccessNode
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||
|
||||
@@ -34,11 +33,11 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
|
||||
val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet()
|
||||
|
||||
val reporterVisitor = UninitializedPropertyReporter(localData, localProperties, reporter, context)
|
||||
val reporterVisitor = PropertyReporter(localData, localProperties, reporter, context)
|
||||
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||
}
|
||||
|
||||
private class UninitializedPropertyReporter(
|
||||
private class PropertyReporter(
|
||||
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||
val localProperties: Set<FirPropertySymbol>,
|
||||
val reporter: DiagnosticReporter,
|
||||
@@ -46,6 +45,38 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
) : ControlFlowGraphVisitorVoid() {
|
||||
override fun visitNode(node: CFGNode<*>) {}
|
||||
|
||||
private fun getPropertySymbol(node: CFGNode<*>): FirPropertySymbol? {
|
||||
val reference = (node.fir as? FirQualifiedAccess)?.calleeReference as? FirResolvedNamedReference ?: return null
|
||||
return reference.resolvedSymbol as? FirPropertySymbol
|
||||
}
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
val symbol = getPropertySymbol(node) ?: return
|
||||
val pathAwareInfo = data.getValue(node)
|
||||
for (label in pathAwareInfo.keys) {
|
||||
if (investigateVariableAssignment(pathAwareInfo[label]!!, symbol, node)) {
|
||||
// To avoid duplicate reports, stop investigating remaining paths if the property is re-initialized at any path.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun investigateVariableAssignment(
|
||||
info: PropertyInitializationInfo,
|
||||
symbol: FirPropertySymbol,
|
||||
node: VariableAssignmentNode
|
||||
): Boolean {
|
||||
val kind = info[symbol] ?: EventOccurrencesRange.ZERO
|
||||
if (symbol.fir.isVal && kind.canBeRevisited()) {
|
||||
node.fir.lValue.source?.let {
|
||||
// TODO: differentiate CAPTURED_VAL_INITIALIZATION
|
||||
reporter.report(FirErrors.VAL_REASSIGNMENT.on(it, symbol), context)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
|
||||
val reference = node.fir.calleeReference as? FirResolvedNamedReference ?: return
|
||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return
|
||||
@@ -53,14 +84,18 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
if (symbol.fir.isLateInit) return
|
||||
val pathAwareInfo = data.getValue(node)
|
||||
for (info in pathAwareInfo.values) {
|
||||
if (investigate(info, symbol, node)) {
|
||||
if (investigateVariableAccess(info, symbol, node)) {
|
||||
// To avoid duplicate reports, stop investigating remaining paths if the property is not initialized at any path.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun investigate(info: PropertyInitializationInfo, symbol: FirPropertySymbol, node: QualifiedAccessNode): Boolean {
|
||||
private fun investigateVariableAccess(
|
||||
info: PropertyInitializationInfo,
|
||||
symbol: FirPropertySymbol,
|
||||
node: QualifiedAccessNode
|
||||
): Boolean {
|
||||
val kind = info[symbol] ?: EventOccurrencesRange.ZERO
|
||||
if (!kind.isDefinitelyVisited()) {
|
||||
node.fir.source?.let {
|
||||
|
||||
+2
@@ -171,6 +171,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_OPERATOR_C
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNUSED_VARIABLE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_REASSIGNMENT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAL_WITH_SETTER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_EXPECTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_INITIALIZER_IS_REDUNDANT
|
||||
@@ -510,6 +511,7 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
|
||||
// Control flow diagnostics
|
||||
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", PROPERTY_NAME)
|
||||
map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", PROPERTY_NAME)
|
||||
map.put(
|
||||
WRONG_INVOCATION_KIND,
|
||||
"{2} wrong invocation kind: given {3} case, but {4} case is possible",
|
||||
|
||||
@@ -394,7 +394,7 @@ class LoopBlockExitNode(owner: ControlFlowGraph, override val fir: FirLoop, leve
|
||||
return visitor.visitLoopBlockExitNode(this, data)
|
||||
}
|
||||
}
|
||||
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, level: Int, id: Int) : CFGNode<FirExpression>(owner, level, id), EnterNodeMarker {
|
||||
class LoopConditionEnterNode(owner: ControlFlowGraph, override val fir: FirExpression, val loop: FirLoop, level: Int, id: Int) : CFGNode<FirExpression>(owner, level, id), EnterNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitLoopConditionEnterNode(this, data)
|
||||
}
|
||||
|
||||
@@ -133,6 +133,11 @@ object NormalPath : EdgeLabel(label = null) {
|
||||
get() = true
|
||||
}
|
||||
|
||||
object LoopBackPath : EdgeLabel(label = null) {
|
||||
override val isNormal: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
|
||||
|
||||
// TODO: Label `return`ing edge with this.
|
||||
|
||||
+10
-5
@@ -657,7 +657,7 @@ class ControlFlowGraphBuilder {
|
||||
}
|
||||
loopExitNodes.push(createLoopExitNode(loop))
|
||||
levelCounter++
|
||||
val conditionEnterNode = createLoopConditionEnterNode(loop.condition).also {
|
||||
val conditionEnterNode = createLoopConditionEnterNode(loop.condition, loop).also {
|
||||
addNewSimpleNode(it)
|
||||
// put conditional node twice so we can refer it after exit from loop block
|
||||
lastNodes.push(it)
|
||||
@@ -686,7 +686,7 @@ class ControlFlowGraphBuilder {
|
||||
if (lastNodes.isNotEmpty) {
|
||||
val conditionEnterNode = lastNodes.pop()
|
||||
require(conditionEnterNode is LoopConditionEnterNode) { loop.render() }
|
||||
addBackEdge(loopBlockExitNode, conditionEnterNode)
|
||||
addBackEdge(loopBlockExitNode, conditionEnterNode, label = LoopBackPath)
|
||||
}
|
||||
val loopExitNode = loopExitNodes.pop()
|
||||
loopExitNode.updateDeadStatus()
|
||||
@@ -714,7 +714,7 @@ class ControlFlowGraphBuilder {
|
||||
fun enterDoWhileLoopCondition(loop: FirLoop): Pair<LoopBlockExitNode, LoopConditionEnterNode> {
|
||||
levelCounter--
|
||||
val blockExitNode = createLoopBlockExitNode(loop).also { addNewSimpleNode(it) }
|
||||
val conditionEnterNode = createLoopConditionEnterNode(loop.condition).also { addNewSimpleNode(it) }
|
||||
val conditionEnterNode = createLoopConditionEnterNode(loop.condition, loop).also { addNewSimpleNode(it) }
|
||||
levelCounter++
|
||||
return blockExitNode to conditionEnterNode
|
||||
}
|
||||
@@ -727,7 +727,7 @@ class ControlFlowGraphBuilder {
|
||||
popAndAddEdge(conditionExitNode)
|
||||
val blockEnterNode = lastNodes.pop()
|
||||
require(blockEnterNode is LoopBlockEnterNode)
|
||||
addBackEdge(conditionExitNode, blockEnterNode, isDead = conditionBooleanValue == false)
|
||||
addBackEdge(conditionExitNode, blockEnterNode, isDead = conditionBooleanValue == false, label = LoopBackPath)
|
||||
val loopExit = loopExitNodes.pop()
|
||||
addEdge(conditionExitNode, loopExit, propagateDeadness = false, isDead = conditionBooleanValue == true)
|
||||
loopExit.updateDeadStatus()
|
||||
@@ -1261,7 +1261,12 @@ class ControlFlowGraphBuilder {
|
||||
popAndAddEdge(node, preferredKind)
|
||||
if (targetNode != null) {
|
||||
if (isBack) {
|
||||
addBackEdge(node, targetNode)
|
||||
if (targetNode is LoopEnterNode) {
|
||||
// `continue` to the loop header
|
||||
addBackEdge(node, targetNode, label = LoopBackPath)
|
||||
} else {
|
||||
addBackEdge(node, targetNode)
|
||||
}
|
||||
} else {
|
||||
addEdge(node, targetNode, propagateDeadness = false)
|
||||
}
|
||||
|
||||
+2
-2
@@ -113,8 +113,8 @@ fun ControlFlowGraphBuilder.createWhenBranchResultEnterNode(fir: FirWhenBranch):
|
||||
fun ControlFlowGraphBuilder.createLoopConditionExitNode(fir: FirExpression): LoopConditionExitNode =
|
||||
LoopConditionExitNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression): LoopConditionEnterNode =
|
||||
LoopConditionEnterNode(currentGraph, fir, levelCounter, createId())
|
||||
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression, loop: FirLoop): LoopConditionEnterNode =
|
||||
LoopConditionEnterNode(currentGraph, fir, loop, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createLoopBlockEnterNode(fir: FirLoop): LoopBlockEnterNode =
|
||||
LoopBlockEnterNode(currentGraph, fir, levelCounter, createId())
|
||||
|
||||
Reference in New Issue
Block a user