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 compiler possible lossy conversions.
260 */
261 LOSSY_CONVERSIONS("lossy-conversions"),
262
263 /**
264 * Warn about compiler generation of a default constructor.
265 */
266 MISSING_EXPLICIT_CTOR("missing-explicit-ctor"),
267
268 /**
269 * Warn about module system related issues.
270 */
271 MODULE("module", true, true),
272
273 /**
274 * Warn about issues regarding module opens.
275 */
276 OPENS("opens", true, true),
277
278 /**
279 * Warn about issues relating to use of command line options.
280 *
281 * <p>
282 * This category is not supported by {@code @SuppressWarnings}.
283 */
284 OPTIONS("options", false, false),
285
286 /**
287 * Warn when any output file is written to more than once.
288 *
289 * <p>
290 * This category is not supported by {@code @SuppressWarnings}.
291 */
292 OUTPUT_FILE_CLASH("output-file-clash", false, false),
293
294 /**
295 * Warn about issues regarding method overloads.
296 */
297 OVERLOADS("overloads"),
298
299 /**
300 * Warn about issues regarding method overrides.
301 */
302 OVERRIDES("overrides"),
303
304 /**
305 * Warn about invalid path elements on the command line.
306 *
307 * <p>
308 * This category is not supported by {@code @SuppressWarnings}.
309 */
310 PATH("path", false, false),
311
312 /**
313 * Warn about issues regarding annotation processing.
314 *
315 * <p>
316 * This category is not supported by {@code @SuppressWarnings}.
317 */
318 PROCESSING("processing", false, false),
319
320 /**
321 * Warn about unchecked operations on raw types.
322 */
323 RAW("rawtypes"),
324
325 /**
326 * Warn about use of deprecated-for-removal items.
327 */
328 REMOVAL("removal", true, true),
329
330 /**
331 * Warn about use of automatic modules in the requires clauses.
332 */
333 REQUIRES_AUTOMATIC("requires-automatic"),
334
335 /**
336 * Warn about automatic modules in requires transitive.
337 */
338 REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic", true, true),
339
340 /**
341 * Warn about Serializable classes that do not provide a serial version ID.
342 */
343 SERIAL("serial"),
344
345 /**
346 * Warn about issues relating to use of statics
347 */
348 STATIC("static"),
349
350 /**
351 * Warn about unnecessary uses of the strictfp modifier
352 */
353 STRICTFP("strictfp", true, true),
354
355 /**
356 * Warn about issues relating to use of text blocks
357 */
358 TEXT_BLOCKS("text-blocks"),
359
360 /**
361 * Warn about possible 'this' escapes before subclass instance is fully initialized.
362 */
363 THIS_ESCAPE("this-escape"),
364
365 /**
366 * Warn about issues relating to use of try blocks (i.e. try-with-resources)
367 */
368 TRY("try"),
369
370 /**
371 * Warn about unchecked operations on raw types.
372 */
373 UNCHECKED("unchecked"),
374
375 /**
376 * Warn about potentially unsafe vararg methods
377 */
378 VARARGS("varargs"),
379
380 /**
381 * Warn about use of preview features.
382 */
383 PREVIEW("preview", true, true),
384
385 /**
386 * Warn about use of restricted methods.
387 */
388 RESTRICTED("restricted");
389
390 LintCategory(String option) {
391 this(option, true, false);
392 }
393
394 LintCategory(String option, boolean annotationSuppression, boolean enabledByDefault, String... aliases) {
395 this.option = option;
396 this.annotationSuppression = annotationSuppression;
397 this.enabledByDefault = enabledByDefault;
398 ArrayList<String> optionList = new ArrayList<>(1 + aliases.length);
399 optionList.add(option);
400 Collections.addAll(optionList, aliases);
401 this.optionList = Collections.unmodifiableList(optionList);
402 this.optionList.forEach(ident -> map.put(ident, this));
403 }
404
405 /**
406 * Get the {@link LintCategory} having the given command line option.
407 *
408 * @param option lint category option string
409 * @return corresponding {@link LintCategory}, or empty if none exists
410 */
411 public static Optional<LintCategory> get(String option) {
412 return Optional.ofNullable(map.get(option));
413 }
414
415 /**
416 * Get all lint category option strings and aliases.
417 */
418 public static Set<String> options() {
419 return Collections.unmodifiableSet(map.keySet());
420 }
421
422 public static EnumSet<LintCategory> newEmptySet() {
423 return EnumSet.noneOf(LintCategory.class);
424 }
425
426 /** Get the "canonical" string representing this category in @SuppressAnnotations and -Xlint options. */
427 public final String option;
428
429 /** Get a list containing "option" followed by zero or more aliases. */
430 public final List<String> optionList;
431
432 /** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */
433 public final boolean annotationSuppression;
434
435 /**
436 * Is this category included in the default set of enabled lint categories?
437 * Note that for some categories, command line options can alter this at runtime.
438 */
439 public final boolean enabledByDefault;
440 }
441
442 /**
443 * Checks if a warning category is enabled. A warning category may be enabled
444 * on the command line, or by default, and can be temporarily disabled with
445 * the SuppressWarnings annotation.
446 */
447 public boolean isEnabled(LintCategory lc) {
448 initializeRootIfNeeded();
449 return values.contains(lc);
450 }
451
452 /**
453 * Checks is a warning category has been specifically suppressed, by means
454 * of the SuppressWarnings annotation, or, in the case of the deprecated
455 * category, whether it has been implicitly suppressed by virtue of the
456 * current entity being itself deprecated.
457 */
458 public boolean isSuppressed(LintCategory lc) {
459 initializeRootIfNeeded();
460 return suppressedValues.contains(lc);
461 }
462
463 /**
464 * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration.
465 *
466 * <p>
467 * This set can be non-empty only if the symbol is annotated with either
468 * @SuppressWarnings or @Deprecated.
469 *
470 * @param symbol symbol corresponding to a possibly-annotated declaration
471 * @return new warning suppressions applied to sym
472 */
473 public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) {
474 EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream());
475 if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation())
476 suppressions.add(LintCategory.DEPRECATION);
477 return suppressions;
478 }
479
480 // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions
481 private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) {
482 initializeSymbolsIfNeeded();
483 EnumSet<LintCategory> result = LintCategory.newEmptySet();
484 attributes
485 .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym)
486 .map(this::suppressionsFrom)
487 .forEach(result::addAll);
488 return result;
489 }
490
491 // Given a @SuppressWarnings annotation, extract the recognized suppressions
492 private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) {
493 EnumSet<LintCategory> result = LintCategory.newEmptySet();
494 if (suppressWarnings.member(names.value) instanceof Attribute.Array values) {
495 for (Attribute value : values.values) {
496 Optional.of(value)
497 .filter(val -> val instanceof Attribute.Constant)
498 .map(val -> (String) ((Attribute.Constant) val).value)
499 .flatMap(LintCategory::get)
500 .filter(lc -> lc.annotationSuppression)
501 .ifPresent(result::add);
502 }
503 }
504 return result;
505 }
506
507 private void initializeSymbolsIfNeeded() {
508 if (syms == null) {
509 syms = Symtab.instance(context);
510 names = Names.instance(context);
511 }
512 }
513 }