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