ResolveHarness.java revision 3643:589ff4d43428
1/*
2 * Copyright (c) 2011, 2016, 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 8014649 8034223
27 * @summary Test harness for overload resolution/inference tests
28 * @library /tools/javac/lib
29 * @modules jdk.compiler/com.sun.tools.javac.api
30 *          jdk.compiler/com.sun.tools.javac.code
31 *          jdk.compiler/com.sun.tools.javac.util
32 * @build JavacTestingAbstractProcessor ResolveHarness
33 * @run main ResolveHarness
34 */
35
36import com.sun.source.util.JavacTask;
37import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
38import com.sun.tools.javac.code.Flags;
39import com.sun.tools.javac.code.Symbol;
40import com.sun.tools.javac.code.Type.MethodType;
41import com.sun.tools.javac.util.JCDiagnostic;
42
43import java.io.File;
44import java.util.Set;
45import java.util.Arrays;
46import java.util.ArrayList;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.List;
51import java.util.Locale;
52import java.util.Map;
53
54import javax.annotation.processing.AbstractProcessor;
55import javax.annotation.processing.RoundEnvironment;
56import javax.annotation.processing.SupportedAnnotationTypes;
57import javax.lang.model.element.Element;
58import javax.lang.model.element.TypeElement;
59import javax.tools.Diagnostic;
60import javax.tools.Diagnostic.Kind;
61import javax.tools.DiagnosticListener;
62import javax.tools.JavaCompiler;
63import javax.tools.JavaFileObject;
64import javax.tools.StandardJavaFileManager;
65import javax.tools.ToolProvider;
66
67import static javax.tools.StandardLocation.*;
68
69public class ResolveHarness implements javax.tools.DiagnosticListener<JavaFileObject> {
70
71    static int nerrors = 0;
72
73    static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
74    static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
75
76    public static void main(String[] args) throws Exception {
77        try {
78            fm.setLocation(SOURCE_PATH,
79                    Arrays.asList(new File(System.getProperty("test.src"), "tests")));
80            for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) {
81                new ResolveHarness(jfo).check();
82            }
83            if (nerrors > 0) {
84                throw new AssertionError("Errors were found");
85            }
86        } finally {
87            fm.close();
88        }
89    }
90
91
92    JavaFileObject jfo;
93    DiagnosticProcessor[] diagProcessors;
94    Map<ElementKey, Candidate> candidatesMap = new HashMap<ElementKey, Candidate>();
95    Set<String> declaredKeys = new HashSet<>();
96    List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>();
97    List<ElementKey> seenCandidates = new ArrayList<>();
98    Map<String, String> predefTranslationMap = new HashMap<>();
99
100    protected ResolveHarness(JavaFileObject jfo) {
101        this.jfo = jfo;
102        this.diagProcessors = new DiagnosticProcessor[] {
103            new VerboseResolutionNoteProcessor(),
104            new VerboseDeferredInferenceNoteProcessor(),
105            new ErrorProcessor()
106        };
107        predefTranslationMap.put("+", "_plus");
108        predefTranslationMap.put("-", "_minus");
109        predefTranslationMap.put("~", "_not");
110        predefTranslationMap.put("++", "_plusplus");
111        predefTranslationMap.put("--", "_minusminus");
112        predefTranslationMap.put("!", "_bang");
113        predefTranslationMap.put("*", "_mul");
114        predefTranslationMap.put("/", "_div");
115        predefTranslationMap.put("%", "_mod");
116        predefTranslationMap.put("&", "_and");
117        predefTranslationMap.put("|", "_or");
118        predefTranslationMap.put("^", "_xor");
119        predefTranslationMap.put("<<", "_lshift");
120        predefTranslationMap.put(">>", "_rshift");
121        predefTranslationMap.put("<<<", "_lshiftshift");
122        predefTranslationMap.put(">>>", "_rshiftshift");
123        predefTranslationMap.put("<", "_lt");
124        predefTranslationMap.put(">", "_gt");
125        predefTranslationMap.put("<=", "_lteq");
126        predefTranslationMap.put(">=", "_gteq");
127        predefTranslationMap.put("==", "_eq");
128        predefTranslationMap.put("!=", "_neq");
129        predefTranslationMap.put("&&", "_andand");
130        predefTranslationMap.put("||", "_oror");
131    }
132
133    protected void check() throws Exception {
134        String[] options = {
135            "--should-stop:at=ATTR",
136            "--debug:verboseResolution=success,failure,applicable,inapplicable,deferred-inference,predef"
137        };
138
139        AbstractProcessor[] processors = { new ResolveCandidateFinder(), null };
140
141        @SuppressWarnings("unchecked")
142        DiagnosticListener<? super JavaFileObject>[] diagListeners =
143                new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) };
144
145        for (int i = 0 ; i < options.length ; i ++) {
146            JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i],
147                    Arrays.asList(options[i]), null, Arrays.asList(jfo));
148            if (processors[i] != null) {
149                ct.setProcessors(Collections.singleton(processors[i]));
150            }
151            ct.analyze();
152        }
153
154        //check diags
155        for (Diagnostic<? extends JavaFileObject> diag : diags) {
156            for (DiagnosticProcessor proc : diagProcessors) {
157                if (proc.matches(diag)) {
158                    proc.process(diag);
159                    break;
160                }
161            }
162        }
163        //check all candidates have been used up
164        for (Map.Entry<ElementKey, Candidate> entry : candidatesMap.entrySet()) {
165            if (!seenCandidates.contains(entry.getKey())) {
166                error("Redundant @Candidate annotation on method " + entry.getKey().elem + " sig = " + entry.getKey().elem.asType());
167            }
168        }
169    }
170
171    public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
172        diags.add(diagnostic);
173    }
174
175    Candidate getCandidateAtPos(Element methodSym, long line, long col) {
176        Candidate c = candidatesMap.get(new ElementKey(methodSym));
177        if (c != null) {
178            Pos pos = c.pos();
179            if (!pos.userDefined() ||
180                    (pos.line() == line && pos.col() == col)) {
181                seenCandidates.add(new ElementKey(methodSym));
182                return c;
183            }
184        } else {
185            error("Missing @Candidate annotation on method " + methodSym);
186        }
187        return null;
188    }
189
190    void checkSig(Candidate c, Element methodSym, MethodType mtype) {
191        if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) {
192            error("Inferred type mismatch for method: " + methodSym);
193        }
194    }
195
196    protected void error(String msg) {
197        nerrors++;
198        System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg);
199    }
200
201    /**
202     * Base class for diagnostic processor. It provides methods for matching and
203     * processing a given diagnostic object (overridden by subclasses).
204     */
205    abstract class DiagnosticProcessor {
206
207        List<String> codes;
208        Diagnostic.Kind kind;
209
210        public DiagnosticProcessor(Kind kind, String... codes) {
211            this.codes = Arrays.asList(codes);
212            this.kind = kind;
213        }
214
215        abstract void process(Diagnostic<? extends JavaFileObject> diagnostic);
216
217        boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) {
218            return (codes.isEmpty() || codes.contains(diagnostic.getCode())) &&
219                    diagnostic.getKind() == kind;
220        }
221
222        JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
223            if (diagnostic instanceof JCDiagnostic) {
224                return (JCDiagnostic)diagnostic;
225            } else if (diagnostic instanceof DiagnosticSourceUnwrapper) {
226                return ((DiagnosticSourceUnwrapper)diagnostic).d;
227            } else {
228                throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName());
229            }
230        }
231
232        List<JCDiagnostic> subDiagnostics(Diagnostic<? extends JavaFileObject> diagnostic) {
233            JCDiagnostic diag = asJCDiagnostic(diagnostic);
234            if (diag instanceof JCDiagnostic.MultilineDiagnostic) {
235                return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics();
236            } else {
237                throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName());
238            }
239        }
240    }
241
242    /**
243     * Processor for verbose resolution notes generated by javac. The processor
244     * checks that the diagnostic is associated with a method declared by
245     * a class annotated with the special @TraceResolve marker annotation. If
246     * that's the case, all subdiagnostics (one for each resolution candidate)
247     * are checked against the corresponding @Candidate annotations, using
248     * a VerboseCandidateSubdiagProcessor.
249     */
250    class VerboseResolutionNoteProcessor extends DiagnosticProcessor {
251
252        VerboseResolutionNoteProcessor() {
253            super(Kind.NOTE,
254                    "compiler.note.verbose.resolve.multi",
255                    "compiler.note.verbose.resolve.multi.1");
256        }
257
258        @Override
259        void process(Diagnostic<? extends JavaFileObject> diagnostic) {
260            Element siteSym = getSiteSym(diagnostic);
261            if (siteSym.getSimpleName().length() != 0 &&
262                    ((Symbol)siteSym).outermostClass().getAnnotation(TraceResolve.class) == null) {
263                return;
264            }
265            int candidateIdx = 0;
266            for (JCDiagnostic d : subDiagnostics(diagnostic)) {
267                boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic);
268                VerboseCandidateSubdiagProcessor subProc =
269                        new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic));
270                if (subProc.matches(d)) {
271                    subProc.process(d);
272                } else {
273                    throw new AssertionError("Bad subdiagnostic: " + d.getCode());
274                }
275            }
276        }
277
278        Element getSiteSym(Diagnostic<? extends JavaFileObject> diagnostic) {
279            return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
280        }
281
282        int mostSpecific(Diagnostic<? extends JavaFileObject> diagnostic) {
283            return success(diagnostic) ?
284                    (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1;
285        }
286
287        boolean success(Diagnostic<? extends JavaFileObject> diagnostic) {
288            return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi");
289        }
290
291        Phase phase(Diagnostic<? extends JavaFileObject> diagnostic) {
292            return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString());
293        }
294    }
295
296    /**
297     * Processor for verbose resolution subdiagnostic notes generated by javac.
298     * The processor checks that the details of the overload candidate
299     * match against the info contained in the corresponding @Candidate
300     * annotation (if any).
301     */
302    class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor {
303
304        boolean mostSpecific;
305        Phase phase;
306        boolean success;
307
308        public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) {
309            super(Kind.OTHER,
310                    "compiler.misc.applicable.method.found",
311                    "compiler.misc.applicable.method.found.1",
312                    "compiler.misc.not.applicable.method.found");
313            this.mostSpecific = mostSpecific;
314            this.phase = phase;
315            this.success = success;
316        }
317
318        @Override
319        void process(Diagnostic<? extends JavaFileObject> diagnostic) {
320            Symbol methodSym = (Symbol)methodSym(diagnostic);
321            if ((methodSym.flags() & Flags.GENERATEDCONSTR) != 0) {
322                //skip resolution of default constructor (put there by javac)
323                return;
324            }
325            Candidate c = getCandidateAtPos(methodSym,
326                    asJCDiagnostic(diagnostic).getLineNumber(),
327                    asJCDiagnostic(diagnostic).getColumnNumber());
328            if (c == null) {
329                return; //nothing to check
330            }
331
332            if (c.applicable().length == 0 && c.mostSpecific()) {
333                error("Inapplicable method cannot be most specific " + methodSym);
334            }
335
336            if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) {
337                error("Invalid candidate's applicability " + methodSym);
338            }
339
340            if (success) {
341                for (Phase p : c.applicable()) {
342                    if (phase.ordinal() < p.ordinal()) {
343                        error("Invalid phase " + p + " on method " + methodSym);
344                    }
345                }
346            }
347
348            if (Arrays.asList(c.applicable()).contains(phase)) { //applicable
349                if (c.mostSpecific() != mostSpecific) {
350                    error("Invalid most specific value for method " + methodSym + " " + new ElementKey(methodSym).key);
351                }
352                MethodType mtype = getSig(diagnostic);
353                if (mtype != null) {
354                    checkSig(c, methodSym, mtype);
355                }
356            }
357        }
358
359        boolean isApplicable(Diagnostic<? extends JavaFileObject> diagnostic) {
360            return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found");
361        }
362
363        Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
364            return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
365        }
366
367        MethodType getSig(Diagnostic<? extends JavaFileObject> diagnostic) {
368            JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2];
369            if (details == null) {
370                return null;
371            } else if (details instanceof JCDiagnostic) {
372                return details.getCode().equals("compiler.misc.full.inst.sig") ?
373                        (MethodType)details.getArgs()[0] : null;
374            } else {
375                throw new AssertionError("Bad diagnostic arg: " + details);
376            }
377        }
378    }
379
380    /**
381     * Processor for verbose deferred inference notes generated by javac. The
382     * processor checks that the inferred signature for a given generic method
383     * call corresponds to the one (if any) declared in the @Candidate annotation.
384     */
385    class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor {
386
387        public VerboseDeferredInferenceNoteProcessor() {
388            super(Kind.NOTE, "compiler.note.deferred.method.inst");
389        }
390
391        @Override
392        void process(Diagnostic<? extends JavaFileObject> diagnostic) {
393            Element methodSym = methodSym(diagnostic);
394            Candidate c = getCandidateAtPos(methodSym,
395                    asJCDiagnostic(diagnostic).getLineNumber(),
396                    asJCDiagnostic(diagnostic).getColumnNumber());
397            MethodType sig = sig(diagnostic);
398            if (c != null && sig != null) {
399                checkSig(c, methodSym, sig);
400            }
401        }
402
403        Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
404            return (Element)asJCDiagnostic(diagnostic).getArgs()[0];
405        }
406
407        MethodType sig(Diagnostic<? extends JavaFileObject> diagnostic) {
408            return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1];
409        }
410    }
411
412    /**
413     * Processor for all error diagnostics; if the error key is not declared in
414     * the test file header, the processor reports an error.
415     */
416    class ErrorProcessor extends DiagnosticProcessor {
417
418        public ErrorProcessor() {
419            super(Diagnostic.Kind.ERROR);
420        }
421
422        @Override
423        void process(Diagnostic<? extends JavaFileObject> diagnostic) {
424            if (!declaredKeys.contains(diagnostic.getCode())) {
425                error("Unexpected compilation error key '" + diagnostic.getCode() + "'");
426            }
427        }
428    }
429
430    @SupportedAnnotationTypes({"Candidate","TraceResolve"})
431    class ResolveCandidateFinder extends JavacTestingAbstractProcessor {
432
433        @Override
434        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
435            if (roundEnv.processingOver())
436                return true;
437
438            TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve");
439            TypeElement candidateAnno = elements.getTypeElement("Candidate");
440
441            if (!annotations.contains(traceResolveAnno)) {
442                error("no @TraceResolve annotation found in test class");
443            }
444
445            if (!annotations.contains(candidateAnno)) {
446                error("no @candidate annotation found in test class");
447            }
448
449            for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) {
450                TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class);
451                declaredKeys.addAll(Arrays.asList(traceResolve.keys()));
452            }
453
454            for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) {
455                candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class));
456            }
457            return true;
458        }
459    }
460
461    class ElementKey {
462
463        String key;
464        Element elem;
465
466        public ElementKey(Element elem) {
467            this.elem = elem;
468            this.key = computeKey(elem);
469        }
470
471        @Override
472        public boolean equals(Object obj) {
473            if (obj instanceof ElementKey) {
474                ElementKey other = (ElementKey)obj;
475                return other.key.equals(key);
476            }
477            return false;
478        }
479
480        @Override
481        public int hashCode() {
482            return key.hashCode();
483        }
484
485        String computeKey(Element e) {
486            String simpleName = e.getSimpleName().toString();
487            String opName = predefTranslationMap.get(simpleName);
488            String name = opName != null ? opName : simpleName;
489            return name + e.asType();
490        }
491
492        @Override
493        public String toString() {
494            return "Key{"+key+"}";
495        }
496    }
497
498    class DiagnosticHandler implements DiagnosticListener<JavaFileObject> {
499
500        boolean shouldRecordDiags;
501
502        DiagnosticHandler(boolean shouldRecordDiags) {
503            this.shouldRecordDiags = shouldRecordDiags;
504        }
505
506        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
507            if (shouldRecordDiags)
508                diags.add(diagnostic);
509        }
510
511    }
512}
513