[FIR] Add processing conditional contracts on property accessors

This commit is contained in:
Dmitriy Novozhilov
2020-04-13 11:24:38 +03:00
parent 1074e4bf29
commit a01bbbb0a0
7 changed files with 54 additions and 37 deletions
@@ -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()
}
@@ -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|()
}
@@ -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(
@@ -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)
}
@@ -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 {
@@ -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 =
@@ -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