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 ----------------------------------- // ----------------------------------- Resolvable call -----------------------------------
fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { // Intentionally left empty for potential future needs (call sites are preserved)
enterSafeCall(qualifiedAccessExpression) fun enterQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {}
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
processConditionalContract(qualifiedAccessExpression)
} }
private fun enterSafeCall(qualifiedAccess: FirQualifiedAccess) { fun enterSafeCallAfterNullCheck(safeCall: FirSafeCallExpression) {
if (!qualifiedAccess.safe) return val node = graphBuilder.enterSafeCall(safeCall).mergeIncomingFlow()
val node = graphBuilder.enterSafeCall(qualifiedAccess).mergeIncomingFlow()
val previousNode = node.firstPreviousNode val previousNode = node.firstPreviousNode
val shouldFork: Boolean val shouldFork: Boolean
var flow = if (previousNode is ExitSafeCallNode) { var flow = if (previousNode is ExitSafeCallNode) {
@@ -599,7 +602,8 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
shouldFork = true shouldFork = true
node.flow node.flow
} }
qualifiedAccess.explicitReceiver?.let { receiver ->
safeCall.receiver.let { receiver ->
val type = receiver.coneType val type = receiver.coneType
?.takeIf { it.isMarkedNullable } ?.takeIf { it.isMarkedNullable }
?.withNullability(ConeNullability.NOT_NULL) ?.withNullability(ConeNullability.NOT_NULL)
@@ -623,10 +627,20 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
node.flow = flow node.flow = flow
} }
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) { fun exitSafeCall(safeCall: FirSafeCallExpression) {
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow() val node = graphBuilder.exitSafeCall(safeCall).mergeIncomingFlow()
processConditionalContract(qualifiedAccessExpression) val previousFlow = node.previousFlow
exitSafeCall(qualifiedAccessExpression)
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) { fun exitResolvedQualifierNode(resolvedQualifier: FirResolvedQualifier) {
@@ -645,7 +659,6 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
exitBooleanNot(functionCall, functionCallNode) exitBooleanNot(functionCall, functionCallNode)
} }
processConditionalContract(functionCall) processConditionalContract(functionCall)
exitSafeCall(functionCall)
} }
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) { 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) { private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) {
val owner: FirContractDescriptionOwner? = when (qualifiedAccess) { val owner: FirContractDescriptionOwner? = when (qualifiedAccess) {
is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction 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.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType 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.coneTypeSafe
import org.jetbrains.kotlin.fir.types.coneTypeUnsafe
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
@@ -47,6 +45,8 @@ class VariableStorage(private val session: FirSession) {
private fun FirElement.unwrapElement(): FirElement = when (this) { private fun FirElement.unwrapElement(): FirElement = when (this) {
is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject }?.unwrapElement() ?: this is FirWhenSubjectExpression -> whenSubject.whenExpression.let { it.subjectVariable ?: it.subject }?.unwrapElement() ?: this
is FirExpressionWithSmartcast -> originalExpression.unwrapElement() is FirExpressionWithSmartcast -> originalExpression.unwrapElement()
is FirSafeCallExpression -> regularQualifiedAccess.unwrapElement()
is FirCheckedSafeCallSubject -> originalReceiverReference.value.unwrapElement()
else -> this 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 { override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
return visitor.visitEnterSafeCallNode(this, data) 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 lastPreviousNode: CFGNode<*> get() = previousNodes.last()
val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1) val secondPreviousNode: CFGNode<*>? get() = previousNodes.getOrNull(1)
@@ -860,18 +860,18 @@ class ControlFlowGraphBuilder {
// ----------------------------------- Safe calls ----------------------------------- // ----------------------------------- Safe calls -----------------------------------
fun enterSafeCall(qualifiedAccess: FirQualifiedAccess): EnterSafeCallNode { fun enterSafeCall(safeCall: FirSafeCallExpression): EnterSafeCallNode {
val lastNode = lastNodes.pop() val lastNode = lastNodes.pop()
val enterNode = createEnterSafeCallNode(qualifiedAccess) val enterNode = createEnterSafeCallNode(safeCall)
lastNodes.push(enterNode) lastNodes.push(enterNode)
val exitNode = createExitSafeCallNode(qualifiedAccess) val exitNode = createExitSafeCallNode(safeCall)
exitSafeCallNodes.push(exitNode) exitSafeCallNodes.push(exitNode)
addEdge(lastNode, enterNode) addEdge(lastNode, enterNode)
addEdge(lastNode, exitNode) addEdge(lastNode, exitNode)
return enterNode return enterNode
} }
fun exitSafeCall(qualifiedAccess: FirQualifiedAccess): ExitSafeCallNode { fun exitSafeCall(safeCall: FirSafeCallExpression): ExitSafeCallNode {
return exitSafeCallNodes.pop().also { return exitSafeCallNodes.pop().also {
addNewSimpleNode(it) addNewSimpleNode(it)
it.updateDeadStatus() it.updateDeadStatus()
@@ -183,10 +183,10 @@ fun ControlFlowGraphBuilder.createBinaryAndEnterRightOperandNode(fir: FirBinaryL
fun ControlFlowGraphBuilder.createBinaryOrEnterRightOperandNode(fir: FirBinaryLogicExpression): BinaryOrEnterRightOperandNode = fun ControlFlowGraphBuilder.createBinaryOrEnterRightOperandNode(fir: FirBinaryLogicExpression): BinaryOrEnterRightOperandNode =
BinaryOrEnterRightOperandNode(graph, fir, levelCounter, createId()) BinaryOrEnterRightOperandNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirQualifiedAccess): ExitSafeCallNode = fun ControlFlowGraphBuilder.createExitSafeCallNode(fir: FirSafeCallExpression): ExitSafeCallNode =
ExitSafeCallNode(graph, fir, levelCounter, createId()) ExitSafeCallNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirQualifiedAccess): EnterSafeCallNode = fun ControlFlowGraphBuilder.createEnterSafeCallNode(fir: FirSafeCallExpression): EnterSafeCallNode =
EnterSafeCallNode(graph, fir, levelCounter, createId()) EnterSafeCallNode(graph, fir, levelCounter, createId())
fun ControlFlowGraphBuilder.createPostponedLambdaExitNode(fir: FirAnonymousFunction): PostponedLambdaExitNode = fun ControlFlowGraphBuilder.createPostponedLambdaExitNode(fir: FirAnonymousFunction): PostponedLambdaExitNode =
@@ -212,4 +212,4 @@ fun ControlFlowGraphBuilder.createClassExitNode(fir: FirClass<*>): ClassExitNode
} }
fun ControlFlowGraphBuilder.createLocalClassExitNode(fir: FirRegularClass): LocalClassExitNode = 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 package org.jetbrains.kotlin.fir.resolve.dfa
import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.PersistentMap
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference import org.jetbrains.kotlin.fir.contracts.description.ConeBooleanConstantReference
import org.jetbrains.kotlin.fir.contracts.description.ConeConstantReference 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.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.FirThisReference import org.jetbrains.kotlin.fir.references.FirThisReference
import org.jetbrains.kotlin.fir.resolve.calls.FirNamedReferenceWithCandidate 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.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.impl.FirAccessorSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeFlexibleType import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -130,6 +123,7 @@ internal val FirElement.symbol: AbstractFirBasedSymbol<*>?
is FirResolvable -> symbol is FirResolvable -> symbol
is FirSymbolOwner<*> -> symbol is FirSymbolOwner<*> -> symbol
is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol is FirWhenSubjectExpression -> whenSubject.whenExpression.subject?.symbol
is FirSafeCallExpression -> regularQualifiedAccess.symbol
else -> null else -> null
}?.takeIf { this is FirThisReceiverExpression || (it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol) } }?.takeIf { this is FirThisReceiverExpression || (it !is FirFunctionSymbol<*> && it !is FirAccessorSymbol) }