Refactor KotlinJvmBinaryClass, VirtualFileKotlinClass, etc.

VirtualFileKotlinClass now reads its header and name on creation. This is not
lazy enough and may be slower in some circumstances, but has the following
advantage: if anything is wrong in the header, a VirtualFileKotlinClass
instance will not be created at all, making it nearly impossible for the client
code to operate on invalid data causing all kinds of exceptions
This commit is contained in:
Alexander Udalov
2014-03-12 20:41:49 +04:00
parent 9bf0d014d5
commit 307f52895a
13 changed files with 78 additions and 104 deletions
@@ -17,29 +17,29 @@
package org.jetbrains.jet.lang.resolve.kotlin;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.SLRUCache;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.storage.LockBasedStorageManager;
import org.jetbrains.annotations.Nullable;
public final class KotlinBinaryClassCache {
// This cache must be small: we only query the same file a few times in a row (from different places)
// Since it is on application level we should be careful about this cache. Consider profiling multiple projects indexing simultaneously.
private final SLRUCache<VirtualFile, KotlinJvmBinaryClass> cache = new SLRUCache<VirtualFile, KotlinJvmBinaryClass>(2, 2) {
private final SLRUCache<VirtualFile, Ref<VirtualFileKotlinClass>> cache = new SLRUCache<VirtualFile, Ref<VirtualFileKotlinClass>>(2, 2) {
@NotNull
@Override
public KotlinJvmBinaryClass createValue(VirtualFile virtualFile) {
// Operations under this lock are not supposed to involve other locks
return new VirtualFileKotlinClass(new LockBasedStorageManager(), virtualFile);
public Ref<VirtualFileKotlinClass> createValue(VirtualFile virtualFile) {
return Ref.create(VirtualFileKotlinClass.create(virtualFile));
}
};
@NotNull
@Nullable
public static KotlinJvmBinaryClass getKotlinBinaryClass(@NotNull VirtualFile file) {
KotlinBinaryClassCache service = ServiceManager.getService(KotlinBinaryClassCache.class);
synchronized (service.cache) {
return service.cache.get(file);
return service.cache.get(file).get();
}
}
}
@@ -19,7 +19,6 @@ package org.jetbrains.jet.lang.resolve.kotlin;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import kotlin.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.ClassReader;
@@ -30,9 +29,6 @@ import org.jetbrains.jet.lang.resolve.java.JvmClassName;
import org.jetbrains.jet.lang.resolve.kotlin.header.KotlinClassHeader;
import org.jetbrains.jet.lang.resolve.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor;
import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.storage.NotNullLazyValue;
import org.jetbrains.jet.storage.NullableLazyValue;
import org.jetbrains.jet.storage.StorageManager;
import org.jetbrains.jet.utils.UtilsPackage;
import static org.jetbrains.asm4.ClassReader.*;
@@ -42,27 +38,49 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
private final static Logger LOG = Logger.getInstance(VirtualFileKotlinClass.class);
private final VirtualFile file;
private final NotNullLazyValue<JvmClassName> className;
private final NullableLazyValue<KotlinClassHeader> classHeader;
private final JvmClassName className;
private final KotlinClassHeader classHeader;
public VirtualFileKotlinClass(@NotNull StorageManager storageManager, @NotNull VirtualFile file) {
private VirtualFileKotlinClass(@NotNull VirtualFile file, @NotNull JvmClassName className, @NotNull KotlinClassHeader classHeader) {
this.file = file;
this.className = storageManager.createLazyValue(
new Function0<JvmClassName>() {
@Override
public JvmClassName invoke() {
return computeClassName();
}
this.className = className;
this.classHeader = classHeader;
}
@Nullable
/* package */ static VirtualFileKotlinClass create(@NotNull VirtualFile file) {
try {
final ReadKotlinClassHeaderAnnotationVisitor readHeaderVisitor = new ReadKotlinClassHeaderAnnotationVisitor();
final Ref<JvmClassName> classNameRef = Ref.create();
new ClassReader(file.contentsToByteArray()).accept(new ClassVisitor(ASM4) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
classNameRef.set(JvmClassName.byInternalName(name));
}
);
this.classHeader = storageManager.createNullableLazyValue(
new Function0<KotlinClassHeader>() {
@Override
public KotlinClassHeader invoke() {
return ReadKotlinClassHeaderAnnotationVisitor.read(VirtualFileKotlinClass.this);
}
@Override
public org.jetbrains.asm4.AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return convertAnnotationVisitor(readHeaderVisitor, desc);
}
);
@Override
public void visitEnd() {
readHeaderVisitor.visitEnd();
}
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
JvmClassName className = classNameRef.get();
if (className == null) return null;
KotlinClassHeader header = readHeaderVisitor.createHeader();
if (header == null) return null;
return new VirtualFileKotlinClass(file, className, header);
}
catch (Throwable e) {
LOG.warn(renderFileReadingErrorMessage(file), e);
return null;
}
}
@NotNull
@@ -71,32 +89,15 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
}
@NotNull
private JvmClassName computeClassName() {
final Ref<JvmClassName> classNameRef = Ref.create();
try {
new ClassReader(file.contentsToByteArray()).accept(new ClassVisitor(ASM4) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
classNameRef.set(JvmClassName.byInternalName(name));
}
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
}
catch (Throwable e) {
logFileReadingError(e);
throw UtilsPackage.rethrow(e);
}
return classNameRef.get();
@Override
public JvmClassName getClassName() {
return className;
}
@NotNull
@Override
public JvmClassName getClassName() {
return className.invoke();
}
@Override
public KotlinClassHeader getClassHeader() {
return classHeader.invoke();
return classHeader;
}
@Override
@@ -115,7 +116,7 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
}
catch (Throwable e) {
logFileReadingError(e);
LOG.error(renderFileReadingErrorMessage(file), e);
throw UtilsPackage.rethrow(e);
}
}
@@ -200,7 +201,7 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
}, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES);
}
catch (Throwable e) {
logFileReadingError(e);
LOG.error(renderFileReadingErrorMessage(file), e);
throw UtilsPackage.rethrow(e);
}
}
@@ -211,6 +212,13 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
return JvmClassName.byInternalName(desc.substring(1, desc.length() - 1));
}
@NotNull
private static String renderFileReadingErrorMessage(@NotNull VirtualFile file) {
return "Could not read file: " + file.getPath() + "\n"
+ "Size in bytes: " + file.getLength() + "\n"
+ "File type: " + file.getFileType().getName();
}
@Override
public int hashCode() {
return file.hashCode();
@@ -225,13 +233,4 @@ public class VirtualFileKotlinClass implements KotlinJvmBinaryClass {
public String toString() {
return getClass().getSimpleName() + ": " + file.toString();
}
private void logFileReadingError(@NotNull Throwable e) {
LOG.error(
"Could not read file: " + file.getPath() + "\n"
+ "Size in bytes: " + file.getLength() + "\n"
+ "File type: " + file.getFileType().getName(),
e
);
}
}