[Analysis API] add checks that KtLifetimeOwners are not leaked to other analyse scope

This commit is contained in:
Ilya Kirillov
2022-06-28 16:34:49 +02:00
parent aca8643262
commit 21fcb9ccc0
4 changed files with 22 additions and 13 deletions
+1
View File
@@ -8,6 +8,7 @@ kotlin {
} }
dependencies { dependencies {
implementation(kotlinxCollectionsImmutable())
compileOnly(project(":kotlin-reflect-api")) compileOnly(project(":kotlin-reflect-api"))
compileOnly(project(":compiler:psi")) compileOnly(project(":compiler:psi"))
@@ -22,8 +22,8 @@ public abstract class KtLifetimeTokenFactory {
public abstract val identifier: KClass<out KtLifetimeToken> public abstract val identifier: KClass<out KtLifetimeToken>
public abstract fun create(project: Project): KtLifetimeToken public abstract fun create(project: Project): KtLifetimeToken
public open fun beforeEnteringAnalysisContext() {} public open fun beforeEnteringAnalysisContext(token: KtLifetimeToken) {}
public open fun afterLeavingAnalysisContext() {} public open fun afterLeavingAnalysisContext(token: KtLifetimeToken) {}
} }
@@ -9,10 +9,10 @@ package org.jetbrains.kotlin.analysis.api.lifetime
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import org.jetbrains.kotlin.analysis.api.* import org.jetbrains.kotlin.analysis.api.*
import org.jetbrains.kotlin.analysis.api.KtAnalysisAllowanceManager import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
import kotlin.reflect.KClass import kotlin.reflect.KClass
@OptIn(KtAllowAnalysisOnEdt::class) @OptIn(KtAllowAnalysisOnEdt::class)
@@ -35,6 +35,7 @@ public class KtReadActionConfinementLifetimeToken(project: Project) : KtLifetime
if (KtAnalysisAllowanceManager.resolveIsForbiddenInActionWithName.get() != null) return false if (KtAnalysisAllowanceManager.resolveIsForbiddenInActionWithName.get() != null) return false
if (!application.isReadAccessAllowed) return false if (!application.isReadAccessAllowed) return false
if (!KtReadActionConfinementLifetimeTokenFactory.isInsideAnalysisContext()) return false if (!KtReadActionConfinementLifetimeTokenFactory.isInsideAnalysisContext()) return false
if (KtReadActionConfinementLifetimeTokenFactory.currentToken() != this) return false
return true return true
} }
@@ -46,6 +47,8 @@ public class KtReadActionConfinementLifetimeToken(project: Project) : KtLifetime
return "Resolve is forbidden in $actionName" return "Resolve is forbidden in $actionName"
} }
if (!KtReadActionConfinementLifetimeTokenFactory.isInsideAnalysisContext()) return "Called outside analyse method" if (!KtReadActionConfinementLifetimeTokenFactory.isInsideAnalysisContext()) return "Called outside analyse method"
if (KtReadActionConfinementLifetimeTokenFactory.currentToken() != this) return "Using KtLifetimeOwner from previous analysis"
error("Getting inaccessibility reason for validity token when it is accessible") error("Getting inaccessibility reason for validity token when it is accessible")
} }
@@ -63,19 +66,24 @@ public object KtReadActionConfinementLifetimeTokenFactory : KtLifetimeTokenFacto
override fun create(project: Project): KtLifetimeToken = KtReadActionConfinementLifetimeToken(project) override fun create(project: Project): KtLifetimeToken = KtReadActionConfinementLifetimeToken(project)
override fun beforeEnteringAnalysisContext() { override fun beforeEnteringAnalysisContext(token: KtLifetimeToken) {
currentAnalysisContextEnteringCount.set(currentAnalysisContextEnteringCount.get() + 1) lifetimeOwnersStack.set(lifetimeOwnersStack.get().add(token))
} }
override fun afterLeavingAnalysisContext() { override fun afterLeavingAnalysisContext(token: KtLifetimeToken) {
currentAnalysisContextEnteringCount.set(currentAnalysisContextEnteringCount.get() - 1) val stack = lifetimeOwnersStack.get()
val last = stack.last()
check(last == token)
lifetimeOwnersStack.set(stack.removeAt(stack.lastIndex))
} }
private val currentAnalysisContextEnteringCount = ThreadLocal.withInitial { 0 } private val lifetimeOwnersStack = ThreadLocal.withInitial<PersistentList<KtLifetimeToken>> { persistentListOf() }
internal fun isInsideAnalysisContext() = currentAnalysisContextEnteringCount.get() > 0 internal fun isInsideAnalysisContext() = lifetimeOwnersStack.get().size > 0
internal fun currentToken() = lifetimeOwnersStack.get().last()
} }
/** /**
* *
* @see KtAnalysisSession * @see KtAnalysisSession
@@ -75,11 +75,11 @@ public abstract class KtAnalysisSessionProvider(public val project: Project) : D
action: KtAnalysisSession.() -> R action: KtAnalysisSession.() -> R
): R { ): R {
noWriteActionInAnalyseCallChecker.beforeEnteringAnalysisContext() noWriteActionInAnalyseCallChecker.beforeEnteringAnalysisContext()
factory.beforeEnteringAnalysisContext() factory.beforeEnteringAnalysisContext(analysisSession.token)
return try { return try {
analysisSession.action() analysisSession.action()
} finally { } finally {
factory.afterLeavingAnalysisContext() factory.afterLeavingAnalysisContext(analysisSession.token)
noWriteActionInAnalyseCallChecker.afterLeavingAnalysisContext() noWriteActionInAnalyseCallChecker.afterLeavingAnalysisContext()
} }
} }