Get rid of FUNCTOR slice, use lazy value in ContractDecription instead

Functor is an imperative representation of function's contract (contrary
to ContractDescription, which is a declarative one). ContractDescription
is convenient when we deal with sources of contracts declarations
(binaries, source), while Functors are convenient for analyzing code
with contracts.

It means that we have to convert ContractDescription into Functor when
we start working with contracts. This computation isn't trivial, and
Functor and ContractDescription are in 1-1 correspondence, so we would
like to cache Functor for each ContractDescription somewhere.

We used to do this in binding trace, in slice FUNCTOR.

Now, it turns out that this approach causes "Rewrite at slice"
exception, see KT-28847. We won't go into details of why that happens
here, you can see the issue comments for details (but be prepared for the
very long and nitty-gritty story)

This commit removes the problematic slice and introduces another
approach, where Functor is attached to the ContractDescription, computed
lazily and cached here.

^KT-28847 Fixed
This commit is contained in:
Dmitry Savvinov
2018-12-20 13:00:00 +03:00
parent 594a6588f9
commit a4d1a8f60a
11 changed files with 43 additions and 29 deletions
@@ -28,10 +28,14 @@ import org.jetbrains.kotlin.metadata.deserialization.isInstanceType
import org.jetbrains.kotlin.serialization.deserialization.ContractDeserializer import org.jetbrains.kotlin.serialization.deserialization.ContractDeserializer
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
import org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer import org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
class ContractDeserializerImpl(private val configuration: DeserializationConfiguration) : ContractDeserializer { class ContractDeserializerImpl(
private val configuration: DeserializationConfiguration,
private val storageManager: StorageManager
) : ContractDeserializer {
override fun deserializeContractFromFunction( override fun deserializeContractFromFunction(
proto: ProtoBuf.Function, proto: ProtoBuf.Function,
ownerFunction: FunctionDescriptor, ownerFunction: FunctionDescriptor,
@@ -42,7 +46,7 @@ class ContractDeserializerImpl(private val configuration: DeserializationConfigu
if (!configuration.readDeserializedContracts) return null if (!configuration.readDeserializedContracts) return null
val worker = ContractDeserializationWorker(typeTable, typeDeserializer, ownerFunction) val worker = ContractDeserializationWorker(typeTable, typeDeserializer, ownerFunction, storageManager)
val contract = worker.deserializeContract(proto.contract) val contract = worker.deserializeContract(proto.contract)
return ContractProviderKey to LazyContractProvider.createInitialized(contract) return ContractProviderKey to LazyContractProvider.createInitialized(contract)
} }
@@ -50,12 +54,13 @@ class ContractDeserializerImpl(private val configuration: DeserializationConfigu
private class ContractDeserializationWorker( private class ContractDeserializationWorker(
private val typeTable: TypeTable, private val typeTable: TypeTable,
private val typeDeserializer: TypeDeserializer, private val typeDeserializer: TypeDeserializer,
private val ownerFunction: FunctionDescriptor private val ownerFunction: FunctionDescriptor,
private val storageManager: StorageManager
) { ) {
fun deserializeContract(proto: ProtoBuf.Contract): ContractDescription? { fun deserializeContract(proto: ProtoBuf.Contract): ContractDescription? {
val effects = proto.effectList.map { deserializePossiblyConditionalEffect(it) ?: return null } val effects = proto.effectList.map { deserializePossiblyConditionalEffect(it) ?: return null }
return ContractDescription(effects, ownerFunction) return ContractDescription(effects, ownerFunction, storageManager)
} }
private fun deserializePossiblyConditionalEffect(proto: ProtoBuf.Effect): EffectDeclaration? { private fun deserializePossiblyConditionalEffect(proto: ProtoBuf.Effect): EffectDeclaration? {
@@ -75,7 +80,7 @@ class ContractDeserializerImpl(private val configuration: DeserializationConfigu
val argument = proto.effectConstructorArgumentList.getOrNull(0) val argument = proto.effectConstructorArgumentList.getOrNull(0)
val returnValue = val returnValue =
if (argument == null) ConstantReference.WILDCARD else deserializeExpression(argument) as? ConstantReference if (argument == null) ConstantReference.WILDCARD else deserializeExpression(argument) as? ConstantReference
?: return null ?: return null
ReturnsEffectDeclaration(returnValue) ReturnsEffectDeclaration(returnValue)
} }
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.contracts package org.jetbrains.kotlin.contracts
import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.contracts.interpretation.ContractInterpretationDispatcher import org.jetbrains.kotlin.contracts.interpretation.ContractInterpretationDispatcher
import org.jetbrains.kotlin.contracts.model.Computation import org.jetbrains.kotlin.contracts.model.Computation
import org.jetbrains.kotlin.contracts.model.ConditionalEffect import org.jetbrains.kotlin.contracts.model.ConditionalEffect
@@ -168,11 +169,8 @@ class EffectsExtractingVisitor(
} }
private fun FunctionDescriptor.getFunctor(): Functor? { private fun FunctionDescriptor.getFunctor(): Functor? {
trace[BindingContext.FUNCTOR, this]?.let { return it } val contractDescription = getUserData(ContractProviderKey)?.getContractDescription() ?: return null
return contractDescription.functor
val functor = ContractInterpretationDispatcher().resolveFunctor(this) ?: return null
trace.record(BindingContext.FUNCTOR, this, functor)
return functor
} }
private fun ResolvedCall<*>.isCallWithUnsupportedReceiver(): Boolean = private fun ResolvedCall<*>.isCallWithUnsupportedReceiver(): Boolean =
@@ -32,8 +32,9 @@ import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
import org.jetbrains.kotlin.storage.StorageManager
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) { class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings, private val storageManager: StorageManager) {
/** /**
* ! IMPORTANT NOTICE ! * ! IMPORTANT NOTICE !
* *
@@ -77,7 +78,7 @@ class ContractParsingServices(val languageVersionSettings: LanguageVersionSettin
// Small optimization: do not even try to parse contract if we already have errors // Small optimization: do not even try to parse contract if we already have errors
if (collector.hasErrors()) return null if (collector.hasErrors()) return null
val parsedContract = PsiContractParserDispatcher(collector, callContext).parseContract() val parsedContract = PsiContractParserDispatcher(collector, callContext, storageManager).parseContract()
// Make sure that at least generic error will be reported if we couldn't parse contract // Make sure that at least generic error will be reported if we couldn't parse contract
// (null returned => at least one error was reported) // (null returned => at least one error was reported)
@@ -40,10 +40,12 @@ import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.storage.StorageManager
internal class PsiContractParserDispatcher( internal class PsiContractParserDispatcher(
private val collector: ContractParsingDiagnosticsCollector, private val collector: ContractParsingDiagnosticsCollector,
private val callContext: ContractCallContext private val callContext: ContractCallContext,
private val storageManager: StorageManager
) { ) {
private val conditionParser = PsiConditionParser(collector, callContext, this) private val conditionParser = PsiConditionParser(collector, callContext, this)
private val constantParser = PsiConstantParser(callContext) private val constantParser = PsiConstantParser(callContext)
@@ -71,7 +73,7 @@ internal class PsiContractParserDispatcher(
if (effects.isEmpty()) return null if (effects.isEmpty()) return null
return ContractDescription(effects, callContext.functionDescriptor) return ContractDescription(effects, callContext.functionDescriptor, storageManager)
} }
fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit) fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit)
@@ -98,7 +98,6 @@ public interface BindingContext {
WritableSlice<KtExpression, DataFlowInfo> DATA_FLOW_INFO_BEFORE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice<KtExpression, DataFlowInfo> DATA_FLOW_INFO_BEFORE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtElement, Computation> EXPRESSION_EFFECTS = Slices.createSimpleSlice(); WritableSlice<KtElement, Computation> EXPRESSION_EFFECTS = Slices.createSimpleSlice();
WritableSlice<FunctionDescriptor, Functor> FUNCTOR = Slices.createSimpleSlice();
WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice(); WritableSlice<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice();
WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice(); WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice();
@@ -16,7 +16,10 @@
package org.jetbrains.kotlin.contracts.description 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.FunctionDescriptor
import org.jetbrains.kotlin.storage.StorageManager
/** /**
* This is actual model of contracts, i.e. what is expected to be parsed from * This is actual model of contracts, i.e. what is expected to be parsed from
@@ -30,7 +33,18 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
* backward compatibility. Ideally, this model should only be extended, but not * backward compatibility. Ideally, this model should only be extended, but not
* changed. * changed.
*/ */
open class ContractDescription(val effects: List<EffectDeclaration>, val ownerFunction: FunctionDescriptor) open class ContractDescription(
val effects: List<EffectDeclaration>,
val ownerFunction: FunctionDescriptor,
storageManager: StorageManager
) {
private val functorLazyValue = storageManager.createNullableLazyValue {
ContractInterpretationDispatcher().convertContractDescriptorToFunctor(this)
}
val functor: Functor?
get() = functorLazyValue()
}
interface ContractDescriptionElement { interface ContractDescriptionElement {
fun <R, D> accept(contractDescriptionVisitor: ContractDescriptionVisitor<R, D>, data: D): R fun <R, D> accept(contractDescriptionVisitor: ContractDescriptionVisitor<R, D>, data: D): R
@@ -39,12 +39,7 @@ class ContractInterpretationDispatcher {
CallsEffectInterpreter(this) CallsEffectInterpreter(this)
) )
fun resolveFunctor(functionDescriptor: FunctionDescriptor): Functor? { fun convertContractDescriptorToFunctor(contractDescription: ContractDescription): Functor? {
val contractDescriptor = functionDescriptor.getUserData(ContractProviderKey)?.getContractDescription() ?: return null
return convertContractDescriptorToFunctor(contractDescriptor)
}
private fun convertContractDescriptorToFunctor(contractDescription: ContractDescription): Functor? {
val resultingClauses = contractDescription.effects.map { effect -> val resultingClauses = contractDescription.effects.map { effect ->
if (effect is ConditionalEffectDeclaration) { if (effect is ConditionalEffectDeclaration) {
conditionalEffectInterpreter.interpret(effect) ?: return null conditionalEffectInterpreter.interpret(effect) ?: return null
@@ -1,10 +1,10 @@
package package
public final class Foo { public final class Foo {
public constructor Foo() public constructor Foo(/*0*/ str: kotlin.String?)
private final val foo: Foo.foo.<no name provided> public final val first: kotlin.Boolean
public final var property: kotlin.String? public final val second: kotlin.Boolean
public final inline fun </*0*/ T> equalVetoingObservable(/*0*/ initialValue: T, /*1*/ crossinline onChange: (newValue: T) -> kotlin.Unit): kotlin.properties.ObservableProperty<T> public final val str: kotlin.String?
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
@@ -77,7 +77,7 @@ class DeserializerForClassfileDecompiler(
storageManager, moduleDescriptor, configuration, classDataFinder, annotationAndConstantLoader, storageManager, moduleDescriptor, configuration, classDataFinder, annotationAndConstantLoader,
packageFragmentProvider, ResolveEverythingToKotlinAnyLocalClassifierResolver(builtIns), LoggingErrorReporter(LOG), packageFragmentProvider, ResolveEverythingToKotlinAnyLocalClassifierResolver(builtIns), LoggingErrorReporter(LOG),
LookupTracker.DO_NOTHING, JavaFlexibleTypeDeserializer, emptyList(), notFoundClasses, LookupTracker.DO_NOTHING, JavaFlexibleTypeDeserializer, emptyList(), notFoundClasses,
ContractDeserializerImpl(configuration), ContractDeserializerImpl(configuration, storageManager),
extensionRegistryLite = JvmProtoBufUtil.EXTENSION_REGISTRY extensionRegistryLite = JvmProtoBufUtil.EXTENSION_REGISTRY
) )
} }
@@ -76,7 +76,7 @@ fun createKotlinJavascriptPackageFragmentProvider(
DynamicTypeDeserializer, DynamicTypeDeserializer,
emptyList(), emptyList(),
notFoundClasses, notFoundClasses,
ContractDeserializerImpl(configuration), ContractDeserializerImpl(configuration, storageManager),
platformDependentDeclarationFilter = PlatformDependentDeclarationFilter.NoPlatformDependent, platformDependentDeclarationFilter = PlatformDependentDeclarationFilter.NoPlatformDependent,
extensionRegistryLite = JsSerializerProtocol.extensionRegistry extensionRegistryLite = JsSerializerProtocol.extensionRegistry
) )
@@ -119,7 +119,7 @@ internal object KonanDeserializedPackageFragmentsFactoryImpl : KonanDeserialized
NullFlexibleTypeDeserializer, NullFlexibleTypeDeserializer,
emptyList(), emptyList(),
notFoundClasses, notFoundClasses,
ContractDeserializerImpl(configuration), ContractDeserializerImpl(configuration, storageManager),
extensionRegistryLite = KonanSerializerProtocol.extensionRegistry extensionRegistryLite = KonanSerializerProtocol.extensionRegistry
) )