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())
|
||||
|
||||
Vendored
+4
-4
@@ -63,7 +63,7 @@ fun t4(a: A) {
|
||||
|
||||
fun t1() {
|
||||
val a : Int = 1
|
||||
a = 2
|
||||
<!VAL_REASSIGNMENT!>a<!> = 2
|
||||
|
||||
var b : Int = 1
|
||||
b = 3
|
||||
@@ -83,8 +83,8 @@ enum class ProtocolState {
|
||||
|
||||
fun t3() {
|
||||
val x: ProtocolState = ProtocolState.WAITING
|
||||
x = x.signal()
|
||||
x = x.signal() //repeat for x
|
||||
<!VAL_REASSIGNMENT!>x<!> = x.signal()
|
||||
<!VAL_REASSIGNMENT!>x<!> = x.signal() //repeat for x
|
||||
}
|
||||
|
||||
fun t4() {
|
||||
@@ -326,7 +326,7 @@ fun func() {
|
||||
val a = object {
|
||||
val x = b
|
||||
init {
|
||||
b = 4
|
||||
<!VAL_REASSIGNMENT!>b<!> = 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -24,7 +24,7 @@ fun assignedInTryAndCatch() {
|
||||
try {
|
||||
a = 42
|
||||
} catch (e: Exception) {
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
} finally {
|
||||
}
|
||||
a.hashCode()
|
||||
@@ -37,7 +37,7 @@ fun sideEffectBeforeAssignedInTryAndCatch(s: Any) {
|
||||
a = 42
|
||||
} catch (e: Exception) {
|
||||
s as String // Potential cast exception
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
} finally {
|
||||
}
|
||||
a.hashCode()
|
||||
@@ -48,9 +48,9 @@ fun assignedAtAll() {
|
||||
try {
|
||||
a = 42
|
||||
} catch (e: Exception) {
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
} finally {
|
||||
a = 40
|
||||
<!VAL_REASSIGNMENT!>a<!> = 40
|
||||
}
|
||||
a.hashCode()
|
||||
}
|
||||
@@ -62,9 +62,9 @@ fun sideEffectBeforeAssignedInTryCatchButNotFinally(s: Any) {
|
||||
a = 42
|
||||
} catch (e: Exception) {
|
||||
s as String // Potential cast exception
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
} finally {
|
||||
a = 40
|
||||
<!VAL_REASSIGNMENT!>a<!> = 40
|
||||
}
|
||||
a.hashCode()
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ fun assignedInTryAndFinally() {
|
||||
try {
|
||||
a = 42
|
||||
} finally {
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
}
|
||||
a.hashCode()
|
||||
}
|
||||
@@ -33,7 +33,7 @@ fun sideEffectBeforeAssignmentInTryButNotFinally(s: Any) {
|
||||
s as String // Potential cast exception
|
||||
a = 42
|
||||
} finally {
|
||||
a = 41
|
||||
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||
}
|
||||
a.hashCode()
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// See KT-15334: incorrect reassignment in do...while
|
||||
|
||||
fun test() {
|
||||
do {
|
||||
val s: String
|
||||
s = ""
|
||||
} while (s == "")
|
||||
}
|
||||
|
||||
fun test2() {
|
||||
do {
|
||||
val s: String
|
||||
s = "1"
|
||||
s = s + "2"
|
||||
} while (s == "1")
|
||||
}
|
||||
|
||||
fun test3() {
|
||||
val s: String
|
||||
do {
|
||||
s = ""
|
||||
} while (s != "")
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// See KT-15334: incorrect reassignment in do...while
|
||||
|
||||
fun test() {
|
||||
|
||||
+6
-6
@@ -8,7 +8,7 @@ fun f1() {
|
||||
}
|
||||
catch (e: Exception) {
|
||||
// KT-13612: reassignment
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
@@ -20,7 +20,7 @@ fun f2() {
|
||||
throw Exception()
|
||||
}
|
||||
finally {
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
@@ -33,7 +33,7 @@ fun g1(flag: Boolean) {
|
||||
}
|
||||
catch (e: Exception) {
|
||||
// KT-13612: ? reassignment or definite assignment ?
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
@@ -45,7 +45,7 @@ fun g2(flag: Boolean) {
|
||||
n = 1
|
||||
}
|
||||
finally {
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
@@ -84,7 +84,7 @@ fun k1(flag: Boolean) {
|
||||
}
|
||||
catch (e: Exception) {
|
||||
// KT-13612: reassignment
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
@@ -96,7 +96,7 @@ fun k2(flag: Boolean) {
|
||||
j(flag)
|
||||
}
|
||||
finally {
|
||||
n = 2
|
||||
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||
}
|
||||
n.hashCode()
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ class A {
|
||||
|
||||
fun foo(list: List<A>) {
|
||||
for (var (c1, c2, c3) in list) {
|
||||
c1 = 1
|
||||
<!VAL_REASSIGNMENT!>c1<!> = 1
|
||||
c3 + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
fun foo(k: Int): Int {
|
||||
val i: Int
|
||||
for (j in 1..k) {
|
||||
i = j
|
||||
<!VAL_REASSIGNMENT!>i<!> = j
|
||||
}
|
||||
i = 6
|
||||
<!VAL_REASSIGNMENT!>i<!> = 6
|
||||
return i
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(): Int {
|
||||
val i: Int
|
||||
var j = 0
|
||||
do {
|
||||
i = ++j
|
||||
} while (j < 5)
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(): Int {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
val i: Int
|
||||
if (f) {}
|
||||
else {
|
||||
i = 2
|
||||
}
|
||||
i = 3
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(k: Int): Int {
|
||||
val i: Int
|
||||
for (j in 1..k) {
|
||||
i = j
|
||||
}
|
||||
return <!UNINITIALIZED_VARIABLE!>i<!>
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(k: Int): Int {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
val i: Int
|
||||
if (f) {
|
||||
i = 1
|
||||
}
|
||||
i = 3
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
val i: Int
|
||||
if (f) {
|
||||
i = 1
|
||||
}
|
||||
else {
|
||||
i = 2
|
||||
}
|
||||
i = 3
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
val i: Int
|
||||
when (f) {
|
||||
true -> i = 1
|
||||
}
|
||||
i = 3
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(f: Boolean): Int {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(): Int {
|
||||
val i: Int
|
||||
var j = 0
|
||||
while (true) {
|
||||
i = ++j
|
||||
if (j > 5) break
|
||||
}
|
||||
return i
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||
|
||||
fun foo(): Int {
|
||||
|
||||
+3
-3
@@ -16,8 +16,8 @@ fun foo(a: Boolean, b: Boolean): Int {
|
||||
x = 1
|
||||
}
|
||||
when (b) {
|
||||
true -> x = 2
|
||||
false -> x = 3
|
||||
true -> <!VAL_REASSIGNMENT!>x<!> = 2
|
||||
false -> <!VAL_REASSIGNMENT!>x<!> = 3
|
||||
}
|
||||
return x
|
||||
}
|
||||
@@ -28,7 +28,7 @@ fun bar(a: Boolean, b: Boolean): Int {
|
||||
x = 1
|
||||
}
|
||||
when (b) {
|
||||
false -> x = 3
|
||||
false -> <!VAL_REASSIGNMENT!>x<!> = 3
|
||||
}
|
||||
return <!UNINITIALIZED_VARIABLE!>x<!>
|
||||
}
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// !LANGUAGE: +VariableDeclarationInWhenSubject
|
||||
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE
|
||||
|
||||
fun foo(): Any = 42
|
||||
|
||||
fun test1(x: Any) {
|
||||
when (val y = foo()) {
|
||||
is String -> y = ""
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +VariableDeclarationInWhenSubject
|
||||
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE
|
||||
|
||||
|
||||
+3
-3
@@ -33,12 +33,12 @@ fun test() {
|
||||
// Loop executed exectly once, initializing x
|
||||
myRun { x.inc() }
|
||||
|
||||
myRun { x = 42 }
|
||||
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
break
|
||||
}
|
||||
// x is I?D here because loop could've been execited
|
||||
// x is ID? here because loop could've been execited
|
||||
// VAL_REASSIGNMENT isn't reported because of repeating diagnostic
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
// x is ID now
|
||||
}
|
||||
else
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ fun test() {
|
||||
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
break
|
||||
}
|
||||
// x is I?D here because loop could've been execited
|
||||
// x is ID? here because loop could've been execited
|
||||
// VAL_REASSIGNMENT isn't reported because of repeating diagnostic
|
||||
x = 42
|
||||
// x is ID now
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ fun threeLevelsReturnNoInitialization(x: Int?): Int? {
|
||||
}
|
||||
}
|
||||
// Possible to report unreachable here
|
||||
y = 54
|
||||
<!VAL_REASSIGNMENT!>y<!> = 54
|
||||
}
|
||||
return <!UNINITIALIZED_VARIABLE!>y<!>.inc()
|
||||
}
|
||||
|
||||
+3
-3
@@ -27,13 +27,13 @@ fun outerFinallyInitializes() {
|
||||
log()
|
||||
}
|
||||
// possible reassignment if innerComputation finished
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
// x is ID here
|
||||
}
|
||||
|
||||
// Definite reassignment here, cause can get here only if myRun finished
|
||||
// Not reported because of repeating diagnostic
|
||||
x = outerComputation()
|
||||
<!VAL_REASSIGNMENT!>x<!> = outerComputation()
|
||||
} catch (e: java.lang.Exception) {
|
||||
// can catch exception thrown by the inner, so x can be not initialized
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
@@ -41,7 +41,7 @@ fun outerFinallyInitializes() {
|
||||
} finally {
|
||||
// Possible reassignment (e.g. if everything finished)
|
||||
// Not reported because of repeating diagnostic
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
}
|
||||
|
||||
// Properly initialized
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ fun innerTryCatchInitializes() {
|
||||
}
|
||||
catch (e: java.lang.Exception) {
|
||||
// Potential reassignment because x.inc() could threw
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
x.inc()
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ fun innerTryCatchInitializes() {
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
|
||||
// Potential reasignment
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
}
|
||||
// Here x=I because outer try-catch either exited normally (x=I) or catched exception (x=I, with reassingment, though)
|
||||
x.inc()
|
||||
|
||||
Vendored
-58
@@ -1,58 +0,0 @@
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
import kotlin.contracts.*
|
||||
|
||||
fun <T> myRun(block: () -> T): T {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
fun someComputation(): Int = 42
|
||||
|
||||
fun tryCatchInlined() {
|
||||
val x: Int
|
||||
|
||||
myRun {
|
||||
try {
|
||||
x = someComputation()
|
||||
x.inc()
|
||||
}
|
||||
catch (e: java.lang.Exception) {
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
}
|
||||
x = 42
|
||||
x.inc()
|
||||
}
|
||||
|
||||
fun possibleReassignmentInTryCatch() {
|
||||
val x: Int
|
||||
|
||||
myRun {
|
||||
try {
|
||||
x = someComputation()
|
||||
x.inc()
|
||||
}
|
||||
catch (e: java.lang.Exception) {
|
||||
x = 42
|
||||
x.inc()
|
||||
}
|
||||
x.inc()
|
||||
}
|
||||
x.inc()
|
||||
}
|
||||
|
||||
fun tryCatchOuter() {
|
||||
val x: Int
|
||||
try {
|
||||
myRun { x = someComputation() }
|
||||
x.inc()
|
||||
}
|
||||
catch (e: java.lang.Exception) {
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
+2
-2
@@ -23,10 +23,10 @@ fun innerTryCatchFinally() {
|
||||
x = someComputation()
|
||||
report(x)
|
||||
} catch (e: java.lang.Exception) {
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
report(x)
|
||||
} finally {
|
||||
x = 0
|
||||
<!VAL_REASSIGNMENT!>x<!> = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -22,7 +22,7 @@ fun <T> runOnce(block: () -> T): T {
|
||||
fun valueReassignment() {
|
||||
val x: Int
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
runTwice { x = 42 }
|
||||
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
x.inc()
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ fun branchingFlow(a: Any?) {
|
||||
val x: Int
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
if (a is String) {
|
||||
runTwice { x = 42 }
|
||||
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
else {
|
||||
x = 43
|
||||
@@ -47,7 +47,7 @@ fun branchingFlow(a: Any?) {
|
||||
fun branchingFlowWithMissingBranches(a: Any?) {
|
||||
val x: Int
|
||||
if (a is String) {
|
||||
runTwice { x = 42 }
|
||||
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
@@ -58,7 +58,7 @@ fun repeatingFlow(n: Int) {
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
|
||||
for (i in 1..n) {
|
||||
runTwice { x = 42 }
|
||||
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
@@ -68,7 +68,7 @@ fun repeatingFlow2(n: Int) {
|
||||
val x: Int
|
||||
|
||||
for (i in 1..n) {
|
||||
runTwice { x = 42 }
|
||||
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
import kotlin.contracts.*
|
||||
|
||||
fun <T> myRun(block: () -> T): T {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
fun reassignmentInUsualFlow() {
|
||||
val x: Int
|
||||
myRun { x = 42 }
|
||||
x = 43
|
||||
x.inc()
|
||||
}
|
||||
|
||||
fun reassignment() {
|
||||
val x = 42
|
||||
myRun {
|
||||
x = 43
|
||||
}
|
||||
x.inc()
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ fun branchingIndetermineFlow(a: Any?) {
|
||||
if (a is String) {
|
||||
myRepeat(a.length) {
|
||||
// Val reassignment because we know that repeat's lambda called in-place
|
||||
myRun { x = 42 }
|
||||
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -45,7 +45,7 @@ fun multipleAssignments() {
|
||||
val x: Int
|
||||
myRepeat(42) {
|
||||
// Val reassignment because we know that repeat's lambda called in-place
|
||||
myRun { x = 42 }
|
||||
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||
}
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
import kotlin.contracts.*
|
||||
|
||||
fun <T> inPlace(block: () -> T): T {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
fun reassignmentAndNoInitializaiton() {
|
||||
val x: Int
|
||||
inPlace { x = 42 }
|
||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||
}
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ fun testRepeatOnVal(x: Int) {
|
||||
val y: Int
|
||||
repeat(x) {
|
||||
// reassignment instead of captured val initialization
|
||||
y = 42
|
||||
<!VAL_REASSIGNMENT!>y<!> = 42
|
||||
}
|
||||
println(<!UNINITIALIZED_VARIABLE!>y<!>)
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ fun case1() {
|
||||
fun case2() {
|
||||
val x: Boolean = false
|
||||
try {
|
||||
x = (throw Exception()) || true //VAL_REASSIGNMENT should be
|
||||
<!VAL_REASSIGNMENT!>x<!> = (throw Exception()) || true //VAL_REASSIGNMENT should be
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -9,12 +9,12 @@
|
||||
fun case1() {
|
||||
val x : Any
|
||||
x = "0"
|
||||
x = 1
|
||||
x = 2.0
|
||||
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||
<!VAL_REASSIGNMENT!>x<!> = 2.0
|
||||
|
||||
val y : Any = 0
|
||||
y = "0"
|
||||
y = 1.0
|
||||
<!VAL_REASSIGNMENT!>y<!> = "0"
|
||||
<!VAL_REASSIGNMENT!>y<!> = 1.0
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -23,8 +23,8 @@ fun case1() {
|
||||
*/
|
||||
fun case2() {
|
||||
val x : Any
|
||||
mutableListOf(0).forEach({ x = it })
|
||||
mutableListOf(0).forEach({ <!VAL_REASSIGNMENT!>x<!> = it })
|
||||
|
||||
val y : Any = 1
|
||||
mutableListOf(1).forEach({ y = it })
|
||||
mutableListOf(1).forEach({ <!VAL_REASSIGNMENT!>y<!> = it })
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
// TESTCASE NUMBER: 1
|
||||
fun case_1() {
|
||||
val value_1: Int
|
||||
funWithAtLeastOnceCallsInPlace { value_1 = 10 }
|
||||
funWithAtLeastOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||
value_1.inc()
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ fun case_2() {
|
||||
// TESTCASE NUMBER: 3
|
||||
fun case_3() {
|
||||
val value_1: Int
|
||||
funWithUnknownCallsInPlace { value_1 = 10 }
|
||||
funWithUnknownCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.inc()
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class case_5 {
|
||||
fun case_6() {
|
||||
val value_1: Int
|
||||
for (i in 0..1)
|
||||
funWithExactlyOnceCallsInPlace { value_1 = 10 }
|
||||
funWithExactlyOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ fun case_1() {
|
||||
val value_1: Int
|
||||
funWithAtLeastOnceCallsInPlace {
|
||||
funWithAtMostOnceCallsInPlace {
|
||||
value_1 = 1
|
||||
<!VAL_REASSIGNMENT!>value_1<!> = 1
|
||||
funWithExactlyOnceCallsInPlace {
|
||||
value_1.inc()
|
||||
}
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ fun case_8() {
|
||||
funWithExactlyOnceCallsInPlace outer@ {
|
||||
funWithAtMostOnceCallsInPlace {
|
||||
funWithUnknownCallsInPlace {
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
}
|
||||
return@outer
|
||||
}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ fun case_5() {
|
||||
value_1.inc()
|
||||
}
|
||||
funWithUnknownCallsInPlace {
|
||||
value_1 = 1
|
||||
<!VAL_REASSIGNMENT!>value_1<!> = 1
|
||||
}
|
||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
||||
}
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ fun case_10() {
|
||||
val x: Int
|
||||
funWithExactlyOnceCallsInPlace outer@ {
|
||||
funWithAtLeastOnceCallsInPlace {
|
||||
x = 42
|
||||
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||
return@outer
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -23,6 +23,6 @@ fun case_3() {
|
||||
// TESTCASE NUMBER: 4
|
||||
fun case_4() {
|
||||
val value_1: Boolean?
|
||||
funWithUnknownCallsInPlace { value_1 = true }
|
||||
funWithUnknownCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = true }
|
||||
<!UNINITIALIZED_VARIABLE!>value_1<!><!UNSAFE_CALL!>.<!>not()
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -23,6 +23,6 @@ fun case_3() {
|
||||
// TESTCASE NUMBER: 4
|
||||
fun case_4() {
|
||||
val value_1: Boolean?
|
||||
funWithAtLeastOnceCallsInPlace { value_1 = true }
|
||||
funWithAtLeastOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = true }
|
||||
value_1<!UNSAFE_CALL!>.<!>not()
|
||||
}
|
||||
|
||||
+8
@@ -28,6 +28,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
|
||||
@@ -1013,6 +1014,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.VAL_REASSIGNMENT) { firDiagnostic ->
|
||||
ValReassignmentImpl(
|
||||
firSymbolBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty),
|
||||
firDiagnostic as FirPsiDiagnostic<*>,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic ->
|
||||
WrongInvocationKindImpl(
|
||||
firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration),
|
||||
|
||||
+7
-1
@@ -32,6 +32,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
|
||||
@@ -708,11 +709,16 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
abstract val componentFunctionName: Name
|
||||
}
|
||||
|
||||
abstract class UninitializedVariable : KtFirDiagnostic<PsiElement>() {
|
||||
abstract class UninitializedVariable : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||
override val diagnosticClass get() = UninitializedVariable::class
|
||||
abstract val variable: KtVariableSymbol
|
||||
}
|
||||
|
||||
abstract class ValReassignment : KtFirDiagnostic<KtExpression>() {
|
||||
override val diagnosticClass get() = ValReassignment::class
|
||||
abstract val variable: KtVariableSymbol
|
||||
}
|
||||
|
||||
abstract class WrongInvocationKind : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = WrongInvocationKind::class
|
||||
abstract val declaration: KtSymbol
|
||||
|
||||
+10
-1
@@ -34,6 +34,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
|
||||
@@ -1148,7 +1149,15 @@ internal class UninitializedVariableImpl(
|
||||
override val variable: KtVariableSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.UninitializedVariable(), KtAbstractFirDiagnostic<PsiElement> {
|
||||
) : KtFirDiagnostic.UninitializedVariable(), KtAbstractFirDiagnostic<KtSimpleNameExpression> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class ValReassignmentImpl(
|
||||
override val variable: KtVariableSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.ValReassignment(), KtAbstractFirDiagnostic<KtExpression> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user