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