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