FIR IDE: add validity check of analysis component that it is valid only in analysis context

This commit is contained in:
Ilya Kirillov
2021-02-19 19:29:20 +01:00
parent bd38363077
commit 29bebae37d
6 changed files with 49 additions and 16 deletions
@@ -17,7 +17,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalKtFile
import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
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.getAnalysisSessionFor import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScopeNameFilter import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScopeNameFilter
@@ -133,7 +133,7 @@ private class KotlinCommonCompletionProvider(
val explicitReceiver = nameExpression.getReceiverExpression() val explicitReceiver = nameExpression.getReceiverExpression()
with(getAnalysisSessionFor(originalFile).createContextDependentCopy(originalFile, nameExpression)) { analyseInFakeAnalysisSession(originalFile, nameExpression) {
val expectedType = nameExpression.getExpectedType() val expectedType = nameExpression.getExpectedType()
val scopesContext = originalFile.getScopeContextForPosition(nameExpression) val scopesContext = originalFile.getScopeContextForPosition(nameExpression)
@@ -11,10 +11,14 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task import com.intellij.openapi.progress.Task
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
@RequiresOptIn("To use analysis session, consider using analyze/analyzeWithReadAction/analyseInModalWindow methods") @RequiresOptIn("To use analysis session, consider using analyze/analyzeWithReadAction/analyseInModalWindow methods")
annotation class InvalidWayOfUsingAnalysisSession annotation class InvalidWayOfUsingAnalysisSession
@RequiresOptIn
annotation class KtAnalysisSessionProviderInternals
/** /**
* Provides [KtAnalysisSession] by [contextElement] * Provides [KtAnalysisSession] by [contextElement]
* Should not be used directly, consider using [analyse]/[analyseWithReadAction]/[analyseInModalWindow] instead * Should not be used directly, consider using [analyse]/[analyseWithReadAction]/[analyseInModalWindow] instead
@@ -23,11 +27,38 @@ annotation class InvalidWayOfUsingAnalysisSession
abstract class KtAnalysisSessionProvider { abstract class KtAnalysisSessionProvider {
@InvalidWayOfUsingAnalysisSession @InvalidWayOfUsingAnalysisSession
abstract fun getAnalysisSessionFor(contextElement: KtElement): KtAnalysisSession abstract fun getAnalysisSessionFor(contextElement: KtElement): KtAnalysisSession
}
@InvalidWayOfUsingAnalysisSession @InvalidWayOfUsingAnalysisSession
fun getAnalysisSessionFor(contextElement: KtElement): KtAnalysisSession = inline fun <R> analyseInFakeAnalysisSession(originalFile: KtFile, fakeExpresion: KtElement, action: KtAnalysisSession.() -> R): R {
contextElement.project.service<KtAnalysisSessionProvider>().getAnalysisSessionFor(contextElement) val fakeAnalysisSession = getAnalysisSessionFor(originalFile).createContextDependentCopy(originalFile, fakeExpresion)
return analyse(fakeAnalysisSession, action)
}
@InvalidWayOfUsingAnalysisSession
inline fun <R> analyse(contextElement: KtElement, action: KtAnalysisSession.() -> R): R =
analyse(getAnalysisSessionFor(contextElement), action)
@OptIn(KtAnalysisSessionProviderInternals::class)
@InvalidWayOfUsingAnalysisSession
inline fun <R> analyse(analysisSession: KtAnalysisSession, action: KtAnalysisSession.() -> R): R {
currentAnalysisContextEnteringCount.set(currentAnalysisContextEnteringCount.get() + 1)
return try {
analysisSession.action()
} finally {
currentAnalysisContextEnteringCount.set(currentAnalysisContextEnteringCount.get() - 1)
}
}
companion object {
@KtAnalysisSessionProviderInternals
val currentAnalysisContextEnteringCount = object : ThreadLocal<Int>() {
override fun initialValue() = 0
}
@OptIn(KtAnalysisSessionProviderInternals::class)
fun isInsideAnalysisContext() = currentAnalysisContextEnteringCount.get() > 0
}
}
/** /**
* Execute given [action] in [KtAnalysisSession] context * Execute given [action] in [KtAnalysisSession] context
@@ -43,8 +74,11 @@ fun getAnalysisSessionFor(contextElement: KtElement): KtAnalysisSession =
*/ */
@OptIn(InvalidWayOfUsingAnalysisSession::class) @OptIn(InvalidWayOfUsingAnalysisSession::class)
inline fun <R> analyse(contextElement: KtElement, action: KtAnalysisSession.() -> R): R = inline fun <R> analyse(contextElement: KtElement, action: KtAnalysisSession.() -> R): R =
getAnalysisSessionFor(contextElement).action() contextElement.project.service<KtAnalysisSessionProvider>().analyse(contextElement, action)
@OptIn(InvalidWayOfUsingAnalysisSession::class)
inline fun <R> analyseInFakeAnalysisSession(originalFile: KtFile, fakeExpresion: KtElement, action: KtAnalysisSession.() -> R): R =
originalFile.project.service<KtAnalysisSessionProvider>().analyseInFakeAnalysisSession(originalFile, fakeExpresion, action)
/** /**
* Execute given [action] in [KtAnalysisSession] context like [analyse] does but execute it in read action * Execute given [action] in [KtAnalysisSession] context like [analyse] does but execute it in read action
@@ -23,16 +23,17 @@ class ReadActionConfinementValidityToken(project: Project) : ValidityToken() {
error("Getting invalidation reason for valid validity token") error("Getting invalidation reason for valid validity token")
} }
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class, ForbidKtResolveInternals::class) @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class, ForbidKtResolveInternals::class, InvalidWayOfUsingAnalysisSession::class)
override fun isAccessible(): Boolean { override fun isAccessible(): Boolean {
val application = ApplicationManager.getApplication() val application = ApplicationManager.getApplication()
if (application.isDispatchThread && !allowOnEdt.get()) return false if (application.isDispatchThread && !allowOnEdt.get()) return false
if (ForbidKtResolve.resovleIsForbidenInActionWithName.get() != null) return false if (ForbidKtResolve.resovleIsForbidenInActionWithName.get() != null) return false
if (!application.isReadAccessAllowed) return false if (!application.isReadAccessAllowed) return false
if (!KtAnalysisSessionProvider.isInsideAnalysisContext()) return false
return true return true
} }
@OptIn(HackToForceAllowRunningAnalyzeOnEDT::class, ForbidKtResolveInternals::class) @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class, ForbidKtResolveInternals::class, InvalidWayOfUsingAnalysisSession::class)
override fun getInaccessibilityReason(): String { override fun getInaccessibilityReason(): String {
val application = ApplicationManager.getApplication() val application = ApplicationManager.getApplication()
if (application.isDispatchThread && !allowOnEdt.get()) return "Called in EDT thread" if (application.isDispatchThread && !allowOnEdt.get()) return "Called in EDT thread"
@@ -40,6 +41,7 @@ class ReadActionConfinementValidityToken(project: Project) : ValidityToken() {
ForbidKtResolve.resovleIsForbidenInActionWithName.get()?.let { actionName -> ForbidKtResolve.resovleIsForbidenInActionWithName.get()?.let { actionName ->
return "Resolve is forbidden in $actionName" return "Resolve is forbidden in $actionName"
} }
if (!KtAnalysisSessionProvider.isInsideAnalysisContext()) return "Called outside analyse method"
error("Getting inaccessibility reason for validity token when it is accessible") error("Getting inaccessibility reason for validity token when it is accessible")
} }
@@ -34,9 +34,6 @@ private constructor(
token: ValidityToken, token: ValidityToken,
val context: KtFirAnalysisSessionContext, val context: KtFirAnalysisSessionContext,
) : KtAnalysisSession(token) { ) : KtAnalysisSession(token) {
init {
assertIsValidAndAccessible()
}
override val smartCastProviderImpl = KtFirSmartcastProvider(this, token) override val smartCastProviderImpl = KtFirSmartcastProvider(this, token)
@@ -38,8 +38,6 @@ class KtFirAnalysisSessionProvider(project: Project) : KtAnalysisSessionProvider
return analysisSessionByModuleInfoCache.value.getOrPut(contextElement.getModuleInfo()) { return analysisSessionByModuleInfoCache.value.getOrPut(contextElement.getModuleInfo()) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
KtFirAnalysisSession.createForElement(contextElement) KtFirAnalysisSession.createForElement(contextElement)
}.apply {
assertIsValidAndAccessible()
} }
} }
@@ -60,8 +60,10 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") LightCodeInsig
buildString { buildString {
append(textWithoutLatestComments) append(textWithoutLatestComments)
append("\n\n") append("\n\n")
callInfos.joinTo(this, separator = "\n") { info -> analyse(file as KtFile) {
"// CALL: ${info?.stringRepresentation()}" callInfos.joinTo(this@buildString, separator = "\n") { info ->
"// CALL: ${info?.stringRepresentation()}"
}
} }
} }
} }