From 54bcbc7a029efd96e6554410ce258257b14fcad5 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Mon, 14 Sep 2015 17:17:27 +0300 Subject: [PATCH] Introduce ClsJavaStubByVirtualFileCache Avoid caching in user data of virtual file because it leads to project leaking --- .../resolve/ClsJavaStubByVirtualFileCache.kt | 71 +++++ .../resolve/IDELightClassGenerationSupport.kt | 273 ++++++++---------- idea/src/META-INF/plugin.xml | 3 + 3 files changed, 187 insertions(+), 160 deletions(-) create mode 100644 idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ClsJavaStubByVirtualFileCache.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ClsJavaStubByVirtualFileCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ClsJavaStubByVirtualFileCache.kt new file mode 100644 index 00000000000..9cb0c07a349 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/ClsJavaStubByVirtualFileCache.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.caches.resolve + +import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.impl.java.stubs.PsiJavaFileStub +import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl +import com.intellij.util.cls.ClsFormatException +import com.intellij.util.containers.ContainerUtil +import java.io.IOException + +public class ClsJavaStubByVirtualFileCache { + private class CachedJavaStub(val modificationStamp: Long, val javaFileStub: PsiJavaFileStubImpl) + + private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap() + + public fun get(classFile: VirtualFile): PsiJavaFileStubImpl? { + val cached = cache.get(classFile) + val fileModificationStamp = classFile.modificationStamp + if (cached != null && cached.modificationStamp == fileModificationStamp) { + return cached.javaFileStub + } + val stub = createStub(classFile) as PsiJavaFileStubImpl? ?: return null + cache.put(classFile, CachedJavaStub(fileModificationStamp, stub)) + return stub + } + + private fun createStub(file: VirtualFile): PsiJavaFileStub? { + if (file.fileType !== JavaClassFileType.INSTANCE) return null + + try { + return ClsFileImpl.buildFileStub(file, file.contentsToByteArray()) + } + catch (e: ClsFormatException) { + LOG.debug(e) + } + catch (e: IOException) { + LOG.debug(e) + } + + LOG.error("Failed to build java cls class for " + file.canonicalPath!!) + return null + } + + companion object { + private val LOG = Logger.getInstance(ClsJavaStubByVirtualFileCache::class.java) + + public fun getInstance(project: Project): ClsJavaStubByVirtualFileCache { + return ServiceManager.getService(project, ClsJavaStubByVirtualFileCache::class.java) + } + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt index 28ab3e6054c..0a45e48d6f2 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.kt @@ -16,12 +16,9 @@ package org.jetbrains.kotlin.idea.caches.resolve -import com.intellij.ide.highlighter.JavaClassFileType import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.roots.libraries.LibraryUtil -import com.intellij.openapi.util.Key -import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.ClassFileViewProvider import com.intellij.psi.PsiClass @@ -29,12 +26,9 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.intellij.psi.impl.compiled.ClsClassImpl import com.intellij.psi.impl.compiled.ClsFileImpl -import com.intellij.psi.impl.java.stubs.PsiJavaFileStub -import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.stubs.PsiClassHolderFileStub import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.cls.ClsFormatException import org.jetbrains.kotlin.asJava.* import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName @@ -52,7 +46,6 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.* import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.utils.sure -import java.io.IOException import java.util.* public class IDELightClassGenerationSupport(private val project: Project) : LightClassGenerationSupport() { @@ -166,7 +159,6 @@ public class IDELightClassGenerationSupport(private val project: Project) : Ligh val clsClassFromPackageClass = createClsJavaClassFromVirtualFile( mirrorFile = files.first(), classFile = virtualFileForPackageClass, - cacheHolder = virtualFileForPackageClass, correspondingClassOrObject = null ) ?: return emptyList() return listOf(KotlinLightClassForDecompiledDeclaration(clsClassFromPackageClass, null)) @@ -245,169 +237,130 @@ public class IDELightClassGenerationSupport(private val project: Project) : Ligh } } - private class CachedJavaStub(public var modificationStamp: Long, public var javaFileStub: PsiJavaFileStubImpl) - companion object { + private fun forceResolvePackageDeclarations(files: Collection, session: KotlinCodeAnalyzer) { + for (file in files) { + // SCRIPT: not supported + if (file.isScript) continue - private val LOG = Logger.getInstance(IDELightClassGenerationSupport::class.java) + val packageFqName = file.packageFqName - private fun forceResolvePackageDeclarations(files: Collection, session: KotlinCodeAnalyzer) { - for (file in files) { - // SCRIPT: not supported - if (file.isScript) continue + // make sure we create a package descriptor + val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName) + if (packageDescriptor.isEmpty()) { + LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text) + session.forceResolveAll() + continue + } - val packageFqName = file.packageFqName - - // make sure we create a package descriptor - val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName) - if (packageDescriptor.isEmpty()) { - LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text) - session.forceResolveAll() - continue - } - - for (declaration in file.declarations) { - if (declaration is JetFunction) { - val name = declaration.nameAsSafeName - val functions = packageDescriptor.memberScope.getFunctions(name, NoLookupLocation.FROM_IDE) - for (descriptor in functions) { - ForceResolveUtil.forceResolveAllContents(descriptor) - } - } - else if (declaration is JetProperty) { - val name = declaration.nameAsSafeName - val properties = packageDescriptor.memberScope.getProperties(name, NoLookupLocation.FROM_IDE) - for (descriptor in properties) { - ForceResolveUtil.forceResolveAllContents(descriptor) - } - } - else if (declaration is JetClassOrObject) { - // Do nothing: we are not interested in classes - } - else { - LOG.error("Unsupported declaration kind: " + declaration + " in file " + file.name + "\n" + file.text) + for (declaration in file.declarations) { + if (declaration is JetFunction) { + val name = declaration.nameAsSafeName + val functions = packageDescriptor.memberScope.getFunctions(name, NoLookupLocation.FROM_IDE) + for (descriptor in functions) { + ForceResolveUtil.forceResolveAllContents(descriptor) } } - } - } - - private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: JetClassOrObject): PsiClass? { - if (decompiledClassOrObject is JetEnumEntry) { - return null - } - val containingJetFile = decompiledClassOrObject.getContainingJetFile() - if (!containingJetFile.isCompiled) { - return null - } - val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingJetFile) ?: return null - - return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile) - } - - private fun findCorrespondingLightClass( - decompiledClassOrObject: JetClassOrObject, - rootLightClassForDecompiledFile: PsiClass): PsiClass { - val relativeFqName = getClassRelativeName(decompiledClassOrObject) - val iterator = relativeFqName.pathSegments().iterator() - val base = iterator.next() - assert(rootLightClassForDecompiledFile.name == base.asString()) { "Light class for file:\n" + decompiledClassOrObject.getContainingJetFile().virtualFile.canonicalPath + "\nwas expected to have name: " + base.asString() + "\n Actual: " + rootLightClassForDecompiledFile.name } - var current = rootLightClassForDecompiledFile - while (iterator.hasNext()) { - val name = iterator.next() - val innerClass = current.findInnerClassByName(name.asString(), false).sure { - "Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\n" + "File: " + decompiledClassOrObject.getContainingJetFile().virtualFile.name - } - current = innerClass - } - return current - } - - private fun getClassRelativeName(decompiledClassOrObject: JetClassOrObject): FqName { - val name = decompiledClassOrObject.nameAsName!! - val parent = PsiTreeUtil.getParentOfType(decompiledClassOrObject, JetClassOrObject::class.java, true) - if (parent == null) { - assert(decompiledClassOrObject.isTopLevel()) - return FqName.topLevel(name) - } - return getClassRelativeName(parent).child(name) - } - - private fun createLightClassForDecompiledKotlinFile(file: JetFile): KotlinLightClassForDecompiledDeclaration? { - val virtualFile = file.virtualFile ?: return null - - val classOrObject = file.declarations.filterIsInstance().singleOrNull() - - val javaClsClass = createClsJavaClassFromVirtualFile( - file, virtualFile, - cacheHolder = file, - correspondingClassOrObject = classOrObject - ) ?: return null - - return KotlinLightClassForDecompiledDeclaration(javaClsClass, classOrObject) - } - - private fun createClsJavaClassFromVirtualFile( - mirrorFile: JetFile, - classFile: VirtualFile, - cacheHolder: UserDataHolder, - correspondingClassOrObject: JetClassOrObject? - ): ClsClassImpl? { - val javaFileStub = getOrCreateJavaFileStub(cacheHolder, classFile) ?: return null - val manager = PsiManager.getInstance(mirrorFile.project) - val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) { - override fun getNavigationElement(): PsiElement { - if (correspondingClassOrObject != null) { - return correspondingClassOrObject.navigationElement.containingFile + else if (declaration is JetProperty) { + val name = declaration.nameAsSafeName + val properties = packageDescriptor.memberScope.getProperties(name, NoLookupLocation.FROM_IDE) + for (descriptor in properties) { + ForceResolveUtil.forceResolveAllContents(descriptor) } - return super.getNavigationElement() } - - override fun getStub(): PsiClassHolderFileStub<*> { - return javaFileStub + else if (declaration is JetClassOrObject) { + // Do nothing: we are not interested in classes } - - override fun getMirror(): PsiElement { - return mirrorFile + else { + LOG.error("Unsupported declaration kind: " + declaration + " in file " + file.name + "\n" + file.text) } } - fakeFile.isPhysical = false - javaFileStub.setPsi(fakeFile) - return fakeFile.classes.single() as ClsClassImpl - } - - private val cachedJavaStubKey = Key.create("CACHED_JAVA_STUB") - - private fun getOrCreateJavaFileStub( - userDataHolder: UserDataHolder, - virtualFile: VirtualFile - ): PsiJavaFileStubImpl? { - val cachedJavaStub = userDataHolder.getUserData(cachedJavaStubKey) - val fileModificationStamp = virtualFile.modificationStamp - if (cachedJavaStub != null && cachedJavaStub.modificationStamp == fileModificationStamp) { - return cachedJavaStub.javaFileStub - } - val stub = createStub(virtualFile) as PsiJavaFileStubImpl? - if (stub != null) { - userDataHolder.putUserData(cachedJavaStubKey, CachedJavaStub(fileModificationStamp, stub)) - } - return stub - } - - private fun createStub(file: VirtualFile): PsiJavaFileStub? { - if (file.fileType !== JavaClassFileType.INSTANCE) return null - - try { - return ClsFileImpl.buildFileStub(file, file.contentsToByteArray()) - } - catch (e: ClsFormatException) { - LOG.debug(e) - } - catch (e: IOException) { - LOG.debug(e) - } - - LOG.error("Failed to build java cls class for " + file.canonicalPath!!) - return null } } + + private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: JetClassOrObject): PsiClass? { + if (decompiledClassOrObject is JetEnumEntry) { + return null + } + val containingJetFile = decompiledClassOrObject.getContainingJetFile() + if (!containingJetFile.isCompiled) { + return null + } + val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingJetFile) ?: return null + + return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile) + } + + private fun findCorrespondingLightClass( + decompiledClassOrObject: JetClassOrObject, + rootLightClassForDecompiledFile: PsiClass): PsiClass { + val relativeFqName = getClassRelativeName(decompiledClassOrObject) + val iterator = relativeFqName.pathSegments().iterator() + val base = iterator.next() + assert(rootLightClassForDecompiledFile.name == base.asString()) { "Light class for file:\n" + decompiledClassOrObject.getContainingJetFile().virtualFile.canonicalPath + "\nwas expected to have name: " + base.asString() + "\n Actual: " + rootLightClassForDecompiledFile.name } + var current = rootLightClassForDecompiledFile + while (iterator.hasNext()) { + val name = iterator.next() + val innerClass = current.findInnerClassByName(name.asString(), false).sure { + "Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\n" + "File: " + decompiledClassOrObject.getContainingJetFile().virtualFile.name + } + current = innerClass + } + return current + } + + private fun getClassRelativeName(decompiledClassOrObject: JetClassOrObject): FqName { + val name = decompiledClassOrObject.nameAsName!! + val parent = PsiTreeUtil.getParentOfType(decompiledClassOrObject, JetClassOrObject::class.java, true) + if (parent == null) { + assert(decompiledClassOrObject.isTopLevel()) + return FqName.topLevel(name) + } + return getClassRelativeName(parent).child(name) + } + + private fun createLightClassForDecompiledKotlinFile(file: JetFile): KotlinLightClassForDecompiledDeclaration? { + val virtualFile = file.virtualFile ?: return null + + val classOrObject = file.declarations.filterIsInstance().singleOrNull() + + val javaClsClass = createClsJavaClassFromVirtualFile( + file, virtualFile, + correspondingClassOrObject = classOrObject + ) ?: return null + + return KotlinLightClassForDecompiledDeclaration(javaClsClass, classOrObject) + } + + private fun createClsJavaClassFromVirtualFile( + mirrorFile: JetFile, + classFile: VirtualFile, + correspondingClassOrObject: JetClassOrObject? + ): ClsClassImpl? { + val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null + val manager = PsiManager.getInstance(mirrorFile.project) + val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) { + override fun getNavigationElement(): PsiElement { + if (correspondingClassOrObject != null) { + return correspondingClassOrObject.navigationElement.containingFile + } + return super.getNavigationElement() + } + + override fun getStub(): PsiClassHolderFileStub<*> { + return javaFileStub + } + + override fun getMirror(): PsiElement { + return mirrorFile + } + } + fakeFile.isPhysical = false + javaFileStub.psi = fakeFile + return fakeFile.classes.single() as ClsClassImpl + } + + companion object { + private val LOG = Logger.getInstance(IDELightClassGenerationSupport::class.java) + } } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 1221c63d036..0ea4da381f2 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -235,6 +235,9 @@ + +