FIR checker: make property init analyzer path-sensitive
In particular, exception throwing path after finally block is distinguished via path label. KT-42350 Fixed
This commit is contained in:
committed by
Dmitriy Novozhilov
parent
6fc3f7e776
commit
ed188204b4
+1
-1
@@ -14,7 +14,7 @@ abstract class AbstractFirPropertyInitializationChecker {
|
|||||||
abstract fun analyze(
|
abstract fun analyze(
|
||||||
graph: ControlFlowGraph,
|
graph: ControlFlowGraph,
|
||||||
reporter: DiagnosticReporter,
|
reporter: DiagnosticReporter,
|
||||||
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
properties: Set<FirPropertySymbol>
|
properties: Set<FirPropertySymbol>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
|||||||
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 java.lang.IllegalStateException
|
||||||
|
|
||||||
class PropertyInitializationInfo(
|
class PropertyInitializationInfo(
|
||||||
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
||||||
@@ -52,17 +52,97 @@ class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PathAwarePropertyInitializationInfo(
|
||||||
|
map: PersistentMap<EdgeLabel, PropertyInitializationInfo> = persistentMapOf()
|
||||||
|
) : ControlFlowInfo<PathAwarePropertyInitializationInfo, EdgeLabel, PropertyInitializationInfo>(map) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY))
|
||||||
|
}
|
||||||
|
|
||||||
|
override val constructor: (PersistentMap<EdgeLabel, PropertyInitializationInfo>) -> PathAwarePropertyInitializationInfo =
|
||||||
|
::PathAwarePropertyInitializationInfo
|
||||||
|
|
||||||
|
val infoAtNormalPath: PropertyInitializationInfo
|
||||||
|
get() = map[NormalPath] ?: PropertyInitializationInfo.EMPTY
|
||||||
|
|
||||||
|
val hasNormalPath: Boolean
|
||||||
|
get() = map.containsKey(NormalPath)
|
||||||
|
|
||||||
|
fun applyLabel(node: CFGNode<*>, label: EdgeLabel): PathAwarePropertyInitializationInfo {
|
||||||
|
if (label.isNormal) {
|
||||||
|
// Special case: when we exit the try expression, null label means a normal path.
|
||||||
|
// Filter out any info bound to non-null label
|
||||||
|
// One day, if we allow multiple edges between nodes with different labels, e.g., labeling all paths in try/catch/finally,
|
||||||
|
// instead of this kind of special handling, proxy enter/exit nodes per label are preferred.
|
||||||
|
if (node is TryExpressionExitNode) {
|
||||||
|
return if (hasNormalPath) {
|
||||||
|
constructor(persistentMapOf(NormalPath to infoAtNormalPath))
|
||||||
|
} else {
|
||||||
|
/* This means no info for normal path. */
|
||||||
|
EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In general, null label means no additional path info, hence return `this` as-is.
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasAbnormalLabels = map.keys.any { !it.isNormal }
|
||||||
|
return if (hasAbnormalLabels) {
|
||||||
|
// { |-> ... l1 |-> I1, l2 |-> I2, ... }
|
||||||
|
// | l1 // path exit: if the given info has non-null labels, this acts like a filtering
|
||||||
|
// { |-> I1 } // NB: remove the path info
|
||||||
|
if (map.keys.contains(label)) {
|
||||||
|
constructor(persistentMapOf(NormalPath to map[label]!!))
|
||||||
|
} else {
|
||||||
|
/* This means no info for the specific label. */
|
||||||
|
EMPTY
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// { |-> ... } // empty path info
|
||||||
|
// | l1 // path entry
|
||||||
|
// { l1 -> ... } // now, every info bound to the label
|
||||||
|
constructor(persistentMapOf(label to infoAtNormalPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun merge(other: PathAwarePropertyInitializationInfo): PathAwarePropertyInitializationInfo {
|
||||||
|
var resultMap = persistentMapOf<EdgeLabel, PropertyInitializationInfo>()
|
||||||
|
for (label in keys.union(other.keys)) {
|
||||||
|
// disjoint merging to preserve paths. i.e., merge the property initialization info if and only if both have the key.
|
||||||
|
// merge({ |-> I1 }, { |-> I2, l1 |-> I3 }
|
||||||
|
// == { |-> merge(I1, I2), l1 |-> I3 }
|
||||||
|
val i1 = this[label]
|
||||||
|
val i2 = other[label]
|
||||||
|
resultMap = when {
|
||||||
|
i1 != null && i2 != null ->
|
||||||
|
resultMap.put(label, i1.merge(i2))
|
||||||
|
i1 != null ->
|
||||||
|
resultMap.put(label, i1)
|
||||||
|
i2 != null ->
|
||||||
|
resultMap.put(label, i2)
|
||||||
|
else ->
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constructor(resultMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PropertyInitializationInfoCollector(private val localProperties: Set<FirPropertySymbol>) :
|
class PropertyInitializationInfoCollector(private val localProperties: Set<FirPropertySymbol>) :
|
||||||
ControlFlowGraphVisitor<PropertyInitializationInfo, Collection<PropertyInitializationInfo>>() {
|
ControlFlowGraphVisitor<PathAwarePropertyInitializationInfo, Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>>() {
|
||||||
override fun visitNode(node: CFGNode<*>, data: Collection<PropertyInitializationInfo>): PropertyInitializationInfo {
|
override fun visitNode(
|
||||||
if (data.isEmpty()) return PropertyInitializationInfo.EMPTY
|
node: CFGNode<*>,
|
||||||
return data.reduce(PropertyInitializationInfo::merge)
|
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||||
|
): PathAwarePropertyInitializationInfo {
|
||||||
|
if (data.isEmpty()) return PathAwarePropertyInitializationInfo.EMPTY
|
||||||
|
return data.map { (label, info) -> info.applyLabel(node, label) }
|
||||||
|
.reduce(PathAwarePropertyInitializationInfo::merge)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitVariableAssignmentNode(
|
override fun visitVariableAssignmentNode(
|
||||||
node: VariableAssignmentNode,
|
node: VariableAssignmentNode,
|
||||||
data: Collection<PropertyInitializationInfo>
|
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||||
): PropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val dataForNode = visitNode(node, data)
|
val dataForNode = visitNode(node, data)
|
||||||
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
|
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode
|
||||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
|
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
|
||||||
@@ -75,8 +155,8 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
|||||||
|
|
||||||
override fun visitVariableDeclarationNode(
|
override fun visitVariableDeclarationNode(
|
||||||
node: VariableDeclarationNode,
|
node: VariableDeclarationNode,
|
||||||
data: Collection<PropertyInitializationInfo>
|
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
|
||||||
): PropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val dataForNode = visitNode(node, data)
|
val dataForNode = visitNode(node, data)
|
||||||
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
return if (node.fir.initializer == null && node.fir.delegate == null) {
|
||||||
dataForNode
|
dataForNode
|
||||||
@@ -86,18 +166,27 @@ class PropertyInitializationInfoCollector(private val localProperties: Set<FirPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getData(graph: ControlFlowGraph) =
|
fun getData(graph: ControlFlowGraph) =
|
||||||
graph.collectDataForNode(
|
graph.collectPathAwareDataForNode(
|
||||||
TraverseDirection.Forward,
|
TraverseDirection.Forward,
|
||||||
PropertyInitializationInfo.EMPTY,
|
PathAwarePropertyInitializationInfo.EMPTY,
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun processVariableWithAssignment(
|
private fun processVariableWithAssignment(
|
||||||
dataForNode: PropertyInitializationInfo,
|
dataForNode: PathAwarePropertyInitializationInfo,
|
||||||
symbol: FirPropertySymbol
|
symbol: FirPropertySymbol
|
||||||
): PropertyInitializationInfo {
|
): PathAwarePropertyInitializationInfo {
|
||||||
val existingKind = dataForNode[symbol] ?: EventOccurrencesRange.ZERO
|
assert(dataForNode.keys.isNotEmpty())
|
||||||
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
|
var resultMap = persistentMapOf<EdgeLabel, PropertyInitializationInfo>()
|
||||||
return dataForNode.put(symbol, kind)
|
// before: { |-> { p1 |-> PI1 }, l1 |-> { p2 |-> PI2 }
|
||||||
|
for (label in dataForNode.keys) {
|
||||||
|
val dataPerLabel = dataForNode[label]!!
|
||||||
|
val existingKind = dataPerLabel[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
|
val kind = existingKind + EventOccurrencesRange.EXACTLY_ONCE
|
||||||
|
resultMap = resultMap.put(label, dataPerLabel.put(symbol, kind))
|
||||||
|
}
|
||||||
|
// after (if symbol is p1):
|
||||||
|
// { |-> { p1 |-> PI1 + 1 }, l1 |-> { p1 |-> [1, 1], p2 |-> PI2 }
|
||||||
|
return PathAwarePropertyInitializationInfo(resultMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.fir.analysis.cfa
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||||
|
|
||||||
|
// ------------------------------ Graph Traversal ------------------------------
|
||||||
|
|
||||||
enum class TraverseDirection {
|
enum class TraverseDirection {
|
||||||
Forward, Backward
|
Forward, Backward
|
||||||
}
|
}
|
||||||
@@ -29,6 +31,10 @@ fun ControlFlowGraph.traverse(
|
|||||||
traverse(direction, visitor, null)
|
traverse(direction, visitor, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------- Path-insensitive data collection ----------------------
|
||||||
|
|
||||||
|
// TODO: Deprecate and make all existing checkers path-sensitive
|
||||||
fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectDataForNode(
|
fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectDataForNode(
|
||||||
direction: TraverseDirection,
|
direction: TraverseDirection,
|
||||||
initialInfo: I,
|
initialInfo: I,
|
||||||
@@ -75,3 +81,62 @@ private fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------- Path-sensitive data collection -----------------------
|
||||||
|
|
||||||
|
// TODO: Once migration is done, get rid of "PathAware" from util names
|
||||||
|
fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectPathAwareDataForNode(
|
||||||
|
direction: TraverseDirection,
|
||||||
|
initialInfo: I,
|
||||||
|
visitor: ControlFlowGraphVisitor<I, Collection<Pair<EdgeLabel, I>>>
|
||||||
|
): Map<CFGNode<*>, I> {
|
||||||
|
val nodeMap = LinkedHashMap<CFGNode<*>, I>()
|
||||||
|
val startNode = getEnterNode(direction)
|
||||||
|
nodeMap[startNode] = initialInfo
|
||||||
|
|
||||||
|
val changed = mutableMapOf<CFGNode<*>, Boolean>()
|
||||||
|
do {
|
||||||
|
collectPathAwareDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed)
|
||||||
|
} while (changed.any { it.value })
|
||||||
|
|
||||||
|
return nodeMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectPathAwareDataForNodeInternal(
|
||||||
|
direction: TraverseDirection,
|
||||||
|
initialInfo: I,
|
||||||
|
visitor: ControlFlowGraphVisitor<I, Collection<Pair<EdgeLabel, I>>>,
|
||||||
|
nodeMap: MutableMap<CFGNode<*>, I>,
|
||||||
|
changed: MutableMap<CFGNode<*>, Boolean>
|
||||||
|
) {
|
||||||
|
val nodes = getNodesInOrder(direction)
|
||||||
|
for (node in nodes) {
|
||||||
|
if (direction == TraverseDirection.Backward && node is CFGNodeWithCfgOwner<*>) {
|
||||||
|
node.subGraphs.forEach { it.collectPathAwareDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed) }
|
||||||
|
}
|
||||||
|
val previousNodes = when (direction) {
|
||||||
|
TraverseDirection.Forward -> node.previousCfgNodes
|
||||||
|
TraverseDirection.Backward -> node.followingCfgNodes
|
||||||
|
}
|
||||||
|
// One noticeable different against the path-unaware version is, here, we pair the control-flow info with the label.
|
||||||
|
val previousData =
|
||||||
|
previousNodes.mapNotNull {
|
||||||
|
val k = when (direction) {
|
||||||
|
TraverseDirection.Forward -> node.incomingEdges[it]?.label ?: NormalPath
|
||||||
|
TraverseDirection.Backward -> node.outgoingEdges[it]?.label ?: NormalPath
|
||||||
|
}
|
||||||
|
val v = nodeMap[it] ?: return@mapNotNull null
|
||||||
|
k to v
|
||||||
|
}
|
||||||
|
val data = nodeMap[node]
|
||||||
|
val newData = node.accept(visitor, previousData)
|
||||||
|
val hasChanged = newData != data
|
||||||
|
changed[node] = hasChanged
|
||||||
|
if (hasChanged) {
|
||||||
|
nodeMap[node] = newData
|
||||||
|
}
|
||||||
|
if (direction == TraverseDirection.Forward && node is CFGNodeWithCfgOwner<*>) {
|
||||||
|
node.subGraphs.forEach { it.collectPathAwareDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+18
-6
@@ -22,7 +22,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
|||||||
override fun analyze(
|
override fun analyze(
|
||||||
graph: ControlFlowGraph,
|
graph: ControlFlowGraph,
|
||||||
reporter: DiagnosticReporter,
|
reporter: DiagnosticReporter,
|
||||||
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
properties: Set<FirPropertySymbol>
|
properties: Set<FirPropertySymbol>
|
||||||
) {
|
) {
|
||||||
val localData = data.filter {
|
val localData = data.filter {
|
||||||
@@ -37,7 +37,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class UninitializedPropertyReporter(
|
private class UninitializedPropertyReporter(
|
||||||
val data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
val localProperties: Set<FirPropertySymbol>,
|
val localProperties: Set<FirPropertySymbol>,
|
||||||
val reporter: DiagnosticReporter
|
val reporter: DiagnosticReporter
|
||||||
) : ControlFlowGraphVisitorVoid() {
|
) : ControlFlowGraphVisitorVoid() {
|
||||||
@@ -48,12 +48,24 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
|
|||||||
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return
|
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return
|
||||||
if (symbol !in localProperties) return
|
if (symbol !in localProperties) return
|
||||||
if (symbol.fir.isLateInit) return
|
if (symbol.fir.isLateInit) return
|
||||||
val kind = data.getValue(node)[symbol] ?: EventOccurrencesRange.ZERO
|
val pathAwareInfo = data.getValue(node)
|
||||||
if (!kind.isDefinitelyVisited()) {
|
for (label in pathAwareInfo.keys) {
|
||||||
node.fir.source?.let {
|
if (investigate(pathAwareInfo[label]!!, symbol, node)) {
|
||||||
reporter.report(FirErrors.UNINITIALIZED_VARIABLE.on(it, symbol))
|
// 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 {
|
||||||
|
val kind = info[symbol] ?: EventOccurrencesRange.ZERO
|
||||||
|
if (!kind.isDefinitelyVisited()) {
|
||||||
|
node.fir.source?.let {
|
||||||
|
reporter.report(FirErrors.UNINITIALIZED_VARIABLE.on(it, symbol))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-9
@@ -5,16 +5,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.extended
|
package org.jetbrains.kotlin.fir.analysis.checkers.extended
|
||||||
|
|
||||||
|
|
||||||
import com.intellij.lang.LighterASTNode
|
import com.intellij.lang.LighterASTNode
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import org.jetbrains.kotlin.KtNodeTypes
|
import org.jetbrains.kotlin.KtNodeTypes
|
||||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||||
import org.jetbrains.kotlin.fir.*
|
import org.jetbrains.kotlin.fir.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker
|
import org.jetbrains.kotlin.fir.analysis.cfa.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.cfa.traverse
|
|
||||||
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.analysis.getChild
|
import org.jetbrains.kotlin.fir.analysis.getChild
|
||||||
@@ -23,12 +19,11 @@ 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.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
|
||||||
|
|
||||||
object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
|
object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
|
||||||
override fun analyze(
|
override fun analyze(
|
||||||
graph: ControlFlowGraph,
|
graph: ControlFlowGraph,
|
||||||
reporter: DiagnosticReporter,
|
reporter: DiagnosticReporter,
|
||||||
data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
properties: Set<FirPropertySymbol>
|
properties: Set<FirPropertySymbol>
|
||||||
) {
|
) {
|
||||||
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
|
||||||
@@ -75,7 +70,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
|
|||||||
value in canBeValOccurrenceRanges && symbol.fir.isVar
|
value in canBeValOccurrenceRanges && symbol.fir.isVar
|
||||||
|
|
||||||
private class UninitializedPropertyReporter(
|
private class UninitializedPropertyReporter(
|
||||||
val data: Map<CFGNode<*>, PropertyInitializationInfo>,
|
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
|
||||||
val localProperties: Set<FirPropertySymbol>,
|
val localProperties: Set<FirPropertySymbol>,
|
||||||
val unprocessedProperties: MutableSet<FirPropertySymbol>,
|
val unprocessedProperties: MutableSet<FirPropertySymbol>,
|
||||||
val propertiesCharacteristics: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
val propertiesCharacteristics: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
||||||
@@ -89,7 +84,8 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
|
|||||||
unprocessedProperties.remove(symbol)
|
unprocessedProperties.remove(symbol)
|
||||||
|
|
||||||
val currentCharacteristic = propertiesCharacteristics.getOrDefault(symbol, EventOccurrencesRange.ZERO)
|
val currentCharacteristic = propertiesCharacteristics.getOrDefault(symbol, EventOccurrencesRange.ZERO)
|
||||||
propertiesCharacteristics[symbol] = currentCharacteristic.or(data.getValue(node)[symbol] ?: EventOccurrencesRange.ZERO)
|
val info = data.getValue(node)
|
||||||
|
propertiesCharacteristics[symbol] = currentCharacteristic.or(info.infoAtNormalPath[symbol] ?: EventOccurrencesRange.ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ fun assignedInTryAndCatch() {
|
|||||||
a = 41
|
a = 41
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>a<!>.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sideEffectBeforeAssignedInTryAndCatch(s: Any) {
|
fun sideEffectBeforeAssignedInTryAndCatch(s: Any) {
|
||||||
@@ -40,7 +40,7 @@ fun sideEffectBeforeAssignedInTryAndCatch(s: Any) {
|
|||||||
a = 41
|
a = 41
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>a<!>.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignedAtAll() {
|
fun assignedAtAll() {
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ fun assignedInTry() {
|
|||||||
a = 42
|
a = 42
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>a<!>.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sideEffectBeforeAssignmentInTry(s: Any) {
|
fun sideEffectBeforeAssignmentInTry(s: Any) {
|
||||||
@@ -14,7 +14,7 @@ fun sideEffectBeforeAssignmentInTry(s: Any) {
|
|||||||
a = 42
|
a = 42
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
<!UNINITIALIZED_VARIABLE!>a<!>.hashCode()
|
a.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assignedInTryAndFinally() {
|
fun assignedInTryAndFinally() {
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ fun case_6() {
|
|||||||
println(<!UNINITIALIZED_VARIABLE!>value_2<!>.inc())
|
println(<!UNINITIALIZED_VARIABLE!>value_2<!>.inc())
|
||||||
}
|
}
|
||||||
|
|
||||||
<!UNINITIALIZED_VARIABLE!>value_2<!>++
|
value_2++
|
||||||
}
|
}
|
||||||
|
|
||||||
// TESTCASE NUMBER: 7
|
// TESTCASE NUMBER: 7
|
||||||
|
|||||||
Reference in New Issue
Block a user