diff --git a/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java b/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java index 55ae9bc97a2f6..0de0a8015b264 100644 --- a/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java +++ b/libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java @@ -24,6 +24,7 @@ import java.util.stream.Collectors; public class Utils { + private static final FileSystem JRT_FS = FileSystems.getFileSystem(URI.create("jrt:/")); // TODO Currently ServerProcessBuilder is using --add-modules=ALL-MODULE-PATH, should this rather // reflect below excludes (except for java.desktop which requires a special handling)? @@ -47,9 +48,9 @@ public class Utils { && m.contains(".internal.") == false && m.contains(".incubator.") == false; - private static Map> findModuleExports(FileSystem fs) throws IOException { + public static Map> findModuleExports() throws IOException { var modulesExports = new HashMap>(); - try (var stream = Files.walk(fs.getPath("modules"))) { + try (var stream = Files.walk(JRT_FS.getPath("modules"))) { stream.filter(p -> p.getFileName().toString().equals("module-info.class")).forEach(x -> { try (var is = Files.newInputStream(x)) { var md = ModuleDescriptor.read(is); @@ -74,21 +75,20 @@ public interface JdkModuleConsumer { } public static void walkJdkModules(JdkModuleConsumer c) throws IOException { - walkJdkModules(DEFAULT_MODULE_PREDICATE, c); + walkJdkModules(DEFAULT_MODULE_PREDICATE, Utils.findModuleExports(), c); } - public static void walkJdkModules(Predicate modulePredicate, JdkModuleConsumer c) throws IOException { - FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); - - var moduleExports = Utils.findModuleExports(fs); - try (var stream = Files.walk(fs.getPath("modules"))) { - var modules = stream.filter(x -> x.toString().endsWith(".class")) - .collect(Collectors.groupingBy(x -> x.subpath(1, 2).toString())); + public static void walkJdkModules(Predicate modulePredicate, Map> exportsByModule, JdkModuleConsumer c) + throws IOException { + try (var stream = Files.walk(JRT_FS.getPath("modules"))) { + var modules = stream.filter( + x -> x.toString().endsWith(".class") && x.getFileName().toString().equals("module-info.class") == false + ).collect(Collectors.groupingBy(x -> x.subpath(1, 2).toString())); for (var kv : modules.entrySet()) { var moduleName = kv.getKey(); if (modulePredicate.test(moduleName)) { - var thisModuleExports = moduleExports.get(moduleName); + var thisModuleExports = exportsByModule.get(moduleName); c.accept(moduleName, kv.getValue(), thisModuleExports); } } diff --git a/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java b/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java index fbdb3fbf76747..c56095cc5c283 100644 --- a/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java +++ b/libs/entitlement/tools/jdk-api-extractor/src/main/java/org/elasticsearch/entitlement/tools/jdkapi/JdkApiExtractor.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -62,21 +63,34 @@ public static void main(String[] args) throws IOException { validateArgs(args); boolean deprecationsOnly = optionalArgs(args).anyMatch(DEPRECATIONS_ONLY::equals); - Map> accessibleImplementationsByClass = new TreeMap<>(); - Map> accessibleForOverridesByClass = new TreeMap<>(); - Map> deprecationsByClass = new TreeMap<>(); + final Map moduleNameByClass = new HashMap<>(); + final Map> accessibleImplementationsByClass = new TreeMap<>(); + final Map> accessibleForOverridesByClass = new TreeMap<>(); + final Map> deprecationsByClass = new TreeMap<>(); + final Map> exportsByModule = Utils.findModuleExports(); + // 1st: map class names to module names (including later excluded modules) for lookup in 2nd step + Utils.walkJdkModules(m -> true, exportsByModule, (moduleName, moduleClasses, moduleExports) -> { + for (var classFile : moduleClasses) { + String prev = moduleNameByClass.put(internalClassName(classFile, moduleName), moduleName); + if (prev != null) { + throw new IllegalStateException("Class " + classFile + " is in both modules " + prev + " and " + moduleName); + } + } + }); + + var visitor = new AccessibleClassVisitor( + moduleNameByClass, + exportsByModule, + accessibleImplementationsByClass, + accessibleForOverridesByClass, + deprecationsByClass + ); Predicate modulePredicate = Utils.DEFAULT_MODULE_PREDICATE.or( m -> optionalArgs(args).anyMatch(INCLUDE_INCUBATOR::equals) && m.contains(".incubator.") ); - - Utils.walkJdkModules(modulePredicate, (moduleName, moduleClasses, moduleExports) -> { - var visitor = new AccessibleClassVisitor( - moduleExports, - accessibleImplementationsByClass, - accessibleForOverridesByClass, - deprecationsByClass - ); + // 2nd: calculate accessible implementations of classes in included modules + Utils.walkJdkModules(modulePredicate, exportsByModule, (moduleName, moduleClasses, moduleExports) -> { for (var classFile : moduleClasses) { // skip if class was already visited earlier due to a dependency on it if (accessibleImplementationsByClass.containsKey(internalClassName(classFile, moduleName))) { @@ -91,7 +105,18 @@ public static void main(String[] args) throws IOException { } }); - writeFile(Path.of(args[0]), deprecationsOnly ? deprecationsByClass : accessibleImplementationsByClass); + // finally, skip some implementations we're not interested in + Predicate>> predicate = entry -> { + if (entry.getKey().startsWith("com/sun/") && entry.getKey().contains("/internal/")) { + // skip com.sun.*.internal classes as they are not part of the supported JDK API + // even if methods override some publicly visible API + return false; + } + // skip classes that are not part of included modules, but checked due to dependencies + String moduleName = moduleNameByClass.get(entry.getKey()); + return modulePredicate.test(moduleName); + }; + writeFile(Path.of(args[0]), deprecationsOnly ? deprecationsByClass : accessibleImplementationsByClass, predicate); } private static String internalClassName(Path clazz, String moduleName) { @@ -132,9 +157,17 @@ private static boolean isWritableOutputPath(String pathStr) { } @SuppressForbidden(reason = "cli tool printing to standard err/out") - private static void writeFile(Path path, Map> methods) throws IOException { + private static void writeFile( + Path path, + Map> methods, + Predicate>> predicate + ) throws IOException { System.out.println("Writing result for " + Runtime.version() + " to " + path.toAbsolutePath()); - Files.write(path, () -> methods.entrySet().stream().flatMap(AccessibleMethod::toLines).iterator(), StandardCharsets.UTF_8); + Files.write( + path, + () -> methods.entrySet().stream().filter(predicate).flatMap(AccessibleMethod::toLines).iterator(), + StandardCharsets.UTF_8 + ); } record AccessibleMethod(String method, String descriptor, boolean isPublic, boolean isFinal, boolean isStatic) { @@ -163,7 +196,8 @@ static Stream toLines(Map.Entry> ent } static class AccessibleClassVisitor extends ClassVisitor { - private final Set moduleExports; + private final Map moduleNameByClass; + private final Map> exportsByModule; private final Map> accessibleImplementationsByClass; private final Map> accessibleForOverridesByClass; private final Map> deprecationsByClass; @@ -179,13 +213,15 @@ static class AccessibleClassVisitor extends ClassVisitor { private boolean isExported; AccessibleClassVisitor( - Set moduleExports, + Map moduleNameByClass, + Map> exportsByModule, Map> accessibleImplementationsByClass, Map> accessibleForOverridesByClass, Map> deprecationsByClass ) { super(ASM9); - this.moduleExports = moduleExports; + this.moduleNameByClass = moduleNameByClass; + this.exportsByModule = exportsByModule; this.accessibleImplementationsByClass = accessibleImplementationsByClass; this.accessibleForOverridesByClass = accessibleForOverridesByClass; this.deprecationsByClass = deprecationsByClass; @@ -214,7 +250,7 @@ public void visit(int version, int access, String name, String signature, String } // only initialize local state AFTER visiting all dependencies above! super.visit(version, access, name, signature, superName, interfaces); - this.isExported = moduleExports.contains(getPackageName(name)); + this.isExported = getModuleExports(getModuleName(name)).contains(getPackageName(name)); this.className = name; this.isPublicClass = (access & ACC_PUBLIC) != 0; this.isFinalClass = (access & ACC_FINAL) != 0; @@ -224,6 +260,22 @@ public void visit(int version, int access, String name, String signature, String this.deprecations = newSortedSet(); } + private String getModuleName(String name) { + String module = moduleNameByClass.get(name); + if (module == null) { + throw new IllegalStateException("Unknown module for class: " + name); + } + return module; + } + + private Set getModuleExports(String module) { + Set exports = exportsByModule.get(module); + if (exports == null) { + throw new IllegalStateException("Unknown exports for module: " + module); + } + return exports; + } + @Override public void visitEnd() { super.visitEnd();