diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/annnotations.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/annnotations.kt index a126fd7bef2..c1ccab11077 100644 --- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/annnotations.kt +++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/annnotations.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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. */ @@ -11,3 +11,6 @@ public annotation class KtAnalysisApiInternals @RequiresOptIn("Analysis should not be allowed to be ran from EDT thread, otherwise it may cause IDE freezes") public annotation class KtAllowAnalysisOnEdt +@RequiresOptIn("Analysis should not be allowed to be ran from write action, otherwise it may cause IDE freezes and incorrect behavior in some cases") +public annotation class KtAllowAnalysisFromWriteAction + diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/lifetime/KtReadActionConfinementLifetimeToken.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/lifetime/KtReadActionConfinementLifetimeToken.kt index 18ed279d04b..8409f725586 100644 --- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/lifetime/KtReadActionConfinementLifetimeToken.kt +++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/lifetime/KtReadActionConfinementLifetimeToken.kt @@ -1,9 +1,9 @@ /* - * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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. */ -@file:OptIn(KtAnalysisApiInternals::class) +@file:OptIn(KtAnalysisApiInternals::class, KtAllowProhibitedAnalyzeFromWriteAction::class) package org.jetbrains.kotlin.analysis.api.lifetime @@ -31,6 +31,7 @@ public class KtReadActionConfinementLifetimeToken(project: Project) : KtLifetime override fun isAccessible(): Boolean { val application = ApplicationManager.getApplication() if (application.isDispatchThread && !allowOnEdt.get()) return false + if (application.isWriteAccessAllowed && !allowFromWriteAction.get()) return false if (KtAnalysisAllowanceManager.resolveIsForbiddenInActionWithName.get() != null) return false if (!application.isReadAccessAllowed) return false if (!KtReadActionConfinementLifetimeTokenFactory.isInsideAnalysisContext()) return false @@ -41,6 +42,7 @@ public class KtReadActionConfinementLifetimeToken(project: Project) : KtLifetime override fun getInaccessibilityReason(): String { val application = ApplicationManager.getApplication() if (application.isDispatchThread && !allowOnEdt.get()) return "Called in EDT thread" + if (application.isWriteAccessAllowed && !allowFromWriteAction.get()) return "Called from write action" if (!application.isReadAccessAllowed) return "Called outside read action" KtAnalysisAllowanceManager.resolveIsForbiddenInActionWithName.get()?.let { actionName -> return "Resolve is forbidden in $actionName" @@ -55,6 +57,10 @@ public class KtReadActionConfinementLifetimeToken(project: Project) : KtLifetime public companion object { @KtAnalysisApiInternals public val allowOnEdt: ThreadLocal = ThreadLocal.withInitial { false } + + @KtAnalysisApiInternals + @KtAllowProhibitedAnalyzeFromWriteAction + public val allowFromWriteAction: ThreadLocal = ThreadLocal.withInitial { false } } public override val factory: KtLifetimeTokenFactory = KtReadActionConfinementLifetimeTokenFactory @@ -78,13 +84,15 @@ public object KtReadActionConfinementLifetimeTokenFactory : KtLifetimeTokenFacto private val lifetimeOwnersStack = ThreadLocal.withInitial> { persistentListOf() } - internal fun isInsideAnalysisContext() = lifetimeOwnersStack.get().size > 0 + internal fun isInsideAnalysisContext() = lifetimeOwnersStack.get().isNotEmpty() internal fun currentToken() = lifetimeOwnersStack.get().last() } +@RequiresOptIn("Analysis should be prohibited to be ran from write action, otherwise it may cause IDE freezes and incorrect behavior in some cases") +private annotation class KtAllowProhibitedAnalyzeFromWriteAction + /** - * * @see KtAnalysisSession * @see KtReadActionConfinementLifetimeToken */ @@ -97,4 +105,23 @@ public inline fun allowAnalysisOnEdt(action: () -> T): T { } finally { KtReadActionConfinementLifetimeToken.allowOnEdt.set(false) } +} + +/** + * Analysis is not supposed to be called from write action. + * Such actions can lead to IDE freezes and incorrect behavior in some cases. + * + * @see KtAnalysisSession + * @see KtReadActionConfinementLifetimeToken + */ +@KtAllowAnalysisFromWriteAction +@KtAllowProhibitedAnalyzeFromWriteAction +public inline fun allowAnalysisFromWriteAction(action: () -> T): T { + if (KtReadActionConfinementLifetimeToken.allowFromWriteAction.get()) return action() + KtReadActionConfinementLifetimeToken.allowFromWriteAction.set(true) + try { + return action() + } finally { + KtReadActionConfinementLifetimeToken.allowFromWriteAction.set(false) + } } \ No newline at end of file diff --git a/analysis/symbol-light-classes/build.gradle.kts b/analysis/symbol-light-classes/build.gradle.kts index 40d50d02aad..2502db8fa83 100644 --- a/analysis/symbol-light-classes/build.gradle.kts +++ b/analysis/symbol-light-classes/build.gradle.kts @@ -39,6 +39,7 @@ projectTest(jUnitMode = JUnitMode.JUnit5) { tasks.withType().configureEach { kotlinOptions.freeCompilerArgs += "-Xcontext-receivers" + kotlinOptions.freeCompilerArgs += "-opt-in=org.jetbrains.kotlin.analysis.api.lifetime.KtAllowProhibitedAnalyzeFromWriteAction" } diff --git a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/lightClassUtils.kt b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/lightClassUtils.kt index c57a551d761..0d84c4edbde 100644 --- a/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/lightClassUtils.kt +++ b/analysis/symbol-light-classes/src/org/jetbrains/kotlin/light/classes/symbol/lightClassUtils.kt @@ -8,9 +8,11 @@ package org.jetbrains.kotlin.light.classes.symbol import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiElement import com.intellij.psi.PsiType +import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisFromWriteAction import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisFromWriteAction import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt import org.jetbrains.kotlin.analysis.project.structure.KtModule import org.jetbrains.kotlin.builtins.StandardNames @@ -19,8 +21,10 @@ import org.jetbrains.kotlin.psi.KtElement internal fun PsiElement.nonExistentType(): PsiType = JavaPsiFacade.getElementFactory(project).createTypeFromText(StandardNames.NON_EXISTENT_CLASS.asString(), this) -@OptIn(KtAllowAnalysisOnEdt::class) -private inline fun allowLightClassesOnEdt(crossinline action: () -> E): E = allowAnalysisOnEdt(action) +@OptIn(KtAllowAnalysisOnEdt::class, KtAllowAnalysisFromWriteAction::class) +private inline fun allowLightClassesOnEdt(crossinline action: () -> E): E = allowAnalysisFromWriteAction { + allowAnalysisOnEdt(action) +} internal inline fun analyzeForLightClasses(context: KtElement, crossinline action: KtAnalysisSession.() -> R): R = allowLightClassesOnEdt { diff --git a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt index 52686ab29ed..8bcb3c1fceb 100644 --- a/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt +++ b/compiler/cli/cli-base/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.kt @@ -1,24 +1,22 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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.cli.jvm.compiler import com.intellij.DynamicBundle import com.intellij.codeInsight.ContainerProvider -import com.intellij.codeInsight.folding.JavaCodeFoldingSettings -import com.intellij.codeInsight.folding.impl.JavaCodeFoldingSettingsBase import com.intellij.codeInsight.runner.JavaMainMethodProvider import com.intellij.core.JavaCoreApplicationEnvironment import com.intellij.ide.highlighter.JavaClassFileType import com.intellij.lang.MetaLanguage +import com.intellij.mock.MockApplication import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFileSystem import com.intellij.psi.FileContextProvider import com.intellij.psi.augment.PsiAugmentProvider -import com.intellij.psi.codeStyle.JavaFileCodeStyleFacade import com.intellij.psi.codeStyle.JavaFileCodeStyleFacadeFactory import com.intellij.psi.impl.smartPointers.SmartPointerAnchorProvider import com.intellij.psi.meta.MetaDataContributor @@ -28,8 +26,7 @@ import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem class KotlinCoreApplicationEnvironment private constructor( parentDisposable: Disposable, unitTestMode: Boolean -) : - JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) { +) : JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) { init { registerApplicationService(JavaFileCodeStyleFacadeFactory::class.java, DummyJavaFileCodeStyleFacadeFactory()) @@ -40,6 +37,24 @@ class KotlinCoreApplicationEnvironment private constructor( return CoreJrtFileSystem() } + override fun createApplication(parentDisposable: Disposable): MockApplication { + val mock = super.createApplication(parentDisposable) + + /** + * We can't use just [unitTestMode] from constructor, because at this moment + * the corresponding property is not yet initialized, so [unitTestMode] is effectively always false, + * because this function called from super class constructor + */ + return if (mock.isUnitTestMode) { + object : MockApplication(parentDisposable) { + override fun isUnitTestMode(): Boolean = true + override fun isWriteAccessAllowed(): Boolean = false + } + } else { + mock + } + } + private var fastJarFileSystemField: FastJarFileSystem? = null private var fastJarFileSystemFieldInitialized = false