FIR IDE: workaround deadlock when resolving class members

When listing members in a class, we need to resolve super types. But if
a super type is defined in another file, this file may not have been
resolved. In this case, listing the members would require resolving the
unresolved file, which can only be done in a write lock.

Before this change, KtFirScopeProvider only acquire the read lock, which
then causes read/write deadlock when the logic tries to further resolve
super types defined in other files. This change makes KtFirScopeProvider
acquire a write lock in the beginning so resolving can happen correctly
later.
This commit is contained in:
Tianyu Geng
2021-04-29 14:07:34 -07:00
committed by TeamCityServer
parent 7a745cfe5c
commit 9273c6326e
3 changed files with 23 additions and 12 deletions
@@ -109,8 +109,12 @@ fun <D : FirDeclaration, R> D.withFirDeclaration(
*/
fun <D : FirDeclaration, R> D.withFirDeclarationInWriteLock(
resolveState: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.RAW_FIR,
action: (D) -> R,
): R = resolveState.withLock(this, DeclarationLockType.WRITE_LOCK, action)
): R {
resolvedFirToPhase(phase, resolveState)
return resolveState.withLock(this, DeclarationLockType.WRITE_LOCK, action)
}
/**
* Returns a list of Diagnostics compiler finds for given [KtElement]
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.scopes.impl.*
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getTowerDataContextUnsafe
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.ValidityTokenOwner
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeProvider
@@ -25,18 +24,14 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.*
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirAnonymousObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirEnumEntrySymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirFileSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirNamedClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef
import org.jetbrains.kotlin.idea.frontend.api.scopes.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFileSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPackageSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithDeclarations
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithMembers
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.psi.KtElement
@@ -61,8 +56,8 @@ internal class KtFirScopeProvider(
private val packageMemberScopeCache = IdentityHashMap<KtPackageSymbol, KtPackageScope>()
private inline fun <T> KtSymbolWithMembers.withFirForScope(crossinline body: (FirClass<*>) -> T): T? = when (this) {
is KtFirNamedClassOrObjectSymbol -> firRef.withFir(FirResolvePhase.TYPES, body)
is KtFirAnonymousObjectSymbol -> firRef.withFir(FirResolvePhase.TYPES, body)
is KtFirNamedClassOrObjectSymbol -> firRef.withFirWithPossibleResolveInside(FirResolvePhase.TYPES, body)
is KtFirAnonymousObjectSymbol -> firRef.withFirWithPossibleResolveInside(FirResolvePhase.TYPES, body)
is KtFirEnumEntrySymbol -> firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) {
val initializer = it.initializer
check(initializer is FirAnonymousObject)
@@ -11,8 +11,8 @@ import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.api.withFirDeclaration
import org.jetbrains.kotlin.idea.fir.low.level.api.api.withFirDeclarationInWriteLock
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.ValidityTokenOwner
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.tokens.assertIsValidAndAccessible
import java.lang.ref.WeakReference
@@ -58,13 +58,25 @@ internal class FirRefWithValidityCheck<out D : FirDeclaration>(fir: D, resolveSt
* Runs [action] with fir element with write action hold
* Consider using this then [action] may call some resolve
*/
inline fun <R> withFirWithPossibleResolveInside(crossinline action: (fir: D) -> R): R {
inline fun <R> withFirWithPossibleResolveInside(
phase: FirResolvePhase = FirResolvePhase.RAW_FIR,
crossinline action: (fir: D) -> R
): R {
token.assertIsValidAndAccessible()
val fir = firWeakRef.get()
?: throw EntityWasGarbageCollectedException("FirElement")
val resolveState = resolveStateWeakRef.get()
?: throw EntityWasGarbageCollectedException("FirModuleResolveState")
return fir.withFirDeclarationInWriteLock(resolveState) { action(it) }
return when (phase) {
FirResolvePhase.BODY_RESOLVE -> {
/*
The BODY_RESOLVE phase is the maximum possible phase we can resolve our declaration to
So there is not need to run whole `action` under write lock
*/
action(fir.withFirDeclarationInWriteLock(resolveState, phase) { it })
}
else -> fir.withFirDeclarationInWriteLock(resolveState, phase) { action(it) }
}
}
val resolveState