8210839: Improve interaction between source launcher and classpath
Reviewed-by: alanb, mchung
This commit is contained in:
parent
64099fc1cc
commit
59fd35292e
@ -50,8 +50,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.AccessController;
|
|
||||||
import java.security.PrivilegedAction;
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -63,6 +61,8 @@ import java.util.Map;
|
|||||||
import java.util.MissingResourceException;
|
import java.util.MissingResourceException;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.lang.model.SourceVersion;
|
import javax.lang.model.SourceVersion;
|
||||||
import javax.lang.model.element.NestingKind;
|
import javax.lang.model.element.NestingKind;
|
||||||
@ -399,9 +399,6 @@ public class Main {
|
|||||||
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
|
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
|
||||||
try {
|
try {
|
||||||
Class<?> appClass = Class.forName(mainClassName, true, cl);
|
Class<?> appClass = Class.forName(mainClassName, true, cl);
|
||||||
if (appClass.getClassLoader() != cl) {
|
|
||||||
throw new Fault(Errors.UnexpectedClass(mainClassName));
|
|
||||||
}
|
|
||||||
Method main = appClass.getDeclaredMethod("main", String[].class);
|
Method main = appClass.getDeclaredMethod("main", String[].class);
|
||||||
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
|
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
|
||||||
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
|
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
|
||||||
@ -535,26 +532,103 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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}.
|
||||||
*
|
*
|
||||||
* <p>The classloader uses the standard parent-delegation model, just providing
|
* <p>The classloader inverts the standard parent-delegation model, giving preference
|
||||||
* {@code findClass} to find classes in the in-memory cache.
|
* 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 {
|
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}.
|
* {@link ClassLoader#name binary name}.
|
||||||
*/
|
*/
|
||||||
private final Map<String, byte[]> map;
|
private final Map<String, byte[]> sourceFileClasses;
|
||||||
|
|
||||||
MemoryClassLoader(Map<String, byte[]> map, ClassLoader parent) {
|
MemoryClassLoader(Map<String, byte[]> sourceFileClasses, ClassLoader parent) {
|
||||||
super(parent);
|
super(parent);
|
||||||
this.map = map;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<URL> getResources(String name) throws IOException {
|
||||||
|
URL u = findResource(name);
|
||||||
|
Enumeration<URL> e = getParent().getResources(name);
|
||||||
|
if (u == null) {
|
||||||
|
return e;
|
||||||
|
} else {
|
||||||
|
List<URL> list = new ArrayList<>();
|
||||||
|
list.add(u);
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
list.add(e.nextElement());
|
||||||
|
}
|
||||||
|
return Collections.enumeration(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
byte[] bytes = map.get(name);
|
byte[] bytes = sourceFileClasses.get(name);
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
throw new ClassNotFoundException(name);
|
throw new ClassNotFoundException(name);
|
||||||
}
|
}
|
||||||
@ -564,7 +638,7 @@ public class Main {
|
|||||||
@Override
|
@Override
|
||||||
public URL findResource(String name) {
|
public URL findResource(String name) {
|
||||||
String binaryName = toBinaryName(name);
|
String binaryName = toBinaryName(name);
|
||||||
if (binaryName == null || map.get(binaryName) == null) {
|
if (binaryName == null || sourceFileClasses.get(binaryName) == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,7 +702,7 @@ public class Main {
|
|||||||
if (!u.getProtocol().equalsIgnoreCase(PROTOCOL)) {
|
if (!u.getProtocol().equalsIgnoreCase(PROTOCOL)) {
|
||||||
throw new IllegalArgumentException(u.toString());
|
throw new IllegalArgumentException(u.toString());
|
||||||
}
|
}
|
||||||
return new MemoryURLConnection(u, map.get(toBinaryName(u.getPath())));
|
return new MemoryURLConnection(u, sourceFileClasses.get(toBinaryName(u.getPath())));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -108,10 +108,6 @@ launcher.err.main.not.void=\
|
|||||||
launcher.err.cant.find.class=\
|
launcher.err.cant.find.class=\
|
||||||
can''t find class: {0}
|
can''t find class: {0}
|
||||||
|
|
||||||
# 0: string
|
|
||||||
launcher.err.unexpected.class=\
|
|
||||||
class found on application class path: {0}
|
|
||||||
|
|
||||||
# 0: string
|
# 0: string
|
||||||
launcher.err.cant.find.main.method=\
|
launcher.err.cant.find.main.method=\
|
||||||
can''t find main(String[]) method in class: {0}
|
can''t find main(String[]) method in class: {0}
|
||||||
|
@ -47,6 +47,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.sun.tools.javac.launcher.Main;
|
import com.sun.tools.javac.launcher.Main;
|
||||||
|
|
||||||
@ -233,22 +234,98 @@ public class SourceLauncherTest extends TestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 src = base.resolve("src");
|
||||||
Path file = src.resolve("WrongClass.java");
|
Path file = src.resolve("GetResource.java");
|
||||||
tb.writeJavaFiles(src, "class WrongClass { }");
|
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"));
|
Path classes = Files.createDirectories(base.resolve("classes"));
|
||||||
new JavacTask(tb)
|
new JavacTask(tb)
|
||||||
.outdir(classes)
|
.outdir(classes)
|
||||||
.files(file)
|
.files(file)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
String log = new JavaTask(tb)
|
String log = new JavaTask(tb)
|
||||||
.classpath(classes.toString())
|
.classpath(classes.toString())
|
||||||
.className(file.toString())
|
.className(file.toString())
|
||||||
.run(Task.Expect.FAIL)
|
.run(Task.Expect.SUCCESS)
|
||||||
.getOutput(Task.OutputKind.STDERR);
|
.getOutput(Task.OutputKind.STDOUT);
|
||||||
checkEqual("stderr", log.trim(),
|
checkMatch("stdout", log.trim(),
|
||||||
"error: class found on application class path: WrongClass");
|
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<URL> 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<String> 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
|
@Test
|
||||||
@ -294,7 +371,37 @@ public class SourceLauncherTest extends TestRunner {
|
|||||||
checkEmpty("stdout", r.stdOut);
|
checkEmpty("stdout", r.stdOut);
|
||||||
checkEqual("stderr", r.stdErr, expectStdErr);
|
checkEqual("stderr", r.stdErr, expectStdErr);
|
||||||
checkFault("exception", r.exception, "error: compilation failed");
|
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
|
// For any source file that is invoked through the OS shebang mechanism, invalid shebang
|
||||||
@ -471,12 +578,18 @@ public class SourceLauncherTest extends TestRunner {
|
|||||||
void checkEqual(String name, String found, String expect) {
|
void checkEqual(String name, String found, String expect) {
|
||||||
expect = expect.replace("\n", tb.lineSeparator);
|
expect = expect.replace("\n", tb.lineSeparator);
|
||||||
out.println(name + ": " + found);
|
out.println(name + ": " + found);
|
||||||
out.println(name + ": " + found);
|
|
||||||
if (!expect.equals(found)) {
|
if (!expect.equals(found)) {
|
||||||
error("Unexpected output; expected: " + expect);
|
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) {
|
void checkEmpty(String name, String found) {
|
||||||
out.println(name + ": " + found);
|
out.println(name + ": " + found);
|
||||||
if (!found.isEmpty()) {
|
if (!found.isEmpty()) {
|
||||||
|
@ -43,38 +43,38 @@ import java.util.*;
|
|||||||
import com.sun.tools.classfile.ClassFile;
|
import com.sun.tools.classfile.ClassFile;
|
||||||
|
|
||||||
public class CLTest {
|
public class CLTest {
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String... args) throws Exception {
|
||||||
try {
|
try {
|
||||||
new CLTest().run();
|
new CLTest().run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
System.exit(1);
|
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 {
|
if (errors > 0) {
|
||||||
String[] names = {
|
throw new Exception(errors + " errors found");
|
||||||
"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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void testGetResource(String name) {
|
void testGetResource(String name) {
|
||||||
System.err.println("testGetResource: " + name);
|
System.err.println("testGetResource: " + name);
|
||||||
try {
|
try {
|
||||||
ClassLoader cl = getClass().getClassLoader();
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
URL u = cl.getResource(name);
|
URL u = cl.getResource(name);
|
||||||
@ -95,7 +95,7 @@ public class CLTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void testGetResources(String name) {
|
void testGetResources(String name) {
|
||||||
System.err.println("testGetResources: " + name);
|
System.err.println("testGetResources: " + name);
|
||||||
try {
|
try {
|
||||||
ClassLoader cl = getClass().getClassLoader();
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
Enumeration<URL> e = cl.getResources(name);
|
Enumeration<URL> e = cl.getResources(name);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user