diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java index bc3fb600231..85f810cd2f1 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -20,12 +21,14 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -51,6 +54,7 @@ public class ClasspathJep247 extends ClasspathJrt { protected Set packageCache; protected final File jdkHome; protected String modulePath; + protected Set currentReleaseModules; public ClasspathJep247(File jdkHome, String release, AccessRuleSet accessRuleSet) { super(new File(new File(jdkHome, "lib"), JRTUtil.JRT_FS_JAR), false, accessRuleSet, null); //$NON-NLS-1$ @@ -68,6 +72,10 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN } @Override public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + return super.findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, asBinaryOnly); + } if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -117,10 +125,22 @@ public void initialize() throws IOException { if (!Files.exists(this.fs.getPath(this.releaseInHex))) { throw new IllegalArgumentException("release " + this.compliance + " is not found in the system"); //$NON-NLS-1$//$NON-NLS-2$ } + Path systemModules = this.fs.getPath(this.releaseInHex, "system-modules"); //$NON-NLS-1$ + if (Files.isRegularFile(systemModules)) { + // Per JEP 247, ct.sym uses this file for the current release instead of storing duplicate signatures. + // Read classes from the live JRT image, but expose only the standard modules listed by ct.sym. + this.currentReleaseModules = Set.copyOf(Files.readAllLines(systemModules, StandardCharsets.UTF_8)); + } super.initialize(); } @Override public void loadModules() { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + super.loadModules(); + this.moduleNamesCache.retainAll(this.currentReleaseModules); + return; + } // Modules below level 9 are not dealt with here. Leave it to ClasspathJrt if (this.jdklevel <= ClassFileConstants.JDK1_8) { super.loadModules(); @@ -187,6 +207,24 @@ public FileVisitResult postVisitDirectory(java.nio.file.Path dir, IOException ex this.moduleNamesCache.addAll(cache.keySet()); } + @Override + public Collection getModuleNames(Collection limitModules, Function getModule) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + return selectModules(this.moduleNamesCache, limitModules, getModule); + } + return super.getModuleNames(limitModules, getModule); + } + + @Override + public IModule getModule(char[] moduleName) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null && !this.currentReleaseModules.contains(String.valueOf(moduleName))) { + return null; + } + return super.getModule(moduleName); + } + @Override void acceptModule(ClassFileReader reader, Map cache) { // Modules below level 9 are not dealt with here. Leave it to ClasspathJrt @@ -208,6 +246,23 @@ protected void addToPackageCache(String packageName, boolean endsWithSep) { } @Override public synchronized char[][] getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + if (moduleName != null && !this.currentReleaseModules.contains(moduleName)) { + return null; + } + char[][] declaringModules = super.getModulesDeclaringPackage(qualifiedPackageName, moduleName); + if (moduleName != null || declaringModules == null) { + return declaringModules; + } + List filteredModules = new ArrayList<>(declaringModules.length); + for (char[] declaringModule : declaringModules) { + if (this.currentReleaseModules.contains(String.valueOf(declaringModule))) { + filteredModules.add(declaringModule); + } + } + return filteredModules.isEmpty() ? null : filteredModules.toArray(char[][]::new); + } if (this.packageCache == null) { this.packageCache = new HashSet<>(41); this.packageCache.add(Util.EMPTY_STRING); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java index 4492a785880..b006a996a5a 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/ClasspathJep247Jdk12.java @@ -58,6 +58,10 @@ public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageN } @Override public NameEnvironmentAnswer findClass(char[] typeName, String qualifiedPackageName, String moduleName, String qualifiedBinaryFileName, boolean asBinaryOnly) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + return super.findClass(typeName, qualifiedPackageName, moduleName, qualifiedBinaryFileName, asBinaryOnly); + } if (!isPackage(qualifiedPackageName, moduleName)) return null; // most common case @@ -147,6 +151,11 @@ public void initialize() throws IOException { } @Override public void loadModules() { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + super.loadModules(); + return; + } // Modules below level 9 are not dealt with here. Leave it to ClasspathJrt if (this.jdklevel <= ClassFileConstants.JDK1_8) { super.loadModules(); @@ -219,6 +228,10 @@ public Collection getModuleNames(Collection limitModule, Functio } @Override public IModule getModule(char[] moduleName) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + return super.getModule(moduleName); + } // Modules below level 9 are not dealt with here. Leave it to ClasspathJrt if (this.jdklevel <= ClassFileConstants.JDK1_8) { return super.getModule(moduleName); @@ -261,6 +274,10 @@ void acceptModule(ClassFileReader reader, Map cache) { } @Override public synchronized char[][] getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) { + // The current release uses the JRT filtered by ct.sym's system-modules. + if (this.currentReleaseModules != null) { + return super.getModulesDeclaringPackage(qualifiedPackageName, moduleName); + } if (this.jdklevel >= ClassFileConstants.JDK9) { // Delegate to the boss, even if it means inaccurate error reporting at times List mods = JRTUtil.getModulesDeclaringPackage(this.jrtFileSystem, qualifiedPackageName, moduleName); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/Main.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/Main.java index eff4260f2bc..3b8e464ab21 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/Main.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/Main.java @@ -5201,7 +5201,7 @@ protected void setPaths(ArrayList bootclasspaths, ArrayList allPaths = null; long jdkLevel = validateClasspathOptions(bootclasspaths, endorsedDirClasspaths, extdirsClasspaths); - if (this.releaseVersion != null && this.complianceLevel < jdkLevel) { + if (this.releaseVersion != null && this.complianceLevel <= jdkLevel) { // TODO: Revisit for access rules allPaths = new ArrayList<>(); Classpath olderSystemRelease = FileSystem.getOlderSystemRelease(this.javaHomeCache.getAbsolutePath(), this.releaseVersion, null); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java index 1ac953cc8ff..d0b41f405aa 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java @@ -547,7 +547,9 @@ protected void handleLocations() { } } } - fileSystemClasspaths.addAll(platformLocations); + if (this.releaseVersion == null) { + fileSystemClasspaths.addAll(platformLocations); + } break; // Only possible scenario is, we have one and only entry representing the Java home. } else { Classpath classpath = FileSystem.getClasspath( @@ -791,7 +793,7 @@ private String getJavaVersion(File javaHome) { } private Classpath getSystemClasspath(File jdkHome, long jdkLevel) { Classpath system; - if (this.releaseVersion != null && this.complianceLevel < jdkLevel) { + if (this.releaseVersion != null && this.complianceLevel <= jdkLevel) { String versionFromJdkLevel = CompilerOptions.versionFromJdkLevel(this.complianceLevel); if (versionFromJdkLevel.length() >= 3) { versionFromJdkLevel = versionFromJdkLevel.substring(2); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java index 3b80655db85..e9f1414a9f2 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java @@ -44,6 +44,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.module.ModuleFinder; import java.text.MessageFormat; import java.util.Iterator; import java.util.List; @@ -55,6 +57,7 @@ import org.eclipse.jdt.core.tests.util.AbstractCompilerTest; import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.internal.compiler.batch.ClasspathDirectory; +import org.eclipse.jdt.internal.compiler.batch.ClasspathJep247; import org.eclipse.jdt.internal.compiler.batch.ClasspathJar; import org.eclipse.jdt.internal.compiler.batch.FileSystem; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; @@ -87,6 +90,20 @@ public static Test suite() { public static Class testClass() { return BatchCompilerTest.class; } + private static class InspectableMain extends Main { + InspectableMain() { + super(new PrintWriter(new StringWriter()), new PrintWriter(new StringWriter()), false, null, null); + } + + boolean usesCtSymReleaseClasspath() { + for (Classpath classpath : this.checkedClasspaths) { + if (classpath instanceof ClasspathJep247) { + return true; + } + } + return false; + } + } static class StringMatcher extends Matcher { private final String expected; private final Normalizer normalizer; @@ -13528,4 +13545,43 @@ public static void main(String[] args) { true); } -} \ No newline at end of file +public void testReleaseCurrentJdkUsesCtSym() { + File outputDirectory = new File(OUTPUT_DIR); + Util.flushDirectoryContent(outputDirectory); + outputDirectory.mkdirs(); + String sourceFile = OUTPUT_DIR + File.separator + "X.java"; + Util.writeToFile("public class X {}", sourceFile); + + InspectableMain compiler = new InspectableMain(); + String release = System.getProperty("java.specification.version"); + assertTrue(compiler.compile(new String[] { "--release", release, "-d", OUTPUT_DIR, sourceFile })); + assertTrue("--release must use ct.sym metadata for the current JDK", compiler.usesCtSymReleaseClasspath()); +} +public void testReleaseCurrentJdkFiltersImplementationSpecificModules() { + String implementationSpecificModule = "jdk.internal.vm.ci"; + if (ModuleFinder.ofSystem().find(implementationSpecificModule).isEmpty()) { + return; + } + + String[] testFiles = { + "X.java", + "public class X {}" + }; + String release = System.getProperty("java.specification.version"); + String sourceFile = "\"" + OUTPUT_DIR + File.separator + "X.java\""; + String outputDirectory = "-d \"" + OUTPUT_DIR + "\""; + + runConformTest( + testFiles, + outputDirectory + " --add-modules " + implementationSpecificModule + " " + sourceFile, + "", + "", + true); + runNegativeTest( + testFiles, + outputDirectory + " --release " + release + " --add-modules " + implementationSpecificModule + " " + sourceFile, + "", + "invalid module name: " + implementationSpecificModule + "\n", + true); +} +}