# HG changeset patch # User jjg # Date 1283216615 25200 # Node ID c7a4fb5a2f86435509876673cbfc681641130330 # Parent f58f0ce45802fb477998bc0225670e321ce4649f 6403465: javac should defer diagnostics until it can be determined they are persistent Reviewed-by: mcimadamore, darcy diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java --- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Fri Aug 27 17:59:08 2010 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Mon Aug 30 18:03:35 2010 -0700 @@ -578,6 +578,8 @@ boolean classExpected, boolean interfaceExpected, boolean checkExtensible) { + if (t.isErroneous()) + return t; if (t.tag == TYPEVAR && !classExpected && !interfaceExpected) { // check that type variable is already visible if (t.getUpperBound() == null) { @@ -595,7 +597,7 @@ } else if (checkExtensible && classExpected && (t.tsym.flags() & INTERFACE) != 0) { - log.error(tree.pos(), "no.intf.expected.here"); + log.error(tree.pos(), "no.intf.expected.here"); return types.createErrorType(t); } if (checkExtensible && @@ -2845,7 +2847,6 @@ if (tree.bounds.tail.nonEmpty()) { log.error(tree.bounds.tail.head.pos(), "type.var.may.not.be.followed.by.other.bounds"); - log.unrecoverableError = true; tree.bounds = List.of(tree.bounds.head); a.bound = bs.head; } diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java --- a/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java Fri Aug 27 17:59:08 2010 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java Mon Aug 30 18:03:35 2010 -0700 @@ -59,6 +59,7 @@ import javax.annotation.processing.Processor; import static javax.tools.StandardLocation.CLASS_OUTPUT; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; import static com.sun.tools.javac.util.ListBuffer.lb; // TEMP, until we have a more efficient way to save doc comment info @@ -579,10 +580,8 @@ TaskEvent e = new TaskEvent(TaskEvent.Kind.PARSE, filename); taskListener.started(e); } - int initialErrorCount = log.nerrors; Parser parser = parserFactory.newParser(content, keepComments(), genEndPos, lineDebugInfo); tree = parser.parseCompilationUnit(); - log.unrecoverableError |= (log.nerrors > initialErrorCount); if (verbose) { printVerbose("parsing.done", Long.toString(elapsed(msec))); } @@ -967,8 +966,7 @@ keepComments = true; if (taskListener != null) taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING)); - - + log.deferDiagnostics = true; } else { // free resources procEnvImpl.close(); } @@ -985,15 +983,23 @@ * @param roots a list of compilation units * @return an instance of the compiler in which to complete the compilation */ + // Implementation note: when this method is called, log.deferredDiagnostics + // will have been set true by initProcessAnnotations, meaning that any diagnostics + // that are reported will go into the log.deferredDiagnostics queue. + // By the time this method exits, log.deferDiagnostics must be set back to false, + // and all deferredDiagnostics must have been handled: i.e. either reported + // or determined to be transient, and therefore suppressed. public JavaCompiler processAnnotations(List roots, List classnames) { if (shouldStop(CompileState.PROCESS)) { // Errors were encountered. - // If log.unrecoverableError is set, the errors were parse errors + // Unless all the errors are resolve errors, the errors were parse errors // or other errors during enter which cannot be fixed by running // any annotation processors. - if (log.unrecoverableError) + if (unrecoverableError()) { + log.reportDeferredDiagnostics(); return this; + } } // ASSERT: processAnnotations and procEnvImpl should have been set up by @@ -1015,6 +1021,7 @@ log.error("proc.no.explicit.annotation.processing.requested", classnames); } + log.reportDeferredDiagnostics(); return this; // continue regular compilation } @@ -1027,6 +1034,7 @@ if (!explicitAnnotationProcessingRequested()) { log.error("proc.no.explicit.annotation.processing.requested", classnames); + log.reportDeferredDiagnostics(); return this; // TODO: Will this halt compilation? } else { boolean errors = false; @@ -1041,7 +1049,6 @@ if (sym.kind == Kinds.PCK) sym.complete(); if (sym.exists()) { - Name name = names.fromString(nameStr); if (sym.kind == Kinds.PCK) pckSymbols = pckSymbols.prepend((PackageSymbol)sym); else @@ -1057,23 +1064,36 @@ continue; } } - if (errors) + if (errors) { + log.reportDeferredDiagnostics(); return this; + } } } try { JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols); if (c != this) annotationProcessingOccurred = c.annotationProcessingOccurred = true; + // doProcessing will have handled deferred diagnostics + assert c.log.deferDiagnostics == false; + assert c.log.deferredDiagnostics.size() == 0; return c; } finally { procEnvImpl.close(); } } catch (CompletionFailure ex) { log.error("cant.access", ex.sym, ex.getDetailValue()); + log.reportDeferredDiagnostics(); return this; + } + } + private boolean unrecoverableError() { + for (JCDiagnostic d: log.deferredDiagnostics) { + if (d.getKind() == JCDiagnostic.Kind.ERROR && !d.isFlagSet(RESOLVE_ERROR)) + return true; } + return false; } boolean explicitAnnotationProcessingRequested() { diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java --- a/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Fri Aug 27 17:59:08 2010 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Mon Aug 30 18:03:35 2010 -0700 @@ -77,6 +77,7 @@ import com.sun.tools.javac.util.Options; import static javax.tools.StandardLocation.*; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; /** * Objects of this class hold and manage the state needed to support @@ -97,6 +98,7 @@ private final boolean procOnly; private final boolean fatalErrors; private final boolean werror; + private final boolean showResolveErrors; private boolean foundTypeProcessors; private final JavacFiler filer; @@ -164,6 +166,7 @@ procOnly = options.get("-proc:only") != null || options.get("-Xprint") != null; fatalErrors = options.get("fatalEnterError") != null; + showResolveErrors = options.get("showResolveErrors") != null; werror = options.get("-Werror") != null; platformAnnotations = initPlatformAnnotations(); foundTypeProcessors = false; @@ -825,6 +828,7 @@ compiler = JavaCompiler.instance(context); log = Log.instance(context); + log.deferDiagnostics = true; // the following is for the benefit of JavacProcessingEnvironment.getContext() JavacProcessingEnvironment.this.context = context; @@ -924,10 +928,24 @@ /** Return whether or not an unrecoverable error has occurred. */ boolean unrecoverableError() { - return log.unrecoverableError - || messager.errorRaised() - || (werror && log.nwarnings > 0) - || (fatalErrors && log.nerrors > 0); + if (messager.errorRaised()) + return true; + + for (JCDiagnostic d: log.deferredDiagnostics) { + switch (d.getKind()) { + case WARNING: + if (werror) + return true; + break; + + case ERROR: + if (fatalErrors || !d.isFlagSet(RESOLVE_ERROR)) + return true; + break; + } + } + + return false; } /** Find the set of annotations present in the set of top level @@ -943,7 +961,7 @@ } /** Enter a set of generated class files. */ - List enterClassFiles(Map classFiles) { + private List enterClassFiles(Map classFiles) { ClassReader reader = ClassReader.instance(context); Names names = Names.instance(context); List list = List.nil(); @@ -970,7 +988,7 @@ } /** Enter a set of syntax trees. */ - void enterTrees(List roots) { + private void enterTrees(List roots) { compiler.enterTrees(roots); } @@ -1000,6 +1018,15 @@ } } + void showDiagnostics(boolean showAll) { + Set kinds = EnumSet.allOf(JCDiagnostic.Kind.class); + if (!showAll) { + // suppress errors, which are all presumed to be transient resolve errors + kinds.remove(JCDiagnostic.Kind.ERROR); + } + log.reportDeferredDiagnostics(kinds); + } + /** Update the processing state for the current context. */ private void updateProcessingState() { filer.newRound(context); @@ -1111,6 +1138,8 @@ errorStatus = round.unrecoverableError(); moreToDo = moreToDo(); + round.showDiagnostics(errorStatus || showResolveErrors); + // Set up next round. // Copy mutable collections returned from filer. round = round.next( @@ -1125,6 +1154,7 @@ // run last round round.run(true, errorStatus); + round.showDiagnostics(true); filer.warnIfUnclosedFiles(); warnIfUnmatchedOptions(); diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/src/share/classes/com/sun/tools/javac/util/Log.java --- a/langtools/src/share/classes/com/sun/tools/javac/util/Log.java Fri Aug 27 17:59:08 2010 -0700 +++ b/langtools/src/share/classes/com/sun/tools/javac/util/Log.java Mon Aug 30 18:03:35 2010 -0700 @@ -27,8 +27,10 @@ import java.io.*; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashSet; import java.util.Map; +import java.util.Queue; import java.util.Set; import javax.tools.DiagnosticListener; import javax.tools.JavaFileObject; @@ -110,6 +112,12 @@ */ private JavacMessages messages; + /** + * Deferred diagnostics + */ + public boolean deferDiagnostics; + public Queue deferredDiagnostics = new ListBuffer(); + /** Construct a log with given I/O redirections. */ @Deprecated @@ -204,12 +212,6 @@ */ public int nwarnings = 0; - /** - * Whether or not an unrecoverable error has been seen. - * Unrecoverable errors prevent subsequent annotation processing. - */ - public boolean unrecoverableError; - /** A set of all errors generated so far. This is used to avoid printing an * error message more than once. For each error, a pair consisting of the * source file name and source code position of the error is added to the set. @@ -347,12 +349,32 @@ nwarnings++; } + /** Report all deferred diagnostics, and clear the deferDiagnostics flag. */ + public void reportDeferredDiagnostics() { + reportDeferredDiagnostics(EnumSet.allOf(JCDiagnostic.Kind.class)); + } + + /** Report selected deferred diagnostics, and clear the deferDiagnostics flag. */ + public void reportDeferredDiagnostics(Set kinds) { + deferDiagnostics = false; + JCDiagnostic d; + while ((d = deferredDiagnostics.poll()) != null) { + if (kinds.contains(d.getKind())) + report(d); + } + } + /** * Common diagnostic handling. * The diagnostic is counted, and depending on the options and how many diagnostics have been * reported so far, the diagnostic may be handed off to writeDiagnostic. */ public void report(JCDiagnostic diagnostic) { + if (deferDiagnostics) { + deferredDiagnostics.add(diagnostic); + return; + } + if (expectDiagKeys != null) expectDiagKeys.remove(diagnostic.getCode()); diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/test/tools/javac/processing/6430209/b6341534.java --- a/langtools/test/tools/javac/processing/6430209/b6341534.java Fri Aug 27 17:59:08 2010 -0700 +++ b/langtools/test/tools/javac/processing/6430209/b6341534.java Mon Aug 30 18:03:35 2010 -0700 @@ -51,7 +51,8 @@ try { PackageElement PE = E.getPackageElement("dir1"); List LEE = PE.getEnclosedElements(); /* <=This line elicits the error message. */ - for(Element e : LEE) System.out.println("found " + e.toString() + " in dir1."); + for(Element e : LEE) + System.out.println("found " + e.toString() + " in dir1."); } catch(NullPointerException npe) { msgr.printMessage(ERROR,npe.toString()); @@ -59,7 +60,11 @@ return false; } } - if( renv.errorRaised() ) { msgr.printMessage(ERROR, "FAILED");} + // on round 1, expect errorRaised == false && processingOver == false + // on round 2, expect errorRaised == true && processingOver == true + if( renv.errorRaised() != renv.processingOver()) { + msgr.printMessage(ERROR, "FAILED"); + } return true; } diff -r f58f0ce45802 -r c7a4fb5a2f86 langtools/test/tools/javac/processing/errors/TestSuppression.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/processing/errors/TestSuppression.java Mon Aug 30 18:03:35 2010 -0700 @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 6403465 + * @summary javac should defer diagnostics until it can be determined they are persistent + */ + +import java.io.*; +import java.util.*; +import javax.annotation.processing.*; +import javax.lang.model.*; +import javax.lang.model.element.TypeElement; +import javax.tools.*; + +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.util.JCDiagnostic; + +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; + + +public class TestSuppression { + public static void main(String... args) throws Exception { + new TestSuppression().run(args); + } + + enum WarningKind { NO, YES }; + + String[] cases = { + // missing class C + "class X { C c; }", + "class X { C foo() { return null; } }", + "class X { void foo(C c) { } }", + "class X extends C { }", + "class X { }", + // missing interface I + "class X implements I { }", + "interface X extends I { }", + // missing exception E + "class X { void m() throws E { } }", + // missing method m + "class X extends C { int i = m(); }", + // missing field f + "class X extends C { int i = f; }" + }; + + void run(String... args) throws Exception { + for (String c: cases) { + for (WarningKind wk: WarningKind.values()) { + for (int g = 1; g <= 3; g++) { + try { + test(c, wk, g); + } catch (Throwable t) { + error("caught: " + t); + } + if (errors > 0) throw new AssertionError(); + } + } + } + + System.err.println(count + " test cases"); + + if (errors > 0) + throw new Exception(errors + " errors occurred"); + } + + void test(String src, WarningKind wk, int gen) throws Exception { + count++; + System.err.println("Test " + count + ": wk:" + wk + " gen:" + gen + " src:" +src); + + File testDir = new File("test" + count); + File srcDir = createDir(testDir, "src"); + File gensrcDir = createDir(testDir, "gensrc"); + File classesDir = createDir(testDir, "classes"); + + File x = writeFile(new File(srcDir, "X.java"), src); + + DiagListener dl = new DiagListener(); + JavacTool tool = JavacTool.create(); + StandardJavaFileManager fm = tool.getStandardFileManager(dl, null, null); + fm.setLocation(StandardLocation.CLASS_PATH, + Arrays.asList(classesDir, new File(System.getProperty("test.classes")))); + fm.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(classesDir)); + fm.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(gensrcDir)); + List args = new ArrayList(); +// args.add("-XprintProcessorInfo"); + args.add("-XprintRounds"); + args.add("-Agen=" + gen); + if (wk == WarningKind.YES) + args.add("-Xlint:serial"); + Iterable files = fm.getJavaFileObjects(x); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + JavacTask task = tool.getTask(pw, fm, dl, args, null, files); + task.setProcessors(Arrays.asList(new AnnoProc())); + boolean ok = task.call(); + pw.close(); + + System.err.println("ok:" + ok + " diags:" + dl.counts); + if (sw.toString().length() > 0) { + System.err.println("output:\n" + sw.toString()); + } + + for (Diagnostic.Kind dk: Diagnostic.Kind.values()) { + Integer v = dl.counts.get(dk); + int found = (v == null) ? 0 : v; + int expect = (dk == Diagnostic.Kind.WARNING && wk == WarningKind.YES) ? gen : 0; + if (found != expect) { + error("Unexpected value for " + dk + ": expected: " + expect + " found: " + found); + } + } + + System.err.println(); + } + + File createDir(File parent, String name) { + File dir = new File(parent, name); + dir.mkdirs(); + return dir; + } + + File writeFile(File f, String content) throws IOException { + FileWriter out = new FileWriter(f); + try { + out.write(content); + } finally { + out.close(); + } + return f; + } + + void add(List list, T... values) { + for (T v: values) + list.add(v); + } + + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + int count; + int errors; + + static class DiagListener implements DiagnosticListener { + int total; + Map counts = new TreeMap(); + + public void report(Diagnostic diagnostic) { + System.err.println((++total) + ": " + + "resolveError:" + isResolveError((JCDiagnostic) diagnostic) + "\n" + + diagnostic); + Diagnostic.Kind dk = diagnostic.getKind(); + Integer c = counts.get(dk); + counts.put(dk, (c == null ? 1 : c + 1)); + } + + private static boolean isResolveError(JCDiagnostic d) { + return d.isFlagSet(RESOLVE_ERROR); + } + } + + @SupportedAnnotationTypes("*") + @SupportedOptions("gen") + public static class AnnoProc extends AbstractProcessor { + Filer f; + Messager m; + int gen; + + @Override + public void init(ProcessingEnvironment processingEnv) { + f = processingEnv.getFiler(); + m = processingEnv.getMessager(); + Map options = processingEnv.getOptions(); + gen = Integer.parseInt(options.get("gen")); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + round++; + if (round < gen) + writeSource("Dummy" + round, "class Dummy" + round + " extends java.util.ArrayList{ }"); + else if (round == gen) { + writeSource("C", "class C { int f; int m() { return 0; } }"); + writeSource("I", "interface I { }"); + writeSource("E", "class E extends Exception { }"); + } + return true; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + private void writeSource(String name, String text) { + try { + JavaFileObject fo = f.createSourceFile(name); + Writer out = fo.openWriter(); + out.write(text); + out.close(); + } catch (IOException e) { + m.printMessage(Diagnostic.Kind.ERROR, e.toString()); + } + } + + int round = 0; + } +}