1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/test/tools/javac/resolve/ResolveHarness.java Mon Oct 24 13:00:30 2011 +0100 1.3 @@ -0,0 +1,475 @@ 1.4 +/* 1.5 + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. 1.11 + * 1.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.15 + * version 2 for more details (a copy is included in the LICENSE file that 1.16 + * accompanied this code). 1.17 + * 1.18 + * You should have received a copy of the GNU General Public License version 1.19 + * 2 along with this work; if not, write to the Free Software Foundation, 1.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.21 + * 1.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.23 + * or visit www.oracle.com if you need additional information or have any 1.24 + * questions. 1.25 + */ 1.26 + 1.27 +/* 1.28 + * @test 1.29 + * @bug 7098660 1.30 + * @summary Write better overload resolution/inference tests 1.31 + * @library ../lib 1.32 + * @build JavacTestingAbstractProcessor ResolveHarness 1.33 + * @run main ResolveHarness 1.34 + */ 1.35 + 1.36 +import com.sun.source.util.JavacTask; 1.37 +import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper; 1.38 +import com.sun.tools.javac.code.Type.MethodType; 1.39 +import com.sun.tools.javac.util.JCDiagnostic; 1.40 + 1.41 +import java.io.File; 1.42 +import java.util.Set; 1.43 +import java.util.Arrays; 1.44 +import java.util.ArrayList; 1.45 +import java.util.Collections; 1.46 +import java.util.HashMap; 1.47 +import java.util.HashSet; 1.48 +import java.util.List; 1.49 +import java.util.Map; 1.50 + 1.51 +import javax.annotation.processing.AbstractProcessor; 1.52 +import javax.annotation.processing.RoundEnvironment; 1.53 +import javax.annotation.processing.SupportedAnnotationTypes; 1.54 +import javax.lang.model.element.Element; 1.55 +import javax.lang.model.element.TypeElement; 1.56 +import javax.tools.Diagnostic; 1.57 +import javax.tools.Diagnostic.Kind; 1.58 +import javax.tools.DiagnosticListener; 1.59 +import javax.tools.JavaCompiler; 1.60 +import javax.tools.JavaFileObject; 1.61 +import javax.tools.StandardJavaFileManager; 1.62 +import javax.tools.ToolProvider; 1.63 + 1.64 +import static javax.tools.StandardLocation.*; 1.65 + 1.66 +public class ResolveHarness implements javax.tools.DiagnosticListener<JavaFileObject> { 1.67 + 1.68 + static int nerrors = 0; 1.69 + 1.70 + static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); 1.71 + static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); 1.72 + 1.73 + public static void main(String[] args) throws Exception { 1.74 + fm.setLocation(SOURCE_PATH, 1.75 + Arrays.asList(new File(System.getProperty("test.src"), "tests"))); 1.76 + for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) { 1.77 + new ResolveHarness(jfo).check(); 1.78 + } 1.79 + if (nerrors > 0) { 1.80 + throw new AssertionError("Errors were found"); 1.81 + } 1.82 + } 1.83 + 1.84 + 1.85 + JavaFileObject jfo; 1.86 + DiagnosticProcessor[] diagProcessors; 1.87 + Map<ElementKey, Candidate> candidatesMap = new HashMap<ElementKey, Candidate>(); 1.88 + Set<String> declaredKeys = new HashSet<>(); 1.89 + List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>(); 1.90 + List<ElementKey> seenCandidates = new ArrayList<>(); 1.91 + 1.92 + protected ResolveHarness(JavaFileObject jfo) { 1.93 + this.jfo = jfo; 1.94 + this.diagProcessors = new DiagnosticProcessor[] { 1.95 + new VerboseResolutionNoteProcessor(), 1.96 + new VerboseDeferredInferenceNoteProcessor(), 1.97 + new ErrorProcessor() 1.98 + }; 1.99 + } 1.100 + 1.101 + protected void check() throws Exception { 1.102 + String[] options = { 1.103 + "-XDshouldStopPolicy=ATTR", 1.104 + "-XDverboseResolution=success,failure,applicable,inapplicable,deferred-inference" 1.105 + }; 1.106 + 1.107 + AbstractProcessor[] processors = { new ResolveCandidateFinder(), null }; 1.108 + 1.109 + @SuppressWarnings("unchecked") 1.110 + DiagnosticListener<? super JavaFileObject>[] diagListeners = 1.111 + new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) }; 1.112 + 1.113 + for (int i = 0 ; i < options.length ; i ++) { 1.114 + JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i], 1.115 + Arrays.asList(options[i]), null, Arrays.asList(jfo)); 1.116 + if (processors[i] != null) { 1.117 + ct.setProcessors(Collections.singleton(processors[i])); 1.118 + } 1.119 + ct.analyze(); 1.120 + } 1.121 + 1.122 + //check diags 1.123 + for (Diagnostic<? extends JavaFileObject> diag : diags) { 1.124 + for (DiagnosticProcessor proc : diagProcessors) { 1.125 + if (proc.matches(diag)) { 1.126 + proc.process(diag); 1.127 + break; 1.128 + } 1.129 + } 1.130 + } 1.131 + //check all candidates have been used up 1.132 + for (Map.Entry<ElementKey, Candidate> entry : candidatesMap.entrySet()) { 1.133 + if (!seenCandidates.contains(entry.getKey())) { 1.134 + error("Redundant @Candidate annotation on method " + entry.getKey().elem); 1.135 + } 1.136 + } 1.137 + } 1.138 + 1.139 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 1.140 + diags.add(diagnostic); 1.141 + } 1.142 + 1.143 + Candidate getCandidateAtPos(Element methodSym, long line, long col) { 1.144 + Candidate c = candidatesMap.get(new ElementKey(methodSym)); 1.145 + if (c != null) { 1.146 + Pos pos = c.pos(); 1.147 + if (!pos.userDefined() || 1.148 + (pos.line() == line && pos.col() == col)) { 1.149 + seenCandidates.add(new ElementKey(methodSym)); 1.150 + return c; 1.151 + } 1.152 + } else { 1.153 + error("Missing @Candidate annotation on method " + methodSym); 1.154 + } 1.155 + return null; 1.156 + } 1.157 + 1.158 + void checkSig(Candidate c, Element methodSym, MethodType mtype) { 1.159 + if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) { 1.160 + error("Inferred type mismatch for method: " + methodSym); 1.161 + } 1.162 + } 1.163 + 1.164 + protected void error(String msg) { 1.165 + nerrors++; 1.166 + System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg); 1.167 + } 1.168 + 1.169 + /** 1.170 + * Base class for diagnostic processor. It provides methods for matching and 1.171 + * processing a given diagnostic object (overridden by subclasses). 1.172 + */ 1.173 + abstract class DiagnosticProcessor { 1.174 + 1.175 + List<String> codes; 1.176 + Diagnostic.Kind kind; 1.177 + 1.178 + public DiagnosticProcessor(Kind kind, String... codes) { 1.179 + this.codes = Arrays.asList(codes); 1.180 + this.kind = kind; 1.181 + } 1.182 + 1.183 + abstract void process(Diagnostic<? extends JavaFileObject> diagnostic); 1.184 + 1.185 + boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) { 1.186 + return (codes.isEmpty() || codes.contains(diagnostic.getCode())) && 1.187 + diagnostic.getKind() == kind; 1.188 + } 1.189 + 1.190 + JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) { 1.191 + if (diagnostic instanceof JCDiagnostic) { 1.192 + return (JCDiagnostic)diagnostic; 1.193 + } else if (diagnostic instanceof DiagnosticSourceUnwrapper) { 1.194 + return ((DiagnosticSourceUnwrapper)diagnostic).d; 1.195 + } else { 1.196 + throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName()); 1.197 + } 1.198 + } 1.199 + 1.200 + List<JCDiagnostic> subDiagnostics(Diagnostic<? extends JavaFileObject> diagnostic) { 1.201 + JCDiagnostic diag = asJCDiagnostic(diagnostic); 1.202 + if (diag instanceof JCDiagnostic.MultilineDiagnostic) { 1.203 + return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics(); 1.204 + } else { 1.205 + throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName()); 1.206 + } 1.207 + } 1.208 + } 1.209 + 1.210 + /** 1.211 + * Processor for verbose resolution notes generated by javac. The processor 1.212 + * checks that the diagnostic is associated with a method declared by 1.213 + * a class annotated with the special @TraceResolve marker annotation. If 1.214 + * that's the case, all subdiagnostics (one for each resolution candidate) 1.215 + * are checked against the corresponding @Candidate annotations, using 1.216 + * a VerboseCandidateSubdiagProcessor. 1.217 + */ 1.218 + class VerboseResolutionNoteProcessor extends DiagnosticProcessor { 1.219 + 1.220 + VerboseResolutionNoteProcessor() { 1.221 + super(Kind.NOTE, 1.222 + "compiler.note.verbose.resolve.multi", 1.223 + "compiler.note.verbose.resolve.multi.1"); 1.224 + } 1.225 + 1.226 + @Override 1.227 + void process(Diagnostic<? extends JavaFileObject> diagnostic) { 1.228 + Element siteSym = getSiteSym(diagnostic); 1.229 + if (siteSym.getAnnotation(TraceResolve.class) == null) { 1.230 + return; 1.231 + } 1.232 + int candidateIdx = 0; 1.233 + for (JCDiagnostic d : subDiagnostics(diagnostic)) { 1.234 + boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic); 1.235 + VerboseCandidateSubdiagProcessor subProc = 1.236 + new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic)); 1.237 + if (subProc.matches(d)) { 1.238 + subProc.process(d); 1.239 + } else { 1.240 + throw new AssertionError("Bad subdiagnostic: " + d.getCode()); 1.241 + } 1.242 + } 1.243 + } 1.244 + 1.245 + Element getSiteSym(Diagnostic<? extends JavaFileObject> diagnostic) { 1.246 + return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; 1.247 + } 1.248 + 1.249 + int mostSpecific(Diagnostic<? extends JavaFileObject> diagnostic) { 1.250 + return success(diagnostic) ? 1.251 + (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1; 1.252 + } 1.253 + 1.254 + boolean success(Diagnostic<? extends JavaFileObject> diagnostic) { 1.255 + return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi"); 1.256 + } 1.257 + 1.258 + Phase phase(Diagnostic<? extends JavaFileObject> diagnostic) { 1.259 + return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString()); 1.260 + } 1.261 + } 1.262 + 1.263 + /** 1.264 + * Processor for verbose resolution subdiagnostic notes generated by javac. 1.265 + * The processor checks that the details of the overload candidate 1.266 + * match against the info contained in the corresponding @Candidate 1.267 + * annotation (if any). 1.268 + */ 1.269 + class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor { 1.270 + 1.271 + boolean mostSpecific; 1.272 + Phase phase; 1.273 + boolean success; 1.274 + 1.275 + public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) { 1.276 + super(Kind.OTHER, 1.277 + "compiler.misc.applicable.method.found", 1.278 + "compiler.misc.applicable.method.found.1", 1.279 + "compiler.misc.not.applicable.method.found"); 1.280 + this.mostSpecific = mostSpecific; 1.281 + this.phase = phase; 1.282 + this.success = success; 1.283 + } 1.284 + 1.285 + @Override 1.286 + void process(Diagnostic<? extends JavaFileObject> diagnostic) { 1.287 + Element methodSym = methodSym(diagnostic); 1.288 + Candidate c = getCandidateAtPos(methodSym, 1.289 + asJCDiagnostic(diagnostic).getLineNumber(), 1.290 + asJCDiagnostic(diagnostic).getColumnNumber()); 1.291 + if (c == null) { 1.292 + return; //nothing to check 1.293 + } 1.294 + 1.295 + if (c.applicable().length == 0 && c.mostSpecific()) { 1.296 + error("Inapplicable method cannot be most specific " + methodSym); 1.297 + } 1.298 + 1.299 + if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) { 1.300 + error("Invalid candidate's applicability " + methodSym); 1.301 + } 1.302 + 1.303 + if (success) { 1.304 + for (Phase p : c.applicable()) { 1.305 + if (phase.ordinal() < p.ordinal()) { 1.306 + error("Invalid phase " + p + " on method " + methodSym); 1.307 + } 1.308 + } 1.309 + } 1.310 + 1.311 + if (Arrays.asList(c.applicable()).contains(phase)) { //applicable 1.312 + if (c.mostSpecific() != mostSpecific) { 1.313 + error("Invalid most specific value for method " + methodSym); 1.314 + } 1.315 + MethodType mtype = getSig(diagnostic); 1.316 + if (mtype != null) { 1.317 + checkSig(c, methodSym, mtype); 1.318 + } 1.319 + } 1.320 + } 1.321 + 1.322 + boolean isApplicable(Diagnostic<? extends JavaFileObject> diagnostic) { 1.323 + return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found"); 1.324 + } 1.325 + 1.326 + Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) { 1.327 + return (Element)asJCDiagnostic(diagnostic).getArgs()[1]; 1.328 + } 1.329 + 1.330 + MethodType getSig(Diagnostic<? extends JavaFileObject> diagnostic) { 1.331 + JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2]; 1.332 + if (details == null) { 1.333 + return null; 1.334 + } else if (details instanceof JCDiagnostic) { 1.335 + return details.getCode().equals("compiler.misc.full.inst.sig") ? 1.336 + (MethodType)details.getArgs()[0] : null; 1.337 + } else { 1.338 + throw new AssertionError("Bad diagnostic arg: " + details); 1.339 + } 1.340 + } 1.341 + } 1.342 + 1.343 + /** 1.344 + * Processor for verbose deferred inference notes generated by javac. The 1.345 + * processor checks that the inferred signature for a given generic method 1.346 + * call corresponds to the one (if any) declared in the @Candidate annotation. 1.347 + */ 1.348 + class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor { 1.349 + 1.350 + public VerboseDeferredInferenceNoteProcessor() { 1.351 + super(Kind.NOTE, "compiler.note.deferred.method.inst"); 1.352 + } 1.353 + 1.354 + @Override 1.355 + void process(Diagnostic<? extends JavaFileObject> diagnostic) { 1.356 + Element methodSym = methodSym(diagnostic); 1.357 + Candidate c = getCandidateAtPos(methodSym, 1.358 + asJCDiagnostic(diagnostic).getLineNumber(), 1.359 + asJCDiagnostic(diagnostic).getColumnNumber()); 1.360 + MethodType sig = sig(diagnostic); 1.361 + if (c != null && sig != null) { 1.362 + checkSig(c, methodSym, sig); 1.363 + } 1.364 + } 1.365 + 1.366 + Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) { 1.367 + return (Element)asJCDiagnostic(diagnostic).getArgs()[0]; 1.368 + } 1.369 + 1.370 + MethodType sig(Diagnostic<? extends JavaFileObject> diagnostic) { 1.371 + return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1]; 1.372 + } 1.373 + } 1.374 + 1.375 + /** 1.376 + * Processor for all error diagnostics; if the error key is not declared in 1.377 + * the test file header, the processor reports an error. 1.378 + */ 1.379 + class ErrorProcessor extends DiagnosticProcessor { 1.380 + 1.381 + public ErrorProcessor() { 1.382 + super(Diagnostic.Kind.ERROR); 1.383 + } 1.384 + 1.385 + @Override 1.386 + void process(Diagnostic<? extends JavaFileObject> diagnostic) { 1.387 + if (!declaredKeys.contains(diagnostic.getCode())) { 1.388 + error("Unexpected compilation error key '" + diagnostic.getCode() + "'"); 1.389 + } 1.390 + } 1.391 + } 1.392 + 1.393 + @SupportedAnnotationTypes({"Candidate","TraceResolve"}) 1.394 + class ResolveCandidateFinder extends JavacTestingAbstractProcessor { 1.395 + 1.396 + @Override 1.397 + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1.398 + if (roundEnv.processingOver()) 1.399 + return true; 1.400 + 1.401 + TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve"); 1.402 + TypeElement candidateAnno = elements.getTypeElement("Candidate"); 1.403 + 1.404 + if (!annotations.contains(traceResolveAnno)) { 1.405 + error("no @TraceResolve annotation found in test class"); 1.406 + } 1.407 + 1.408 + if (!annotations.contains(candidateAnno)) { 1.409 + error("no @candidate annotation found in test class"); 1.410 + } 1.411 + 1.412 + for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) { 1.413 + TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class); 1.414 + declaredKeys.addAll(Arrays.asList(traceResolve.keys())); 1.415 + } 1.416 + 1.417 + for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) { 1.418 + candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class)); 1.419 + } 1.420 + return true; 1.421 + } 1.422 + } 1.423 + 1.424 + class ElementKey { 1.425 + 1.426 + String key; 1.427 + Element elem; 1.428 + 1.429 + public ElementKey(Element elem) { 1.430 + this.elem = elem; 1.431 + this.key = computeKey(elem); 1.432 + } 1.433 + 1.434 + @Override 1.435 + public boolean equals(Object obj) { 1.436 + if (obj instanceof ElementKey) { 1.437 + ElementKey other = (ElementKey)obj; 1.438 + return other.key.equals(key); 1.439 + } 1.440 + return false; 1.441 + } 1.442 + 1.443 + @Override 1.444 + public int hashCode() { 1.445 + return key.hashCode(); 1.446 + } 1.447 + 1.448 + String computeKey(Element e) { 1.449 + StringBuilder buf = new StringBuilder(); 1.450 + while (e != null) { 1.451 + buf.append(e.toString()); 1.452 + e = e.getEnclosingElement(); 1.453 + } 1.454 + buf.append(jfo.getName()); 1.455 + return buf.toString(); 1.456 + } 1.457 + 1.458 + @Override 1.459 + public String toString() { 1.460 + return "Key{"+key+"}"; 1.461 + } 1.462 + } 1.463 + 1.464 + class DiagnosticHandler implements DiagnosticListener<JavaFileObject> { 1.465 + 1.466 + boolean shouldRecordDiags; 1.467 + 1.468 + DiagnosticHandler(boolean shouldRecordDiags) { 1.469 + this.shouldRecordDiags = shouldRecordDiags; 1.470 + } 1.471 + 1.472 + public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 1.473 + if (shouldRecordDiags) 1.474 + diags.add(diagnostic); 1.475 + } 1.476 + 1.477 + } 1.478 +}