# HG changeset patch # User jjg # Date 1537896632 25200 # Node ID 8f66a57054b76d1131cfc35ff93a5b28322f707b # Parent cdfabab3413fb2018403cf3e3aac9b36c59d7dbb 8210839: Improve interaction between source launcher and classpath Reviewed-by: alanb, mchung diff -r cdfabab3413f -r 8f66a57054b7 src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java Tue Sep 25 09:34:51 2018 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java Tue Sep 25 10:30:32 2018 -0700 @@ -50,8 +50,6 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -63,6 +61,8 @@ import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.ResourceBundle; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.lang.model.SourceVersion; import javax.lang.model.element.NestingKind; @@ -399,9 +399,6 @@ ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader()); try { Class appClass = Class.forName(mainClassName, true, cl); - if (appClass.getClassLoader() != cl) { - throw new Fault(Errors.UnexpectedClass(mainClassName)); - } Method main = appClass.getDeclaredMethod("main", String[].class); int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC; if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) { @@ -535,26 +532,103 @@ } /** - * An in-memory classloader, that uses an in-memory cache written by {@link MemoryFileManager}. + * An in-memory classloader, that uses an in-memory cache of classes written by + * {@link MemoryFileManager}. * - *

The classloader uses the standard parent-delegation model, just providing - * {@code findClass} to find classes in the in-memory cache. + *

The classloader inverts the standard parent-delegation model, giving preference + * to classes defined in the source file before classes known to the parent (such + * as any like-named classes that might be found on the application class path.) */ private static class MemoryClassLoader extends ClassLoader { /** - * The map of classes known to this class loader, indexed by + * The map of all classes found in the source file, indexed by * {@link ClassLoader#name binary name}. */ - private final Map map; + private final Map sourceFileClasses; + + MemoryClassLoader(Map sourceFileClasses, ClassLoader parent) { + super(parent); + this.sourceFileClasses = sourceFileClasses; + } + + /** + * Override loadClass to check for classes defined in the source file + * before checking for classes in the parent class loader, + * including those on the classpath. + * + * {@code loadClass(String name)} calls this method, and so will have the same behavior. + * + * @param name the name of the class to load + * @param resolve whether or not to resolve the class + * @return the class + * @throws ClassNotFoundException if the class is not found + */ + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class c = findLoadedClass(name); + if (c == null) { + if (sourceFileClasses.containsKey(name)) { + c = findClass(name); + } else { + c = getParent().loadClass(name); + } + if (resolve) { + resolveClass(c); + } + } + return c; + } + } + - MemoryClassLoader(Map map, ClassLoader parent) { - super(parent); - this.map = map; + /** + * Override getResource to check for resources (i.e. class files) defined in the + * source file before checking resources in the parent class loader, + * including those on the class path. + * + * {@code getResourceAsStream(String name)} calls this method, + * and so will have the same behavior. + * + * @param name the name of the resource + * @return a URL for the resource, or null if not found + */ + @Override + public URL getResource(String name) { + if (sourceFileClasses.containsKey(toBinaryName(name))) { + return findResource(name); + } else { + return getParent().getResource(name); + } + } + + /** + * Override getResources to check for resources (i.e. class files) defined in the + * source file before checking resources in the parent class loader, + * including those on the class path. + * + * @param name the name of the resource + * @return an enumeration of the resources in this loader and in the application class loader + */ + @Override + public Enumeration getResources(String name) throws IOException { + URL u = findResource(name); + Enumeration e = getParent().getResources(name); + if (u == null) { + return e; + } else { + List list = new ArrayList<>(); + list.add(u); + while (e.hasMoreElements()) { + list.add(e.nextElement()); + } + return Collections.enumeration(list); + } } @Override protected Class findClass(String name) throws ClassNotFoundException { - byte[] bytes = map.get(name); + byte[] bytes = sourceFileClasses.get(name); if (bytes == null) { throw new ClassNotFoundException(name); } @@ -564,7 +638,7 @@ @Override public URL findResource(String name) { String binaryName = toBinaryName(name); - if (binaryName == null || map.get(binaryName) == null) { + if (binaryName == null || sourceFileClasses.get(binaryName) == null) { return null; } @@ -628,7 +702,7 @@ if (!u.getProtocol().equalsIgnoreCase(PROTOCOL)) { throw new IllegalArgumentException(u.toString()); } - return new MemoryURLConnection(u, map.get(toBinaryName(u.getPath()))); + return new MemoryURLConnection(u, sourceFileClasses.get(toBinaryName(u.getPath()))); } } diff -r cdfabab3413f -r 8f66a57054b7 src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties Tue Sep 25 09:34:51 2018 -0700 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties Tue Sep 25 10:30:32 2018 -0700 @@ -109,10 +109,6 @@ can''t find class: {0} # 0: string -launcher.err.unexpected.class=\ - class found on application class path: {0} - -# 0: string launcher.err.cant.find.main.method=\ can''t find main(String[]) method in class: {0} diff -r cdfabab3413f -r 8f66a57054b7 test/langtools/tools/javac/launcher/SourceLauncherTest.java --- a/test/langtools/tools/javac/launcher/SourceLauncherTest.java Tue Sep 25 09:34:51 2018 -0700 +++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java Tue Sep 25 10:30:32 2018 -0700 @@ -47,6 +47,7 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.regex.Pattern; import com.sun.tools.javac.launcher.Main; @@ -233,22 +234,98 @@ } @Test - public void testWrongClass(Path base) throws IOException { + public void testLoadClass(Path base) throws IOException { + Path src1 = base.resolve("src1"); + Path file1 = src1.resolve("LoadClass.java"); + tb.writeJavaFiles(src1, + "class LoadClass {\n" + + " public static void main(String... args) {\n" + + " System.out.println(\"on classpath\");\n" + + " };\n" + + "}\n"); + Path classes1 = Files.createDirectories(base.resolve("classes")); + new JavacTask(tb) + .outdir(classes1) + .files(file1) + .run(); + String log1 = new JavaTask(tb) + .classpath(classes1.toString()) + .className("LoadClass") + .run(Task.Expect.SUCCESS) + .getOutput(Task.OutputKind.STDOUT); + checkEqual("stdout", log1.trim(), + "on classpath"); + + Path src2 = base.resolve("src2"); + Path file2 = src2.resolve("LoadClass.java"); + tb.writeJavaFiles(src2, + "class LoadClass {\n" + + " public static void main(String... args) {\n" + + " System.out.println(\"in source file\");\n" + + " };\n" + + "}\n"); + String log2 = new JavaTask(tb) + .classpath(classes1.toString()) + .className(file2.toString()) + .run(Task.Expect.SUCCESS) + .getOutput(Task.OutputKind.STDOUT); + checkEqual("stdout", log2.trim(), + "in source file"); + } + + @Test + public void testGetResource(Path base) throws IOException { Path src = base.resolve("src"); - Path file = src.resolve("WrongClass.java"); - tb.writeJavaFiles(src, "class WrongClass { }"); + Path file = src.resolve("GetResource.java"); + tb.writeJavaFiles(src, + "class GetResource {\n" + + " public static void main(String... args) {\n" + + " System.out.println(GetResource.class.getClassLoader().getResource(\"GetResource.class\"));\n" + + " };\n" + + "}\n"); Path classes = Files.createDirectories(base.resolve("classes")); new JavacTask(tb) .outdir(classes) .files(file) .run(); + String log = new JavaTask(tb) .classpath(classes.toString()) .className(file.toString()) - .run(Task.Expect.FAIL) - .getOutput(Task.OutputKind.STDERR); - checkEqual("stderr", log.trim(), - "error: class found on application class path: WrongClass"); + .run(Task.Expect.SUCCESS) + .getOutput(Task.OutputKind.STDOUT); + checkMatch("stdout", log.trim(), + Pattern.compile("sourcelauncher-memoryclassloader[0-9]+:GetResource.class")); + } + + @Test + public void testGetResources(Path base) throws IOException { + Path src = base.resolve("src"); + Path file = src.resolve("GetResources.java"); + tb.writeJavaFiles(src, + "import java.io.*; import java.net.*; import java.util.*;\n" + + "class GetResources {\n" + + " public static void main(String... args) throws IOException {\n" + + " Enumeration e =\n" + + " GetResources.class.getClassLoader().getResources(\"GetResources.class\");\n" + + " while (e.hasMoreElements()) System.out.println(e.nextElement());\n" + + " };\n" + + "}\n"); + Path classes = Files.createDirectories(base.resolve("classes")); + new JavacTask(tb) + .outdir(classes) + .files(file) + .run(); + + List log = new JavaTask(tb) + .classpath(classes.toString()) + .className(file.toString()) + .run(Task.Expect.SUCCESS) + .getOutputLines(Task.OutputKind.STDOUT); + checkMatch("stdout:0", log.get(0).trim(), + Pattern.compile("sourcelauncher-memoryclassloader[0-9]+:GetResources.class")); + checkMatch("stdout:1", log.get(1).trim(), + Pattern.compile("file:/.*/testGetResources/classes/GetResources.class")); } @Test @@ -294,7 +371,37 @@ checkEmpty("stdout", r.stdOut); checkEqual("stderr", r.stdErr, expectStdErr); checkFault("exception", r.exception, "error: compilation failed"); + } + @Test + public void testClassNotFound(Path base) throws IOException { + Path src = base.resolve("src"); + Path file = src.resolve("ClassNotFound.java"); + tb.writeJavaFiles(src, + "class ClassNotFound {\n" + + " public static void main(String... args) {\n" + + " try {\n" + + " Class.forName(\"NoSuchClass\");\n" + + " System.out.println(\"no exception\");\n" + + " System.exit(1);\n" + + " } catch (ClassNotFoundException e) {\n" + + " System.out.println(\"Expected exception thrown: \" + e);\n" + + " }\n" + + " };\n" + + "}\n"); + Path classes = Files.createDirectories(base.resolve("classes")); + new JavacTask(tb) + .outdir(classes) + .files(file) + .run(); + + String log = new JavaTask(tb) + .classpath(classes.toString()) + .className(file.toString()) + .run(Task.Expect.SUCCESS) + .getOutput(Task.OutputKind.STDOUT); + checkEqual("stdout", log.trim(), + "Expected exception thrown: java.lang.ClassNotFoundException: NoSuchClass"); } // For any source file that is invoked through the OS shebang mechanism, invalid shebang @@ -471,12 +578,18 @@ void checkEqual(String name, String found, String expect) { expect = expect.replace("\n", tb.lineSeparator); out.println(name + ": " + found); - out.println(name + ": " + found); if (!expect.equals(found)) { error("Unexpected output; expected: " + expect); } } + void checkMatch(String name, String found, Pattern expect) { + out.println(name + ": " + found); + if (!expect.matcher(found).matches()) { + error("Unexpected output; expected match for: " + expect); + } + } + void checkEmpty(String name, String found) { out.println(name + ": " + found); if (!found.isEmpty()) { diff -r cdfabab3413f -r 8f66a57054b7 test/langtools/tools/javac/launcher/src/CLTest.java --- a/test/langtools/tools/javac/launcher/src/CLTest.java Tue Sep 25 09:34:51 2018 -0700 +++ b/test/langtools/tools/javac/launcher/src/CLTest.java Tue Sep 25 10:30:32 2018 -0700 @@ -43,38 +43,38 @@ import com.sun.tools.classfile.ClassFile; public class CLTest { - public static void main(String... args) throws Exception { - try { - new CLTest().run(); - } catch (Throwable t) { - t.printStackTrace(); - System.exit(1); - } + public static void main(String... args) throws Exception { + try { + new CLTest().run(); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + void run() throws Exception { + String[] names = { + "p/q/CLTest.class", + "p/q/CLTest$Inner.class", + "p/q/CLTest2.class", + "java/lang/Object.class", + "UNKNOWN.class", + "UNKNOWN" + }; + + for (String name : names) { + testGetResource(name); + testGetResources(name); + testGetResourceAsStream(name); } - void run() throws Exception { - String[] names = { - "p/q/CLTest.class", - "p/q/CLTest$Inner.class", - "p/q/CLTest2.class", - "java/lang/Object.class", - "UNKNOWN.class", - "UNKNOWN" - }; - - for (String name : names) { - testGetResource(name); - testGetResources(name); - testGetResourceAsStream(name); - } - - if (errors > 0) { - throw new Exception(errors + " errors found"); - } + if (errors > 0) { + throw new Exception(errors + " errors found"); } + } void testGetResource(String name) { - System.err.println("testGetResource: " + name); + System.err.println("testGetResource: " + name); try { ClassLoader cl = getClass().getClassLoader(); URL u = cl.getResource(name); @@ -95,7 +95,7 @@ } void testGetResources(String name) { - System.err.println("testGetResources: " + name); + System.err.println("testGetResources: " + name); try { ClassLoader cl = getClass().getClassLoader(); Enumeration e = cl.getResources(name);