ResolveHarness.java revision 1465:b52a38d4536c
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 /tools/javac/lib 29 * @build JavacTestingAbstractProcessor ResolveHarness 30 * @run main ResolveHarness 31 */ 32 33import com.sun.source.util.JavacTask; 34import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper; 35import com.sun.tools.javac.code.Type.MethodType; 36import com.sun.tools.javac.util.JCDiagnostic; 37 38import java.io.File; 39import java.util.Set; 40import java.util.Arrays; 41import java.util.ArrayList; 42import java.util.Collections; 43import java.util.HashMap; 44import java.util.HashSet; 45import java.util.List; 46import java.util.Map; 47 48import javax.annotation.processing.AbstractProcessor; 49import javax.annotation.processing.RoundEnvironment; 50import javax.annotation.processing.SupportedAnnotationTypes; 51import javax.lang.model.element.Element; 52import javax.lang.model.element.TypeElement; 53import javax.tools.Diagnostic; 54import javax.tools.Diagnostic.Kind; 55import javax.tools.DiagnosticListener; 56import javax.tools.JavaCompiler; 57import javax.tools.JavaFileObject; 58import javax.tools.StandardJavaFileManager; 59import javax.tools.ToolProvider; 60 61import static javax.tools.StandardLocation.*; 62 63public 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} 476