Fix a memory leak of old resolve session from light classes
The problem is that diagnostics might indirectly retain a reference to an obsolete ResolverForProject (it becomes outdated after out of block modification). At the same time light class instances may survive afer OOM when they are leaked somewhere (e.g. to Java resolve) The idea is to store the Diagnostics instance inside a ResolverForProject component which will be collected once ResolverForProject is collected itself. #KT-23380 In Progress
This commit is contained in:
+47
-33
@@ -5,15 +5,11 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.caches.lightClasses
|
package org.jetbrains.kotlin.idea.caches.lightClasses
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiClass
|
import com.intellij.psi.PsiClass
|
||||||
import com.intellij.psi.PsiMember
|
import com.intellij.psi.PsiMember
|
||||||
import com.intellij.psi.PsiMethod
|
import com.intellij.psi.PsiMethod
|
||||||
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
|
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
|
||||||
import com.intellij.psi.util.CachedValueProvider
|
|
||||||
import com.intellij.psi.util.CachedValuesManager
|
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
|
||||||
import org.jetbrains.kotlin.asJava.LightClassBuilder
|
import org.jetbrains.kotlin.asJava.LightClassBuilder
|
||||||
import org.jetbrains.kotlin.asJava.builder.*
|
import org.jetbrains.kotlin.asJava.builder.*
|
||||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||||
@@ -26,46 +22,55 @@ import org.jetbrains.kotlin.psi.KtClassOrObject
|
|||||||
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
||||||
import org.jetbrains.kotlin.psi.debugText.getDebugText
|
import org.jetbrains.kotlin.psi.debugText.getDebugText
|
||||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||||
|
import org.jetbrains.kotlin.storage.StorageManager
|
||||||
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
|
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
|
||||||
|
|
||||||
typealias ExactLightClassContextProvider = () -> LightClassConstructionContext
|
typealias ExactLightClassContextProvider = () -> LightClassConstructionContext
|
||||||
typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)?
|
typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)?
|
||||||
|
typealias DiagnosticsHolderProvider = () -> LazyLightClassDataHolder.DiagnosticsHolder
|
||||||
|
|
||||||
sealed class LazyLightClassDataHolder(
|
sealed class LazyLightClassDataHolder(
|
||||||
builder: LightClassBuilder,
|
builder: LightClassBuilder,
|
||||||
project: Project,
|
|
||||||
exactContextProvider: ExactLightClassContextProvider,
|
exactContextProvider: ExactLightClassContextProvider,
|
||||||
dummyContextProvider: DummyLightClassContextProvider,
|
dummyContextProvider: DummyLightClassContextProvider,
|
||||||
isLocal: Boolean = false
|
private val diagnosticsHolderProvider: DiagnosticsHolderProvider
|
||||||
) : LightClassDataHolder {
|
) : LightClassDataHolder {
|
||||||
|
|
||||||
private data class CachedLightClassBuilderResult(val stub: PsiJavaFileStub, val diagnostics: Diagnostics)
|
class DiagnosticsHolder(private val storageManager: StorageManager) {
|
||||||
|
private val computedLightClassDiagnostics = hashMapOf<LazyLightClassDataHolder, Diagnostics>()
|
||||||
|
|
||||||
private val exactResultCachedValue =
|
fun putDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder, diagnostics: Diagnostics) {
|
||||||
CachedValuesManager.getManager(project).createCachedValue(
|
storageManager.compute {
|
||||||
{
|
computedLightClassDiagnostics[lazyLightClassDataHolder] = diagnostics
|
||||||
val (stub, _, diagnostics) = builder(exactContextProvider())
|
}
|
||||||
val cachedResult = CachedLightClassBuilderResult(stub, diagnostics)
|
}
|
||||||
|
|
||||||
CachedValueProvider.Result.create(
|
fun getComputedDiagnostics(lazyLightClassDataHolder: LazyLightClassDataHolder): Diagnostics? =
|
||||||
cachedResult,
|
storageManager.compute { computedLightClassDiagnostics[lazyLightClassDataHolder] }
|
||||||
if (isLocal)
|
}
|
||||||
PsiModificationTracker.MODIFICATION_COUNT
|
|
||||||
else
|
private val exactResultLazyValue = lazyPub {
|
||||||
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
|
val (stub, _, diagnostics) = builder(exactContextProvider())
|
||||||
)
|
diagnosticsHolderProvider().putDiagnostics(this, diagnostics)
|
||||||
}, false
|
stub
|
||||||
)
|
}
|
||||||
|
|
||||||
private val lazyInexactStub by lazyPub {
|
private val lazyInexactStub by lazyPub {
|
||||||
dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } }
|
dummyContextProvider?.let { provider -> provider()?.let { context -> builder.invoke(context).stub } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val inexactStub: PsiJavaFileStub?
|
private val inexactStub: PsiJavaFileStub?
|
||||||
get() = if (exactResultCachedValue.hasUpToDateValue()) null else lazyInexactStub
|
get() = if (exactResultLazyValue.isInitialized()) null else lazyInexactStub
|
||||||
|
|
||||||
override val javaFileStub get() = exactResultCachedValue.value.stub
|
override val javaFileStub by exactResultLazyValue
|
||||||
override val extraDiagnostics get() = exactResultCachedValue.value.diagnostics
|
|
||||||
|
override val extraDiagnostics: Diagnostics
|
||||||
|
get() {
|
||||||
|
// run light class builder
|
||||||
|
exactResultLazyValue.value
|
||||||
|
|
||||||
|
return diagnosticsHolderProvider().getComputedDiagnostics(this) ?: Diagnostics.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
// for facade or defaultImpls
|
// for facade or defaultImpls
|
||||||
override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass): LightClassData =
|
override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass): LightClassData =
|
||||||
@@ -74,9 +79,12 @@ sealed class LazyLightClassDataHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ForClass(
|
class ForClass(
|
||||||
builder: LightClassBuilder, project: Project, isLocal: Boolean,
|
builder: LightClassBuilder,
|
||||||
exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider
|
exactContextProvider: ExactLightClassContextProvider,
|
||||||
) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider, isLocal), LightClassDataHolder.ForClass {
|
dummyContextProvider: DummyLightClassContextProvider,
|
||||||
|
diagnosticsHolderProvider: DiagnosticsHolderProvider
|
||||||
|
) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider),
|
||||||
|
LightClassDataHolder.ForClass {
|
||||||
override fun findDataForClassOrObject(classOrObject: KtClassOrObject): LightClassData =
|
override fun findDataForClassOrObject(classOrObject: KtClassOrObject): LightClassData =
|
||||||
LazyLightClassData { stub ->
|
LazyLightClassData { stub ->
|
||||||
stub.findDelegate(classOrObject)
|
stub.findDelegate(classOrObject)
|
||||||
@@ -84,14 +92,20 @@ sealed class LazyLightClassDataHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ForFacade(
|
class ForFacade(
|
||||||
builder: LightClassBuilder, project: Project,
|
builder: LightClassBuilder,
|
||||||
exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider
|
exactContextProvider: ExactLightClassContextProvider,
|
||||||
) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForFacade
|
dummyContextProvider: DummyLightClassContextProvider,
|
||||||
|
diagnosticsHolderProvider: DiagnosticsHolderProvider
|
||||||
|
) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider),
|
||||||
|
LightClassDataHolder.ForFacade
|
||||||
|
|
||||||
class ForScript(
|
class ForScript(
|
||||||
builder: LightClassBuilder, project: Project,
|
builder: LightClassBuilder,
|
||||||
exactContextProvider: ExactLightClassContextProvider, dummyContextProvider: DummyLightClassContextProvider
|
exactContextProvider: ExactLightClassContextProvider,
|
||||||
) : LazyLightClassDataHolder(builder, project, exactContextProvider, dummyContextProvider), LightClassDataHolder.ForScript
|
dummyContextProvider: DummyLightClassContextProvider,
|
||||||
|
diagnosticsHolderProvider: DiagnosticsHolderProvider
|
||||||
|
) : LazyLightClassDataHolder(builder, exactContextProvider, dummyContextProvider, diagnosticsHolderProvider),
|
||||||
|
LightClassDataHolder.ForScript
|
||||||
|
|
||||||
private inner class LazyLightClassData(
|
private inner class LazyLightClassData(
|
||||||
findDelegate: (PsiJavaFileStub) -> PsiClass
|
findDelegate: (PsiJavaFileStub) -> PsiClass
|
||||||
|
|||||||
+9
-8
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
|||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.lightClasses.IDELightClassContexts
|
import org.jetbrains.kotlin.idea.caches.lightClasses.IDELightClassContexts
|
||||||
import org.jetbrains.kotlin.idea.caches.lightClasses.LazyLightClassDataHolder
|
import org.jetbrains.kotlin.idea.caches.lightClasses.LazyLightClassDataHolder
|
||||||
|
import org.jetbrains.kotlin.idea.resolve.frontendService
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.lazy.NoDescriptorForDeclarationException
|
import org.jetbrains.kotlin.resolve.lazy.NoDescriptorForDeclarationException
|
||||||
@@ -39,17 +40,15 @@ class IDELightClassGenerationSupport(project: Project) : LightClassGenerationSup
|
|||||||
classOrObject.shouldNotBeVisibleAsLightClass() -> InvalidLightClassDataHolder
|
classOrObject.shouldNotBeVisibleAsLightClass() -> InvalidLightClassDataHolder
|
||||||
classOrObject.isLocal -> LazyLightClassDataHolder.ForClass(
|
classOrObject.isLocal -> LazyLightClassDataHolder.ForClass(
|
||||||
builder,
|
builder,
|
||||||
classOrObject.project,
|
|
||||||
exactContextProvider = { IDELightClassContexts.contextForLocalClassOrObject(classOrObject) },
|
exactContextProvider = { IDELightClassContexts.contextForLocalClassOrObject(classOrObject) },
|
||||||
dummyContextProvider = null,
|
dummyContextProvider = null,
|
||||||
isLocal = true
|
diagnosticsHolderProvider = { classOrObject.getDiagnosticsHolder() }
|
||||||
)
|
)
|
||||||
else -> LazyLightClassDataHolder.ForClass(
|
else -> LazyLightClassDataHolder.ForClass(
|
||||||
builder,
|
builder,
|
||||||
classOrObject.project,
|
|
||||||
exactContextProvider = { IDELightClassContexts.contextForNonLocalClassOrObject(classOrObject) },
|
exactContextProvider = { IDELightClassContexts.contextForNonLocalClassOrObject(classOrObject) },
|
||||||
dummyContextProvider = { IDELightClassContexts.lightContextForClassOrObject(classOrObject) },
|
dummyContextProvider = { IDELightClassContexts.lightContextForClassOrObject(classOrObject) },
|
||||||
isLocal = false
|
diagnosticsHolderProvider = { classOrObject.getDiagnosticsHolder() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,21 +61,23 @@ class IDELightClassGenerationSupport(project: Project) : LightClassGenerationSup
|
|||||||
|
|
||||||
return LazyLightClassDataHolder.ForFacade(
|
return LazyLightClassDataHolder.ForFacade(
|
||||||
builder,
|
builder,
|
||||||
files.first().project,
|
|
||||||
exactContextProvider = { IDELightClassContexts.contextForFacade(sortedFiles) },
|
exactContextProvider = { IDELightClassContexts.contextForFacade(sortedFiles) },
|
||||||
dummyContextProvider = { IDELightClassContexts.lightContextForFacade(sortedFiles) }
|
dummyContextProvider = { IDELightClassContexts.lightContextForFacade(sortedFiles) },
|
||||||
|
diagnosticsHolderProvider = { files.first().getDiagnosticsHolder() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createDataHolderForScript(script: KtScript, builder: LightClassBuilder): LightClassDataHolder.ForScript {
|
override fun createDataHolderForScript(script: KtScript, builder: LightClassBuilder): LightClassDataHolder.ForScript {
|
||||||
return LazyLightClassDataHolder.ForScript(
|
return LazyLightClassDataHolder.ForScript(
|
||||||
builder,
|
builder,
|
||||||
script.project,
|
|
||||||
exactContextProvider = { IDELightClassContexts.contextForScript(script) },
|
exactContextProvider = { IDELightClassContexts.contextForScript(script) },
|
||||||
dummyContextProvider = { IDELightClassContexts.lightContextForScript(script) }
|
dummyContextProvider = { IDELightClassContexts.lightContextForScript(script) },
|
||||||
|
diagnosticsHolderProvider = { script.getDiagnosticsHolder() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KtElement.getDiagnosticsHolder() =
|
||||||
|
getResolutionFacade().frontendService<LazyLightClassDataHolder.DiagnosticsHolder>()
|
||||||
|
|
||||||
override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor? {
|
override fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor? {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.project
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.container.StorageComponentContainer
|
import org.jetbrains.kotlin.container.StorageComponentContainer
|
||||||
import org.jetbrains.kotlin.container.useImpl
|
import org.jetbrains.kotlin.container.useImpl
|
||||||
|
import org.jetbrains.kotlin.idea.caches.lightClasses.LazyLightClassDataHolder
|
||||||
import org.jetbrains.kotlin.resolve.TargetEnvironment
|
import org.jetbrains.kotlin.resolve.TargetEnvironment
|
||||||
|
|
||||||
object IdeaEnvironment : TargetEnvironment("Idea") {
|
object IdeaEnvironment : TargetEnvironment("Idea") {
|
||||||
@@ -25,5 +26,6 @@ object IdeaEnvironment : TargetEnvironment("Idea") {
|
|||||||
container.useImpl<ResolveElementCache>()
|
container.useImpl<ResolveElementCache>()
|
||||||
container.useImpl<IdeaLocalDescriptorResolver>()
|
container.useImpl<IdeaLocalDescriptorResolver>()
|
||||||
container.useImpl<IdeaAbsentDescriptorHandler>()
|
container.useImpl<IdeaAbsentDescriptorHandler>()
|
||||||
|
container.useImpl<LazyLightClassDataHolder.DiagnosticsHolder>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user