FIR: Support safe-calls new format in DFA
^KT-38444 In Progress
This commit is contained in:
+24
-27
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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)
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -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()
|
||||||
|
|||||||
+2
-2
@@ -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 =
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user