Thu, 06 Jun 2013 15:38:42 +0100
Merge
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 */
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 */
33 import com.sun.source.util.JavacTask;
34 import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
35 import com.sun.tools.javac.code.Type.MethodType;
36 import com.sun.tools.javac.util.JCDiagnostic;
38 import java.io.File;
39 import java.util.Set;
40 import java.util.Arrays;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
49 import javax.annotation.processing.AbstractProcessor;
50 import javax.annotation.processing.RoundEnvironment;
51 import javax.annotation.processing.SupportedAnnotationTypes;
52 import javax.lang.model.element.Element;
53 import javax.lang.model.element.TypeElement;
54 import javax.tools.Diagnostic;
55 import javax.tools.Diagnostic.Kind;
56 import javax.tools.DiagnosticListener;
57 import javax.tools.JavaCompiler;
58 import javax.tools.JavaFileObject;
59 import javax.tools.StandardJavaFileManager;
60 import javax.tools.ToolProvider;
62 import static javax.tools.StandardLocation.*;
64 public class ResolveHarness implements javax.tools.DiagnosticListener<JavaFileObject> {
66 static int nerrors = 0;
68 static final JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
69 static final StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
71 public static void main(String[] args) throws Exception {
72 fm.setLocation(SOURCE_PATH,
73 Arrays.asList(new File(System.getProperty("test.src"), "tests")));
74 for (JavaFileObject jfo : fm.list(SOURCE_PATH, "", Collections.singleton(JavaFileObject.Kind.SOURCE), true)) {
75 new ResolveHarness(jfo).check();
76 }
77 if (nerrors > 0) {
78 throw new AssertionError("Errors were found");
79 }
80 }
83 JavaFileObject jfo;
84 DiagnosticProcessor[] diagProcessors;
85 Map<ElementKey, Candidate> candidatesMap = new HashMap<ElementKey, Candidate>();
86 Set<String> declaredKeys = new HashSet<>();
87 List<Diagnostic<? extends JavaFileObject>> diags = new ArrayList<>();
88 List<ElementKey> seenCandidates = new ArrayList<>();
89 Map<String, String> predefTranslationMap = new HashMap<>();
91 protected ResolveHarness(JavaFileObject jfo) {
92 this.jfo = jfo;
93 this.diagProcessors = new DiagnosticProcessor[] {
94 new VerboseResolutionNoteProcessor(),
95 new VerboseDeferredInferenceNoteProcessor(),
96 new ErrorProcessor()
97 };
98 predefTranslationMap.put("+", "_plus");
99 predefTranslationMap.put("-", "_minus");
100 predefTranslationMap.put("~", "_not");
101 predefTranslationMap.put("++", "_plusplus");
102 predefTranslationMap.put("--", "_minusminus");
103 predefTranslationMap.put("!", "_bang");
104 predefTranslationMap.put("*", "_mul");
105 predefTranslationMap.put("/", "_div");
106 predefTranslationMap.put("%", "_mod");
107 predefTranslationMap.put("&", "_and");
108 predefTranslationMap.put("|", "_or");
109 predefTranslationMap.put("^", "_xor");
110 predefTranslationMap.put("<<", "_lshift");
111 predefTranslationMap.put(">>", "_rshift");
112 predefTranslationMap.put("<<<", "_lshiftshift");
113 predefTranslationMap.put(">>>", "_rshiftshift");
114 predefTranslationMap.put("<", "_lt");
115 predefTranslationMap.put(">", "_gt");
116 predefTranslationMap.put("<=", "_lteq");
117 predefTranslationMap.put(">=", "_gteq");
118 predefTranslationMap.put("==", "_eq");
119 predefTranslationMap.put("!=", "_neq");
120 predefTranslationMap.put("&&", "_andand");
121 predefTranslationMap.put("||", "_oror");
122 }
124 protected void check() throws Exception {
125 String[] options = {
126 "-XDshouldStopPolicy=ATTR",
127 "-XDverboseResolution=success,failure,applicable,inapplicable,deferred-inference,predef"
128 };
130 AbstractProcessor[] processors = { new ResolveCandidateFinder(), null };
132 @SuppressWarnings("unchecked")
133 DiagnosticListener<? super JavaFileObject>[] diagListeners =
134 new DiagnosticListener[] { new DiagnosticHandler(false), new DiagnosticHandler(true) };
136 for (int i = 0 ; i < options.length ; i ++) {
137 JavacTask ct = (JavacTask)comp.getTask(null, fm, diagListeners[i],
138 Arrays.asList(options[i]), null, Arrays.asList(jfo));
139 if (processors[i] != null) {
140 ct.setProcessors(Collections.singleton(processors[i]));
141 }
142 ct.analyze();
143 }
145 //check diags
146 for (Diagnostic<? extends JavaFileObject> diag : diags) {
147 for (DiagnosticProcessor proc : diagProcessors) {
148 if (proc.matches(diag)) {
149 proc.process(diag);
150 break;
151 }
152 }
153 }
154 //check all candidates have been used up
155 for (Map.Entry<ElementKey, Candidate> entry : candidatesMap.entrySet()) {
156 if (!seenCandidates.contains(entry.getKey())) {
157 error("Redundant @Candidate annotation on method " + entry.getKey().elem);
158 }
159 }
160 }
162 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
163 diags.add(diagnostic);
164 }
166 Candidate getCandidateAtPos(Element methodSym, long line, long col) {
167 Candidate c = candidatesMap.get(new ElementKey(methodSym));
168 if (c != null) {
169 Pos pos = c.pos();
170 if (!pos.userDefined() ||
171 (pos.line() == line && pos.col() == col)) {
172 seenCandidates.add(new ElementKey(methodSym));
173 return c;
174 }
175 } else {
176 error("Missing @Candidate annotation on method " + methodSym);
177 }
178 return null;
179 }
181 void checkSig(Candidate c, Element methodSym, MethodType mtype) {
182 if (c.sig().length() > 0 && !c.sig().equals(mtype.toString())) {
183 error("Inferred type mismatch for method: " + methodSym);
184 }
185 }
187 protected void error(String msg) {
188 nerrors++;
189 System.err.printf("Error occurred while checking file: %s\nreason: %s\n", jfo.getName(), msg);
190 }
192 /**
193 * Base class for diagnostic processor. It provides methods for matching and
194 * processing a given diagnostic object (overridden by subclasses).
195 */
196 abstract class DiagnosticProcessor {
198 List<String> codes;
199 Diagnostic.Kind kind;
201 public DiagnosticProcessor(Kind kind, String... codes) {
202 this.codes = Arrays.asList(codes);
203 this.kind = kind;
204 }
206 abstract void process(Diagnostic<? extends JavaFileObject> diagnostic);
208 boolean matches(Diagnostic<? extends JavaFileObject> diagnostic) {
209 return (codes.isEmpty() || codes.contains(diagnostic.getCode())) &&
210 diagnostic.getKind() == kind;
211 }
213 JCDiagnostic asJCDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
214 if (diagnostic instanceof JCDiagnostic) {
215 return (JCDiagnostic)diagnostic;
216 } else if (diagnostic instanceof DiagnosticSourceUnwrapper) {
217 return ((DiagnosticSourceUnwrapper)diagnostic).d;
218 } else {
219 throw new AssertionError("Cannot convert diagnostic to JCDiagnostic: " + diagnostic.getClass().getName());
220 }
221 }
223 List<JCDiagnostic> subDiagnostics(Diagnostic<? extends JavaFileObject> diagnostic) {
224 JCDiagnostic diag = asJCDiagnostic(diagnostic);
225 if (diag instanceof JCDiagnostic.MultilineDiagnostic) {
226 return ((JCDiagnostic.MultilineDiagnostic)diag).getSubdiagnostics();
227 } else {
228 throw new AssertionError("Cannot extract subdiagnostics: " + diag.getClass().getName());
229 }
230 }
231 }
233 /**
234 * Processor for verbose resolution notes generated by javac. The processor
235 * checks that the diagnostic is associated with a method declared by
236 * a class annotated with the special @TraceResolve marker annotation. If
237 * that's the case, all subdiagnostics (one for each resolution candidate)
238 * are checked against the corresponding @Candidate annotations, using
239 * a VerboseCandidateSubdiagProcessor.
240 */
241 class VerboseResolutionNoteProcessor extends DiagnosticProcessor {
243 VerboseResolutionNoteProcessor() {
244 super(Kind.NOTE,
245 "compiler.note.verbose.resolve.multi",
246 "compiler.note.verbose.resolve.multi.1");
247 }
249 @Override
250 void process(Diagnostic<? extends JavaFileObject> diagnostic) {
251 Element siteSym = getSiteSym(diagnostic);
252 if (siteSym.getSimpleName().length() != 0 &&
253 siteSym.getAnnotation(TraceResolve.class) == null) {
254 return;
255 }
256 int candidateIdx = 0;
257 for (JCDiagnostic d : subDiagnostics(diagnostic)) {
258 boolean isMostSpecific = candidateIdx++ == mostSpecific(diagnostic);
259 VerboseCandidateSubdiagProcessor subProc =
260 new VerboseCandidateSubdiagProcessor(isMostSpecific, phase(diagnostic), success(diagnostic));
261 if (subProc.matches(d)) {
262 subProc.process(d);
263 } else {
264 throw new AssertionError("Bad subdiagnostic: " + d.getCode());
265 }
266 }
267 }
269 Element getSiteSym(Diagnostic<? extends JavaFileObject> diagnostic) {
270 return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
271 }
273 int mostSpecific(Diagnostic<? extends JavaFileObject> diagnostic) {
274 return success(diagnostic) ?
275 (Integer)asJCDiagnostic(diagnostic).getArgs()[2] : -1;
276 }
278 boolean success(Diagnostic<? extends JavaFileObject> diagnostic) {
279 return diagnostic.getCode().equals("compiler.note.verbose.resolve.multi");
280 }
282 Phase phase(Diagnostic<? extends JavaFileObject> diagnostic) {
283 return Phase.fromString(asJCDiagnostic(diagnostic).getArgs()[3].toString());
284 }
285 }
287 /**
288 * Processor for verbose resolution subdiagnostic notes generated by javac.
289 * The processor checks that the details of the overload candidate
290 * match against the info contained in the corresponding @Candidate
291 * annotation (if any).
292 */
293 class VerboseCandidateSubdiagProcessor extends DiagnosticProcessor {
295 boolean mostSpecific;
296 Phase phase;
297 boolean success;
299 public VerboseCandidateSubdiagProcessor(boolean mostSpecific, Phase phase, boolean success) {
300 super(Kind.OTHER,
301 "compiler.misc.applicable.method.found",
302 "compiler.misc.applicable.method.found.1",
303 "compiler.misc.not.applicable.method.found");
304 this.mostSpecific = mostSpecific;
305 this.phase = phase;
306 this.success = success;
307 }
309 @Override
310 void process(Diagnostic<? extends JavaFileObject> diagnostic) {
311 Element methodSym = methodSym(diagnostic);
312 Candidate c = getCandidateAtPos(methodSym,
313 asJCDiagnostic(diagnostic).getLineNumber(),
314 asJCDiagnostic(diagnostic).getColumnNumber());
315 if (c == null) {
316 return; //nothing to check
317 }
319 if (c.applicable().length == 0 && c.mostSpecific()) {
320 error("Inapplicable method cannot be most specific " + methodSym);
321 }
323 if (isApplicable(diagnostic) != Arrays.asList(c.applicable()).contains(phase)) {
324 error("Invalid candidate's applicability " + methodSym);
325 }
327 if (success) {
328 for (Phase p : c.applicable()) {
329 if (phase.ordinal() < p.ordinal()) {
330 error("Invalid phase " + p + " on method " + methodSym);
331 }
332 }
333 }
335 if (Arrays.asList(c.applicable()).contains(phase)) { //applicable
336 if (c.mostSpecific() != mostSpecific) {
337 error("Invalid most specific value for method " + methodSym + " " + new ElementKey(methodSym).key);
338 }
339 MethodType mtype = getSig(diagnostic);
340 if (mtype != null) {
341 checkSig(c, methodSym, mtype);
342 }
343 }
344 }
346 boolean isApplicable(Diagnostic<? extends JavaFileObject> diagnostic) {
347 return !diagnostic.getCode().equals("compiler.misc.not.applicable.method.found");
348 }
350 Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
351 return (Element)asJCDiagnostic(diagnostic).getArgs()[1];
352 }
354 MethodType getSig(Diagnostic<? extends JavaFileObject> diagnostic) {
355 JCDiagnostic details = (JCDiagnostic)asJCDiagnostic(diagnostic).getArgs()[2];
356 if (details == null) {
357 return null;
358 } else if (details instanceof JCDiagnostic) {
359 return details.getCode().equals("compiler.misc.full.inst.sig") ?
360 (MethodType)details.getArgs()[0] : null;
361 } else {
362 throw new AssertionError("Bad diagnostic arg: " + details);
363 }
364 }
365 }
367 /**
368 * Processor for verbose deferred inference notes generated by javac. The
369 * processor checks that the inferred signature for a given generic method
370 * call corresponds to the one (if any) declared in the @Candidate annotation.
371 */
372 class VerboseDeferredInferenceNoteProcessor extends DiagnosticProcessor {
374 public VerboseDeferredInferenceNoteProcessor() {
375 super(Kind.NOTE, "compiler.note.deferred.method.inst");
376 }
378 @Override
379 void process(Diagnostic<? extends JavaFileObject> diagnostic) {
380 Element methodSym = methodSym(diagnostic);
381 Candidate c = getCandidateAtPos(methodSym,
382 asJCDiagnostic(diagnostic).getLineNumber(),
383 asJCDiagnostic(diagnostic).getColumnNumber());
384 MethodType sig = sig(diagnostic);
385 if (c != null && sig != null) {
386 checkSig(c, methodSym, sig);
387 }
388 }
390 Element methodSym(Diagnostic<? extends JavaFileObject> diagnostic) {
391 return (Element)asJCDiagnostic(diagnostic).getArgs()[0];
392 }
394 MethodType sig(Diagnostic<? extends JavaFileObject> diagnostic) {
395 return (MethodType)asJCDiagnostic(diagnostic).getArgs()[1];
396 }
397 }
399 /**
400 * Processor for all error diagnostics; if the error key is not declared in
401 * the test file header, the processor reports an error.
402 */
403 class ErrorProcessor extends DiagnosticProcessor {
405 public ErrorProcessor() {
406 super(Diagnostic.Kind.ERROR);
407 }
409 @Override
410 void process(Diagnostic<? extends JavaFileObject> diagnostic) {
411 if (!declaredKeys.contains(diagnostic.getCode())) {
412 error("Unexpected compilation error key '" + diagnostic.getCode() + "'");
413 }
414 }
415 }
417 @SupportedAnnotationTypes({"Candidate","TraceResolve"})
418 class ResolveCandidateFinder extends JavacTestingAbstractProcessor {
420 @Override
421 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
422 if (roundEnv.processingOver())
423 return true;
425 TypeElement traceResolveAnno = elements.getTypeElement("TraceResolve");
426 TypeElement candidateAnno = elements.getTypeElement("Candidate");
428 if (!annotations.contains(traceResolveAnno)) {
429 error("no @TraceResolve annotation found in test class");
430 }
432 if (!annotations.contains(candidateAnno)) {
433 error("no @candidate annotation found in test class");
434 }
436 for (Element elem: roundEnv.getElementsAnnotatedWith(traceResolveAnno)) {
437 TraceResolve traceResolve = elem.getAnnotation(TraceResolve.class);
438 declaredKeys.addAll(Arrays.asList(traceResolve.keys()));
439 }
441 for (Element elem: roundEnv.getElementsAnnotatedWith(candidateAnno)) {
442 candidatesMap.put(new ElementKey(elem), elem.getAnnotation(Candidate.class));
443 }
444 return true;
445 }
446 }
448 class ElementKey {
450 String key;
451 Element elem;
453 public ElementKey(Element elem) {
454 this.elem = elem;
455 this.key = computeKey(elem);
456 }
458 @Override
459 public boolean equals(Object obj) {
460 if (obj instanceof ElementKey) {
461 ElementKey other = (ElementKey)obj;
462 return other.key.equals(key);
463 }
464 return false;
465 }
467 @Override
468 public int hashCode() {
469 return key.hashCode();
470 }
472 String computeKey(Element e) {
473 StringBuilder buf = new StringBuilder();
474 if (predefTranslationMap.containsKey(e.getSimpleName().toString())) {
475 //predef element
476 buf.append("<predef>.");
477 String replacedName = predefTranslationMap.get(e.getSimpleName().toString());
478 buf.append(e.toString().replace(e.getSimpleName().toString(), replacedName));
479 } else if (e.getSimpleName().toString().startsWith("_")) {
480 buf.append("<predef>.");
481 buf.append(e.toString());
482 } else {
483 while (e != null) {
484 buf.append(e.toString());
485 e = e.getEnclosingElement();
486 }
487 buf.append(jfo.getName());
488 }
489 return buf.toString();
490 }
492 @Override
493 public String toString() {
494 return "Key{"+key+"}";
495 }
496 }
498 class DiagnosticHandler implements DiagnosticListener<JavaFileObject> {
500 boolean shouldRecordDiags;
502 DiagnosticHandler(boolean shouldRecordDiags) {
503 this.shouldRecordDiags = shouldRecordDiags;
504 }
506 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
507 if (shouldRecordDiags)
508 diags.add(diagnostic);
509 }
511 }
512 }