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