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:
+19
-8
@@ -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?>
|
||||
|
||||
Reference in New Issue
Block a user