| /* |
| * Copyright (c) 2011, 2016, 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 8014649 8034223 |
| * @summary Test harness for overload resolution/inference tests |
| * @library /tools/javac/lib |
| * @modules jdk.compiler/com.sun.tools.javac.api |
| * jdk.compiler/com.sun.tools.javac.code |
| * jdk.compiler/com.sun.tools.javac.util |
| * @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.Flags; |
| import com.sun.tools.javac.code.Symbol; |
| 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.Locale; |
| 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<JavaFileObject> { |
| |
| 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 { |
| try { |
| 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"); |
| } |
| } finally { |
| fm.close(); |
| } |
| } |
| |
| |
| JavaFileObject jfo; |
| DiagnosticProcessor[] diagProcessors; |
| Map<ElementKey, Candidate> candidatesMap = new HashMap<ElementKey, Candidate>(); |
| Set<String> declaredKeys = new HashSet<>(); |
| List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>(); |
| List<ElementKey> seenCandidates = new ArrayList<>(); |
| Map<String, String> predefTranslationMap = new HashMap<>(); |
| |
| protected ResolveHarness(JavaFileObject jfo) { |
| this.jfo = jfo; |
| this.diagProcessors = new DiagnosticProcessor[] { |
| new VerboseResolutionNoteProcessor(), |
| new VerboseDeferredInferenceNoteProcessor(), |
| new ErrorProcessor() |
| }; |
| predefTranslationMap.put("+", "_plus"); |
| predefTranslationMap.put("-", "_minus"); |
| predefTranslationMap.put("~", "_not"); |
| predefTranslationMap.put("++", "_plusplus"); |
| predefTranslationMap.put("--", "_minusminus"); |
| predefTranslationMap.put("!", "_bang"); |
| predefTranslationMap.put("*", "_mul"); |
| predefTranslationMap.put("/", "_div"); |
| predefTranslationMap.put("%", "_mod"); |
| predefTranslationMap.put("&", "_and"); |
| predefTranslationMap.put("|", "_or"); |
| predefTranslationMap.put("^", "_xor"); |
| predefTranslationMap.put("<<", "_lshift"); |
| predefTranslationMap.put(">>", "_rshift"); |
| predefTranslationMap.put("<<<", "_lshiftshift"); |
| predefTranslationMap.put(">>>", "_rshiftshift"); |
| predefTranslationMap.put("<", "_lt"); |
| predefTranslationMap.put(">", "_gt"); |
| predefTranslationMap.put("<=", "_lteq"); |
| predefTranslationMap.put(">=", "_gteq"); |
| predefTranslationMap.put("==", "_eq"); |
| predefTranslationMap.put("!=", "_neq"); |
| predefTranslationMap.put("&&", "_andand"); |
| predefTranslationMap.put("||", "_oror"); |
| } |
| |
| protected void check() throws Exception { |
| String[] options = { |
| "--should-stop:at=ATTR", |
| "--debug:verboseResolution=success,failure,applicable,inapplicable,deferred-inference,predef" |
| }; |
| |
| AbstractProcessor[] processors = { new ResolveCandidateFinder(), null }; |
| |
| @SuppressWarnings("unchecked") |
| DiagnosticListener<? super JavaFileObject>[] 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<? extends JavaFileObject> diag : diags) { |
| for (DiagnosticProcessor proc : diagProcessors) { |
| if (proc.matches(diag)) { |
| proc.process(diag); |
| break; |
| } |
| } |
| } |
| //check all candidates have been used up |
| for (Map.Entry<ElementKey, Candidate> entry : candidatesMap.entrySet()) { |
| if (!seenCandidates.contains(entry.getKey())) { |
| error("Redundant @Candidate annotation on method " + entry.getKey().elem + " sig = " + entry.getKey().elem.asType()); |
| } |
| } |
| } |
| |
| public void report(Diagnostic<? extends JavaFileObject> 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<String> codes; |
| Diagnostic.Kind kind; |
| |
| public DiagnosticProcessor(Kind kind, String... codes) { |
| this.codes = Arrays.asList(codes); |
| this.kind = kind; |
| } |
| |
| abstract void process(Diagnostic<? extends JavaFileObject> diagnostic); |
| |
| boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) { |
| return (codes.isEmpty() || codes.contains(diagnostic.getCode())) && |
| diagnostic.getKind() == kind; |
| } |
| |
| JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> 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<JCDiagnostic> subDiagnostics(Diagnostic<? extends JavaFileObject> 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<? extends JavaFileObject> diagnostic) { |
| Element siteSym = getSiteSym(diagnostic); |
| if (siteSym.getSimpleName().length() != 0 && |
| ((Symbol)siteSym).outermostClass().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<? extends JavaFileObject> diagnostic) { |
| return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; |
| } |
| |
| int mostSpecific(Diagnostic<? extends JavaFileObject> diagnostic) { |
| return success(diagnostic) ? |
| (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1; |
| } |
| |
| boolean success(Diagnostic<? extends JavaFileObject> diagnostic) { |
| return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi"); |
| } |
| |
| Phase phase(Diagnostic<? extends JavaFileObject> 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<? extends JavaFileObject> diagnostic) { |
| Symbol methodSym = (Symbol)methodSym(diagnostic); |
| if ((methodSym.flags() & Flags.GENERATEDCONSTR) != 0) { |
| //skip resolution of default constructor (put there by javac) |
| return; |
| } |
| 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 + " " + new ElementKey(methodSym).key); |
| } |
| MethodType mtype = getSig(diagnostic); |
| if (mtype != null) { |
| checkSig(c, methodSym, mtype); |
| } |
| } |
| } |
| |
| boolean isApplicable(Diagnostic<? extends JavaFileObject> diagnostic) { |
| return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found"); |
| } |
| |
| Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) { |
| return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; |
| } |
| |
| MethodType getSig(Diagnostic<? extends JavaFileObject> 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<? extends JavaFileObject> 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<? extends JavaFileObject> diagnostic) { |
| return (Element)asJCDiagnostic(diagnostic).getArgs()[0]; |
| } |
| |
| MethodType sig(Diagnostic<? extends JavaFileObject> 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<? extends JavaFileObject> 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<? extends TypeElement> 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) { |
| String simpleName = e.getSimpleName().toString(); |
| String opName = predefTranslationMap.get(simpleName); |
| String name = opName != null ? opName : simpleName; |
| return name + e.asType(); |
| } |
| |
| @Override |
| public String toString() { |
| return "Key{"+key+"}"; |
| } |
| } |
| |
| class DiagnosticHandler implements DiagnosticListener<JavaFileObject> { |
| |
| boolean shouldRecordDiags; |
| |
| DiagnosticHandler(boolean shouldRecordDiags) { |
| this.shouldRecordDiags = shouldRecordDiags; |
| } |
| |
| public void report(Diagnostic<? extends JavaFileObject> diagnostic) { |
| if (shouldRecordDiags) |
| diags.add(diagnostic); |
| } |
| |
| } |
| } |