aoqi@0: /* aoqi@0: * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: /* aoqi@0: * @test aoqi@0: * @bug 7098660 8014649 8034223 aoqi@0: * @summary Test harness for overload resolution/inference tests aoqi@0: * @library /tools/javac/lib aoqi@0: * @build JavacTestingAbstractProcessor ResolveHarness aoqi@0: * @run main ResolveHarness aoqi@0: */ aoqi@0: aoqi@0: import com.sun.source.util.JavacTask; aoqi@0: import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper; aoqi@0: import com.sun.tools.javac.code.Flags; aoqi@0: import com.sun.tools.javac.code.Symbol; aoqi@0: import com.sun.tools.javac.code.Type.MethodType; aoqi@0: import com.sun.tools.javac.util.JCDiagnostic; aoqi@0: aoqi@0: import java.io.File; aoqi@0: import java.util.Set; aoqi@0: import java.util.Arrays; aoqi@0: import java.util.ArrayList; aoqi@0: import java.util.Collections; aoqi@0: import java.util.HashMap; aoqi@0: import java.util.HashSet; aoqi@0: import java.util.List; aoqi@0: import java.util.Locale; aoqi@0: import java.util.Map; aoqi@0: aoqi@0: import javax.annotation.processing.AbstractProcessor; aoqi@0: import javax.annotation.processing.RoundEnvironment; aoqi@0: import javax.annotation.processing.SupportedAnnotationTypes; aoqi@0: import javax.lang.model.element.Element; aoqi@0: import javax.lang.model.element.TypeElement; aoqi@0: import javax.tools.Diagnostic; aoqi@0: import javax.tools.Diagnostic.Kind; aoqi@0: import javax.tools.DiagnosticListener; aoqi@0: import javax.tools.JavaCompiler; aoqi@0: import javax.tools.JavaFileObject; aoqi@0: import javax.tools.StandardJavaFileManager; aoqi@0: import javax.tools.ToolProvider; aoqi@0: aoqi@0: import static javax.tools.StandardLocation.*; aoqi@0: aoqi@0: public class ResolveHarness implements javax.tools.DiagnosticListener { aoqi@0: aoqi@0: static int nerrors = 0; aoqi@0: aoqi@0: static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); aoqi@0: static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); aoqi@0: aoqi@0: public static void main(String[] args) throws Exception { aoqi@0: fm.setLocation(SOURCE_PATH, aoqi@0: Arrays.asList(new File(System.getProperty("test.src"), "tests"))); aoqi@0: for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) { aoqi@0: new ResolveHarness(jfo).check(); aoqi@0: } aoqi@0: if (nerrors > 0) { aoqi@0: throw new AssertionError("Errors were found"); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: aoqi@0: JavaFileObject jfo; aoqi@0: DiagnosticProcessor[] diagProcessors; aoqi@0: Map candidatesMap = new HashMap(); aoqi@0: Set declaredKeys = new HashSet<>(); aoqi@0: List> diags = new ArrayList<>(); aoqi@0: List seenCandidates = new ArrayList<>(); aoqi@0: Map predefTranslationMap = new HashMap<>(); aoqi@0: aoqi@0: protected ResolveHarness(JavaFileObject jfo) { aoqi@0: this.jfo = jfo; aoqi@0: this.diagProcessors = new DiagnosticProcessor[] { aoqi@0: new VerboseResolutionNoteProcessor(), aoqi@0: new VerboseDeferredInferenceNoteProcessor(), aoqi@0: new ErrorProcessor() aoqi@0: }; aoqi@0: predefTranslationMap.put("+", "_plus"); aoqi@0: predefTranslationMap.put("-", "_minus"); aoqi@0: predefTranslationMap.put("~", "_not"); aoqi@0: predefTranslationMap.put("++", "_plusplus"); aoqi@0: predefTranslationMap.put("--", "_minusminus"); aoqi@0: predefTranslationMap.put("!", "_bang"); aoqi@0: predefTranslationMap.put("*", "_mul"); aoqi@0: predefTranslationMap.put("/", "_div"); aoqi@0: predefTranslationMap.put("%", "_mod"); aoqi@0: predefTranslationMap.put("&", "_and"); aoqi@0: predefTranslationMap.put("|", "_or"); aoqi@0: predefTranslationMap.put("^", "_xor"); aoqi@0: predefTranslationMap.put("<<", "_lshift"); aoqi@0: predefTranslationMap.put(">>", "_rshift"); aoqi@0: predefTranslationMap.put("<<<", "_lshiftshift"); aoqi@0: predefTranslationMap.put(">>>", "_rshiftshift"); aoqi@0: predefTranslationMap.put("<", "_lt"); aoqi@0: predefTranslationMap.put(">", "_gt"); aoqi@0: predefTranslationMap.put("<=", "_lteq"); aoqi@0: predefTranslationMap.put(">=", "_gteq"); aoqi@0: predefTranslationMap.put("==", "_eq"); aoqi@0: predefTranslationMap.put("!=", "_neq"); aoqi@0: predefTranslationMap.put("&&", "_andand"); aoqi@0: predefTranslationMap.put("||", "_oror"); aoqi@0: } aoqi@0: aoqi@0: protected void check() throws Exception { aoqi@0: String[] options = { aoqi@0: "-XDshouldStopPolicy=ATTR", aoqi@0: "-XDverboseResolution=success,failure,applicable,inapplicable,deferred-inference,predef" aoqi@0: }; aoqi@0: aoqi@0: AbstractProcessor[] processors = { new ResolveCandidateFinder(), null }; aoqi@0: aoqi@0: @SuppressWarnings("unchecked") aoqi@0: DiagnosticListener[] diagListeners = aoqi@0: new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) }; aoqi@0: aoqi@0: for (int i = 0 ; i < options.length ; i ++) { aoqi@0: JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i], aoqi@0: Arrays.asList(options[i]), null, Arrays.asList(jfo)); aoqi@0: if (processors[i] != null) { aoqi@0: ct.setProcessors(Collections.singleton(processors[i])); aoqi@0: } aoqi@0: ct.analyze(); aoqi@0: } aoqi@0: aoqi@0: //check diags aoqi@0: for (Diagnostic diag : diags) { aoqi@0: for (DiagnosticProcessor proc : diagProcessors) { aoqi@0: if (proc.matches(diag)) { aoqi@0: proc.process(diag); aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: //check all candidates have been used up aoqi@0: for (Map.Entry entry : candidatesMap.entrySet()) { aoqi@0: if (!seenCandidates.contains(entry.getKey())) { aoqi@0: error("Redundant @Candidate annotation on method " + entry.getKey().elem + " sig = " + entry.getKey().elem.asType()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public void report(Diagnostic diagnostic) { aoqi@0: diags.add(diagnostic); aoqi@0: } aoqi@0: aoqi@0: Candidate getCandidateAtPos(Element methodSym, long line, long col) { aoqi@0: Candidate c = candidatesMap.get(new ElementKey(methodSym)); aoqi@0: if (c != null) { aoqi@0: Pos pos = c.pos(); aoqi@0: if (!pos.userDefined() || aoqi@0: (pos.line() == line && pos.col() == col)) { aoqi@0: seenCandidates.add(new ElementKey(methodSym)); aoqi@0: return c; aoqi@0: } aoqi@0: } else { aoqi@0: error("Missing @Candidate annotation on method " + methodSym); aoqi@0: } aoqi@0: return null; aoqi@0: } aoqi@0: aoqi@0: void checkSig(Candidate c, Element methodSym, MethodType mtype) { aoqi@0: if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) { aoqi@0: error("Inferred type mismatch for method: " + methodSym); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: protected void error(String msg) { aoqi@0: nerrors++; aoqi@0: System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Base class for diagnostic processor. It provides methods for matching and aoqi@0: * processing a given diagnostic object (overridden by subclasses). aoqi@0: */ aoqi@0: abstract class DiagnosticProcessor { aoqi@0: aoqi@0: List codes; aoqi@0: Diagnostic.Kind kind; aoqi@0: aoqi@0: public DiagnosticProcessor(Kind kind, String... codes) { aoqi@0: this.codes = Arrays.asList(codes); aoqi@0: this.kind = kind; aoqi@0: } aoqi@0: aoqi@0: abstract void process(Diagnostic diagnostic); aoqi@0: aoqi@0: boolean matches(Diagnostic diagnostic) { aoqi@0: return (codes.isEmpty() || codes.contains(diagnostic.getCode())) && aoqi@0: diagnostic.getKind() == kind; aoqi@0: } aoqi@0: aoqi@0: JCDiagnostic asJCDiagnostic(Diagnostic diagnostic) { aoqi@0: if (diagnostic instanceof JCDiagnostic) { aoqi@0: return (JCDiagnostic)diagnostic; aoqi@0: } else if (diagnostic instanceof DiagnosticSourceUnwrapper) { aoqi@0: return ((DiagnosticSourceUnwrapper)diagnostic).d; aoqi@0: } else { aoqi@0: throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName()); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: List subDiagnostics(Diagnostic diagnostic) { aoqi@0: JCDiagnostic diag = asJCDiagnostic(diagnostic); aoqi@0: if (diag instanceof JCDiagnostic.MultilineDiagnostic) { aoqi@0: return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics(); aoqi@0: } else { aoqi@0: throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Processor for verbose resolution notes generated by javac. The processor aoqi@0: * checks that the diagnostic is associated with a method declared by aoqi@0: * a class annotated with the special @TraceResolve marker annotation. If aoqi@0: * that's the case, all subdiagnostics (one for each resolution candidate) aoqi@0: * are checked against the corresponding @Candidate annotations, using aoqi@0: * a VerboseCandidateSubdiagProcessor. aoqi@0: */ aoqi@0: class VerboseResolutionNoteProcessor extends DiagnosticProcessor { aoqi@0: aoqi@0: VerboseResolutionNoteProcessor() { aoqi@0: super(Kind.NOTE, aoqi@0: "compiler.note.verbose.resolve.multi", aoqi@0: "compiler.note.verbose.resolve.multi.1"); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: void process(Diagnostic diagnostic) { aoqi@0: Element siteSym = getSiteSym(diagnostic); aoqi@0: if (siteSym.getSimpleName().length() != 0 && aoqi@0: ((Symbol)siteSym).outermostClass().getAnnotation(TraceResolve.class) == null) { aoqi@0: return; aoqi@0: } aoqi@0: int candidateIdx = 0; aoqi@0: for (JCDiagnostic d : subDiagnostics(diagnostic)) { aoqi@0: boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic); aoqi@0: VerboseCandidateSubdiagProcessor subProc = aoqi@0: new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic)); aoqi@0: if (subProc.matches(d)) { aoqi@0: subProc.process(d); aoqi@0: } else { aoqi@0: throw new AssertionError("Bad subdiagnostic: " + d.getCode()); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: Element getSiteSym(Diagnostic diagnostic) { aoqi@0: return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; aoqi@0: } aoqi@0: aoqi@0: int mostSpecific(Diagnostic diagnostic) { aoqi@0: return success(diagnostic) ? aoqi@0: (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1; aoqi@0: } aoqi@0: aoqi@0: boolean success(Diagnostic diagnostic) { aoqi@0: return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi"); aoqi@0: } aoqi@0: aoqi@0: Phase phase(Diagnostic diagnostic) { aoqi@0: return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString()); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Processor for verbose resolution subdiagnostic notes generated by javac. aoqi@0: * The processor checks that the details of the overload candidate aoqi@0: * match against the info contained in the corresponding @Candidate aoqi@0: * annotation (if any). aoqi@0: */ aoqi@0: class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor { aoqi@0: aoqi@0: boolean mostSpecific; aoqi@0: Phase phase; aoqi@0: boolean success; aoqi@0: aoqi@0: public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) { aoqi@0: super(Kind.OTHER, aoqi@0: "compiler.misc.applicable.method.found", aoqi@0: "compiler.misc.applicable.method.found.1", aoqi@0: "compiler.misc.not.applicable.method.found"); aoqi@0: this.mostSpecific = mostSpecific; aoqi@0: this.phase = phase; aoqi@0: this.success = success; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: void process(Diagnostic diagnostic) { aoqi@0: Symbol methodSym = (Symbol)methodSym(diagnostic); aoqi@0: if ((methodSym.flags() & Flags.GENERATEDCONSTR) != 0) { aoqi@0: //skip resolution of default constructor (put there by javac) aoqi@0: return; aoqi@0: } aoqi@0: Candidate c = getCandidateAtPos(methodSym, aoqi@0: asJCDiagnostic(diagnostic).getLineNumber(), aoqi@0: asJCDiagnostic(diagnostic).getColumnNumber()); aoqi@0: if (c == null) { aoqi@0: return; //nothing to check aoqi@0: } aoqi@0: aoqi@0: if (c.applicable().length == 0 && c.mostSpecific()) { aoqi@0: error("Inapplicable method cannot be most specific " + methodSym); aoqi@0: } aoqi@0: aoqi@0: if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) { aoqi@0: error("Invalid candidate's applicability " + methodSym); aoqi@0: } aoqi@0: aoqi@0: if (success) { aoqi@0: for (Phase p : c.applicable()) { aoqi@0: if (phase.ordinal() < p.ordinal()) { aoqi@0: error("Invalid phase " + p + " on method " + methodSym); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (Arrays.asList(c.applicable()).contains(phase)) { //applicable aoqi@0: if (c.mostSpecific() != mostSpecific) { aoqi@0: error("Invalid most specific value for method " + methodSym + " " + new ElementKey(methodSym).key); aoqi@0: } aoqi@0: MethodType mtype = getSig(diagnostic); aoqi@0: if (mtype != null) { aoqi@0: checkSig(c, methodSym, mtype); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: boolean isApplicable(Diagnostic diagnostic) { aoqi@0: return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found"); aoqi@0: } aoqi@0: aoqi@0: Element methodSym(Diagnostic diagnostic) { aoqi@0: return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; aoqi@0: } aoqi@0: aoqi@0: MethodType getSig(Diagnostic diagnostic) { aoqi@0: JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2]; aoqi@0: if (details == null) { aoqi@0: return null; aoqi@0: } else if (details instanceof JCDiagnostic) { aoqi@0: return details.getCode().equals("compiler.misc.full.inst.sig") ? aoqi@0: (MethodType)details.getArgs()[0] : null; aoqi@0: } else { aoqi@0: throw new AssertionError("Bad diagnostic arg: " + details); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Processor for verbose deferred inference notes generated by javac. The aoqi@0: * processor checks that the inferred signature for a given generic method aoqi@0: * call corresponds to the one (if any) declared in the @Candidate annotation. aoqi@0: */ aoqi@0: class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor { aoqi@0: aoqi@0: public VerboseDeferredInferenceNoteProcessor() { aoqi@0: super(Kind.NOTE, "compiler.note.deferred.method.inst"); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: void process(Diagnostic diagnostic) { aoqi@0: Element methodSym = methodSym(diagnostic); aoqi@0: Candidate c = getCandidateAtPos(methodSym, aoqi@0: asJCDiagnostic(diagnostic).getLineNumber(), aoqi@0: asJCDiagnostic(diagnostic).getColumnNumber()); aoqi@0: MethodType sig = sig(diagnostic); aoqi@0: if (c != null && sig != null) { aoqi@0: checkSig(c, methodSym, sig); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: Element methodSym(Diagnostic diagnostic) { aoqi@0: return (Element)asJCDiagnostic(diagnostic).getArgs()[0]; aoqi@0: } aoqi@0: aoqi@0: MethodType sig(Diagnostic diagnostic) { aoqi@0: return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1]; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Processor for all error diagnostics; if the error key is not declared in aoqi@0: * the test file header, the processor reports an error. aoqi@0: */ aoqi@0: class ErrorProcessor extends DiagnosticProcessor { aoqi@0: aoqi@0: public ErrorProcessor() { aoqi@0: super(Diagnostic.Kind.ERROR); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: void process(Diagnostic diagnostic) { aoqi@0: if (!declaredKeys.contains(diagnostic.getCode())) { aoqi@0: error("Unexpected compilation error key '" + diagnostic.getCode() + "'"); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: @SupportedAnnotationTypes({"Candidate","TraceResolve"}) aoqi@0: class ResolveCandidateFinder extends JavacTestingAbstractProcessor { aoqi@0: aoqi@0: @Override aoqi@0: public boolean process(Set annotations, RoundEnvironment roundEnv) { aoqi@0: if (roundEnv.processingOver()) aoqi@0: return true; aoqi@0: aoqi@0: TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve"); aoqi@0: TypeElement candidateAnno = elements.getTypeElement("Candidate"); aoqi@0: aoqi@0: if (!annotations.contains(traceResolveAnno)) { aoqi@0: error("no @TraceResolve annotation found in test class"); aoqi@0: } aoqi@0: aoqi@0: if (!annotations.contains(candidateAnno)) { aoqi@0: error("no @candidate annotation found in test class"); aoqi@0: } aoqi@0: aoqi@0: for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) { aoqi@0: TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class); aoqi@0: declaredKeys.addAll(Arrays.asList(traceResolve.keys())); aoqi@0: } aoqi@0: aoqi@0: for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) { aoqi@0: candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class)); aoqi@0: } aoqi@0: return true; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: class ElementKey { aoqi@0: aoqi@0: String key; aoqi@0: Element elem; aoqi@0: aoqi@0: public ElementKey(Element elem) { aoqi@0: this.elem = elem; aoqi@0: this.key = computeKey(elem); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public boolean equals(Object obj) { aoqi@0: if (obj instanceof ElementKey) { aoqi@0: ElementKey other = (ElementKey)obj; aoqi@0: return other.key.equals(key); aoqi@0: } aoqi@0: return false; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public int hashCode() { aoqi@0: return key.hashCode(); aoqi@0: } aoqi@0: aoqi@0: String computeKey(Element e) { aoqi@0: String simpleName = e.getSimpleName().toString(); aoqi@0: String opName = predefTranslationMap.get(simpleName); aoqi@0: String name = opName != null ? opName : simpleName; aoqi@0: return name + e.asType(); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public String toString() { aoqi@0: return "Key{"+key+"}"; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: class DiagnosticHandler implements DiagnosticListener { aoqi@0: aoqi@0: boolean shouldRecordDiags; aoqi@0: aoqi@0: DiagnosticHandler(boolean shouldRecordDiags) { aoqi@0: this.shouldRecordDiags = shouldRecordDiags; aoqi@0: } aoqi@0: aoqi@0: public void report(Diagnostic diagnostic) { aoqi@0: if (shouldRecordDiags) aoqi@0: diags.add(diagnostic); aoqi@0: } aoqi@0: aoqi@0: } aoqi@0: }