From 1bba3b22560c65aaff52584df19db68b8048b595 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 21 Dec 2021 18:56:46 +0300 Subject: [PATCH] Migrate decompiled light classes factories from IJ --- .../classes/ClsJavaStubByVirtualFileCache.kt | 55 ++++++++ .../classes/DecompiledLightClassesFactory.kt | 123 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/ClsJavaStubByVirtualFileCache.kt create mode 100644 analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/DecompiledLightClassesFactory.kt diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/ClsJavaStubByVirtualFileCache.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/ClsJavaStubByVirtualFileCache.kt new file mode 100644 index 00000000000..86ba5cab940 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/ClsJavaStubByVirtualFileCache.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2021 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.analysis.decompiled.light.classes + +import com.intellij.ide.highlighter.JavaClassFileType +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 + +internal class ClsJavaStubByVirtualFileCache { + private class CachedJavaStub(val modificationStamp: Long, val javaFileStub: PsiJavaFileStubImpl) + + private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap() + + fun get(classFile: VirtualFile): PsiJavaFileStubImpl? { + val cached = cache[classFile] + val fileModificationStamp = classFile.modificationStamp + if (cached != null && cached.modificationStamp == fileModificationStamp) { + return cached.javaFileStub + } + val stub = createStub(classFile) as PsiJavaFileStubImpl? ?: return null + cache[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(false)) + } catch (e: ClsFormatException) { + LOG.warn("Failed to build java cls class for " + file.canonicalPath!!, e) + } catch (e: IOException) { + LOG.warn("Failed to build java cls class for " + file.canonicalPath!!, e) + } + + return null + } + + companion object { + private val LOG = Logger.getInstance(ClsJavaStubByVirtualFileCache::class.java) + + fun getInstance(project: Project): ClsJavaStubByVirtualFileCache = + project.getService(ClsJavaStubByVirtualFileCache::class.java) + } +} \ No newline at end of file diff --git a/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/DecompiledLightClassesFactory.kt b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/DecompiledLightClassesFactory.kt new file mode 100644 index 00000000000..65bc495c3e8 --- /dev/null +++ b/analysis/decompiled/light-classes-for-decompiled/src/org/jetbrains/kotlin/analysis/decompiled/light/classes/DecompiledLightClassesFactory.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2010-2021 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.analysis.decompiled.light.classes + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.ClassFileViewProvider +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.util.PsiTreeUtil +import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile +import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.checkWithAttachment + +object DecompiledLightClassesFactory { + fun getLightClassForDecompiledClassOrObject( + decompiledClassOrObject: KtClassOrObject, + project: Project + ): KtLightClassForDecompiledDeclaration? { + if (decompiledClassOrObject is KtEnumEntry) { + return null + } + val containingKtFile = decompiledClassOrObject.containingFile as? KtClsFile ?: return null + val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingKtFile, project) ?: return null + + return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile) + } + + private fun findCorrespondingLightClass( + decompiledClassOrObject: KtClassOrObject, + rootLightClassForDecompiledFile: KtLightClassForDecompiledDeclaration + ): KtLightClassForDecompiledDeclaration? { + val relativeFqName = getClassRelativeName(decompiledClassOrObject) ?: return null + val iterator = relativeFqName.pathSegments().iterator() + val base = iterator.next() + + // In case class files have been obfuscated (i.e., SomeClass belongs to a.class file), just ignore them + if (rootLightClassForDecompiledFile.name != base.asString()) return null + + var current: KtLightClassForDecompiledDeclaration = rootLightClassForDecompiledFile + while (iterator.hasNext()) { + val name = iterator.next() + val innerClass = current.findInnerClassByName(name.asString(), false) + checkWithAttachment( + innerClass != null, + { "Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\nFile: " + decompiledClassOrObject.containingKtFile.virtualFile.name }, + { + it.withAttachment("decompiledClassOrObject", decompiledClassOrObject.text) + it.withAttachment("fileClass", decompiledClassOrObject.containingFile::class) + it.withAttachment("file", decompiledClassOrObject.containingFile.text) + it.withAttachment("root", rootLightClassForDecompiledFile.text) + }, + ) + + current = innerClass as KtLightClassForDecompiledDeclaration + } + return current + } + + private fun getClassRelativeName(decompiledClassOrObject: KtClassOrObject): FqName? { + val name = decompiledClassOrObject.nameAsName ?: return null + val parent = PsiTreeUtil.getParentOfType( + decompiledClassOrObject, + KtClassOrObject::class.java, + true + ) + if (parent == null) { + assert(decompiledClassOrObject.isTopLevel()) + return FqName.topLevel(name) + } + return getClassRelativeName(parent)?.child(name) + } + + fun createLightClassForDecompiledKotlinFile(file: KtClsFile, project: Project): KtLightClassForDecompiledDeclaration? { + val virtualFile = file.virtualFile ?: return null + + val classOrObject = file.declarations.filterIsInstance().singleOrNull() + + val javaClsClass = createClsJavaClassFromVirtualFile( + file, virtualFile, + correspondingClassOrObject = classOrObject, + project, + ) ?: return null + + return KtLightClassForDecompiledDeclaration(javaClsClass, javaClsClass.parent, file, classOrObject) + } + + fun createClsJavaClassFromVirtualFile( + mirrorFile: KtFile, + classFile: VirtualFile, + correspondingClassOrObject: KtClassOrObject?, + project: Project, + ): ClsClassImpl? { + val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null + javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE + 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() = javaFileStub + + override fun getMirror() = mirrorFile + + override fun isPhysical() = false + } + javaFileStub.psi = fakeFile + return fakeFile.classes.single() as ClsClassImpl + } +} \ No newline at end of file