Thu, 10 Jun 2010 09:29:23 +0100
6945418: Project Coin: Simplified Varargs Method Invocation
Summary: Add new mandatory warning for unsafe vararg method declaration. Warning can be suppressed as usual (@SuppressWarnings("varargs")/-Xlint:-varargs)
Reviewed-by: jjg, darcy
1 /*
2 * Copyright (c) 2009, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.sun.tools.javac.util;
27 import java.util.EnumSet;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.Locale;
31 import java.util.Map;
33 import com.sun.tools.javac.code.Kinds;
34 import com.sun.tools.javac.code.Printer;
35 import com.sun.tools.javac.code.Symbol;
36 import com.sun.tools.javac.code.Symbol.*;
37 import com.sun.tools.javac.code.Symtab;
38 import com.sun.tools.javac.code.Type;
39 import com.sun.tools.javac.code.Type.*;
40 import com.sun.tools.javac.code.Types;
42 import static com.sun.tools.javac.code.TypeTags.*;
43 import static com.sun.tools.javac.code.Flags.*;
44 import static com.sun.tools.javac.util.LayoutCharacters.*;
45 import static com.sun.tools.javac.util.RichDiagnosticFormatter.RichConfiguration.*;
47 /**
48 * A rich diagnostic formatter is a formatter that provides better integration
49 * with javac's type system. A diagostic is first preprocessed in order to keep
50 * track of each types/symbols in it; after these informations are collected,
51 * the diagnostic is rendered using a standard formatter, whose type/symbol printer
52 * has been replaced by a more refined version provided by this rich formatter.
53 * The rich formatter currently enables three different features: (i) simple class
54 * names - that is class names are displayed used a non qualified name (thus
55 * omitting package info) whenever possible - (ii) where clause list - a list of
56 * additional subdiagnostics that provide specific info about type-variables,
57 * captured types, intersection types that occur in the diagnostic that is to be
58 * formatted and (iii) type-variable disambiguation - when the diagnostic refers
59 * to two different type-variables with the same name, their representation is
60 * disambiguated by appending an index to the type variable name.
61 *
62 * <p><b>This is NOT part of any API supported by Sun Microsystems.
63 * If you write code that depends on this, you do so at your own risk.
64 * This code and its internal interfaces are subject to change or
65 * deletion without notice.</b>
66 */
67 public class RichDiagnosticFormatter extends
68 ForwardingDiagnosticFormatter<JCDiagnostic, AbstractDiagnosticFormatter> {
70 final Symtab syms;
71 final Types types;
72 final JCDiagnostic.Factory diags;
73 final JavacMessages messages;
75 /* name simplifier used by this formatter */
76 protected ClassNameSimplifier nameSimplifier;
78 /* type/symbol printer used by this formatter */
79 private RichPrinter printer;
81 /* map for keeping track of a where clause associated to a given type */
82 Map<WhereClauseKind, Map<Type, JCDiagnostic>> whereClauses;
84 /** Get the DiagnosticFormatter instance for this context. */
85 public static RichDiagnosticFormatter instance(Context context) {
86 RichDiagnosticFormatter instance = context.get(RichDiagnosticFormatter.class);
87 if (instance == null)
88 instance = new RichDiagnosticFormatter(context);
89 return instance;
90 }
92 protected RichDiagnosticFormatter(Context context) {
93 super((AbstractDiagnosticFormatter)Log.instance(context).getDiagnosticFormatter());
94 setRichPrinter(new RichPrinter());
95 this.syms = Symtab.instance(context);
96 this.diags = JCDiagnostic.Factory.instance(context);
97 this.types = Types.instance(context);
98 this.messages = JavacMessages.instance(context);
99 whereClauses = new LinkedHashMap<WhereClauseKind, Map<Type, JCDiagnostic>>();
100 configuration = new RichConfiguration(Options.instance(context), formatter);
101 for (WhereClauseKind kind : WhereClauseKind.values())
102 whereClauses.put(kind, new LinkedHashMap<Type, JCDiagnostic>());
103 }
105 @Override
106 public String format(JCDiagnostic diag, Locale l) {
107 StringBuilder sb = new StringBuilder();
108 nameSimplifier = new ClassNameSimplifier();
109 for (WhereClauseKind kind : WhereClauseKind.values())
110 whereClauses.get(kind).clear();
111 preprocessDiagnostic(diag);
112 sb.append(formatter.format(diag, l));
113 if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
114 List<JCDiagnostic> clauses = getWhereClauses();
115 String indent = formatter.isRaw() ? "" :
116 formatter.indentString(DetailsInc);
117 for (JCDiagnostic d : clauses) {
118 String whereClause = formatter.format(d, l);
119 if (whereClause.length() > 0) {
120 sb.append('\n' + indent + whereClause);
121 }
122 }
123 }
124 return sb.toString();
125 }
127 /**
128 * Sets the type/symbol printer used by this formatter.
129 * @param printer the rich printer to be set
130 */
131 protected void setRichPrinter(RichPrinter printer) {
132 this.printer = printer;
133 formatter.setPrinter(printer);
134 }
136 /**
137 * Gets the type/symbol printer used by this formatter.
138 * @return type/symbol rich printer
139 */
140 protected RichPrinter getRichPrinter() {
141 return printer;
142 }
144 /**
145 * Preprocess a given diagnostic by looking both into its arguments and into
146 * its subdiagnostics (if any). This preprocessing is responsible for
147 * generating info corresponding to features like where clauses, name
148 * simplification, etc.
149 *
150 * @param diag the diagnostic to be preprocessed
151 */
152 protected void preprocessDiagnostic(JCDiagnostic diag) {
153 for (Object o : diag.getArgs()) {
154 if (o != null) {
155 preprocessArgument(o);
156 }
157 }
158 if (diag.isMultiline()) {
159 for (JCDiagnostic d : diag.getSubdiagnostics())
160 preprocessDiagnostic(d);
161 }
162 }
164 /**
165 * Preprocess a diagnostic argument. A type/symbol argument is
166 * preprocessed by specialized type/symbol preprocessors.
167 *
168 * @param arg the argument to be translated
169 */
170 protected void preprocessArgument(Object arg) {
171 if (arg instanceof Type) {
172 preprocessType((Type)arg);
173 }
174 else if (arg instanceof Symbol) {
175 preprocessSymbol((Symbol)arg);
176 }
177 else if (arg instanceof JCDiagnostic) {
178 preprocessDiagnostic((JCDiagnostic)arg);
179 }
180 else if (arg instanceof Iterable<?>) {
181 for (Object o : (Iterable<?>)arg) {
182 preprocessArgument(o);
183 }
184 }
185 }
187 /**
188 * Build a list of multiline diagnostics containing detailed info about
189 * type-variables, captured types, and intersection types
190 *
191 * @return where clause list
192 */
193 protected List<JCDiagnostic> getWhereClauses() {
194 List<JCDiagnostic> clauses = List.nil();
195 for (WhereClauseKind kind : WhereClauseKind.values()) {
196 List<JCDiagnostic> lines = List.nil();
197 for (Map.Entry<Type, JCDiagnostic> entry : whereClauses.get(kind).entrySet()) {
198 lines = lines.prepend(entry.getValue());
199 }
200 if (!lines.isEmpty()) {
201 String key = kind.key();
202 if (lines.size() > 1)
203 key += ".1";
204 JCDiagnostic d = diags.fragment(key, whereClauses.get(kind).keySet());
205 d = new JCDiagnostic.MultilineDiagnostic(d, lines.reverse());
206 clauses = clauses.prepend(d);
207 }
208 }
209 return clauses.reverse();
210 }
212 private int indexOf(Type type, WhereClauseKind kind) {
213 int index = 1;
214 for (Type t : whereClauses.get(kind).keySet()) {
215 if (t.tsym == type.tsym) {
216 return index;
217 }
218 if (kind != WhereClauseKind.TYPEVAR ||
219 t.toString().equals(type.toString())) {
220 index++;
221 }
222 }
223 return -1;
224 }
226 private boolean unique(TypeVar typevar) {
227 int found = 0;
228 for (Type t : whereClauses.get(WhereClauseKind.TYPEVAR).keySet()) {
229 if (t.toString().equals(typevar.toString())) {
230 found++;
231 }
232 }
233 if (found < 1)
234 throw new AssertionError("Missing type variable in where clause " + typevar);
235 return found == 1;
236 }
237 //where
238 /**
239 * This enum defines all posssible kinds of where clauses that can be
240 * attached by a rich diagnostic formatter to a given diagnostic
241 */
242 enum WhereClauseKind {
244 /** where clause regarding a type variable */
245 TYPEVAR("where.description.typevar"),
246 /** where clause regarding a captured type */
247 CAPTURED("where.description.captured"),
248 /** where clause regarding an intersection type */
249 INTERSECTION("where.description.intersection");
251 /** resource key for this where clause kind */
252 private String key;
254 WhereClauseKind(String key) {
255 this.key = key;
256 }
258 String key() {
259 return key;
260 }
261 }
263 // <editor-fold defaultstate="collapsed" desc="name simplifier">
264 /**
265 * A name simplifier keeps track of class names usages in order to determine
266 * whether a class name can be compacted or not. Short names are not used
267 * if a conflict is detected, e.g. when two classes with the same simple
268 * name belong to different packages - in this case the formatter reverts
269 * to fullnames as compact names might lead to a confusing diagnostic.
270 */
271 protected class ClassNameSimplifier {
273 /* table for keeping track of all short name usages */
274 Map<Name, List<Symbol>> nameClashes = new HashMap<Name, List<Symbol>>();
276 /**
277 * Add a name usage to the simplifier's internal cache
278 */
279 protected void addUsage(Symbol sym) {
280 Name n = sym.getSimpleName();
281 List<Symbol> conflicts = nameClashes.get(n);
282 if (conflicts == null) {
283 conflicts = List.nil();
284 }
285 if (!conflicts.contains(sym))
286 nameClashes.put(n, conflicts.append(sym));
287 }
289 public String simplify(Symbol s) {
290 String name = s.getQualifiedName().toString();
291 if (!s.type.isCompound()) {
292 List<Symbol> conflicts = nameClashes.get(s.getSimpleName());
293 if (conflicts == null ||
294 (conflicts.size() == 1 &&
295 conflicts.contains(s))) {
296 List<Name> l = List.nil();
297 Symbol s2 = s;
298 while (s2.type.getEnclosingType().tag == CLASS
299 && s2.owner.kind == Kinds.TYP) {
300 l = l.prepend(s2.getSimpleName());
301 s2 = s2.owner;
302 }
303 l = l.prepend(s2.getSimpleName());
304 StringBuilder buf = new StringBuilder();
305 String sep = "";
306 for (Name n2 : l) {
307 buf.append(sep);
308 buf.append(n2);
309 sep = ".";
310 }
311 name = buf.toString();
312 }
313 }
314 return name;
315 }
316 };
317 // </editor-fold>
319 // <editor-fold defaultstate="collapsed" desc="rich printer">
320 /**
321 * Enhanced type/symbol printer that provides support for features like simple names
322 * and type variable disambiguation. This enriched printer exploits the info
323 * discovered during type/symbol preprocessing. This printer is set on the delegate
324 * formatter so that rich type/symbol info can be properly rendered.
325 */
326 protected class RichPrinter extends Printer {
328 @Override
329 public String localize(Locale locale, String key, Object... args) {
330 return formatter.localize(locale, key, args);
331 }
333 @Override
334 public String capturedVarId(CapturedType t, Locale locale) {
335 return indexOf(t, WhereClauseKind.CAPTURED) + "";
336 }
338 @Override
339 public String visitType(Type t, Locale locale) {
340 String s = super.visitType(t, locale);
341 if (t == syms.botType)
342 s = localize(locale, "compiler.misc.type.null");
343 return s;
344 }
346 @Override
347 public String visitCapturedType(CapturedType t, Locale locale) {
348 if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
349 return localize(locale,
350 "compiler.misc.captured.type",
351 indexOf(t, WhereClauseKind.CAPTURED));
352 }
353 else
354 return super.visitCapturedType(t, locale);
355 }
357 @Override
358 public String visitClassType(ClassType t, Locale locale) {
359 if (t.isCompound() &&
360 getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) {
361 return localize(locale,
362 "compiler.misc.intersection.type",
363 indexOf(t, WhereClauseKind.INTERSECTION));
364 }
365 else
366 return super.visitClassType(t, locale);
367 }
369 @Override
370 protected String className(ClassType t, boolean longform, Locale locale) {
371 Symbol sym = t.tsym;
372 if (sym.name.length() == 0 ||
373 !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
374 return super.className(t, longform, locale);
375 }
376 else if (longform)
377 return nameSimplifier.simplify(sym).toString();
378 else
379 return sym.name.toString();
380 }
382 @Override
383 public String visitTypeVar(TypeVar t, Locale locale) {
384 if (unique(t) ||
385 !getConfiguration().isEnabled(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES)) {
386 return t.toString();
387 }
388 else {
389 return localize(locale,
390 "compiler.misc.type.var",
391 t.toString(), indexOf(t, WhereClauseKind.TYPEVAR));
392 }
393 }
395 @Override
396 protected String printMethodArgs(List<Type> args, boolean varArgs, Locale locale) {
397 return super.printMethodArgs(args, varArgs, locale);
398 }
400 @Override
401 public String visitClassSymbol(ClassSymbol s, Locale locale) {
402 String name = nameSimplifier.simplify(s);
403 if (name.length() == 0 ||
404 !getConfiguration().isEnabled(RichFormatterFeature.SIMPLE_NAMES)) {
405 return super.visitClassSymbol(s, locale);
406 }
407 else {
408 return name;
409 }
410 }
412 @Override
413 public String visitMethodSymbol(MethodSymbol s, Locale locale) {
414 String ownerName = visit(s.owner, locale);
415 if ((s.flags() & BLOCK) != 0) {
416 return ownerName;
417 } else {
418 String ms = (s.name == s.name.table.names.init)
419 ? ownerName
420 : s.name.toString();
421 if (s.type != null) {
422 if (s.type.tag == FORALL) {
423 ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms;
424 }
425 ms += "(" + printMethodArgs(
426 s.type.getParameterTypes(),
427 (s.flags() & VARARGS) != 0,
428 locale) + ")";
429 }
430 return ms;
431 }
432 }
433 };
434 // </editor-fold>
436 // <editor-fold defaultstate="collapsed" desc="type scanner">
437 /**
438 * Preprocess a given type looking for (i) additional info (where clauses) to be
439 * added to the main diagnostic (ii) names to be compacted.
440 */
441 protected void preprocessType(Type t) {
442 typePreprocessor.visit(t);
443 }
444 //where
445 protected Types.UnaryVisitor<Void> typePreprocessor =
446 new Types.UnaryVisitor<Void>() {
448 public Void visit(List<Type> ts) {
449 for (Type t : ts)
450 visit(t);
451 return null;
452 }
454 @Override
455 public Void visitForAll(ForAll t, Void ignored) {
456 visit(t.tvars);
457 visit(t.qtype);
458 return null;
459 }
461 @Override
462 public Void visitMethodType(MethodType t, Void ignored) {
463 visit(t.argtypes);
464 visit(t.restype);
465 return null;
466 }
468 @Override
469 public Void visitErrorType(ErrorType t, Void ignored) {
470 Type ot = t.getOriginalType();
471 if (ot != null)
472 visit(ot);
473 return null;
474 }
476 @Override
477 public Void visitArrayType(ArrayType t, Void ignored) {
478 visit(t.elemtype);
479 return null;
480 }
482 @Override
483 public Void visitWildcardType(WildcardType t, Void ignored) {
484 visit(t.type);
485 return null;
486 }
488 public Void visitType(Type t, Void ignored) {
489 return null;
490 }
492 @Override
493 public Void visitCapturedType(CapturedType t, Void ignored) {
494 if (indexOf(t, WhereClauseKind.CAPTURED) == -1) {
495 String suffix = t.lower == syms.botType ? ".1" : "";
496 JCDiagnostic d = diags.fragment("where.captured"+ suffix, t, t.bound, t.lower, t.wildcard);
497 whereClauses.get(WhereClauseKind.CAPTURED).put(t, d);
498 visit(t.wildcard);
499 visit(t.lower);
500 visit(t.bound);
501 }
502 return null;
503 }
505 @Override
506 public Void visitClassType(ClassType t, Void ignored) {
507 if (t.isCompound()) {
508 if (indexOf(t, WhereClauseKind.INTERSECTION) == -1) {
509 Type supertype = types.supertype(t);
510 List<Type> interfaces = types.interfaces(t);
511 JCDiagnostic d = diags.fragment("where.intersection", t, interfaces.prepend(supertype));
512 whereClauses.get(WhereClauseKind.INTERSECTION).put(t, d);
513 visit(supertype);
514 visit(interfaces);
515 }
516 }
517 nameSimplifier.addUsage(t.tsym);
518 visit(t.getTypeArguments());
519 if (t.getEnclosingType() != Type.noType)
520 visit(t.getEnclosingType());
521 return null;
522 }
524 @Override
525 public Void visitTypeVar(TypeVar t, Void ignored) {
526 if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) {
527 //access the bound type and skip error types
528 Type bound = t.bound;
529 while ((bound instanceof ErrorType))
530 bound = ((ErrorType)bound).getOriginalType();
531 //retrieve the bound list - if the type variable
532 //has not been attributed the bound is not set
533 List<Type> bounds = bound != null ?
534 types.getBounds(t) :
535 List.<Type>nil();
537 nameSimplifier.addUsage(t.tsym);
539 boolean boundErroneous = bounds.head == null ||
540 bounds.head.tag == NONE ||
541 bounds.head.tag == ERROR;
544 JCDiagnostic d = diags.fragment("where.typevar" +
545 (boundErroneous ? ".1" : ""), t, bounds,
546 Kinds.kindName(t.tsym.location()), t.tsym.location());
547 whereClauses.get(WhereClauseKind.TYPEVAR).put(t, d);
548 symbolPreprocessor.visit(t.tsym.location(), null);
549 visit(bounds);
550 }
551 return null;
552 }
553 };
554 // </editor-fold>
556 // <editor-fold defaultstate="collapsed" desc="symbol scanner">
557 /**
558 * Preprocess a given symbol looking for (i) additional info (where clauses) to be
559 * asdded to the main diagnostic (ii) names to be compacted
560 */
561 protected void preprocessSymbol(Symbol s) {
562 symbolPreprocessor.visit(s, null);
563 }
564 //where
565 protected Types.DefaultSymbolVisitor<Void, Void> symbolPreprocessor =
566 new Types.DefaultSymbolVisitor<Void, Void>() {
568 @Override
569 public Void visitClassSymbol(ClassSymbol s, Void ignored) {
570 nameSimplifier.addUsage(s);
571 return null;
572 }
574 @Override
575 public Void visitSymbol(Symbol s, Void ignored) {
576 return null;
577 }
579 @Override
580 public Void visitMethodSymbol(MethodSymbol s, Void ignored) {
581 visit(s.owner, null);
582 if (s.type != null)
583 typePreprocessor.visit(s.type);
584 return null;
585 }
586 };
587 // </editor-fold>
589 @Override
590 public RichConfiguration getConfiguration() {
591 //the following cast is always safe - see init
592 return (RichConfiguration)configuration;
593 }
595 /**
596 * Configuration object provided by the rich formatter.
597 */
598 public static class RichConfiguration extends ForwardingDiagnosticFormatter.ForwardingConfiguration {
600 /** set of enabled rich formatter's features */
601 protected java.util.EnumSet<RichFormatterFeature> features;
603 @SuppressWarnings("fallthrough")
604 public RichConfiguration(Options options, AbstractDiagnosticFormatter formatter) {
605 super(formatter.getConfiguration());
606 features = formatter.isRaw() ? EnumSet.noneOf(RichFormatterFeature.class) :
607 EnumSet.of(RichFormatterFeature.SIMPLE_NAMES,
608 RichFormatterFeature.WHERE_CLAUSES,
609 RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
610 String diagOpts = options.get("diags");
611 if (diagOpts != null) {
612 for (String args: diagOpts.split(",")) {
613 if (args.equals("-where")) {
614 features.remove(RichFormatterFeature.WHERE_CLAUSES);
615 }
616 else if (args.equals("where")) {
617 features.add(RichFormatterFeature.WHERE_CLAUSES);
618 }
619 if (args.equals("-simpleNames")) {
620 features.remove(RichFormatterFeature.SIMPLE_NAMES);
621 }
622 else if (args.equals("simpleNames")) {
623 features.add(RichFormatterFeature.SIMPLE_NAMES);
624 }
625 if (args.equals("-disambiguateTvars")) {
626 features.remove(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
627 }
628 else if (args.equals("disambiguateTvars")) {
629 features.add(RichFormatterFeature.UNIQUE_TYPEVAR_NAMES);
630 }
631 }
632 }
633 }
635 /**
636 * Returns a list of all the features supported by the rich formatter.
637 * @return list of supported features
638 */
639 public RichFormatterFeature[] getAvailableFeatures() {
640 return RichFormatterFeature.values();
641 }
643 /**
644 * Enable a specific feature on this rich formatter.
645 * @param feature feature to be enabled
646 */
647 public void enable(RichFormatterFeature feature) {
648 features.add(feature);
649 }
651 /**
652 * Disable a specific feature on this rich formatter.
653 * @param feature feature to be disabled
654 */
655 public void disable(RichFormatterFeature feature) {
656 features.remove(feature);
657 }
659 /**
660 * Is a given feature enabled on this formatter?
661 * @param feature feature to be tested
662 */
663 public boolean isEnabled(RichFormatterFeature feature) {
664 return features.contains(feature);
665 }
667 /**
668 * The advanced formatting features provided by the rich formatter
669 */
670 public enum RichFormatterFeature {
671 /** a list of additional info regarding a given type/symbol */
672 WHERE_CLAUSES,
673 /** full class names simplification (where possible) */
674 SIMPLE_NAMES,
675 /** type-variable names disambiguation */
676 UNIQUE_TYPEVAR_NAMES;
677 }
678 }
679 }