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 }