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|
|
lval b: R|kotlin/String|
|
||||||
R|<local>/bool| = Boolean(false)
|
R|<local>/b| = Boolean(false)
|
||||||
}
|
}
|
||||||
public final fun cycles(): R|kotlin/Unit| {
|
public final fun cycles(): R|kotlin/Unit| {
|
||||||
lvar a: R|kotlin/Int| = Int(10)
|
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<!>
|
<!VARIABLE_NEVER_READ{LT}!><!CAN_BE_VAL!>var<!> <!VARIABLE_NEVER_READ{PSI}!>a<!>: Int<!>
|
||||||
val bool = true
|
val bool = true
|
||||||
if (bool) <!ASSIGNED_VALUE_IS_NEVER_READ!>a<!> = 4 else <!ASSIGNED_VALUE_IS_NEVER_READ!>a<!> = 42
|
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() {
|
fun cycles() {
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ fun myRun(block: () -> Unit) {
|
|||||||
fun test_1() {
|
fun test_1() {
|
||||||
val x: Int
|
val x: Int
|
||||||
inlineRun {
|
inlineRun {
|
||||||
x = 1
|
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||||
}
|
}
|
||||||
x.inc()
|
x.inc()
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ fun test_1() {
|
|||||||
fun test_2() {
|
fun test_2() {
|
||||||
val x: Int
|
val x: Int
|
||||||
myRun {
|
myRun {
|
||||||
x = 1
|
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||||
}
|
}
|
||||||
x.inc()
|
x.inc()
|
||||||
}
|
}
|
||||||
+2
-2
@@ -19,7 +19,7 @@ fun myRun(block: () -> Unit) {
|
|||||||
fun test_1() {
|
fun test_1() {
|
||||||
val x: Int
|
val x: Int
|
||||||
inlineRun {
|
inlineRun {
|
||||||
x = 1
|
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ fun test_1() {
|
|||||||
fun test_2() {
|
fun test_2() {
|
||||||
val x: Int
|
val x: Int
|
||||||
myRun {
|
myRun {
|
||||||
x = 1
|
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
}
|
}
|
||||||
+4
-1
@@ -355,7 +355,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val CONTROL_FLOW by object : DiagnosticGroup("Control flow diagnostics") {
|
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")
|
parameter<FirPropertySymbol>("variable")
|
||||||
}
|
}
|
||||||
val WRONG_INVOCATION_KIND by warning<FirSourceElement, PsiElement> {
|
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.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameter
|
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
@@ -233,7 +234,8 @@ object FirErrors {
|
|||||||
val COMPONENT_FUNCTION_ON_NULLABLE by error1<FirSourceElement, KtExpression, Name>()
|
val COMPONENT_FUNCTION_ON_NULLABLE by error1<FirSourceElement, KtExpression, Name>()
|
||||||
|
|
||||||
// Control flow diagnostics
|
// 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 WRONG_INVOCATION_KIND by warning3<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>, EventOccurrencesRange, EventOccurrencesRange>()
|
||||||
val LEAKED_IN_PLACE_LAMBDA by error1<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>>()
|
val LEAKED_IN_PLACE_LAMBDA by error1<FirSourceElement, PsiElement, AbstractFirBasedSymbol<*>>()
|
||||||
val WRONG_IMPLIES_CONDITION by warning0<FirSourceElement, PsiElement>()
|
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.PersistentMap
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
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.references.FirResolvedNamedReference
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
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>(
|
abstract class EventOccurrencesRangeInfo<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
|
||||||
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
||||||
@@ -74,8 +80,10 @@ class PathAwarePropertyInitializationInfo(
|
|||||||
::EMPTY
|
::EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyInitializationInfoCollector(private val localProperties: Set<FirPropertySymbol>) :
|
class PropertyInitializationInfoCollector(
|
||||||
ControlFlowGraphVisitor<PathAwarePropertyInitializationInfo, Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>>() {
|
private val localProperties: Set<FirPropertySymbol>,
|
||||||
|
private val declaredVariableCollector: DeclaredVariableCollector = DeclaredVariableCollector(),
|
||||||
|
) : ControlFlowGraphVisitor<PathAwarePropertyInitializationInfo, Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>>() {
|
||||||
override fun visitNode(
|
override fun visitNode(
|
||||||
node: CFGNode<*>,
|
node: CFGNode<*>,
|
||||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||||
@@ -104,11 +112,11 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
|||||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||||
): PathAwarePropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val dataForNode = visitNode(node, data)
|
val dataForNode = visitNode(node, data)
|
||||||
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
return processVariableWithAssignment(
|
||||||
dataForNode
|
dataForNode,
|
||||||
} else {
|
node.fir.symbol,
|
||||||
processVariableWithAssignment(dataForNode, node.fir.symbol)
|
overwriteRange = node.fir.initializer == null && node.fir.delegate == null
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getData(graph: ControlFlowGraph) =
|
fun getData(graph: ControlFlowGraph) =
|
||||||
@@ -120,10 +128,160 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
|||||||
|
|
||||||
private fun processVariableWithAssignment(
|
private fun processVariableWithAssignment(
|
||||||
dataForNode: PathAwarePropertyInitializationInfo,
|
dataForNode: PathAwarePropertyInitializationInfo,
|
||||||
symbol: FirPropertySymbol
|
symbol: FirPropertySymbol,
|
||||||
|
overwriteRange: Boolean = false,
|
||||||
): PathAwarePropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
assert(dataForNode.keys.isNotEmpty())
|
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,
|
key: K,
|
||||||
range: EventOccurrencesRange,
|
range: EventOccurrencesRange,
|
||||||
constructor: (PersistentMap<EdgeLabel, S>) -> P
|
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 {
|
): P {
|
||||||
var resultMap = persistentMapOf<EdgeLabel, S>()
|
var resultMap = persistentMapOf<EdgeLabel, S>()
|
||||||
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 } }
|
||||||
for ((label, dataPerLabel) in pathAwareInfo) {
|
for ((label, dataPerLabel) in pathAwareInfo) {
|
||||||
val existingKind = dataPerLabel[key] ?: EventOccurrencesRange.ZERO
|
val existingKind = dataPerLabel[key] ?: EventOccurrencesRange.ZERO
|
||||||
val kind = existingKind + range
|
val kind = computeNewRange.invoke(existingKind)
|
||||||
resultMap = resultMap.put(label, dataPerLabel.put(key, kind))
|
resultMap = resultMap.put(label, dataPerLabel.put(key, kind))
|
||||||
}
|
}
|
||||||
// after (if key is p1):
|
// 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)
|
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))
|
return constructor(map.put(key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun remove(key: K): S {
|
||||||
|
return constructor(map.remove(key))
|
||||||
|
}
|
||||||
|
|
||||||
abstract fun merge(other: S): S
|
abstract fun merge(other: S): S
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-8
@@ -6,16 +6,15 @@
|
|||||||
package org.jetbrains.kotlin.fir.analysis.cfa
|
package org.jetbrains.kotlin.fir.analysis.cfa
|
||||||
|
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
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.contracts.description.isDefinitelyVisited
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.declarations.isLateInit
|
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.references.FirResolvedNamedReference
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
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.symbols.impl.FirPropertySymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
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 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)
|
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UninitializedPropertyReporter(
|
private class PropertyReporter(
|
||||||
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
val localProperties: Set<FirPropertySymbol>,
|
val localProperties: Set<FirPropertySymbol>,
|
||||||
val reporter: DiagnosticReporter,
|
val reporter: DiagnosticReporter,
|
||||||
@@ -46,6 +45,38 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
|||||||
) : ControlFlowGraphVisitorVoid() {
|
) : ControlFlowGraphVisitorVoid() {
|
||||||
override fun visitNode(node: CFGNode<*>) {}
|
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) {
|
override fun visitQualifiedAccessNode(node: QualifiedAccessNode) {
|
||||||
val reference = node.fir.calleeReference as? FirResolvedNamedReference ?: return
|
val reference = node.fir.calleeReference as? FirResolvedNamedReference ?: return
|
||||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return
|
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return
|
||||||
@@ -53,14 +84,18 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
|||||||
if (symbol.fir.isLateInit) return
|
if (symbol.fir.isLateInit) return
|
||||||
val pathAwareInfo = data.getValue(node)
|
val pathAwareInfo = data.getValue(node)
|
||||||
for (info in pathAwareInfo.values) {
|
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.
|
// To avoid duplicate reports, stop investigating remaining paths if the property is not initialized at any path.
|
||||||
break
|
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
|
val kind = info[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
if (!kind.isDefinitelyVisited()) {
|
if (!kind.isDefinitelyVisited()) {
|
||||||
node.fir.source?.let {
|
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.UNUSED_VARIABLE
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED
|
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.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.VAL_WITH_SETTER
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_EXPECTED
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_EXPECTED
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_INITIALIZER_IS_REDUNDANT
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_INITIALIZER_IS_REDUNDANT
|
||||||
@@ -510,6 +511,7 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
|||||||
|
|
||||||
// Control flow diagnostics
|
// Control flow diagnostics
|
||||||
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", PROPERTY_NAME)
|
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", PROPERTY_NAME)
|
||||||
|
map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", PROPERTY_NAME)
|
||||||
map.put(
|
map.put(
|
||||||
WRONG_INVOCATION_KIND,
|
WRONG_INVOCATION_KIND,
|
||||||
"{2} wrong invocation kind: given {3} case, but {4} case is possible",
|
"{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)
|
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 {
|
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||||
return visitor.visitLoopConditionEnterNode(this, data)
|
return visitor.visitLoopConditionEnterNode(this, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ object NormalPath : EdgeLabel(label = null) {
|
|||||||
get() = true
|
get() = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object LoopBackPath : EdgeLabel(label = null) {
|
||||||
|
override val isNormal: Boolean
|
||||||
|
get() = true
|
||||||
|
}
|
||||||
|
|
||||||
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
|
object UncaughtExceptionPath : EdgeLabel(label = "onUncaughtException")
|
||||||
|
|
||||||
// TODO: Label `return`ing edge with this.
|
// TODO: Label `return`ing edge with this.
|
||||||
|
|||||||
+10
-5
@@ -657,7 +657,7 @@ class ControlFlowGraphBuilder {
|
|||||||
}
|
}
|
||||||
loopExitNodes.push(createLoopExitNode(loop))
|
loopExitNodes.push(createLoopExitNode(loop))
|
||||||
levelCounter++
|
levelCounter++
|
||||||
val conditionEnterNode = createLoopConditionEnterNode(loop.condition).also {
|
val conditionEnterNode = createLoopConditionEnterNode(loop.condition, loop).also {
|
||||||
addNewSimpleNode(it)
|
addNewSimpleNode(it)
|
||||||
// put conditional node twice so we can refer it after exit from loop block
|
// put conditional node twice so we can refer it after exit from loop block
|
||||||
lastNodes.push(it)
|
lastNodes.push(it)
|
||||||
@@ -686,7 +686,7 @@ class ControlFlowGraphBuilder {
|
|||||||
if (lastNodes.isNotEmpty) {
|
if (lastNodes.isNotEmpty) {
|
||||||
val conditionEnterNode = lastNodes.pop()
|
val conditionEnterNode = lastNodes.pop()
|
||||||
require(conditionEnterNode is LoopConditionEnterNode) { loop.render() }
|
require(conditionEnterNode is LoopConditionEnterNode) { loop.render() }
|
||||||
addBackEdge(loopBlockExitNode, conditionEnterNode)
|
addBackEdge(loopBlockExitNode, conditionEnterNode, label = LoopBackPath)
|
||||||
}
|
}
|
||||||
val loopExitNode = loopExitNodes.pop()
|
val loopExitNode = loopExitNodes.pop()
|
||||||
loopExitNode.updateDeadStatus()
|
loopExitNode.updateDeadStatus()
|
||||||
@@ -714,7 +714,7 @@ class ControlFlowGraphBuilder {
|
|||||||
fun enterDoWhileLoopCondition(loop: FirLoop): Pair<LoopBlockExitNode, LoopConditionEnterNode> {
|
fun enterDoWhileLoopCondition(loop: FirLoop): Pair<LoopBlockExitNode, LoopConditionEnterNode> {
|
||||||
levelCounter--
|
levelCounter--
|
||||||
val blockExitNode = createLoopBlockExitNode(loop).also { addNewSimpleNode(it) }
|
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++
|
levelCounter++
|
||||||
return blockExitNode to conditionEnterNode
|
return blockExitNode to conditionEnterNode
|
||||||
}
|
}
|
||||||
@@ -727,7 +727,7 @@ class ControlFlowGraphBuilder {
|
|||||||
popAndAddEdge(conditionExitNode)
|
popAndAddEdge(conditionExitNode)
|
||||||
val blockEnterNode = lastNodes.pop()
|
val blockEnterNode = lastNodes.pop()
|
||||||
require(blockEnterNode is LoopBlockEnterNode)
|
require(blockEnterNode is LoopBlockEnterNode)
|
||||||
addBackEdge(conditionExitNode, blockEnterNode, isDead = conditionBooleanValue == false)
|
addBackEdge(conditionExitNode, blockEnterNode, isDead = conditionBooleanValue == false, label = LoopBackPath)
|
||||||
val loopExit = loopExitNodes.pop()
|
val loopExit = loopExitNodes.pop()
|
||||||
addEdge(conditionExitNode, loopExit, propagateDeadness = false, isDead = conditionBooleanValue == true)
|
addEdge(conditionExitNode, loopExit, propagateDeadness = false, isDead = conditionBooleanValue == true)
|
||||||
loopExit.updateDeadStatus()
|
loopExit.updateDeadStatus()
|
||||||
@@ -1261,7 +1261,12 @@ class ControlFlowGraphBuilder {
|
|||||||
popAndAddEdge(node, preferredKind)
|
popAndAddEdge(node, preferredKind)
|
||||||
if (targetNode != null) {
|
if (targetNode != null) {
|
||||||
if (isBack) {
|
if (isBack) {
|
||||||
addBackEdge(node, targetNode)
|
if (targetNode is LoopEnterNode) {
|
||||||
|
// `continue` to the loop header
|
||||||
|
addBackEdge(node, targetNode, label = LoopBackPath)
|
||||||
|
} else {
|
||||||
|
addBackEdge(node, targetNode)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addEdge(node, targetNode, propagateDeadness = false)
|
addEdge(node, targetNode, propagateDeadness = false)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -113,8 +113,8 @@ fun ControlFlowGraphBuilder.createWhenBranchResultEnterNode(fir: FirWhenBranch):
|
|||||||
fun ControlFlowGraphBuilder.createLoopConditionExitNode(fir: FirExpression): LoopConditionExitNode =
|
fun ControlFlowGraphBuilder.createLoopConditionExitNode(fir: FirExpression): LoopConditionExitNode =
|
||||||
LoopConditionExitNode(currentGraph, fir, levelCounter, createId())
|
LoopConditionExitNode(currentGraph, fir, levelCounter, createId())
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression): LoopConditionEnterNode =
|
fun ControlFlowGraphBuilder.createLoopConditionEnterNode(fir: FirExpression, loop: FirLoop): LoopConditionEnterNode =
|
||||||
LoopConditionEnterNode(currentGraph, fir, levelCounter, createId())
|
LoopConditionEnterNode(currentGraph, fir, loop, levelCounter, createId())
|
||||||
|
|
||||||
fun ControlFlowGraphBuilder.createLoopBlockEnterNode(fir: FirLoop): LoopBlockEnterNode =
|
fun ControlFlowGraphBuilder.createLoopBlockEnterNode(fir: FirLoop): LoopBlockEnterNode =
|
||||||
LoopBlockEnterNode(currentGraph, fir, levelCounter, createId())
|
LoopBlockEnterNode(currentGraph, fir, levelCounter, createId())
|
||||||
|
|||||||
Vendored
+4
-4
@@ -63,7 +63,7 @@ fun t4(a: A) {
|
|||||||
|
|
||||||
fun t1() {
|
fun t1() {
|
||||||
val a : Int = 1
|
val a : Int = 1
|
||||||
a = 2
|
<!VAL_REASSIGNMENT!>a<!> = 2
|
||||||
|
|
||||||
var b : Int = 1
|
var b : Int = 1
|
||||||
b = 3
|
b = 3
|
||||||
@@ -83,8 +83,8 @@ enum class ProtocolState {
|
|||||||
|
|
||||||
fun t3() {
|
fun t3() {
|
||||||
val x: ProtocolState = ProtocolState.WAITING
|
val x: ProtocolState = ProtocolState.WAITING
|
||||||
x = x.signal()
|
<!VAL_REASSIGNMENT!>x<!> = x.signal()
|
||||||
x = x.signal() //repeat for x
|
<!VAL_REASSIGNMENT!>x<!> = x.signal() //repeat for x
|
||||||
}
|
}
|
||||||
|
|
||||||
fun t4() {
|
fun t4() {
|
||||||
@@ -326,7 +326,7 @@ fun func() {
|
|||||||
val a = object {
|
val a = object {
|
||||||
val x = b
|
val x = b
|
||||||
init {
|
init {
|
||||||
b = 4
|
<!VAL_REASSIGNMENT!>b<!> = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -24,7 +24,7 @@ fun assignedInTryAndCatch() {
|
|||||||
try {
|
try {
|
||||||
a = 42
|
a = 42
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
a.hashCode()
|
a.hashCode()
|
||||||
@@ -37,7 +37,7 @@ fun sideEffectBeforeAssignedInTryAndCatch(s: Any) {
|
|||||||
a = 42
|
a = 42
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
s as String // Potential cast exception
|
s as String // Potential cast exception
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
a.hashCode()
|
a.hashCode()
|
||||||
@@ -48,9 +48,9 @@ fun assignedAtAll() {
|
|||||||
try {
|
try {
|
||||||
a = 42
|
a = 42
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
} finally {
|
} finally {
|
||||||
a = 40
|
<!VAL_REASSIGNMENT!>a<!> = 40
|
||||||
}
|
}
|
||||||
a.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
@@ -62,9 +62,9 @@ fun sideEffectBeforeAssignedInTryCatchButNotFinally(s: Any) {
|
|||||||
a = 42
|
a = 42
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
s as String // Potential cast exception
|
s as String // Potential cast exception
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
} finally {
|
} finally {
|
||||||
a = 40
|
<!VAL_REASSIGNMENT!>a<!> = 40
|
||||||
}
|
}
|
||||||
a.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ fun assignedInTryAndFinally() {
|
|||||||
try {
|
try {
|
||||||
a = 42
|
a = 42
|
||||||
} finally {
|
} finally {
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
}
|
}
|
||||||
a.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ fun sideEffectBeforeAssignmentInTryButNotFinally(s: Any) {
|
|||||||
s as String // Potential cast exception
|
s as String // Potential cast exception
|
||||||
a = 42
|
a = 42
|
||||||
} finally {
|
} finally {
|
||||||
a = 41
|
<!VAL_REASSIGNMENT!>a<!> = 41
|
||||||
}
|
}
|
||||||
a.hashCode()
|
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
|
// See KT-15334: incorrect reassignment in do...while
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
|
|||||||
+6
-6
@@ -8,7 +8,7 @@ fun f1() {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: reassignment
|
// KT-13612: reassignment
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ fun f2() {
|
|||||||
throw Exception()
|
throw Exception()
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ fun g1(flag: Boolean) {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: ? reassignment or definite assignment ?
|
// KT-13612: ? reassignment or definite assignment ?
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ fun g2(flag: Boolean) {
|
|||||||
n = 1
|
n = 1
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ fun k1(flag: Boolean) {
|
|||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
// KT-13612: reassignment
|
// KT-13612: reassignment
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ fun k2(flag: Boolean) {
|
|||||||
j(flag)
|
j(flag)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
n = 2
|
<!VAL_REASSIGNMENT!>n<!> = 2
|
||||||
}
|
}
|
||||||
n.hashCode()
|
n.hashCode()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ class A {
|
|||||||
|
|
||||||
fun foo(list: List<A>) {
|
fun foo(list: List<A>) {
|
||||||
for (var (c1, c2, c3) in list) {
|
for (var (c1, c2, c3) in list) {
|
||||||
c1 = 1
|
<!VAL_REASSIGNMENT!>c1<!> = 1
|
||||||
c3 + 1
|
c3 + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
fun foo(k: Int): Int {
|
fun foo(k: Int): Int {
|
||||||
val i: Int
|
val i: Int
|
||||||
for (j in 1..k) {
|
for (j in 1..k) {
|
||||||
i = j
|
<!VAL_REASSIGNMENT!>i<!> = j
|
||||||
}
|
}
|
||||||
i = 6
|
<!VAL_REASSIGNMENT!>i<!> = 6
|
||||||
return i
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(f: Boolean): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(k: Int): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(f: Boolean): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(f: Boolean): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(f: Boolean): Int {
|
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
|
// !DIAGNOSTICS: -UNUSED_VALUE
|
||||||
|
|
||||||
fun foo(): Int {
|
fun foo(): Int {
|
||||||
|
|||||||
+3
-3
@@ -16,8 +16,8 @@ fun foo(a: Boolean, b: Boolean): Int {
|
|||||||
x = 1
|
x = 1
|
||||||
}
|
}
|
||||||
when (b) {
|
when (b) {
|
||||||
true -> x = 2
|
true -> <!VAL_REASSIGNMENT!>x<!> = 2
|
||||||
false -> x = 3
|
false -> <!VAL_REASSIGNMENT!>x<!> = 3
|
||||||
}
|
}
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ fun bar(a: Boolean, b: Boolean): Int {
|
|||||||
x = 1
|
x = 1
|
||||||
}
|
}
|
||||||
when (b) {
|
when (b) {
|
||||||
false -> x = 3
|
false -> <!VAL_REASSIGNMENT!>x<!> = 3
|
||||||
}
|
}
|
||||||
return <!UNINITIALIZED_VARIABLE!>x<!>
|
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
|
// !LANGUAGE: +VariableDeclarationInWhenSubject
|
||||||
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE
|
// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER -UNUSED_VALUE
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -33,12 +33,12 @@ fun test() {
|
|||||||
// Loop executed exectly once, initializing x
|
// Loop executed exectly once, initializing x
|
||||||
myRun { x.inc() }
|
myRun { x.inc() }
|
||||||
|
|
||||||
myRun { x = 42 }
|
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
break
|
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
|
// VAL_REASSIGNMENT isn't reported because of repeating diagnostic
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
// x is ID now
|
// x is ID now
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ fun test() {
|
|||||||
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
break
|
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
|
// VAL_REASSIGNMENT isn't reported because of repeating diagnostic
|
||||||
x = 42
|
x = 42
|
||||||
// x is ID now
|
// x is ID now
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ fun threeLevelsReturnNoInitialization(x: Int?): Int? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Possible to report unreachable here
|
// Possible to report unreachable here
|
||||||
y = 54
|
<!VAL_REASSIGNMENT!>y<!> = 54
|
||||||
}
|
}
|
||||||
return <!UNINITIALIZED_VARIABLE!>y<!>.inc()
|
return <!UNINITIALIZED_VARIABLE!>y<!>.inc()
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -27,13 +27,13 @@ fun outerFinallyInitializes() {
|
|||||||
log()
|
log()
|
||||||
}
|
}
|
||||||
// possible reassignment if innerComputation finished
|
// possible reassignment if innerComputation finished
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
// x is ID here
|
// x is ID here
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definite reassignment here, cause can get here only if myRun finished
|
// Definite reassignment here, cause can get here only if myRun finished
|
||||||
// Not reported because of repeating diagnostic
|
// Not reported because of repeating diagnostic
|
||||||
x = outerComputation()
|
<!VAL_REASSIGNMENT!>x<!> = outerComputation()
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
// can catch exception thrown by the inner, so x can be not initialized
|
// can catch exception thrown by the inner, so x can be not initialized
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
@@ -41,7 +41,7 @@ fun outerFinallyInitializes() {
|
|||||||
} finally {
|
} finally {
|
||||||
// Possible reassignment (e.g. if everything finished)
|
// Possible reassignment (e.g. if everything finished)
|
||||||
// Not reported because of repeating diagnostic
|
// Not reported because of repeating diagnostic
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properly initialized
|
// Properly initialized
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ fun innerTryCatchInitializes() {
|
|||||||
}
|
}
|
||||||
catch (e: java.lang.Exception) {
|
catch (e: java.lang.Exception) {
|
||||||
// Potential reassignment because x.inc() could threw
|
// Potential reassignment because x.inc() could threw
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
x.inc()
|
x.inc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ fun innerTryCatchInitializes() {
|
|||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
|
|
||||||
// Potential reasignment
|
// 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)
|
// Here x=I because outer try-catch either exited normally (x=I) or catched exception (x=I, with reassingment, though)
|
||||||
x.inc()
|
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
|
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||||
|
|||||||
+2
-2
@@ -23,10 +23,10 @@ fun innerTryCatchFinally() {
|
|||||||
x = someComputation()
|
x = someComputation()
|
||||||
report(x)
|
report(x)
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
report(x)
|
report(x)
|
||||||
} finally {
|
} finally {
|
||||||
x = 0
|
<!VAL_REASSIGNMENT!>x<!> = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -22,7 +22,7 @@ fun <T> runOnce(block: () -> T): T {
|
|||||||
fun valueReassignment() {
|
fun valueReassignment() {
|
||||||
val x: Int
|
val x: Int
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
runTwice { x = 42 }
|
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
x.inc()
|
x.inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ fun branchingFlow(a: Any?) {
|
|||||||
val x: Int
|
val x: Int
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
if (a is String) {
|
if (a is String) {
|
||||||
runTwice { x = 42 }
|
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
x = 43
|
x = 43
|
||||||
@@ -47,7 +47,7 @@ fun branchingFlow(a: Any?) {
|
|||||||
fun branchingFlowWithMissingBranches(a: Any?) {
|
fun branchingFlowWithMissingBranches(a: Any?) {
|
||||||
val x: Int
|
val x: Int
|
||||||
if (a is String) {
|
if (a is String) {
|
||||||
runTwice { x = 42 }
|
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
|
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
@@ -58,7 +58,7 @@ fun repeatingFlow(n: Int) {
|
|||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
|
|
||||||
for (i in 1..n) {
|
for (i in 1..n) {
|
||||||
runTwice { x = 42 }
|
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
|
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
||||||
@@ -68,7 +68,7 @@ fun repeatingFlow2(n: Int) {
|
|||||||
val x: Int
|
val x: Int
|
||||||
|
|
||||||
for (i in 1..n) {
|
for (i in 1..n) {
|
||||||
runTwice { x = 42 }
|
runTwice { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
|
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!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
|
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ fun branchingIndetermineFlow(a: Any?) {
|
|||||||
if (a is String) {
|
if (a is String) {
|
||||||
myRepeat(a.length) {
|
myRepeat(a.length) {
|
||||||
// Val reassignment because we know that repeat's lambda called in-place
|
// Val reassignment because we know that repeat's lambda called in-place
|
||||||
myRun { x = 42 }
|
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -45,7 +45,7 @@ fun multipleAssignments() {
|
|||||||
val x: Int
|
val x: Int
|
||||||
myRepeat(42) {
|
myRepeat(42) {
|
||||||
// Val reassignment because we know that repeat's lambda called in-place
|
// Val reassignment because we know that repeat's lambda called in-place
|
||||||
myRun { x = 42 }
|
myRun { <!VAL_REASSIGNMENT!>x<!> = 42 }
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>x<!>.inc()
|
<!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
|
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
|
||||||
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
|
||||||
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ fun testRepeatOnVal(x: Int) {
|
|||||||
val y: Int
|
val y: Int
|
||||||
repeat(x) {
|
repeat(x) {
|
||||||
// reassignment instead of captured val initialization
|
// reassignment instead of captured val initialization
|
||||||
y = 42
|
<!VAL_REASSIGNMENT!>y<!> = 42
|
||||||
}
|
}
|
||||||
println(<!UNINITIALIZED_VARIABLE!>y<!>)
|
println(<!UNINITIALIZED_VARIABLE!>y<!>)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ fun case1() {
|
|||||||
fun case2() {
|
fun case2() {
|
||||||
val x: Boolean = false
|
val x: Boolean = false
|
||||||
try {
|
try {
|
||||||
x = (throw Exception()) || true //VAL_REASSIGNMENT should be
|
<!VAL_REASSIGNMENT!>x<!> = (throw Exception()) || true //VAL_REASSIGNMENT should be
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -9,12 +9,12 @@
|
|||||||
fun case1() {
|
fun case1() {
|
||||||
val x : Any
|
val x : Any
|
||||||
x = "0"
|
x = "0"
|
||||||
x = 1
|
<!VAL_REASSIGNMENT!>x<!> = 1
|
||||||
x = 2.0
|
<!VAL_REASSIGNMENT!>x<!> = 2.0
|
||||||
|
|
||||||
val y : Any = 0
|
val y : Any = 0
|
||||||
y = "0"
|
<!VAL_REASSIGNMENT!>y<!> = "0"
|
||||||
y = 1.0
|
<!VAL_REASSIGNMENT!>y<!> = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -23,8 +23,8 @@ fun case1() {
|
|||||||
*/
|
*/
|
||||||
fun case2() {
|
fun case2() {
|
||||||
val x : Any
|
val x : Any
|
||||||
mutableListOf(0).forEach({ x = it })
|
mutableListOf(0).forEach({ <!VAL_REASSIGNMENT!>x<!> = it })
|
||||||
|
|
||||||
val y : Any = 1
|
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
|
// TESTCASE NUMBER: 1
|
||||||
fun case_1() {
|
fun case_1() {
|
||||||
val value_1: Int
|
val value_1: Int
|
||||||
funWithAtLeastOnceCallsInPlace { value_1 = 10 }
|
funWithAtLeastOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||||
value_1.inc()
|
value_1.inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ fun case_2() {
|
|||||||
// TESTCASE NUMBER: 3
|
// TESTCASE NUMBER: 3
|
||||||
fun case_3() {
|
fun case_3() {
|
||||||
val value_1: Int
|
val value_1: Int
|
||||||
funWithUnknownCallsInPlace { value_1 = 10 }
|
funWithUnknownCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.inc()
|
<!UNINITIALIZED_VARIABLE!>value_1<!>.inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class case_5 {
|
|||||||
fun case_6() {
|
fun case_6() {
|
||||||
val value_1: Int
|
val value_1: Int
|
||||||
for (i in 0..1)
|
for (i in 0..1)
|
||||||
funWithExactlyOnceCallsInPlace { value_1 = 10 }
|
funWithExactlyOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = 10 }
|
||||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ fun case_1() {
|
|||||||
val value_1: Int
|
val value_1: Int
|
||||||
funWithAtLeastOnceCallsInPlace {
|
funWithAtLeastOnceCallsInPlace {
|
||||||
funWithAtMostOnceCallsInPlace {
|
funWithAtMostOnceCallsInPlace {
|
||||||
value_1 = 1
|
<!VAL_REASSIGNMENT!>value_1<!> = 1
|
||||||
funWithExactlyOnceCallsInPlace {
|
funWithExactlyOnceCallsInPlace {
|
||||||
value_1.inc()
|
value_1.inc()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ fun case_8() {
|
|||||||
funWithExactlyOnceCallsInPlace outer@ {
|
funWithExactlyOnceCallsInPlace outer@ {
|
||||||
funWithAtMostOnceCallsInPlace {
|
funWithAtMostOnceCallsInPlace {
|
||||||
funWithUnknownCallsInPlace {
|
funWithUnknownCallsInPlace {
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
}
|
}
|
||||||
return@outer
|
return@outer
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@ fun case_5() {
|
|||||||
value_1.inc()
|
value_1.inc()
|
||||||
}
|
}
|
||||||
funWithUnknownCallsInPlace {
|
funWithUnknownCallsInPlace {
|
||||||
value_1 = 1
|
<!VAL_REASSIGNMENT!>value_1<!> = 1
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
<!UNINITIALIZED_VARIABLE!>value_1<!>.dec()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -155,7 +155,7 @@ fun case_10() {
|
|||||||
val x: Int
|
val x: Int
|
||||||
funWithExactlyOnceCallsInPlace outer@ {
|
funWithExactlyOnceCallsInPlace outer@ {
|
||||||
funWithAtLeastOnceCallsInPlace {
|
funWithAtLeastOnceCallsInPlace {
|
||||||
x = 42
|
<!VAL_REASSIGNMENT!>x<!> = 42
|
||||||
return@outer
|
return@outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -23,6 +23,6 @@ fun case_3() {
|
|||||||
// TESTCASE NUMBER: 4
|
// TESTCASE NUMBER: 4
|
||||||
fun case_4() {
|
fun case_4() {
|
||||||
val value_1: Boolean?
|
val value_1: Boolean?
|
||||||
funWithUnknownCallsInPlace { value_1 = true }
|
funWithUnknownCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = true }
|
||||||
<!UNINITIALIZED_VARIABLE!>value_1<!><!UNSAFE_CALL!>.<!>not()
|
<!UNINITIALIZED_VARIABLE!>value_1<!><!UNSAFE_CALL!>.<!>not()
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -23,6 +23,6 @@ fun case_3() {
|
|||||||
// TESTCASE NUMBER: 4
|
// TESTCASE NUMBER: 4
|
||||||
fun case_4() {
|
fun case_4() {
|
||||||
val value_1: Boolean?
|
val value_1: Boolean?
|
||||||
funWithAtLeastOnceCallsInPlace { value_1 = true }
|
funWithAtLeastOnceCallsInPlace { <!VAL_REASSIGNMENT!>value_1<!> = true }
|
||||||
value_1<!UNSAFE_CALL!>.<!>not()
|
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.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameter
|
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
@@ -1013,6 +1014,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
|||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
add(FirErrors.VAL_REASSIGNMENT) { firDiagnostic ->
|
||||||
|
ValReassignmentImpl(
|
||||||
|
firSymbolBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty),
|
||||||
|
firDiagnostic as FirPsiDiagnostic<*>,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
}
|
||||||
add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic ->
|
add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic ->
|
||||||
WrongInvocationKindImpl(
|
WrongInvocationKindImpl(
|
||||||
firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration),
|
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.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameter
|
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
@@ -708,11 +709,16 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
|||||||
abstract val componentFunctionName: Name
|
abstract val componentFunctionName: Name
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class UninitializedVariable : KtFirDiagnostic<PsiElement>() {
|
abstract class UninitializedVariable : KtFirDiagnostic<KtSimpleNameExpression>() {
|
||||||
override val diagnosticClass get() = UninitializedVariable::class
|
override val diagnosticClass get() = UninitializedVariable::class
|
||||||
abstract val variable: KtVariableSymbol
|
abstract val variable: KtVariableSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class ValReassignment : KtFirDiagnostic<KtExpression>() {
|
||||||
|
override val diagnosticClass get() = ValReassignment::class
|
||||||
|
abstract val variable: KtVariableSymbol
|
||||||
|
}
|
||||||
|
|
||||||
abstract class WrongInvocationKind : KtFirDiagnostic<PsiElement>() {
|
abstract class WrongInvocationKind : KtFirDiagnostic<PsiElement>() {
|
||||||
override val diagnosticClass get() = WrongInvocationKind::class
|
override val diagnosticClass get() = WrongInvocationKind::class
|
||||||
abstract val declaration: KtSymbol
|
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.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
import org.jetbrains.kotlin.psi.KtPropertyAccessor
|
||||||
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
import org.jetbrains.kotlin.psi.KtPropertyDelegate
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameter
|
import org.jetbrains.kotlin.psi.KtTypeParameter
|
||||||
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
import org.jetbrains.kotlin.psi.KtTypeParameterList
|
||||||
import org.jetbrains.kotlin.psi.KtTypeReference
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
@@ -1148,7 +1149,15 @@ internal class UninitializedVariableImpl(
|
|||||||
override val variable: KtVariableSymbol,
|
override val variable: KtVariableSymbol,
|
||||||
firDiagnostic: FirPsiDiagnostic<*>,
|
firDiagnostic: FirPsiDiagnostic<*>,
|
||||||
override val token: ValidityToken,
|
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)
|
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user