diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/EnterResult.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/EnterResult.kt new file mode 100644 index 0000000000000..7fbfbb46d6b41 --- /dev/null +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/EnterResult.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder + +enum class EnterResult { + CYCLE, + SUCCESS, + ; +} diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt index 8e46f0d123f68..49c399f34c401 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/builder/LLFirLockProvider.kt @@ -5,21 +5,16 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.registry.Registry import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirLazyResolveContractChecker -import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries import org.jetbrains.kotlin.analysis.low.level.api.fir.util.lockWithPCECheck import org.jetbrains.kotlin.fir.FirElementWithResolveState import org.jetbrains.kotlin.fir.declarations.* -import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicReferenceFieldUpdater import java.util.concurrent.locks.ReentrantLock -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Keyed locks provider. @@ -42,6 +37,8 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh phase: FirResolvePhase, action: () -> Unit, ) { + if (!implicitPhaseLockEnabled) return action() + val lock = when (phase) { FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE -> implicitTypesLock else -> null @@ -109,17 +106,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh action: () -> Unit, ) { checker.lazyResolveToPhaseInside(phase) { - target.withCriticalSection(toPhase = phase, updatePhase = updatePhase, action = action) - } - } - - inline fun withJumpingLock( - target: FirElementWithResolveState, - phase: FirResolvePhase, - action: () -> Unit, - ) { - checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) { - target.withCriticalSection(toPhase = phase, updatePhase = true, action = action) + target.withNonJumpingLock(toPhase = phase, updatePhase = updatePhase, action = action) } } @@ -143,7 +130,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh * - If some other thread tries to resolve current [FirElementWithResolveState], it changes `resolveState` and puts the barrier there. Then it awaits on it until the initial thread which hold the lock finishes its job. * - This way, no barrier is used in a case when no contention arise. */ - private inline fun FirElementWithResolveState.withCriticalSection( + private inline fun FirElementWithResolveState.withNonJumpingLock( toPhase: FirResolvePhase, updatePhase: Boolean, action: () -> Unit, @@ -172,7 +159,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh } is FirResolvedToPhaseState -> { - if (!tryLock(toPhase, stateSnapshot)) continue + if (!tryNonJumpingLock(toPhase, stateSnapshot)) continue var exceptionOccurred = false try { @@ -182,11 +169,15 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh throw e } finally { val newPhase = if (updatePhase && !exceptionOccurred) toPhase else stateSnapshot.resolvePhase - unlock(toPhase = newPhase) + nonJumpingUnlock(toPhase = newPhase) } return } + + is FirInProcessOfResolvingToJumpingPhaseState -> { + errorWithFirSpecificEntries("$stateSnapshot state are not allowed to be inside non-jumping lock", fir = this) + } } } } @@ -201,12 +192,11 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh toPhase: FirResolvePhase, stateSnapshot: FirResolveState, ) { - val latch = CountDownLatch(1) - val newState = FirInProcessOfResolvingToPhaseStateWithBarrier(toPhase, latch) + val newState = FirInProcessOfResolvingToPhaseStateWithBarrier(toPhase) resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState) } - private fun FirElementWithResolveState.tryLock( + private fun FirElementWithResolveState.tryNonJumpingLock( toPhase: FirResolvePhase, stateSnapshot: FirResolveState, ): Boolean { @@ -214,17 +204,123 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh return resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState) } - private fun FirElementWithResolveState.unlock(toPhase: FirResolvePhase) { + private fun FirElementWithResolveState.nonJumpingUnlock(toPhase: FirResolvePhase) { when (val stateSnapshotAfter = resolveStateFieldUpdater.getAndSet(this, FirResolvedToPhaseState(toPhase))) { is FirInProcessOfResolvingToPhaseStateWithoutBarrier -> {} is FirInProcessOfResolvingToPhaseStateWithBarrier -> { stateSnapshotAfter.barrier.countDown() } - is FirResolvedToPhaseState -> { - error("phase is unexpectedly unlocked $stateSnapshotAfter") + is FirResolvedToPhaseState, is FirInProcessOfResolvingToJumpingPhaseState -> { + errorWithFirSpecificEntries("phase is unexpectedly unlocked $stateSnapshotAfter", fir = this) } } } + + inline fun withJumpingLock( + target: FirElementWithResolveState, + phase: FirResolvePhase, + session: FirJumpingResolveSession, + actionUnderLock: () -> Unit, + cycleAction: () -> Unit, + ) { + checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) { + target.withJumpingLockImpl(phase, session, actionUnderLock, cycleAction) + } + } + + private inline fun FirElementWithResolveState.withJumpingLockImpl( + toPhase: FirResolvePhase, + session: FirJumpingResolveSession, + actionUnderLock: () -> Unit, + cycleAction: () -> Unit, + ) { + while (true) { + checkCanceled() + + @OptIn(ResolveStateAccess::class) + val stateSnapshot = resolveState + if (stateSnapshot.resolvePhase >= toPhase) { + // already resolved by some other thread + return + } + + when (stateSnapshot) { + is FirResolvedToPhaseState -> { + if (!tryJumpingLock(toPhase, stateSnapshot, session)) continue + + var exceptionOccurred = false + try { + actionUnderLock() + } catch (e: Throwable) { + exceptionOccurred = true + throw e + } finally { + val newPhase = if (!exceptionOccurred) toPhase else stateSnapshot.resolvePhase + jumpingUnlock(toPhase = newPhase, session) + } + + return + } + + is FirInProcessOfResolvingToJumpingPhaseState -> { + val previousState = session.states.lastOrNull() + if (previousState != null) { + // Check for multi-thread cycles + // all writes to waitingFor will be consistent, as it is the last write if we have cycle + previousState.waitingFor = stateSnapshot + var next: FirInProcessOfResolvingToJumpingPhaseState? = stateSnapshot + while (next != null) { + if (next === previousState) { + previousState.waitingFor = null + + // Do I need it??? +// previousState.latch.countDown() + + return cycleAction() + } + + next = next.waitingFor + } + } + + try { + stateSnapshot.latch.await(DEFAULT_LOCKING_INTERVAL, TimeUnit.MILLISECONDS) + } finally { + previousState?.waitingFor = null + } + } + + is FirInProcessOfResolvingToPhaseStateWithoutBarrier, is FirInProcessOfResolvingToPhaseStateWithBarrier -> { + errorWithFirSpecificEntries("$stateSnapshot state are not allowed to be inside non-jumping lock", fir = this) + } + } + } + } + + private fun FirElementWithResolveState.tryJumpingLock( + toPhase: FirResolvePhase, + stateSnapshot: FirResolveState, + session: FirJumpingResolveSession, + ): Boolean { + val newState = FirInProcessOfResolvingToJumpingPhaseState(toPhase) + val isSucceed = resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState) + if (!isSucceed) return false + + session.states.lastOrNull()?.waitingFor = newState + session.states += newState + + return true + } + + private fun FirElementWithResolveState.jumpingUnlock(toPhase: FirResolvePhase, session: FirJumpingResolveSession) { + val currentState = session.states.removeLast() + val prevState = session.states.lastOrNull() + require(prevState == null || prevState.waitingFor == currentState) + prevState?.waitingFor = null + + resolveStateFieldUpdater.set(this, FirResolvedToPhaseState(toPhase)) + currentState.latch.countDown() + } } private val resolveStateFieldUpdater = AtomicReferenceFieldUpdater.newUpdater( @@ -237,4 +333,8 @@ private val globalLockEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) Registry.`is`("kotlin.parallel.resolve.under.global.lock", false) } +private val implicitPhaseLockEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { + Registry.`is`("kotlin.implicit.resolve.phase.under.global.lock", false) +} + private const val DEFAULT_LOCKING_INTERVAL = 50L \ No newline at end of file diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt index 722dcdc1e1673..b013191b92bb1 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirBodyLazyResolver.kt @@ -130,7 +130,7 @@ private class LLFirBodyTargetResolver( // resolve class CFG graph here, to do this we need to have property & init blocks resoled resolveMembersForControlFlowGraph(target) - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { calculateControlFlowGraph(target) } @@ -143,7 +143,7 @@ private class LLFirBodyTargetResolver( // resolve file CFG graph here, to do this we need to have property blocks resoled resolveMembersForControlFlowGraph(target) - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { calculateControlFlowGraph(target) } @@ -157,7 +157,7 @@ private class LLFirBodyTargetResolver( } is FirCodeFragment -> { resolveCodeFragmentContext(target) - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { resolve(target, BodyStateKeepers.CODE_FRAGMENT) } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt index 72c39b51b9fa7..abd47a99f65ee 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirCompilerAnnotationsLazyResolver.kt @@ -58,7 +58,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver( lockProvider: LLFirLockProvider, scopeSession: ScopeSession, computationSession: LLFirCompilerRequiredAnnotationsComputationSession? = null, -) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS, isJumpingPhase = false) { +) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS) { inner class LLFirCompilerRequiredAnnotationsComputationSession : CompilerRequiredAnnotationsComputationSession() { override fun resolveAnnotationSymbol(symbol: FirRegularClassSymbol, scopeSession: ScopeSession) { val regularClass = symbol.fir @@ -134,7 +134,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver( // 4. Exit if there are no applicable annotations, so we can just update the phase if (annotationTransformer.isNothingToResolve()) { - return performCustomResolveUnderLock(target) { + return performCustomResolveUnderNonJumpingWriteLock(target) { // just update deprecations annotationTransformer.publishResult(target) } @@ -150,7 +150,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver( annotationTransformer.calculateDeprecations(target) // 8. Publish result - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { annotationTransformer.publishResult(target) } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt index c66056149a29f..fccd5ed192afe 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirImplicitTypesLazyResolver.kt @@ -26,9 +26,11 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.util.setMultimapOf +import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry import org.jetbrains.kotlin.fir.utils.exceptions.withFirSymbolEntry import org.jetbrains.kotlin.fir.visitors.transformSingle import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment +import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment internal object LLFirImplicitTypesLazyResolver : LLFirLazyResolver(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { override fun resolve( @@ -47,7 +49,7 @@ internal object LLFirImplicitTypesLazyResolver : LLFirLazyResolver(FirResolvePha } } -internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComputationSession() { +internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComputationSession(), FirJumpingResolveSession { /** * The symbol on which foreign annotations will be postponed * @@ -105,6 +107,17 @@ internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComp fun postponedSymbols(target: FirCallableDeclaration): Collection> { return postponedSymbols[target.symbol] } + + override val states: MutableList = mutableListOf() + + private var cycledSymbol: FirCallableSymbol<*>? = null + + fun pushCycledSymbol(symbol: FirCallableSymbol<*>) { + requireWithAttachment(cycledSymbol == null, { "Nested recursion is not allowed" }) + cycledSymbol = symbol + } + + fun popCycledSymbol(): FirCallableSymbol<*>? = cycledSymbol?.also { cycledSymbol = null } } internal class LLFirImplicitBodyTargetResolver( @@ -139,6 +152,16 @@ internal class LLFirImplicitBodyTargetResolver( } } + override val jumpingResolveSession: FirJumpingResolveSession get() = llImplicitBodyResolveComputationSession + + override fun handleResolutionCycle(target: FirElementWithResolveState) { + requireWithAttachment(target is FirCallableDeclaration, { "Resolution cycle is supposed to be only for callable declaration" }) { + withFirEntry("target", target) + } + + llImplicitBodyResolveComputationSession.pushCycledSymbol(target.symbol) + } + override fun doLazyResolveUnderLock(target: FirElementWithResolveState) { when { target is FirCallableDeclaration && target.isCopyCreatedInScope -> { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirReturnTypeCalculatorWithJump.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirReturnTypeCalculatorWithJump.kt index 0e4ceeab4d331..ac9d5c5d3d51a 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirReturnTypeCalculatorWithJump.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirReturnTypeCalculatorWithJump.kt @@ -35,18 +35,23 @@ internal class LLFirReturnTypeCalculatorWithJump( val designation = declaration.collectDesignationWithFile().asResolveTarget() val targetSession = designation.target.llFirSession + val computationSession = implicitBodyResolveComputationSession as LLImplicitBodyResolveComputationSession val resolver = LLFirImplicitBodyTargetResolver( designation, lockProvider = lockProvider, scopeSession = targetSession.getScopeSession(), firResolveContextCollector = towerDataContextCollector, - llImplicitBodyResolveComputationSessionParameter = implicitBodyResolveComputationSession as LLImplicitBodyResolveComputationSession, + llImplicitBodyResolveComputationSessionParameter = computationSession, ) lockProvider.withGlobalPhaseLock(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { resolver.resolveDesignation() } + if (computationSession.popCycledSymbol() == declaration.symbol) { + return recursionInImplicitTypeRef() + } + LLFirImplicitTypesLazyResolver.checkIsResolved(designation) return declaration.returnTypeRef as FirResolvedTypeRef } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirStatusLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirStatusLazyResolver.kt index abde386217b43..22ed68cde8a5c 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirStatusLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirStatusLazyResolver.kt @@ -112,7 +112,7 @@ private class LLFirStatusTargetResolver( scopeSession: ScopeSession, private val statusComputationSession: LLStatusComputationSession = LLStatusComputationSession(target.session), private val resolveMode: StatusResolveMode, -) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.STATUS, isJumpingPhase = false) { +) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.STATUS) { private val transformer = Transformer(resolveTargetSession, scopeSession) @Deprecated("Should never be called directly, only for override purposes, please use withRegularClass", level = DeprecationLevel.ERROR) @@ -181,7 +181,7 @@ private class LLFirStatusTargetResolver( ) { if (target.resolvePhase >= resolverPhase) return val overriddenDeclarations = getOverridden(target) - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { transform(target, overriddenDeclarations) } } @@ -202,7 +202,7 @@ private class LLFirStatusTargetResolver( statusComputationSession.withClass(firClass, transformer::forceResolveStatusesOfSupertypes) } - performCustomResolveUnderLock(firClass) { + performCustomResolveUnderNonJumpingWriteLock(firClass) { transformer.transformClassStatus(firClass) transformer.transformValueClassRepresentation(firClass) transformer.storeClass(firClass) { diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirSupertypeLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirSupertypeLazyResolver.kt index b89f86b195042..62640743bdedd 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirSupertypeLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirSupertypeLazyResolver.kt @@ -65,7 +65,7 @@ private class LLFirSuperTypeTargetResolver( private val scopeSession: ScopeSession, private val supertypeComputationSession: LLFirSupertypeComputationSession = LLFirSupertypeComputationSession(target.session), private val visitedElements: MutableSet = hashSetOf(), -) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.SUPER_TYPES, isJumpingPhase = false) { +) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.SUPER_TYPES) { private val supertypeResolver = object : FirSupertypeResolverVisitor( session = resolveTargetSession, supertypeComputationSession = supertypeComputationSession, @@ -123,7 +123,7 @@ private class LLFirSuperTypeTargetResolver( superTypeUpdater = { target.replaceExpandedTypeRef(it.single()) }, ) else -> { - performCustomResolveUnderLock(target) { + performCustomResolveUnderNonJumpingWriteLock(target) { // just update the phase } } @@ -175,7 +175,7 @@ private class LLFirSuperTypeTargetResolver( val resultedTypeRefs = loopedSuperTypeRefs ?: resolvedSuperTypeRefs // 5. Publish the result - performCustomResolveUnderLock(declaration) { + performCustomResolveUnderNonJumpingWriteLock(declaration) { superTypeUpdater(resultedTypeRefs) } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTargetResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTargetResolver.kt index cccd13bd13ee9..99406f39e3ca2 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTargetResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTargetResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.api.targets.* import org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder.LLFirLockProvider import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSession import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkPhase +import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElementWithResolveState import org.jetbrains.kotlin.fir.FirFileAnnotationsContainer @@ -17,6 +18,7 @@ import org.jetbrains.kotlin.fir.declarations.FirConstructor import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.FirField import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirJumpingResolveSession import org.jetbrains.kotlin.fir.declarations.FirProperty import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirResolvePhase @@ -31,6 +33,7 @@ import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry import org.jetbrains.kotlin.resolve.DataClassResolver import org.jetbrains.kotlin.utils.exceptions.checkWithAttachment import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment +import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment internal abstract class LLFirTargetResolver( protected val resolveTarget: LLFirResolveTarget, @@ -166,24 +169,42 @@ internal abstract class LLFirTargetResolver( resolveDependencies(target) if (doResolveWithoutLock(target)) return - performCustomResolveUnderLock(target) { - doLazyResolveUnderLock(target) + + if (isJumpingPhase) { + lockProvider.withJumpingLock( + target, + resolverPhase, + jumpingResolveSession, + actionUnderLock = { + doLazyResolveUnderLock(target) + LLFirLazyPhaseResolverByPhase.getByPhase(resolverPhase).updatePhaseForDeclarationInternals(target) + }, + cycleAction = { + handleResolutionCycle(target) + } + ) + } else { + performCustomResolveUnderNonJumpingWriteLock(target) { + doLazyResolveUnderLock(target) + } } } - protected inline fun performCustomResolveUnderLock(target: FirElementWithResolveState, crossinline action: () -> Unit) { + open val jumpingResolveSession: FirJumpingResolveSession get() = throw UnsupportedOperationException("${this::class.simpleName} doesn't support jumping lock") + + protected open fun handleResolutionCycle(target: FirElementWithResolveState) { + errorWithFirSpecificEntries("Resolution cycle is detected", fir = target) + } + + protected inline fun performCustomResolveUnderNonJumpingWriteLock(target: FirElementWithResolveState, crossinline action: () -> Unit) { checkThatResolvedAtLeastToPreviousPhase(target) - withPossiblyJumpingLock(target) { - action() - LLFirLazyPhaseResolverByPhase.getByPhase(resolverPhase).updatePhaseForDeclarationInternals(target) + requireWithAttachment(!isJumpingPhase, { "This function cannot be called for jumping phase" }) { + withFirEntry("target", target) } - } - private inline fun withPossiblyJumpingLock(target: FirElementWithResolveState, action: () -> Unit) { - if (isJumpingPhase) { - lockProvider.withJumpingLock(target, resolverPhase, action) - } else { - lockProvider.withWriteLock(target, resolverPhase, action) + lockProvider.withWriteLock(target, resolverPhase) { + action() + LLFirLazyPhaseResolverByPhase.getByPhase(resolverPhase).updatePhaseForDeclarationInternals(target) } } diff --git a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt index 6e63843002a2d..3fc2832fadd20 100644 --- a/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt +++ b/analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/transformers/LLFirTypeLazyResolver.kt @@ -89,7 +89,7 @@ private class LLFirTypeTargetResolver( override fun withRegularClassImpl(firClass: FirRegularClass, action: () -> Unit) { firClass.lazyResolveToPhase(resolverPhase.previous) transformer.withClassDeclarationCleanup(firClass) { - performCustomResolveUnderLock(firClass) { + performCustomResolveUnderNonJumpingWriteLock(firClass) { resolveClassTypes(firClass) } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt index 8abc1df24eca2..f005a6b6dfeff 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirImplicitBodyResolve.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirSyntheticPropertySymbol import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase +import org.jetbrains.kotlin.fir.types.FirErrorTypeRef import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef @@ -271,13 +272,15 @@ open class ReturnTypeCalculatorWithJump( } } + + protected fun recursionInImplicitTypeRef(): FirErrorTypeRef = buildErrorTypeRef { + diagnostic = ConeSimpleDiagnostic("cycle", DiagnosticKind.RecursionInImplicitTypes) + } + private fun computeReturnTypeRef(declaration: FirCallableDeclaration): FirResolvedTypeRef { val computedReturnType = when (val status = implicitBodyResolveComputationSession.getStatus(declaration.symbol)) { is ImplicitBodyResolveComputationStatus.Computed -> status.resolvedTypeRef - is ImplicitBodyResolveComputationStatus.Computing -> buildErrorTypeRef { - diagnostic = ConeSimpleDiagnostic("cycle", DiagnosticKind.RecursionInImplicitTypes) - } - + is ImplicitBodyResolveComputationStatus.Computing -> recursionInImplicitTypeRef() else -> null } diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolveState.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolveState.kt index f1316a965e54e..f786c0d8313ed 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolveState.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirResolveState.kt @@ -99,10 +99,9 @@ class FirInProcessOfResolvingToPhaseStateWithoutBarrier private constructor( * * @see FirResolveState */ -class FirInProcessOfResolvingToPhaseStateWithBarrier( - override val resolvingTo: FirResolvePhase, - val barrier: CountDownLatch, -) : FirInProcessOfResolvingToPhaseState() { +class FirInProcessOfResolvingToPhaseStateWithBarrier(override val resolvingTo: FirResolvePhase) : FirInProcessOfResolvingToPhaseState() { + val barrier: CountDownLatch = CountDownLatch(1) + init { require(resolvingTo != FirResolvePhase.RAW_FIR) { "Cannot resolve to ${FirResolvePhase.RAW_FIR} as it's a first phase" @@ -110,4 +109,17 @@ class FirInProcessOfResolvingToPhaseStateWithBarrier( } override fun toString(): String = "ResolvingToWithBarrier($resolvingTo)" -} \ No newline at end of file +} + +class FirInProcessOfResolvingToJumpingPhaseState(override val resolvingTo: FirResolvePhase) : FirInProcessOfResolvingToPhaseState() { + val latch = CountDownLatch(1) + + @Volatile + var waitingFor: FirInProcessOfResolvingToJumpingPhaseState? = null + + override fun toString(): String = "ResolvingJumpingTo($resolvingTo)" +} + +interface FirJumpingResolveSession { + val states: MutableList +}