From 9516d6e89beefc8819d8c67ea17087ea20731f6d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 21 Dec 2018 16:35:59 +0100 Subject: [PATCH] Do not use DefaultBuiltIns in contracts Default built-ins represent built-ins loaded from the compiler jar via class loader, and they may not be equivalent to the built-ins present in the standard library that is used in compilation dependencies, in case the compiler and stdlib versions do not match. Use built-ins from the given module instead. This commit deals with more-or-less obvious usages of DefaultBuiltIns; next commits refactor the ESConstant values and related code to support injected built-ins --- .../contracts/ContextInfoToDataFlowInfo.kt | 33 +++++++++++-------- .../kotlin/contracts/EffectSystem.kt | 21 +++++------- .../contracts/EffectsExtractingVisitor.kt | 21 ++++++------ .../description/ContractDescription.kt | 10 +++--- .../ContractInterpretationDispatcher.kt | 19 ++++++----- .../model/functors/SubstitutingFunctor.kt | 10 ++++-- .../contracts/model/visitors/Substitutor.kt | 23 ++++++++----- 7 files changed, 77 insertions(+), 60 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt index 28f2049f907..8d1f5bcfe35 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/ContextInfoToDataFlowInfo.kt @@ -16,8 +16,9 @@ package org.jetbrains.kotlin.contracts -import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference import org.jetbrains.kotlin.contracts.model.ESValue import org.jetbrains.kotlin.contracts.model.MutableContextInfo import org.jetbrains.kotlin.contracts.model.structure.ESConstant @@ -26,41 +27,47 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue import org.jetbrains.kotlin.resolve.calls.smartcasts.IdentifierInfo -fun MutableContextInfo.toDataFlowInfo(languageVersionSettings: LanguageVersionSettings): DataFlowInfo { +fun MutableContextInfo.toDataFlowInfo(languageVersionSettings: LanguageVersionSettings, builtIns: KotlinBuiltIns): DataFlowInfo { var resultingDataFlowInfo = DataFlowInfoFactory.EMPTY - extractDataFlowStatements(equalValues) { leftDfv, rightValue -> - val rightDfv = rightValue.toDataFlowValue() + extractDataFlowStatements(equalValues, builtIns) { leftDfv, rightValue -> + val rightDfv = rightValue.toDataFlowValue(builtIns) if (rightDfv != null) { resultingDataFlowInfo = resultingDataFlowInfo.equate(leftDfv, rightDfv, false, languageVersionSettings) } IntArray(42) { it } } - extractDataFlowStatements(notEqualValues) { leftDfv, rightValue -> - val rightDfv = rightValue.toDataFlowValue() + extractDataFlowStatements(notEqualValues, builtIns) { leftDfv, rightValue -> + val rightDfv = rightValue.toDataFlowValue(builtIns) if (rightDfv != null) { resultingDataFlowInfo = resultingDataFlowInfo.disequate(leftDfv, rightDfv, languageVersionSettings) } } - extractDataFlowStatements(subtypes) { leftDfv, type -> + extractDataFlowStatements(subtypes, builtIns) { leftDfv, type -> resultingDataFlowInfo = resultingDataFlowInfo.establishSubtyping(leftDfv, type, languageVersionSettings) } return resultingDataFlowInfo } -inline private fun extractDataFlowStatements(dictionary: Map>, callback: (DataFlowValue, D) -> Unit) { +private inline fun extractDataFlowStatements( + dictionary: Map>, + builtIns: KotlinBuiltIns, + callback: (DataFlowValue, D) -> Unit +) { for ((key, setOfValues) in dictionary) { - val leftDfv = key.toDataFlowValue() ?: continue + val leftDfv = key.toDataFlowValue(builtIns) ?: continue setOfValues.forEach { callback(leftDfv, it) } } } -private fun ESValue.toDataFlowValue(): DataFlowValue? = when (this) { +private fun ESValue.toDataFlowValue(builtIns: KotlinBuiltIns): DataFlowValue? = when (this) { is ESDataFlowValue -> dataFlowValue - ESConstant.NULL -> DataFlowValue.nullValue(DefaultBuiltIns.Instance) - is ESConstant -> DataFlowValue(IdentifierInfo.NO, type) + is ESConstant -> when (constantReference) { + ConstantReference.NULL -> DataFlowValue.nullValue(builtIns) + else -> DataFlowValue(IdentifierInfo.NO, type) + } else -> null -} \ No newline at end of file +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt index 882a55d2e2a..d108dabecd8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt @@ -18,15 +18,11 @@ package org.jetbrains.kotlin.contracts import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings -import org.jetbrains.kotlin.contracts.model.structure.ESCalls -import org.jetbrains.kotlin.contracts.model.structure.ESReturns -import org.jetbrains.kotlin.contracts.model.functors.EqualsFunctor -import org.jetbrains.kotlin.contracts.model.structure.ESConstant -import org.jetbrains.kotlin.contracts.model.structure.UNKNOWN_COMPUTATION -import org.jetbrains.kotlin.contracts.model.structure.lift -import org.jetbrains.kotlin.contracts.model.ESEffect import org.jetbrains.kotlin.contracts.model.Computation +import org.jetbrains.kotlin.contracts.model.ESEffect import org.jetbrains.kotlin.contracts.model.MutableContextInfo +import org.jetbrains.kotlin.contracts.model.functors.EqualsFunctor +import org.jetbrains.kotlin.contracts.model.structure.* import org.jetbrains.kotlin.contracts.model.visitors.InfoCollector import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.psi.KtCallExpression @@ -54,7 +50,7 @@ class EffectSystem(val languageVersionSettings: LanguageVersionSettings, val dat val resultContextInfo = getContextInfoWhen(ESReturns(ESConstant.WILDCARD), callExpression, bindingTrace, moduleDescriptor) - return resultContextInfo.toDataFlowInfo(languageVersionSettings) + return resultContextInfo.toDataFlowInfo(languageVersionSettings, moduleDescriptor.builtIns) } fun getDataFlowInfoWhenEquals( @@ -73,12 +69,13 @@ class EffectSystem(val languageVersionSettings: LanguageVersionSettings, val dat val effects = EqualsFunctor(false).invokeWithArguments(leftComputation, rightComputation) + val builtIns = moduleDescriptor.builtIns val equalsContextInfo = InfoCollector(ESReturns(true.lift())).collectFromSchema(effects) val notEqualsContextInfo = InfoCollector(ESReturns(false.lift())).collectFromSchema(effects) return ConditionalDataFlowInfo( - equalsContextInfo.toDataFlowInfo(languageVersionSettings), - notEqualsContextInfo.toDataFlowInfo(languageVersionSettings) + equalsContextInfo.toDataFlowInfo(languageVersionSettings, builtIns), + notEqualsContextInfo.toDataFlowInfo(languageVersionSettings, builtIns) ) } @@ -107,7 +104,7 @@ class EffectSystem(val languageVersionSettings: LanguageVersionSettings, val dat if (condition == null) return DataFlowInfo.EMPTY return getContextInfoWhen(ESReturns(value.lift()), condition, bindingTrace, moduleDescriptor) - .toDataFlowInfo(languageVersionSettings) + .toDataFlowInfo(languageVersionSettings, moduleDescriptor.builtIns) } private fun getContextInfoWhen( @@ -124,4 +121,4 @@ class EffectSystem(val languageVersionSettings: LanguageVersionSettings, val dat val computation = EffectsExtractingVisitor(trace, moduleDescriptor, dataFlowValueFactory).extractOrGetCached(expression) return if (computation == UNKNOWN_COMPUTATION) null else computation } -} \ No newline at end of file +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt index 1510d7e02ef..97a6a9a5778 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectsExtractingVisitor.kt @@ -16,9 +16,8 @@ package org.jetbrains.kotlin.contracts -import org.jetbrains.kotlin.builtins.DefaultBuiltIns +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.contracts.description.ContractProviderKey -import org.jetbrains.kotlin.contracts.interpretation.ContractInterpretationDispatcher import org.jetbrains.kotlin.contracts.model.Computation import org.jetbrains.kotlin.contracts.model.ConditionalEffect import org.jetbrains.kotlin.contracts.model.ESEffect @@ -54,6 +53,8 @@ class EffectsExtractingVisitor( private val moduleDescriptor: ModuleDescriptor, private val dataFlowValueFactory: DataFlowValueFactory ) : KtVisitor() { + private val builtIns: KotlinBuiltIns = moduleDescriptor.builtIns + fun extractOrGetCached(element: KtElement): Computation { trace[BindingContext.EXPRESSION_EFFECTS, element]?.let { return it } return element.accept(this, Unit).also { trace.record(BindingContext.EXPRESSION_EFFECTS, element, it) } @@ -68,7 +69,7 @@ class EffectsExtractingVisitor( val descriptor = resolvedCall.resultingDescriptor return when { descriptor.isEqualsDescriptor() -> CallComputation( - DefaultBuiltIns.Instance.booleanType, + builtIns.booleanType, EqualsFunctor(false).invokeWithArguments(arguments) ) descriptor is ValueDescriptor -> ESDataFlowValue( @@ -112,7 +113,7 @@ class EffectsExtractingVisitor( val rightType: KotlinType = trace[BindingContext.TYPE, expression.typeReference] ?: return UNKNOWN_COMPUTATION val arg = extractOrGetCached(expression.leftHandSide) return CallComputation( - DefaultBuiltIns.Instance.booleanType, + builtIns.booleanType, IsFunctor(rightType, expression.isNegated).invokeWithArguments(listOf(arg)) ) } @@ -138,10 +139,10 @@ class EffectsExtractingVisitor( val args = listOf(left, right) return when (expression.operationToken) { - KtTokens.EXCLEQ -> CallComputation(DefaultBuiltIns.Instance.booleanType, EqualsFunctor(true).invokeWithArguments(args)) - KtTokens.EQEQ -> CallComputation(DefaultBuiltIns.Instance.booleanType, EqualsFunctor(false).invokeWithArguments(args)) - KtTokens.ANDAND -> CallComputation(DefaultBuiltIns.Instance.booleanType, AndFunctor().invokeWithArguments(args)) - KtTokens.OROR -> CallComputation(DefaultBuiltIns.Instance.booleanType, OrFunctor().invokeWithArguments(args)) + KtTokens.EXCLEQ -> CallComputation(builtIns.booleanType, EqualsFunctor(true).invokeWithArguments(args)) + KtTokens.EQEQ -> CallComputation(builtIns.booleanType, EqualsFunctor(false).invokeWithArguments(args)) + KtTokens.ANDAND -> CallComputation(builtIns.booleanType, AndFunctor().invokeWithArguments(args)) + KtTokens.OROR -> CallComputation(builtIns.booleanType, OrFunctor().invokeWithArguments(args)) else -> UNKNOWN_COMPUTATION } } @@ -149,7 +150,7 @@ class EffectsExtractingVisitor( override fun visitUnaryExpression(expression: KtUnaryExpression, data: Unit): Computation { val arg = extractOrGetCached(expression.baseExpression ?: return UNKNOWN_COMPUTATION) return when (expression.operationToken) { - KtTokens.EXCL -> CallComputation(DefaultBuiltIns.Instance.booleanType, NotFunctor().invokeWithArguments(arg)) + KtTokens.EXCL -> CallComputation(builtIns.booleanType, NotFunctor().invokeWithArguments(arg)) else -> UNKNOWN_COMPUTATION } } @@ -170,7 +171,7 @@ class EffectsExtractingVisitor( private fun FunctionDescriptor.getFunctor(): Functor? { val contractDescription = getUserData(ContractProviderKey)?.getContractDescription() ?: return null - return contractDescription.functor + return contractDescription.getFunctor(moduleDescriptor) } private fun ResolvedCall<*>.isCallWithUnsupportedReceiver(): Boolean = diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/description/ContractDescription.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/description/ContractDescription.kt index 65624b94088..f167bb96308 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/description/ContractDescription.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/description/ContractDescription.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.contracts.description import org.jetbrains.kotlin.contracts.interpretation.ContractInterpretationDispatcher import org.jetbrains.kotlin.contracts.model.Functor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.storage.StorageManager /** @@ -38,12 +39,11 @@ open class ContractDescription( val ownerFunction: FunctionDescriptor, storageManager: StorageManager ) { - private val functorLazyValue = storageManager.createNullableLazyValue { - ContractInterpretationDispatcher().convertContractDescriptorToFunctor(this) + private val computeFunctor = storageManager.createMemoizedFunctionWithNullableValues { module -> + ContractInterpretationDispatcher(module).convertContractDescriptorToFunctor(this) } - val functor: Functor? - get() = functorLazyValue() + fun getFunctor(usageModule: ModuleDescriptor): Functor? = computeFunctor(usageModule) } interface ContractDescriptionElement { @@ -58,4 +58,4 @@ interface EffectDeclaration : ContractDescriptionElement { interface BooleanExpression : ContractDescriptionElement { override fun accept(contractDescriptionVisitor: ContractDescriptionVisitor, data: D): R = contractDescriptionVisitor.visitBooleanExpression(this, data) -} \ No newline at end of file +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/interpretation/ContractInterpretationDispatcher.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/interpretation/ContractInterpretationDispatcher.kt index 9e09e07879f..daeda57346a 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/interpretation/ContractInterpretationDispatcher.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/interpretation/ContractInterpretationDispatcher.kt @@ -16,21 +16,24 @@ package org.jetbrains.kotlin.contracts.interpretation -import org.jetbrains.kotlin.contracts.description.* +import org.jetbrains.kotlin.contracts.description.BooleanExpression +import org.jetbrains.kotlin.contracts.description.ConditionalEffectDeclaration +import org.jetbrains.kotlin.contracts.description.ContractDescription +import org.jetbrains.kotlin.contracts.description.EffectDeclaration import org.jetbrains.kotlin.contracts.description.expressions.ConstantReference import org.jetbrains.kotlin.contracts.description.expressions.VariableReference -import org.jetbrains.kotlin.contracts.model.functors.SubstitutingFunctor -import org.jetbrains.kotlin.contracts.model.structure.ESConstant -import org.jetbrains.kotlin.contracts.model.structure.ESVariable import org.jetbrains.kotlin.contracts.model.ESEffect import org.jetbrains.kotlin.contracts.model.ESExpression import org.jetbrains.kotlin.contracts.model.Functor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.contracts.model.functors.SubstitutingFunctor +import org.jetbrains.kotlin.contracts.model.structure.ESConstant +import org.jetbrains.kotlin.contracts.model.structure.ESVariable +import org.jetbrains.kotlin.descriptors.ModuleDescriptor /** * This class manages conversion of [ContractDescription] to [Functor] */ -class ContractInterpretationDispatcher { +class ContractInterpretationDispatcher(internal val module: ModuleDescriptor) { private val constantsInterpreter = ConstantValuesInterpreter() private val conditionInterpreter = ConditionInterpreter(this) private val conditionalEffectInterpreter = ConditionalEffectInterpreter(this) @@ -48,7 +51,7 @@ class ContractInterpretationDispatcher { } } - return SubstitutingFunctor(resultingClauses, contractDescription.ownerFunction) + return SubstitutingFunctor(resultingClauses, contractDescription.ownerFunction, module.builtIns) } internal fun interpretEffect(effectDeclaration: EffectDeclaration): ESEffect? { @@ -63,4 +66,4 @@ class ContractInterpretationDispatcher { booleanExpression.accept(conditionInterpreter, Unit) internal fun interpretVariable(variableReference: VariableReference): ESVariable? = ESVariable(variableReference.descriptor) -} \ No newline at end of file +} diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/SubstitutingFunctor.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/SubstitutingFunctor.kt index f0924092b45..1f5a147f7ca 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/SubstitutingFunctor.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/functors/SubstitutingFunctor.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.contracts.model.functors +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.contracts.model.* import org.jetbrains.kotlin.contracts.model.structure.* import org.jetbrains.kotlin.contracts.model.visitors.Substitutor @@ -23,8 +24,11 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ValueDescriptor import org.jetbrains.kotlin.utils.addIfNotNull -class SubstitutingFunctor(private val basicEffects: List, private val ownerFunction: FunctionDescriptor) : - AbstractReducingFunctor() { +class SubstitutingFunctor( + private val basicEffects: List, + private val ownerFunction: FunctionDescriptor, + private val builtIns: KotlinBuiltIns +) : AbstractReducingFunctor() { override fun doInvocation(arguments: List): List { if (basicEffects.isEmpty()) return emptyList() @@ -37,7 +41,7 @@ class SubstitutingFunctor(private val basicEffects: List, private val } val substitutions = parameters.zip(arguments).toMap() - val substitutor = Substitutor(substitutions) + val substitutor = Substitutor(substitutions, builtIns) val substitutedClauses = mutableListOf() effectsLoop@ for (effect in basicEffects) { diff --git a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/visitors/Substitutor.kt b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/visitors/Substitutor.kt index 8a0479d9088..efebeede110 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/visitors/Substitutor.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/contracts/model/visitors/Substitutor.kt @@ -16,8 +16,10 @@ package org.jetbrains.kotlin.contracts.model.visitors -import org.jetbrains.kotlin.builtins.DefaultBuiltIns -import org.jetbrains.kotlin.contracts.model.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.contracts.model.Computation +import org.jetbrains.kotlin.contracts.model.ESExpression +import org.jetbrains.kotlin.contracts.model.ESExpressionVisitor import org.jetbrains.kotlin.contracts.model.structure.* /** @@ -25,36 +27,39 @@ import org.jetbrains.kotlin.contracts.model.structure.* * and then flattens resulting tree, producing an [EffectSchema], which describes effects * of this [ESExpression] with effects of arguments taken into consideration. */ -class Substitutor(private val substitutions: Map) : ESExpressionVisitor { +class Substitutor( + private val substitutions: Map, + private val builtIns: KotlinBuiltIns +) : ESExpressionVisitor { override fun visitIs(isOperator: ESIs): Computation? { val arg = isOperator.left.accept(this) ?: return null - return CallComputation(DefaultBuiltIns.Instance.booleanType, isOperator.functor.invokeWithArguments(arg)) + return CallComputation(builtIns.booleanType, isOperator.functor.invokeWithArguments(arg)) } override fun visitNot(not: ESNot): Computation? { val arg = not.arg.accept(this) ?: return null - return CallComputation(DefaultBuiltIns.Instance.booleanType, not.functor.invokeWithArguments(arg)) + return CallComputation(builtIns.booleanType, not.functor.invokeWithArguments(arg)) } override fun visitEqual(equal: ESEqual): Computation? { val left = equal.left.accept(this) ?: return null val right = equal.right.accept(this) ?: return null - return CallComputation(DefaultBuiltIns.Instance.booleanType, equal.functor.invokeWithArguments(listOf(left, right))) + return CallComputation(builtIns.booleanType, equal.functor.invokeWithArguments(listOf(left, right))) } override fun visitAnd(and: ESAnd): Computation? { val left = and.left.accept(this) ?: return null val right = and.right.accept(this) ?: return null - return CallComputation(DefaultBuiltIns.Instance.booleanType, and.functor.invokeWithArguments(left, right)) + return CallComputation(builtIns.booleanType, and.functor.invokeWithArguments(left, right)) } override fun visitOr(or: ESOr): Computation? { val left = or.left.accept(this) ?: return null val right = or.right.accept(this) ?: return null - return CallComputation(DefaultBuiltIns.Instance.booleanType, or.functor.invokeWithArguments(left, right)) + return CallComputation(builtIns.booleanType, or.functor.invokeWithArguments(left, right)) } override fun visitVariable(esVariable: ESVariable): Computation? = substitutions[esVariable] ?: esVariable override fun visitConstant(esConstant: ESConstant): Computation? = esConstant -} \ No newline at end of file +}