[FIR] Remove dependent data flow variables after receiver reassignment

This commit is contained in:
Ivan Kochurkin
2021-12-14 22:01:45 +03:00
committed by teamcity
parent 1f9ea50d1a
commit c5a03d0573
11 changed files with 184 additions and 25 deletions
@@ -3474,6 +3474,12 @@ public class DiagnosisCompilerFirTestdataTestGenerated extends AbstractDiagnosis
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
public class Smartcasts { public class Smartcasts {
@Test
@TestMetadata("accessToMemberAfterReceiverReassignment.kt")
public void testAccessToMemberAfterReceiverReassignment() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt");
}
@Test @Test
public void testAllFilesPresentInSmartcasts() throws Exception { public void testAllFilesPresentInSmartcasts() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
@@ -3075,6 +3075,11 @@ public class LazyBodyIsNotTouchedTilContractsPhaseTestGenerated extends Abstract
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
} }
@TestMetadata("accessToMemberAfterReceiverReassignment.kt")
public void testAccessToMemberAfterReceiverReassignment() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt");
}
public void testAllFilesPresentInSmartcasts() throws Exception { public void testAllFilesPresentInSmartcasts() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
} }
@@ -0,0 +1,64 @@
FILE: accessToMemberAfterReceiverReassignment.kt
public final class My : R|kotlin/Any| {
public constructor(x: R|My?|, z: R|My?| = Null(null)): R|My| {
super<R|kotlin/Any|>()
}
public final val x: R|My?| = R|<local>/x|
public get(): R|My?|
public final val z: R|My?| = R|<local>/z|
public get(): R|My?|
}
public final fun baseTest(): R|kotlin/Unit| {
lvar y: R|My?| = R|/My.My|(R|/My.My|(Null(null)))
when () {
!=(R|<local>/y|?.{ $subj$.R|/My.x| }, Null(null)) -> {
R|<local>/y|.R|/My.x|.R|/My.x|
R|<local>/y| = R|/My.My|(Null(null))
R|<local>/y|.R|/My.x|.<Inapplicable(UNSAFE_CALL): /My.x>#
}
}
}
public final fun deepChainTest(): R|kotlin/Unit| {
lvar y: R|My?| = R|/My.My|(R|/My.My|(Null(null)))
when () {
!=(R|<local>/y|?.{ $subj$.R|/My.x| }?.{ $subj$.R|/My.x| }, Null(null)) -> {
R|<local>/y|.R|/My.x|.R|/My.x|.R|/My.x|
R|<local>/y| = R|/My.My|(Null(null))
R|<local>/y|.R|/My.x|.<Inapplicable(UNSAFE_CALL): /My.x>#.<Inapplicable(UNSAFE_CALL): /My.x>#
}
}
}
public final fun backwardAliasTest(z: R|My|): R|kotlin/Unit| {
lvar y: R|My| = R|<local>/z|
when () {
!=(R|<local>/y|.R|/My.x|, Null(null)) -> {
R|<local>/y|.R|/My.x|.R|/My.x|
R|<local>/y| = R|/My.My|(Null(null))
R|<local>/y|.R|/My.x|.<Inapplicable(UNSAFE_CALL): /My.x>#
}
}
}
public final fun severalMembersTest(): R|kotlin/Unit| {
lvar y: R|My| = R|/My.My|(R|/My.My|(Null(null)), R|/My.My|(Null(null)))
when () {
!=(R|<local>/y|.R|/My.x|, Null(null)) -> {
when () {
!=(R|<local>/y|.R|/My.z|, Null(null)) -> {
R|<local>/y|.R|/My.x|.R|/My.x|
R|<local>/y|.R|/My.z|.R|/My.z|
R|<local>/y| = R|/My.My|(Null(null))
R|<local>/y|.R|/My.x|.<Inapplicable(UNSAFE_CALL): /My.x>#
R|<local>/y|.R|/My.z|.<Inapplicable(UNSAFE_CALL): /My.z>#
}
}
}
}
}
@@ -0,0 +1,41 @@
class My(val x: My?, val z: My? = null)
fun baseTest() {
var y: My? = My(My(null))
if (y?.x != null) {
y.x.x
y = My(null)
y.x<!UNSAFE_CALL!>.<!>x
}
}
fun deepChainTest() {
var y: My? = My(My(null))
if (y?.x?.x != null) {
y.x.x.x
y = My(null)
y.x<!UNSAFE_CALL!>.<!>x<!UNSAFE_CALL!>.<!>x
}
}
fun backwardAliasTest(z: My) {
var y = z
if (y.x != null) {
y.x.x
y = My(null)
y.x<!UNSAFE_CALL!>.<!>x
}
}
fun severalMembersTest() {
var y = My(My(null), My(null))
if (y.x != null) {
if (y.z != null) {
y.x.x
y.z.z
y = My(null)
y.x<!UNSAFE_CALL!>.<!>x
y.z<!UNSAFE_CALL!>.<!>z
}
}
}
@@ -3474,6 +3474,12 @@ public class FirDiagnosticTestGenerated extends AbstractFirDiagnosticTest {
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
public class Smartcasts { public class Smartcasts {
@Test
@TestMetadata("accessToMemberAfterReceiverReassignment.kt")
public void testAccessToMemberAfterReceiverReassignment() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt");
}
@Test @Test
public void testAllFilesPresentInSmartcasts() throws Exception { public void testAllFilesPresentInSmartcasts() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
@@ -3474,6 +3474,12 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
@TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts") @TestMetadata("compiler/fir/analysis-tests/testData/resolve/smartcasts")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
public class Smartcasts { public class Smartcasts {
@Test
@TestMetadata("accessToMemberAfterReceiverReassignment.kt")
public void testAccessToMemberAfterReceiverReassignment() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/smartcasts/accessToMemberAfterReceiverReassignment.kt");
}
@Test @Test
public void testAllFilesPresentInSmartcasts() throws Exception { public void testAllFilesPresentInSmartcasts() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/resolve/smartcasts"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
@@ -863,7 +863,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (possiblyChangedVariables.isEmpty()) return if (possiblyChangedVariables.isEmpty()) return
val flow = node.flow val flow = node.flow
for (variable in possiblyChangedVariables) { for (variable in possiblyChangedVariables) {
flow.removeAllAboutVariable(variable) logicSystem.removeAllAboutVariable(flow, variable)
} }
} }
@@ -1170,7 +1170,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val isAssignment = assignment != null val isAssignment = assignment != null
if (isAssignment) { if (isAssignment) {
logicSystem.removeLocalVariableAlias(flow, propertyVariable) logicSystem.removeLocalVariableAlias(flow, propertyVariable)
flow.removeAllAboutVariable(propertyVariable) logicSystem.removeAllAboutVariable(flow, propertyVariable)
logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex()) logicSystem.recordNewAssignment(flow, propertyVariable, context.newAssignmentIndex())
} }
@@ -1508,13 +1508,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
logicSystem.addTypeStatement(this, info) logicSystem.addTypeStatement(this, info)
} }
private fun FLOW.removeAllAboutVariable(variable: RealVariable?) {
if (variable == null) return
logicSystem.removeTypeStatementsAboutVariable(this, variable)
logicSystem.removeLogicStatementsAboutVariable(this, variable)
logicSystem.removeAliasInformationAboutVariable(this, variable)
}
private fun FLOW.fork(): FLOW { private fun FLOW.fork(): FLOW {
return logicSystem.forkFlow(this) return logicSystem.forkFlow(this)
} }
@@ -61,6 +61,8 @@ class RealVariable(
variableIndexForDebug: Int, variableIndexForDebug: Int,
val stability: PropertyStability, val stability: PropertyStability,
) : DataFlowVariable(variableIndexForDebug) { ) : DataFlowVariable(variableIndexForDebug) {
val dependentVariables = mutableSetOf<RealVariable>()
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return this === other return this === other
} }
@@ -22,6 +22,13 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
abstract fun addImplication(flow: FLOW, implication: Implication) abstract fun addImplication(flow: FLOW, implication: Implication)
fun removeAllAboutVariable(flow: FLOW, variable: RealVariable?) {
if (variable == null) return
removeTypeStatementsAboutVariable(flow, variable)
removeLogicStatementsAboutVariable(flow, variable)
removeAliasInformationAboutVariable(flow, variable)
}
abstract fun removeTypeStatementsAboutVariable(flow: FLOW, variable: RealVariable) abstract fun removeTypeStatementsAboutVariable(flow: FLOW, variable: RealVariable)
abstract fun removeLogicStatementsAboutVariable(flow: FLOW, variable: DataFlowVariable) abstract fun removeLogicStatementsAboutVariable(flow: FLOW, variable: DataFlowVariable)
abstract fun removeAliasInformationAboutVariable(flow: FLOW, variable: RealVariable) abstract fun removeAliasInformationAboutVariable(flow: FLOW, variable: RealVariable)
@@ -135,11 +135,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
val variables = flows.flatMap { it.approvedTypeStatements.keys }.toSet() val variables = flows.flatMap { it.approvedTypeStatements.keys }.toSet()
for (variable in variables) { for (variable in variables) {
val info = mergeOperation(flows.map { it.getApprovedTypeStatements(variable) }) ?: continue val info = mergeOperation(flows.map { it.getApprovedTypeStatements(variable) }) ?: continue
removeTypeStatementsAboutVariable(commonFlow, variable) commonFlow.approvedTypeStatements -= variable
commonFlow.approvedTypeStatementsDiff -= variable
val thereWereReassignments = variable.hasDifferentReassignments(flows) val thereWereReassignments = variable.hasDifferentReassignments(flows)
if (thereWereReassignments) { if (thereWereReassignments) {
removeLogicStatementsAboutVariable(commonFlow, variable) removeAllAboutVariable(commonFlow, variable)
removeAliasInformationAboutVariable(commonFlow, variable)
} }
commonFlow.addApprovedStatements(info) commonFlow.addApprovedStatements(info)
} }
@@ -222,18 +222,24 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
flow.logicStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements.toPersistentList() flow.logicStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements.toPersistentList()
flow.logicStatements = flow.logicStatements.put(backAlias, replacedStatements) flow.logicStatements = flow.logicStatements.put(backAlias, replacedStatements)
} }
flow.approvedTypeStatements[alias]?.let { it ->
val newStatements = it.replaceVariable(alias, backAlias) fun processApprovedStatements(isDiff: Boolean) {
val replacedStatements = val statements = if (isDiff) flow.approvedTypeStatementsDiff else flow.approvedTypeStatements
flow.approvedTypeStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements statements[alias]?.let { it ->
flow.approvedTypeStatements = flow.approvedTypeStatements.put(backAlias, replacedStatements.toPersistent()) val newStatements = it.replaceVariable(alias, backAlias)
} val replacedStatements =
flow.approvedTypeStatementsDiff[alias]?.let { it -> statements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements
val newStatements = it.replaceVariable(alias, backAlias) val result = statements.put(backAlias, replacedStatements.toPersistent())
val replacedStatements = if (isDiff) {
flow.approvedTypeStatementsDiff[backAlias]?.let { existing -> existing + newStatements } ?: newStatements flow.approvedTypeStatementsDiff = result
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.put(backAlias, replacedStatements.toPersistent()) } else {
flow.approvedTypeStatements = result
}
}
} }
processApprovedStatements(isDiff = false)
processApprovedStatements(isDiff = true)
} }
val original = flow.directAliasMap[alias]?.variable val original = flow.directAliasMap[alias]?.variable
@@ -310,17 +316,31 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
override fun removeTypeStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) { override fun removeTypeStatementsAboutVariable(flow: PersistentFlow, variable: RealVariable) {
flow.approvedTypeStatements -= variable flow.approvedTypeStatements -= variable
flow.approvedTypeStatementsDiff -= variable flow.approvedTypeStatementsDiff -= variable
variable.forEachTransitiveDependentVariable {
flow.approvedTypeStatements -= it
flow.approvedTypeStatementsDiff -= it
}
} }
override fun removeLogicStatementsAboutVariable(flow: PersistentFlow, variable: DataFlowVariable) { override fun removeLogicStatementsAboutVariable(flow: PersistentFlow, variable: DataFlowVariable) {
flow.logicStatements -= variable flow.logicStatements -= variable
val realVariable = variable as? RealVariable
realVariable?.forEachTransitiveDependentVariable {
flow.logicStatements -= it
}
var newLogicStatements = flow.logicStatements var newLogicStatements = flow.logicStatements
for ((key, implications) in flow.logicStatements) { for ((key, implications) in flow.logicStatements) {
val implicationsToDelete = mutableListOf<Implication>() val implicationsToDelete = mutableListOf<Implication>()
implications.forEach { implication -> implications.forEach { implication ->
if (implication.effect.variable == variable) { val implicationVariable = implication.effect.variable
if (implicationVariable == variable) {
implicationsToDelete += implication implicationsToDelete += implication
} }
realVariable?.forEachTransitiveDependentVariable {
if (implicationVariable == it) {
implicationsToDelete += implication
}
}
} }
if (implicationsToDelete.isEmpty()) continue if (implicationsToDelete.isEmpty()) continue
val newImplications = implications.removeAll(implicationsToDelete) val newImplications = implications.removeAll(implicationsToDelete)
@@ -346,6 +366,13 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
} }
} }
private fun RealVariable.forEachTransitiveDependentVariable(action: (RealVariable) -> Unit) {
dependentVariables.forEach {
it.forEachTransitiveDependentVariable(action)
action(it)
}
}
override fun translateVariableFromConditionInStatements( override fun translateVariableFromConditionInStatements(
flow: PersistentFlow, flow: PersistentFlow,
originalVariable: DataFlowVariable, originalVariable: DataFlowVariable,
@@ -106,7 +106,9 @@ 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) return RealVariable(identifier, isThisReference, receiverVariable, counter++, stability).also {
(receiverVariable as? RealVariable)?.dependentVariables?.add(it)
}
} }
@JvmName("getOrCreateRealVariableOrNull") @JvmName("getOrCreateRealVariableOrNull")