1 /*
2 * Copyright (c) 2005, 2025, 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
26 package com.sun.tools.javac.code;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.EnumSet;
32 import java.util.LinkedHashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Optional;
36 import java.util.Set;
37 import java.util.stream.Stream;
38
39 import com.sun.tools.javac.main.Option;
40 import com.sun.tools.javac.util.Context;
41 import com.sun.tools.javac.util.Log;
42 import com.sun.tools.javac.util.Names;
43 import com.sun.tools.javac.util.Options;
44
45 /**
46 * A class for handling -Xlint suboptions and @SuppressWarnings.
47 *
48 * <p><b>This is NOT part of any supported API.
49 * If you write code that depends on this, you do so at your own risk.
50 * This code and its internal interfaces are subject to change or
51 * deletion without notice.</b>
52 */
53 public class Lint {
54
55 /** The context key for the root Lint object. */
56 protected static final Context.Key<Lint> lintKey = new Context.Key<>();
57
58 /** Get the root Lint instance. */
59 public static Lint instance(Context context) {
60 Lint instance = context.get(lintKey);
61 if (instance == null)
62 instance = new Lint(context);
63 return instance;
64 }
65
66 /**
67 * Obtain an instance with additional warning supression applied from any
68 * @SuppressWarnings and/or @Deprecated annotations on the given symbol.
69 *
70 * <p>
71 * The returned instance will be different from this instance if and only if
72 * {@link #suppressionsFrom} returns a non-empty set.
73 *
74 * @param sym symbol
75 * @return lint instance with new warning suppressions applied, or this instance if none
76 */
77 public Lint augment(Symbol sym) {
78 EnumSet<LintCategory> suppressions = suppressionsFrom(sym);
79 if (!suppressions.isEmpty()) {
80 Lint lint = new Lint(this);
81 lint.values.removeAll(suppressions);
82 lint.suppressedValues.addAll(suppressions);
83 return lint;
84 }
85 return this;
86 }
87
88 /**
89 * Returns a new Lint that has the given LintCategorys enabled.
90 * @param lc one or more categories to be enabled
91 */
92 public Lint enable(LintCategory... lc) {
93 Lint l = new Lint(this);
94 l.values.addAll(Arrays.asList(lc));
95 l.suppressedValues.removeAll(Arrays.asList(lc));
96 return l;
97 }
98
99 /**
100 * Returns a new Lint that has the given LintCategorys suppressed.
101 * @param lc one or more categories to be suppressed
102 */
103 public Lint suppress(LintCategory... lc) {
104 Lint l = new Lint(this);
105 l.values.removeAll(Arrays.asList(lc));
106 l.suppressedValues.addAll(Arrays.asList(lc));
107 return l;
108 }
109
110 private final Context context;
111 private final Options options;
112 private final Log log;
113
114 // These are initialized lazily to avoid dependency loops
115 private Symtab syms;
116 private Names names;
117
118 // Invariant: it's never the case that a category is in both "values" and "suppressedValues"
119 private EnumSet<LintCategory> values;
120 private EnumSet<LintCategory> suppressedValues;
121
122 private static final Map<String, LintCategory> map = new LinkedHashMap<>(40);
123
124 @SuppressWarnings("this-escape")
125 protected Lint(Context context) {
126 this.context = context;
127 context.put(lintKey, this);
128 options = Options.instance(context);
129 log = Log.instance(context);
130 }
131
132 // Instantiate a non-root ("symbol scoped") instance
133 protected Lint(Lint other) {
134 other.initializeRootIfNeeded();
135 this.context = other.context;
136 this.options = other.options;
137 this.log = other.log;
138 this.syms = other.syms;
139 this.names = other.names;
140 this.values = other.values.clone();
141 this.suppressedValues = other.suppressedValues.clone();
142 }
143
144 // Process command line options on demand to allow use of root Lint early during startup
145 private void initializeRootIfNeeded() {
146 if (values == null) {
147 values = options.getLintCategoriesOf(Option.XLINT, this::getDefaults);
148 suppressedValues = LintCategory.newEmptySet();
149 }
150 }
151
152 // Obtain the set of on-by-default categories. Note that for a few categories,
153 // whether the category is on-by-default depends on other compiler options.
154 private EnumSet<LintCategory> getDefaults() {
155 EnumSet<LintCategory> defaults = LintCategory.newEmptySet();
156 Source source = Source.instance(context);
157 Stream.of(LintCategory.values())
158 .filter(lc ->
159 switch (lc) {
160 case DEP_ANN -> source.compareTo(Source.JDK9) >= 0;
161 case STRICTFP -> Source.Feature.REDUNDANT_STRICTFP.allowedInSource(source);
162 case PREVIEW -> !options.isSet(Option.PREVIEW);
163 default -> lc.enabledByDefault;
164 })
165 .forEach(defaults::add);
166 return defaults;
167 }
168
169 @Override
170 public String toString() {
171 initializeRootIfNeeded();
172 return "Lint:[enable" + values + ",suppress" + suppressedValues + "]";
173 }
174
175 /**
176 * Categories of warnings that can be generated by the compiler.
177 * Each lint category has a logical name (a string), which is the string used e.g. in a {@code SuppressWarning} annotation.
178 * To ensure automation, the enum field name for a lint category string {@code C} should be obtained by:
179 * <ol>
180 * <li>capitalize all the letters in {@code C}, and</li>
181 * <li>replacing any occurrence of {@code -} with {@code _}</li>
182 * </ol>
183 * For instance, the lint category string {@code dangling-doc-comments} corresponds to the enum field
184 * {@code DANGLING_DOC_COMMENTS}.
185 */
186 public enum LintCategory {
187 /**
188 * Warn when code refers to a auxiliary class that is hidden in a source file (ie source file name is
189 * different from the class name, and the type is not properly nested) and the referring code
190 * is not located in the same source file.
191 */
192 AUXILIARYCLASS("auxiliaryclass"),
193
194 /**
195 * Warn about use of unnecessary casts.
196 */
197 CAST("cast"),
198
199 /**
200 * Warn about issues related to classfile contents.
201 *
202 * <p>
203 * This category is not supported by {@code @SuppressWarnings}.
204 */
205 CLASSFILE("classfile", false, false),
206
207 /**
208 * Warn about "dangling" documentation comments,
209 * not attached to any declaration.
210 */
211 DANGLING_DOC_COMMENTS("dangling-doc-comments"),
212
213 /**
214 * Warn about use of deprecated items.
215 */
216 DEPRECATION("deprecation"),
217
218 /**
219 * Warn about items which are documented with an {@code @deprecated} JavaDoc
220 * comment, but which do not have {@code @Deprecated} annotation.
221 */
222 DEP_ANN("dep-ann", true, true),
223
224 /**
225 * Warn about division by constant integer 0.
226 */
227 DIVZERO("divzero"),
228
229 /**
230 * Warn about empty statement after if.
231 */
232 EMPTY("empty"),
233
234 /**
235 * Warn about issues regarding module exports.
236 */
237 EXPORTS("exports"),
238
239 /**
240 * Warn about falling through from one case of a switch statement to the next.
241 */
242 FALLTHROUGH("fallthrough"),
243
244 /**
245 * Warn about finally clauses that do not terminate normally.
246 */
247 FINALLY("finally"),
248
249 /**
250 * Warn about uses of @ValueBased classes where an identity class is expected.
251 */
252 IDENTITY("identity", true, true, "synchronization"),
253
254 /**
255 * Warn about use of incubating modules.
256 *
257 * <p>
258 * This category is not supported by {@code @SuppressWarnings}.
259 */
260 INCUBATING("incubating", false, true),
261
262 /**
263 * Warn about code in identity classes that wouldn't be allowed in early
264 * construction due to a this dependency.
265 */
266 INITIALIZATION("initialization"),
267
268 /**
269 * Warn about compiler possible lossy conversions.
270 */
271 LOSSY_CONVERSIONS("lossy-conversions"),
272
273 /**
274 * Warn about compiler generation of a default constructor.
275 */
276 MISSING_EXPLICIT_CTOR("missing-explicit-ctor"),
277
278 /**
279 * Warn about module system related issues.
280 */
281 MODULE("module", true, true),
282
283 /**
284 * Warn about issues related to migration of JDK classes.
285 */
286 MIGRATION("migration"),
287
288 /**
289 * Warn about issues regarding module opens.
290 */
291 OPENS("opens", true, true),
292
293 /**
294 * Warn about issues relating to use of command line options.
295 *
296 * <p>
297 * This category is not supported by {@code @SuppressWarnings}.
298 */
299 OPTIONS("options", false, false),
300
301 /**
302 * Warn when any output file is written to more than once.
303 *
304 * <p>
305 * This category is not supported by {@code @SuppressWarnings}.
306 */
307 OUTPUT_FILE_CLASH("output-file-clash", false, false),
308
309 /**
310 * Warn about issues regarding method overloads.
311 */
312 OVERLOADS("overloads"),
313
314 /**
315 * Warn about issues regarding method overrides.
316 */
317 OVERRIDES("overrides"),
318
319 /**
320 * Warn about invalid path elements on the command line.
321 *
322 * <p>
323 * This category is not supported by {@code @SuppressWarnings}.
324 */
325 PATH("path", false, false),
326
327 /**
328 * Warn about issues regarding annotation processing.
329 *
330 * <p>
331 * This category is not supported by {@code @SuppressWarnings}.
332 */
333 PROCESSING("processing", false, false),
334
335 /**
336 * Warn about unchecked operations on raw types.
337 */
338 RAWTYPES("rawtypes"),
339
340 /**
341 * Warn about use of deprecated-for-removal items.
342 */
343 REMOVAL("removal", true, true),
344
345 /**
346 * Warn about use of automatic modules in the requires clauses.
347 */
348 REQUIRES_AUTOMATIC("requires-automatic"),
349
350 /**
351 * Warn about automatic modules in requires transitive.
352 */
353 REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic", true, true),
354
355 /**
356 * Warn about Serializable classes that do not provide a serial version ID.
357 */
358 SERIAL("serial"),
359
360 /**
361 * Warn about issues relating to use of statics
362 */
363 STATIC("static"),
364
365 /**
366 * Warn about unnecessary uses of the strictfp modifier
367 */
368 STRICTFP("strictfp", true, true),
369
370 /**
371 * Warn about issues relating to use of text blocks
372 */
373 TEXT_BLOCKS("text-blocks"),
374
375 /**
376 * Warn about possible 'this' escapes before subclass instance is fully initialized.
377 */
378 THIS_ESCAPE("this-escape"),
379
380 /**
381 * Warn about issues relating to use of try blocks (i.e. try-with-resources)
382 */
383 TRY("try"),
384
385 /**
386 * Warn about unchecked operations on raw types.
387 */
388 UNCHECKED("unchecked"),
389
390 /**
391 * Warn about potentially unsafe vararg methods
392 */
393 VARARGS("varargs"),
394
395 /**
396 * Warn about use of preview features.
397 */
398 PREVIEW("preview", true, true),
399
400 /**
401 * Warn about use of restricted methods.
402 */
403 RESTRICTED("restricted");
404
405 LintCategory(String option) {
406 this(option, true, false);
407 }
408
409 LintCategory(String option, boolean annotationSuppression, boolean enabledByDefault, String... aliases) {
410 this.option = option;
411 this.annotationSuppression = annotationSuppression;
412 this.enabledByDefault = enabledByDefault;
413 ArrayList<String> optionList = new ArrayList<>(1 + aliases.length);
414 optionList.add(option);
415 Collections.addAll(optionList, aliases);
416 this.optionList = Collections.unmodifiableList(optionList);
417 this.optionList.forEach(ident -> map.put(ident, this));
418 }
419
420 /**
421 * Get the {@link LintCategory} having the given command line option.
422 *
423 * @param option lint category option string
424 * @return corresponding {@link LintCategory}, or empty if none exists
425 */
426 public static Optional<LintCategory> get(String option) {
427 return Optional.ofNullable(map.get(option));
428 }
429
430 /**
431 * Get all lint category option strings and aliases.
432 */
433 public static Set<String> options() {
434 return Collections.unmodifiableSet(map.keySet());
435 }
436
437 public static EnumSet<LintCategory> newEmptySet() {
438 return EnumSet.noneOf(LintCategory.class);
439 }
440
441 /** Get the "canonical" string representing this category in @SuppressAnnotations and -Xlint options. */
442 public final String option;
443
444 /** Get a list containing "option" followed by zero or more aliases. */
445 public final List<String> optionList;
446
447 /** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */
448 public final boolean annotationSuppression;
449
450 /**
451 * Is this category included in the default set of enabled lint categories?
452 * Note that for some categories, command line options can alter this at runtime.
453 */
454 public final boolean enabledByDefault;
455 }
456
457 /**
458 * Checks if a warning category is enabled. A warning category may be enabled
459 * on the command line, or by default, and can be temporarily disabled with
460 * the SuppressWarnings annotation.
461 */
462 public boolean isEnabled(LintCategory lc) {
463 initializeRootIfNeeded();
464 return values.contains(lc);
465 }
466
467 /**
468 * Checks is a warning category has been specifically suppressed, by means
469 * of the SuppressWarnings annotation, or, in the case of the deprecated
470 * category, whether it has been implicitly suppressed by virtue of the
471 * current entity being itself deprecated.
472 */
473 public boolean isSuppressed(LintCategory lc) {
474 initializeRootIfNeeded();
475 return suppressedValues.contains(lc);
476 }
477
478 /**
479 * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration.
480 *
481 * <p>
482 * This set can be non-empty only if the symbol is annotated with either
483 * @SuppressWarnings or @Deprecated.
484 *
485 * @param symbol symbol corresponding to a possibly-annotated declaration
486 * @return new warning suppressions applied to sym
487 */
488 public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) {
489 EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream());
490 if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation())
491 suppressions.add(LintCategory.DEPRECATION);
492 return suppressions;
493 }
494
495 // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions
496 private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) {
497 initializeSymbolsIfNeeded();
498 EnumSet<LintCategory> result = LintCategory.newEmptySet();
499 attributes
500 .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym)
501 .map(this::suppressionsFrom)
502 .forEach(result::addAll);
503 return result;
504 }
505
506 // Given a @SuppressWarnings annotation, extract the recognized suppressions
507 private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) {
508 EnumSet<LintCategory> result = LintCategory.newEmptySet();
509 if (suppressWarnings.member(names.value) instanceof Attribute.Array values) {
510 for (Attribute value : values.values) {
511 Optional.of(value)
512 .filter(val -> val instanceof Attribute.Constant)
513 .map(val -> (String) ((Attribute.Constant) val).value)
514 .flatMap(LintCategory::get)
515 .filter(lc -> lc.annotationSuppression)
516 .ifPresent(result::add);
517 }
518 }
519 return result;
520 }
521
522 private void initializeSymbolsIfNeeded() {
523 if (syms == null) {
524 syms = Symtab.instance(context);
525 names = Names.instance(context);
526 }
527 }
528 }