8236842: Surprising 'multiple elements' behaviour from getTypeElement when cross-compiling with --release

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2020-11-02 10:15:16 +00:00
parent 64feeab70a
commit d05df7c17a
4 changed files with 522 additions and 61 deletions

View File

@ -147,7 +147,7 @@
* <p> Otherwise, resolution succeeds, and the result of resolution is the
* readability graph.
*
* <h3> Root modules </h3>
* <h3><a id="root-modules"></a> Root modules </h3>
*
* <p> The set of root modules at compile-time is usually the set of modules
* being compiled. At run-time, the set of root modules is usually the

View File

@ -51,11 +51,30 @@ import javax.lang.model.element.*;
public interface Elements {
/**
* Returns a package given its fully qualified name if the package is unique in the environment.
* If running with modules, all modules in the modules graph are searched for matching packages.
* Returns a package given its fully qualified name if the package is uniquely
* determinable in the environment.
*
* @param name fully qualified package name, or an empty string for an unnamed package
* @return the specified package, or {@code null} if it cannot be uniquely found
* If running with modules, packages of the given name are searched in a
* two-stage process:
* <ul>
* <li>find non-empty packages with the given name returned by
* {@link #getPackageElement(ModuleElement, CharSequence)},
* where the provided ModuleSymbol is any
* <a href="../../../../../java.base/java/lang/module/package-summary.html#root-modules">root module</a>,
* </li>
* <li>if the above yields an empty list, search
* {@link #getAllModuleElements() all modules} for observable
* packages with the given name
* </li>
* </ul>
*
* If this process leads to a list with a single element,
* the single element is returned, otherwise null is returned.
*
* @param name fully qualified package name,
* or an empty string for an unnamed package
* @return the specified package,
* or {@code null} if no package can be uniquely determined.
*/
PackageElement getPackageElement(CharSequence name);
@ -119,12 +138,29 @@ public interface Elements {
}
/**
* Returns a type element given its canonical name if the type element is unique in the environment.
* If running with modules, all modules in the modules graph are searched for matching
* type elements.
* Returns a type element given its canonical name if the type element is uniquely
* determinable in the environment.
*
* @param name the canonical name
* @return the named type element, or {@code null} if it cannot be uniquely found
* If running with modules, type elements of the given name are
* searched in a two-stage process:
* <ul>
* <li>find type elements with the given name returned by
* {@link #getTypeElement(ModuleElement, CharSequence)},
* where the provided ModuleSymbol is any
* <a href="../../../../../java.base/java/lang/module/package-summary.html#root-modules">root module</a>,
* </li>
* <li>if the above yields an empty list, search
* {@link #getAllModuleElements() all modules} for observable
* type elements with the given name
* </li>
* </ul>
*
* If this process leads to a list with a single element,
* the single element is returned, otherwise null is returned.
*
* @param name the canonical name
* @return the named type element,
* or {@code null} if no type element can be uniquely determined.
*/
TypeElement getTypeElement(CharSequence name);

View File

@ -25,6 +25,7 @@
package com.sun.tools.javac.model;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -198,43 +199,48 @@ public class JavacElements implements Elements {
return (S) resultCache.computeIfAbsent(Pair.of(methodName, nameStr), p -> {
Set<S> found = new LinkedHashSet<>();
Set<ModuleSymbol> allModules = new HashSet<>(modules.allModules());
for (ModuleSymbol msym : modules.allModules()) {
S sym = nameToSymbol(msym, nameStr, clazz);
allModules.removeAll(modules.getRootModules());
if (sym == null)
continue;
for (Set<ModuleSymbol> modules : Arrays.asList(modules.getRootModules(), allModules)) {
for (ModuleSymbol msym : modules) {
S sym = nameToSymbol(msym, nameStr, clazz);
if (clazz == ClassSymbol.class) {
// Always include classes
found.add(sym);
} else if (clazz == PackageSymbol.class) {
// In module mode, ignore the "spurious" empty packages that "enclose" module-specific packages.
// For example, if a module contains classes or package info in package p.q.r, it will also appear
// to have additional packages p.q and p, even though these packages have no content other
// than the subpackage. We don't want those empty packages showing up in searches for p or p.q.
if (!sym.members().isEmpty() || ((PackageSymbol) sym).package_info != null) {
if (sym == null)
continue;
if (clazz == ClassSymbol.class) {
// Always include classes
found.add(sym);
} else if (clazz == PackageSymbol.class) {
// In module mode, ignore the "spurious" empty packages that "enclose" module-specific packages.
// For example, if a module contains classes or package info in package p.q.r, it will also appear
// to have additional packages p.q and p, even though these packages have no content other
// than the subpackage. We don't want those empty packages showing up in searches for p or p.q.
if (!sym.members().isEmpty() || ((PackageSymbol) sym).package_info != null) {
found.add(sym);
}
}
}
}
if (found.size() == 1) {
return Optional.of(found.iterator().next());
} else if (found.size() > 1) {
//more than one element found, produce a note:
if (alreadyWarnedDuplicates.add(methodName + ":" + nameStr)) {
String moduleNames = found.stream()
.map(s -> s.packge().modle)
.map(m -> m.toString())
.collect(Collectors.joining(", "));
log.note(Notes.MultipleElements(methodName, nameStr, moduleNames));
if (found.size() == 1) {
return Optional.of(found.iterator().next());
} else if (found.size() > 1) {
//more than one element found, produce a note:
if (alreadyWarnedDuplicates.add(methodName + ":" + nameStr)) {
String moduleNames = found.stream()
.map(s -> s.packge().modle)
.map(m -> m.toString())
.collect(Collectors.joining(", "));
log.note(Notes.MultipleElements(methodName, nameStr, moduleNames));
}
return Optional.empty();
} else {
//not found, try another option
}
return Optional.empty();
} else {
//not found:
return Optional.empty();
}
return Optional.empty();
}).orElse(null);
}

View File

@ -23,7 +23,7 @@
/**
* @test
* @bug 8133884 8162711 8133896 8172158 8172262 8173636 8175119 8189747
* @bug 8133884 8162711 8133896 8172158 8172262 8173636 8175119 8189747 8236842
* @summary Verify that annotation processing works.
* @library /tools/lib
* @modules
@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -53,7 +54,6 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
@ -80,11 +80,8 @@ import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import toolbox.JavacTask;
@ -1299,6 +1296,440 @@ public class AnnotationProcessing extends ModuleTestBase {
}
}
@Test
public void testUnboundLookupNew(Path base) throws Exception {
Path moduleSrc = base.resolve("module-src");
Path m1 = moduleSrc.resolve("m1x");
Path m2 = moduleSrc.resolve("m2x");
Path m3 = moduleSrc.resolve("m3x");
Path m4 = moduleSrc.resolve("m4x");
Path src = base.resolve("src");
Path classes = base.resolve("classes");
Path srcClasses = base.resolve("src-classes");
Files.createDirectories(classes);
Files.createDirectories(srcClasses);
Files.createDirectories(moduleSrc);
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports api; }",
"package test; public class Test { }",
"package api; public class API {}");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; }",
"package test; public class Test { }",
"package api.impl; public class Impl { }");
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString())
.outdir(classes)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test,+api",
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(m2))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList("- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m2x",
"- compiler.note.proc.messager: api found in module: m1x",
"- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m2x",
"- compiler.note.proc.messager: api found in module: m1x"
);
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; }",
"package test; public class Test { }");
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"-XDrawDiagnostics",
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(findJavaFiles(m2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"Test.java:1:1: compiler.err.package.in.other.module: m1x",
"- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m1x",
"- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m1x",
"1 error");
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; requires m3x; }",
"package test; public class Test { }");
tb.writeJavaFiles(m3,
"module m3x { }",
"package test; public class Test { }");
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString())
.outdir(classes)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(m2))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m2x",
"- compiler.note.proc.messager: test.Test found in module: m2x",
"- compiler.note.proc.messager: test found in module: m2x"
);
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; requires m3x; }");
tb.writeJavaFiles(m3,
"module m3x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m4,
"module m4x { }",
"package test; public class Test { }");
{
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"-XDrawDiagnostics",
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(findJavaFiles(m2))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<Set<String>> expected = Arrays.asList(
variants("module-info.java:1:1: compiler.err.package.clash.from.requires: m2x, test, m1x, m3x"),
variants("- compiler.note.proc.messager: test.Test found in module: m1x"),
variants("- compiler.note.proc.messager: test found in module: m1x"),
variants("- compiler.note.proc.messager: test.Test found in module: m1x"),
variants("- compiler.note.proc.messager: test found in module: m1x"),
variants("1 error"));
assertErrorsWithVariants(expected, log);
}
{
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=-test.Test",
"-AlookupPackage=-test",
"-XDrawDiagnostics",
"-XDshould-stop.at=FLOW")
.outdir(classes)
.files(findJavaFiles(m2, m4))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"module-info.java:1:1: compiler.err.package.clash.from.requires: m2x, test, m1x, m3x",
"- compiler.note.multiple.elements: getTypeElement, test.Test, m1x, m4x",
"- compiler.note.multiple.elements: getPackageElement, test, m1x, m4x",
"1 error");
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; }");
tb.writeJavaFiles(src,
"package test; public class Test { }");
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString())
.outdir(classes)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"--source-path", src.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(m2))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"- compiler.note.proc.messager: test.Test found in module: m1x",
"- compiler.note.proc.messager: test found in module: m1x",
"- compiler.note.proc.messager: test.Test found in module: m1x",
"- compiler.note.proc.messager: test found in module: m1x"
);
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; }");
tb.writeJavaFiles(src,
"package test; public class Test { }");
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString())
.outdir(classes)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> log = new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"--source-path", src.toString(),
"--add-reads=m2x=ALL-UNNAMED",
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"-XDrawDiagnostics")
.outdir(classes)
.files(findJavaFiles(m2))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList(
"- compiler.note.proc.messager: test.Test found in module: m1x",
"- compiler.note.proc.messager: test found in module: m1x",
"- compiler.note.proc.messager: test.Test found in module: m1x",
"- compiler.note.proc.messager: test found in module: m1x"
);
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
{
tb.cleanDirectory(classes);
tb.cleanDirectory(srcClasses);
tb.cleanDirectory(moduleSrc);
tb.writeJavaFiles(m1,
"module m1x { exports test; }",
"package test; public class Test { }");
tb.writeJavaFiles(m2,
"module m2x { requires m1x; }");
tb.writeJavaFiles(src,
"package test; public class Test { }");
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString())
.outdir(classes)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> log = new JavacTask(tb)
.options("--module-path", classes.toString(),
"--source-path", src.toString(),
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookupNew.class.getName(),
"-AlookupClass=+test.Test",
"-AlookupPackage=+test",
"--add-modules=m2x",
"-XDshould-stop.at=ATTR",
"-XDrawDiagnostics")
.outdir(srcClasses)
.files(findJavaFiles(src))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(OutputKind.DIRECT);
List<String> expected = Arrays.asList("Test.java:1:1: compiler.err.package.in.other.module: m1x",
"- compiler.note.proc.messager: test.Test found in module: unnamed module",
"- compiler.note.proc.messager: test found in module: m1x",
"- compiler.note.proc.messager: test.Test found in module: unnamed module",
"- compiler.note.proc.messager: test found in module: m1x",
"1 error"
);
if (!expected.equals(log)) {
throw new AssertionError("Expected output not found: " + log);
}
}
}
private Set<String> variants(String... expected) {
return new HashSet<>(Arrays.asList(expected));
}
private void assertErrorsWithVariants(List<Set<String>> expectedVariants, List<String> actual) {
assertEquals(expectedVariants.size(), actual.size());
Iterator<Set<String>> expIt = expectedVariants.iterator();
Iterator<String> actIt = actual.iterator();
while (expIt.hasNext() && actIt.hasNext()) {
Set<String> exp = expIt.next();
String act = actIt.next();
if (!exp.contains(act)) {
throw new AssertionError("Expected: " + exp + ", actual: " + act);
}
}
}
@SupportedAnnotationTypes("*")
@SupportedOptions({"lookupClass", "lookupPackage"})
public static final class UnboundLookupNew extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
performLookup("lookupClass", processingEnv.getElementUtils()::getTypeElement);
performLookup("lookupPackage", processingEnv.getElementUtils()::getPackageElement);
return false;
}
private void performLookup(String optionName, Function<String, Element> name2Element) {
String[] lookupList = processingEnv.getOptions().get(optionName).split(",");
for (String lookup : lookupList) {
boolean shouldExists = lookup.charAt(0) == '+';
String name = lookup.substring(1);
Element type = name2Element.apply(name);
if (shouldExists) {
if (type == null) {
throw new AssertionError("Did not find the expected type.");
} else {
processingEnv.getMessager().printMessage(Kind.NOTE, name + " found in module: " + processingEnv.getElementUtils().getModuleOf(type));
}
} else {
if (type != null) {
throw new AssertionError("Found the unexpected type.");
}
}
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
@Test
public void testUnboundLookup(Path base) throws Exception {
Path src = base.resolve("src");
@ -1333,7 +1764,7 @@ public class AnnotationProcessing extends ModuleTestBase {
"package nested.pack; public class Impl { }");
//from source:
String log = new JavacTask(tb)
new JavacTask(tb)
.options("--module-source-path", moduleSrc.toString(),
"--source-path", src.toString(),
"-processorpath", System.getProperty("test.class.path"),
@ -1345,20 +1776,6 @@ public class AnnotationProcessing extends ModuleTestBase {
.writeAll()
.getOutput(OutputKind.DIRECT);
String moduleImplConflictString =
"- compiler.note.multiple.elements: getTypeElement, impl.conflict.module.Impl, m2x, m1x";
String srcConflictString =
"- compiler.note.multiple.elements: getTypeElement, impl.conflict.src.Impl, m1x, unnamed module";
if (!log.contains(moduleImplConflictString) ||
!log.contains(srcConflictString)) {
throw new AssertionError("Expected output not found: " + log);
}
if (log.split(Pattern.quote(moduleImplConflictString)).length > 2) {
throw new AssertionError("Too many warnings in: " + log);
}
new JavacTask(tb)
.options("--source-path", src.toString())
.outdir(cpClasses)
@ -1373,7 +1790,8 @@ public class AnnotationProcessing extends ModuleTestBase {
"--add-modules", "m1x,m2x",
"-processorpath", System.getProperty("test.class.path"),
"-processor", UnboundLookup.class.getName(),
"-proc:only")
"-proc:only",
"-Aunnamedmodule")
.classes("java.lang.Object")
.run()
.writeAll();
@ -1405,6 +1823,7 @@ public class AnnotationProcessing extends ModuleTestBase {
}
@SupportedAnnotationTypes("*")
@SupportedOptions("unnamedmodule")
public static final class UnboundLookup extends AbstractProcessor {
@Override
@ -1422,8 +1841,8 @@ public class AnnotationProcessing extends ModuleTestBase {
assertTypeElementNotFound("impl.conflict.module.Impl");
assertTypeElementNotFound("impl.conflict.module.Impl"); //check that the warning/note is produced only once
assertPackageElementNotFound("impl.conflict.module");
assertTypeElementNotFound("impl.conflict.src.Impl");
assertPackageElementNotFound("impl.conflict.src");
assertTypeElementExists("impl.conflict.src.Impl", processingEnv.getOptions().containsKey("unnamedmodule") ? "" : "m1x");
assertPackageElementExists("impl.conflict.src", processingEnv.getOptions().containsKey("unnamedmodule") ? "" : "m1x");
assertTypeElementNotFound("impl.conflict.clazz.pkg");
assertPackageElementNotFound("unique"); //do not return packages without members in module mode
assertTypeElementNotFound("nested"); //cannot distinguish between m1x and m2x