FIR: Support safe-calls new format in DFA

^KT-38444 In Progress
This commit is contained in:
Denis Zharkov
2020-06-01 16:01:36 +03:00
parent ec746e17c9
commit 080565e482
6 changed files with 39 additions and 48 deletions
@@ -583,13 +583,16 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
// ----------------------------------- Resolvable call -----------------------------------
fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
enterSafeCall(qualifiedAccessExpression)
// Intentionally left empty for potential future needs (call sites are preserved)
fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {}
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
processConditionalContract(qualifiedAccessExpression)
}
private fun enterSafeCall(qualifiedAccess: FirQualifiedAccess) {
if (!qualifiedAccess.safe) return
val node = graphBuilder.enterSafeCall(qualifiedAccess).mergeIncomingFlow()
fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
val node = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow()
val previousNode = node.firstPreviousNode
val shouldFork: Boolean
var flow = if (previousNode is ExitSafeCallNode) {
@@ -599,7 +602,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
shouldFork = true
node.flow
}
qualifiedAccess.explicitReceiver?.let { receiver ->
safeCall.receiver.let { receiver ->
val type = receiver.coneType
?.takeIf { it.isMarkedNullable }
?.withNullability(ConeNullability.NOT_NULL)
@@ -623,10 +627,20 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
node.flow = flow
}
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
processConditionalContract(qualifiedAccessExpression)
exitSafeCall(qualifiedAccessExpression)
fun exitSafeCall(safeCall: FirSafeCallExpression) {
val node = graphBuilder.exitSafeCall(safeCall).mergeIncomingFlow()
val previousFlow = node.previousFlow
val variable = variableStorage.getOrCreateVariable(previousFlow, safeCall)
val receiverVariable = when (variable) {
// There is some bug with invokes. See KT-36014
is RealVariable -> variable.explicitReceiverVariable ?: return
is SyntheticVariable -> variableStorage.getOrCreateVariable(previousFlow, safeCall.receiver)
}
logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable notEq null))
if (receiverVariable.isReal()) {
logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable typeEq any))
}
}
fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) {
@@ -645,7 +659,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
exitBooleanNot(functionCall, functionCallNode)
}
processConditionalContract(functionCall)
exitSafeCall(functionCall)
}
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
@@ -661,22 +674,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
}
}
private fun exitSafeCall(qualifiedAccess: FirQualifiedAccess) {
if (!qualifiedAccess.safe) return
val node = graphBuilder.exitSafeCall(qualifiedAccess).mergeIncomingFlow()
val previousFlow = node.previousFlow
val variable = variableStorage.getOrCreateVariable(previousFlow, qualifiedAccess)
val receiverVariable = when (variable) {
// There is some bug with invokes. See KT-36014
is RealVariable -> variable.explicitReceiverVariable ?: return
is SyntheticVariable -> variableStorage.getOrCreateVariable(previousFlow, qualifiedAccess.explicitReceiver!!)
}
logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable notEq null))
if (receiverVariable.isReal()) {
logicSystem.addImplication(node.flow, (variable notEq null) implies (receiverVariable typeEq any))
}
}
private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) {
val owner: FirContractDescriptionOwner? = when (qualifiedAccess) {
is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction
@@ -21,9 +21,7 @@ import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -47,6 +45,8 @@ class VariableStorage(private val session: FirSession) {
private fun FirElement.unwrapElement(): FirElement = when (this) {
is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject }?.unwrapElement() ?: this
is FirExpressionWithSmartcast -> originalExpression.unwrapElement()
is FirSafeCallExpression -> regularQualifiedAccess.unwrapElement()
is FirCheckedSafeCallSubject -> originalReceiverReference.value.unwrapElement()
else -> this
}
@@ -570,12 +570,12 @@ class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAc
}
}
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id) {
class EnterSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterSafeCallNode(this, data)
}
}
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id) {
class ExitSafeCallNode(owner: ControlFlowGraph, override val fir: FirSafeCallExpression, level: Int, id: Int) : CFGNode<FirSafeCallExpression>(owner, level, id) {
val lastPreviousNode: CFGNode<*> get() = previousNodes.last()
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
@@ -860,18 +860,18 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Safe calls -----------------------------------
fun enterSafeCall(qualifiedAccess: FirQualifiedAccess): EnterSafeCallNode {
fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode {
val lastNode = lastNodes.pop()
val enterNode = createEnterSafeCallNode(qualifiedAccess)
val enterNode = createEnterSafeCallNode(safeCall)
lastNodes.push(enterNode)
val exitNode = createExitSafeCallNode(qualifiedAccess)
val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode)
addEdge(lastNode, exitNode)
return enterNode
}
fun exitSafeCall(qualifiedAccess: FirQualifiedAccess): ExitSafeCallNode {
fun exitSafeCall(safeCall: FirSafeCallExpression): ExitSafeCallNode {
return exitSafeCallNodes.pop().also {
addNewSimpleNode(it)
it.updateDeadStatus()
@@ -183,10 +183,10 @@ fun ControlFlowGraphBuilder.createBinaryAndEnterRightOperandNode(fir: FirBinaryL
fun ControlFlowGraphBuilder.createBinaryOrEnterRightOperandNode(fir: FirBinaryLogicExpression): BinaryOrEnterRightOperandNode =
BinaryOrEnterRightOperandNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirQualifiedAccess): ExitSafeCallNode =
fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirSafeCallExpression): ExitSafeCallNode =
ExitSafeCallNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirQualifiedAccess): EnterSafeCallNode =
fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirSafeCallExpression): EnterSafeCallNode =
EnterSafeCallNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createPostponedLambdaExitNode(fir: FirAnonymousFunction): PostponedLambdaExitNode =
@@ -212,4 +212,4 @@ fun ControlFlowGraphBuilder.createClassExitNode(fir: FirClass<*>): ClassExitNode
}
fun ControlFlowGraphBuilder.createLocalClassExitNode(fir: FirRegularClass): LocalClassExitNode =
LocalClassExitNode(graph, fir, levelCounter, createId())
LocalClassExitNode(graph, fir, levelCounter, createId())
@@ -6,25 +6,18 @@
package org.jetbrains.kotlin.fir.resolve.dfa
import kotlinx.collections.immutable.PersistentMap
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate
import org.jetbrains.kotlin.fir.resolve.directExpansionType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeFlexibleType
import org.jetbrains.kotlin.fir.symbols.impl.FirAccessorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -130,6 +123,7 @@ internal val FirElement.symbol: AbstractFirBasedSymbol<*>?
is FirResolvable -> symbol
is FirSymbolOwner<*> -> symbol
is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol
is FirSafeCallExpression -> regularQualifiedAccess.symbol
else -> null
}?.takeIf { this is FirThisReceiverExpression || (it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol) }