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 }