[FIR] Remove old DFA

This commit is contained in:
Dmitriy Novozhilov
2020-01-21 12:28:36 +03:00
parent 5d3b75ebc3
commit 16a9ca5912
7 changed files with 0 additions and 1951 deletions
@@ -1,141 +0,0 @@
/*
* Copyright 2010-2019 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.FirElement
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
/*
* isSynthetic = false for variables that represents actual variables in fir
* isSynthetic = true for complex expressions (like when expression)
*/
sealed class DataFlowVariable(private val variableIndexForDebug: Int, val fir: FirElement) {
final override fun toString(): String {
return "d$variableIndexForDebug"
}
}
open class RealDataFlowVariable(index: Int, fir: FirElement, val isThisReference: Boolean) : DataFlowVariable(index, fir)
class SyntheticDataFlowVariable(index: Int, fir: FirElement) : DataFlowVariable(index, fir)
class AliasedDataFlowVariable(
index: Int,
fir: FirElement,
var delegate: RealDataFlowVariable
) : RealDataFlowVariable(index, fir, delegate.isThisReference)
// -------------------------------------------------------------------------------------------------------------------------
@UseExperimental(ExperimentalContracts::class)
fun DataFlowVariable.isSynthetic(): Boolean {
contract {
returns(true) implies (this@isSynthetic is SyntheticDataFlowVariable)
}
return this is SyntheticDataFlowVariable
}
@UseExperimental(ExperimentalContracts::class)
fun DataFlowVariable.isAliasVariable(): Boolean {
contract {
returns(true) implies (this@isAliasVariable is AliasedDataFlowVariable)
}
return this is AliasedDataFlowVariable
}
val RealDataFlowVariable.variableUnderAlias: RealDataFlowVariable
get() {
var variable = this
while (variable.isAliasVariable()) {
variable = variable.delegate
}
return variable
}
// -------------------------------------------------------------------------------------------------------------------------
class DataFlowVariableStorage {
private val fir2DfiMap: MutableMap<FirElement, DataFlowVariable> = mutableMapOf()
private var debugIndexCounter: Int = 1
fun getOrCreateNewRealVariable(symbol: AbstractFirBasedSymbol<*>): RealDataFlowVariable {
return getOrCreateNewRealVariableImpl(symbol, false)
}
fun getOrCreateNewThisRealVariable(symbol: AbstractFirBasedSymbol<*>): RealDataFlowVariable {
return getOrCreateNewRealVariableImpl(symbol, true)
}
private fun getOrCreateNewRealVariableImpl(symbol: AbstractFirBasedSymbol<*>, isThisReference: Boolean): RealDataFlowVariable {
val fir = symbol.fir
get(fir)?.let { return it as RealDataFlowVariable }
return RealDataFlowVariable(debugIndexCounter++, fir, isThisReference).also { storeVariable(it, fir) }
}
fun getOrCreateNewSyntheticVariable(fir: FirElement): SyntheticDataFlowVariable {
get(fir)?.let { return it as SyntheticDataFlowVariable }
return SyntheticDataFlowVariable(debugIndexCounter++, fir).also { storeVariable(it, fir) }
}
fun createAliasVariable(symbol: AbstractFirBasedSymbol<*>, variable: RealDataFlowVariable) {
createAliasVariable(symbol.fir, variable)
}
private fun createAliasVariable(fir: FirElement, variable: RealDataFlowVariable) {
AliasedDataFlowVariable(debugIndexCounter++, fir, variable).also { storeVariable(it, fir) }
}
fun rebindAliasVariable(aliasVariable: RealDataFlowVariable, newVariable: RealDataFlowVariable) {
val fir = removeVariable(aliasVariable)
requireNotNull(fir)
createAliasVariable(fir, newVariable)
}
fun removeRealVariable(symbol: AbstractFirBasedSymbol<*>) {
removeSyntheticVariable(symbol.fir)
}
fun removeSyntheticVariable(fir: FirElement) {
val variable = fir2DfiMap[fir] ?: return
removeVariable(variable)
}
fun removeVariable(variable: DataFlowVariable): FirElement? {
return variable.fir.also {
fir2DfiMap.remove(it)
}
}
fun removeVariableIfSynthetic(variable: DataFlowVariable): FirElement? {
if (variable is SyntheticDataFlowVariable) {
return removeVariable(variable)
}
return null
}
operator fun get(variable: DataFlowVariable): FirElement? {
return variable.fir
}
operator fun get(firElement: FirElement): DataFlowVariable? {
return fir2DfiMap[firElement]
}
operator fun get(symbol: AbstractFirBasedSymbol<*>): RealDataFlowVariable? {
return fir2DfiMap[symbol.fir] as RealDataFlowVariable?
}
fun reset() {
fir2DfiMap.clear()
debugIndexCounter = 1
}
private fun storeVariable(variable: DataFlowVariable, fir: FirElement) {
fir2DfiMap[fir] = variable
}
}
@@ -1,232 +0,0 @@
/*
* Copyright 2010-2019 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 com.google.common.collect.ArrayListMultimap
import com.google.common.collect.Multimap
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
class DelegatingFlow(
val previousFlow: DelegatingFlow?,
val approvedInfos: MutableApprovedInfos = mutableMapOf(),
val conditionalInfos: ConditionalInfos = ArrayListMultimap.create()
) : Flow {
val level: Int = previousFlow?.level?.plus(1) ?: 0
override fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo? {
val info = MutableFirDataFlowInfo(mutableSetOf(), mutableSetOf())
collect { flow ->
flow.approvedInfos[variable]?.let { info += it }
true
}
return info.takeIf { it.isNotEmpty }
}
override fun getConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
val result = mutableSetOf<ConditionalFirDataFlowInfo>()
val isReal = !variable.isSynthetic()
collect {
val infos = it.conditionalInfos[variable]
result += infos
/*
* Here we use invariant that if we found some conditional info about synthetic variable
* on some level then there is definitely no another info on other levels of flow
* It's correct because we can add condtional info about synthetic variable only
* when we first met expression corresponding to that variable
*/
isReal || infos.isEmpty()
}
return result
}
override fun getVariablesInApprovedInfos(): Collection<RealDataFlowVariable> {
val result = mutableSetOf<RealDataFlowVariable>()
collect {
result += approvedInfos.keys
true
}
return result
}
override fun removeConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
return conditionalInfos.removeAll(variable)
}
private inline fun collect(block: (DelegatingFlow) -> Boolean) {
var flow: DelegatingFlow? = this
while (flow != null) {
val shouldContinue = block(flow)
if (!shouldContinue) break
flow = flow.previousFlow
}
}
}
fun DelegatingFlow.approvedInfosFromTopFlow(): ApprovedInfos {
return approvedInfos
}
fun DelegatingFlow.conditionalInfosFromTopFlow(): ConditionalInfos {
return ImmutableMultimap(conditionalInfos)
}
private fun DelegatingFlow.approvedInfosUntilParent(parent: DelegatingFlow): ApprovedInfos {
return when(level - parent.level) {
0 -> emptyMap()
1 -> approvedInfosFromTopFlow()
else -> mutableMapOf<RealDataFlowVariable, FirDataFlowInfo>().apply {
traverse(parent) {
it.approvedInfosFromTopFlow().forEach { (variable, info) ->
compute(variable) { _, existingInfo ->
existingInfo?.plus(info) ?: info
}
}
}
}
}
}
private inline fun DelegatingFlow.traverse(untilFlow: DelegatingFlow, block: (DelegatingFlow) -> Unit) {
var flow = this
while (flow != untilFlow) {
block(flow)
flow = flow.previousFlow!!
}
}
private class ImmutableMultimap<K, V>(private val original: Multimap<K, V>) : Multimap<K, V> by original {
override fun put(p0: K?, p1: V?): Boolean {
throw IllegalStateException()
}
override fun remove(p0: Any?, p1: Any?): Boolean {
throw IllegalStateException()
}
override fun putAll(p0: Multimap<out K, out V>): Boolean {
throw IllegalStateException()
}
override fun putAll(p0: K?, p1: MutableIterable<V>): Boolean {
throw IllegalStateException()
}
override fun removeAll(p0: Any?): MutableCollection<V> {
throw IllegalStateException()
}
override fun replaceValues(p0: K?, p1: MutableIterable<V>): MutableCollection<V> {
throw IllegalStateException()
}
}
abstract class DelegatingLogicSystem(context: ConeInferenceContext) : LogicSystem(context) {
override val Flow.approvedInfos: MutableApprovedInfos
get() = (this as DelegatingFlow).approvedInfos
override val Flow.conditionalInfos: ConditionalInfos
get() = (this as DelegatingFlow).conditionalInfos
override fun createEmptyFlow(): Flow {
return DelegatingFlow(null)
}
override fun forkFlow(flow: Flow): Flow {
require(flow is DelegatingFlow)
return DelegatingFlow(flow)
}
override fun collectInfoForBooleanOperator(
leftFlow: Flow,
leftVariable: DataFlowVariable,
rightFlow: Flow,
rightVariable: DataFlowVariable
): InfoForBooleanOperator {
require(leftFlow is DelegatingFlow && rightFlow is DelegatingFlow)
val parent = lowestCommonFlow(leftFlow, rightFlow)
return InfoForBooleanOperator(
leftFlow.previousFlow!!.conditionalInfosFromTopFlow()[leftVariable],
rightFlow.conditionalInfosFromTopFlow()[rightVariable],
rightFlow.approvedInfosUntilParent(parent)
)
}
// ------------------------------- Flow operations -------------------------------
override fun joinFlow(flows: Collection<Flow>): Flow {
if (flows.isEmpty()) return createEmptyFlow()
flows.singleOrNull()?.let { return it }
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
val flows = flows as Collection<DelegatingFlow>
val commonFlow = flows.reduce(this::lowestCommonFlow)
val approvedInfosFromAllFlows = mutableListOf<ApprovedInfos>()
flows.forEach {
approvedInfosFromAllFlows += it.collectInfos(commonFlow)
}
// approved info
val variablesFromApprovedInfos = approvedInfosFromAllFlows.map { it.keys }.intersectSets()
for (variable in variablesFromApprovedInfos) {
val infos = approvedInfosFromAllFlows.mapNotNull { it[variable] }
if (infos.size != flows.size) continue
val intersectedInfo = or(infos)
if (intersectedInfo.isEmpty) continue
val existingInfo = commonFlow.approvedInfos[variable]
if (existingInfo == null) {
commonFlow.approvedInfos[variable] = intersectedInfo
} else {
existingInfo += intersectedInfo
}
}
updateAllReceivers(commonFlow)
return commonFlow
}
// ------------------------------- Util functions -------------------------------
private fun lowestCommonFlow(left: DelegatingFlow, right: DelegatingFlow): DelegatingFlow {
val level = minOf(left.level, right.level)
@Suppress("NAME_SHADOWING")
var left = left
while (left.level > level) {
left = left.previousFlow!!
}
@Suppress("NAME_SHADOWING")
var right = right
while (right.level > level) {
right = right.previousFlow!!
}
while (left != right) {
left = left.previousFlow!!
right = right.previousFlow!!
}
return left
}
private fun DelegatingFlow.collectInfos(untilFlow: DelegatingFlow): ApprovedInfos {
val approvedInfos: MutableApprovedInfos = mutableMapOf()
// val conditionalInfos: ConditionalInfos = ArrayListMultimap.create()
var flow = this
while (flow != untilFlow) {
flow.approvedInfos.forEach { (variable, info) -> approvedInfos.addInfo(variable, info) }
/*
* we don't join conditional infos yet
flow.conditionalInfos.asMap().forEach { (variable, infos) ->
conditionalInfos.putAll(variable, infos)
}
*/
flow = flow.previousFlow!!
}
return approvedInfos
}
}
@@ -1,920 +0,0 @@
/*
* Copyright 2010-2019 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.*
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConditionalEffectDeclaration
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeReturnsEffectDeclaration
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirThisReceiverExpressionImpl
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirExplicitThisReference
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStackImpl
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
import org.jetbrains.kotlin.fir.resolve.dfa.Condition.*
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.resolve.dfa.contracts.buildContractFir
import org.jetbrains.kotlin.fir.resolve.dfa.contracts.createArgumentsMapping
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.resultType
import org.jetbrains.kotlin.fir.resolve.withNullability
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.CallableId
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class FirDataFlowAnalyzer(private val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents) : BodyResolveComponents by components {
companion object {
private val KOTLIN_BOOLEAN_NOT = CallableId(FqName("kotlin"), FqName("Boolean"), Name.identifier("not"))
}
private val context: ConeInferenceContext get() = inferenceComponents.ctx
private val receiverStack: ImplicitReceiverStackImpl = components.implicitReceiverStack as ImplicitReceiverStackImpl
private val graphBuilder = ControlFlowGraphBuilder()
private val logicSystem: LogicSystem = LogicSystemImpl(context)
private val variableStorage = DataFlowVariableStorage()
private val flowOnNodes = mutableMapOf<CFGNode<*>, Flow>()
private val variablesForWhenConditions = mutableMapOf<WhenBranchConditionExitNode, DataFlowVariable>()
private var contractDescriptionVisitingMode = false
/*
* If there is no types from smartcasts function returns null
*
* Note that return value based on state of DataFlowAnalyzer (despite of stateless model of old frontend)
*/
fun getTypeUsingSmartcastInfo(qualifiedAccessExpression: FirQualifiedAccessExpression): Collection<ConeKotlinType>? {
/*
* DataFlowAnalyzer holds variables only for declarations that have some smartcast (or can have)
* If there is no useful information there is no data flow variable also
*/
val variable = qualifiedAccessExpression.realVariable?.variableUnderAlias ?: return null
return graphBuilder.lastNode.flow.getApprovedInfo(variable)?.exactType ?: return null
}
// ----------------------------------- Named function -----------------------------------
fun enterFunction(function: FirFunction<*>) {
val (functionEnterNode, previousNode) = graphBuilder.enterFunction(function)
if (previousNode == null) {
functionEnterNode.mergeIncomingFlow()
} else {
assert(functionEnterNode.previousNodes.isEmpty())
functionEnterNode.flow = logicSystem.forkFlow(previousNode.flow)
}
}
fun exitFunction(function: FirFunction<*>): ControlFlowGraph? {
val (node, graph) = graphBuilder.exitFunction(function)
if (function.body == null) {
node.mergeIncomingFlow()
}
for (valueParameter in function.valueParameters) {
variableStorage.removeRealVariable(valueParameter.symbol)
}
if (graphBuilder.isTopLevel()) {
flowOnNodes.clear()
variableStorage.reset()
graphBuilder.reset()
}
return graph
}
// ----------------------------------- Property -----------------------------------
fun enterProperty(property: FirProperty) {
graphBuilder.enterProperty(property).mergeIncomingFlow()
}
fun exitProperty(property: FirProperty): ControlFlowGraph {
val (node, graph) = graphBuilder.exitProperty(property)
node.mergeIncomingFlow()
return graph
}
// ----------------------------------- Block -----------------------------------
fun enterBlock(block: FirBlock) {
graphBuilder.enterBlock(block)?.mergeIncomingFlow()
}
fun exitBlock(block: FirBlock) {
graphBuilder.exitBlock(block).mergeIncomingFlow()
}
private fun FirElement.extractReturnType(target: FirFunction<*>?): ConeKotlinType? {
return when (this) {
is FirReturnExpression -> {
if (this.target.labeledElement == target) {
result.extractReturnType(target)
} else {
result.resultType.coneTypeSafe()
}
}
is FirExpression -> {
typeRef.coneTypeSafe()
}
else -> session.builtinTypes.unitType.type
}
}
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): List<FirStatement> {
return graphBuilder.returnExpressionsOfAnonymousFunction(function)
}
// ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) {
val node = graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow()
if (typeOperatorCall.operation !in FirOperation.TYPES) return
val type = typeOperatorCall.conversionTypeRef.coneTypeSafe<ConeKotlinType>() ?: return
val operandVariable = getOrCreateRealVariable(typeOperatorCall.argument) ?: return
val flow = node.flow
when (typeOperatorCall.operation) {
FirOperation.IS, FirOperation.NOT_IS -> {
val expressionVariable = getOrCreateSyntheticVariable(typeOperatorCall)
val trueInfo = FirDataFlowInfo(setOf(type), emptySet())
val falseInfo = FirDataFlowInfo(emptySet(), setOf(type))
fun chooseInfo(trueBranch: Boolean) =
if ((typeOperatorCall.operation == FirOperation.IS) == trueBranch) trueInfo else falseInfo
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
EqTrue, operandVariable, chooseInfo(true)
)
)
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
EqFalse, operandVariable, chooseInfo(false)
)
)
}
FirOperation.AS -> {
logicSystem.addApprovedInfo(flow, operandVariable, FirDataFlowInfo(setOf(type), emptySet()))
}
FirOperation.SAFE_AS -> {
val expressionVariable = getOrCreateSyntheticVariable(typeOperatorCall)
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
NotEqNull, operandVariable, FirDataFlowInfo(setOf(type), emptySet())
)
)
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
EqNull, operandVariable, FirDataFlowInfo(emptySet(), setOf(type))
)
)
}
else -> throw IllegalStateException()
}
node.flow = flow
}
fun exitOperatorCall(operatorCall: FirOperatorCall) {
val node = graphBuilder.exitOperatorCall(operatorCall).mergeIncomingFlow()
when (val operation = operatorCall.operation) {
FirOperation.EQ, FirOperation.NOT_EQ, FirOperation.IDENTITY, FirOperation.NOT_IDENTITY -> {
val leftOperand = operatorCall.arguments[0]
val rightOperand = operatorCall.arguments[1]
val leftConst = leftOperand as? FirConstExpression<*>
val rightConst = rightOperand as? FirConstExpression<*>
when {
leftConst?.kind == FirConstKind.Null -> processEqNull(node, rightOperand, operation)
rightConst?.kind == FirConstKind.Null -> processEqNull(node, leftOperand, operation)
leftConst != null -> processEqWithConst(node, rightOperand, leftConst, operation)
rightConst != null -> processEqWithConst(node, leftOperand, rightConst, operation)
operation != FirOperation.EQ && operation != FirOperation.IDENTITY -> return
else -> processEq(node, leftOperand, rightOperand, operation)
}
}
}
}
private fun processEqWithConst(node: OperatorCallNode, operand: FirExpression, const: FirConstExpression<*>, operation: FirOperation) {
val isEq = when (operation) {
FirOperation.EQ, FirOperation.IDENTITY -> true
FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY -> false
else -> return
}
val expressionVariable = getOrCreateVariable(node.fir)
val flow = node.flow
// not null for comparisons with constants
getRealVariablesForSafeCallChain(operand).takeIf { it.isNotEmpty() }?.let { operandVariables ->
operandVariables.forEach { operandVariable ->
logicSystem.addConditionalInfo(
flow,
expressionVariable, ConditionalFirDataFlowInfo(
isEq.toEqBoolean(),
operandVariable,
FirDataFlowInfo(setOf(session.builtinTypes.anyType.coneTypeUnsafe()), emptySet())
)
)
}
}
// propagating facts for (... == true) and (... == false)
variableStorage[operand]?.let { operandVariable ->
if (const.kind != FirConstKind.Boolean) return@let
val constValue = (const.value as Boolean)
val shouldInvert = isEq xor constValue
flow.getConditionalInfos(operandVariable).forEach { info ->
logicSystem.addConditionalInfo(flow, expressionVariable, info.let { if (shouldInvert) it.invert() else it })
}
}
}
private fun processEq(node: OperatorCallNode, leftOperand: FirExpression, rightOperand: FirExpression, operation: FirOperation) {
val leftType = leftOperand.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
val rightType = rightOperand.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
when {
leftType.isMarkedNullable && rightType.isMarkedNullable -> return
leftType.isMarkedNullable -> processEqNull(node, leftOperand, FirOperation.NOT_EQ)
rightType.isMarkedNullable -> processEqNull(node, rightOperand, FirOperation.NOT_EQ)
}
// TODO: process EQUALITY
}
private fun processEqNull(node: OperatorCallNode, operand: FirExpression, operation: FirOperation) {
val flow = node.flow
val expressionVariable = getOrCreateVariable(node.fir)
variableStorage[operand]?.let { operandVariable ->
val condition = when (operation) {
FirOperation.EQ, FirOperation.IDENTITY -> EqNull
FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY -> NotEqNull
else -> throw IllegalArgumentException()
}
val facts = logicSystem.approveFact(operandVariable, condition, flow)
facts.forEach { (variable, info) ->
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
EqTrue, variable, info
)
)
logicSystem.addConditionalInfo(
flow,
expressionVariable,
ConditionalFirDataFlowInfo(
EqFalse, variable, info.invert()
)
)
}
node.flow = flow
return
}
val operandVariables = getRealVariablesForSafeCallChain(operand).takeIf { it.isNotEmpty() } ?: return
val condition = when (operation) {
FirOperation.EQ, FirOperation.IDENTITY -> EqFalse
FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY -> EqTrue
else -> throw IllegalArgumentException()
}
operandVariables.forEach { operandVariable ->
logicSystem.addConditionalInfo(
flow,
expressionVariable, ConditionalFirDataFlowInfo(
condition,
operandVariable,
FirDataFlowInfo(setOf(session.builtinTypes.anyType.coneTypeUnsafe()), emptySet())
)
)
// TODO: design do we need casts to Nothing?
/*
flow = addConditionalInfo(
expressionVariable, UnapprovedFirDataFlowInfo(
eq(conditionValue.invert()!!),
operandVariable,
FirDataFlowInfo(setOf(session.builtinTypes.nullableNothingType.coneTypeUnsafe()), emptySet())
)
)
*/
}
}
// ----------------------------------- Jump -----------------------------------
fun exitJump(jump: FirJump<*>) {
graphBuilder.exitJump(jump).mergeIncomingFlow()
}
// ----------------------------------- Check not null call -----------------------------------
fun exitCheckNotNullCall(checkNotNullCall: FirCheckNotNullCall) {
// Add `Any` to the set of possible types; the intersection type `T? & Any` will be reduced to `T` after smartcast.
val node = graphBuilder.exitCheckNotNullCall(checkNotNullCall).mergeIncomingFlow()
val operandVariable = getOrCreateRealVariable(checkNotNullCall.argument) ?: return
logicSystem.addApprovedInfo(
node.flow,
operandVariable,
FirDataFlowInfo(setOf(session.builtinTypes.anyType.coneTypeUnsafe()), emptySet())
)
}
// ----------------------------------- When -----------------------------------
fun enterWhenExpression(whenExpression: FirWhenExpression) {
graphBuilder.enterWhenExpression(whenExpression).mergeIncomingFlow()
}
fun enterWhenBranchCondition(whenBranch: FirWhenBranch) {
val node = graphBuilder.enterWhenBranchCondition(whenBranch).mergeIncomingFlow()
val previousNode = node.previousNodes.single()
if (previousNode is WhenBranchConditionExitNode) {
node.flow = logicSystem.approveFactsInsideFlow(
variablesForWhenConditions.remove(previousNode)!!,
EqFalse,
node.flow,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
}
}
fun exitWhenBranchCondition(whenBranch: FirWhenBranch) {
val (conditionExitNode, branchEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch)
conditionExitNode.mergeIncomingFlow()
val conditionVariable = getOrCreateVariable(whenBranch.condition)
variablesForWhenConditions[conditionExitNode] = conditionVariable
branchEnterNode.flow = logicSystem.approveFactsInsideFlow(
conditionVariable,
EqTrue,
conditionExitNode.flow,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
}
fun exitWhenBranchResult(whenBranch: FirWhenBranch) {
graphBuilder.exitWhenBranchResult(whenBranch).mergeIncomingFlow()
}
fun exitWhenExpression(whenExpression: FirWhenExpression) {
val (whenExitNode, syntheticElseNode) = graphBuilder.exitWhenExpression(whenExpression)
if (syntheticElseNode != null) {
syntheticElseNode.mergeIncomingFlow()
val previousConditionExitNode = syntheticElseNode.previousNodes.single() as? WhenBranchConditionExitNode
// previous node for syntheticElseNode can be not WhenBranchConditionExitNode in case of `when` without any branches
// in that case there will be when enter or subject access node
if (previousConditionExitNode != null) {
syntheticElseNode.flow = logicSystem.approveFactsInsideFlow(
variablesForWhenConditions.remove(previousConditionExitNode)!!,
EqFalse,
syntheticElseNode.flow,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
}
}
val previousFlows = whenExitNode.alivePreviousNodes.map { it.flow }
val flow = logicSystem.joinFlow(previousFlows)
whenExitNode.flow = flow
// TODO
// val subjectSymbol = whenExpression.subjectVariable?.symbol
// if (subjectSymbol != null) {
// variableStorage[subjectSymbol]?.let { flow = flow.removeVariable(it) }
// }
// node.flow = flow
}
// ----------------------------------- While Loop -----------------------------------
fun enterWhileLoop(loop: FirLoop) {
val (loopEnterNode, loopConditionEnterNode) = graphBuilder.enterWhileLoop(loop)
loopEnterNode.mergeIncomingFlow()
loopConditionEnterNode.mergeIncomingFlow()
}
fun exitWhileLoopCondition(loop: FirLoop) {
val (loopConditionExitNode, loopBlockEnterNode) = graphBuilder.exitWhileLoopCondition(loop)
loopConditionExitNode.mergeIncomingFlow()
loopBlockEnterNode.mergeIncomingFlow()
variableStorage[loop.condition]?.let { conditionVariable ->
loopBlockEnterNode.flow = logicSystem.approveFactsInsideFlow(
conditionVariable,
EqTrue,
loopBlockEnterNode.flow,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
}
}
fun exitWhileLoop(loop: FirLoop) {
val (blockExitNode, exitNode) = graphBuilder.exitWhileLoop(loop)
blockExitNode.mergeIncomingFlow()
exitNode.mergeIncomingFlow()
}
// ----------------------------------- Do while Loop -----------------------------------
fun enterDoWhileLoop(loop: FirLoop) {
val (loopEnterNode, loopBlockEnterNode) = graphBuilder.enterDoWhileLoop(loop)
loopEnterNode.mergeIncomingFlow()
loopBlockEnterNode.mergeIncomingFlow()
}
fun enterDoWhileLoopCondition(loop: FirLoop) {
val (loopBlockExitNode, loopConditionEnterNode) = graphBuilder.enterDoWhileLoopCondition(loop)
loopBlockExitNode.mergeIncomingFlow()
loopConditionEnterNode.mergeIncomingFlow()
}
fun exitDoWhileLoop(loop: FirLoop) {
val (loopConditionExitNode, loopExitNode) = graphBuilder.exitDoWhileLoop(loop)
loopConditionExitNode.mergeIncomingFlow()
loopExitNode.mergeIncomingFlow()
}
// ----------------------------------- Try-catch-finally -----------------------------------
fun enterTryExpression(tryExpression: FirTryExpression) {
val (tryExpressionEnterNode, tryMainBlockEnterNode) = graphBuilder.enterTryExpression(tryExpression)
tryExpressionEnterNode.mergeIncomingFlow()
tryMainBlockEnterNode.mergeIncomingFlow()
}
fun exitTryMainBlock(tryExpression: FirTryExpression) {
graphBuilder.exitTryMainBlock(tryExpression).mergeIncomingFlow()
}
fun enterCatchClause(catch: FirCatch) {
graphBuilder.enterCatchClause(catch).mergeIncomingFlow()
}
fun exitCatchClause(catch: FirCatch) {
graphBuilder.exitCatchClause(catch).mergeIncomingFlow()
}
fun enterFinallyBlock(tryExpression: FirTryExpression) {
// TODO
graphBuilder.enterFinallyBlock(tryExpression).mergeIncomingFlow()
}
fun exitFinallyBlock(tryExpression: FirTryExpression) {
// TODO
graphBuilder.exitFinallyBlock(tryExpression).mergeIncomingFlow()
}
fun exitTryExpression(tryExpression: FirTryExpression) {
// TODO
graphBuilder.exitTryExpression(tryExpression).mergeIncomingFlow()
}
// ----------------------------------- Resolvable call -----------------------------------
private fun enterSafeCall(qualifiedAccess: FirQualifiedAccess) {
if (!qualifiedAccess.safe) return
val node = graphBuilder.enterSafeCall(qualifiedAccess).mergeIncomingFlow()
val previousNode = node.alivePreviousNodes.first()
val shouldFork: Boolean
var flow= if (previousNode is ExitSafeCallNode) {
shouldFork = false
previousNode.alivePreviousNodes.getOrNull(1)?.flow ?: node.flow
} else {
shouldFork = true
node.flow
}
qualifiedAccess.explicitReceiver?.let {
val type = it.typeRef.coneTypeSafe<ConeKotlinType>()
?.takeIf { it.isMarkedNullable }
?.withNullability(ConeNullability.NOT_NULL, session.inferenceContext)
?: return@let
when (val variable = getOrCreateVariable(it)) {
is RealDataFlowVariable -> {
if (shouldFork) {
flow = logicSystem.forkFlow(flow)
}
logicSystem.addApprovedInfo(flow, variable, FirDataFlowInfo(setOf(type), emptySet()))
}
is SyntheticDataFlowVariable -> {
flow = logicSystem.approveFactsInsideFlow(variable, NotEqNull, flow, shouldFork, true)
}
}
}
node.flow = flow
}
private fun exitSafeCall(qualifiedAccess: FirQualifiedAccess) {
if (!qualifiedAccess.safe) return
graphBuilder.exitSafeCall(qualifiedAccess).mergeIncomingFlow()
}
fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
enterSafeCall(qualifiedAccessExpression)
}
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
exitSafeCall(qualifiedAccessExpression)
}
fun exitFunctionCall(functionCall: FirFunctionCall) {
val node = graphBuilder.exitFunctionCall(functionCall).mergeIncomingFlow()
if (functionCall.isBooleanNot()) {
exitBooleanNot(functionCall, node)
}
processConditionalContract(functionCall)
if (functionCall.safe) {
exitSafeCall(functionCall)
}
}
private fun processConditionalContract(functionCall: FirFunctionCall) {
val contractDescription = (functionCall.resolvedSymbol?.fir as? FirSimpleFunction)?.contractDescription ?: return
val conditionalEffects = contractDescription.effects.filterIsInstance<ConeConditionalEffectDeclaration>()
if (conditionalEffects.isEmpty()) return
val argumentsMapping = createArgumentsMapping(functionCall) ?: return
contractDescriptionVisitingMode = true
graphBuilder.enterContract(functionCall).mergeIncomingFlow()
val functionCallVariable = getOrCreateVariable(functionCall)
for (conditionalEffect in conditionalEffects) {
val fir = conditionalEffect.buildContractFir(argumentsMapping) ?: continue
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
fir.transformSingle(components.transformer, ResolutionMode.ContextDependent)
val argumentVariable = getOrCreateVariable(fir)
val lastNode = graphBuilder.lastNode
when (val value = effect.value) {
ConeConstantReference.WILDCARD -> {
lastNode.flow = logicSystem.approveFactsInsideFlow(
argumentVariable,
EqTrue,
lastNode.flow,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
}
ConeBooleanConstantReference.TRUE, ConeBooleanConstantReference.FALSE -> {
logicSystem.changeVariableForConditionFlow(lastNode.flow, argumentVariable, functionCallVariable) {
it.takeIf { it.condition == if (value == ConeBooleanConstantReference.TRUE) EqTrue else EqFalse }
}
}
ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> {
logicSystem.changeVariableForConditionFlow(lastNode.flow, argumentVariable, functionCallVariable) {
it.takeIf { it.condition == EqTrue }?.let {
val condition = if (value == ConeConstantReference.NOT_NULL) Condition.NotEqNull else NotEqNull
ConditionalFirDataFlowInfo(condition, it.variable, it.info)
}
}
}
else -> throw IllegalArgumentException(value.toString())
}
}
graphBuilder.exitContract(functionCall).mergeIncomingFlow()
contractDescriptionVisitingMode = true
}
private val FirElement.resolvedSymbol: AbstractFirBasedSymbol<*>?
get() {
return when (this) {
is FirResolvable -> resolvedSymbol
is FirSymbolOwner<*> -> symbol
else -> null
}
}
private val FirResolvable.resolvedSymbol: AbstractFirBasedSymbol<*>?
get() = calleeReference.let {
when (it) {
is FirExplicitThisReference -> it.boundSymbol
is FirResolvedNamedReference -> it.resolvedSymbol
is FirNamedReferenceWithCandidate -> it.candidateSymbol
else -> null
}
}
private fun FirFunctionCall.isBooleanNot(): Boolean {
val symbol = calleeReference.safeAs<FirResolvedNamedReference>()?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false
return symbol.callableId == KOTLIN_BOOLEAN_NOT
}
fun exitConstExpresion(constExpression: FirConstExpression<*>) {
if (constExpression.resultType is FirResolvedTypeRef && !contractDescriptionVisitingMode) return
graphBuilder.exitConstExpresion(constExpression).mergeIncomingFlow()
}
fun exitVariableDeclaration(variable: FirProperty) {
val node = graphBuilder.exitVariableDeclaration(variable).mergeIncomingFlow()
val initializer = variable.initializer ?: return
/*
* That part is needed for cases like that:
*
* val b = x is String
* ...
* if (b) {
* x.length
* }
*/
variableStorage[initializer]?.takeIf { it.isSynthetic() }?.let { initializerVariable ->
val realVariable = getOrCreateRealVariable(variable)
requireNotNull(realVariable)
logicSystem.changeVariableForConditionFlow(node.flow, initializerVariable, realVariable)
}
getOrCreateRealVariable(initializer)?.let { rhsVariable ->
variableStorage.createAliasVariable(variable.symbol, rhsVariable)
}
}
fun exitVariableAssignment(assignment: FirVariableAssignment) {
val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
val lhsSymbol: AbstractFirBasedSymbol<*> = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol ?: return
val lhsVariable = getOrCreateRealVariable(lhsSymbol.fir) ?: return
val rhsSymbol = assignment.rValue.resolvedSymbol
val rhsVariable = rhsSymbol?.let { variableStorage[it]?.takeIf { !it.isSynthetic() } }
if (rhsVariable == null) {
val type = assignment.rValue.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
logicSystem.addApprovedInfo(
node.flow,
lhsVariable,
FirDataFlowInfo(setOf(type), emptySet())
)
return
} else {
variableStorage.rebindAliasVariable(lhsVariable, rhsVariable)
}
}
fun exitThrowExceptionNode(throwExpression: FirThrowExpression) {
graphBuilder.exitThrowExceptionNode(throwExpression).mergeIncomingFlow()
}
// ----------------------------------- Boolean operators -----------------------------------
fun enterBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression) {
graphBuilder.enterBinaryAnd(binaryLogicExpression).mergeIncomingFlow()
}
fun exitLeftBinaryAndArgument(binaryLogicExpression: FirBinaryLogicExpression) {
val (leftNode, rightNode) = graphBuilder.exitLeftBinaryAndArgument(binaryLogicExpression)
exitLeftArgumentOfBinaryBooleanOperator(leftNode, rightNode, isAnd = true)
}
fun exitBinaryAnd(binaryLogicExpression: FirBinaryLogicExpression) {
val node = graphBuilder.exitBinaryAnd(binaryLogicExpression)
exitBinaryBooleanOperator(binaryLogicExpression, node, isAnd = true)
}
fun enterBinaryOr(binaryLogicExpression: FirBinaryLogicExpression) {
graphBuilder.enterBinaryOr(binaryLogicExpression).mergeIncomingFlow()
}
fun exitLeftBinaryOrArgument(binaryLogicExpression: FirBinaryLogicExpression) {
val (leftNode, rightNode) = graphBuilder.exitLeftBinaryOrArgument(binaryLogicExpression)
exitLeftArgumentOfBinaryBooleanOperator(leftNode, rightNode, isAnd = false)
}
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression) {
val node = graphBuilder.exitBinaryOr(binaryLogicExpression)
exitBinaryBooleanOperator(binaryLogicExpression, node, isAnd = false)
}
private fun exitLeftArgumentOfBinaryBooleanOperator(leftNode: CFGNode<*>, rightNode: CFGNode<*>, isAnd: Boolean) {
val parentFlow = leftNode.alivePreviousNodes.first().flow
leftNode.flow = logicSystem.forkFlow(parentFlow)
val leftOperandVariable = getOrCreateVariable(leftNode.previousNodes.first().fir)
rightNode.flow = logicSystem.approveFactsInsideFlow(
leftOperandVariable,
if (isAnd) EqTrue else EqFalse,
parentFlow,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
}
private fun exitBinaryBooleanOperator(
binaryLogicExpression: FirBinaryLogicExpression,
node: AbstractBinaryExitNode<*>,
isAnd: Boolean
) {
val bothEvaluated = if (isAnd) EqTrue else EqFalse
val onlyLeftEvaluated = bothEvaluated.invert()
// Naming for all variables was chosen in assumption that we processing && expression
val flowFromLeft = node.leftOperandNode.flow
val flowFromRight = node.rightOperandNode.flow
val flow = node.mergeIncomingFlow().flow
val (leftVariable, rightVariable) = binaryLogicExpression.getVariables()
val andVariable = getOrCreateVariable(binaryLogicExpression)
val (conditionalFromLeft, conditionalFromRight, approvedFromRight) = logicSystem.collectInfoForBooleanOperator(
flowFromLeft,
leftVariable,
flowFromRight,
rightVariable
)
// left && right == True
// left || right == False
val approvedIfTrue: MutableApprovedInfos = mutableMapOf()
logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromLeft)
logicSystem.approveFactTo(approvedIfTrue, bothEvaluated, conditionalFromRight)
approvedFromRight.forEach { (variable, info) ->
approvedIfTrue.addInfo(variable, info)
}
approvedIfTrue.forEach { (variable, info) ->
logicSystem.addConditionalInfo(flow, andVariable, info.toConditional(bothEvaluated, variable))
}
// left && right == False
// left || right == True
val approvedIfFalse: MutableApprovedInfos = mutableMapOf()
val leftIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromLeft)
val rightIsFalse = logicSystem.approveFact(onlyLeftEvaluated, conditionalFromRight)
approvedIfFalse.mergeInfo(logicSystem.orForVerifiedFacts(leftIsFalse, rightIsFalse))
approvedIfFalse.forEach { (variable, info) ->
logicSystem.addConditionalInfo(flow, andVariable, info.toConditional(onlyLeftEvaluated, variable))
}
node.flow = flow
variableStorage.removeVariableIfSynthetic(leftVariable)
variableStorage.removeVariableIfSynthetic(rightVariable)
}
private fun exitBooleanNot(functionCall: FirFunctionCall, node: FunctionCallNode) {
val booleanExpressionVariable = getOrCreateVariable(node.previousNodes.first().fir)
val variable = getOrCreateVariable(functionCall)
logicSystem.changeVariableForConditionFlow(node.flow, booleanExpressionVariable, variable) { it.invert() }
}
// ----------------------------------- Annotations -----------------------------------
fun enterAnnotationCall(annotationCall: FirAnnotationCall) {
graphBuilder.enterAnnotationCall(annotationCall).mergeIncomingFlow()
}
fun exitAnnotationCall(annotationCall: FirAnnotationCall) {
graphBuilder.exitAnnotationCall(annotationCall).mergeIncomingFlow()
}
// ----------------------------------- Init block -----------------------------------
fun enterInitBlock(initBlock: FirAnonymousInitializer) {
graphBuilder.enterInitBlock(initBlock).mergeIncomingFlow()
}
fun exitInitBlock(initBlock: FirAnonymousInitializer) {
graphBuilder.exitInitBlock(initBlock).mergeIncomingFlow()
}
// -------------------------------------------------------------------------------------------------------------------------
private fun FirBinaryLogicExpression.getVariables(): Pair<DataFlowVariable, DataFlowVariable> =
getOrCreateVariable(leftOperand) to getOrCreateVariable(rightOperand)
private var CFGNode<*>.flow: Flow
get() = flowOnNodes.getValue(this.origin)
set(value) {
flowOnNodes[this.origin] = value
}
private val CFGNode<*>.origin: CFGNode<*> get() = if (this is StubNode) previousNodes.first() else this
private fun <T : CFGNode<*>> T.mergeIncomingFlow(): T = this.also { node ->
val previousFlows = node.alivePreviousNodes.map { it.flow }
node.flow = logicSystem.joinFlow(previousFlows)
}
// -------------------------------- get or create variable --------------------------------
private fun getOrCreateSyntheticVariable(fir: FirElement): SyntheticDataFlowVariable =
variableStorage.getOrCreateNewSyntheticVariable(fir)
private fun getOrCreateRealVariable(fir: FirElement): RealDataFlowVariable? {
@Suppress("NAME_SHADOWING")
val fir = fir.unwrapWhenSubjectExpression().unwrapSmartcast()
val symbol = fir.resolvedSymbol ?: return null
return when {
fir is FirThisReceiverExpressionImpl -> variableStorage.getOrCreateNewThisRealVariable(symbol)
symbol is FirVariableSymbol<*> ->
// TODO: Fix this for non-local properties.
variableStorage.getOrCreateNewRealVariable(symbol).variableUnderAlias
else -> null
}
}
private fun getOrCreateVariable(fir: FirElement): DataFlowVariable {
return getOrCreateRealVariable(fir) ?: getOrCreateSyntheticVariable(fir)
}
// -------------------------------- get variable --------------------------------
private val FirElement.realVariable: RealDataFlowVariable?
get() {
val symbol: AbstractFirBasedSymbol<*> = if (this is FirThisReceiverExpression) {
calleeReference.boundSymbol
} else {
resolvedSymbol
} ?: return null
return variableStorage[symbol]
}
// TODO: Fix this -- see broken test_3 in compiler/fir/resolve/testData/resolve/smartcasts/notBoundSmartcasts.kt
// If we have multiple local variables (could be value parameter) of the same type, there should be separate DataFlowVariables for
// accesses to a property for each distinct local variable, i.e., DataFlowVariables in storage for properties should be keyed by
// the "chain" of variable/property accesses. We also need to check that the property is not mutable and has no custom getter.
private fun getRealVariablesForSafeCallChain(call: FirExpression): Collection<RealDataFlowVariable> {
val result = mutableListOf<RealDataFlowVariable>()
fun collect(call: FirExpression) {
when (call) {
is FirQualifiedAccess -> {
if (call.safe) {
val explicitReceiver = call.explicitReceiver
require(explicitReceiver != null)
collect(explicitReceiver)
}
result.addIfNotNull(getOrCreateRealVariable(call))
}
is FirWhenSubjectExpression -> {
// TODO: check
call.whenSubject.whenExpression.subjectVariable?.let { result += getOrCreateRealVariable(it)!! }
call.whenSubject.whenExpression.subject?.let { collect(it) }
}
}
}
collect(call)
return result
}
private inner class LogicSystemImpl(context: ConeInferenceContext) : DelegatingLogicSystem(context) {
override fun processUpdatedReceiverVariable(flow: Flow, variable: RealDataFlowVariable) {
val symbol = (variable.fir as? FirSymbolOwner<*>)?.symbol ?: return
val index = receiverStack.getReceiverIndex(symbol) ?: return
val info = flow.getApprovedInfo(variable)
if (info == null) {
receiverStack.replaceReceiverType(index, receiverStack.getOriginalType(index))
} else {
val types = info.exactType.toMutableList().also {
it += receiverStack.getOriginalType(index)
}
receiverStack.replaceReceiverType(index, context.intersectTypesOrNull(types)!!)
}
}
override fun updateAllReceivers(flow: Flow) {
receiverStack.mapNotNull { variableStorage[it.boundSymbol] }.forEach { processUpdatedReceiverVariable(flow, it) }
}
}
}
@@ -1,89 +0,0 @@
/*
* Copyright 2010-2019 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 com.google.common.collect.Multimap
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.render
data class ConditionalFirDataFlowInfo(
val condition: Condition,
val variable: RealDataFlowVariable,
val info: FirDataFlowInfo
) {
fun invert(): ConditionalFirDataFlowInfo {
return ConditionalFirDataFlowInfo(condition.invert(), variable, info)
}
override fun toString(): String {
return "$condition -> $variable: ${info.exactType.render()}, ${info.exactNotType.render()}"
}
private fun Set<ConeKotlinType>.render(): String = joinToString { it.render() }
}
typealias ApprovedInfos = Map<RealDataFlowVariable, FirDataFlowInfo>
typealias MutableApprovedInfos = MutableMap<RealDataFlowVariable, MutableFirDataFlowInfo>
typealias ConditionalInfos = Multimap<DataFlowVariable, ConditionalFirDataFlowInfo>
interface FirDataFlowInfo {
companion object {
// TODO: temporary
operator fun invoke(exactType: Set<ConeKotlinType>, exactNotType: Set<ConeKotlinType>): FirDataFlowInfo =
MutableFirDataFlowInfo(exactType.toMutableSet(), exactNotType.toMutableSet())
}
val exactType: Set<ConeKotlinType>
val exactNotType: Set<ConeKotlinType>
operator fun plus(other: FirDataFlowInfo): FirDataFlowInfo
operator fun minus(other: FirDataFlowInfo): FirDataFlowInfo
val isNotEmpty: Boolean
val isEmpty: Boolean get() = !isNotEmpty
fun invert(): FirDataFlowInfo
}
data class MutableFirDataFlowInfo(
override val exactType: MutableSet<ConeKotlinType> = mutableSetOf(),
override val exactNotType: MutableSet<ConeKotlinType> = mutableSetOf()
) : FirDataFlowInfo {
override operator fun plus(other: FirDataFlowInfo): MutableFirDataFlowInfo = MutableFirDataFlowInfo(
HashSet(exactType).apply { addAll(other.exactType) },
HashSet(exactNotType).apply { addAll(other.exactNotType) }
)
override operator fun minus(other: FirDataFlowInfo): MutableFirDataFlowInfo = MutableFirDataFlowInfo(
HashSet(exactType).apply { removeAll(other.exactType) },
HashSet(exactNotType).apply { removeAll(other.exactNotType) }
)
override val isNotEmpty: Boolean get() = exactType.isNotEmpty() || exactNotType.isNotEmpty()
override fun invert(): FirDataFlowInfo = MutableFirDataFlowInfo(exactNotType, exactType)
operator fun plusAssign(info: FirDataFlowInfo) {
exactType += info.exactType
exactNotType += info.exactNotType
}
fun copy(): MutableFirDataFlowInfo = MutableFirDataFlowInfo(exactType.toMutableSet(), exactNotType.toMutableSet())
}
operator fun FirDataFlowInfo.plus(other: FirDataFlowInfo?): FirDataFlowInfo = other?.let { this + other } ?: this
fun FirDataFlowInfo.toConditional(condition: Condition, variable: RealDataFlowVariable): ConditionalFirDataFlowInfo =
ConditionalFirDataFlowInfo(condition, variable, this)
fun MutableApprovedInfos.addInfo(variable: RealDataFlowVariable, info: FirDataFlowInfo) {
merge(variable, info as MutableFirDataFlowInfo) { existingInfo, newInfo ->
existingInfo.apply { this += newInfo }
}
}
fun MutableApprovedInfos.mergeInfo(other: Map<RealDataFlowVariable, FirDataFlowInfo>) {
other.forEach { (variable, info) ->
addInfo(variable, info)
}
}
@@ -1,197 +0,0 @@
/*
* Copyright 2010-2019 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 com.google.common.collect.ArrayListMultimap
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
interface Flow {
fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo?
fun getConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo>
fun getVariablesInApprovedInfos(): Collection<RealDataFlowVariable>
fun removeConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo>
}
abstract class LogicSystem(private val context: ConeInferenceContext) {
// ------------------------------- Flow operations -------------------------------
abstract fun createEmptyFlow(): Flow
abstract fun forkFlow(flow: Flow): Flow
abstract fun joinFlow(flows: Collection<Flow>): Flow
open fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) {
assert(info is MutableFirDataFlowInfo)
flow.approvedInfos.addInfo(variable, info)
if (variable.isThisReference) {
processUpdatedReceiverVariable(flow, variable)
}
}
open fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) {
flow.conditionalInfos.put(variable, info)
}
/*
* used for:
* 1. val b = x is String
* 2. b = x is String
* 3. !b | b.not() for Booleans
*/
open fun changeVariableForConditionFlow(
flow: Flow,
sourceVariable: DataFlowVariable,
newVariable: DataFlowVariable,
transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo?)? = null
) {
var infos = flow.getConditionalInfos(sourceVariable)
if (transform != null) {
infos = infos.mapNotNull(transform)
}
flow.conditionalInfos.putAll(newVariable, infos)
if (sourceVariable.isSynthetic()) {
flow.conditionalInfos.removeAll(sourceVariable)
}
}
open fun approveFactsInsideFlow(
variable: DataFlowVariable,
condition: Condition,
flow: Flow,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): Flow {
val notApprovedFacts: Collection<ConditionalFirDataFlowInfo> = if (shouldRemoveSynthetics && variable.isSynthetic()) {
flow.removeConditionalInfos(variable)
} else {
flow.getConditionalInfos(variable)
}
val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow
if (notApprovedFacts.isEmpty()) {
return resultFlow
}
val newFacts = ArrayListMultimap.create<RealDataFlowVariable, FirDataFlowInfo>()
notApprovedFacts.forEach {
if (it.condition == condition) {
newFacts.put(it.variable, it.info)
}
}
val updatedReceivers = mutableSetOf<RealDataFlowVariable>()
newFacts.asMap().forEach { (variable, infos) ->
@Suppress("NAME_SHADOWING")
val info = MutableFirDataFlowInfo()
infos.forEach {
info += it
}
if (variable.isThisReference) {
updatedReceivers += variable
}
addApprovedInfo(resultFlow, variable, info)
}
updatedReceivers.forEach {
processUpdatedReceiverVariable(resultFlow, it)
}
return resultFlow
}
// ------------------------------- Callbacks for updating implicit receiver stack -------------------------------
abstract fun processUpdatedReceiverVariable(flow: Flow, variable: RealDataFlowVariable)
abstract fun updateAllReceivers(flow: Flow)
// ------------------------------- Accessors to flow implementation -------------------------------
protected abstract val Flow.approvedInfos: MutableApprovedInfos
protected abstract val Flow.conditionalInfos: ConditionalInfos
// ------------------------------- Public DataFlowInfo util functions -------------------------------
data class InfoForBooleanOperator(
val conditionalFromLeft: Collection<ConditionalFirDataFlowInfo>,
val conditionalFromRight: Collection<ConditionalFirDataFlowInfo>,
val approvedFromRight: ApprovedInfos
)
abstract fun collectInfoForBooleanOperator(
leftFlow: Flow,
leftVariable: DataFlowVariable,
rightFlow: Flow,
rightVariable: DataFlowVariable
): InfoForBooleanOperator
fun orForVerifiedFacts(
left: ApprovedInfos,
right: ApprovedInfos
): MutableApprovedInfos {
if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf()
val map = mutableMapOf<RealDataFlowVariable, MutableFirDataFlowInfo>()
for (variable in left.keys.intersect(right.keys)) {
val leftInfo = left.getValue(variable)
val rightInfo = right.getValue(variable)
map[variable] = or(listOf(leftInfo, rightInfo))
}
return map
}
fun approveFactTo(destination: MutableApprovedInfos, variable: DataFlowVariable, condition: Condition, flow: Flow) {
val notApprovedFacts: Collection<ConditionalFirDataFlowInfo> = flow.getConditionalInfos(variable)
approveFactTo(destination, condition, notApprovedFacts)
}
fun approveFactTo(destination: MutableApprovedInfos, condition: Condition, notApprovedFacts: Collection<ConditionalFirDataFlowInfo>) {
if (notApprovedFacts.isEmpty()) return
notApprovedFacts.forEach {
if (it.condition == condition) {
destination.addInfo(it.variable, it.info)
}
}
}
fun approveFact(condition: Condition, notApprovedFacts: Collection<ConditionalFirDataFlowInfo>): MutableApprovedInfos {
return mutableMapOf<RealDataFlowVariable, MutableFirDataFlowInfo>().apply { approveFactTo(this, condition, notApprovedFacts) }
}
fun approveFact(variable: DataFlowVariable, condition: Condition, flow: Flow): MutableApprovedInfos {
return mutableMapOf<RealDataFlowVariable, MutableFirDataFlowInfo>().apply { approveFactTo(this, variable, condition, flow) }
}
// ------------------------------- Util functions -------------------------------
// TODO
protected fun <E> Collection<Collection<E>>.intersectSets(): Set<E> {
if (isEmpty()) return emptySet()
val iterator = iterator()
val result = HashSet<E>(iterator.next())
while (iterator.hasNext()) {
result.retainAll(iterator.next())
}
return result
}
protected fun or(infos: Collection<FirDataFlowInfo>): MutableFirDataFlowInfo {
infos.singleOrNull()?.let { return it as MutableFirDataFlowInfo }
val exactType = orTypes(infos.map { it.exactType })
val exactNotType = orTypes(infos.map { it.exactNotType })
return MutableFirDataFlowInfo(exactType, exactNotType)
}
private fun orTypes(types: Collection<Set<ConeKotlinType>>): MutableSet<ConeKotlinType> {
if (types.any { it.isEmpty() }) return mutableSetOf()
val allTypes = types.flatMapTo(mutableSetOf()) { it }
val commonTypes = allTypes.toMutableSet()
types.forEach { commonTypes.retainAll(it) }
val differentTypes = allTypes - commonTypes
context.commonSuperTypeOrNull(differentTypes.toList())?.let { commonTypes += it }
return commonTypes
}
}
@@ -1,266 +0,0 @@
///*
// * Copyright 2010-2019 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 kotlinx.collections.immutable.*
//import kotlinx.collections.immutable.PersistentMap
//import kotlinx.collections.immutable.PersistentSet
//import org.jetbrains.kotlin.fir.types.ConeKotlinType
//
//
//private data class PersistentFirDataFlowInfo(
// override val exactType: PersistentSet<ConeKotlinType>,
// override val exactNotType: PersistentSet<ConeKotlinType>
//) : FirDataFlowInfo {
//
// override operator fun plus(other: FirDataFlowInfo): PersistentFirDataFlowInfo {
// return PersistentFirDataFlowInfo(
// exactType + other.exactType,
// exactNotType + other.exactNotType
// )
// }
//
// override fun minus(other: FirDataFlowInfo): FirDataFlowInfo {
// // TODO
// throw IllegalStateException()
// }
//
// override val isNotEmpty: Boolean
// get() = exactType.isNotEmpty() || exactNotType.isNotEmpty()
//
// override fun invert(): PersistentFirDataFlowInfo {
// return PersistentFirDataFlowInfo(exactNotType, exactType)
// }
//
//// override fun toMutableInfo(): MutableFirDataFlowInfo {
//// return MutableFirDataFlowInfo(exactType.toMutableSet(), exactNotType.toMutableSet())
//// }
//}
//
//private typealias PersistentApprovedInfos = PersistentMap<RealDataFlowVariable, PersistentFirDataFlowInfo>
//private typealias PersistentConditionalInfos = PersistentMap<DataFlowVariable, PersistentList<ConditionalFirDataFlowInfo>>
//
//private fun FirDataFlowInfo.toPersistent(): PersistentFirDataFlowInfo = PersistentFirDataFlowInfo(
// exactType.toPersistentSet(),
// exactNotType.toPersistentSet()
//)
//
//private fun PersistentApprovedInfos.addNewInfo(variable: RealDataFlowVariable, info: FirDataFlowInfo): PersistentApprovedInfos {
// val existingInfo = this[variable]
// return if (existingInfo == null) {
// val persistentInfo = if (info is PersistentFirDataFlowInfo) info else info.toPersistent()
// put(variable, persistentInfo)
// } else {
// put(variable, existingInfo + info)
// }
//}
//
//private class PersistentFlow : Flow {
// val previousFlow: PersistentFlow?
// var approvedInfos: PersistentApprovedInfos
// var conditionalInfos: PersistentConditionalInfos
//
// constructor(previousFlow: PersistentFlow) {
// this.previousFlow = previousFlow
// approvedInfos = previousFlow.approvedInfos
// conditionalInfos = previousFlow.conditionalInfos
// level = previousFlow.level + 1
// }
//
// constructor() {
// previousFlow = null
// approvedInfos = persistentHashMapOf()
// conditionalInfos = persistentHashMapOf()
// level = 1
// }
//
// val level: Int
// var approvedInfosDiff: PersistentApprovedInfos = persistentHashMapOf()
//
// override fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo? {
// return approvedInfos[variable]
// }
//
// override fun getConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
// return conditionalInfos[variable] ?: emptyList()
// }
//
// override fun getVariablesInApprovedInfos(): Collection<RealDataFlowVariable> {
// return approvedInfos.keys
// }
//
// override fun removeConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
// val result = getConditionalInfos(variable)
// if (result.isNotEmpty()) {
// conditionalInfos = conditionalInfos.remove(variable)
// }
// return result
// }
//}
//
//abstract class PersistentLogicSystem(context: DataFlowInferenceContext) : LogicSystem(context) {
// override fun createEmptyFlow(): Flow {
// return PersistentFlow()
// }
//
// override fun forkFlow(flow: Flow): Flow {
// require(flow is PersistentFlow)
// return PersistentFlow(flow)
// }
//
// override fun joinFlow(flows: Collection<Flow>): Flow {
// if (flows.isEmpty()) return createEmptyFlow()
// flows.singleOrNull()?.let { return it }
//
// @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
// val flows = flows as Collection<PersistentFlow>
// val commonFlow = flows.reduce(this::lowestCommonFlow)
//
// val commonVariables = flows.map { it.diffVariablesIterable(commonFlow).toList() }
// .intersectSets()
// .takeIf { it.isNotEmpty() }
// ?: return commonFlow
//
// for (variable in commonVariables) {
// val info = or(flows.map { it.getApprovedDiff(variable, commonFlow) })
// if (info.isEmpty) continue
// commonFlow.approvedInfos = commonFlow.approvedInfos.addNewInfo(variable, info)
// commonFlow.approvedInfosDiff = commonFlow.approvedInfosDiff.addNewInfo(variable, info)
// }
//
// updateAllReceivers(commonFlow)
//
// return commonFlow
// }
//
// private fun PersistentFlow.diffVariablesIterable(parentFlow: PersistentFlow): Iterable<RealDataFlowVariable> =
// object : DiffIterable<RealDataFlowVariable>(parentFlow, this) {
// override fun extractIterator(flow: PersistentFlow): Iterator<RealDataFlowVariable> {
// return flow.approvedInfosDiff.keys.iterator()
// }
// }
//
// private abstract class DiffIterable<T>(private val parentFlow: PersistentFlow, private var currentFlow: PersistentFlow) : Iterable<T> {
// private var currentIterator = extractIterator(currentFlow)
//
// abstract fun extractIterator(flow: PersistentFlow): Iterator<T>
//
// override fun iterator(): Iterator<T> {
// return object : Iterator<T> {
// override fun hasNext(): Boolean {
// if (currentIterator.hasNext()) return true
// while (currentFlow != parentFlow) {
// currentFlow = currentFlow.previousFlow!!
// currentIterator = extractIterator(currentFlow)
// if (currentIterator.hasNext()) return true
// }
// return false
// }
//
// override fun next(): T {
// if (!hasNext()) {
// throw NoSuchElementException()
// }
// return currentIterator.next()
// }
// }
// }
// }
//
// private fun PersistentFlow.getApprovedDiff(variable: RealDataFlowVariable, parentFlow: PersistentFlow): MutableFirDataFlowInfo {
// var flow = this
// val result = MutableFirDataFlowInfo()
// while (flow != parentFlow) {
// flow.approvedInfosDiff[variable]?.let {
// result += it
// }
// flow = flow.previousFlow!!
// }
// return result
// }
//
// override fun collectInfoForBooleanOperator(
// leftFlow: Flow,
// leftVariable: DataFlowVariable,
// rightFlow: Flow,
// rightVariable: DataFlowVariable
// ): InfoForBooleanOperator {
// require(leftFlow is PersistentFlow && rightFlow is PersistentFlow)
// return InfoForBooleanOperator(
// leftFlow.conditionalInfos[leftVariable] ?: emptyList(),
// rightFlow.conditionalInfos[rightVariable] ?: emptyList(),
// rightFlow.approvedInfosDiff
// )
// }
//
// override fun changeVariableForConditionFlow(
// flow: Flow,
// sourceVariable: DataFlowVariable,
// newVariable: DataFlowVariable,
// transform: ((ConditionalFirDataFlowInfo) -> ConditionalFirDataFlowInfo?)?
// ) {
// require(flow is PersistentFlow)
// with(flow) {
// val existingInfo = conditionalInfos[sourceVariable]?.takeIf { it.isNotEmpty() } ?: return
// val transformedInfo = if (transform == null) {
// existingInfo
// } else {
// existingInfo.map(transform).toPersistentList()
// }
// conditionalInfos = conditionalInfos.remove(sourceVariable).put(newVariable, transformedInfo)
// }
// }
//
// override fun addApprovedInfo(flow: Flow, variable: RealDataFlowVariable, info: FirDataFlowInfo) {
// require(flow is PersistentFlow)
// with(flow) {
// approvedInfos = approvedInfos.addNewInfo(variable, info)
// if (previousFlow != null) {
// approvedInfosDiff = approvedInfosDiff.addNewInfo(variable, info)
// }
// if (variable.isThisReference) {
// processUpdatedReceiverVariable(flow, variable)
// }
// }
// }
//
// override fun addConditionalInfo(flow: Flow, variable: DataFlowVariable, info: ConditionalFirDataFlowInfo) {
// require(flow is PersistentFlow)
// with(flow) {
// val existingInfo = conditionalInfos[variable]
// conditionalInfos = if (existingInfo == null) {
// conditionalInfos.put(variable, persistentListOf(info))
// } else {
// conditionalInfos.put(variable, existingInfo + info)
// }
// }
// }
//
// override val Flow.approvedInfos: MutableApprovedInfos
// get() = throw IllegalStateException()
//
// override val Flow.conditionalInfos: ConditionalInfos
// get() = throw IllegalStateException()
//
// private fun lowestCommonFlow(left: PersistentFlow, right: PersistentFlow): PersistentFlow {
// val level = minOf(left.level, right.level)
// @Suppress("NAME_SHADOWING")
// var left = left
// while (left.level > level) {
// left = left.previousFlow!!
// }
// @Suppress("NAME_SHADOWING")
// var right = right
// while (right.level > level) {
// right = right.previousFlow!!
// }
// while (left != right) {
// left = left.previousFlow!!
// right = right.previousFlow!!
// }
// return left
// }
//}
@@ -1,106 +0,0 @@
/*
* Copyright 2010-2019 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 com.google.common.collect.ArrayListMultimap
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
private class SimpleFlow(
val approvedInfos: MutableApprovedInfos = mutableMapOf(),
val conditionalInfos: ConditionalInfos = HashMultimap.create()
) : Flow {
override fun getApprovedInfo(variable: RealDataFlowVariable): FirDataFlowInfo? {
return approvedInfos[variable]
}
override fun getConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
return conditionalInfos[variable]
}
override fun getVariablesInApprovedInfos(): Collection<RealDataFlowVariable> {
return approvedInfos.keys
}
override fun removeConditionalInfos(variable: DataFlowVariable): Collection<ConditionalFirDataFlowInfo> {
return conditionalInfos.removeAll(variable)
}
}
abstract class SimpleLogicSystem(context: ConeInferenceContext) : LogicSystem(context) {
override val Flow.approvedInfos: MutableApprovedInfos
get() = (this as SimpleFlow).approvedInfos
override val Flow.conditionalInfos: ConditionalInfos
get() = (this as SimpleFlow).conditionalInfos
override fun createEmptyFlow(): Flow {
return SimpleFlow()
}
override fun forkFlow(flow: Flow): Flow {
require(flow is SimpleFlow)
return SimpleFlow(
flow.approvedInfos.mapValuesTo(mutableMapOf()) { (_, info) -> info.copy() },
flow.conditionalInfos.copy()
)
}
override fun joinFlow(flows: Collection<Flow>): Flow {
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
val flows = flows as Collection<SimpleFlow>
flows.singleOrNull()?.let { return it }
val approvedFacts: MutableApprovedInfos = mutableMapOf()
flows.map { it.approvedInfos.keys }
.intersectSets()
.forEach { variable ->
val infos = flows.map { it.approvedInfos[variable]!! }
if (infos.isNotEmpty()) {
approvedFacts[variable] = or(infos)
}
}
val notApprovedFacts: ConditionalInfos = ArrayListMultimap.create()
flows.map { it.conditionalInfos.keySet() }
.intersectSets()
.forEach { variable ->
val infos = flows.map { it.conditionalInfos[variable] }.intersectSets()
if (infos.isNotEmpty()) {
notApprovedFacts.putAll(variable, infos)
}
}
val result = SimpleFlow(approvedFacts, notApprovedFacts)
updateAllReceivers(result)
return result
}
override fun collectInfoForBooleanOperator(
leftFlow: Flow,
leftVariable: DataFlowVariable,
rightFlow: Flow,
rightVariable: DataFlowVariable
): InfoForBooleanOperator {
require(leftFlow is SimpleFlow && rightFlow is SimpleFlow)
return InfoForBooleanOperator(
leftFlow.conditionalInfos[leftVariable],
rightFlow.conditionalInfos[rightVariable],
rightFlow.approvedInfos - leftFlow.approvedInfos
)
}
private operator fun ApprovedInfos.minus(other: ApprovedInfos): ApprovedInfos {
val result: MutableApprovedInfos = mutableMapOf()
forEach { (variable, info) ->
require(info is MutableFirDataFlowInfo)
result[variable] = other[variable]?.let { info - it } ?: info
}
return result
}
}
private fun <K, V> Multimap<K, V>.copy(): Multimap<K, V> = ArrayListMultimap.create(this)