FIR DFA: when removing a variable, move type statements about dependents
val c = C("...")
val d = c
if (c.x == null) return
c.x.length // c.x has type String
d.x.length // d.x -> c.x through d -> c
c = C(null) // remove alias d -> c
d.x.length // info from c.x moved to d.x
^KT-54824 Fixed
This commit is contained in:
+3
@@ -48,6 +48,9 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
|
|||||||
if (effects.isNullOrEmpty()) return
|
if (effects.isNullOrEmpty()) return
|
||||||
|
|
||||||
val logicSystem = object : PersistentLogicSystem(context.session.typeContext) {
|
val logicSystem = object : PersistentLogicSystem(context.session.typeContext) {
|
||||||
|
override val variableStorage: VariableStorageImpl
|
||||||
|
get() = dataFlowInfo.variableStorage as VariableStorageImpl
|
||||||
|
|
||||||
override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) =
|
override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) =
|
||||||
throw IllegalStateException("Receiver variable update is not possible for this logic system")
|
throw IllegalStateException("Receiver variable update is not possible for this logic system")
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
|||||||
|
|
||||||
override val logicSystem: PersistentLogicSystem =
|
override val logicSystem: PersistentLogicSystem =
|
||||||
object : PersistentLogicSystem(components.session.typeContext) {
|
object : PersistentLogicSystem(components.session.typeContext) {
|
||||||
|
override val variableStorage: VariableStorageImpl
|
||||||
|
get() = dataFlowAnalyzerContext.variableStorage
|
||||||
|
|
||||||
override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) {
|
override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||||
val symbol = variable.identifier.symbol
|
val symbol = variable.identifier.symbol
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ class RealVariable(
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return _hashCode
|
return _hashCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (explicitReceiverVariable is RealVariable) {
|
||||||
|
explicitReceiverVariable.dependentVariables.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyntheticVariable(val fir: FirElement, variableIndexForDebug: Int) : DataFlowVariable(variableIndexForDebug) {
|
class SyntheticVariable(val fir: FirElement, variableIndexForDebug: Int) : DataFlowVariable(variableIndexForDebug) {
|
||||||
|
|||||||
+35
-28
@@ -94,6 +94,8 @@ class PersistentFlow : Flow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSystem<PersistentFlow>(context) {
|
abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSystem<PersistentFlow>(context) {
|
||||||
|
abstract val variableStorage: VariableStorageImpl
|
||||||
|
|
||||||
override fun createEmptyFlow(): PersistentFlow {
|
override fun createEmptyFlow(): PersistentFlow {
|
||||||
return PersistentFlow()
|
return PersistentFlow()
|
||||||
}
|
}
|
||||||
@@ -174,45 +176,50 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun removeAllAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
override fun removeAllAboutVariable(flow: PersistentFlow, variable: RealVariable) {
|
||||||
val original = flow.directAliasMap[variable]
|
flow.replaceVariable(variable, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PersistentFlow.replaceVariable(variable: RealVariable, replacement: RealVariable?) {
|
||||||
|
val original = directAliasMap[variable]
|
||||||
if (original != null) {
|
if (original != null) {
|
||||||
// All statements should've been made about whatever variable this is an alias to. There is nothing to remove.
|
// All statements should've been made about whatever variable this is an alias to. There is nothing to replace.
|
||||||
if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) {
|
if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) {
|
||||||
assert(variable !in flow.backwardsAliasMap)
|
assert(variable !in backwardsAliasMap)
|
||||||
assert(variable !in flow.logicStatements)
|
assert(variable !in logicStatements)
|
||||||
assert(variable !in flow.approvedTypeStatements)
|
assert(variable !in approvedTypeStatements)
|
||||||
assert(variable !in flow.approvedTypeStatementsDiff)
|
assert(variable !in approvedTypeStatementsDiff)
|
||||||
}
|
}
|
||||||
// `variable.dependentVariables` is not separated by flow, so it may be non-empty if aliasing of this variable
|
// `variable.dependentVariables` is not separated by flow, so it may be non-empty if aliasing of this variable
|
||||||
// was broken in another flow. However, in *this* flow dependent variables should have no information attached to them.
|
// was broken in another flow. However, in *this* flow dependent variables should have no information attached to them.
|
||||||
|
|
||||||
val siblings = flow.backwardsAliasMap.getValue(original).remove(variable)
|
val siblings = backwardsAliasMap.getValue(original).remove(variable)
|
||||||
flow.directAliasMap -= variable
|
directAliasMap -= variable
|
||||||
flow.backwardsAliasMap = if (siblings.isNotEmpty()) {
|
backwardsAliasMap = if (siblings.isNotEmpty()) {
|
||||||
flow.backwardsAliasMap.put(original, siblings)
|
backwardsAliasMap.put(original, siblings)
|
||||||
} else {
|
} else {
|
||||||
flow.backwardsAliasMap.remove(original)
|
backwardsAliasMap.remove(original)
|
||||||
|
}
|
||||||
|
if (replacement != null) {
|
||||||
|
addLocalVariableAlias(this, replacement, original)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val aliases = flow.backwardsAliasMap[variable]
|
val aliases = backwardsAliasMap[variable]
|
||||||
val replacement = aliases?.first()
|
// If asked to remove the variable but there are aliases, replace with a new representative for the alias group instead.
|
||||||
|
val replacementOrNext = replacement ?: aliases?.first()
|
||||||
for (dependent in variable.dependentVariables) {
|
for (dependent in variable.dependentVariables) {
|
||||||
// TODO: replace the identifier in dependent variables instead (need to somehow interface with VariableStorage for that)
|
replaceVariable(dependent, replacementOrNext?.let {
|
||||||
// var x = something
|
variableStorage.copyRealVariableWithRemapping(dependent, variable, it)
|
||||||
// val y = x
|
})
|
||||||
// if (x.stableProperty !is A) return
|
|
||||||
// y.stableProperty // A
|
|
||||||
// x = somethingElse
|
|
||||||
// y.stableProperty // still A
|
|
||||||
removeAllAboutVariable(flow, dependent)
|
|
||||||
}
|
}
|
||||||
flow.logicStatements = flow.logicStatements.replaceVariable(variable, replacement)
|
logicStatements = logicStatements.replaceVariable(variable, replacementOrNext)
|
||||||
flow.approvedTypeStatements = flow.approvedTypeStatements.replaceVariable(variable, replacement)
|
approvedTypeStatements = approvedTypeStatements.replaceVariable(variable, replacementOrNext)
|
||||||
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.replaceVariable(variable, replacement)
|
approvedTypeStatementsDiff = approvedTypeStatementsDiff.replaceVariable(variable, replacementOrNext)
|
||||||
if (replacement != null) {
|
if (aliases != null) {
|
||||||
flow.directAliasMap -= replacement
|
backwardsAliasMap -= variable
|
||||||
flow.backwardsAliasMap -= variable
|
if (replacementOrNext != null) {
|
||||||
flow.addAliases(aliases, replacement)
|
directAliasMap -= replacementOrNext
|
||||||
|
addAliases(aliases, replacementOrNext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -108,8 +108,23 @@ class VariableStorageImpl(private val session: FirSession) : VariableStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
|
val receiverVariable = receiver?.let { getOrCreateVariable(flow, it) }
|
||||||
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability).also {
|
return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability)
|
||||||
(receiverVariable as? RealVariable)?.dependentVariables?.add(it)
|
}
|
||||||
|
|
||||||
|
fun copyRealVariableWithRemapping(variable: RealVariable, from: RealVariable, to: RealVariable): RealVariable {
|
||||||
|
val newIdentifier = with(variable.identifier) {
|
||||||
|
copy(
|
||||||
|
dispatchReceiver = if (dispatchReceiver == from) to else dispatchReceiver,
|
||||||
|
extensionReceiver = if (extensionReceiver == from) to else extensionReceiver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return _realVariables.getOrPut(newIdentifier) {
|
||||||
|
with(variable) {
|
||||||
|
RealVariable(
|
||||||
|
newIdentifier, isThisReference, if (explicitReceiverVariable == from) to else explicitReceiverVariable,
|
||||||
|
counter++, stability
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -73,5 +73,5 @@ fun test5() {
|
|||||||
c = C(null)
|
c = C(null)
|
||||||
x.length // ok
|
x.length // ok
|
||||||
c.x<!UNSAFE_CALL!>.<!>length // bad
|
c.x<!UNSAFE_CALL!>.<!>length // bad
|
||||||
d.x<!UNSAFE_CALL!>.<!>length // ok
|
d.x.length // ok
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user