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( fun <D : FirDeclaration, R> D.withFirDeclarationInWriteLock(
resolveState: FirModuleResolveState, resolveState: FirModuleResolveState,
phase: FirResolvePhase = FirResolvePhase.RAW_FIR,
action: (D) -> R, 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] * 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.fir.scopes.impl.*
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState 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.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.ValidityTokenOwner
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeProvider 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.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.* 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.*
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.types.KtFirType
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef 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.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.KtFileSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPackageSymbol 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.KtSymbolWithDeclarations
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithMembers 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.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
@@ -61,8 +56,8 @@ internal class KtFirScopeProvider(
private val packageMemberScopeCache = IdentityHashMap<KtPackageSymbol, KtPackageScope>() private val packageMemberScopeCache = IdentityHashMap<KtPackageSymbol, KtPackageScope>()
private inline fun <T> KtSymbolWithMembers.withFirForScope(crossinline body: (FirClass<*>) -> T): T? = when (this) { private inline fun <T> KtSymbolWithMembers.withFirForScope(crossinline body: (FirClass<*>) -> T): T? = when (this) {
is KtFirNamedClassOrObjectSymbol -> firRef.withFir(FirResolvePhase.TYPES, body) is KtFirNamedClassOrObjectSymbol -> firRef.withFirWithPossibleResolveInside(FirResolvePhase.TYPES, body)
is KtFirAnonymousObjectSymbol -> firRef.withFir(FirResolvePhase.TYPES, body) is KtFirAnonymousObjectSymbol -> firRef.withFirWithPossibleResolveInside(FirResolvePhase.TYPES, body)
is KtFirEnumEntrySymbol -> firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { is KtFirEnumEntrySymbol -> firRef.withFir(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) {
val initializer = it.initializer val initializer = it.initializer
check(initializer is FirAnonymousObject) 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.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.api.withFirDeclaration 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.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.ValidityTokenOwner
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.tokens.assertIsValidAndAccessible import org.jetbrains.kotlin.idea.frontend.api.tokens.assertIsValidAndAccessible
import java.lang.ref.WeakReference 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 * Runs [action] with fir element with write action hold
* Consider using this then [action] may call some resolve * 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() token.assertIsValidAndAccessible()
val fir = firWeakRef.get() val fir = firWeakRef.get()
?: throw EntityWasGarbageCollectedException("FirElement") ?: throw EntityWasGarbageCollectedException("FirElement")
val resolveState = resolveStateWeakRef.get() val resolveState = resolveStateWeakRef.get()
?: throw EntityWasGarbageCollectedException("FirModuleResolveState") ?: 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 val resolveState