8152911: javac assertion error when compiling overlay sources

Avoid creating ModuleSymbols with unspecified name, to avoid conflicts with predefined ModuleSymbol for the java.base module.

Reviewed-by: jjg
This commit is contained in:
Jan Lahoda 2016-10-04 16:25:19 +02:00
parent 16bf17a9a7
commit c58a8aae3e
11 changed files with 303 additions and 89 deletions

View File

@ -31,6 +31,7 @@ import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileManager.Location;
@ -38,10 +39,14 @@ import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;
import com.sun.tools.javac.code.Symbol.Completer;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
import com.sun.tools.javac.jvm.ModuleNameReader;
import com.sun.tools.javac.jvm.ModuleNameReader.BadClassFile;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
@ -50,7 +55,6 @@ import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.StringUtils;
import static com.sun.tools.javac.code.Kinds.Kind.*;
@ -84,6 +88,10 @@ public class ModuleFinder {
private final JCDiagnostic.Factory diags;
private ModuleNameReader moduleNameReader;
public ModuleInfoSourceFileCompleter sourceFileCompleter;
/** Get the ModuleFinder instance for this invocation. */
public static ModuleFinder instance(Context context) {
ModuleFinder instance = context.get(moduleFinderKey);
@ -182,6 +190,8 @@ public class ModuleFinder {
return list;
}
private boolean inFindSingleModule;
public ModuleSymbol findSingleModule() {
try {
JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
@ -194,26 +204,41 @@ public class ModuleFinder {
if (fo == null) {
msym = syms.unnamedModule;
} else {
// Note: the following may trigger a re-entrant call to Modules.enter
// msym = new ModuleSymbol();
// ClassSymbol info = new ClassSymbol(Flags.MODULE, names.module_info, msym);
// info.modle = msym;
// info.classfile = fo;
// info.members_field = WriteableScope.create(info);
// msym.module_info = info;
msym = ModuleSymbol.create(null, names.module_info);
msym.module_info.classfile = fo;
msym.completer = sym -> classFinder.fillIn(msym.module_info);
// // TODO: should we do the following here, or as soon as we find the name in
// // the source or class file?
// // Consider the case when the class/source path module shadows one on the
// // module source path
// if (syms.modules.get(msym.name) != null) {
// // error: module already defined
// System.err.println("ERROR: module already defined: " + msym);
// } else {
// syms.modules.put(msym.name, msym);
// }
switch (fo.getKind()) {
case SOURCE:
if (!inFindSingleModule) {
try {
inFindSingleModule = true;
// Note: the following will trigger a re-entrant call to Modules.enter
msym = sourceFileCompleter.complete(fo);
msym.module_info.classfile = fo;
} finally {
inFindSingleModule = false;
}
} else {
//the module-info.java does not contain a module declaration,
//avoid infinite recursion:
msym = syms.unnamedModule;
}
break;
case CLASS:
Name name;
try {
name = names.fromString(readModuleName(fo));
} catch (BadClassFile | IOException ex) {
//fillIn will report proper errors:
name = names.error;
}
msym = syms.enterModule(name);
msym.module_info.classfile = fo;
msym.completer = Completer.NULL_COMPLETER;
classFinder.fillIn(msym.module_info);
break;
default:
Assert.error();
msym = syms.unnamedModule;
break;
}
}
msym.classLocation = StandardLocation.CLASS_OUTPUT;
@ -224,6 +249,12 @@ public class ModuleFinder {
}
}
private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
if (moduleNameReader == null)
moduleNameReader = new ModuleNameReader();
return moduleNameReader.readModuleName(jfo);
}
private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
if (!fileManager.hasLocation(location))
return null;
@ -332,4 +363,8 @@ public class ModuleFinder {
}
}
public interface ModuleInfoSourceFileCompleter {
public ModuleSymbol complete(JavaFileObject file);
}
}

View File

@ -916,7 +916,6 @@ public abstract class Symbol extends AnnoConstruct implements Element {
/**
* Create a ModuleSymbol with an associated module-info ClassSymbol.
* The name of the module may be null, if it is not known yet.
*/
public static ModuleSymbol create(Name name, Name module_info) {
ModuleSymbol msym = new ModuleSymbol(name, null);
@ -930,6 +929,7 @@ public abstract class Symbol extends AnnoConstruct implements Element {
public ModuleSymbol(Name name, Symbol owner) {
super(MDL, 0, name, null, owner);
Assert.checkNonNull(name);
this.type = new ModuleType(this);
}

View File

@ -764,17 +764,6 @@ public class Symtab {
return msym;
}
public void enterModule(ModuleSymbol msym, Name name) {
Assert.checkNull(modules.get(name));
Assert.checkNull(msym.name);
msym.name = name;
addRootPackageFor(msym);
ClassSymbol info = msym.module_info;
info.fullname = msym.name.append('.', names.module_info);
info.flatname = info.fullname;
modules.put(name, msym);
}
public ModuleSymbol getModule(Name name) {
return modules.get(name);
}

View File

@ -292,15 +292,12 @@ public class Modules extends JCTree.Visitor {
Name name = TreeInfo.fullName(decl.qualId);
ModuleSymbol sym;
if (c != null) {
sym = (ModuleSymbol) c.owner;
if (sym.name == null) {
//ModuleFinder.findSingleModule creates a stub of a ModuleSymbol without a name,
//fill the name here after the module-info.java has been parsed
//also enter the ModuleSymbol among modules:
syms.enterModule(sym, name);
} else {
// TODO: validate name
}
sym = (ModuleSymbol) c.owner;
Assert.checkNonNull(sym.name);
Name treeName = TreeInfo.fullName(decl.qualId);
if (sym.name != treeName) {
log.error(decl.pos(), Errors.ModuleNameMismatch(name, sym.name));
}
} else {
sym = syms.enterModule(name);
if (sym.module_info.sourcefile != null && sym.module_info.sourcefile != toplevel.sourcefile) {
@ -1006,6 +1003,10 @@ public class Modules extends JCTree.Visitor {
return new Symbol.Completer() {
@Override
public void complete(Symbol sym) throws CompletionFailure {
if (inInitModules) {
sym.completer = this;
return ;
}
ModuleSymbol msym = (ModuleSymbol) sym;
Set<ModuleSymbol> allModules = allModules();
for (ModuleSymbol m : allModules) {

View File

@ -80,6 +80,7 @@ import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.jvm.ModuleNameReader;
import com.sun.tools.javac.util.Pair;
import com.sun.tools.javac.util.StringUtils;

View File

@ -2396,12 +2396,15 @@ public class ClassReader {
} else {
c.flags_field = flags;
Name modInfoName = readModuleInfoName(nextChar());
if (c.owner.name == null) {
syms.enterModule((ModuleSymbol) c.owner, Convert.packagePart(modInfoName));
} else {
// TODO: validate name
}
currentModule = (ModuleSymbol) c.owner;
if (currentModule.name.append('.', names.module_info) != modInfoName) {
//strip trailing .module-info, if exists:
int modInfoStart = modInfoName.length() - names.module_info.length();
modInfoName = modInfoName.subName(modInfoStart, modInfoName.length()) == names.module_info &&
modInfoName.charAt(modInfoStart - 1) == '.' ?
modInfoName.subName(0, modInfoStart - 1) : modInfoName;
throw badClassFile("module.name.mismatch", modInfoName, currentModule.name);
}
}
// class attributes must be read before class

View File

@ -22,13 +22,15 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.file;
package com.sun.tools.javac.jvm;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.tools.JavaFileObject;
import com.sun.tools.javac.jvm.ClassFile;
import static com.sun.tools.javac.jvm.ClassFile.*;
@ -44,7 +46,7 @@ import static com.sun.tools.javac.jvm.ClassFile.*;
* notice.</b>
*/
public class ModuleNameReader {
static class BadClassFile extends Exception {
public static class BadClassFile extends Exception {
private static final long serialVersionUID = 0;
BadClassFile(String msg) {
super(msg);
@ -61,37 +63,43 @@ public class ModuleNameReader {
*/
private int bp;
/** The objects of the constant pool.
*/
private Object[] poolObj;
/** For every constant pool entry, an index into buf where the
* defining section of the entry is found.
*/
private int[] poolIdx;
ModuleNameReader() {
public ModuleNameReader() {
}
String readModuleName(Path p) throws IOException, BadClassFile {
public String readModuleName(Path p) throws IOException, BadClassFile {
try (InputStream in = Files.newInputStream(p)) {
bp = 0;
buf = readInputStream(buf, in);
int magic = nextInt();
if (magic != JAVA_MAGIC)
throw new BadClassFile("illegal.start.of.class.file");
int minorVersion = nextChar();
int majorVersion = nextChar();
indexPool();
int accessflags = nextChar();
return readModuleInfoName(nextChar());
return readModuleName(in);
}
}
public String readModuleName(JavaFileObject jfo) throws IOException, BadClassFile {
try (InputStream in = jfo.openInputStream()) {
return readModuleName(in);
}
}
public String readModuleName(InputStream in) throws IOException, BadClassFile {
bp = 0;
buf = readInputStream(buf, in);
int magic = nextInt();
if (magic != JAVA_MAGIC)
throw new BadClassFile("illegal.start.of.class.file");
int minorVersion = nextChar();
int majorVersion = nextChar();
indexPool();
int accessflags = nextChar();
return readModuleInfoName(nextChar());
}
/** Extract a character at position bp from buf.
*/
char getChar(int bp) {
@ -120,7 +128,6 @@ public class ModuleNameReader {
*/
void indexPool() throws BadClassFile {
poolIdx = new int[nextChar()];
poolObj = new Object[poolIdx.length];
int i = 1;
while (i < poolIdx.length) {
poolIdx[i++] = bp;

View File

@ -37,6 +37,7 @@ import java.util.MissingResourceException;
import java.util.Queue;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
@ -67,7 +68,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.JCDiagnostic.Factory;
@ -341,6 +344,13 @@ public class JavaCompiler {
}
};
protected final ModuleFinder.ModuleInfoSourceFileCompleter moduleInfoSourceFileCompleter =
fo -> (ModuleSymbol) readSourceFile(parseImplicitFile(fo), null, tl -> {
return tl.defs.nonEmpty() && tl.defs.head.hasTag(Tag.MODULEDEF) ?
((JCModuleDecl) tl.defs.head).sym.module_info :
syms.defineClass(names.module_info, syms.errModule);
}).owner;
/**
* Command line options.
*/
@ -411,6 +421,7 @@ public class JavaCompiler {
diags = Factory.instance(context);
finder.sourceCompleter = sourceCompleter;
moduleFinder.sourceFileCompleter = moduleInfoSourceFileCompleter;
options = Options.instance(context);
@ -779,6 +790,19 @@ public class JavaCompiler {
readSourceFile(null, c);
}
private JCTree.JCCompilationUnit parseImplicitFile(JavaFileObject filename) {
JavaFileObject prev = log.useSource(filename);
try {
JCTree.JCCompilationUnit t = parse(filename, filename.getCharContent(false));
return t;
} catch (IOException e) {
log.error("error.reading.file", filename, JavacFileManager.getMessage(e));
return make.TopLevel(List.<JCTree>nil());
} finally {
log.useSource(prev);
}
}
/** Compile a ClassSymbol from source, optionally using the given compilation unit as
* the source tree.
* @param tree the compilation unit in which the given ClassSymbol resides,
@ -789,20 +813,20 @@ public class JavaCompiler {
if (completionFailureName == c.fullname) {
throw new CompletionFailure(c, "user-selected completion failure by class name");
}
JavaFileObject filename = c.classfile;
JavaFileObject prev = log.useSource(filename);
if (tree == null) {
try {
tree = parse(filename, filename.getCharContent(false));
} catch (IOException e) {
log.error("error.reading.file", filename, JavacFileManager.getMessage(e));
tree = make.TopLevel(List.<JCTree>nil());
} finally {
log.useSource(prev);
}
tree = parseImplicitFile(c.classfile);
}
readSourceFile(tree, c, cut -> c);
}
private ClassSymbol readSourceFile(JCCompilationUnit tree,
ClassSymbol expectedSymbol,
Function<JCCompilationUnit, ClassSymbol> symbolGetter)
throws CompletionFailure {
Assert.checkNonNull(tree);
if (!taskListener.isEmpty()) {
TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, tree);
taskListener.started(e);
@ -814,18 +838,20 @@ public class JavaCompiler {
// Note that if module resolution failed, we may not even
// have enough modules available to access java.lang, and
// so risk getting FatalError("no.java.lang") from MemberEnter.
if (!modules.enter(List.of(tree), c)) {
throw new CompletionFailure(c, diags.fragment("cant.resolve.modules"));
if (!modules.enter(List.of(tree), expectedSymbol)) {
throw new CompletionFailure(symbolGetter.apply(tree),
diags.fragment("cant.resolve.modules"));
}
enter.complete(List.of(tree), c);
enter.complete(List.of(tree), expectedSymbol);
if (!taskListener.isEmpty()) {
TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, tree);
taskListener.finished(e);
}
if (enter.getEnv(c) == null) {
ClassSymbol sym = symbolGetter.apply(tree);
if (sym == null || enter.getEnv(sym) == null) {
boolean isPkgInfo =
tree.sourcefile.isNameCompatible("package-info",
JavaFileObject.Kind.SOURCE);
@ -836,24 +862,26 @@ public class JavaCompiler {
if (enter.getEnv(tree.modle) == null) {
JCDiagnostic diag =
diagFactory.fragment("file.does.not.contain.module");
throw new ClassFinder.BadClassFile(c, filename, diag, diagFactory);
throw new ClassFinder.BadClassFile(sym, tree.sourcefile, diag, diagFactory);
}
} else if (isPkgInfo) {
if (enter.getEnv(tree.packge) == null) {
JCDiagnostic diag =
diagFactory.fragment("file.does.not.contain.package",
c.location());
throw new ClassFinder.BadClassFile(c, filename, diag, diagFactory);
sym.location());
throw new ClassFinder.BadClassFile(sym, tree.sourcefile, diag, diagFactory);
}
} else {
JCDiagnostic diag =
diagFactory.fragment("file.doesnt.contain.class",
c.getQualifiedName());
throw new ClassFinder.BadClassFile(c, filename, diag, diagFactory);
sym.getQualifiedName());
throw new ClassFinder.BadClassFile(sym, tree.sourcefile, diag, diagFactory);
}
}
implicitSourceFilesRead = true;
return sym;
}
/** Track when the JavaCompiler has been used to compile something. */

View File

@ -2770,6 +2770,10 @@ compiler.err.unnamed.pkg.not.allowed.named.modules=\
compiler.err.module.name.mismatch=\
module name {0} does not match expected name {1}
# 0: name, 1: name
compiler.misc.module.name.mismatch=\
module name {0} does not match expected name {1}
compiler.err.module.decl.sb.in.module-info.java=\
module declarations should be in a file named module-info.java

View File

@ -66,6 +66,7 @@ compiler.misc.kindname.type.variable
compiler.misc.kindname.type.variable.bound
compiler.misc.kindname.value
compiler.misc.incompatible.eq.lower.bounds # cannot happen?
compiler.misc.module.name.mismatch
compiler.misc.no.unique.minimal.instance.exists
compiler.misc.no.unique.maximal.instance.exists # cannot happen?
compiler.misc.resume.abort # prompt for a response

View File

@ -58,6 +58,8 @@ import com.sun.tools.javac.code.Symbol.ModuleSymbol;
import toolbox.JarTask;
import toolbox.JavacTask;
import toolbox.Task;
import toolbox.Task.Expect;
import toolbox.Task.OutputKind;
public class EdgeCases extends ModuleTestBase {
@ -304,4 +306,147 @@ public class EdgeCases extends ModuleTestBase {
}
}
@Test
public void testImplicitJavaBase(Path base) throws Exception {
Path src = base.resolve("src");
Path src_java_base = src.resolve("java.base");
Files.createDirectories(src_java_base);
tb.writeJavaFiles(src_java_base, "module java.base { exports java.lang; }");
tb.writeJavaFiles(src_java_base,
"package java.lang; public class Object {}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
//module-info from source:
new JavacTask(tb)
.options("-sourcepath", src_java_base.toString())
.outdir(classes)
.files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java")))
.run()
.writeAll();
//module-info from class:
if (!Files.exists(classes.resolve("module-info.class"))) {
throw new AssertionError("module-info.class not created!");
}
new JavacTask(tb)
.outdir(classes)
.files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java")))
.run()
.writeAll();
//broken module-info.class:
Files.newOutputStream(classes.resolve("module-info.class")).close();
List<String> log = new JavacTask(tb)
.options("-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java")))
.run(Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"- compiler.err.cant.access: <error>.module-info, (compiler.misc.bad.class.file.header: module-info.class, (compiler.misc.illegal.start.of.class.file))",
"1 error");
if (!expected.equals(log)) {
throw new AssertionError("Unexpected output: " + log);
}
//broken module-info.java:
Files.delete(classes.resolve("module-info.class"));
try (Writer out = Files.newBufferedWriter(src_java_base.resolve("module-info.java"))) {
out.write("class Broken {}");
}
log = new JavacTask(tb)
.options("-sourcepath", src_java_base.toString(),
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(src_java_base.resolve("java").resolve("lang").resolve("Object.java")))
.run(Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
expected = Arrays.asList("X");
if (expected.equals(log)) {
throw new AssertionError("Unexpected output: " + log);
}
}
@Test
public void testModuleInfoNameMismatchSource(Path base) throws Exception {
Path src = base.resolve("src");
Path m1 = src.resolve("m1");
Files.createDirectories(m1);
tb.writeJavaFiles(m1, "module other { }",
"package test; public class Test {}");
Path classes = base.resolve("classes");
tb.createDirectories(classes);
List<String> log = new JavacTask(tb)
.options("--module-source-path", src.toString(),
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(m1.resolve("test").resolve("Test.java")))
.run(Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"module-info.java:1:1: compiler.err.module.name.mismatch: other, m1",
"- compiler.err.cant.access: m1.module-info, (compiler.misc.cant.resolve.modules)",
"2 errors");
if (!expected.equals(log)) {
throw new AssertionError("Unexpected output: " + log);
}
}
@Test
public void testModuleInfoNameMismatchClass(Path base) throws Exception {
Path src = base.resolve("src");
Files.createDirectories(src);
tb.writeJavaFiles(src, "module other { }",
"package test; public class Test {}");
Path classes = base.resolve("classes");
Path m1Classes = classes.resolve("m1");
tb.createDirectories(m1Classes);
new JavacTask(tb)
.outdir(m1Classes)
.files(findJavaFiles(src))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
Path src2 = base.resolve("src2");
Files.createDirectories(src2);
tb.writeJavaFiles(src2, "module use { requires m1; }");
Path classes2 = base.resolve("classes2");
tb.createDirectories(classes2);
List<String> log = new JavacTask(tb)
.options("--module-path", classes.toString(),
"-XDrawDiagnostics")
.outdir(classes2)
.files(findJavaFiles(src2))
.run(Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"- compiler.err.cant.access: m1.module-info, (compiler.misc.bad.class.file.header: module-info.class, (compiler.misc.module.name.mismatch: other, m1))",
"1 error");
if (!expected.equals(log)) {
throw new AssertionError("Unexpected output: " + log);
}
}
}