Move KotlinBinaryClassCache to separate module, so it can be used outside FE1.0

This commit is contained in:
Ilya Kirillov
2021-12-16 11:08:25 +03:00
parent 25fe14b514
commit 52baf7338e
8 changed files with 35 additions and 39 deletions
+2
View File
@@ -11,6 +11,8 @@ dependencies {
api("javax.annotation:jsr250-api:1.0")
api(project(":compiler:frontend"))
api(project(":compiler:resolution.common.jvm"))
api(project(":compiler:frontend.common.jvm"))
compileOnly(intellijCore())
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
compileOnly(commonDependency("org.jetbrains.intellij.deps:trove4j"))
@@ -1,368 +0,0 @@
/*
* 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.load.kotlin;
import com.intellij.openapi.util.Ref;
import kotlin.jvm.functions.Function4;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap;
import org.jetbrains.kotlin.descriptors.SourceElement;
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader;
import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.constants.ClassLiteralValue;
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
import org.jetbrains.org.objectweb.asm.*;
import java.util.*;
import static org.jetbrains.org.objectweb.asm.ClassReader.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION;
public abstract class FileBasedKotlinClass implements KotlinJvmBinaryClass {
private final ClassId classId;
private final int classVersion;
private final KotlinClassHeader classHeader;
private final InnerClassesInfo innerClasses;
protected FileBasedKotlinClass(
@NotNull ClassId classId,
int classVersion,
@NotNull KotlinClassHeader classHeader,
@NotNull InnerClassesInfo innerClasses
) {
this.classId = classId;
this.classVersion = classVersion;
this.classHeader = classHeader;
this.innerClasses = innerClasses;
}
public static class OuterAndInnerName {
public final String outerInternalName;
public final String innerSimpleName;
private OuterAndInnerName(@Nullable String outerInternalName, @Nullable String innerSimpleName) {
this.outerInternalName = outerInternalName;
this.innerSimpleName = innerSimpleName;
}
}
public static class InnerClassesInfo {
private Map<String, OuterAndInnerName> map = null;
public void add(@NotNull String name, @Nullable String outerName, @Nullable String innerName) {
if (map == null) {
map = new HashMap<>();
}
map.put(name, new OuterAndInnerName(outerName, innerName));
}
@Nullable
public OuterAndInnerName get(@NotNull String name) {
return map == null ? null : map.get(name);
}
}
@NotNull
protected abstract byte[] getFileContents();
// TODO public to be accessible in companion object of subclass, workaround for KT-3974
@Nullable
public static <T> T create(
@NotNull byte[] fileContents,
@NotNull Function4<ClassId, Integer, KotlinClassHeader, InnerClassesInfo, T> factory
) {
ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor();
Ref<String> classNameRef = Ref.create();
Ref<Integer> classVersion = Ref.create();
InnerClassesInfo innerClasses = new InnerClassesInfo();
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
classNameRef.set(name);
classVersion.set(version);
}
@Override
public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) {
innerClasses.add(name, outerName, innerName);
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
return convertAnnotationVisitor(readHeaderVisitor, desc, innerClasses);
}
@Override
public void visitEnd() {
readHeaderVisitor.visitEnd();
}
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
String className = classNameRef.get();
if (className == null) return null;
KotlinClassHeader header = readHeaderVisitor.createHeader();
if (header == null) return null;
ClassId id = resolveNameByInternalName(className, innerClasses);
return factory.invoke(id, classVersion.get(), header, innerClasses);
}
@NotNull
@Override
public ClassId getClassId() {
return classId;
}
public int getClassVersion() {
return classVersion;
}
@NotNull
@Override
public KotlinClassHeader getClassHeader() {
return classHeader;
}
@Override
public void loadClassAnnotations(@NotNull AnnotationVisitor annotationVisitor, @Nullable byte[] cachedContents) {
byte[] fileContents = cachedContents != null ? cachedContents : getFileContents();
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
return convertAnnotationVisitor(annotationVisitor, desc, innerClasses);
}
@Override
public void visitEnd() {
annotationVisitor.visitEnd();
}
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
}
@Nullable
public static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
@NotNull AnnotationVisitor visitor, @NotNull String desc, @NotNull InnerClassesInfo innerClasses
) {
AnnotationArgumentVisitor v = visitor.visitAnnotation(resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
return v == null ? null : convertAnnotationVisitor(v, innerClasses);
}
@NotNull
private static org.jetbrains.org.objectweb.asm.AnnotationVisitor convertAnnotationVisitor(
@NotNull AnnotationArgumentVisitor v, @NotNull InnerClassesInfo innerClasses
) {
return new org.jetbrains.org.objectweb.asm.AnnotationVisitor(API_VERSION) {
@Override
public void visit(String name, @NotNull Object value) {
Name identifier = name == null ? null : Name.identifier(name);
if (value instanceof Type) {
v.visitClassLiteral(identifier, resolveKotlinNameByType((Type) value, innerClasses));
}
else {
v.visit(identifier, value);
}
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitArray(String name) {
AnnotationArrayArgumentVisitor arv = v.visitArray(name == null ? null : Name.identifier(name));
return arv == null ? null : new org.jetbrains.org.objectweb.asm.AnnotationVisitor(API_VERSION) {
@Override
public void visit(String name, @NotNull Object value) {
if (value instanceof Type) {
arv.visitClassLiteral(resolveKotlinNameByType((Type) value, innerClasses));
}
else {
arv.visit(value);
}
}
@Override
public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
arv.visitEnum(resolveNameByDesc(desc, innerClasses), Name.identifier(value));
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) {
AnnotationArgumentVisitor aav = arv.visitAnnotation(resolveNameByDesc(desc, innerClasses));
return aav == null ? null : convertAnnotationVisitor(aav, innerClasses);
}
@Override
public void visitEnd() {
arv.visitEnd();
}
};
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, @NotNull String desc) {
AnnotationArgumentVisitor arv =
v.visitAnnotation(name == null ? null : Name.identifier(name), resolveNameByDesc(desc, innerClasses));
return arv == null ? null : convertAnnotationVisitor(arv, innerClasses);
}
@Override
public void visitEnum(String name, @NotNull String desc, @NotNull String value) {
v.visitEnum(name == null ? null : Name.identifier(name), resolveNameByDesc(desc, innerClasses), Name.identifier(value));
}
@Override
public void visitEnd() {
v.visitEnd();
}
};
}
@Override
public void visitMembers(@NotNull MemberVisitor memberVisitor, @Nullable byte[] cachedContents) {
byte[] fileContents = cachedContents != null ? cachedContents : getFileContents();
new ClassReader(fileContents).accept(new ClassVisitor(API_VERSION) {
@Override
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
AnnotationVisitor v = memberVisitor.visitField(Name.identifier(name), desc, value);
if (v == null) return null;
return new FieldVisitor(API_VERSION) {
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
return convertAnnotationVisitor(v, desc, innerClasses);
}
@Override
public void visitEnd() {
v.visitEnd();
}
};
}
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
MethodAnnotationVisitor v = memberVisitor.visitMethod(Name.identifier(name), desc);
if (v == null) return null;
int methodParamCount = Type.getArgumentTypes(desc).length;
return new MethodVisitor(API_VERSION) {
private int visibleAnnotableParameterCount = methodParamCount;
private int invisibleAnnotableParameterCount = methodParamCount;
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotation(@NotNull String desc, boolean visible) {
return convertAnnotationVisitor(v, desc, innerClasses);
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitAnnotationDefault() {
AnnotationArgumentVisitor av = v.visitAnnotationMemberDefaultValue();
return av == null ? null : convertAnnotationVisitor(av, innerClasses);
}
@Override
public org.jetbrains.org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(int parameter, @NotNull String desc, boolean visible) {
int parameterIndex = parameter + methodParamCount - (visible ? visibleAnnotableParameterCount : invisibleAnnotableParameterCount);
AnnotationArgumentVisitor av = v.visitParameterAnnotation(parameterIndex, resolveNameByDesc(desc, innerClasses), SourceElement.NO_SOURCE);
return av == null ? null : convertAnnotationVisitor(av, innerClasses);
}
public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
if (visible)
visibleAnnotableParameterCount = parameterCount;
else {
invisibleAnnotableParameterCount = parameterCount;
}
}
@Override
public void visitEnd() {
v.visitEnd();
}
};
}
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
}
@NotNull
private static ClassId resolveNameByDesc(@NotNull String desc, @NotNull InnerClassesInfo innerClasses) {
assert desc.startsWith("L") && desc.endsWith(";") : "Not a JVM descriptor: " + desc;
String name = desc.substring(1, desc.length() - 1);
return resolveNameByInternalName(name, innerClasses);
}
// See ReflectKotlinStructure.classLiteralValue
@NotNull
private static ClassLiteralValue resolveKotlinNameByType(@NotNull Type type, @NotNull InnerClassesInfo innerClasses) {
String typeDesc = type.getDescriptor();
int dimensions = typeDesc.charAt(0) == '[' ? type.getDimensions() : 0;
String elementDesc = dimensions == 0 ? typeDesc : type.getElementType().getDescriptor();
JvmPrimitiveType primType = JvmPrimitiveType.getByDesc(elementDesc);
if (primType != null) {
if (dimensions > 0) {
// "int[][]" should be loaded as "Array<IntArray>", not as "Array<Array<Int>>"
return new ClassLiteralValue(ClassId.topLevel(primType.getPrimitiveType().getArrayTypeFqName()), dimensions - 1);
}
return new ClassLiteralValue(ClassId.topLevel(primType.getPrimitiveType().getTypeFqName()), dimensions);
}
ClassId javaClassId = resolveNameByDesc(elementDesc, innerClasses);
ClassId kotlinClassId = JavaToKotlinClassMap.INSTANCE.mapJavaToKotlin(javaClassId.asSingleFqName());
return new ClassLiteralValue(kotlinClassId != null ? kotlinClassId : javaClassId, dimensions);
}
@NotNull
public static ClassId resolveNameByInternalName(@NotNull String name, @NotNull InnerClassesInfo innerClasses) {
if (!name.contains("$")) {
return ClassId.topLevel(new FqName(name.replace('/', '.')));
}
List<String> classes = new ArrayList<>(1);
boolean local = false;
while (true) {
OuterAndInnerName outer = innerClasses.get(name);
if (outer == null) break;
if (outer.outerInternalName == null) {
local = true;
break;
}
classes.add(outer.innerSimpleName);
name = outer.outerInternalName;
}
FqName outermostClassFqName = new FqName(name.replace('/', '.'));
classes.add(outermostClassFqName.shortName().asString());
Collections.reverse(classes);
FqName packageFqName = outermostClassFqName.parent();
FqName relativeClassName = FqName.fromSegments(classes);
return new ClassId(packageFqName, relativeClassName, local);
}
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract String toString();
}
@@ -1,93 +0,0 @@
/*
* 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.load.kotlin
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.util.Computable
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiJavaModule
import java.lang.ref.WeakReference
import java.util.concurrent.CopyOnWriteArrayList
class KotlinBinaryClassCache : Disposable {
private val requestCaches = CopyOnWriteArrayList<WeakReference<RequestCache>>()
private class RequestCache {
var virtualFile: VirtualFile? = null
var modificationStamp: Long = 0
var result: KotlinClassFinder.Result? = null
fun cache(
file: VirtualFile,
result: KotlinClassFinder.Result?
): KotlinClassFinder.Result? {
virtualFile = file
this.result = result
modificationStamp = file.modificationStamp
return result
}
}
private val cache = object : ThreadLocal<RequestCache>() {
override fun initialValue(): RequestCache {
return RequestCache().also {
requestCaches.add(WeakReference(it))
}
}
}
override fun dispose() {
for (cache in requestCaches) {
cache.get()?.run {
result = null
virtualFile = null
}
}
requestCaches.clear()
// This is only relevant for tests. We create a new instance of Application for each test, and so a new instance of this service is
// also created for each test. However all tests share the same event dispatch thread, which would collect all instances of this
// thread-local if they're not removed properly. Each instance would transitively retain VFS resulting in OutOfMemoryError
cache.remove()
}
companion object {
fun getKotlinBinaryClassOrClassFileContent(
file: VirtualFile, fileContent: ByteArray? = null
): KotlinClassFinder.Result? {
if (file.fileType !== JavaClassFileType.INSTANCE) return null
if (file.name == PsiJavaModule.MODULE_INFO_CLS_FILE) return null
val service = ServiceManager.getService(KotlinBinaryClassCache::class.java)
val requestCache = service.cache.get()
if (file.modificationStamp == requestCache.modificationStamp && file == requestCache.virtualFile) {
return requestCache.result
}
val aClass = ApplicationManager.getApplication().runReadAction(Computable {
VirtualFileKotlinClass.create(file, fileContent)
})
return requestCache.cache(file, aClass)
}
}
}
@@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass
import org.jetbrains.kotlin.name.ClassId
@@ -1,93 +0,0 @@
/*
* 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.load.kotlin
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder.Result.KotlinClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.util.PerformanceCounter
import java.io.FileNotFoundException
import java.io.IOException
class VirtualFileKotlinClass private constructor(
val file: VirtualFile,
className: ClassId,
classVersion: Int,
classHeader: KotlinClassHeader,
innerClasses: InnerClassesInfo
) : FileBasedKotlinClass(className, classVersion, classHeader, innerClasses) {
override val location: String
get() = file.path
override val containingLibrary: String?
get() = file.path.split("!/").firstOrNull()
override fun getFileContents(): ByteArray {
try {
return file.contentsToByteArray()
} catch (e: IOException) {
throw logFileReadingErrorMessage(e, file)
}
}
override fun equals(other: Any?) = other is VirtualFileKotlinClass && other.file == file
override fun hashCode() = file.hashCode()
override fun toString() = "${this::class.java.simpleName}: $file"
companion object Factory {
private val LOG = Logger.getInstance(VirtualFileKotlinClass::class.java)
private val perfCounter = PerformanceCounter.create("Binary class from Kotlin file")
internal fun create(file: VirtualFile, fileContent: ByteArray?): KotlinClassFinder.Result? {
return perfCounter.time {
assert(file.fileType == JavaClassFileType.INSTANCE) { "Trying to read binary data from a non-class file $file" }
try {
val byteContent = fileContent ?: file.contentsToByteArray(false)
if (byteContent.isNotEmpty()) {
val kotlinJvmBinaryClass = create(byteContent) { name, classVersion, header, innerClasses ->
VirtualFileKotlinClass(file, name, classVersion, header, innerClasses)
}
return@time kotlinJvmBinaryClass?.let { KotlinClass(it, byteContent) }
?: KotlinClassFinder.Result.ClassFileContent(byteContent)
}
} catch (e: FileNotFoundException) {
// Valid situation. User can delete jar file.
} catch (e: Throwable) {
if (e is ControlFlowException) throw e
throw logFileReadingErrorMessage(e, file)
}
null
}
}
private fun logFileReadingErrorMessage(e: Throwable, file: VirtualFile): Throwable {
val errorMessage = renderFileReadingErrorMessage(file)
LOG.warn(errorMessage, e)
return IllegalStateException(errorMessage, e)
}
private fun renderFileReadingErrorMessage(file: VirtualFile): String =
"Could not read file: ${file.path}; size in bytes: ${file.length}; file type: ${file.fileType.name}"
}
}
@@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
import org.jetbrains.kotlin.modules.Module
import org.jetbrains.kotlin.resolve.DescriptorUtils