diff options
author | George Fraser <george@fivetran.com> | 2019-01-09 00:07:25 -0800 |
---|---|---|
committer | George Fraser <george@fivetran.com> | 2019-01-09 00:07:25 -0800 |
commit | 2a29930c8677b75b9c99a9cd5339f0b8009e72f4 (patch) | |
tree | 4bb98471450d85dd3a2f8cfa278157e823569e8b /src/main | |
parent | 04e4f359cb80c7b8e450103dc7331f6deb363872 (diff) | |
download | java-language-server-2a29930c8677b75b9c99a9cd5339f0b8009e72f4.zip |
Reader access flags directly from class file
Diffstat (limited to 'src/main')
-rw-r--r-- | src/main/java/org/javacs/ClassHeader.java | 140 | ||||
-rw-r--r-- | src/main/java/org/javacs/ClassSource.java | 35 | ||||
-rw-r--r-- | src/main/java/org/javacs/Classes.java | 50 | ||||
-rw-r--r-- | src/main/java/org/javacs/CompileFile.java | 4 | ||||
-rw-r--r-- | src/main/java/org/javacs/CompileFocus.java | 45 | ||||
-rw-r--r-- | src/main/java/org/javacs/JavaCompilerService.java | 2 |
6 files changed, 186 insertions, 90 deletions
diff --git a/src/main/java/org/javacs/ClassHeader.java b/src/main/java/org/javacs/ClassHeader.java new file mode 100644 index 0000000..5f5ad0d --- /dev/null +++ b/src/main/java/org/javacs/ClassHeader.java @@ -0,0 +1,140 @@ +package org.javacs; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +// Read the classfile format defined in https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html +class ClassHeader { + + final boolean isPublic, isFinal, isInterface, isAbstract, isAnnotation, isEnum, isModule; + + static ClassHeader of(InputStream in) { + return new ClassHeader(new DataInputStream(in)); + } + + private ClassHeader(DataInputStream in) { + try { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + // u2 access_flags; + var magic = in.readNBytes(4); + var minorVersion = in.readUnsignedShort(); + var majorVersion = in.readUnsignedShort(); + var constantPoolCount = in.readUnsignedShort(); + var constants = new Constant[constantPoolCount]; + var i = 0; + while (i < constantPoolCount - 1) { + constants[i] = readConstant(in); + i += slots(constants[i]); + } + var accessFlags = in.readUnsignedShort(); + + this.isPublic = (accessFlags & ACC_PUBLIC) != 0; + this.isFinal = (accessFlags & ACC_FINAL) != 0; + this.isInterface = (accessFlags & ACC_INTERFACE) != 0; + this.isAbstract = (accessFlags & ACC_ABSTRACT) != 0; + this.isAnnotation = (accessFlags & ACC_ANNOTATION) != 0; + this.isEnum = (accessFlags & ACC_ENUM) != 0; + this.isModule = (accessFlags & ACC_MODULE) != 0; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static final int ACC_PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. + private static final int ACC_FINAL = 0x0010; // Declared final; no subclasses allowed. + private static final int ACC_SUPER = + 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction. + private static final int ACC_INTERFACE = 0x0200; // Is an interface, not a class. + private static final int ACC_ABSTRACT = 0x0400; // Declared abstract; must not be instantiated. + private static final int ACC_SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code. + private static final int ACC_ANNOTATION = 0x2000; // Declared as an annotation type. + private static final int ACC_ENUM = 0x4000; // Declared as an enum type. + private static final int ACC_MODULE = 0x8000; // Is a module, not a class or interface. + + private static final int CONSTANT_Class = 7; + private static final int CONSTANT_Fieldref = 9; + private static final int CONSTANT_Methodref = 10; + private static final int CONSTANT_InterfaceMethodref = 11; + private static final int CONSTANT_String = 8; + private static final int CONSTANT_Integer = 3; + private static final int CONSTANT_Float = 4; + private static final int CONSTANT_Long = 5; + private static final int CONSTANT_Double = 6; + private static final int CONSTANT_NameAndType = 12; + private static final int CONSTANT_Utf8 = 1; + private static final int CONSTANT_MethodHandle = 15; + private static final int CONSTANT_MethodType = 16; + private static final int CONSTANT_InvokeDynamic = 18; + private static final int CONSTANT_Module = 19; + private static final int CONSTANT_Package = 20; + + private Constant readConstant(DataInputStream in) throws IOException { + var tag = (short) in.readByte(); + switch (tag) { + case CONSTANT_Class: + case CONSTANT_String: + case CONSTANT_MethodType: + { + var info = in.readNBytes(2); + return new Constant(tag, info); + } + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_NameAndType: + case CONSTANT_InvokeDynamic: + { + var info = in.readNBytes(4); + return new Constant(tag, info); + } + case CONSTANT_Long: + case CONSTANT_Double: + { + var info = in.readNBytes(8); + return new Constant(tag, info); + } + case CONSTANT_Utf8: + { + var length = in.readUnsignedShort(); + var string = in.readNBytes(length); + return new Constant(tag, string); + } + case CONSTANT_MethodHandle: + case CONSTANT_Module: + case CONSTANT_Package: + { + var info = in.readNBytes(3); + return new Constant(tag, info); + } + default: + throw new RuntimeException("Don't know what to do with " + tag); + } + } + + private int slots(Constant c) { + switch (c.tag) { + case CONSTANT_Long: + case CONSTANT_Double: + return 2; + default: + return 1; + } + } + + private static class Constant { + final short tag; + final byte[] info; + + Constant(short tag, byte[] info) { + this.tag = tag; + this.info = info; + } + } +} diff --git a/src/main/java/org/javacs/ClassSource.java b/src/main/java/org/javacs/ClassSource.java deleted file mode 100644 index cbf6bbd..0000000 --- a/src/main/java/org/javacs/ClassSource.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.javacs; - -import java.lang.reflect.Modifier; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.logging.Logger; - -interface ClassSource { - Set<String> classes(); - - Optional<Class<?>> load(String className); - - static final Logger LOG = Logger.getLogger("main"); - static final Set<String> failedToLoad = new HashSet<>(); - - // TODO figure this out by directly reading the class name - // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html - // https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassFile.java - default boolean isPublic(String className) { - if (failedToLoad.contains(className)) return false; - try { - return load(className).map(c -> Modifier.isPublic(c.getModifiers())).orElse(false); - } catch (Exception e) { - LOG.warning(String.format("Failed to load %s: %s", className, e.getMessage())); - failedToLoad.add(className); - return false; - } - } - - default boolean isAccessibleFromPackage(String className, String fromPackage) { - var packageName = Parser.mostName(className); - return packageName.equals(fromPackage) || isPublic(className); - } -} diff --git a/src/main/java/org/javacs/Classes.java b/src/main/java/org/javacs/Classes.java index bc6b321..a935d33 100644 --- a/src/main/java/org/javacs/Classes.java +++ b/src/main/java/org/javacs/Classes.java @@ -9,9 +9,7 @@ import java.net.URLClassLoader; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.logging.Level; @@ -115,9 +113,7 @@ class Classes { "jdk.zipfs", }; - private static Set<String> loadError = new HashSet<String>(); - - static ClassSource jdkTopLevelClasses() { + static Set<String> jdkTopLevelClasses() { LOG.info("Searching for top-level classes in the JDK"); var classes = new HashSet<String>(); @@ -142,29 +138,10 @@ class Classes { LOG.info(String.format("Found %d classes in the java platform", classes.size())); - class PlatformClassSource implements ClassSource { - @Override - public Set<String> classes() { - return Collections.unmodifiableSet(classes); - } - - @Override - public Optional<Class<?>> load(String className) { - if (loadError.contains(className)) return Optional.empty(); - - try { - return Optional.of(ClassLoader.getPlatformClassLoader().loadClass(className)); - } catch (ClassNotFoundException | NoClassDefFoundError | ClassFormatError e) { - LOG.log(Level.WARNING, "Could not load " + className + ": " + e.getMessage()); - loadError.add(className); - return Optional.empty(); - } - } - } - return new PlatformClassSource(); + return classes; } - static ClassSource classPathTopLevelClasses(Set<Path> classPath) { + static Set<String> classPathTopLevelClasses(Set<Path> classPath) { LOG.info(String.format("Searching for top-level classes in %d classpath locations", classPath.size())); Function<Path, URL> toUrl = @@ -187,26 +164,7 @@ class Classes { LOG.info(String.format("Found %d classes in classpath", classes.size())); - class ClassPathClassSource implements ClassSource { - @Override - public Set<String> classes() { - return Collections.unmodifiableSet(classes); - } - - @Override - public Optional<Class<?>> load(String className) { - if (loadError.contains(className)) return Optional.empty(); - - try { - return Optional.of(classLoader.loadClass(className)); - } catch (ClassNotFoundException | NoClassDefFoundError | ClassFormatError e) { - LOG.log(Level.WARNING, "Could not load " + className + ": " + e.getMessage()); - loadError.add(className); - return Optional.empty(); - } - } - } - return new ClassPathClassSource(); + return classes; } private static final Logger LOG = Logger.getLogger("main"); diff --git a/src/main/java/org/javacs/CompileFile.java b/src/main/java/org/javacs/CompileFile.java index aa38001..f0d2f86 100644 --- a/src/main/java/org/javacs/CompileFile.java +++ b/src/main/java/org/javacs/CompileFile.java @@ -220,8 +220,8 @@ public class CompileFile { // TODO cache parsed imports on a per-file basis var sourcePathImports = Parser.existingImports(FileStore.all()); var classes = new HashSet<String>(); - classes.addAll(parent.jdkClasses.classes()); - classes.addAll(parent.classPathClasses.classes()); + classes.addAll(parent.jdkClasses); + classes.addAll(parent.classPathClasses); var fixes = Parser.resolveSymbols(unresolved, sourcePathImports, classes); // Figure out which existing imports are actually used var trees = Trees.instance(task); diff --git a/src/main/java/org/javacs/CompileFocus.java b/src/main/java/org/javacs/CompileFocus.java index d7634e8..38a71d4 100644 --- a/src/main/java/org/javacs/CompileFocus.java +++ b/src/main/java/org/javacs/CompileFocus.java @@ -15,6 +15,8 @@ import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; public class CompileFocus { public static final int MAX_COMPLETION_ITEMS = 50; @@ -625,8 +627,8 @@ public class CompileFocus { result.add(prefix); } }; - for (var name : parent.jdkClasses.classes()) checkClassName.accept(name); - for (var name : parent.classPathClasses.classes()) checkClassName.accept(name); + for (var name : parent.jdkClasses) checkClassName.accept(name); + for (var name : parent.classPathClasses) checkClassName.accept(name); return result; } @@ -665,9 +667,10 @@ public class CompileFocus { // Check JDK LOG.info("...checking JDK"); - for (var c : parent.jdkClasses.classes()) { + for (var c : parent.jdkClasses) { if (tooManyItems(result.size())) return; - if (matchesPartialName.test(c) && parent.jdkClasses.isAccessibleFromPackage(c, packageName)) { + if (!matchesPartialName.test(c)) continue; + if (isSamePackage(c, packageName) || isPublicClassFile(c)) { result.add(Completion.ofClassName(c, isImported(c))); } } @@ -675,9 +678,10 @@ public class CompileFocus { // Check classpath LOG.info("...checking classpath"); var classPathNames = new HashSet<String>(); - for (var c : parent.classPathClasses.classes()) { + for (var c : parent.classPathClasses) { if (tooManyItems(result.size())) return; - if (matchesPartialName.test(c) && parent.classPathClasses.isAccessibleFromPackage(c, packageName)) { + if (!matchesPartialName.test(c)) continue; + if (isSamePackage(c, packageName) || isPublicClassFile(c)) { result.add(Completion.ofClassName(c, isImported(c))); classPathNames.add(c); } @@ -699,6 +703,35 @@ public class CompileFocus { } } + private boolean isSamePackage(String className, String fromPackage) { + return Parser.mostName(className).equals(fromPackage); + } + + private boolean isPublicClassFile(String className) { + try { + var platform = + parent.fileManager.getJavaFileForInput( + StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS); + if (platform != null) return isPublic(platform); + var classpath = + parent.fileManager.getJavaFileForInput( + StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS); + if (classpath != null) return isPublic(classpath); + return false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private boolean isPublic(JavaFileObject classFile) { + try (var in = classFile.openInputStream()) { + var header = ClassHeader.of(in); + return header.isPublic; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private List<Completion> accessibleClasses(Path file, String partialName, String fromPackage, Set<String> skip) { var parse = Parser.parse(file); var toPackage = Objects.toString(parse.getPackageName(), ""); diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java index 80fbd2b..8dfeaf8 100644 --- a/src/main/java/org/javacs/JavaCompilerService.java +++ b/src/main/java/org/javacs/JavaCompilerService.java @@ -18,7 +18,7 @@ public class JavaCompilerService { final Set<Path> classPath, docPath; final JavaCompiler compiler = ServiceLoader.load(JavaCompiler.class).iterator().next(); final Docs docs; - final ClassSource jdkClasses = Classes.jdkTopLevelClasses(), classPathClasses; + final Set<String> jdkClasses = Classes.jdkTopLevelClasses(), classPathClasses; // Diagnostics from the last compilation task final List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>(); // Use the same file manager for multiple tasks, so we don't repeatedly re-compile the same files |