[FIR] Add processing conditional contracts on property accessors
This commit is contained in:
+2
-2
@@ -20,11 +20,11 @@ var Any?.isNotNull: Boolean
|
||||
|
||||
fun test_1(a: A?) {
|
||||
if (a.isNotNull) {
|
||||
a.<!INAPPLICABLE_CANDIDATE!>foo<!>()
|
||||
a.foo()
|
||||
}
|
||||
}
|
||||
|
||||
fun test_2(a: A?) {
|
||||
a.isNotNull = true
|
||||
a.<!INAPPLICABLE_CANDIDATE!>foo<!>()
|
||||
a.foo()
|
||||
}
|
||||
+2
-2
@@ -24,12 +24,12 @@ FILE: propertyAccessors.kt
|
||||
public final fun test_1(a: R|A?|): R|kotlin/Unit| {
|
||||
when () {
|
||||
R|<local>/a|.R|/isNotNull| -> {
|
||||
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.foo]>#()
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public final fun test_2(a: R|A?|): R|kotlin/Unit| {
|
||||
R|<local>/a|.R|/isNotNull| = Boolean(true)
|
||||
R|<local>/a|.<Inapplicable(WRONG_RECEIVER): [/A.foo]>#()
|
||||
R|<local>/a|.R|/A.foo|()
|
||||
}
|
||||
|
||||
+26
-14
@@ -14,6 +14,7 @@ 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.FirResolvedCallableReference
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStackImpl
|
||||
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
|
||||
@@ -618,6 +619,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
|
||||
fun exitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression) {
|
||||
graphBuilder.exitQualifiedAccessExpression(qualifiedAccessExpression).mergeIncomingFlow()
|
||||
processConditionalContract(qualifiedAccessExpression)
|
||||
exitSafeCall(qualifiedAccessExpression)
|
||||
}
|
||||
|
||||
@@ -637,9 +639,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
exitBooleanNot(functionCall, functionCallNode)
|
||||
}
|
||||
processConditionalContract(functionCall)
|
||||
if (functionCall.safe) {
|
||||
exitSafeCall(functionCall)
|
||||
}
|
||||
exitSafeCall(functionCall)
|
||||
}
|
||||
|
||||
fun exitDelegatedConstructorCall(call: FirDelegatedConstructorCall, callCompleted: Boolean) {
|
||||
@@ -671,18 +671,28 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
}
|
||||
}
|
||||
|
||||
private fun processConditionalContract(functionCall: FirFunctionCall) {
|
||||
val contractDescription = (functionCall.symbol as? FirNamedFunctionSymbol)
|
||||
?.fir
|
||||
?.contractDescription as? FirResolvedContractDescription
|
||||
?: return
|
||||
private fun processConditionalContract(qualifiedAccess: FirQualifiedAccess) {
|
||||
val owner: FirContractDescriptionOwner? = when (qualifiedAccess) {
|
||||
is FirFunctionCall -> qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction
|
||||
is FirQualifiedAccessExpression -> {
|
||||
val property = (qualifiedAccess.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty
|
||||
property?.getter
|
||||
}
|
||||
is FirVariableAssignment -> {
|
||||
val property = (qualifiedAccess.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty
|
||||
property?.setter
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
val contractDescription = owner?.contractDescription as? FirResolvedContractDescription ?: return
|
||||
val conditionalEffects = contractDescription.effects.filterIsInstance<ConeConditionalEffectDeclaration>()
|
||||
if (conditionalEffects.isEmpty()) return
|
||||
val argumentsMapping = createArgumentsMapping(functionCall) ?: return
|
||||
val argumentsMapping = createArgumentsMapping(qualifiedAccess) ?: return
|
||||
contractDescriptionVisitingMode = true
|
||||
graphBuilder.enterContract(functionCall).mergeIncomingFlow()
|
||||
graphBuilder.enterContract(qualifiedAccess).mergeIncomingFlow()
|
||||
val lastFlow = graphBuilder.lastNode.flow
|
||||
val functionCallVariable = variableStorage.getOrCreateVariable(lastFlow, functionCall)
|
||||
val functionCallVariable = variableStorage.getOrCreateVariable(lastFlow, qualifiedAccess)
|
||||
for (conditionalEffect in conditionalEffects) {
|
||||
val fir = conditionalEffect.buildContractFir(argumentsMapping) ?: continue
|
||||
val effect = conditionalEffect.effect as? ConeReturnsEffectDeclaration ?: continue
|
||||
@@ -728,7 +738,7 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
else -> throw IllegalArgumentException("Unsupported constant reference: $value")
|
||||
}
|
||||
}
|
||||
graphBuilder.exitContract(functionCall).mergeIncomingFlow(updateReceivers = true)
|
||||
graphBuilder.exitContract(qualifiedAccess).mergeIncomingFlow(updateReceivers = true)
|
||||
contractDescriptionVisitingMode = false
|
||||
}
|
||||
|
||||
@@ -747,8 +757,10 @@ abstract class FirDataFlowAnalyzer<FLOW : Flow>(
|
||||
val node = graphBuilder.exitVariableAssignment(assignment).mergeIncomingFlow()
|
||||
val property = (assignment.lValue as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirProperty ?: return
|
||||
// TODO: add unstable smartcast
|
||||
if (!property.isLocal && property.isVar) return
|
||||
exitVariableInitialization(node, assignment.rValue, property, assignment)
|
||||
if (property.isLocal || !property.isVar) {
|
||||
exitVariableInitialization(node, assignment.rValue, property, assignment)
|
||||
}
|
||||
processConditionalContract(assignment)
|
||||
}
|
||||
|
||||
private fun exitVariableInitialization(
|
||||
|
||||
+2
-2
@@ -538,12 +538,12 @@ class VariableAssignmentNode(owner: ControlFlowGraph, override val fir: FirVaria
|
||||
}
|
||||
}
|
||||
|
||||
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int, id: Int) : CFGNode<FirFunctionCall>(owner, level, id), EnterNodeMarker {
|
||||
class EnterContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id), EnterNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitEnterContractNode(this, data)
|
||||
}
|
||||
}
|
||||
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirFunctionCall, level: Int, id: Int) : CFGNode<FirFunctionCall>(owner, level, id), ExitNodeMarker {
|
||||
class ExitContractNode(owner: ControlFlowGraph, override val fir: FirQualifiedAccess, level: Int, id: Int) : CFGNode<FirQualifiedAccess>(owner, level, id), ExitNodeMarker {
|
||||
override fun <R, D> accept(visitor: ControlFlowGraphVisitor<R, D>, data: D): R {
|
||||
return visitor.visitExitContractNode(this, data)
|
||||
}
|
||||
|
||||
+4
-4
@@ -607,12 +607,12 @@ class ControlFlowGraphBuilder {
|
||||
return leftExitNode to rightExitNode
|
||||
}
|
||||
|
||||
fun enterContract(functionCall: FirFunctionCall): EnterContractNode {
|
||||
return createEnterContractNode(functionCall).also { addNewSimpleNode(it) }
|
||||
fun enterContract(qualifiedAccess: FirQualifiedAccess): EnterContractNode {
|
||||
return createEnterContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
|
||||
}
|
||||
|
||||
fun exitContract(functionCall: FirFunctionCall): ExitContractNode {
|
||||
return createExitContractNode(functionCall).also { addNewSimpleNode(it) }
|
||||
fun exitContract(qualifiedAccess: FirQualifiedAccess): ExitContractNode {
|
||||
return createExitContractNode(qualifiedAccess).also { addNewSimpleNode(it) }
|
||||
}
|
||||
|
||||
fun exitBinaryOr(binaryLogicExpression: FirBinaryLogicExpression): BinaryOrExitNode {
|
||||
|
||||
+2
-2
@@ -132,10 +132,10 @@ fun ControlFlowGraphBuilder.createAnnotationEnterNode(fir: FirAnnotationCall): A
|
||||
fun ControlFlowGraphBuilder.createVariableDeclarationNode(fir: FirProperty): VariableDeclarationNode =
|
||||
VariableDeclarationNode(graph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createExitContractNode(fir: FirFunctionCall): ExitContractNode =
|
||||
fun ControlFlowGraphBuilder.createExitContractNode(fir: FirQualifiedAccess): ExitContractNode =
|
||||
ExitContractNode(graph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirFunctionCall): EnterContractNode =
|
||||
fun ControlFlowGraphBuilder.createEnterContractNode(fir: FirQualifiedAccess): EnterContractNode =
|
||||
EnterContractNode(graph, fir, levelCounter, createId())
|
||||
|
||||
fun ControlFlowGraphBuilder.createConstExpressionNode(fir: FirConstExpression<*>): ConstExpressionNode =
|
||||
|
||||
+16
-11
@@ -85,18 +85,23 @@ fun ConeConditionalEffectDeclaration.buildContractFir(argumentMapping: Map<Int,
|
||||
return condition.accept(ConeConditionalEffectToFirVisitor, argumentMapping)
|
||||
}
|
||||
|
||||
fun createArgumentsMapping(functionCall: FirFunctionCall): Map<Int, FirExpression>? {
|
||||
val function = functionCall.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null
|
||||
fun createArgumentsMapping(qualifiedAccess: FirQualifiedAccess): Map<Int, FirExpression>? {
|
||||
val argumentsMapping = mutableMapOf<Int, FirExpression>()
|
||||
functionCall.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
||||
?: functionCall.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
||||
|
||||
|
||||
val nameToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter.name to index }.toMap()
|
||||
functionCall.arguments.forEachIndexed { index, argument ->
|
||||
when (argument) {
|
||||
is FirNamedArgumentExpression -> argumentsMapping[nameToIndex[argument.name]!!] = argument
|
||||
else -> argumentsMapping[index] = argument
|
||||
qualifiedAccess.extensionReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
||||
?: qualifiedAccess.dispatchReceiver.takeIf { it != FirNoReceiverExpression }?.let { argumentsMapping[-1] = it }
|
||||
when (qualifiedAccess) {
|
||||
is FirFunctionCall -> {
|
||||
val function = qualifiedAccess.toResolvedCallableSymbol()?.fir as? FirSimpleFunction ?: return null
|
||||
val nameToIndex = function.valueParameters.mapIndexed { index, parameter -> parameter.name to index }.toMap()
|
||||
qualifiedAccess.arguments.forEachIndexed { index, argument ->
|
||||
when (argument) {
|
||||
is FirNamedArgumentExpression -> argumentsMapping[nameToIndex[argument.name]!!] = argument
|
||||
else -> argumentsMapping[index] = argument
|
||||
}
|
||||
}
|
||||
}
|
||||
is FirVariableAssignment -> {
|
||||
argumentsMapping[0] = qualifiedAccess.rValue
|
||||
}
|
||||
}
|
||||
return argumentsMapping
|
||||
|
||||
Reference in New Issue
Block a user