[FIR] Implement new data flow analysis

This commit is contained in:
Dmitriy Novozhilov
2019-12-12 16:11:21 +03:00
parent 2c66c3b0b6
commit 5d3b75ebc3
31 changed files with 3062 additions and 532 deletions
+2
View File
@@ -46,6 +46,8 @@ messages/**)
-dontwarn javaslang.match.annotation.Unapply
-dontwarn javaslang.match.annotation.Patterns
-dontwarn javaslang.*
-dontwarn kotlinx.collections.immutable.*
-dontwarn kotlinx.collections.immutable.**
-dontwarn com.google.errorprone.**
-dontwarn com.google.j2objc.**
-dontwarn javax.crypto.**
+2
View File
@@ -43,6 +43,8 @@ messages/**)
-dontwarn javaslang.match.annotation.Unapply
-dontwarn javaslang.match.annotation.Patterns
-dontwarn javaslang.*
-dontwarn kotlinx.collections.immutable.*
-dontwarn kotlinx.collections.immutable.**
-dontwarn com.google.errorprone.**
-dontwarn com.google.j2objc.**
-dontwarn javax.crypto.**
+5
View File
@@ -3,12 +3,17 @@ plugins {
id("jps-compatible")
}
repositories {
maven(url = "https://dl.bintray.com/kotlin/kotlinx")
}
dependencies {
compile(project(":core:descriptors"))
compile(project(":core:descriptors.jvm"))
compile(project(":core:deserialization"))
compile(project(":compiler:fir:cones"))
compile(project(":compiler:fir:tree"))
compile("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.2")
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core", "guava", rootProject = rootProject) }
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.calls.InferenceComponents
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionStageRunner
import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.dfa.new.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.transformers.*
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef
@@ -36,7 +36,7 @@ interface BodyResolveComponents : SessionHolder {
val callResolver: FirCallResolver
val doubleColonExpressionResolver: FirDoubleColonExpressionResolver
val syntheticCallGenerator: FirSyntheticCallGenerator
val dataFlowAnalyzer: FirDataFlowAnalyzer
val dataFlowAnalyzer: FirDataFlowAnalyzer<*>
val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer
val integerOperatorsTypeUpdater: IntegerOperatorsTypeUpdater
@@ -0,0 +1,266 @@
///*
// * 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
// }
//}
@@ -0,0 +1,813 @@
/*
* 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.new
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.references.FirResolvedNamedReference
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.dfa.*
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.types.*
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.IllegalArgumentException
abstract class FirDataFlowAnalyzer<FLOW : Flow>(
protected val components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents
) {
companion object {
internal val KOTLIN_BOOLEAN_NOT = CallableId(FqName("kotlin"), FqName("Boolean"), Name.identifier("not"))
fun createFirDataFlowAnalyzer(
components: FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents
): FirDataFlowAnalyzer<*> = object : FirDataFlowAnalyzer<PersistentFlow>(components) {
private val receiverStack: ImplicitReceiverStackImpl = components.implicitReceiverStack as ImplicitReceiverStackImpl
override val logicSystem: PersistentLogicSystem = object : PersistentLogicSystem(any, components.inferenceComponents.ctx) {
override fun processUpdatedReceiverVariable(flow: PersistentFlow, variable: RealVariable) {
val symbol = variable.identifier.symbol
val index = receiverStack.getReceiverIndex(symbol) ?: return
val info = flow.getKnownInfo(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: PersistentFlow) {
receiverStack.mapNotNull { variableStorage[it.boundSymbol, it.receiverExpression] }.forEach { processUpdatedReceiverVariable(flow, it) }
}
}
}
}
protected abstract val logicSystem: LogicSystem<FLOW>
private val context: ConeInferenceContext = components.inferenceComponents.ctx
private val graphBuilder = ControlFlowGraphBuilder()
protected val variableStorage = VariableStorage()
private val flowOnNodes = mutableMapOf<CFGNode<*>, FLOW>()
private val variablesForWhenConditions = mutableMapOf<WhenBranchConditionExitNode, DataFlowVariable>()
private var contractDescriptionVisitingMode = false
protected val any = components.session.builtinTypes.anyType.coneTypeUnsafe<ConeKotlinType>()
private val nullableNothing = components.session.builtinTypes.nullableNothingType.coneTypeUnsafe<ConeKotlinType>()
// ----------------------------------- Requests -----------------------------------
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 symbol: AbstractFirBasedSymbol<*> = qualifiedAccessExpression.symbol ?: return null
val variable = variableStorage[symbol, qualifiedAccessExpression] ?: return null
return graphBuilder.lastNode.flow.getKnownInfo(variable)?.exactType
}
fun returnExpressionsOfAnonymousFunction(function: FirAnonymousFunction): List<FirStatement> {
return graphBuilder.returnExpressionsOfAnonymousFunction(function)
}
// ----------------------------------- Named function -----------------------------------
fun enterFunction(function: FirFunction<*>) {
val (functionEnterNode, previousNode) = graphBuilder.enterFunction(function)
if (previousNode == null) {
functionEnterNode.mergeIncomingFlow()
} else {
// Enter anonymous function
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()
}
if (!graphBuilder.isTopLevel()) {
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()
}
// ----------------------------------- Operator call -----------------------------------
fun exitTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall) {
val node = graphBuilder.exitTypeOperatorCall(typeOperatorCall).mergeIncomingFlow()
if (typeOperatorCall.operation !in FirOperation.TYPES) return
val type = typeOperatorCall.conversionTypeRef.coneTypeUnsafe<ConeKotlinType>()
val operandVariable = variableStorage.getOrCreateVariable(typeOperatorCall.argument)
val flow = node.flow
when (val operation = typeOperatorCall.operation) {
FirOperation.IS, FirOperation.NOT_IS -> {
val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall)
val isNotNullCheck = operation == FirOperation.IS && type.nullability == ConeNullability.NOT_NULL
if (operandVariable.isReal()) {
val hasTypeInfo = operandVariable has type
val hasNotTypeInfo = operandVariable hasNot type
fun chooseInfo(trueBranch: Boolean) =
if ((typeOperatorCall.operation == FirOperation.IS) == trueBranch) hasTypeInfo else hasNotTypeInfo
flow.addLogicStatement((expressionVariable eq true) implies chooseInfo(true))
flow.addLogicStatement((expressionVariable eq false) implies chooseInfo(false))
if (operation == FirOperation.NOT_IS && type == nullableNothing) {
flow.addKnownInfo(operandVariable has any)
}
if (isNotNullCheck) {
flow.addLogicStatement((expressionVariable eq true) implies (operandVariable has any)) }
} else {
if (isNotNullCheck) {
flow.addLogicStatement((expressionVariable eq true) implies (operandVariable notEq null))
}
}
}
FirOperation.AS -> {
if (operandVariable.isReal()) {
flow.addKnownInfo(operandVariable has type)
} else {
logicSystem.approveStatementsInsideFlow(
flow,
operandVariable notEq null,
shouldRemoveSynthetics = true,
shouldForkFlow = false
)
}
}
FirOperation.SAFE_AS -> {
val expressionVariable = variableStorage.createSyntheticVariable(typeOperatorCall)
if (operandVariable.isReal()) {
flow.addLogicStatement((expressionVariable notEq null) implies (operandVariable has type))
flow.addLogicStatement((expressionVariable eq null) implies (operandVariable hasNot type))
} else {
if (type.nullability == ConeNullability.NOT_NULL) {
flow.addLogicStatement((expressionVariable notEq null) implies (operandVariable notEq null))
}
}
}
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 != null && rightConst != null -> return
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)
else -> processEq(node, leftOperand, rightOperand, operation)
}
}
}
}
// const != null
private fun processEqWithConst(node: OperatorCallNode, operand: FirExpression, const: FirConstExpression<*>, operation: FirOperation) {
val isEq = operation.isEq()
val expressionVariable = variableStorage.createSyntheticVariable(node.fir)
val flow = node.flow
val operandVariable = variableStorage.getOrCreateVariable(operand)
// expression == const -> expression != null
flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable notEq null))
if (operandVariable is RealVariable) {
flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable has any))
}
// propagating facts for (... == true) and (... == false)
if (const.kind == FirConstKind.Boolean) {
val constValue = const.value as Boolean
val shouldInvert = isEq xor constValue
logicSystem.translateConditionalVariableInStatements(
flow,
operandVariable,
expressionVariable,
shouldRemoveOriginalStatements = operandVariable.isSynthetic()
) {
if (shouldInvert) (it.condition.invert()) implies (it.effect)
else it
}
}
}
private fun processEq(node: OperatorCallNode, leftOperand: FirExpression, rightOperand: FirExpression, operation: FirOperation) {
val leftIsNullable = leftOperand.coneType?.isMarkedNullable ?: return
val rightIsNullable = rightOperand.coneType?.isMarkedNullable ?: return
// left == right && right not null -> left != null
when {
leftIsNullable && rightIsNullable -> return
leftIsNullable -> processEqNull(node, leftOperand, operation.invert())
rightIsNullable -> processEqNull(node, rightOperand, operation.invert())
}
}
private fun processEqNull(node: OperatorCallNode, operand: FirExpression, operation: FirOperation) {
val flow = node.flow
val expressionVariable = variableStorage.createSyntheticVariable(node.fir)
val operandVariable = variableStorage.getOrCreateVariable(operand)
val isEq = operation.isEq()
val predicate = when (isEq) {
true -> operandVariable eq null
false -> operandVariable notEq null
}
logicSystem.approvePredicate(flow, predicate).forEach { effect ->
flow.addLogicStatement((expressionVariable eq true) implies effect)
flow.addLogicStatement((expressionVariable eq false) implies effect.invert())
}
flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable eq null))
flow.addLogicStatement((expressionVariable notEq isEq) implies (operandVariable notEq null))
if (operandVariable is RealVariable) {
flow.addLogicStatement((expressionVariable eq isEq) implies (operandVariable hasNot any))
flow.addLogicStatement((expressionVariable notEq isEq) implies (operandVariable has any))
// TODO: design do we need casts to Nothing?
// flow.addLogicStatement((expressionVariable eq !isEq) implies (operandVariable has nullableNothing))
// flow.addLogicStatement((expressionVariable notEq !isEq) implies (operandVariable hasNot nullableNothing))
}
node.flow = flow
}
// ----------------------------------- 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 argument = checkNotNullCall.argument
val operandVariable = variableStorage.getOrCreateRealVariable(argument.symbol, argument) ?: return
node.flow.addKnownInfo(operandVariable has any)
}
// ----------------------------------- 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) {
val conditionVariable = variablesForWhenConditions.remove(previousNode)!!
node.flow = logicSystem.approveStatementsInsideFlow(
node.flow,
conditionVariable eq false,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
}
}
fun exitWhenBranchCondition(whenBranch: FirWhenBranch) {
val (conditionExitNode, branchEnterNode) = graphBuilder.exitWhenBranchCondition(whenBranch)
conditionExitNode.mergeIncomingFlow()
val conditionVariable = variableStorage.getOrCreateVariable(whenBranch.condition)
variablesForWhenConditions[conditionExitNode] = conditionVariable
branchEnterNode.flow = logicSystem.approveStatementsInsideFlow(
conditionExitNode.flow,
conditionVariable eq true,
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) {
val conditionVariable = variablesForWhenConditions.remove(previousConditionExitNode)!!
syntheticElseNode.flow = logicSystem.approveStatementsInsideFlow(
syntheticElseNode.flow,
conditionVariable eq false,
shouldForkFlow = true,
shouldRemoveSynthetics = true
)
}
}
val previousFlows = whenExitNode.alivePreviousNodes.map { it.flow }
val flow = logicSystem.joinFlow(previousFlows)
whenExitNode.flow = flow
// TODO: wtf?
// 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.approveStatementsInsideFlow(
loopBlockEnterNode.flow,
conditionVariable eq true,
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 -----------------------------------
fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
enterSafeCall(qualifiedAccessExpression)
}
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 { receiver ->
val type = receiver.coneType
?.takeIf { it.isMarkedNullable }
?.withNullability(ConeNullability.NOT_NULL)
?: return@let
when (val variable = variableStorage.getOrCreateVariable(receiver)) {
is RealVariable -> {
if (shouldFork) {
flow = logicSystem.forkFlow(flow)
}
flow.addKnownInfo(variable has type)
}
is SyntheticVariable -> {
flow = logicSystem.approveStatementsInsideFlow(
flow,
variable notEq null,
shouldFork,
shouldRemoveSynthetics = true
)
}
}
}
node.flow = flow
}
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 exitSafeCall(qualifiedAccess: FirQualifiedAccess) {
if (!qualifiedAccess.safe) return
val node = graphBuilder.exitSafeCall(qualifiedAccess).mergeIncomingFlow()
val variable = variableStorage.getOrCreateVariable(qualifiedAccess)
val receiverVariable = when (variable) {
is RealVariable -> variable.explicitReceiverVariable!!
is SyntheticVariable -> variableStorage.getOrCreateVariable(qualifiedAccess.explicitReceiver!!)
}
logicSystem.addLogicStatement(node.flow, (variable notEq null) implies (receiverVariable notEq null))
if (receiverVariable.isReal()) {
logicSystem.addLogicStatement(node.flow, (variable notEq null) implies (receiverVariable has any))
}
}
private fun processConditionalContract(functionCall: FirFunctionCall) {
val contractDescription = (functionCall.symbol as? FirNamedFunctionSymbol)?.fir?.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 = variableStorage.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 = variableStorage.getOrCreateVariable(fir)
val lastNode = graphBuilder.lastNode
when (val value = effect.value) {
ConeConstantReference.WILDCARD -> {
lastNode.flow = logicSystem.approveStatementsInsideFlow(
lastNode.flow,
argumentVariable eq true,
shouldForkFlow = false,
shouldRemoveSynthetics = true
)
}
is ConeBooleanConstantReference -> {
logicSystem.replaceConditionalVariableInStatements(
lastNode.flow,
argumentVariable,
functionCallVariable,
filter = { it.condition.condition == value.toCondition() }
)
}
ConeConstantReference.NOT_NULL, ConeConstantReference.NULL -> {
logicSystem.replaceConditionalVariableInStatements(
lastNode.flow,
argumentVariable,
functionCallVariable,
filter = { it.condition.condition == Condition.EqTrue },
transform = { Predicate(it.condition.variable, value.toCondition()) implies it.effect }
)
}
else -> throw IllegalArgumentException("Unsupported constant reference: $value")
}
}
graphBuilder.exitContract(functionCall).mergeIncomingFlow()
contractDescriptionVisitingMode = true
}
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
exitVariableInitialization(node, initializer, variable, isVariableDeclaration = true)
}
private fun exitVariableInitialization(node: CFGNode<*>, initializer: FirExpression, variable: FirProperty, isVariableDeclaration: Boolean) {
val propertyVariable = variableStorage.getOrCreateRealVariable(variable.symbol, variable)
if (!isVariableDeclaration) {
node.flow.removeAllAboutVariable(propertyVariable)
variableStorage.unboundPossiblyAliasedVariable(variable.symbol)
}
variableStorage[initializer]?.safeAs<SyntheticVariable>()?.let { initializerVariable ->
/*
* That part is needed for cases like that:
*
* val b = x is String
* ...
* if (b) {
* x.length
* }
*/
logicSystem.replaceConditionalVariableInStatements(node.flow, initializerVariable, propertyVariable)
return
}
variableStorage.getOrCreateRealVariable(initializer.symbol, initializer)?.let { initializerVariable ->
if (initializerVariable.isStable) {
variableStorage.attachSymbolToVariable(variable.symbol, initializerVariable)
} else {
node.flow.addLogicStatement((propertyVariable notEq null) implies (initializerVariable notEq null))
}
}
if (!isVariableDeclaration) {
node.flow.addKnownInfo(propertyVariable has initializer.typeRef.coneTypeUnsafe<ConeKotlinType>())
}
}
fun exitVariableAssignment(assignment: FirVariableAssignment) {
val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
val property = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty ?: return //error("left side of assignment should have symbol")
if (!property.isLocal) return
exitVariableInitialization(node, assignment.rValue, property, isVariableDeclaration = false)
}
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 = variableStorage.getOrCreateVariable(leftNode.previousNodes.first().fir)
rightNode.flow = logicSystem.approveStatementsInsideFlow(
parentFlow,
leftOperandVariable eq isAnd,
shouldForkFlow = true,
shouldRemoveSynthetics = false
)
}
private fun exitBinaryBooleanOperator(
binaryLogicExpression: FirBinaryLogicExpression,
node: AbstractBinaryExitNode<*>,
isAnd: Boolean
) {
val bothEvaluated = isAnd
val onlyLeftEvaluated = !bothEvaluated
// 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 = variableStorage.getOrCreateVariable(binaryLogicExpression.leftOperand)
val rightVariable = variableStorage.getOrCreateVariable(binaryLogicExpression.rightOperand)
val operatorVariable = variableStorage.getOrCreateVariable(binaryLogicExpression)
val (conditionalFromLeft, conditionalFromRight, approvedFromRight) = logicSystem.collectInfoForBooleanOperator(
flowFromLeft,
leftVariable,
flowFromRight,
rightVariable
)
// left && right == True
// left || right == False
val approvedIfTrue: MutableKnownFacts = mutableMapOf()
logicSystem.approvePredicateTo(approvedIfTrue, flowFromRight, leftVariable eq bothEvaluated, conditionalFromLeft)
logicSystem.approvePredicateTo(approvedIfTrue, flowFromRight, rightVariable eq bothEvaluated, conditionalFromRight)
approvedFromRight.forEach { (variable, info) ->
approvedIfTrue.addInfo(variable, info)
}
approvedIfTrue.values.forEach { info ->
flow.addLogicStatement((operatorVariable eq bothEvaluated) implies info)
}
// left && right == False
// left || right == True
val approvedIfFalse: MutableKnownFacts = mutableMapOf()
val leftIsFalse = logicSystem.approvePredicate(flowFromLeft, leftVariable eq onlyLeftEvaluated, conditionalFromLeft)
val rightIsFalse = logicSystem.approvePredicate(flowFromRight, rightVariable eq onlyLeftEvaluated, conditionalFromRight)
approvedIfFalse.mergeInfo(logicSystem.orForVerifiedFacts(leftIsFalse, rightIsFalse))
approvedIfFalse.values.forEach { info ->
flow.addLogicStatement((operatorVariable eq onlyLeftEvaluated) implies info)
}
node.flow = flow
variableStorage.removeSyntheticVariable(leftVariable)
variableStorage.removeSyntheticVariable(rightVariable)
}
private fun exitBooleanNot(functionCall: FirFunctionCall, node: FunctionCallNode) {
val booleanExpressionVariable = variableStorage.getOrCreateVariable(node.previousNodes.first().fir)
val variable = variableStorage.getOrCreateVariable(functionCall)
logicSystem.replaceConditionalVariableInStatements(
node.flow,
booleanExpressionVariable,
variable,
transform = { it.invertCondition() }
)
}
// ----------------------------------- 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()
}
// ------------------------------------------------------ Utils ------------------------------------------------------
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)
}
private fun FLOW.addLogicStatement(statement: LogicStatement) {
logicSystem.addLogicStatement(this, statement)
}
private fun FLOW.addKnownInfo(info: DataFlowInfo) {
logicSystem.addKnownInfo(this, info)
}
private fun FLOW.removeAllAboutVariable(variable: RealVariable?) {
if (variable == null) return
logicSystem.removeAllAboutVariable(this, variable)
}
}
@@ -0,0 +1,161 @@
/*
* 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.new
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
abstract class Flow {
abstract fun getKnownInfo(variable: RealVariable): DataFlowInfo?
abstract fun getLogicStatements(variable: DataFlowVariable): Collection<LogicStatement>
abstract fun getVariablesInKnownInfos(): Collection<RealVariable>
abstract fun removeConditions(variable: DataFlowVariable): Collection<LogicStatement>
}
abstract class LogicSystem<FLOW : Flow>(protected val context: ConeInferenceContext) {
// ------------------------------- Flow operations -------------------------------
abstract fun createEmptyFlow(): FLOW
abstract fun forkFlow(flow: FLOW): FLOW
abstract fun joinFlow(flows: Collection<FLOW>): FLOW
abstract fun addKnownInfo(flow: FLOW, info: DataFlowInfo)
abstract fun addLogicStatement(flow: FLOW, statement: LogicStatement)
abstract fun removeAllAboutVariable(flow: FLOW, variable: RealVariable)
abstract fun translateConditionalVariableInStatements(
flow: FLOW,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean,
filter: (LogicStatement) -> Boolean = { true },
transform: (LogicStatement) -> LogicStatement = { it }
)
abstract fun approveStatementsInsideFlow(
flow: FLOW,
predicate: Predicate,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): FLOW
protected abstract fun getLogicStatementsWithVariable(flow: FLOW, variable: DataFlowVariable): Collection<LogicStatement>
// ------------------------------- Callbacks for updating implicit receiver stack -------------------------------
abstract fun processUpdatedReceiverVariable(flow: FLOW, variable: RealVariable)
abstract fun updateAllReceivers(flow: FLOW)
// ------------------------------- Public DataFlowInfo util functions -------------------------------
data class InfoForBooleanOperator(
val conditionalFromLeft: Collection<LogicStatement>,
val conditionalFromRight: Collection<LogicStatement>,
val knownFromRight: KnownInfos
)
abstract fun collectInfoForBooleanOperator(
leftFlow: FLOW,
leftVariable: DataFlowVariable,
rightFlow: FLOW,
rightVariable: DataFlowVariable
): InfoForBooleanOperator
abstract fun approvePredicateTo(
destination: MutableKnownFacts,
flow: FLOW,
predicate: Predicate,
statements: Collection<LogicStatement>
)
/**
* Recursively collects all DataFlowInfos approved by [predicate] and all predicates
* that has been implied by it
* TODO: or not recursively?
*/
fun approvePredicate(flow: FLOW, predicate: Predicate): Collection<DataFlowInfo> {
val statements = getLogicStatementsWithVariable(flow, predicate.variable)
return approvePredicate(flow, predicate, statements).values
}
fun orForVerifiedFacts(
left: KnownInfos,
right: KnownInfos
): MutableKnownFacts {
if (left.isNullOrEmpty() || right.isNullOrEmpty()) return mutableMapOf()
val map = mutableMapOf<RealVariable, MutableDataFlowInfo>()
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
}
// ------------------------------- 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<DataFlowInfo>): MutableDataFlowInfo {
require(infos.isNotEmpty())
infos.singleOrNull()?.let { return it as MutableDataFlowInfo }
val variable = infos.first().variable
assert(infos.all { it.variable == variable })
val exactType = orTypes(infos.map { it.exactType })
val exactNotType = orTypes(infos.map { it.exactNotType })
return MutableDataFlowInfo(variable, 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
}
}
fun <FLOW : Flow> LogicSystem<FLOW>.approvePredicate(flow: FLOW, predicate: Predicate, statements: Collection<LogicStatement>): MutableKnownFacts {
return mutableMapOf<RealVariable, MutableDataFlowInfo>().apply {
approvePredicateTo(this, flow, predicate, statements)
}
}
/*
* used for:
* 1. val b = x is String
* 2. b = x is String
* 3. !b | b.not() for Booleans
*/
fun <F : Flow> LogicSystem<F>.replaceConditionalVariableInStatements(
flow: F,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
filter: (LogicStatement) -> Boolean = { true },
transform: (LogicStatement) -> LogicStatement = { it }
) {
translateConditionalVariableInStatements(
flow,
originalVariable,
newVariable,
shouldRemoveOriginalStatements = true,
filter,
transform
)
}
@@ -0,0 +1,411 @@
/*
* 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.new
import com.google.common.collect.ArrayListMultimap
import kotlinx.collections.immutable.*
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import java.util.*
import kotlin.NoSuchElementException
import kotlin.collections.HashSet
data class PersistentDataFlowInfo(
override val variable: RealVariable,
override val exactType: PersistentSet<ConeKotlinType>,
override val exactNotType: PersistentSet<ConeKotlinType>
) : DataFlowInfo() {
override operator fun plus(other: DataFlowInfo): PersistentDataFlowInfo {
return PersistentDataFlowInfo(
variable,
exactType + other.exactType,
exactNotType + other.exactNotType
)
}
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactNotType.isEmpty()
override fun invert(): PersistentDataFlowInfo {
return PersistentDataFlowInfo(variable, exactNotType, exactType)
}
}
typealias PersistentKnownFacts = PersistentMap<RealVariable, PersistentDataFlowInfo>
typealias PersistentLogicStatements = PersistentMap<DataFlowVariable, PersistentList<LogicStatement>>
class PersistentFlow : Flow {
val previousFlow: PersistentFlow?
var knownFacts: PersistentKnownFacts
var logicStatements: PersistentLogicStatements
val level: Int
var knownFactsDiff: PersistentKnownFacts = persistentHashMapOf()
constructor(previousFlow: PersistentFlow) {
this.previousFlow = previousFlow
knownFacts = previousFlow.knownFacts
logicStatements = previousFlow.logicStatements
level = previousFlow.level + 1
}
constructor() {
previousFlow = null
knownFacts = persistentHashMapOf()
logicStatements = persistentHashMapOf()
level = 1
}
override fun getKnownInfo(variable: RealVariable): DataFlowInfo? {
return knownFacts[variable]
}
override fun getLogicStatements(variable: DataFlowVariable): Collection<LogicStatement> {
return logicStatements[variable] ?: emptyList()
}
override fun getVariablesInKnownInfos(): Collection<RealVariable> {
return knownFacts.keys
}
override fun removeConditions(variable: DataFlowVariable): Collection<LogicStatement> {
return getLogicStatements(variable).also {
if (it.isNotEmpty()) {
logicStatements -= variable
}
}
}
}
abstract class PersistentLogicSystem(private val anyType: ConeKotlinType, context: ConeInferenceContext) :
LogicSystem<PersistentFlow>(context) {
override fun createEmptyFlow(): PersistentFlow {
return PersistentFlow()
}
override fun forkFlow(flow: PersistentFlow): PersistentFlow {
return PersistentFlow(flow)
}
override fun joinFlow(flows: Collection<PersistentFlow>): PersistentFlow {
if (flows.isEmpty()) return createEmptyFlow()
flows.singleOrNull()?.let { return it }
val commonFlow = flows.reduce(::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.getKnownFactsDiff(variable, commonFlow) })
if (info.isEmpty) continue
commonFlow.knownFacts = commonFlow.knownFacts.addNewInfo(info)
if (commonFlow.previousFlow != null) {
commonFlow.knownFactsDiff = commonFlow.knownFactsDiff.addNewInfo(info)
}
}
updateAllReceivers(commonFlow)
return commonFlow
}
private fun PersistentFlow.getKnownFactsDiff(variable: RealVariable, parentFlow: PersistentFlow): MutableDataFlowInfo {
var flow = this
val result = MutableDataFlowInfo(variable)
while (flow != parentFlow) {
flow.knownFactsDiff[variable]?.let {
result += it
}
flow = flow.previousFlow!!
}
return result
}
/**
* This is an iterable over real variable that has known facts in flow range
* from [this] to [parentFlow]
*/
private fun PersistentFlow.diffVariablesIterable(parentFlow: PersistentFlow): Iterable<RealVariable> =
object : DiffIterable<RealVariable>(parentFlow, this) {
override fun extractIterator(flow: PersistentFlow): Iterator<RealVariable> {
return flow.knownFactsDiff.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()
}
}
}
}
override fun addKnownInfo(flow: PersistentFlow, info: DataFlowInfo) {
with(flow) {
knownFacts = knownFacts.addNewInfo(info)
if (previousFlow != null) {
knownFactsDiff = knownFactsDiff.addNewInfo(info)
}
if (info.variable.isThisReference) {
processUpdatedReceiverVariable(flow, info.variable)
}
}
}
override fun addLogicStatement(flow: PersistentFlow, statement: LogicStatement) {
if (statement.condition == statement.effect) return
with(flow) {
val variable = statement.condition.variable
val existingFacts = logicStatements[variable]
logicStatements = if (existingFacts == null) {
logicStatements.put(variable, persistentListOf(statement))
} else {
logicStatements.put(variable, existingFacts + statement)
}
}
}
override fun removeAllAboutVariable(flow: PersistentFlow, variable: RealVariable) {
flow.knownFacts -= variable
flow.knownFactsDiff -= variable
// TODO: should we search variable in all logic statements?
}
override fun translateConditionalVariableInStatements(
flow: PersistentFlow,
originalVariable: DataFlowVariable,
newVariable: DataFlowVariable,
shouldRemoveOriginalStatements: Boolean,
filter: (LogicStatement) -> Boolean,
transform: (LogicStatement) -> LogicStatement
) {
with(flow) {
val statements = logicStatements[originalVariable]?.takeIf { it.isNotEmpty() } ?: return
val newStatements = statements.filter(filter).map {
val newStatement = Predicate(newVariable, it.condition.condition) implies it.effect
transform(newStatement)
}.toPersistentList()
if (shouldRemoveOriginalStatements) {
logicStatements -= originalVariable
}
logicStatements = logicStatements.put(newVariable, newStatements)
}
}
override fun approveStatementsInsideFlow(
flow: PersistentFlow,
predicate: Predicate,
shouldForkFlow: Boolean,
shouldRemoveSynthetics: Boolean
): PersistentFlow {
val approvedFacts = approvePredicatesInternal(
flow,
predicate,
initialStatements = null,
shouldRemoveSynthetics
)
val resultFlow = if (shouldForkFlow) forkFlow(flow) else flow
if (approvedFacts.isEmpty) return resultFlow
val updatedReceivers = mutableSetOf<RealVariable>()
approvedFacts.asMap().forEach { (variable, infos) ->
var resultInfo = PersistentDataFlowInfo(variable, persistentSetOf(), persistentSetOf())
for (info in infos) {
resultInfo += info
}
if (variable.isThisReference) {
updatedReceivers += variable
}
addKnownInfo(resultFlow, resultInfo)
}
updatedReceivers.forEach {
processUpdatedReceiverVariable(resultFlow, it)
}
return resultFlow
}
private fun approvePredicatesInternal(
flow: PersistentFlow,
predicate: Predicate,
initialStatements: Collection<LogicStatement>?,
shouldRemoveSynthetics: Boolean
): ArrayListMultimap<RealVariable, DataFlowInfo> {
val approvedFacts: ArrayListMultimap<RealVariable, DataFlowInfo> = ArrayListMultimap.create()
val predicatesToApprove = LinkedList<Predicate>().apply { this += predicate }
approvePredicatesInternal(flow, predicatesToApprove, initialStatements, shouldRemoveSynthetics, approvedFacts)
return approvedFacts
}
private fun approvePredicatesInternal(
flow: PersistentFlow,
predicatesToApprove: LinkedList<Predicate>,
initialStatements: Collection<LogicStatement>?,
shouldRemoveSynthetics: Boolean,
approvedFacts: ArrayListMultimap<RealVariable, DataFlowInfo>
) {
if (predicatesToApprove.isEmpty()) return
val approvedVariables = mutableSetOf<RealVariable>()
val approvedPredicates = mutableSetOf<Predicate>()
var firstIteration = true
while (predicatesToApprove.isNotEmpty()) {
@Suppress("NAME_SHADOWING")
val predicate: Predicate = predicatesToApprove.removeFirst()
// Defense from cycles in facts
if (!approvedPredicates.add(predicate)) {
continue
}
val statements = initialStatements?.takeIf { firstIteration }
?: flow.logicStatements[predicate.variable]?.takeIf { it.isNotEmpty() }
?: continue
if (shouldRemoveSynthetics && predicate.variable.isSynthetic()) {
flow.logicStatements -= predicate.variable
}
for (statement in statements) {
if (statement.condition == predicate) {
when (val effect = statement.effect) {
is Predicate -> predicatesToApprove += effect
is DataFlowInfo -> {
approvedFacts.put(effect.variable, effect)
approvedVariables += effect.variable
}
}
}
}
firstIteration = false
}
val newPredicates = LinkedList<Predicate>()
for (approvedVariable in approvedVariables) {
var variable = approvedVariable
foo@ while (variable.explicitReceiverVariable != null && variable.isSafeCall) {
when (val receiver = variable.explicitReceiverVariable!!) {
is RealVariable -> {
approvedFacts.put(receiver, receiver has anyType)
variable = receiver
}
is SyntheticVariable -> {
newPredicates += receiver notEq null
break@foo
}
else -> throw IllegalStateException()
}
}
}
approvePredicatesInternal(flow, newPredicates, initialStatements = null, shouldRemoveSynthetics, approvedFacts)
}
override fun approvePredicateTo(
destination: MutableKnownFacts,
flow: PersistentFlow,
predicate: Predicate,
statements: Collection<LogicStatement>
) {
val approvePredicates = approvePredicatesInternal(flow, predicate, statements, shouldRemoveSynthetics = false)
approvePredicates.asMap().forEach { (variable, infos) ->
for (info in infos) {
val mutableInfo = info.asMutableInfo()
destination.put(variable, mutableInfo) {
it += mutableInfo
it
}
}
}
}
override fun collectInfoForBooleanOperator(
leftFlow: PersistentFlow,
leftVariable: DataFlowVariable,
rightFlow: PersistentFlow,
rightVariable: DataFlowVariable
): InfoForBooleanOperator {
return InfoForBooleanOperator(
leftFlow.logicStatements[leftVariable] ?: emptyList(),
rightFlow.logicStatements[rightVariable] ?: emptyList(),
rightFlow.knownFactsDiff
)
}
override fun getLogicStatementsWithVariable(flow: PersistentFlow, variable: DataFlowVariable): Collection<LogicStatement> {
return flow.logicStatements[variable] ?: emptyList()
}
// --------------------------------------------------------------------\
}
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
}
private 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
}
private fun PersistentKnownFacts.addNewInfo(info: DataFlowInfo): PersistentKnownFacts {
val variable = info.variable
val existingInfo = this[variable]
return if (existingInfo == null) {
val persistentInfo = if (info is PersistentDataFlowInfo) info else info.toPersistent()
put(variable, persistentInfo)
} else {
put(variable, existingInfo + info)
}
}
private fun DataFlowInfo.toPersistent(): PersistentDataFlowInfo = PersistentDataFlowInfo(
variable,
exactType.toPersistentSet(),
exactNotType.toPersistentSet()
)
fun DataFlowInfo.asMutableInfo(): MutableDataFlowInfo = when (this) {
is MutableDataFlowInfo -> this
is PersistentDataFlowInfo -> MutableDataFlowInfo(variable, exactType.toMutableSet(), exactNotType.toMutableSet())
else -> throw IllegalArgumentException("Unknown DataFlowInfo type: ${this::class}")
}
@@ -0,0 +1,152 @@
/*
* 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.new
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.references.impl.FirExplicitThisReference
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
class VariableStorage {
private var counter = 1
private val realVariables: MutableMap<Identifier, RealVariable> = HashMap()
private val syntheticVariables: MutableMap<FirElement, SyntheticVariable> = HashMap()
private val localVariableAliases: MutableMap<AbstractFirBasedSymbol<*>, Identifier> = HashMap()
fun getOrCreateRealVariable(symbol: AbstractFirBasedSymbol<*>, fir: FirElement): RealVariable {
val realFir = fir.unwrapElement()
val identifier = getIdentifierBySymbol(symbol, realFir)
return realVariables.getOrPut(identifier) { createRealVariableInternal(identifier, realFir) }
}
private fun FirElement.unwrapElement(): FirElement = when (this) {
is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject } ?: this
is FirExpressionWithSmartcast -> originalExpression.unwrapElement()
else -> this
}
private fun getIdentifierBySymbol(
symbol: AbstractFirBasedSymbol<*>,
fir: FirElement,
): Identifier {
return localVariableAliases[symbol] ?: run {
val expression = fir as? FirQualifiedAccessExpression
Identifier(
symbol,
expression?.dispatchReceiver?.takeIf { it != FirNoReceiverExpression }?.let(this::getOrCreateVariable),
expression?.extensionReceiver?.takeIf { it != FirNoReceiverExpression }?.let(this::getOrCreateVariable)
)
}
}
/**
* [originalFir] used for extracting expression under <when_subject> and extracting receiver
*/
private fun createRealVariableInternal(identifier: Identifier, originalFir: FirElement): RealVariable {
val receiver: FirExpression?
val isSafeCall: Boolean
val isThisReference: Boolean
val expression = when (originalFir) {
is FirQualifiedAccessExpression -> originalFir
is FirWhenSubjectExpression -> originalFir.whenSubject.whenExpression.subject as? FirQualifiedAccessExpression
else -> null
}
if (expression != null) {
receiver = expression.explicitReceiver
isSafeCall = expression.safe
isThisReference = expression.calleeReference is FirThisReference
} else {
receiver = null
isSafeCall = false
isThisReference = false
}
val receiverVariable = receiver?.let { getOrCreateVariable(it) }
return RealVariable(identifier, isThisReference, receiverVariable, isSafeCall, counter++)
}
@JvmName("getOrCreateRealVariableOrNull")
fun getOrCreateRealVariable(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? =
symbol?.let { getOrCreateRealVariable(it, fir) }
fun createSyntheticVariable(fir: FirElement): SyntheticVariable =
SyntheticVariable(fir, counter++).also { syntheticVariables[fir] = it }
fun getOrCreateVariable(fir: FirElement): DataFlowVariable {
val realFir = fir.unwrapElement()
return when (val symbol = realFir.symbol) {
null -> syntheticVariables[realFir] ?: createSyntheticVariable(realFir)
else -> getOrCreateRealVariable(symbol, realFir)
}
}
/**
* Also removes existing real variable for [varSymbol] if it exists
*/
fun attachSymbolToVariable(varSymbol: AbstractFirBasedSymbol<*>, targetVariable: RealVariable) {
localVariableAliases[varSymbol] = targetVariable.identifier
realVariables.remove(Identifier(varSymbol, null, null))
}
operator fun get(symbol: AbstractFirBasedSymbol<*>?, fir: FirElement): RealVariable? {
return symbol?.let { realVariables[getIdentifierBySymbol(it, fir.unwrapElement())] }
}
operator fun get(fir: FirElement): DataFlowVariable? {
val realFir = fir.unwrapElement()
val symbol = realFir.symbol
return if (symbol != null) {
get(symbol, realFir)
} else {
syntheticVariables[realFir]
}
}
fun removeRealVariable(symbol: AbstractFirBasedSymbol<*>) {
// TODO: this shit fails
// assert(!localVariableAliases.containsValue(symbol))
realVariables.remove(Identifier(symbol, null, null))
}
fun unboundPossiblyAliasedVariable(symbol: AbstractFirBasedSymbol<*>) {
localVariableAliases.remove(symbol)
}
fun removeSyntheticVariable(variable: DataFlowVariable) {
if (variable !is SyntheticVariable) return
syntheticVariables.remove(variable.fir)
}
fun reset() {
counter = 0
realVariables.clear()
syntheticVariables.clear()
localVariableAliases.clear()
}
}
internal val FirElement.symbol: AbstractFirBasedSymbol<*>?
get() = when (this) {
is FirResolvable -> symbol
is FirSymbolOwner<*> -> symbol
is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol
else -> null
}?.takeIf { this is FirThisReceiverExpression || it !is FirFunctionSymbol<*> }
internal val FirResolvable.symbol: AbstractFirBasedSymbol<*>?
get() = when (val reference = calleeReference) {
is FirExplicitThisReference -> reference.boundSymbol
is FirResolvedNamedReference -> reference.resolvedSymbol
is FirNamedReferenceWithCandidate -> reference.candidateSymbol
else -> null
}
@@ -0,0 +1,228 @@
/*
* 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.new
import com.google.common.collect.Multimap
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.modality
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.resolve.dfa.Condition
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
// --------------------------------------- Variables ---------------------------------------
class Identifier(
val symbol: AbstractFirBasedSymbol<*>,
val dispatchReceiver: DataFlowVariable?,
val extensionReceiver: DataFlowVariable?
) {
override fun toString(): String {
val callableId = (symbol as? FirCallableSymbol<*>)?.callableId
return "[$callableId, dispatchReceiver = $dispatchReceiver, extensionReceiver = $extensionReceiver]"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Identifier
if (symbol !== other.symbol) return false
if (dispatchReceiver != other.dispatchReceiver) return false
if (extensionReceiver != other.extensionReceiver) return false
return true
}
override fun hashCode(): Int {
var result = symbol.hashCode()
result = 31 * result + (dispatchReceiver?.hashCode() ?: 0)
result = 31 * result + (extensionReceiver?.hashCode() ?: 0)
return result
}
}
sealed class DataFlowVariable(private val variableIndexForDebug: Int) {
abstract val isStable: Boolean
final override fun toString(): String {
return "d$variableIndexForDebug"
}
}
class RealVariable(
val identifier: Identifier,
val isThisReference: Boolean,
val explicitReceiverVariable: DataFlowVariable?,
val isSafeCall: Boolean,
variableIndexForDebug: Int
) : DataFlowVariable(variableIndexForDebug) {
override val isStable: Boolean by lazy {
when (val symbol = identifier.symbol) {
is FirPropertySymbol -> {
val property = symbol.fir
when {
property.isLocal -> true
property.isVar -> false
property.modality != Modality.FINAL -> false
else -> true
}
}
else -> true
}
}
override fun equals(other: Any?): Boolean {
return this === other
}
private val _hashCode by lazy {
31 * identifier.hashCode() + (explicitReceiverVariable?.hashCode() ?: 0)
}
override fun hashCode(): Int {
return _hashCode
}
}
class SyntheticVariable(val fir: FirElement, variableIndexForDebug: Int) : DataFlowVariable(variableIndexForDebug) {
override val isStable: Boolean get() = true
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as SyntheticVariable
return fir isEqualsTo other.fir
}
override fun hashCode(): Int {
return if (fir is FirResolvedQualifier) {
31 * fir.packageFqName.hashCode() + fir.classId.hashCode()
} else {
fir.hashCode()
}
}
}
private infix fun FirElement.isEqualsTo(other: FirElement): Boolean {
if (this !is FirResolvedQualifier || other !is FirResolvedQualifier) return this == other
if (packageFqName != other.packageFqName) return false
if (classId != other.classId) return false
return true
}
// --------------------------------------- Facts ---------------------------------------
sealed class PredicateEffect<T : PredicateEffect<T>> {
abstract fun invert(): T
}
data class Predicate(val variable: DataFlowVariable, val condition: Condition) : PredicateEffect<Predicate>() {
override fun invert(): Predicate {
return Predicate(variable, condition.invert())
}
override fun toString(): String {
return "$variable $condition"
}
}
abstract class DataFlowInfo : PredicateEffect<DataFlowInfo>() {
abstract val variable: RealVariable
abstract val exactType: Set<ConeKotlinType>
abstract val exactNotType: Set<ConeKotlinType>
abstract operator fun plus(other: DataFlowInfo): DataFlowInfo
abstract val isEmpty: Boolean
val isNotEmpty: Boolean get() = !isEmpty
override fun toString(): String {
return "$variable: $exactType, $exactNotType"
}
}
class MutableDataFlowInfo(
override val variable: RealVariable,
override val exactType: MutableSet<ConeKotlinType> = HashSet(),
override val exactNotType: MutableSet<ConeKotlinType> = HashSet()
) : DataFlowInfo() {
override fun plus(other: DataFlowInfo): MutableDataFlowInfo = MutableDataFlowInfo(
variable,
HashSet(exactType).apply { addAll(other.exactType) },
HashSet(exactNotType).apply { addAll(other.exactNotType) }
)
override val isEmpty: Boolean
get() = exactType.isEmpty() && exactType.isEmpty()
override fun invert(): DataFlowInfo {
return MutableDataFlowInfo(
variable,
HashSet(exactNotType),
HashSet(exactType)
)
}
operator fun plusAssign(info: DataFlowInfo) {
exactType += info.exactType
exactNotType += info.exactNotType
}
fun copy(): MutableDataFlowInfo = MutableDataFlowInfo(variable, HashSet(exactType), HashSet(exactNotType))
}
class LogicStatement(
val condition: Predicate,
val effect: PredicateEffect<*>
) {
override fun toString(): String {
return "$condition -> $effect"
}
}
fun LogicStatement.invertCondition(): LogicStatement = LogicStatement(condition.invert(), effect)
// --------------------------------------- Aliases ---------------------------------------
typealias KnownInfos = Map<RealVariable, DataFlowInfo>
typealias MutableKnownFacts = MutableMap<RealVariable, MutableDataFlowInfo>
typealias LogicStatements = Multimap<Predicate, LogicStatement>
// --------------------------------------- DSL ---------------------------------------
infix fun DataFlowVariable.eq(constant: Boolean?): Predicate {
val condition = when (constant) {
true -> Condition.EqTrue
false -> Condition.EqFalse
null -> Condition.EqNull
}
return Predicate(this, condition)
}
infix fun DataFlowVariable.notEq(constant: Boolean?): Predicate {
val condition = when (constant) {
true -> Condition.EqFalse
false -> Condition.EqTrue
null -> Condition.NotEqNull
}
return Predicate(this, condition)
}
infix fun Predicate.implies(effect: PredicateEffect<*>): LogicStatement = LogicStatement(this, effect)
infix fun RealVariable.has(types: MutableSet<ConeKotlinType>): DataFlowInfo = MutableDataFlowInfo(this, types, HashSet())
infix fun RealVariable.has(type: ConeKotlinType): DataFlowInfo =
MutableDataFlowInfo(this, HashSet<ConeKotlinType>().apply { this += type }, HashSet())
infix fun RealVariable.hasNot(types: MutableSet<ConeKotlinType>): DataFlowInfo = MutableDataFlowInfo(this, HashSet(), types)
infix fun RealVariable.hasNot(type: ConeKotlinType): DataFlowInfo =
MutableDataFlowInfo(this, HashSet(), HashSet<ConeKotlinType>().apply { this += type })
@@ -0,0 +1,117 @@
/*
* 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.new
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirOperation
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.calls.ConeInferenceContext
import org.jetbrains.kotlin.fir.resolve.dfa.Condition
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeIntersector
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
fun ConeInferenceContext.commonSuperTypeOrNull(types: List<ConeKotlinType>): ConeKotlinType? {
return when (types.size) {
0 -> null
1 -> types.first()
else -> with(NewCommonSuperTypeCalculator) {
commonSuperType(types) as ConeKotlinType
}
}
}
fun ConeInferenceContext.intersectTypesOrNull(types: List<ConeKotlinType>): ConeKotlinType? {
return when (types.size) {
0 -> null
1 -> types.first()
else -> ConeTypeIntersector.intersectTypes(this, types)
}
}
@UseExperimental(ExperimentalContracts::class)
fun DataFlowVariable.isSynthetic(): Boolean {
contract {
returns(true) implies (this@isSynthetic is SyntheticVariable)
returns(false) implies (this@isSynthetic is RealVariable)
}
return this is SyntheticVariable
}
@UseExperimental(ExperimentalContracts::class)
fun DataFlowVariable.isReal(): Boolean {
contract {
returns(true) implies (this@isReal is RealVariable)
returns(false) implies (this@isReal is SyntheticVariable)
}
return this is RealVariable
}
operator fun DataFlowInfo.plus(other: DataFlowInfo?): DataFlowInfo = other?.let { this + other } ?: this
fun MutableKnownFacts.addInfo(variable: RealVariable, info: DataFlowInfo) {
put(variable, info.asMutableInfo()) { it.apply { this += info } }
}
fun MutableKnownFacts.mergeInfo(other: Map<RealVariable, DataFlowInfo>) {
other.forEach { (variable, info) ->
addInfo(variable, info)
}
}
@UseExperimental(ExperimentalContracts::class)
internal inline fun <K, V> MutableMap<K, V>.put(key: K, value: V, remappingFunction: (existing: V) -> V) {
contract {
callsInPlace(remappingFunction, InvocationKind.AT_MOST_ONCE)
}
val existing = this[key]
if (existing == null) {
put(key, value)
} else {
put(key, remappingFunction(existing))
}
}
internal val FirExpression.coneType: ConeKotlinType? get() = typeRef.coneTypeSafe()
internal fun FirOperation.invert(): FirOperation = when (this) {
FirOperation.EQ -> FirOperation.NOT_EQ
FirOperation.NOT_EQ -> FirOperation.EQ
FirOperation.IDENTITY -> FirOperation.NOT_IDENTITY
FirOperation.NOT_IDENTITY -> FirOperation.IDENTITY
else -> throw IllegalArgumentException("$this can not be inverted")
}
internal fun FirOperation.isEq(): Boolean {
return when (this) {
FirOperation.EQ, FirOperation.IDENTITY -> true
FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY -> false
else -> throw IllegalArgumentException("$this should not be there")
}
}
internal fun FirFunctionCall.isBooleanNot(): Boolean {
val symbol = calleeReference.safeAs<FirResolvedNamedReference>()?.resolvedSymbol as? FirNamedFunctionSymbol ?: return false
return symbol.callableId == FirDataFlowAnalyzer.KOTLIN_BOOLEAN_NOT
}
internal fun ConeConstantReference.toCondition(): Condition = when (this) {
ConeConstantReference.NULL -> Condition.EqNull
ConeConstantReference.NOT_NULL -> Condition.NotEqNull
ConeBooleanConstantReference.TRUE -> Condition.EqTrue
ConeBooleanConstantReference.FALSE -> Condition.EqFalse
else -> throw IllegalArgumentException("$this can not be transformed to Condition")
}
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.InferenceComponents
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionStageRunner
import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.dfa.new.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.inference.FirCallCompleter
import org.jetbrains.kotlin.fir.resolve.transformers.*
import org.jetbrains.kotlin.fir.scopes.FirScope
@@ -76,7 +76,7 @@ abstract class FirAbstractBodyResolveTransformer(phase: FirResolvePhase) : FirAb
protected inline val samResolver: FirSamResolver get() = components.samResolver
protected inline val typeResolverTransformer: FirSpecificTypeResolverTransformer get() = components.typeResolverTransformer
protected inline val callCompleter: FirCallCompleter get() = components.callCompleter
protected inline val dataFlowAnalyzer: FirDataFlowAnalyzer get() = components.dataFlowAnalyzer
protected inline val dataFlowAnalyzer: FirDataFlowAnalyzer<*> get() = components.dataFlowAnalyzer
protected inline val scopeSession: ScopeSession get() = components.scopeSession
protected inline val file: FirFile get() = components.file
protected inline val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer get() = components.integerLiteralTypeApproximator
@@ -123,7 +123,7 @@ abstract class FirAbstractBodyResolveTransformer(phase: FirResolvePhase) : FirAb
FirTypeResolveScopeForBodyResolve(topLevelScopes, implicitReceiverStack, localScopes), session
)
val callCompleter: FirCallCompleter = FirCallCompleter(transformer, this)
override val dataFlowAnalyzer: FirDataFlowAnalyzer = FirDataFlowAnalyzer(this)
override val dataFlowAnalyzer: FirDataFlowAnalyzer<*> = FirDataFlowAnalyzer.createFirDataFlowAnalyzer(this)
override val syntheticCallGenerator: FirSyntheticCallGenerator = FirSyntheticCallGenerator(this, callCompleter)
override val integerLiteralTypeApproximator: IntegerLiteralTypeApproximationTransformer =
IntegerLiteralTypeApproximationTransformer(symbolProvider, inferenceComponents.ctx)
+145 -141
View File
@@ -45,7 +45,9 @@ digraph jumps_kt {
22 [label="Variable declaration: lval y: R|kotlin/Int|"];
23 [label="Access variable R|<local>/y|"];
24 [label="Function call: R|<local>/y|.R|kotlin/Int.inc|()"];
25 [label="Exit function test_1" style="filled" fillcolor=red];
25 [label="Access variable R|<local>/x|"];
26 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
27 [label="Exit function test_1" style="filled" fillcolor=red];
}
0 -> {1};
@@ -65,7 +67,7 @@ digraph jumps_kt {
14 -> {15};
15 -> {16};
16 -> {17};
17 -> {25};
17 -> {27};
17 -> {18} [style=dotted];
18 -> {19} [style=dotted];
19 -> {20} [style=dotted];
@@ -74,66 +76,66 @@ digraph jumps_kt {
22 -> {23};
23 -> {24};
24 -> {25};
25 -> {26};
26 -> {27};
subgraph cluster_6 {
color=red
26 [label="Enter function test_2" style="filled" fillcolor=red];
28 [label="Enter function test_2" style="filled" fillcolor=red];
subgraph cluster_7 {
color=blue
27 [label="Enter when"];
29 [label="Enter when"];
subgraph cluster_8 {
color=blue
28 [label="Enter when branch condition "];
29 [label="Access variable R|<local>/x|"];
30 [label="Const: Null(null)"];
31 [label="Operator =="];
32 [label="Exit when branch condition"];
30 [label="Enter when branch condition "];
31 [label="Access variable R|<local>/x|"];
32 [label="Const: Null(null)"];
33 [label="Operator =="];
34 [label="Exit when branch condition"];
}
subgraph cluster_9 {
color=blue
33 [label="Enter when branch condition else"];
34 [label="Exit when branch condition"];
35 [label="Enter when branch condition else"];
36 [label="Exit when branch condition"];
}
35 [label="Enter when branch result"];
37 [label="Enter when branch result"];
subgraph cluster_10 {
color=blue
36 [label="Enter block"];
37 [label="Access variable R|<local>/x|"];
38 [label="Exit block"];
38 [label="Enter block"];
39 [label="Access variable R|<local>/x|"];
40 [label="Exit block"];
}
39 [label="Exit when branch result"];
40 [label="Enter when branch result"];
41 [label="Exit when branch result"];
42 [label="Enter when branch result"];
subgraph cluster_11 {
color=blue
41 [label="Enter block"];
42 [label="Access variable R|<local>/x|"];
43 [label="Exit block"];
43 [label="Enter block"];
44 [label="Access variable R|<local>/x|"];
45 [label="Exit block"];
}
44 [label="Exit when branch result"];
45 [label="Exit when"];
46 [label="Exit when branch result"];
47 [label="Exit when"];
}
46 [label="Variable declaration: lval y: R|kotlin/Int?|"];
47 [label="Access variable R|<local>/y|"];
48 [label="Function call: R|<local>/y|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
49 [label="Exit function test_2" style="filled" fillcolor=red];
48 [label="Variable declaration: lval y: R|kotlin/Int?|"];
49 [label="Access variable R|<local>/y|"];
50 [label="Function call: R|<local>/y|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
51 [label="Exit function test_2" style="filled" fillcolor=red];
}
26 -> {27};
27 -> {28};
28 -> {29};
29 -> {30};
30 -> {31};
31 -> {32};
32 -> {40 33};
32 -> {33};
33 -> {34};
34 -> {35};
34 -> {42 35};
35 -> {36};
36 -> {37};
37 -> {38};
38 -> {39};
39 -> {45};
39 -> {40};
40 -> {41};
41 -> {42};
41 -> {47};
42 -> {43};
43 -> {44};
44 -> {45};
@@ -141,219 +143,221 @@ digraph jumps_kt {
46 -> {47};
47 -> {48};
48 -> {49};
49 -> {50};
50 -> {51};
subgraph cluster_12 {
color=red
50 [label="Enter function test_3" style="filled" fillcolor=red];
52 [label="Enter function test_3" style="filled" fillcolor=red];
subgraph cluster_13 {
color=blue
51 [label="Enter while loop"];
53 [label="Enter while loop"];
subgraph cluster_14 {
color=blue
52 [label="Enter loop condition"];
53 [label="Const: Boolean(true)"];
54 [label="Exit loop condition"];
54 [label="Enter loop condition"];
55 [label="Const: Boolean(true)"];
56 [label="Exit loop condition"];
}
subgraph cluster_15 {
color=blue
55 [label="Enter loop block"];
57 [label="Enter loop block"];
subgraph cluster_16 {
color=blue
56 [label="Enter block"];
57 [label="Access variable R|<local>/x|"];
58 [label="Type operator: x as Int"];
59 [label="Jump: break@@@[Boolean(true)] "];
60 [label="Stub" style="filled" fillcolor=gray];
61 [label="Exit block" style="filled" fillcolor=gray];
58 [label="Enter block"];
59 [label="Access variable R|<local>/x|"];
60 [label="Type operator: x as Int"];
61 [label="Jump: break@@@[Boolean(true)] "];
62 [label="Stub" style="filled" fillcolor=gray];
63 [label="Exit block" style="filled" fillcolor=gray];
}
62 [label="Exit loop block" style="filled" fillcolor=gray];
64 [label="Exit loop block" style="filled" fillcolor=gray];
}
63 [label="Stub" style="filled" fillcolor=gray];
64 [label="Exit whileloop"];
65 [label="Stub" style="filled" fillcolor=gray];
66 [label="Exit whileloop"];
}
65 [label="Access variable R|<local>/x|"];
66 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
67 [label="Exit function test_3" style="filled" fillcolor=red];
67 [label="Access variable R|<local>/x|"];
68 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
69 [label="Exit function test_3" style="filled" fillcolor=red];
}
50 -> {51};
51 -> {52};
52 -> {53};
53 -> {54};
54 -> {55};
54 -> {63} [style=dotted];
55 -> {56};
56 -> {57};
56 -> {65} [style=dotted];
57 -> {58};
58 -> {59};
59 -> {64};
59 -> {60} [style=dotted];
60 -> {61} [style=dotted];
59 -> {60};
60 -> {61};
61 -> {66};
61 -> {62} [style=dotted];
62 -> {52} [style=dotted];
62 -> {63} [style=dotted];
63 -> {64} [style=dotted];
64 -> {65};
65 -> {66};
64 -> {54} [style=dotted];
65 -> {66} [style=dotted];
66 -> {67};
67 -> {68};
68 -> {69};
subgraph cluster_17 {
color=red
68 [label="Enter function test_4" style="filled" fillcolor=red];
70 [label="Enter function test_4" style="filled" fillcolor=red];
subgraph cluster_18 {
color=blue
69 [label="Enter do-while loop"];
71 [label="Enter do-while loop"];
subgraph cluster_19 {
color=blue
70 [label="Enter loop block"];
72 [label="Enter loop block"];
subgraph cluster_20 {
color=blue
71 [label="Enter block"];
72 [label="Access variable R|<local>/x|"];
73 [label="Type operator: x as Int"];
74 [label="Jump: break@@@[Boolean(true)] "];
75 [label="Stub" style="filled" fillcolor=gray];
76 [label="Exit block" style="filled" fillcolor=gray];
73 [label="Enter block"];
74 [label="Access variable R|<local>/x|"];
75 [label="Type operator: x as Int"];
76 [label="Jump: break@@@[Boolean(true)] "];
77 [label="Stub" style="filled" fillcolor=gray];
78 [label="Exit block" style="filled" fillcolor=gray];
}
77 [label="Exit loop block" style="filled" fillcolor=gray];
79 [label="Exit loop block" style="filled" fillcolor=gray];
}
subgraph cluster_21 {
color=blue
78 [label="Enter loop condition" style="filled" fillcolor=gray];
79 [label="Const: Boolean(true)" style="filled" fillcolor=gray];
80 [label="Exit loop condition" style="filled" fillcolor=gray];
80 [label="Enter loop condition" style="filled" fillcolor=gray];
81 [label="Const: Boolean(true)" style="filled" fillcolor=gray];
82 [label="Exit loop condition" style="filled" fillcolor=gray];
}
81 [label="Stub" style="filled" fillcolor=gray];
82 [label="Exit do-whileloop"];
83 [label="Stub" style="filled" fillcolor=gray];
84 [label="Exit do-whileloop"];
}
83 [label="Access variable R|<local>/x|"];
84 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
85 [label="Exit function test_4" style="filled" fillcolor=red];
85 [label="Access variable R|<local>/x|"];
86 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
87 [label="Exit function test_4" style="filled" fillcolor=red];
}
68 -> {69};
69 -> {70};
70 -> {71};
71 -> {72};
72 -> {73};
73 -> {74};
74 -> {82};
74 -> {75} [style=dotted];
75 -> {76} [style=dotted];
74 -> {75};
75 -> {76};
76 -> {84};
76 -> {77} [style=dotted];
77 -> {78} [style=dotted];
78 -> {79} [style=dotted];
79 -> {80} [style=dotted];
80 -> {70 81} [style=dotted];
80 -> {81} [style=dotted];
81 -> {82} [style=dotted];
82 -> {83};
83 -> {84};
82 -> {72 83} [style=dotted];
83 -> {84} [style=dotted];
84 -> {85};
85 -> {86};
86 -> {87};
subgraph cluster_22 {
color=red
86 [label="Enter function test_5" style="filled" fillcolor=red];
88 [label="Enter function test_5" style="filled" fillcolor=red];
subgraph cluster_23 {
color=blue
87 [label="Enter while loop"];
89 [label="Enter while loop"];
subgraph cluster_24 {
color=blue
88 [label="Enter loop condition"];
89 [label="Access variable R|<local>/b|"];
90 [label="Exit loop condition"];
90 [label="Enter loop condition"];
91 [label="Access variable R|<local>/b|"];
92 [label="Exit loop condition"];
}
subgraph cluster_25 {
color=blue
91 [label="Enter loop block"];
93 [label="Enter loop block"];
subgraph cluster_26 {
color=blue
92 [label="Enter block"];
94 [label="Enter block"];
subgraph cluster_27 {
color=blue
93 [label="Enter when"];
95 [label="Enter when"];
subgraph cluster_28 {
color=blue
94 [label="Enter when branch condition "];
95 [label="Access variable R|<local>/b|"];
96 [label="Exit when branch condition"];
96 [label="Enter when branch condition "];
97 [label="Access variable R|<local>/b|"];
98 [label="Exit when branch condition"];
}
97 [label="Synthetic else branch"];
98 [label="Enter when branch result"];
99 [label="Synthetic else branch"];
100 [label="Enter when branch result"];
subgraph cluster_29 {
color=blue
99 [label="Enter block"];
100 [label="Jump: continue@@@[R|<local>/b|] "];
101 [label="Stub" style="filled" fillcolor=gray];
102 [label="Exit block" style="filled" fillcolor=gray];
101 [label="Enter block"];
102 [label="Jump: continue@@@[R|<local>/b|] "];
103 [label="Stub" style="filled" fillcolor=gray];
104 [label="Exit block" style="filled" fillcolor=gray];
}
103 [label="Exit when branch result" style="filled" fillcolor=gray];
104 [label="Exit when"];
105 [label="Exit when branch result" style="filled" fillcolor=gray];
106 [label="Exit when"];
}
105 [label="Exit block"];
107 [label="Exit block"];
}
106 [label="Exit loop block"];
108 [label="Exit loop block"];
}
107 [label="Exit whileloop"];
109 [label="Exit whileloop"];
}
108 [label="Exit function test_5" style="filled" fillcolor=red];
110 [label="Exit function test_5" style="filled" fillcolor=red];
}
86 -> {87};
87 -> {88};
88 -> {89};
89 -> {90};
90 -> {107 91};
90 -> {91};
91 -> {92};
92 -> {93};
92 -> {109 93};
93 -> {94};
94 -> {95};
95 -> {96};
96 -> {98 97};
97 -> {104};
98 -> {99};
99 -> {100};
100 -> {87};
100 -> {101} [style=dotted];
101 -> {102} [style=dotted];
96 -> {97};
97 -> {98};
98 -> {100 99};
99 -> {106};
100 -> {101};
101 -> {102};
102 -> {89};
102 -> {103} [style=dotted];
103 -> {104} [style=dotted];
104 -> {105};
105 -> {106};
106 -> {88};
104 -> {105} [style=dotted];
105 -> {106} [style=dotted];
106 -> {107};
107 -> {108};
108 -> {90};
109 -> {110};
subgraph cluster_30 {
color=red
109 [label="Enter function run" style="filled" fillcolor=red];
110 [label="Function call: R|<local>/block|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Unit|>|()"];
111 [label="Exit function run" style="filled" fillcolor=red];
111 [label="Enter function run" style="filled" fillcolor=red];
112 [label="Function call: R|<local>/block|.R|FakeOverride<kotlin/Function0.invoke: R|kotlin/Unit|>|()"];
113 [label="Exit function run" style="filled" fillcolor=red];
}
109 -> {110};
110 -> {111};
111 -> {112};
112 -> {113};
subgraph cluster_31 {
color=red
112 [label="Enter function test_6" style="filled" fillcolor=red];
114 [label="Enter function test_6" style="filled" fillcolor=red];
subgraph cluster_32 {
color=blue
113 [label="Enter function anonymousFunction"];
114 [label="Jump: ^@run Unit"];
115 [label="Stub" style="filled" fillcolor=gray];
116 [label="Exit function anonymousFunction"];
115 [label="Enter function anonymousFunction"];
116 [label="Jump: ^@run Unit"];
117 [label="Stub" style="filled" fillcolor=gray];
118 [label="Exit function anonymousFunction"];
}
117 [label="Function call: R|/run|(<L> = run@fun <anonymous>(): R|kotlin/Unit| <kind=UNKNOWN> {
119 [label="Function call: R|/run|(<L> = run@fun <anonymous>(): R|kotlin/Unit| <kind=UNKNOWN> {
^@run Unit
}
)"];
118 [label="Exit function test_6" style="filled" fillcolor=red];
120 [label="Exit function test_6" style="filled" fillcolor=red];
}
112 -> {113};
113 -> {116 114};
114 -> {116};
114 -> {115} [style=dotted];
115 -> {116} [style=dotted];
116 -> {113 117};
117 -> {118};
114 -> {115};
115 -> {118 116};
116 -> {118};
116 -> {117} [style=dotted];
117 -> {118} [style=dotted];
118 -> {115 119};
119 -> {120};
}
+1
View File
@@ -5,6 +5,7 @@ fun test_1(x: Int?) {
x
}
y.inc()
x.inc()
}
fun test_2(x: Int?) {
+1
View File
@@ -10,6 +10,7 @@ FILE: jumps.kt
}
R|<local>/y|.R|kotlin/Int.inc|()
R|<local>/x|.R|kotlin/Int.inc|()
}
public final fun test_2(x: R|kotlin/Int?|): R|kotlin/Unit| {
lval y: R|kotlin/Int?| = when () {
@@ -5,222 +5,459 @@ digraph anotherBoundSmartcasts_kt {
subgraph cluster_0 {
color=red
0 [label="Enter function foo" style="filled" fillcolor=red];
1 [label="Exit function foo" style="filled" fillcolor=red];
0 [label="Enter function <init>" style="filled" fillcolor=red];
1 [label="Exit function <init>" style="filled" fillcolor=red];
}
0 -> {1};
subgraph cluster_1 {
color=red
2 [label="Enter function getter" style="filled" fillcolor=red];
3 [label="Exit function getter" style="filled" fillcolor=red];
2 [label="Enter function foo" style="filled" fillcolor=red];
3 [label="Const: Int(1)"];
4 [label="Jump: ^foo Int(1)"];
5 [label="Stub" style="filled" fillcolor=gray];
6 [label="Exit function foo" style="filled" fillcolor=red];
}
2 -> {3};
3 -> {4};
4 -> {6};
4 -> {5} [style=dotted];
5 -> {6} [style=dotted];
subgraph cluster_2 {
color=red
4 [label="Enter property" style="filled" fillcolor=red];
5 [label="Exit property" style="filled" fillcolor=red];
7 [label="Enter function getter" style="filled" fillcolor=red];
8 [label="Exit function getter" style="filled" fillcolor=red];
}
4 -> {5};
7 -> {8};
subgraph cluster_3 {
color=red
6 [label="Enter function bar" style="filled" fillcolor=red];
7 [label="Exit function bar" style="filled" fillcolor=red];
9 [label="Enter property" style="filled" fillcolor=red];
10 [label="Const: Int(1)"];
11 [label="Exit property" style="filled" fillcolor=red];
}
6 -> {7};
9 -> {10};
10 -> {11};
subgraph cluster_4 {
color=red
8 [label="Enter function test_1" style="filled" fillcolor=red];
9 [label="Access variable R|<local>/a|"];
10 [label="Enter safe call"];
11 [label="Access variable R|/A.x|"];
12 [label="Exit safe call"];
13 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_5 {
color=blue
14 [label="Enter when"];
subgraph cluster_6 {
color=blue
15 [label="Enter when branch condition "];
16 [label="Access variable R|<local>/x|"];
17 [label="Const: Null(null)"];
18 [label="Operator !="];
19 [label="Exit when branch condition"];
}
20 [label="Synthetic else branch"];
21 [label="Enter when branch result"];
subgraph cluster_7 {
color=blue
22 [label="Enter block"];
23 [label="Access variable R|<local>/a|"];
24 [label="Function call: R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()"];
25 [label="Exit block"];
}
26 [label="Exit when branch result"];
27 [label="Exit when"];
}
28 [label="Exit function test_1" style="filled" fillcolor=red];
12 [label="Enter function bar" style="filled" fillcolor=red];
13 [label="Exit function bar" style="filled" fillcolor=red];
}
8 -> {9};
9 -> {10 12};
10 -> {11};
11 -> {12};
12 -> {13};
13 -> {14};
subgraph cluster_5 {
color=red
14 [label="Enter function test_1" style="filled" fillcolor=red];
15 [label="Access variable R|<local>/a|"];
16 [label="Enter safe call"];
17 [label="Access variable R|/A.x|"];
18 [label="Exit safe call"];
19 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_6 {
color=blue
20 [label="Enter when"];
subgraph cluster_7 {
color=blue
21 [label="Enter when branch condition "];
22 [label="Access variable R|<local>/x|"];
23 [label="Const: Null(null)"];
24 [label="Operator !="];
25 [label="Exit when branch condition"];
}
26 [label="Synthetic else branch"];
27 [label="Enter when branch result"];
subgraph cluster_8 {
color=blue
28 [label="Enter block"];
29 [label="Access variable R|<local>/a|"];
30 [label="Function call: R|<local>/a|.R|/A.bar|()"];
31 [label="Exit block"];
}
32 [label="Exit when branch result"];
33 [label="Exit when"];
}
34 [label="Exit function test_1" style="filled" fillcolor=red];
}
14 -> {15};
15 -> {16};
15 -> {16 18};
16 -> {17};
17 -> {18};
18 -> {19};
19 -> {21 20};
20 -> {27};
19 -> {20};
20 -> {21};
21 -> {22};
22 -> {23};
23 -> {24};
24 -> {25};
25 -> {26};
26 -> {27};
25 -> {27 26};
26 -> {33};
27 -> {28};
subgraph cluster_8 {
color=red
29 [label="Enter function test_2" style="filled" fillcolor=red];
30 [label="Access variable R|<local>/a|"];
31 [label="Enter safe call"];
32 [label="Function call: R|<local>/a|?.R|/A.foo|()"];
33 [label="Exit safe call"];
34 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_9 {
color=blue
35 [label="Enter when"];
subgraph cluster_10 {
color=blue
36 [label="Enter when branch condition "];
37 [label="Access variable R|<local>/x|"];
38 [label="Const: Null(null)"];
39 [label="Operator !="];
40 [label="Exit when branch condition"];
}
41 [label="Synthetic else branch"];
42 [label="Enter when branch result"];
subgraph cluster_11 {
color=blue
43 [label="Enter block"];
44 [label="Access variable R|<local>/a|"];
45 [label="Function call: R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()"];
46 [label="Exit block"];
}
47 [label="Exit when branch result"];
48 [label="Exit when"];
}
49 [label="Exit function test_2" style="filled" fillcolor=red];
}
28 -> {29};
29 -> {30};
30 -> {31 33};
30 -> {31};
31 -> {32};
32 -> {33};
33 -> {34};
34 -> {35};
subgraph cluster_9 {
color=red
35 [label="Enter function test_2" style="filled" fillcolor=red];
36 [label="Access variable R|<local>/a|"];
37 [label="Enter safe call"];
38 [label="Function call: R|<local>/a|?.R|/A.foo|()"];
39 [label="Exit safe call"];
40 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_10 {
color=blue
41 [label="Enter when"];
subgraph cluster_11 {
color=blue
42 [label="Enter when branch condition "];
43 [label="Access variable R|<local>/x|"];
44 [label="Const: Null(null)"];
45 [label="Operator !="];
46 [label="Exit when branch condition"];
}
47 [label="Synthetic else branch"];
48 [label="Enter when branch result"];
subgraph cluster_12 {
color=blue
49 [label="Enter block"];
50 [label="Access variable R|<local>/a|"];
51 [label="Function call: R|<local>/a|.R|/A.bar|()"];
52 [label="Exit block"];
}
53 [label="Exit when branch result"];
54 [label="Exit when"];
}
55 [label="Exit function test_2" style="filled" fillcolor=red];
}
35 -> {36};
36 -> {37};
36 -> {37 39};
37 -> {38};
38 -> {39};
39 -> {40};
40 -> {42 41};
41 -> {48};
40 -> {41};
41 -> {42};
42 -> {43};
43 -> {44};
44 -> {45};
45 -> {46};
46 -> {47};
47 -> {48};
46 -> {48 47};
47 -> {54};
48 -> {49};
subgraph cluster_12 {
color=red
50 [label="Enter function test_3" style="filled" fillcolor=red];
subgraph cluster_13 {
color=blue
51 [label="Enter when"];
52 [label="Access variable R|<local>/x|"];
53 [label="Type operator: x as? A"];
54 [label="Variable declaration: lval <elvis>: R|A?|"];
subgraph cluster_14 {
color=blue
55 [label="Enter when branch condition "];
56 [label="Const: Null(null)"];
57 [label="Operator =="];
58 [label="Exit when branch condition"];
}
subgraph cluster_15 {
color=blue
59 [label="Enter when branch condition else"];
60 [label="Exit when branch condition"];
}
61 [label="Enter when branch result"];
subgraph cluster_16 {
color=blue
62 [label="Enter block"];
63 [label="Access variable R|<local>/<elvis>|"];
64 [label="Exit block"];
}
65 [label="Exit when branch result"];
66 [label="Enter when branch result"];
subgraph cluster_17 {
color=blue
67 [label="Enter block"];
68 [label="Jump: ^test_3 Unit"];
69 [label="Stub" style="filled" fillcolor=gray];
70 [label="Exit block" style="filled" fillcolor=gray];
}
71 [label="Exit when branch result" style="filled" fillcolor=gray];
72 [label="Exit when"];
}
73 [label="Variable declaration: lval a: R|A|"];
74 [label="Access variable R|<local>/a|"];
75 [label="Function call: R|<local>/a|.R|/A.foo|()"];
76 [label="Access variable R|<local>/x|"];
77 [label="Function call: R|<local>/x|.<Unresolved name: foo>#()"];
78 [label="Exit function test_3" style="filled" fillcolor=red];
}
49 -> {50};
50 -> {51};
51 -> {52};
52 -> {53};
53 -> {54};
54 -> {55};
55 -> {56};
subgraph cluster_13 {
color=red
56 [label="Enter function test_3" style="filled" fillcolor=red];
subgraph cluster_14 {
color=blue
57 [label="Enter when"];
58 [label="Access variable R|<local>/x|"];
59 [label="Type operator: x as? A"];
60 [label="Variable declaration: lval <elvis>: R|A?|"];
subgraph cluster_15 {
color=blue
61 [label="Enter when branch condition "];
62 [label="Const: Null(null)"];
63 [label="Operator =="];
64 [label="Exit when branch condition"];
}
subgraph cluster_16 {
color=blue
65 [label="Enter when branch condition else"];
66 [label="Exit when branch condition"];
}
67 [label="Enter when branch result"];
subgraph cluster_17 {
color=blue
68 [label="Enter block"];
69 [label="Access variable R|<local>/<elvis>|"];
70 [label="Exit block"];
}
71 [label="Exit when branch result"];
72 [label="Enter when branch result"];
subgraph cluster_18 {
color=blue
73 [label="Enter block"];
74 [label="Jump: ^test_3 Unit"];
75 [label="Stub" style="filled" fillcolor=gray];
76 [label="Exit block" style="filled" fillcolor=gray];
}
77 [label="Exit when branch result" style="filled" fillcolor=gray];
78 [label="Exit when"];
}
79 [label="Variable declaration: lval a: R|A|"];
80 [label="Access variable R|<local>/a|"];
81 [label="Function call: R|<local>/a|.R|/A.foo|()"];
82 [label="Access variable R|<local>/x|"];
83 [label="Function call: R|<local>/x|.R|/A.foo|()"];
84 [label="Exit function test_3" style="filled" fillcolor=red];
}
56 -> {57};
57 -> {58};
58 -> {66 59};
58 -> {59};
59 -> {60};
60 -> {61};
61 -> {62};
62 -> {63};
63 -> {64};
64 -> {65};
65 -> {72};
64 -> {72 65};
65 -> {66};
66 -> {67};
67 -> {68};
68 -> {78};
68 -> {69} [style=dotted];
69 -> {70} [style=dotted];
70 -> {71} [style=dotted];
71 -> {72} [style=dotted];
68 -> {69};
69 -> {70};
70 -> {71};
71 -> {78};
72 -> {73};
73 -> {74};
74 -> {75};
75 -> {76};
76 -> {77};
77 -> {78};
74 -> {84};
74 -> {75} [style=dotted];
75 -> {76} [style=dotted];
76 -> {77} [style=dotted];
77 -> {78} [style=dotted];
78 -> {79};
79 -> {80};
80 -> {81};
81 -> {82};
82 -> {83};
83 -> {84};
subgraph cluster_19 {
color=red
85 [label="Enter function foo" style="filled" fillcolor=red];
86 [label="Exit function foo" style="filled" fillcolor=red];
}
85 -> {86};
subgraph cluster_20 {
color=red
87 [label="Enter function getter" style="filled" fillcolor=red];
88 [label="Exit function getter" style="filled" fillcolor=red];
}
87 -> {88};
subgraph cluster_21 {
color=red
89 [label="Enter property" style="filled" fillcolor=red];
90 [label="Exit property" style="filled" fillcolor=red];
}
89 -> {90};
subgraph cluster_22 {
color=red
91 [label="Enter function bar" style="filled" fillcolor=red];
92 [label="Exit function bar" style="filled" fillcolor=red];
}
91 -> {92};
subgraph cluster_23 {
color=red
93 [label="Enter function test_1" style="filled" fillcolor=red];
94 [label="Access variable R|<local>/a|"];
95 [label="Enter safe call"];
96 [label="Access variable R|/B.x|"];
97 [label="Exit safe call"];
98 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_24 {
color=blue
99 [label="Enter when"];
subgraph cluster_25 {
color=blue
100 [label="Enter when branch condition "];
101 [label="Access variable R|<local>/x|"];
102 [label="Const: Null(null)"];
103 [label="Operator !="];
104 [label="Exit when branch condition"];
}
105 [label="Synthetic else branch"];
106 [label="Enter when branch result"];
subgraph cluster_26 {
color=blue
107 [label="Enter block"];
108 [label="Access variable R|<local>/a|"];
109 [label="Function call: R|<local>/a|.R|/B.bar|()"];
110 [label="Exit block"];
}
111 [label="Exit when branch result"];
112 [label="Exit when"];
}
113 [label="Exit function test_1" style="filled" fillcolor=red];
}
93 -> {94};
94 -> {95 97};
95 -> {96};
96 -> {97};
97 -> {98};
98 -> {99};
99 -> {100};
100 -> {101};
101 -> {102};
102 -> {103};
103 -> {104};
104 -> {106 105};
105 -> {112};
106 -> {107};
107 -> {108};
108 -> {109};
109 -> {110};
110 -> {111};
111 -> {112};
112 -> {113};
subgraph cluster_27 {
color=red
114 [label="Enter function test_2" style="filled" fillcolor=red];
115 [label="Access variable R|<local>/a|"];
116 [label="Enter safe call"];
117 [label="Function call: R|<local>/a|?.R|/B.foo|()"];
118 [label="Exit safe call"];
119 [label="Variable declaration: lval x: R|kotlin/Int?|"];
subgraph cluster_28 {
color=blue
120 [label="Enter when"];
subgraph cluster_29 {
color=blue
121 [label="Enter when branch condition "];
122 [label="Access variable R|<local>/x|"];
123 [label="Const: Null(null)"];
124 [label="Operator !="];
125 [label="Exit when branch condition"];
}
126 [label="Synthetic else branch"];
127 [label="Enter when branch result"];
subgraph cluster_30 {
color=blue
128 [label="Enter block"];
129 [label="Access variable R|<local>/a|"];
130 [label="Function call: R|<local>/a|.R|/B.bar|()"];
131 [label="Exit block"];
}
132 [label="Exit when branch result"];
133 [label="Exit when"];
}
134 [label="Exit function test_2" style="filled" fillcolor=red];
}
114 -> {115};
115 -> {116 118};
116 -> {117};
117 -> {118};
118 -> {119};
119 -> {120};
120 -> {121};
121 -> {122};
122 -> {123};
123 -> {124};
124 -> {125};
125 -> {127 126};
126 -> {133};
127 -> {128};
128 -> {129};
129 -> {130};
130 -> {131};
131 -> {132};
132 -> {133};
133 -> {134};
subgraph cluster_31 {
color=red
135 [label="Enter function test_3" style="filled" fillcolor=red];
subgraph cluster_32 {
color=blue
136 [label="Enter when"];
137 [label="Access variable R|<local>/x|"];
138 [label="Type operator: x as? B"];
139 [label="Variable declaration: lval <elvis>: R|B?|"];
subgraph cluster_33 {
color=blue
140 [label="Enter when branch condition "];
141 [label="Const: Null(null)"];
142 [label="Operator =="];
143 [label="Exit when branch condition"];
}
subgraph cluster_34 {
color=blue
144 [label="Enter when branch condition else"];
145 [label="Exit when branch condition"];
}
146 [label="Enter when branch result"];
subgraph cluster_35 {
color=blue
147 [label="Enter block"];
148 [label="Access variable R|<local>/<elvis>|"];
149 [label="Exit block"];
}
150 [label="Exit when branch result"];
151 [label="Enter when branch result"];
subgraph cluster_36 {
color=blue
152 [label="Enter block"];
153 [label="Jump: ^test_3 Unit"];
154 [label="Stub" style="filled" fillcolor=gray];
155 [label="Exit block" style="filled" fillcolor=gray];
}
156 [label="Exit when branch result" style="filled" fillcolor=gray];
157 [label="Exit when"];
}
158 [label="Variable declaration: lval a: R|B|"];
159 [label="Access variable R|<local>/a|"];
160 [label="Function call: R|<local>/a|.R|/B.foo|()"];
161 [label="Access variable R|<local>/x|"];
162 [label="Function call: R|<local>/x|.R|/B.foo|()"];
163 [label="Exit function test_3" style="filled" fillcolor=red];
}
135 -> {136};
136 -> {137};
137 -> {138};
138 -> {139};
139 -> {140};
140 -> {141};
141 -> {142};
142 -> {143};
143 -> {151 144};
144 -> {145};
145 -> {146};
146 -> {147};
147 -> {148};
148 -> {149};
149 -> {150};
150 -> {157};
151 -> {152};
152 -> {153};
153 -> {163};
153 -> {154} [style=dotted];
154 -> {155} [style=dotted];
155 -> {156} [style=dotted];
156 -> {157} [style=dotted];
157 -> {158};
158 -> {159};
159 -> {160};
160 -> {161};
161 -> {162};
162 -> {163};
}
@@ -1,4 +1,36 @@
interface A {
// ----------------- Stable -----------------
class A {
fun foo(): Int = 1
val x: Int = 1
fun bar() {}
}
fun test_1(a: A?) {
val x = a?.x
if (x != null) {
a.bar() // Should be OK
}
}
fun test_2(a: A?) {
val x = a?.foo()
if (x != null) {
a.bar() // Should be OK
}
}
fun test_3(x: Any?) {
val a = x as? A ?: return
a.foo() // Should be OK
x.foo() // Should be OK
}
// ----------------- Unstable -----------------
interface B {
fun foo(): Int
val x: Int
@@ -6,24 +38,22 @@ interface A {
fun bar()
}
fun test_1(a: A?) {
fun test_1(a: B?) {
val x = a?.x
if (x != null) {
a.<!INAPPLICABLE_CANDIDATE!>bar<!>()
a.bar() // Should be OK
}
}
fun test_2(a: A?) {
fun test_2(a: B?) {
val x = a?.foo()
if (x != null) {
a.<!INAPPLICABLE_CANDIDATE!>bar<!>()
a.bar() // Should be OK
}
}
// ----------------------------------------------------------------
fun test_3(x: Any?) {
val a = x as? A ?: return
a.foo()
x.<!UNRESOLVED_REFERENCE!>foo<!>()
val a = x as? B ?: return
a.foo() // Should be OK
x.foo() // Should be OK
}
@@ -1,18 +1,25 @@
FILE: anotherBoundSmartcasts.kt
public abstract interface A : R|kotlin/Any| {
public abstract fun foo(): R|kotlin/Int|
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
public abstract val x: R|kotlin/Int|
public final fun foo(): R|kotlin/Int| {
^foo Int(1)
}
public final val x: R|kotlin/Int| = Int(1)
public get(): R|kotlin/Int|
public abstract fun bar(): R|kotlin/Unit|
public final fun bar(): R|kotlin/Unit| {
}
}
public final fun test_1(a: R|A?|): R|kotlin/Unit| {
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/A.x|
when () {
!=(R|<local>/x|, Null(null)) -> {
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()
R|<local>/a|.R|/A.bar|()
}
}
@@ -21,7 +28,7 @@ FILE: anotherBoundSmartcasts.kt
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/A.foo|()
when () {
!=(R|<local>/x|, Null(null)) -> {
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.bar]>#()
R|<local>/a|.R|/A.bar|()
}
}
@@ -37,5 +44,45 @@ FILE: anotherBoundSmartcasts.kt
}
R|<local>/a|.R|/A.foo|()
R|<local>/x|.<Unresolved name: foo>#()
R|<local>/x|.R|/A.foo|()
}
public abstract interface B : R|kotlin/Any| {
public abstract fun foo(): R|kotlin/Int|
public abstract val x: R|kotlin/Int|
public get(): R|kotlin/Int|
public abstract fun bar(): R|kotlin/Unit|
}
public final fun test_1(a: R|B?|): R|kotlin/Unit| {
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/B.x|
when () {
!=(R|<local>/x|, Null(null)) -> {
R|<local>/a|.R|/B.bar|()
}
}
}
public final fun test_2(a: R|B?|): R|kotlin/Unit| {
lval x: R|kotlin/Int?| = R|<local>/a|?.R|/B.foo|()
when () {
!=(R|<local>/x|, Null(null)) -> {
R|<local>/a|.R|/B.bar|()
}
}
}
public final fun test_3(x: R|kotlin/Any?|): R|kotlin/Unit| {
lval a: R|B| = when (lval <elvis>: R|B?| = (R|<local>/x| as? R|B|)) {
==($subj$, Null(null)) -> {
^test_3 Unit
}
else -> {
R|<local>/<elvis>|
}
}
R|<local>/a|.R|/B.foo|()
R|<local>/x|.R|/B.foo|()
}
@@ -455,4 +455,49 @@ digraph booleanOperators_kt {
160 -> {161};
161 -> {162};
subgraph cluster_38 {
color=red
163 [label="Enter function test_8" style="filled" fillcolor=red];
subgraph cluster_39 {
color=blue
164 [label="Enter when"];
subgraph cluster_40 {
color=blue
165 [label="Enter when branch condition "];
166 [label="Access variable R|<local>/x|"];
167 [label="Type operator: x !is A"];
168 [label="Function call: (R|<local>/x| !is R|A|).R|kotlin/Boolean.not|()"];
169 [label="Exit when branch condition"];
}
170 [label="Synthetic else branch"];
171 [label="Enter when branch result"];
subgraph cluster_41 {
color=blue
172 [label="Enter block"];
173 [label="Access variable R|<local>/x|"];
174 [label="Function call: R|<local>/x|.R|/A.foo|()"];
175 [label="Exit block"];
}
176 [label="Exit when branch result"];
177 [label="Exit when"];
}
178 [label="Exit function test_8" style="filled" fillcolor=red];
}
163 -> {164};
164 -> {165};
165 -> {166};
166 -> {167};
167 -> {168};
168 -> {169};
169 -> {171 170};
170 -> {177};
171 -> {172};
172 -> {173};
173 -> {174};
174 -> {175};
175 -> {176};
176 -> {177};
177 -> {178};
}
@@ -57,4 +57,10 @@ fun test_7(x: Any) {
if (x is A && x.bool()) {
x.foo()
}
}
fun test_8(x: Any) {
if ((x !is A).not()) {
x.foo()
}
}
@@ -74,3 +74,11 @@ FILE: booleanOperators.kt
}
}
public final fun test_8(x: R|kotlin/Any|): R|kotlin/Unit| {
when () {
(R|<local>/x| !is R|A|).R|kotlin/Boolean.not|() -> {
R|<local>/x|.R|/A.foo|()
}
}
}
@@ -166,13 +166,15 @@ digraph boundSmartcasts_kt {
color=blue
67 [label="Enter block"];
68 [label="Access variable R|<local>/z|"];
69 [label="Function call: R|<local>/z|.<Unresolved name: bar>#()"];
70 [label="Exit block"];
69 [label="Function call: R|<local>/z|.<Unresolved name: foo>#()"];
70 [label="Access variable R|<local>/z|"];
71 [label="Function call: R|<local>/z|.R|/B.bar|()"];
72 [label="Exit block"];
}
71 [label="Exit when branch result"];
72 [label="Exit when"];
73 [label="Exit when branch result"];
74 [label="Exit when"];
}
73 [label="Exit function test_3" style="filled" fillcolor=red];
75 [label="Exit function test_3" style="filled" fillcolor=red];
}
42 -> {43};
@@ -198,7 +200,7 @@ digraph boundSmartcasts_kt {
62 -> {63};
63 -> {64};
64 -> {66 65};
65 -> {72};
65 -> {74};
66 -> {67};
67 -> {68};
68 -> {69};
@@ -206,49 +208,49 @@ digraph boundSmartcasts_kt {
70 -> {71};
71 -> {72};
72 -> {73};
73 -> {74};
74 -> {75};
subgraph cluster_17 {
color=red
74 [label="Enter function test_4" style="filled" fillcolor=red];
75 [label="Const: Int(1)"];
76 [label="Variable declaration: lvar x: R|kotlin/Any|"];
77 [label="Access variable R|<local>/x|"];
78 [label="Type operator: x as Int"];
76 [label="Enter function test_4" style="filled" fillcolor=red];
77 [label="Const: Int(1)"];
78 [label="Variable declaration: lvar x: R|kotlin/Any|"];
79 [label="Access variable R|<local>/x|"];
80 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
81 [label="Access variable R|<local>/y|"];
82 [label="Assignmenet: R|<local>/x|"];
83 [label="Access variable R|<local>/x|"];
84 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
80 [label="Type operator: x as Int"];
81 [label="Access variable R|<local>/x|"];
82 [label="Function call: R|<local>/x|.R|kotlin/Int.inc|()"];
83 [label="Access variable R|<local>/y|"];
84 [label="Assignmenet: R|<local>/x|"];
85 [label="Access variable R|<local>/x|"];
86 [label="Function call: R|<local>/x|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
subgraph cluster_18 {
color=blue
85 [label="Enter when"];
87 [label="Enter when"];
subgraph cluster_19 {
color=blue
86 [label="Enter when branch condition "];
87 [label="Access variable R|<local>/y|"];
88 [label="Type operator: y is A"];
89 [label="Exit when branch condition"];
88 [label="Enter when branch condition "];
89 [label="Access variable R|<local>/y|"];
90 [label="Type operator: y is A"];
91 [label="Exit when branch condition"];
}
90 [label="Synthetic else branch"];
91 [label="Enter when branch result"];
92 [label="Synthetic else branch"];
93 [label="Enter when branch result"];
subgraph cluster_20 {
color=blue
92 [label="Enter block"];
93 [label="Access variable R|<local>/x|"];
94 [label="Function call: R|<local>/x|.<Unresolved name: foo>#()"];
95 [label="Access variable R|<local>/y|"];
96 [label="Function call: R|<local>/y|.R|/A.foo|()"];
97 [label="Exit block"];
94 [label="Enter block"];
95 [label="Access variable R|<local>/x|"];
96 [label="Function call: R|<local>/x|.R|/A.foo|()"];
97 [label="Access variable R|<local>/y|"];
98 [label="Function call: R|<local>/y|.R|/A.foo|()"];
99 [label="Exit block"];
}
98 [label="Exit when branch result"];
99 [label="Exit when"];
100 [label="Exit when branch result"];
101 [label="Exit when"];
}
100 [label="Exit function test_4" style="filled" fillcolor=red];
102 [label="Exit function test_4" style="filled" fillcolor=red];
}
74 -> {75};
75 -> {76};
76 -> {77};
77 -> {78};
78 -> {79};
@@ -262,10 +264,10 @@ digraph boundSmartcasts_kt {
86 -> {87};
87 -> {88};
88 -> {89};
89 -> {91 90};
90 -> {99};
91 -> {92};
92 -> {93};
89 -> {90};
90 -> {91};
91 -> {93 92};
92 -> {101};
93 -> {94};
94 -> {95};
95 -> {96};
@@ -273,5 +275,7 @@ digraph boundSmartcasts_kt {
97 -> {98};
98 -> {99};
99 -> {100};
100 -> {101};
101 -> {102};
}
@@ -29,7 +29,8 @@ fun test_3(x: Any, y: Any) {
}
z = y
if (y is B) {
z.<!UNRESOLVED_REFERENCE!>bar<!>()
z.<!UNRESOLVED_REFERENCE!>foo<!>()
z.bar()
}
}
@@ -38,9 +39,9 @@ fun test_4(y: Any) {
x as Int
x.inc()
x = y
x.inc()
x.<!AMBIGUITY!>inc<!>()
if (y is A) {
x.<!UNRESOLVED_REFERENCE!>foo<!>()
x.foo()
y.foo()
}
}
@@ -38,7 +38,8 @@ FILE: boundSmartcasts.kt
R|<local>/z| = R|<local>/y|
when () {
(R|<local>/y| is R|B|) -> {
R|<local>/z|.<Unresolved name: bar>#()
R|<local>/z|.<Unresolved name: foo>#()
R|<local>/z|.R|/B.bar|()
}
}
@@ -48,10 +49,10 @@ FILE: boundSmartcasts.kt
(R|<local>/x| as R|kotlin/Int|)
R|<local>/x|.R|kotlin/Int.inc|()
R|<local>/x| = R|<local>/y|
R|<local>/x|.R|kotlin/Int.inc|()
R|<local>/x|.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()
when () {
(R|<local>/y| is R|A|) -> {
R|<local>/x|.<Unresolved name: foo>#()
R|<local>/x|.R|/A.foo|()
R|<local>/y|.R|/A.foo|()
}
}
@@ -164,119 +164,88 @@ digraph notBoundSmartcasts_kt {
subgraph cluster_12 {
color=red
57 [label="Enter function test_2" style="filled" fillcolor=red];
subgraph cluster_13 {
color=blue
58 [label="Enter when"];
59 [label="Access variable R|<local>/d1|"];
60 [label="Access variable R|/D.any|"];
61 [label="Variable declaration: lval <elvis>: R|kotlin/Any?|"];
subgraph cluster_14 {
color=blue
62 [label="Enter when branch condition "];
63 [label="Const: Null(null)"];
64 [label="Operator =="];
65 [label="Exit when branch condition"];
}
subgraph cluster_15 {
color=blue
66 [label="Enter when branch condition else"];
67 [label="Exit when branch condition"];
}
68 [label="Enter when branch result"];
subgraph cluster_16 {
color=blue
69 [label="Enter block"];
70 [label="Access variable R|<local>/<elvis>|"];
71 [label="Exit block"];
}
72 [label="Exit when branch result"];
73 [label="Enter when branch result"];
subgraph cluster_17 {
color=blue
74 [label="Enter block"];
75 [label="Jump: ^test_2 Unit"];
76 [label="Stub" style="filled" fillcolor=gray];
77 [label="Exit block" style="filled" fillcolor=gray];
}
78 [label="Exit when branch result" style="filled" fillcolor=gray];
79 [label="Exit when"];
}
80 [label="Variable declaration: lval a: R|kotlin/Any|"];
subgraph cluster_18 {
color=blue
81 [label="Enter when"];
82 [label="Access variable R|<local>/d2|"];
83 [label="Access variable R|/D.any|"];
84 [label="Variable declaration: lval <elvis>: R|kotlin/Any|"];
subgraph cluster_19 {
color=blue
85 [label="Enter when branch condition "];
86 [label="Const: Null(null)"];
87 [label="Operator =="];
88 [label="Exit when branch condition"];
}
subgraph cluster_20 {
color=blue
89 [label="Enter when branch condition else"];
90 [label="Exit when branch condition"];
}
91 [label="Enter when branch result"];
subgraph cluster_21 {
color=blue
92 [label="Enter block"];
93 [label="Access variable R|<local>/<elvis>|"];
94 [label="Exit block"];
}
95 [label="Exit when branch result"];
96 [label="Enter when branch result"];
subgraph cluster_22 {
color=blue
97 [label="Enter block"];
98 [label="Jump: ^test_2 Unit"];
99 [label="Stub" style="filled" fillcolor=gray];
100 [label="Exit block" style="filled" fillcolor=gray];
}
101 [label="Exit when branch result" style="filled" fillcolor=gray];
102 [label="Exit when"];
}
103 [label="Variable declaration: lval b: R|kotlin/Any|"];
104 [label="Access variable R|<local>/a|"];
105 [label="Type operator: a as A"];
106 [label="Access variable R|<local>/a|"];
107 [label="Function call: R|<local>/a|.R|/A.foo|()"];
108 [label="Access variable R|<local>/b|"];
109 [label="Type operator: b as B"];
110 [label="Access variable R|<local>/b|"];
111 [label="Function call: R|<local>/b|.R|/B.foo|()"];
112 [label="Exit function test_2" style="filled" fillcolor=red];
57 [label="Enter function baz" style="filled" fillcolor=red];
58 [label="Exit function baz" style="filled" fillcolor=red];
}
57 -> {58};
58 -> {59};
subgraph cluster_13 {
color=red
59 [label="Enter function test_2" style="filled" fillcolor=red];
subgraph cluster_14 {
color=blue
60 [label="Enter when"];
61 [label="Access variable R|<local>/d|"];
62 [label="Access variable R|/D.any|"];
63 [label="Variable declaration: lval <elvis>: R|kotlin/Any?|"];
subgraph cluster_15 {
color=blue
64 [label="Enter when branch condition "];
65 [label="Const: Null(null)"];
66 [label="Operator =="];
67 [label="Exit when branch condition"];
}
subgraph cluster_16 {
color=blue
68 [label="Enter when branch condition else"];
69 [label="Exit when branch condition"];
}
70 [label="Enter when branch result"];
subgraph cluster_17 {
color=blue
71 [label="Enter block"];
72 [label="Access variable R|<local>/<elvis>|"];
73 [label="Exit block"];
}
74 [label="Exit when branch result"];
75 [label="Enter when branch result"];
subgraph cluster_18 {
color=blue
76 [label="Enter block"];
77 [label="Jump: ^test_2 Unit"];
78 [label="Stub" style="filled" fillcolor=gray];
79 [label="Exit block" style="filled" fillcolor=gray];
}
80 [label="Exit when branch result" style="filled" fillcolor=gray];
81 [label="Exit when"];
}
82 [label="Variable declaration: lval a: R|kotlin/Any|"];
83 [label="Access variable R|<local>/a|"];
84 [label="Function call: R|<local>/a|.R|/baz|()"];
85 [label="Access variable R|<local>/d|"];
86 [label="Access variable R|/D.any|"];
87 [label="Function call: R|<local>/d|.R|/D.any|.R|/baz|()"];
88 [label="Access variable R|<local>/a|"];
89 [label="Type operator: a as A"];
90 [label="Access variable R|<local>/a|"];
91 [label="Function call: R|<local>/a|.R|/A.foo|()"];
92 [label="Exit function test_2" style="filled" fillcolor=red];
}
59 -> {60};
60 -> {61};
61 -> {62};
62 -> {63};
63 -> {64};
64 -> {65};
65 -> {73 66};
65 -> {66};
66 -> {67};
67 -> {68};
67 -> {75 68};
68 -> {69};
69 -> {70};
70 -> {71};
71 -> {72};
72 -> {79};
72 -> {73};
73 -> {74};
74 -> {75};
75 -> {112};
75 -> {76} [style=dotted];
76 -> {77} [style=dotted];
74 -> {81};
75 -> {76};
76 -> {77};
77 -> {92};
77 -> {78} [style=dotted];
78 -> {79} [style=dotted];
79 -> {80};
80 -> {81};
79 -> {80} [style=dotted];
80 -> {81} [style=dotted];
81 -> {82};
82 -> {83};
83 -> {84};
@@ -284,63 +253,81 @@ digraph notBoundSmartcasts_kt {
85 -> {86};
86 -> {87};
87 -> {88};
88 -> {96 89};
88 -> {89};
89 -> {90};
90 -> {91};
91 -> {92};
92 -> {93};
subgraph cluster_19 {
color=red
93 [label="Enter function test_3" style="filled" fillcolor=red];
94 [label="Access variable R|<local>/d1|"];
95 [label="Access variable R|/D.any|"];
96 [label="Variable declaration: lval a: R|kotlin/Any?|"];
97 [label="Access variable R|<local>/a|"];
98 [label="Type operator: a as A"];
99 [label="Access variable R|<local>/a|"];
100 [label="Function call: R|<local>/a|.R|/A.foo|()"];
101 [label="Access variable R|<local>/d1|"];
102 [label="Access variable R|/D.any|"];
103 [label="Function call: R|<local>/d1|.R|/D.any|.R|/A.foo|()"];
104 [label="Access variable R|<local>/d1|"];
105 [label="Access variable R|/D.any|"];
106 [label="Function call: R|<local>/d1|.R|/D.any|.R|/baz|()"];
107 [label="Exit function test_3" style="filled" fillcolor=red];
}
93 -> {94};
94 -> {95};
95 -> {102};
95 -> {96};
96 -> {97};
97 -> {98};
98 -> {112};
98 -> {99} [style=dotted];
99 -> {100} [style=dotted];
100 -> {101} [style=dotted];
101 -> {102} [style=dotted];
98 -> {99};
99 -> {100};
100 -> {101};
101 -> {102};
102 -> {103};
103 -> {104};
104 -> {105};
105 -> {106};
106 -> {107};
107 -> {108};
108 -> {109};
109 -> {110};
110 -> {111};
111 -> {112};
subgraph cluster_23 {
subgraph cluster_20 {
color=red
113 [label="Enter function test_3" style="filled" fillcolor=red];
114 [label="Access variable R|<local>/d1|"];
108 [label="Enter function test_4" style="filled" fillcolor=red];
109 [label="Access variable R|<local>/d1|"];
110 [label="Enter safe call"];
111 [label="Access variable R|/D.any|"];
112 [label="Exit safe call"];
113 [label="Variable declaration: lval a: R|kotlin/Any?|"];
114 [label="Access variable R|<local>/d2|"];
115 [label="Enter safe call"];
116 [label="Access variable R|/D.any|"];
117 [label="Exit safe call"];
118 [label="Variable declaration: lval a: R|kotlin/Any?|"];
119 [label="Access variable R|<local>/d2|"];
120 [label="Enter safe call"];
121 [label="Access variable R|/D.any|"];
122 [label="Exit safe call"];
123 [label="Variable declaration: lval b: R|kotlin/Any?|"];
124 [label="Access variable R|<local>/a|"];
125 [label="Type operator: a as A"];
126 [label="Access variable R|<local>/a|"];
127 [label="Function call: R|<local>/a|.R|/A.foo|()"];
128 [label="Access variable R|<local>/b|"];
129 [label="Type operator: b as B"];
130 [label="Access variable R|<local>/b|"];
131 [label="Function call: R|<local>/b|.<Ambiguity: foo, [/A.foo, /B.foo]>#()"];
132 [label="Exit function test_3" style="filled" fillcolor=red];
118 [label="Variable declaration: lval b: R|kotlin/Any?|"];
119 [label="Access variable R|<local>/a|"];
120 [label="Type operator: a as A"];
121 [label="Access variable R|<local>/a|"];
122 [label="Function call: R|<local>/a|.R|/A.foo|()"];
123 [label="Access variable R|<local>/b|"];
124 [label="Type operator: b as B"];
125 [label="Access variable R|<local>/b|"];
126 [label="Function call: R|<local>/b|.R|/B.foo|()"];
127 [label="Exit function test_4" style="filled" fillcolor=red];
}
108 -> {109};
109 -> {110 112};
110 -> {111};
111 -> {112};
112 -> {113};
113 -> {114};
114 -> {115 117};
115 -> {116};
116 -> {117};
117 -> {118};
118 -> {119};
119 -> {120 122};
119 -> {120};
120 -> {121};
121 -> {122};
122 -> {123};
@@ -348,10 +335,5 @@ digraph notBoundSmartcasts_kt {
124 -> {125};
125 -> {126};
126 -> {127};
127 -> {128};
128 -> {129};
129 -> {130};
130 -> {131};
131 -> {132};
}
@@ -29,23 +29,30 @@ fun test_1() {
class D(val any: Any?)
fun test_2(d1: D, d2: D) {
fun Any.baz() {}
fun test_2(d: D) {
// Elvis operator is converted into == function call
val a = d1.any ?: return
val b = d2.any ?: return
val a = d.any ?: return
a.baz()
d.any.baz()
a as A
a.foo()
b as B
b.foo()
}
// TODO: Fix this -- see comment in FirDataFlowAnalyzer.getRealVariablesForSafeCallChain()
fun test_3(d1: D, d2: D) {
fun test_3(d1: D) {
val a = d1.any
a as A
a.foo()
d1.any.foo()
d1.any.baz()
}
fun test_4(d1: D, d2: D) {
val a = d1?.any
val b = d2?.any
a as A
a.foo()
b as B
// Issue: b incorrectly smartcasted to (A & B)
b.<!AMBIGUITY!>foo<!>() // should be OK
b.foo() // should be OK
}
@@ -38,17 +38,10 @@ FILE: notBoundSmartcasts.kt
public get(): R|kotlin/Any?|
}
public final fun test_2(d1: R|D|, d2: R|D|): R|kotlin/Unit| {
lval a: R|kotlin/Any| = when (lval <elvis>: R|kotlin/Any?| = R|<local>/d1|.R|/D.any|) {
==($subj$, Null(null)) -> {
^test_2 Unit
}
else -> {
R|<local>/<elvis>|
}
}
lval b: R|kotlin/Any| = when (lval <elvis>: R|kotlin/Any| = R|<local>/d2|.R|/D.any|) {
public final fun R|kotlin/Any|.baz(): R|kotlin/Unit| {
}
public final fun test_2(d: R|D|): R|kotlin/Unit| {
lval a: R|kotlin/Any| = when (lval <elvis>: R|kotlin/Any?| = R|<local>/d|.R|/D.any|) {
==($subj$, Null(null)) -> {
^test_2 Unit
}
@@ -57,16 +50,23 @@ FILE: notBoundSmartcasts.kt
}
}
R|<local>/a|.R|/baz|()
R|<local>/d|.R|/D.any|.R|/baz|()
(R|<local>/a| as R|A|)
R|<local>/a|.R|/A.foo|()
(R|<local>/b| as R|B|)
R|<local>/b|.R|/B.foo|()
}
public final fun test_3(d1: R|D|, d2: R|D|): R|kotlin/Unit| {
public final fun test_3(d1: R|D|): R|kotlin/Unit| {
lval a: R|kotlin/Any?| = R|<local>/d1|.R|/D.any|
(R|<local>/a| as R|A|)
R|<local>/a|.R|/A.foo|()
R|<local>/d1|.R|/D.any|.R|/A.foo|()
R|<local>/d1|.R|/D.any|.R|/baz|()
}
public final fun test_4(d1: R|D|, d2: R|D|): R|kotlin/Unit| {
lval a: R|kotlin/Any?| = R|<local>/d1|?.R|/D.any|
lval b: R|kotlin/Any?| = R|<local>/d2|?.R|/D.any|
(R|<local>/a| as R|A|)
R|<local>/a|.R|/A.foo|()
(R|<local>/b| as R|B|)
R|<local>/b|.<Ambiguity: foo, [/A.foo, /B.foo]>#()
R|<local>/b|.R|/B.foo|()
}
@@ -1153,11 +1153,11 @@ digraph nullability_kt {
434 [label="Access variable R|/QImpl.data|"];
435 [label="Access variable R|<local>/q2|"];
436 [label="Access variable R|/QImpl.data|"];
437 [label="Access variable R|/MyData.s|"];
437 [label="Access variable <Inapplicable(WRONG_RECEIVER): [/MyData.s]>#"];
438 [label="Access variable R|<local>/q2|"];
439 [label="Access variable R|/QImpl.data|"];
440 [label="Access variable R|/MyData.s|"];
441 [label="Function call: R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()"];
440 [label="Access variable <Inapplicable(WRONG_RECEIVER): [/MyData.s]>#"];
441 [label="Function call: R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()"];
subgraph cluster_91 {
color=blue
442 [label="Enter when"];
@@ -1,3 +1,5 @@
// CONTAINS ERRORS
interface A {
fun foo()
fun getA(): A
@@ -148,8 +150,8 @@ fun test_11(q: QImpl?, q2: QImpl) {
// Smartcasting of `q.data` should have no effect on `q2.data`.
// Issue: Smartcasting of QImpl.data affects all instances
q2.data
q2.data.s // should be bad
q2.data.s.inc() // should be bad
q2.data.<!INAPPLICABLE_CANDIDATE!>s<!> // should be bad
q2.data.<!INAPPLICABLE_CANDIDATE!>s<!>.<!AMBIGUITY!>inc<!>() // should be bad
if (q2.data != null) {
q2.data.s
@@ -215,8 +215,8 @@ FILE: nullability.kt
R|<local>/q|.R|/QImpl.data|.R|/MyData.s|
R|<local>/q|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()
R|<local>/q2|.R|/QImpl.data|
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|.R|kotlin/Int.inc|()
R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#
R|<local>/q2|.R|/QImpl.data|.<Inapplicable(WRONG_RECEIVER): [/MyData.s]>#.<Ambiguity: inc, [kotlin/inc, kotlin/inc]>#()
when () {
!=(R|<local>/q2|.R|/QImpl.data|, Null(null)) -> {
R|<local>/q2|.R|/QImpl.data|.R|/MyData.s|
@@ -88,9 +88,8 @@ digraph smartcastAfterReassignment_kt {
35 [label="Const: Null(null)"];
36 [label="Assignmenet: R|<local>/x|"];
37 [label="Access variable R|<local>/x|"];
38 [label="Stub" style="filled" fillcolor=gray];
39 [label="Access variable <Unresolved name: length>#" style="filled" fillcolor=gray];
40 [label="Exit function test_3" style="filled" fillcolor=red];
38 [label="Access variable <Unresolved name: length>#"];
39 [label="Exit function test_3" style="filled" fillcolor=red];
}
28 -> {29};
@@ -102,9 +101,7 @@ digraph smartcastAfterReassignment_kt {
34 -> {35};
35 -> {36};
36 -> {37};
37 -> {40};
37 -> {38} [style=dotted];
38 -> {39} [style=dotted];
39 -> {40} [style=dotted];
37 -> {38};
38 -> {39};
}