diff -r a719aa5f1631 -r ce8a7e9d8882 langtools/test/tools/javac/resolve/ResolveHarness.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/langtools/test/tools/javac/resolve/ResolveHarness.java Mon Oct 24 13:00:30 2011 +0100 @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2011, 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 7098660 + * @summary Write better overload resolution/inference tests + * @library ../lib + * @build JavacTestingAbstractProcessor ResolveHarness + * @run main ResolveHarness + */ + +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper; +import com.sun.tools.javac.code.Type.MethodType; +import com.sun.tools.javac.util.JCDiagnostic; + +import java.io.File; +import java.util.Set; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.Diagnostic.Kind; +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import static javax.tools.StandardLocation.*; + +public class ResolveHarness implements javax.tools.DiagnosticListener { + + static int nerrors = 0; + + static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); + static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); + + public static void main(String[] args) throws Exception { + fm.setLocation(SOURCE_PATH, + Arrays.asList(new File(System.getProperty("test.src"), "tests"))); + for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) { + new ResolveHarness(jfo).check(); + } + if (nerrors > 0) { + throw new AssertionError("Errors were found"); + } + } + + + JavaFileObject jfo; + DiagnosticProcessor[] diagProcessors; + Map candidatesMap = new HashMap(); + Set declaredKeys = new HashSet<>(); + List> diags = new ArrayList<>(); + List seenCandidates = new ArrayList<>(); + + protected ResolveHarness(JavaFileObject jfo) { + this.jfo = jfo; + this.diagProcessors = new DiagnosticProcessor[] { + new VerboseResolutionNoteProcessor(), + new VerboseDeferredInferenceNoteProcessor(), + new ErrorProcessor() + }; + } + + protected void check() throws Exception { + String[] options = { + "-XDshouldStopPolicy=ATTR", + "-XDverboseResolution=success,failure,applicable,inapplicable,deferred-inference" + }; + + AbstractProcessor[] processors = { new ResolveCandidateFinder(), null }; + + @SuppressWarnings("unchecked") + DiagnosticListener[] diagListeners = + new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) }; + + for (int i = 0 ; i < options.length ; i ++) { + JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i], + Arrays.asList(options[i]), null, Arrays.asList(jfo)); + if (processors[i] != null) { + ct.setProcessors(Collections.singleton(processors[i])); + } + ct.analyze(); + } + + //check diags + for (Diagnostic diag : diags) { + for (DiagnosticProcessor proc : diagProcessors) { + if (proc.matches(diag)) { + proc.process(diag); + break; + } + } + } + //check all candidates have been used up + for (Map.Entry entry : candidatesMap.entrySet()) { + if (!seenCandidates.contains(entry.getKey())) { + error("Redundant @Candidate annotation on method " + entry.getKey().elem); + } + } + } + + public void report(Diagnostic diagnostic) { + diags.add(diagnostic); + } + + Candidate getCandidateAtPos(Element methodSym, long line, long col) { + Candidate c = candidatesMap.get(new ElementKey(methodSym)); + if (c != null) { + Pos pos = c.pos(); + if (!pos.userDefined() || + (pos.line() == line && pos.col() == col)) { + seenCandidates.add(new ElementKey(methodSym)); + return c; + } + } else { + error("Missing @Candidate annotation on method " + methodSym); + } + return null; + } + + void checkSig(Candidate c, Element methodSym, MethodType mtype) { + if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) { + error("Inferred type mismatch for method: " + methodSym); + } + } + + protected void error(String msg) { + nerrors++; + System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg); + } + + /** + * Base class for diagnostic processor. It provides methods for matching and + * processing a given diagnostic object (overridden by subclasses). + */ + abstract class DiagnosticProcessor { + + List codes; + Diagnostic.Kind kind; + + public DiagnosticProcessor(Kind kind, String... codes) { + this.codes = Arrays.asList(codes); + this.kind = kind; + } + + abstract void process(Diagnostic diagnostic); + + boolean matches(Diagnostic diagnostic) { + return (codes.isEmpty() || codes.contains(diagnostic.getCode())) && + diagnostic.getKind() == kind; + } + + JCDiagnostic asJCDiagnostic(Diagnostic diagnostic) { + if (diagnostic instanceof JCDiagnostic) { + return (JCDiagnostic)diagnostic; + } else if (diagnostic instanceof DiagnosticSourceUnwrapper) { + return ((DiagnosticSourceUnwrapper)diagnostic).d; + } else { + throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName()); + } + } + + List subDiagnostics(Diagnostic diagnostic) { + JCDiagnostic diag = asJCDiagnostic(diagnostic); + if (diag instanceof JCDiagnostic.MultilineDiagnostic) { + return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics(); + } else { + throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName()); + } + } + } + + /** + * Processor for verbose resolution notes generated by javac. The processor + * checks that the diagnostic is associated with a method declared by + * a class annotated with the special @TraceResolve marker annotation. If + * that's the case, all subdiagnostics (one for each resolution candidate) + * are checked against the corresponding @Candidate annotations, using + * a VerboseCandidateSubdiagProcessor. + */ + class VerboseResolutionNoteProcessor extends DiagnosticProcessor { + + VerboseResolutionNoteProcessor() { + super(Kind.NOTE, + "compiler.note.verbose.resolve.multi", + "compiler.note.verbose.resolve.multi.1"); + } + + @Override + void process(Diagnostic diagnostic) { + Element siteSym = getSiteSym(diagnostic); + if (siteSym.getAnnotation(TraceResolve.class) == null) { + return; + } + int candidateIdx = 0; + for (JCDiagnostic d : subDiagnostics(diagnostic)) { + boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic); + VerboseCandidateSubdiagProcessor subProc = + new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic)); + if (subProc.matches(d)) { + subProc.process(d); + } else { + throw new AssertionError("Bad subdiagnostic: " + d.getCode()); + } + } + } + + Element getSiteSym(Diagnostic diagnostic) { + return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; + } + + int mostSpecific(Diagnostic diagnostic) { + return success(diagnostic) ? + (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1; + } + + boolean success(Diagnostic diagnostic) { + return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi"); + } + + Phase phase(Diagnostic diagnostic) { + return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString()); + } + } + + /** + * Processor for verbose resolution subdiagnostic notes generated by javac. + * The processor checks that the details of the overload candidate + * match against the info contained in the corresponding @Candidate + * annotation (if any). + */ + class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor { + + boolean mostSpecific; + Phase phase; + boolean success; + + public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) { + super(Kind.OTHER, + "compiler.misc.applicable.method.found", + "compiler.misc.applicable.method.found.1", + "compiler.misc.not.applicable.method.found"); + this.mostSpecific = mostSpecific; + this.phase = phase; + this.success = success; + } + + @Override + void process(Diagnostic diagnostic) { + Element methodSym = methodSym(diagnostic); + Candidate c = getCandidateAtPos(methodSym, + asJCDiagnostic(diagnostic).getLineNumber(), + asJCDiagnostic(diagnostic).getColumnNumber()); + if (c == null) { + return; //nothing to check + } + + if (c.applicable().length == 0 && c.mostSpecific()) { + error("Inapplicable method cannot be most specific " + methodSym); + } + + if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) { + error("Invalid candidate's applicability " + methodSym); + } + + if (success) { + for (Phase p : c.applicable()) { + if (phase.ordinal() < p.ordinal()) { + error("Invalid phase " + p + " on method " + methodSym); + } + } + } + + if (Arrays.asList(c.applicable()).contains(phase)) { //applicable + if (c.mostSpecific() != mostSpecific) { + error("Invalid most specific value for method " + methodSym); + } + MethodType mtype = getSig(diagnostic); + if (mtype != null) { + checkSig(c, methodSym, mtype); + } + } + } + + boolean isApplicable(Diagnostic diagnostic) { + return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found"); + } + + Element methodSym(Diagnostic diagnostic) { + return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; + } + + MethodType getSig(Diagnostic diagnostic) { + JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2]; + if (details == null) { + return null; + } else if (details instanceof JCDiagnostic) { + return details.getCode().equals("compiler.misc.full.inst.sig") ? + (MethodType)details.getArgs()[0] : null; + } else { + throw new AssertionError("Bad diagnostic arg: " + details); + } + } + } + + /** + * Processor for verbose deferred inference notes generated by javac. The + * processor checks that the inferred signature for a given generic method + * call corresponds to the one (if any) declared in the @Candidate annotation. + */ + class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor { + + public VerboseDeferredInferenceNoteProcessor() { + super(Kind.NOTE, "compiler.note.deferred.method.inst"); + } + + @Override + void process(Diagnostic diagnostic) { + Element methodSym = methodSym(diagnostic); + Candidate c = getCandidateAtPos(methodSym, + asJCDiagnostic(diagnostic).getLineNumber(), + asJCDiagnostic(diagnostic).getColumnNumber()); + MethodType sig = sig(diagnostic); + if (c != null && sig != null) { + checkSig(c, methodSym, sig); + } + } + + Element methodSym(Diagnostic diagnostic) { + return (Element)asJCDiagnostic(diagnostic).getArgs()[0]; + } + + MethodType sig(Diagnostic diagnostic) { + return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1]; + } + } + + /** + * Processor for all error diagnostics; if the error key is not declared in + * the test file header, the processor reports an error. + */ + class ErrorProcessor extends DiagnosticProcessor { + + public ErrorProcessor() { + super(Diagnostic.Kind.ERROR); + } + + @Override + void process(Diagnostic diagnostic) { + if (!declaredKeys.contains(diagnostic.getCode())) { + error("Unexpected compilation error key '" + diagnostic.getCode() + "'"); + } + } + } + + @SupportedAnnotationTypes({"Candidate","TraceResolve"}) + class ResolveCandidateFinder extends JavacTestingAbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) + return true; + + TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve"); + TypeElement candidateAnno = elements.getTypeElement("Candidate"); + + if (!annotations.contains(traceResolveAnno)) { + error("no @TraceResolve annotation found in test class"); + } + + if (!annotations.contains(candidateAnno)) { + error("no @candidate annotation found in test class"); + } + + for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) { + TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class); + declaredKeys.addAll(Arrays.asList(traceResolve.keys())); + } + + for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) { + candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class)); + } + return true; + } + } + + class ElementKey { + + String key; + Element elem; + + public ElementKey(Element elem) { + this.elem = elem; + this.key = computeKey(elem); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ElementKey) { + ElementKey other = (ElementKey)obj; + return other.key.equals(key); + } + return false; + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + String computeKey(Element e) { + StringBuilder buf = new StringBuilder(); + while (e != null) { + buf.append(e.toString()); + e = e.getEnclosingElement(); + } + buf.append(jfo.getName()); + return buf.toString(); + } + + @Override + public String toString() { + return "Key{"+key+"}"; + } + } + + class DiagnosticHandler implements DiagnosticListener { + + boolean shouldRecordDiags; + + DiagnosticHandler(boolean shouldRecordDiags) { + this.shouldRecordDiags = shouldRecordDiags; + } + + public void report(Diagnostic diagnostic) { + if (shouldRecordDiags) + diags.add(diagnostic); + } + + } +}