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