langtools/test/tools/javac/resolve/ResolveHarness.java
changeset 10816 ce8a7e9d8882
child 14963 974d4423c999
equal deleted inserted replaced
10815:a719aa5f1631 10816:ce8a7e9d8882
       
     1 /*
       
     2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test
       
    26  * @bug 7098660
       
    27  * @summary Write better overload resolution/inference tests
       
    28  * @library ../lib
       
    29  * @build JavacTestingAbstractProcessor ResolveHarness
       
    30  * @run main ResolveHarness
       
    31  */
       
    32 
       
    33 import com.sun.source.util.JavacTask;
       
    34 import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
       
    35 import com.sun.tools.javac.code.Type.MethodType;
       
    36 import com.sun.tools.javac.util.JCDiagnostic;
       
    37 
       
    38 import java.io.File;
       
    39 import java.util.Set;
       
    40 import java.util.Arrays;
       
    41 import java.util.ArrayList;
       
    42 import java.util.Collections;
       
    43 import java.util.HashMap;
       
    44 import java.util.HashSet;
       
    45 import java.util.List;
       
    46 import java.util.Map;
       
    47 
       
    48 import javax.annotation.processing.AbstractProcessor;
       
    49 import javax.annotation.processing.RoundEnvironment;
       
    50 import javax.annotation.processing.SupportedAnnotationTypes;
       
    51 import javax.lang.model.element.Element;
       
    52 import javax.lang.model.element.TypeElement;
       
    53 import javax.tools.Diagnostic;
       
    54 import javax.tools.Diagnostic.Kind;
       
    55 import javax.tools.DiagnosticListener;
       
    56 import javax.tools.JavaCompiler;
       
    57 import javax.tools.JavaFileObject;
       
    58 import javax.tools.StandardJavaFileManager;
       
    59 import javax.tools.ToolProvider;
       
    60 
       
    61 import static javax.tools.StandardLocation.*;
       
    62 
       
    63 public class ResolveHarness implements javax.tools.DiagnosticListener<JavaFileObject> {
       
    64 
       
    65     static int nerrors = 0;
       
    66 
       
    67     static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
       
    68     static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
       
    69 
       
    70     public static void main(String[] args) throws Exception {
       
    71         fm.setLocation(SOURCE_PATH,
       
    72                 Arrays.asList(new File(System.getProperty("test.src"), "tests")));
       
    73         for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) {
       
    74             new ResolveHarness(jfo).check();
       
    75         }
       
    76         if (nerrors > 0) {
       
    77             throw new AssertionError("Errors were found");
       
    78         }
       
    79     }
       
    80 
       
    81 
       
    82     JavaFileObject jfo;
       
    83     DiagnosticProcessor[] diagProcessors;
       
    84     Map<ElementKey, Candidate> candidatesMap = new HashMap<ElementKey, Candidate>();
       
    85     Set<String> declaredKeys = new HashSet<>();
       
    86     List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>();
       
    87     List<ElementKey> seenCandidates = new ArrayList<>();
       
    88 
       
    89     protected ResolveHarness(JavaFileObject jfo) {
       
    90         this.jfo = jfo;
       
    91         this.diagProcessors = new DiagnosticProcessor[] {
       
    92             new VerboseResolutionNoteProcessor(),
       
    93             new VerboseDeferredInferenceNoteProcessor(),
       
    94             new ErrorProcessor()
       
    95         };
       
    96     }
       
    97 
       
    98     protected void check() throws Exception {
       
    99         String[] options = {
       
   100             "-XDshouldStopPolicy=ATTR",
       
   101             "-XDverboseResolution=success,failure,applicable,inapplicable,deferred-inference"
       
   102         };
       
   103 
       
   104         AbstractProcessor[] processors = { new ResolveCandidateFinder(), null };
       
   105 
       
   106         @SuppressWarnings("unchecked")
       
   107         DiagnosticListener<? super JavaFileObject>[] diagListeners =
       
   108                 new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) };
       
   109 
       
   110         for (int i = 0 ; i < options.length ; i ++) {
       
   111             JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i],
       
   112                     Arrays.asList(options[i]), null, Arrays.asList(jfo));
       
   113             if (processors[i] != null) {
       
   114                 ct.setProcessors(Collections.singleton(processors[i]));
       
   115             }
       
   116             ct.analyze();
       
   117         }
       
   118 
       
   119         //check diags
       
   120         for (Diagnostic<? extends JavaFileObject> diag : diags) {
       
   121             for (DiagnosticProcessor proc : diagProcessors) {
       
   122                 if (proc.matches(diag)) {
       
   123                     proc.process(diag);
       
   124                     break;
       
   125                 }
       
   126             }
       
   127         }
       
   128         //check all candidates have been used up
       
   129         for (Map.Entry<ElementKey, Candidate> entry : candidatesMap.entrySet()) {
       
   130             if (!seenCandidates.contains(entry.getKey())) {
       
   131                 error("Redundant @Candidate annotation on method " + entry.getKey().elem);
       
   132             }
       
   133         }
       
   134     }
       
   135 
       
   136     public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   137         diags.add(diagnostic);
       
   138     }
       
   139 
       
   140     Candidate getCandidateAtPos(Element methodSym, long line, long col) {
       
   141         Candidate c = candidatesMap.get(new ElementKey(methodSym));
       
   142         if (c != null) {
       
   143             Pos pos = c.pos();
       
   144             if (!pos.userDefined() ||
       
   145                     (pos.line() == line && pos.col() == col)) {
       
   146                 seenCandidates.add(new ElementKey(methodSym));
       
   147                 return c;
       
   148             }
       
   149         } else {
       
   150             error("Missing @Candidate annotation on method " + methodSym);
       
   151         }
       
   152         return null;
       
   153     }
       
   154 
       
   155     void checkSig(Candidate c, Element methodSym, MethodType mtype) {
       
   156         if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) {
       
   157             error("Inferred type mismatch for method: " + methodSym);
       
   158         }
       
   159     }
       
   160 
       
   161     protected void error(String msg) {
       
   162         nerrors++;
       
   163         System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg);
       
   164     }
       
   165 
       
   166     /**
       
   167      * Base class for diagnostic processor. It provides methods for matching and
       
   168      * processing a given diagnostic object (overridden by subclasses).
       
   169      */
       
   170     abstract class DiagnosticProcessor {
       
   171 
       
   172         List<String> codes;
       
   173         Diagnostic.Kind kind;
       
   174 
       
   175         public DiagnosticProcessor(Kind kind, String... codes) {
       
   176             this.codes = Arrays.asList(codes);
       
   177             this.kind = kind;
       
   178         }
       
   179 
       
   180         abstract void process(Diagnostic<? extends JavaFileObject> diagnostic);
       
   181 
       
   182         boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   183             return (codes.isEmpty() || codes.contains(diagnostic.getCode())) &&
       
   184                     diagnostic.getKind() == kind;
       
   185         }
       
   186 
       
   187         JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   188             if (diagnostic instanceof JCDiagnostic) {
       
   189                 return (JCDiagnostic)diagnostic;
       
   190             } else if (diagnostic instanceof DiagnosticSourceUnwrapper) {
       
   191                 return ((DiagnosticSourceUnwrapper)diagnostic).d;
       
   192             } else {
       
   193                 throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName());
       
   194             }
       
   195         }
       
   196 
       
   197         List<JCDiagnostic> subDiagnostics(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   198             JCDiagnostic diag = asJCDiagnostic(diagnostic);
       
   199             if (diag instanceof JCDiagnostic.MultilineDiagnostic) {
       
   200                 return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics();
       
   201             } else {
       
   202                 throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName());
       
   203             }
       
   204         }
       
   205     }
       
   206 
       
   207     /**
       
   208      * Processor for verbose resolution notes generated by javac. The processor
       
   209      * checks that the diagnostic is associated with a method declared by
       
   210      * a class annotated with the special @TraceResolve marker annotation. If
       
   211      * that's the case, all subdiagnostics (one for each resolution candidate)
       
   212      * are checked against the corresponding @Candidate annotations, using
       
   213      * a VerboseCandidateSubdiagProcessor.
       
   214      */
       
   215     class VerboseResolutionNoteProcessor extends DiagnosticProcessor {
       
   216 
       
   217         VerboseResolutionNoteProcessor() {
       
   218             super(Kind.NOTE,
       
   219                     "compiler.note.verbose.resolve.multi",
       
   220                     "compiler.note.verbose.resolve.multi.1");
       
   221         }
       
   222 
       
   223         @Override
       
   224         void process(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   225             Element siteSym = getSiteSym(diagnostic);
       
   226             if (siteSym.getAnnotation(TraceResolve.class) == null) {
       
   227                 return;
       
   228             }
       
   229             int candidateIdx = 0;
       
   230             for (JCDiagnostic d : subDiagnostics(diagnostic)) {
       
   231                 boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic);
       
   232                 VerboseCandidateSubdiagProcessor subProc =
       
   233                         new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic));
       
   234                 if (subProc.matches(d)) {
       
   235                     subProc.process(d);
       
   236                 } else {
       
   237                     throw new AssertionError("Bad subdiagnostic: " + d.getCode());
       
   238                 }
       
   239             }
       
   240         }
       
   241 
       
   242         Element getSiteSym(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   243             return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
       
   244         }
       
   245 
       
   246         int mostSpecific(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   247             return success(diagnostic) ?
       
   248                     (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1;
       
   249         }
       
   250 
       
   251         boolean success(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   252             return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi");
       
   253         }
       
   254 
       
   255         Phase phase(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   256             return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString());
       
   257         }
       
   258     }
       
   259 
       
   260     /**
       
   261      * Processor for verbose resolution subdiagnostic notes generated by javac.
       
   262      * The processor checks that the details of the overload candidate
       
   263      * match against the info contained in the corresponding @Candidate
       
   264      * annotation (if any).
       
   265      */
       
   266     class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor {
       
   267 
       
   268         boolean mostSpecific;
       
   269         Phase phase;
       
   270         boolean success;
       
   271 
       
   272         public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) {
       
   273             super(Kind.OTHER,
       
   274                     "compiler.misc.applicable.method.found",
       
   275                     "compiler.misc.applicable.method.found.1",
       
   276                     "compiler.misc.not.applicable.method.found");
       
   277             this.mostSpecific = mostSpecific;
       
   278             this.phase = phase;
       
   279             this.success = success;
       
   280         }
       
   281 
       
   282         @Override
       
   283         void process(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   284             Element methodSym = methodSym(diagnostic);
       
   285             Candidate c = getCandidateAtPos(methodSym,
       
   286                     asJCDiagnostic(diagnostic).getLineNumber(),
       
   287                     asJCDiagnostic(diagnostic).getColumnNumber());
       
   288             if (c == null) {
       
   289                 return; //nothing to check
       
   290             }
       
   291 
       
   292             if (c.applicable().length == 0 && c.mostSpecific()) {
       
   293                 error("Inapplicable method cannot be most specific " + methodSym);
       
   294             }
       
   295 
       
   296             if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) {
       
   297                 error("Invalid candidate's applicability " + methodSym);
       
   298             }
       
   299 
       
   300             if (success) {
       
   301                 for (Phase p : c.applicable()) {
       
   302                     if (phase.ordinal() < p.ordinal()) {
       
   303                         error("Invalid phase " + p + " on method " + methodSym);
       
   304                     }
       
   305                 }
       
   306             }
       
   307 
       
   308             if (Arrays.asList(c.applicable()).contains(phase)) { //applicable
       
   309                 if (c.mostSpecific() != mostSpecific) {
       
   310                     error("Invalid most specific value for method " + methodSym);
       
   311                 }
       
   312                 MethodType mtype = getSig(diagnostic);
       
   313                 if (mtype != null) {
       
   314                     checkSig(c, methodSym, mtype);
       
   315                 }
       
   316             }
       
   317         }
       
   318 
       
   319         boolean isApplicable(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   320             return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found");
       
   321         }
       
   322 
       
   323         Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   324             return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
       
   325         }
       
   326 
       
   327         MethodType getSig(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   328             JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2];
       
   329             if (details == null) {
       
   330                 return null;
       
   331             } else if (details instanceof JCDiagnostic) {
       
   332                 return details.getCode().equals("compiler.misc.full.inst.sig") ?
       
   333                         (MethodType)details.getArgs()[0] : null;
       
   334             } else {
       
   335                 throw new AssertionError("Bad diagnostic arg: " + details);
       
   336             }
       
   337         }
       
   338     }
       
   339 
       
   340     /**
       
   341      * Processor for verbose deferred inference notes generated by javac. The
       
   342      * processor checks that the inferred signature for a given generic method
       
   343      * call corresponds to the one (if any) declared in the @Candidate annotation.
       
   344      */
       
   345     class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor {
       
   346 
       
   347         public VerboseDeferredInferenceNoteProcessor() {
       
   348             super(Kind.NOTE, "compiler.note.deferred.method.inst");
       
   349         }
       
   350 
       
   351         @Override
       
   352         void process(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   353             Element methodSym = methodSym(diagnostic);
       
   354             Candidate c = getCandidateAtPos(methodSym,
       
   355                     asJCDiagnostic(diagnostic).getLineNumber(),
       
   356                     asJCDiagnostic(diagnostic).getColumnNumber());
       
   357             MethodType sig = sig(diagnostic);
       
   358             if (c != null && sig != null) {
       
   359                 checkSig(c, methodSym, sig);
       
   360             }
       
   361         }
       
   362 
       
   363         Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   364             return (Element)asJCDiagnostic(diagnostic).getArgs()[0];
       
   365         }
       
   366 
       
   367         MethodType sig(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   368             return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1];
       
   369         }
       
   370     }
       
   371 
       
   372     /**
       
   373      * Processor for all error diagnostics; if the error key is not declared in
       
   374      * the test file header, the processor reports an error.
       
   375      */
       
   376     class ErrorProcessor extends DiagnosticProcessor {
       
   377 
       
   378         public ErrorProcessor() {
       
   379             super(Diagnostic.Kind.ERROR);
       
   380         }
       
   381 
       
   382         @Override
       
   383         void process(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   384             if (!declaredKeys.contains(diagnostic.getCode())) {
       
   385                 error("Unexpected compilation error key '" + diagnostic.getCode() + "'");
       
   386             }
       
   387         }
       
   388     }
       
   389 
       
   390     @SupportedAnnotationTypes({"Candidate","TraceResolve"})
       
   391     class ResolveCandidateFinder extends JavacTestingAbstractProcessor {
       
   392 
       
   393         @Override
       
   394         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
       
   395             if (roundEnv.processingOver())
       
   396                 return true;
       
   397 
       
   398             TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve");
       
   399             TypeElement candidateAnno = elements.getTypeElement("Candidate");
       
   400 
       
   401             if (!annotations.contains(traceResolveAnno)) {
       
   402                 error("no @TraceResolve annotation found in test class");
       
   403             }
       
   404 
       
   405             if (!annotations.contains(candidateAnno)) {
       
   406                 error("no @candidate annotation found in test class");
       
   407             }
       
   408 
       
   409             for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) {
       
   410                 TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class);
       
   411                 declaredKeys.addAll(Arrays.asList(traceResolve.keys()));
       
   412             }
       
   413 
       
   414             for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) {
       
   415                 candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class));
       
   416             }
       
   417             return true;
       
   418         }
       
   419     }
       
   420 
       
   421     class ElementKey {
       
   422 
       
   423         String key;
       
   424         Element elem;
       
   425 
       
   426         public ElementKey(Element elem) {
       
   427             this.elem = elem;
       
   428             this.key = computeKey(elem);
       
   429         }
       
   430 
       
   431         @Override
       
   432         public boolean equals(Object obj) {
       
   433             if (obj instanceof ElementKey) {
       
   434                 ElementKey other = (ElementKey)obj;
       
   435                 return other.key.equals(key);
       
   436             }
       
   437             return false;
       
   438         }
       
   439 
       
   440         @Override
       
   441         public int hashCode() {
       
   442             return key.hashCode();
       
   443         }
       
   444 
       
   445         String computeKey(Element e) {
       
   446             StringBuilder buf = new StringBuilder();
       
   447             while (e != null) {
       
   448                 buf.append(e.toString());
       
   449                 e = e.getEnclosingElement();
       
   450             }
       
   451             buf.append(jfo.getName());
       
   452             return buf.toString();
       
   453         }
       
   454 
       
   455         @Override
       
   456         public String toString() {
       
   457             return "Key{"+key+"}";
       
   458         }
       
   459     }
       
   460 
       
   461     class DiagnosticHandler implements DiagnosticListener<JavaFileObject> {
       
   462 
       
   463         boolean shouldRecordDiags;
       
   464 
       
   465         DiagnosticHandler(boolean shouldRecordDiags) {
       
   466             this.shouldRecordDiags = shouldRecordDiags;
       
   467         }
       
   468 
       
   469         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   470             if (shouldRecordDiags)
       
   471                 diags.add(diagnostic);
       
   472         }
       
   473 
       
   474     }
       
   475 }