FIR DFA: remove approved and impossible statements from flows

This commit is contained in:
pyos
2022-11-10 17:41:41 +01:00
committed by teamcity
parent 5796f0eb07
commit 98f52f13ef
4 changed files with 48 additions and 73 deletions
@@ -436,7 +436,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
flow.addTypeStatement(operandVariable typeEq type) flow.addTypeStatement(operandVariable typeEq type)
} }
if (!type.canBeNull) { if (!type.canBeNull) {
flow.commitOperationStatement(operandVariable notEq null, shouldRemoveSynthetics = true) flow.commitOperationStatement(operandVariable notEq null)
} else { } else {
val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall) val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall)
flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null)) flow.addImplication((expressionVariable notEq null) implies (operandVariable notEq null))
@@ -514,32 +514,18 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val constValue = const.value as Boolean val constValue = const.value as Boolean
val shouldInvert = isEq xor constValue val shouldInvert = isEq xor constValue
logicSystem.translateVariableFromConditionInStatements( logicSystem.translateVariableFromConditionInStatements(flow, operandVariable, expressionVariable) {
flow,
operandVariable,
expressionVariable,
shouldRemoveOriginalStatements = operandVariable.isSynthetic()
) {
when (it.condition.operation) { when (it.condition.operation) {
// Whatever the result is after comparing operandVariable with `true` or `false` cannot let you imply effects that apply // Whatever the result is after comparing operandVariable with `true` or `false` cannot let you imply effects that apply
// when the operandVariable is null. Hence we return null here. // when the operandVariable is null. Hence we return null here.
Operation.EqNull -> null Operation.EqNull -> null
Operation.NotEqNull -> { Operation.NotEqNull -> (expressionVariable eq isEq) implies (it.effect)
(expressionVariable eq isEq) implies (it.effect) Operation.EqTrue, Operation.EqFalse -> if (shouldInvert) it.invertCondition() else it
}
Operation.EqTrue, Operation.EqFalse -> {
if (shouldInvert) it.invertCondition() else it
}
} }
} }
} }
ConstantValueKind.Null -> { ConstantValueKind.Null -> {
logicSystem.translateVariableFromConditionInStatements( logicSystem.translateVariableFromConditionInStatements(flow, operandVariable, expressionVariable) {
flow,
operandVariable,
expressionVariable,
shouldRemoveOriginalStatements = operandVariable.isSynthetic()
) {
when (it.condition.operation) { when (it.condition.operation) {
Operation.EqNull -> (expressionVariable eq isEq) implies (it.effect) Operation.EqNull -> (expressionVariable eq isEq) implies (it.effect)
Operation.NotEqNull -> (expressionVariable eq !isEq) implies (it.effect) Operation.NotEqNull -> (expressionVariable eq !isEq) implies (it.effect)
@@ -661,7 +647,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// Add `Any` to the set of possible types; the intersection type `T? & Any` will be reduced to `T` after smartcast. // Add `Any` to the set of possible types; the intersection type `T? & Any` will be reduced to `T` after smartcast.
val (node, unionNode) = graphBuilder.exitCheckNotNullCall(checkNotNullCall, callCompleted) val (node, unionNode) = graphBuilder.exitCheckNotNullCall(checkNotNullCall, callCompleted)
val argumentVariable = variableStorage.getOrCreateVariable(node.previousFlow, checkNotNullCall.argument) val argumentVariable = variableStorage.getOrCreateVariable(node.previousFlow, checkNotNullCall.argument)
node.mergeIncomingFlow().commitOperationStatement(argumentVariable notEq null, shouldRemoveSynthetics = false) node.mergeIncomingFlow().commitOperationStatement(argumentVariable notEq null)
unionNode?.unionFlowFromArguments() unionNode?.unionFlowFromArguments()
} }
@@ -677,7 +663,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val previousNode = node.previousNodes.single() val previousNode = node.previousNodes.single()
if (previousNode is WhenBranchConditionExitNode) { if (previousNode is WhenBranchConditionExitNode) {
val conditionVariable = context.variablesForWhenConditions.remove(previousNode)!! val conditionVariable = context.variablesForWhenConditions.remove(previousNode)!!
flow.commitOperationStatement(conditionVariable eq false, shouldRemoveSynthetics = true) flow.commitOperationStatement(conditionVariable eq false)
} }
} }
@@ -687,7 +673,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val conditionVariable = variableStorage.getOrCreateVariable(conditionExitFlow, whenBranch.condition) val conditionVariable = variableStorage.getOrCreateVariable(conditionExitFlow, whenBranch.condition)
context.variablesForWhenConditions[conditionExitNode] = conditionVariable context.variablesForWhenConditions[conditionExitNode] = conditionVariable
branchEnterNode.flow = conditionExitFlow.fork().also { branchEnterNode.flow = conditionExitFlow.fork().also {
it.commitOperationStatement(conditionVariable eq true, shouldRemoveSynthetics = false) it.commitOperationStatement(conditionVariable eq true)
} }
} }
@@ -704,7 +690,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (previousConditionExitNode != null) { if (previousConditionExitNode != null) {
val conditionVariable = context.variablesForWhenConditions.remove(previousConditionExitNode)!! val conditionVariable = context.variablesForWhenConditions.remove(previousConditionExitNode)!!
syntheticElseNode.flow = previousConditionExitNode.flow.fork().also { syntheticElseNode.flow = previousConditionExitNode.flow.fork().also {
it.commitOperationStatement(conditionVariable eq false, shouldRemoveSynthetics = true) it.commitOperationStatement(conditionVariable eq false)
} }
} else { } else {
syntheticElseNode.mergeIncomingFlow() syntheticElseNode.mergeIncomingFlow()
@@ -724,7 +710,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val singlePreviousNode = exitNode.previousNodes.singleOrNull { !it.isDead } val singlePreviousNode = exitNode.previousNodes.singleOrNull { !it.isDead }
if (singlePreviousNode is LoopConditionExitNode) { if (singlePreviousNode is LoopConditionExitNode) {
val variable = variableStorage.getOrCreateVariable(exitNode.previousFlow, singlePreviousNode.fir) val variable = variableStorage.getOrCreateVariable(exitNode.previousFlow, singlePreviousNode.fir)
exitNode.flow.commitOperationStatement(variable eq false, shouldRemoveSynthetics = true) exitNode.flow.commitOperationStatement(variable eq false)
} }
exitCapturingStatement(exitNode.fir) exitCapturingStatement(exitNode.fir)
} }
@@ -742,7 +728,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
loopBlockEnterNode.flow = conditionExitFlow.fork().also { loopBlockEnterNode.flow = conditionExitFlow.fork().also {
val conditionVariable = variableStorage.getVariable(conditionExitFlow, loop.condition) val conditionVariable = variableStorage.getVariable(conditionExitFlow, loop.condition)
if (conditionVariable != null) { if (conditionVariable != null) {
it.commitOperationStatement(conditionVariable eq true, shouldRemoveSynthetics = false) it.commitOperationStatement(conditionVariable eq true)
} }
} }
} }
@@ -852,7 +838,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
logicSystem.copyAllInformation(flowFromPreviousSafeCall, flow) logicSystem.copyAllInformation(flowFromPreviousSafeCall, flow)
} }
val receiverVariable = variableStorage.getOrCreateVariable(node.flow, safeCall.receiver) val receiverVariable = variableStorage.getOrCreateVariable(node.flow, safeCall.receiver)
flow.commitOperationStatement(receiverVariable notEq null, shouldRemoveSynthetics = true) flow.commitOperationStatement(receiverVariable notEq null)
} }
fun exitSafeCall(safeCall: FirSafeCallExpression) { fun exitSafeCall(safeCall: FirSafeCallExpression) {
@@ -972,14 +958,15 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val lastNode = graphBuilder.lastNode val lastNode = graphBuilder.lastNode
when (val value = effect.value) { when (val value = effect.value) {
ConeConstantReference.WILDCARD -> { ConeConstantReference.WILDCARD -> {
lastNode.flow.commitOperationStatement(argumentVariable eq true, shouldRemoveSynthetics = true) lastNode.flow.commitOperationStatement(argumentVariable eq true)
} }
is ConeBooleanConstantReference -> { is ConeBooleanConstantReference -> {
logicSystem.replaceVariableFromConditionInStatements( logicSystem.translateVariableFromConditionInStatements(
lastNode.flow, lastNode.flow,
argumentVariable, argumentVariable,
functionCallVariable, functionCallVariable,
shouldRemoveOriginalStatements = true,
filter = { it.condition.operation == Operation.EqTrue }, filter = { it.condition.operation == Operation.EqTrue },
transform = { transform = {
when (value) { when (value) {
@@ -992,10 +979,11 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
} }
ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> { ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> {
logicSystem.replaceVariableFromConditionInStatements( logicSystem.translateVariableFromConditionInStatements(
lastNode.flow, lastNode.flow,
argumentVariable, argumentVariable,
functionCallVariable, functionCallVariable,
shouldRemoveOriginalStatements = true,
filter = { it.condition.operation == Operation.EqTrue }, filter = { it.condition.operation == Operation.EqTrue },
transform = { OperationStatement(it.condition.variable, value.toOperation()) implies it.effect } transform = { OperationStatement(it.condition.variable, value.toOperation()) implies it.effect }
) )
@@ -1060,7 +1048,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (!hasExplicitType && isInitializerStable && (propertyVariable.hasLocalStability || propertyVariable.isStable)) { if (!hasExplicitType && isInitializerStable && (propertyVariable.hasLocalStability || propertyVariable.isStable)) {
logicSystem.addLocalVariableAlias(flow, propertyVariable, initializerVariable) logicSystem.addLocalVariableAlias(flow, propertyVariable, initializerVariable)
} else { } else {
logicSystem.replaceVariableFromConditionInStatements(flow, initializerVariable, propertyVariable) logicSystem.translateVariableFromConditionInStatements(flow, initializerVariable, propertyVariable)
} }
} }
@@ -1074,7 +1062,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
* x.length * x.length
* } * }
*/ */
logicSystem.replaceVariableFromConditionInStatements(flow, initializerVariable, propertyVariable) logicSystem.translateVariableFromConditionInStatements(flow, initializerVariable, propertyVariable)
} }
if (isAssignment) { if (isAssignment) {
@@ -1130,7 +1118,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val leftOperandVariable = variableStorage.getOrCreateVariable(parentFlow, leftNode.firstPreviousNode.fir) val leftOperandVariable = variableStorage.getOrCreateVariable(parentFlow, leftNode.firstPreviousNode.fir)
leftNode.flow = parentFlow.fork() leftNode.flow = parentFlow.fork()
rightNode.flow = parentFlow.fork().also { rightNode.flow = parentFlow.fork().also {
it.commitOperationStatement(leftOperandVariable eq isAnd, shouldRemoveSynthetics = false) it.commitOperationStatement(leftOperandVariable eq isAnd)
} }
} }
@@ -1159,7 +1147,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
if (!node.leftOperandNode.isDead && node.rightOperandNode.isDead) { if (!node.leftOperandNode.isDead && node.rightOperandNode.isDead) {
// If the right operand does not terminate, then we know that the value of the entire expression // If the right operand does not terminate, then we know that the value of the entire expression
// has to be `onlyLeftEvaluated`, and it has to be produced by the left operand. // has to be `onlyLeftEvaluated`, and it has to be produced by the left operand.
flow.commitOperationStatement(leftVariable eq onlyLeftEvaluated, shouldRemoveSynthetics = true) flow.commitOperationStatement(leftVariable eq onlyLeftEvaluated)
} else { } else {
// If `left && right` is true, then both are true (and evaluated). // If `left && right` is true, then both are true (and evaluated).
// If `left || right` is false, then both are false. // If `left || right` is false, then both are false.
@@ -1191,7 +1179,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val previousFlow = node.previousFlow val previousFlow = node.previousFlow
val booleanExpressionVariable = variableStorage.getOrCreateVariable(previousFlow, node.firstPreviousNode.fir) val booleanExpressionVariable = variableStorage.getOrCreateVariable(previousFlow, node.firstPreviousNode.fir)
val variable = variableStorage.getOrCreateVariable(previousFlow, functionCall) val variable = variableStorage.getOrCreateVariable(previousFlow, functionCall)
logicSystem.replaceVariableFromConditionInStatements( logicSystem.translateVariableFromConditionInStatements(
node.flow, node.flow,
booleanExpressionVariable, booleanExpressionVariable,
variable, variable,
@@ -1248,10 +1236,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
val flow = lhsExitNode.mergeIncomingFlow() val flow = lhsExitNode.mergeIncomingFlow()
val lhsVariable = variableStorage.getOrCreateVariable(flow, elvisExpression.lhs) val lhsVariable = variableStorage.getOrCreateVariable(flow, elvisExpression.lhs)
lhsIsNotNullNode.flow = flow.fork().also { lhsIsNotNullNode.flow = flow.fork().also {
it.commitOperationStatement(lhsVariable notEq null, shouldRemoveSynthetics = false) it.commitOperationStatement(lhsVariable notEq null)
} }
rhsEnterNode.flow = flow.fork().also { rhsEnterNode.flow = flow.fork().also {
it.commitOperationStatement(lhsVariable eq null, shouldRemoveSynthetics = false) it.commitOperationStatement(lhsVariable eq null)
resetReceivers(it) resetReceivers(it)
} }
} }
@@ -1262,7 +1250,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
mergePostponedLambdaExitsNode?.mergeIncomingFlow() mergePostponedLambdaExitsNode?.mergeIncomingFlow()
if (isLhsNotNull) { if (isLhsNotNull) {
val lhsVariable = variableStorage.getOrCreateVariable(node.previousFlow, elvisExpression.lhs) val lhsVariable = variableStorage.getOrCreateVariable(node.previousFlow, elvisExpression.lhs)
flow.commitOperationStatement(lhsVariable notEq null, shouldRemoveSynthetics = true) flow.commitOperationStatement(lhsVariable notEq null)
} }
if (!components.session.languageVersionSettings.supportsFeature(LanguageFeature.BooleanElvisBoundSmartCasts)) return if (!components.session.languageVersionSettings.supportsFeature(LanguageFeature.BooleanElvisBoundSmartCasts)) return
@@ -1367,8 +1355,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
private fun FLOW.fork(): FLOW = private fun FLOW.fork(): FLOW =
logicSystem.forkFlow(this) logicSystem.forkFlow(this)
private fun FLOW.commitOperationStatement(statement: OperationStatement, shouldRemoveSynthetics: Boolean) { private fun FLOW.commitOperationStatement(statement: OperationStatement) {
logicSystem.approveOperationStatement(this, statement, shouldRemoveSynthetics).values.forEach { logicSystem.approveOperationStatement(this, statement, removeApprovedOrImpossible = true).values.forEach {
addTypeStatement(it) addTypeStatement(it)
} }
if (statement.operation == Operation.NotEqNull) { if (statement.operation == Operation.NotEqNull) {
@@ -26,16 +26,16 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
flow: FLOW, flow: FLOW,
originalVariable: DataFlowVariable, originalVariable: DataFlowVariable,
newVariable: DataFlowVariable, newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean, shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(),
filter: (Implication) -> Boolean = { true }, filter: (Implication) -> Boolean = { true },
transform: (Implication) -> Implication? = { it }, transform: (Implication) -> Implication? = { it },
) )
// This does *not* commit the results to the flow (but it does mutate the flow if shouldRemoveSynthetics=true) // This does *not* commit the results to the flow (but it does mutate the flow if removeApprovedOrImpossible=true)
abstract fun approveOperationStatement( abstract fun approveOperationStatement(
flow: FLOW, flow: FLOW,
approvedStatement: OperationStatement, approvedStatement: OperationStatement,
shouldRemoveSynthetics: Boolean = false removeApprovedOrImpossible: Boolean = false
): TypeStatements ): TypeStatements
protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean protected abstract fun ConeKotlinType.isAcceptableForSmartcast(): Boolean
@@ -90,26 +90,3 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
protected fun or(statements: Collection<TypeStatement>): TypeStatement = protected fun or(statements: Collection<TypeStatement>): TypeStatement =
statements.singleOrNew { unifyTypes(statements.map { it.exactType })?.let { mutableSetOf(it) } ?: mutableSetOf() } statements.singleOrNew { unifyTypes(statements.map { it.exactType })?.let { mutableSetOf(it) } ?: mutableSetOf() }
} }
/*
* used for:
* 1. val b = x is String
* 2. b = x is String
* 3. !b | b.not() for Booleans
*/
fun <F : Flow> LogicSystem<F>.replaceVariableFromConditionInStatements(
flow: F,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
filter: (Implication) -> Boolean = { true },
transform: (Implication) -> Implication = { it },
) {
translateVariableFromConditionInStatements(
flow,
originalVariable,
newVariable,
shouldRemoveOriginalStatements = true,
filter,
transform,
)
}
@@ -256,7 +256,7 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
override fun approveOperationStatement( override fun approveOperationStatement(
flow: PersistentFlow, flow: PersistentFlow,
approvedStatement: OperationStatement, approvedStatement: OperationStatement,
shouldRemoveSynthetics: Boolean, removeApprovedOrImpossible: Boolean,
): TypeStatements { ): TypeStatements {
val approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create() val approvedTypeStatements: ArrayListMultimap<RealVariable, TypeStatement> = ArrayListMultimap.create()
val queue = LinkedList<OperationStatement>().apply { this += approvedStatement } val queue = LinkedList<OperationStatement>().apply { this += approvedStatement }
@@ -266,17 +266,21 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
// Defense from cycles in facts // Defense from cycles in facts
if (!approved.add(next)) continue if (!approved.add(next)) continue
val variable = next.variable val variable = next.variable
val statements = flow.logicStatements[variable]?.takeIf { it.isNotEmpty() } ?: continue val statements = flow.logicStatements[variable] ?: continue
if (shouldRemoveSynthetics && variable.isSynthetic()) { val stillUnknown = statements.removeAll {
flow.logicStatements -= variable val knownValue = it.condition.operation.valueIfKnown(next.operation)
} if (knownValue == true) {
for (statement in statements) { when (val effect = it.effect) {
if (statement.condition == next) {
when (val effect = statement.effect) {
is OperationStatement -> queue += effect is OperationStatement -> queue += effect
is TypeStatement -> approvedTypeStatements.put(effect.variable, effect) is TypeStatement -> approvedTypeStatements.put(effect.variable, effect)
} }
} }
removeApprovedOrImpossible && knownValue != null
}
if (stillUnknown.isEmpty()) {
flow.logicStatements -= variable
} else if (stillUnknown != statements) {
flow.logicStatements = flow.logicStatements.put(variable, stillUnknown)
} }
} }
return approvedTypeStatements.asMap().mapValues { and(it.value) } return approvedTypeStatements.asMap().mapValues { and(it.value) }
@@ -62,6 +62,12 @@ enum class Operation {
NotEqNull -> EqNull NotEqNull -> EqNull
} }
fun valueIfKnown(given: Operation): Boolean? = when (this) {
EqTrue, EqFalse -> if (given == NotEqNull) null else given == this
EqNull -> given == EqNull
NotEqNull -> given == NotEqNull
}
override fun toString(): String = when (this) { override fun toString(): String = when (this) {
EqTrue -> "== True" EqTrue -> "== True"
EqFalse -> "== False" EqFalse -> "== False"