[Analysis API] forbid providing custom KtLifetimeToken for every analyze call

^KT-60488 fixed
This commit is contained in:
Ilya Kirillov
2023-07-17 14:21:09 +02:00
committed by Space Team
parent b617b24d48
commit 4b523825be
7 changed files with 41 additions and 63 deletions
@@ -17,11 +17,11 @@ import java.lang.UnsupportedOperationException
@OptIn(KtAnalysisApiInternals::class) @OptIn(KtAnalysisApiInternals::class)
class KtFe10AnalysisSessionProvider(project: Project) : KtAnalysisSessionProvider(project) { class KtFe10AnalysisSessionProvider(project: Project) : KtAnalysisSessionProvider(project) {
override fun getAnalysisSession(useSiteKtElement: KtElement, factory: KtLifetimeTokenFactory): KtAnalysisSession { override fun getAnalysisSession(useSiteKtElement: KtElement): KtAnalysisSession {
return KtFe10AnalysisSession(project, useSiteKtElement, factory.create(project)) return KtFe10AnalysisSession(project, useSiteKtElement, tokenFactory.create(project))
} }
override fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule, factory: KtLifetimeTokenFactory): KtAnalysisSession { override fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule): KtAnalysisSession {
throw UnsupportedOperationException("getAnalysisSessionByModule() should not be used on KtFe10AnalysisSession") throw UnsupportedOperationException("getAnalysisSessionByModule() should not be used on KtFe10AnalysisSession")
} }
@@ -35,17 +35,17 @@ class KtFirAnalysisSessionProvider(project: Project) : KtAnalysisSessionProvider
LowMemoryWatcher.register(::clearCaches, project) LowMemoryWatcher.register(::clearCaches, project)
} }
override fun getAnalysisSession(useSiteKtElement: KtElement, factory: KtLifetimeTokenFactory): KtAnalysisSession { override fun getAnalysisSession(useSiteKtElement: KtElement): KtAnalysisSession {
val module = ProjectStructureProvider.getModule(project, useSiteKtElement, contextualModule = null) val module = ProjectStructureProvider.getModule(project, useSiteKtElement, contextualModule = null)
return getAnalysisSessionByUseSiteKtModule(module, factory) return getAnalysisSessionByUseSiteKtModule(module)
} }
override fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule, factory: KtLifetimeTokenFactory): KtAnalysisSession { override fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule): KtAnalysisSession {
val key = Pair(useSiteKtModule, factory.identifier) val key = Pair(useSiteKtModule, tokenFactory.identifier)
return cache.computeIfAbsent(key) { return cache.computeIfAbsent(key) {
CachedValuesManager.getManager(project).createCachedValue { CachedValuesManager.getManager(project).createCachedValue {
val firResolveSession = useSiteKtModule.getFirResolveSession(project) val firResolveSession = useSiteKtModule.getFirResolveSession(project)
val validityToken = factory.create(project) val validityToken = tokenFactory.create(project)
CachedValueProvider.Result( CachedValueProvider.Result(
KtFirAnalysisSession.createAnalysisSessionByFirResolveSession(firResolveSession, validityToken), KtFirAnalysisSession.createAnalysisSessionByFirResolveSession(firResolveSession, validityToken),
@@ -10,8 +10,8 @@ import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.lifetime.KtDefaultLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementDefaultLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache
import org.jetbrains.kotlin.analysis.decompiled.light.classes.DecompiledLightClassesFactory import org.jetbrains.kotlin.analysis.decompiled.light.classes.DecompiledLightClassesFactory
@@ -38,7 +38,7 @@ object AnalysisApiBaseTestServiceRegistrar: AnalysisApiTestServiceRegistrar() {
override fun registerProjectServices(project: MockProject, testServices: TestServices) { override fun registerProjectServices(project: MockProject, testServices: TestServices) {
project.apply { project.apply {
registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java) registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java)
registerService(KtDefaultLifetimeTokenProvider::class.java, KtReadActionConfinementDefaultLifetimeTokenProvider::class.java) registerService(KtLifetimeTokenProvider::class.java, KtReadActionConfinementLifetimeTokenProvider::class.java)
//KotlinClassFileDecompiler is registered as application service so it's available for the tests run in parallel as well //KotlinClassFileDecompiler is registered as application service so it's available for the tests run in parallel as well
//when the decompiler is registered, for compiled class KtClsFile is created instead of ClsFileImpl //when the decompiler is registered, for compiled class KtClsFile is created instead of ClsFileImpl
@@ -16,8 +16,8 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.lifetime.KtDefaultLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementDefaultLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtReadActionConfinementLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.FirStandaloneServiceRegistrar import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.FirStandaloneServiceRegistrar
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.FirSealedClassInheritorsProcessorFactory import org.jetbrains.kotlin.analysis.low.level.api.fir.api.services.FirSealedClassInheritorsProcessorFactory
@@ -121,7 +121,7 @@ public class StandaloneAnalysisAPISessionBuilder(
FirStandaloneServiceRegistrar.registerProjectModelServices(project, kotlinCoreProjectEnvironment.parentDisposable) FirStandaloneServiceRegistrar.registerProjectModelServices(project, kotlinCoreProjectEnvironment.parentDisposable)
registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java) registerService(KotlinModificationTrackerFactory::class.java, KotlinStaticModificationTrackerFactory::class.java)
registerService(KtDefaultLifetimeTokenProvider::class.java, KtReadActionConfinementDefaultLifetimeTokenProvider::class.java) registerService(KtLifetimeTokenProvider::class.java, KtReadActionConfinementLifetimeTokenProvider::class.java)
registerService(KtModuleScopeProvider::class.java, KtModuleScopeProviderImpl()) registerService(KtModuleScopeProvider::class.java, KtModuleScopeProviderImpl())
registerService(KotlinAnnotationsResolverFactory::class.java, KotlinStaticAnnotationsResolverFactory(ktFiles)) registerService(KotlinAnnotationsResolverFactory::class.java, KotlinStaticAnnotationsResolverFactory(ktFiles))
@@ -22,50 +22,40 @@ import org.jetbrains.kotlin.psi.KtFile
* Uses [useSiteKtElement] as an [KtElement] which containing module is a use-site module, * Uses [useSiteKtElement] as an [KtElement] which containing module is a use-site module,
* i.e, the module from which perspective the project will be analyzed. * i.e, the module from which perspective the project will be analyzed.
* *
* [nonDefaultLifetimeTokenFactory] represents lifetime and accessibility guaranties
* which will be applied to the [org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeOwner] instances created during code analysis.
*
* @see KtAnalysisSession * @see KtAnalysisSession
* @see KtLifetimeTokenFactory
* @see analyzeWithReadAction * @see analyzeWithReadAction
*/ */
public inline fun <R> analyze( public inline fun <R> analyze(
useSiteKtElement: KtElement, useSiteKtElement: KtElement,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory? = null,
action: KtAnalysisSession.() -> R action: KtAnalysisSession.() -> R
): R = ): R =
KtAnalysisSessionProvider.getInstance(useSiteKtElement.project) KtAnalysisSessionProvider.getInstance(useSiteKtElement.project)
.analyse(useSiteKtElement, nonDefaultLifetimeTokenFactory, action) .analyse(useSiteKtElement, action)
/** /**
* Execute given [action] in [KtAnalysisSession] context * Execute given [action] in [KtAnalysisSession] context
* Uses [useSiteKtModule] as use-site module, i.e, the module from which perspective the project will be analyzed. * Uses [useSiteKtModule] as use-site module, i.e, the module from which perspective the project will be analyzed.
* *
* [nonDefaultLifetimeTokenFactory] represents lifetime and accessibility guaranties
* which will be applied to the [org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeOwner] instances created during code analysis.
*
* @see KtAnalysisSession * @see KtAnalysisSession
* @see KtLifetimeTokenFactory * @see KtLifetimeTokenFactory
*/ */
public inline fun <R> analyze( public inline fun <R> analyze(
useSiteKtModule: KtModule, useSiteKtModule: KtModule,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory? = null,
crossinline action: KtAnalysisSession.() -> R crossinline action: KtAnalysisSession.() -> R
): R { ): R {
val sessionProvider = KtAnalysisSessionProvider.getInstance(useSiteKtModule.project) val sessionProvider = KtAnalysisSessionProvider.getInstance(useSiteKtModule.project)
return sessionProvider.analyze(useSiteKtModule, nonDefaultLifetimeTokenFactory, action) return sessionProvider.analyze(useSiteKtModule, action)
} }
public inline fun <R> analyzeInDependedAnalysisSession( public inline fun <R> analyzeInDependedAnalysisSession(
originalFile: KtFile, originalFile: KtFile,
elementToReanalyze: KtElement, elementToReanalyze: KtElement,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory? = null,
action: KtAnalysisSession.() -> R action: KtAnalysisSession.() -> R
): R = ): R =
KtAnalysisSessionProvider.getInstance(originalFile.project) KtAnalysisSessionProvider.getInstance(originalFile.project)
.analyseInDependedAnalysisSession(originalFile, elementToReanalyze, nonDefaultLifetimeTokenFactory, action) .analyseInDependedAnalysisSession(originalFile, elementToReanalyze, action)
/** /**
* Execute given [action] in [KtAnalysisSession] context like [analyze] does but execute it in read action * Execute given [action] in [KtAnalysisSession] context like [analyze] does but execute it in read action
@@ -81,10 +71,9 @@ public inline fun <R> analyzeInDependedAnalysisSession(
*/ */
public inline fun <R> analyzeWithReadAction( public inline fun <R> analyzeWithReadAction(
contextElement: KtElement, contextElement: KtElement,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory? = null,
crossinline action: KtAnalysisSession.() -> R crossinline action: KtAnalysisSession.() -> R
): R = ApplicationManager.getApplication().runReadAction(Computable { ): R = ApplicationManager.getApplication().runReadAction(Computable {
analyze(contextElement, nonDefaultLifetimeTokenFactory, action) analyze(contextElement, action)
}) })
/** /**
@@ -97,13 +86,12 @@ public inline fun <R> analyzeWithReadAction(
public inline fun <R> analyzeInModalWindow( public inline fun <R> analyzeInModalWindow(
contextElement: KtElement, contextElement: KtElement,
windowTitle: String, windowTitle: String,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory? = null,
crossinline action: KtAnalysisSession.() -> R crossinline action: KtAnalysisSession.() -> R
): R { ): R {
ApplicationManager.getApplication().assertIsDispatchThread() ApplicationManager.getApplication().assertIsDispatchThread()
val task = object : Task.WithResult<R, Exception>(contextElement.project, windowTitle, /*canBeCancelled*/ true) { val task = object : Task.WithResult<R, Exception>(contextElement.project, windowTitle, /*canBeCancelled*/ true) {
override fun compute(indicator: ProgressIndicator): R = override fun compute(indicator: ProgressIndicator): R =
analyzeWithReadAction(contextElement, nonDefaultLifetimeTokenFactory) { action() } analyzeWithReadAction(contextElement) { action() }
} }
task.queue() task.queue()
return task.result return task.result
@@ -9,19 +9,19 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
@KtAnalysisApiInternals @KtAnalysisApiInternals
public abstract class KtDefaultLifetimeTokenProvider { public abstract class KtLifetimeTokenProvider {
public abstract fun getDefaultLifetimeTokenFactory(): KtLifetimeTokenFactory public abstract fun getLifetimeTokenFactory(): KtLifetimeTokenFactory
public companion object { public companion object {
@KtAnalysisApiInternals @KtAnalysisApiInternals
public fun getService(project: Project): KtDefaultLifetimeTokenProvider = public fun getService(project: Project): KtLifetimeTokenProvider =
project.getService(KtDefaultLifetimeTokenProvider::class.java) project.getService(KtLifetimeTokenProvider::class.java)
} }
} }
@KtAnalysisApiInternals @KtAnalysisApiInternals
public class KtReadActionConfinementDefaultLifetimeTokenProvider: KtDefaultLifetimeTokenProvider() { public class KtReadActionConfinementLifetimeTokenProvider : KtLifetimeTokenProvider() {
override fun getDefaultLifetimeTokenFactory(): KtLifetimeTokenFactory { override fun getLifetimeTokenFactory(): KtLifetimeTokenFactory {
return KtReadActionConfinementLifetimeTokenFactory return KtReadActionConfinementLifetimeTokenFactory
} }
} }
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals import org.jetbrains.kotlin.analysis.api.KtAnalysisApiInternals
import org.jetbrains.kotlin.analysis.api.lifetime.impl.NoWriteActionInAnalyseCallChecker import org.jetbrains.kotlin.analysis.api.lifetime.impl.NoWriteActionInAnalyseCallChecker
import org.jetbrains.kotlin.analysis.api.lifetime.KtDefaultLifetimeTokenProvider import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenProvider
import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenFactory import org.jetbrains.kotlin.analysis.api.lifetime.KtLifetimeTokenFactory
import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
@@ -21,63 +21,53 @@ import org.jetbrains.kotlin.psi.KtFile
* Provides [KtAnalysisSession] by [contextElement] * Provides [KtAnalysisSession] by [contextElement]
* Should not be used directly, consider using [analyse]/[analyzeWithReadAction]/[analyzeInModalWindow] instead * Should not be used directly, consider using [analyse]/[analyzeWithReadAction]/[analyzeInModalWindow] instead
*/ */
@KtAnalysisApiInternals @OptIn(KtAnalysisApiInternals::class)
public abstract class KtAnalysisSessionProvider(public val project: Project) : Disposable { public abstract class KtAnalysisSessionProvider(public val project: Project) : Disposable {
@KtAnalysisApiInternals
public val tokenFactory: KtLifetimeTokenFactory = KtLifetimeTokenProvider.getService(project).getLifetimeTokenFactory()
@Suppress("LeakingThis") @Suppress("LeakingThis")
public val noWriteActionInAnalyseCallChecker: NoWriteActionInAnalyseCallChecker = NoWriteActionInAnalyseCallChecker(this) public val noWriteActionInAnalyseCallChecker: NoWriteActionInAnalyseCallChecker = NoWriteActionInAnalyseCallChecker(this)
public abstract fun getAnalysisSession(useSiteKtElement: KtElement, factory: KtLifetimeTokenFactory): KtAnalysisSession public abstract fun getAnalysisSession(useSiteKtElement: KtElement): KtAnalysisSession
public abstract fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule, factory: KtLifetimeTokenFactory): KtAnalysisSession public abstract fun getAnalysisSessionByUseSiteKtModule(useSiteKtModule: KtModule): KtAnalysisSession
public inline fun <R> analyseInDependedAnalysisSession( public inline fun <R> analyseInDependedAnalysisSession(
originalFile: KtFile, originalFile: KtFile,
elementToReanalyze: KtElement, elementToReanalyze: KtElement,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory?, action: KtAnalysisSession.() -> R,
action: KtAnalysisSession.() -> R
): R { ): R {
val factory = val originalAnalysisSession = getAnalysisSession(originalFile)
nonDefaultLifetimeTokenFactory ?: KtDefaultLifetimeTokenProvider.getService(project).getDefaultLifetimeTokenFactory()
val originalAnalysisSession = getAnalysisSession(originalFile, factory)
val dependedAnalysisSession = originalAnalysisSession val dependedAnalysisSession = originalAnalysisSession
.createContextDependentCopy(originalFile, elementToReanalyze) .createContextDependentCopy(originalFile, elementToReanalyze)
return analyse(dependedAnalysisSession, factory, action) return analyse(dependedAnalysisSession, action)
} }
public inline fun <R> analyse( public inline fun <R> analyse(
useSiteKtElement: KtElement, useSiteKtElement: KtElement,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory?, action: KtAnalysisSession.() -> R,
action: KtAnalysisSession.() -> R
): R { ): R {
val factory = return analyse(getAnalysisSession(useSiteKtElement), action)
nonDefaultLifetimeTokenFactory ?: KtDefaultLifetimeTokenProvider.getService(project).getDefaultLifetimeTokenFactory()
return analyse(getAnalysisSession(useSiteKtElement, factory), factory, action)
} }
public inline fun <R> analyze( public inline fun <R> analyze(
useSiteKtModule: KtModule, useSiteKtModule: KtModule,
nonDefaultLifetimeTokenFactory: KtLifetimeTokenFactory?, action: KtAnalysisSession.() -> R,
action: KtAnalysisSession.() -> R
): R { ): R {
val factory = return analyse(getAnalysisSessionByUseSiteKtModule(useSiteKtModule), action)
nonDefaultLifetimeTokenFactory ?: KtDefaultLifetimeTokenProvider.getService(project).getDefaultLifetimeTokenFactory()
return analyse(getAnalysisSessionByUseSiteKtModule(useSiteKtModule, factory), factory, action)
} }
public inline fun <R> analyse( public inline fun <R> analyse(
analysisSession: KtAnalysisSession, analysisSession: KtAnalysisSession,
factory: KtLifetimeTokenFactory, action: KtAnalysisSession.() -> R,
action: KtAnalysisSession.() -> R
): R { ): R {
noWriteActionInAnalyseCallChecker.beforeEnteringAnalysisContext() noWriteActionInAnalyseCallChecker.beforeEnteringAnalysisContext()
factory.beforeEnteringAnalysisContext(analysisSession.token) tokenFactory.beforeEnteringAnalysisContext(analysisSession.token)
return try { return try {
analysisSession.action() analysisSession.action()
} finally { } finally {
factory.afterLeavingAnalysisContext(analysisSession.token) tokenFactory.afterLeavingAnalysisContext(analysisSession.token)
noWriteActionInAnalyseCallChecker.afterLeavingAnalysisContext() noWriteActionInAnalyseCallChecker.afterLeavingAnalysisContext()
} }
} }