FIR checker: report CAPTURED_VAL_INITIALIZATION
This commit is contained in:
committed by
TeamCityServer
parent
d38effcbbe
commit
f0f1e2d945
+2
-2
@@ -2,7 +2,7 @@ fun test1() {
|
||||
val x: Int
|
||||
|
||||
fun func() {
|
||||
x = 0
|
||||
<!CAPTURED_VAL_INITIALIZATION!>x<!> = 0
|
||||
}
|
||||
|
||||
println(<!UNINITIALIZED_VARIABLE!>x<!>)
|
||||
@@ -43,4 +43,4 @@ fun test3() {
|
||||
|
||||
println(<!UNINITIALIZED_VARIABLE!>x<!>)
|
||||
println(<!UNINITIALIZED_VARIABLE!>x<!>)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -691,6 +691,12 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error<KtExpression> {
|
||||
parameter<FirPropertySymbol>("property")
|
||||
}
|
||||
val CAPTURED_VAL_INITIALIZATION by error<KtExpression> {
|
||||
parameter<FirPropertySymbol>("property")
|
||||
}
|
||||
val CAPTURED_MEMBER_VAL_INITIALIZATION by error<KtExpression> {
|
||||
parameter<FirPropertySymbol>("property")
|
||||
}
|
||||
val WRONG_INVOCATION_KIND by warning<PsiElement> {
|
||||
parameter<Symbol>("declaration")
|
||||
parameter<EventOccurrencesRange>("requiredRange")
|
||||
|
||||
@@ -411,6 +411,8 @@ object FirErrors {
|
||||
val VAL_REASSIGNMENT by error1<KtExpression, FirVariableSymbol<*>>()
|
||||
val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1<KtExpression, FirPropertySymbol>()
|
||||
val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1<KtExpression, FirPropertySymbol>()
|
||||
val CAPTURED_VAL_INITIALIZATION by error1<KtExpression, FirPropertySymbol>()
|
||||
val CAPTURED_MEMBER_VAL_INITIALIZATION by error1<KtExpression, FirPropertySymbol>()
|
||||
val WRONG_INVOCATION_KIND by warning3<PsiElement, AbstractFirBasedSymbol<*>, EventOccurrencesRange, EventOccurrencesRange>()
|
||||
val LEAKED_IN_PLACE_LAMBDA by error1<PsiElement, AbstractFirBasedSymbol<*>>()
|
||||
val WRONG_IMPLIES_CONDITION by warning0<PsiElement>()
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.cfa
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
@@ -17,6 +18,7 @@ abstract class AbstractFirPropertyInitializationChecker {
|
||||
reporter: DiagnosticReporter,
|
||||
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||
properties: Set<FirPropertySymbol>,
|
||||
capturedWrites: Set<FirVariableAssignment>,
|
||||
context: CheckerContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ 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
|
||||
@@ -60,21 +59,58 @@ class PropertyInitializationInfo(
|
||||
::EMPTY
|
||||
}
|
||||
|
||||
class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
||||
class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
||||
companion object {
|
||||
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> {
|
||||
val collector = LocalPropertyCollector()
|
||||
fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
|
||||
val collector = LocalPropertyAndCapturedWriteCollector()
|
||||
graph.traverse(TraverseDirection.Forward, collector)
|
||||
return collector.symbols
|
||||
return collector.symbols.keys to collector.capturedWrites
|
||||
}
|
||||
}
|
||||
|
||||
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf()
|
||||
// Mapping from a property symbol to its declaration context
|
||||
// `true` if the (local) property is declared in the currently visited function.
|
||||
// `false` if it is declared in a lambda or a local function (inside the currently visited function).
|
||||
private val symbols: MutableMap<FirPropertySymbol, Boolean> = mutableMapOf()
|
||||
|
||||
private val lambdaOrLocalFunctionStack: MutableList<FirFunction<*>> = mutableListOf()
|
||||
private val capturedWrites: MutableSet<FirVariableAssignment> = mutableSetOf()
|
||||
|
||||
override fun visitNode(node: CFGNode<*>) {}
|
||||
|
||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||
symbols += node.fir.symbol
|
||||
symbols[node.fir.symbol] = lambdaOrLocalFunctionStack.lastOrNull() == null
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir)
|
||||
}
|
||||
|
||||
override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitFunctionExitNode(node: FunctionExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir)
|
||||
}
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
// Check if this variable assignment is inside a lambda or a local function.
|
||||
if (lambdaOrLocalFunctionStack.isEmpty()) return
|
||||
|
||||
// Check if the assigned variable doesn't belong to any lambda or local function.
|
||||
val symbol = node.fir.referredPropertySymbol ?: return
|
||||
if (symbol !in symbols || symbols[symbol] == false) return
|
||||
|
||||
// If all nested declarations are lambdas that are invoked in-place (according to the contract),
|
||||
// this variable assignment is not a captured write.
|
||||
if (lambdaOrLocalFunctionStack.all { it is FirAnonymousFunction && it.invocationKind.isInPlace }) return
|
||||
|
||||
capturedWrites.add(node.fir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +146,7 @@ class PropertyInitializationInfoCollector(
|
||||
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||
): PathAwarePropertyInitializationInfo {
|
||||
val dataForNode = visitNode(node, data)
|
||||
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
|
||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
|
||||
val symbol = node.fir.referredPropertySymbol ?: return dataForNode
|
||||
return if (symbol !in localProperties) {
|
||||
dataForNode
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -61,9 +61,9 @@ class FirControlFlowAnalyzer(
|
||||
}
|
||||
|
||||
private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter, context: CheckerContext) {
|
||||
val properties = LocalPropertyCollector.collect(graph)
|
||||
val (properties, capturedWrites) = LocalPropertyAndCapturedWriteCollector.collect(graph)
|
||||
if (properties.isEmpty()) return
|
||||
val data = PropertyInitializationInfoCollector(properties).getData(graph)
|
||||
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties, context) }
|
||||
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties, capturedWrites, context) }
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -11,8 +11,11 @@ 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.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.declarations.isLateInit
|
||||
import org.jetbrains.kotlin.fir.declarations.referredPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
@@ -24,6 +27,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
reporter: DiagnosticReporter,
|
||||
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||
properties: Set<FirPropertySymbol>,
|
||||
capturedWrites: Set<FirVariableAssignment>,
|
||||
context: CheckerContext
|
||||
) {
|
||||
val localData = data.filter {
|
||||
@@ -33,21 +37,21 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
|
||||
val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet()
|
||||
|
||||
val reporterVisitor = PropertyReporter(localData, localProperties, reporter, context)
|
||||
val reporterVisitor = PropertyReporter(localData, localProperties, capturedWrites, reporter, context)
|
||||
graph.traverse(TraverseDirection.Forward, reporterVisitor)
|
||||
}
|
||||
|
||||
private class PropertyReporter(
|
||||
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||
val localProperties: Set<FirPropertySymbol>,
|
||||
val capturedWrites: Set<FirVariableAssignment>,
|
||||
val reporter: DiagnosticReporter,
|
||||
val context: CheckerContext
|
||||
) : 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
|
||||
return (node.fir as? FirQualifiedAccess)?.referredPropertySymbol
|
||||
}
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
@@ -66,10 +70,17 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
||||
symbol: FirPropertySymbol,
|
||||
node: VariableAssignmentNode
|
||||
): Boolean {
|
||||
if (symbol.fir.isVal && node.fir in capturedWrites) {
|
||||
if (symbol.fir.isLocal) {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_VAL_INITIALIZATION, symbol, context)
|
||||
} else {
|
||||
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION, symbol, context)
|
||||
}
|
||||
return true
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.getChildren
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
@@ -28,6 +29,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
|
||||
reporter: DiagnosticReporter,
|
||||
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||
properties: Set<FirPropertySymbol>,
|
||||
capturedWrites: Set<FirVariableAssignment>,
|
||||
context: CheckerContext
|
||||
) {
|
||||
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ object UnusedChecker : FirControlFlowChecker() {
|
||||
!it.symbol.classId.isLocal
|
||||
} != null
|
||||
) return
|
||||
val properties = LocalPropertyCollector.collect(graph)
|
||||
val (properties, _) = LocalPropertyAndCapturedWriteCollector.collect(graph)
|
||||
if (properties.isEmpty()) return
|
||||
|
||||
val data = ValueWritesWithoutReading(context.session, properties).getData(graph)
|
||||
|
||||
+12
@@ -58,6 +58,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CANNOT_INFER_PARA
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CANNOT_WEAKEN_ACCESS_PRIVILEGE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_VAL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAPTURED_VAL_INITIALIZATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CATCH_PARAMETER_WITH_DEFAULT_VALUE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_IN_SUPERTYPE_FOR_ENUM
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_LITERAL_LHS_NOT_A_CLASS
|
||||
@@ -941,6 +943,16 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", VARIABLE_NAME)
|
||||
map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD, "Reassignment of read-only property via backing field is deprecated", VARIABLE_NAME)
|
||||
map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, "Reassignment of read-only property via backing field", VARIABLE_NAME)
|
||||
map.put(
|
||||
CAPTURED_VAL_INITIALIZATION,
|
||||
"Captured values initialization is forbidden due to possible reassignment",
|
||||
VARIABLE_NAME
|
||||
)
|
||||
map.put(
|
||||
CAPTURED_MEMBER_VAL_INITIALIZATION,
|
||||
"Captured member values initialization is forbidden due to possible reassignment",
|
||||
VARIABLE_NAME
|
||||
)
|
||||
map.put(
|
||||
WRONG_INVOCATION_KIND,
|
||||
"{2} wrong invocation kind: given {3} case, but {4} case is possible",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.dfa.cfg
|
||||
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
|
||||
val EventOccurrencesRange?.isInPlace: Boolean
|
||||
get() = this != null
|
||||
-3
@@ -306,9 +306,6 @@ class ControlFlowGraphBuilder {
|
||||
else -> false
|
||||
}
|
||||
|
||||
private val EventOccurrencesRange?.isInPlace: Boolean
|
||||
get() = this != null
|
||||
|
||||
fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple<FunctionExitNode, PostponedLambdaExitNode?, ControlFlowGraph> {
|
||||
val symbol = anonymousFunction.symbol
|
||||
val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also {
|
||||
|
||||
@@ -11,10 +11,13 @@ import org.jetbrains.kotlin.fir.declarations.builder.FirRegularClassBuilder
|
||||
import org.jetbrains.kotlin.fir.declarations.builder.FirTypeParameterBuilder
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
import org.jetbrains.kotlin.fir.types.ConeFlexibleType
|
||||
@@ -216,6 +219,12 @@ val FirProperty.hasBackingField: Boolean
|
||||
}
|
||||
}
|
||||
|
||||
val FirQualifiedAccess.referredPropertySymbol: FirPropertySymbol?
|
||||
get() {
|
||||
val reference = calleeReference as? FirResolvedNamedReference ?: return null
|
||||
return reference.resolvedSymbol as? FirPropertySymbol
|
||||
}
|
||||
|
||||
inline val FirDeclaration.isFromLibrary: Boolean
|
||||
get() = origin == FirDeclarationOrigin.Library
|
||||
inline val FirDeclaration.isSynthetic: Boolean
|
||||
|
||||
Reference in New Issue
Block a user