summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Fraser <george@fivetran.com>2019-01-09 00:07:25 -0800
committerGeorge Fraser <george@fivetran.com>2019-01-09 00:07:25 -0800
commit2a29930c8677b75b9c99a9cd5339f0b8009e72f4 (patch)
tree4bb98471450d85dd3a2f8cfa278157e823569e8b
parent04e4f359cb80c7b8e450103dc7331f6deb363872 (diff)
downloadjava-language-server-2a29930c8677b75b9c99a9cd5339f0b8009e72f4.zip
Reader access flags directly from class file
-rw-r--r--src/main/java/org/javacs/ClassHeader.java140
-rw-r--r--src/main/java/org/javacs/ClassSource.java35
-rw-r--r--src/main/java/org/javacs/Classes.java50
-rw-r--r--src/main/java/org/javacs/CompileFile.java4
-rw-r--r--src/main/java/org/javacs/CompileFocus.java45
-rw-r--r--src/main/java/org/javacs/JavaCompilerService.java2
-rw-r--r--src/test/java/org/javacs/ClassesTest.java11
-rw-r--r--src/test/java/org/javacs/SourceFileManagerTest.java13
8 files changed, 202 insertions, 98 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
diff --git a/src/test/java/org/javacs/ClassesTest.java b/src/test/java/org/javacs/ClassesTest.java
index 95212fe..c5e2a7e 100644
--- a/src/test/java/org/javacs/ClassesTest.java
+++ b/src/test/java/org/javacs/ClassesTest.java
@@ -16,18 +16,16 @@ public class ClassesTest {
@Test
public void list() {
var jdk = Classes.jdkTopLevelClasses();
- assertThat(jdk.classes(), hasItem("java.util.List"));
- assertThat(jdk.load("java.util.List"), not(nullValue()));
+ assertThat(jdk, hasItem("java.util.List"));
var empty = Classes.classPathTopLevelClasses(Collections.emptySet());
- assertThat(empty.classes(), not(hasItem("java.util.List")));
+ assertThat(empty, not(hasItem("java.util.List")));
}
@Test
public void arrayList() {
var jdk = Classes.jdkTopLevelClasses();
- assertThat(jdk.classes(), hasItem("java.util.ArrayList"));
- assertThat(jdk.load("java.util.ArrayList"), not(nullValue()));
+ assertThat(jdk, hasItem("java.util.ArrayList"));
}
@Test
@@ -46,9 +44,6 @@ public class ClassesTest {
var main = found.stream().filter(c -> c.getName().equals("org.javacs.Main")).findFirst();
assertTrue(main.isPresent());
-
- var load = main.get().load();
- assertNotNull(load);
}
void ancestors(ClassLoader classLoader) {
diff --git a/src/test/java/org/javacs/SourceFileManagerTest.java b/src/test/java/org/javacs/SourceFileManagerTest.java
index 64a35ac..3b50327 100644
--- a/src/test/java/org/javacs/SourceFileManagerTest.java
+++ b/src/test/java/org/javacs/SourceFileManagerTest.java
@@ -71,5 +71,18 @@ public class SourceFileManagerTest {
assertThat(sourceClassName, equalTo(standardJavaName));
}
+ @Test
+ public void javaUtilList() throws IOException {
+ var file =
+ sourceFileManager.getJavaFileForInput(
+ StandardLocation.PLATFORM_CLASS_PATH, "java.util.List", JavaFileObject.Kind.CLASS);
+ assertThat("Found java.util.List in platform classpath", file, notNullValue());
+
+ var header = ClassHeader.of(file.openInputStream());
+ assertTrue(header.isInterface);
+ assertTrue(header.isAbstract);
+ assertTrue(header.isPublic);
+ }
+
private static final Logger LOG = Logger.getLogger("main");
}