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