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:
Denis Zharkov
2018-03-22 13:00:11 +03:00
parent bd7e9d5397
commit 673d1aa91d
3 changed files with 59 additions and 42 deletions
@@ -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
@@ -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>()
} }
} }