Force-resolve body in contract computation under lock

Even though acquire/release pattern guarantees memory visibility across
threads, it doesn't prevents concurrent access to critical section (i.e.
to force-resolve of the corresponding body).

This can lead to multiple resolution passes over one and the same PSI in
IDE, which, in turn, leads to 'rewrite at slice'-exceptions. See
KT-30030 for case description and details.

^KT-30030 Fixed
This commit is contained in:
Dmitry Savvinov
2019-02-21 11:14:22 +03:00
parent 705ec503cf
commit 50d1c013fc
5 changed files with 26 additions and 13 deletions
@@ -18,21 +18,29 @@ package org.jetbrains.kotlin.contracts.description
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.serialization.deserialization.ContractProvider
import org.jetbrains.kotlin.storage.StorageManager
abstract class AbstractContractProvider : ContractProvider {
abstract fun getContractDescription(): ContractDescription?
}
/**
* Essentially, this is a composition of two fields: value of type 'ContractDescription' and
* 'computation', which guarantees to initialize this field.
*
* Such contract providers are present only for source-based declarations, where we need additional
* resolve (force-resolve of the body) to get ContractDescription
*/
class LazyContractProvider(private val computation: () -> Any?) : ContractProvider {
class LazyContractProvider(private val storageManager: StorageManager, private val computation: () -> Any?) : AbstractContractProvider() {
@Volatile
private var isComputed: Boolean = false
private var contractDescription: ContractDescription? = null
fun getContractDescription(): ContractDescription? {
override fun getContractDescription(): ContractDescription? {
if (!isComputed) {
computation.invoke() // should initialize contractDescription
storageManager.compute(computation)
assert(isComputed) { "Computation of contract hasn't initialized contract properly" }
}
@@ -43,12 +51,15 @@ class LazyContractProvider(private val computation: () -> Any?) : ContractProvid
this.contractDescription = contractDescription
isComputed = true // publish
}
}
companion object {
fun createInitialized(contract: ContractDescription?): LazyContractProvider =
LazyContractProvider({}).apply { setContractDescription(contract) }
}
/**
* Such contract providers are used where we can be sure about contract presence and don't need
* additional resolve (e.g., for deserialized declarations)
*/
class ContractProviderImpl(private val contractDescription: ContractDescription) : AbstractContractProvider() {
override fun getContractDescription(): ContractDescription = contractDescription
}
// For storing into UserDataMap of FunctionDescriptor
object ContractProviderKey : CallableDescriptor.UserDataKey<LazyContractProvider?>
object ContractProviderKey : CallableDescriptor.UserDataKey<AbstractContractProvider?>