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