FIR DFA: do not generate nodes (and flows) for contracts on calls

The code was already duplicated between FirDataFlowAnalyzer and
FirReturnsImpliesAnalyzer, so might as well use the latter to slightly
speed up the former.
This commit is contained in:
pyos
2022-11-12 18:11:00 +01:00
committed by teamcity
parent 67a6785f63
commit ea1caf0955
15 changed files with 390 additions and 642 deletions
@@ -28,7 +28,6 @@ abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceCont
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean = originalVariable.isSynthetic(),
filter: (Implication) -> Boolean = { true },
transform: (Implication) -> Implication? = { it },
)
@@ -237,12 +237,11 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean,
filter: (Implication) -> Boolean,
transform: (Implication) -> Implication?
) {
with(flow) {
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
val newStatements = statements.filter(filter).mapNotNull {
val newStatements = statements.mapNotNull {
val newStatement = OperationStatement(newVariable, it.condition.operation) implies it.effect
transform(newStatement)
}.toPersistentList()
@@ -761,19 +761,6 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria
}
}
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
EnterNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterContractNode(this, data)
}
}
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id),
ExitNodeMarker {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitExitContractNode(this, data)
}
}
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterSafeCallNode(this, data)
@@ -92,9 +92,6 @@ fun CFGNode<*>.render(): String =
is AnnotationEnterNode -> "Enter annotation"
is AnnotationExitNode -> "Exit annotation"
is EnterContractNode -> "Enter contract"
is ExitContractNode -> "Exit contract"
is EnterSafeCallNode -> "Enter safe call"
is ExitSafeCallNode -> "Exit safe call"
@@ -339,14 +339,6 @@ abstract class ControlFlowGraphVisitor<out R, in D> {
return visitNode(node, data)
}
open fun visitEnterContractNode(node: EnterContractNode, data: D): R {
return visitNode(node, data)
}
open fun visitExitContractNode(node: ExitContractNode, data: D): R {
return visitNode(node, data)
}
open fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: D): R {
return visitNode(node, data)
}
@@ -273,14 +273,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
visitNode(node)
}
open fun visitEnterContractNode(node: EnterContractNode) {
visitNode(node)
}
open fun visitExitContractNode(node: ExitContractNode) {
visitNode(node)
}
open fun visitEnterSafeCallNode(node: EnterSafeCallNode) {
visitNode(node)
}
@@ -570,14 +562,6 @@ abstract class ControlFlowGraphVisitorVoid : ControlFlowGraphVisitor<Unit, Nothi
visitVariableAssignmentNode(node)
}
final override fun visitEnterContractNode(node: EnterContractNode, data: Nothing?) {
visitEnterContractNode(node)
}
final override fun visitExitContractNode(node: ExitContractNode, data: Nothing?) {
visitExitContractNode(node)
}
final override fun visitEnterSafeCallNode(node: EnterSafeCallNode, data: Nothing?) {
visitEnterSafeCallNode(node)
}
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2022 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
import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.expressions.LogicOperationKind
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.types.*
fun ConeConstantReference.toOperation(): Operation? = when (this) {
ConeConstantReference.WILDCARD -> null
ConeConstantReference.NULL -> Operation.EqNull
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
}
// Returns `null` if the statement is always false.
fun <F : Flow> LogicSystem<F>.approveContractStatement(
flow: F,
statement: ConeBooleanExpression,
arguments: Array<out DataFlowVariable?>, // 0 = receiver (null if doesn't exist)
substitutor: ConeSubstitutor?,
removeApprovedOrImpossible: Boolean = false,
): TypeStatements? {
fun OperationStatement.approve() =
approveOperationStatement(flow, this, removeApprovedOrImpossible)
fun DataFlowVariable.processEqNull(isEq: Boolean): TypeStatements =
OperationStatement(this, if (isEq) Operation.EqNull else Operation.NotEqNull).approve()
fun ConeBooleanExpression.visit(inverted: Boolean): TypeStatements? = when (this) {
is ConeBooleanConstantReference ->
if (inverted == (this == ConeBooleanConstantReference.TRUE)) null else mapOf()
is ConeLogicalNot -> arg.visit(inverted = !inverted)
is ConeIsInstancePredicate ->
arguments.getOrNull(arg.parameterIndex + 1)?.let {
val isType = inverted == isNegated
val substitutedType = substitutor?.substituteOrNull(type) ?: type
when {
substitutedType.isAny -> it.processEqNull(!isType)
substitutedType.isNullableNothing -> it.processEqNull(isType)
else -> {
// x is (T & Any) => x != null
// TODO? (KT-22996) x !is T? => x != null: change `&&` to `==`
val fromNullability = if (isType && !type.canBeNull) it.processEqNull(false) else mapOf()
if (isType && it is RealVariable) {
andForTypeStatements(fromNullability, mapOf(it to (it typeEq substitutedType)))
} else {
fromNullability
}
}
}
} ?: mapOf()
is ConeIsNullPredicate ->
arguments.getOrNull(arg.parameterIndex + 1)?.processEqNull(inverted == isNegated) ?: mapOf()
is ConeBooleanValueParameterReference ->
arguments.getOrNull(parameterIndex + 1)?.let {
OperationStatement(it, if (inverted) Operation.EqFalse else Operation.EqTrue).approve()
} ?: mapOf()
is ConeBinaryLogicExpression -> {
val a = left.visit(inverted)
val b = right.visit(inverted)
val isAnd = inverted != (kind == LogicOperationKind.AND)
when {
a == null -> b.takeIf { !isAnd } // false || b == b; false && b = false
b == null -> a.takeIf { !isAnd } // a || false == a; a && false = false
isAnd -> andForTypeStatements(a, b)
else -> orForTypeStatements(a, b)
}
}
else -> mapOf()
}
return statement.visit(inverted = false)
}
@@ -7,8 +7,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa
import kotlinx.collections.immutable.PersistentMap
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
@@ -16,12 +14,8 @@ import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeContext
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.fir.types.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@@ -58,9 +52,9 @@ internal inline fun <K, V> PersistentMap<K, V>.put(
}
}
fun Set<ConeKotlinType>?.intersectWith(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
if (!isNullOrEmpty()) {
context.intersectTypes(toMutableList().also { it += originalType })
fun TypeStatement?.smartCastedType(context: ConeTypeContext, originalType: ConeKotlinType): ConeKotlinType =
if (this != null && exactType.isNotEmpty()) {
context.intersectTypes(exactType.toMutableList().also { it += originalType })
} else {
originalType
}
@@ -74,19 +68,6 @@ fun FirOperation.isEq(): Boolean {
}
}
fun FirFunctionCall.isBooleanNot(): Boolean {
val symbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false
return symbol.callableId == StandardClassIds.Callables.not
}
fun ConeConstantReference.toOperation(): Operation = when (this) {
ConeConstantReference.NULL -> Operation.EqNull
ConeConstantReference.NOT_NULL -> Operation.NotEqNull
ConeBooleanConstantReference.TRUE -> Operation.EqTrue
ConeBooleanConstantReference.FALSE -> Operation.EqFalse
else -> throw IllegalArgumentException("$this can not be transformed to Operation")
}
@DfaInternals
val FirExpression.coneType: ConeKotlinType
get() = typeRef.coneType