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 
151         // Already initialized?
152         if (values != null)
153             return;
154 
155         // Initialize enabled categories based on "-Xlint" flags
156         if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_ALL)) {
157             // If -Xlint or -Xlint:all is given, enable all categories by default
158             values = EnumSet.allOf(LintCategory.class);
159         } else if (options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_NONE)) {
160             // if -Xlint:none is given, disable all categories by default
161             values = LintCategory.newEmptySet();
162         } else {
163             // otherwise, enable on-by-default categories
164             values = LintCategory.newEmptySet();
165 
166             Source source = Source.instance(context);
167             if (source.compareTo(Source.JDK9) >= 0) {
168                 values.add(LintCategory.DEP_ANN);
169             }
170             if (Source.Feature.REDUNDANT_STRICTFP.allowedInSource(source)) {
171                 values.add(LintCategory.STRICTFP);
172             }
173             values.add(LintCategory.REQUIRES_TRANSITIVE_AUTOMATIC);
174             values.add(LintCategory.OPENS);
175             values.add(LintCategory.MODULE);
176             values.add(LintCategory.REMOVAL);
177             if (!options.isSet(Option.PREVIEW)) {
178                 values.add(LintCategory.PREVIEW);
179             }
180             values.add(LintCategory.IDENTITY);
181             values.add(LintCategory.INCUBATING);
182         }
183 
184         // Look for specific overrides
185         for (LintCategory lc : LintCategory.values()) {
186             if (options.isExplicitlyEnabled(Option.XLINT, lc)) {
187                 values.add(lc);
188             } else if (options.isExplicitlyDisabled(Option.XLINT, lc)) {
189                 values.remove(lc);
190             }
191         }
192 
193         suppressedValues = LintCategory.newEmptySet();
194     }
195 
196     @Override
197     public String toString() {
198         initializeRootIfNeeded();
199         return "Lint:[enable" + values + ",suppress" + suppressedValues + "]";
200     }
201 
202     /**
203      * Categories of warnings that can be generated by the compiler.
204      */
205     public enum LintCategory {
206         /**
207          * Warn when code refers to a auxiliary class that is hidden in a source file (ie source file name is
208          * different from the class name, and the type is not properly nested) and the referring code
209          * is not located in the same source file.
210          */
211         AUXILIARYCLASS("auxiliaryclass"),
212 
213         /**
214          * Warn about use of unnecessary casts.
215          */
216         CAST("cast"),
217 
218         /**
219          * Warn about issues related to classfile contents.
220          *
221          * <p>
222          * This category is not supported by {@code @SuppressWarnings}.
223          */
224         CLASSFILE("classfile", false),
225 
226         /**
227          * Warn about "dangling" documentation comments,
228          * not attached to any declaration.
229          */
230         DANGLING_DOC_COMMENTS("dangling-doc-comments"),
231 
232         /**
233          * Warn about use of deprecated items.
234          */
235         DEPRECATION("deprecation"),
236 
237         /**
238          * Warn about items which are documented with an {@code @deprecated} JavaDoc
239          * comment, but which do not have {@code @Deprecated} annotation.
240          */
241         DEP_ANN("dep-ann"),
242 
243         /**
244          * Warn about division by constant integer 0.
245          */
246         DIVZERO("divzero"),
247 
248         /**
249          * Warn about empty statement after if.
250          */
251         EMPTY("empty"),
252 
253         /**
254          * Warn about issues regarding module exports.
255          */
256         EXPORTS("exports"),
257 
258         /**
259          * Warn about falling through from one case of a switch statement to the next.
260          */
261         FALLTHROUGH("fallthrough"),
262 
263         /**
264          * Warn about finally clauses that do not terminate normally.
265          */
266         FINALLY("finally"),
267 
268         /**
269          * Warn about uses of @ValueBased classes where an identity class is expected.
270          */
271         IDENTITY("identity", true, "synchronization"),
272 
273         /**
274          * Warn about use of incubating modules.
275          *
276          * <p>
277          * This category is not supported by {@code @SuppressWarnings}.
278          */
279         INCUBATING("incubating", false),
280 
281         /**
282           * Warn about compiler possible lossy conversions.
283           */
284         LOSSY_CONVERSIONS("lossy-conversions"),
285 
286         /**
287           * Warn about compiler generation of a default constructor.
288           */
289         MISSING_EXPLICIT_CTOR("missing-explicit-ctor"),
290 
291         /**
292          * Warn about module system related issues.
293          */
294         MODULE("module"),
295 
296         /**
297          * Warn about issues regarding module opens.
298          */
299         OPENS("opens"),
300 
301         /**
302          * Warn about issues relating to use of command line options.
303          *
304          * <p>
305          * This category is not supported by {@code @SuppressWarnings}.
306          */
307         OPTIONS("options", false),
308 
309         /**
310          * Warn when any output file is written to more than once.
311          *
312          * <p>
313          * This category is not supported by {@code @SuppressWarnings}.
314          */
315         OUTPUT_FILE_CLASH("output-file-clash", false),
316 
317         /**
318          * Warn about issues regarding method overloads.
319          */
320         OVERLOADS("overloads"),
321 
322         /**
323          * Warn about issues regarding method overrides.
324          */
325         OVERRIDES("overrides"),
326 
327         /**
328          * Warn about invalid path elements on the command line.
329          *
330          * <p>
331          * This category is not supported by {@code @SuppressWarnings}.
332          */
333         PATH("path", false),
334 
335         /**
336          * Warn about issues regarding annotation processing.
337          */
338         PROCESSING("processing"),
339 
340         /**
341          * Warn about unchecked operations on raw types.
342          */
343         RAW("rawtypes"),
344 
345         /**
346          * Warn about use of deprecated-for-removal items.
347          */
348         REMOVAL("removal"),
349 
350         /**
351          * Warn about use of automatic modules in the requires clauses.
352          */
353         REQUIRES_AUTOMATIC("requires-automatic"),
354 
355         /**
356          * Warn about automatic modules in requires transitive.
357          */
358         REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic"),
359 
360         /**
361          * Warn about Serializable classes that do not provide a serial version ID.
362          */
363         SERIAL("serial"),
364 
365         /**
366          * Warn about issues relating to use of statics
367          */
368         STATIC("static"),
369 
370         /**
371          * Warn about unnecessary uses of the strictfp modifier
372          */
373         STRICTFP("strictfp"),
374 
375         /**
376          * Warn about issues relating to use of text blocks
377          *
378          * <p>
379          * This category is not supported by {@code @SuppressWarnings} (yet - see JDK-8224228).
380          */
381         TEXT_BLOCKS("text-blocks", false),
382 
383         /**
384          * Warn about possible 'this' escapes before subclass instance is fully initialized.
385          */
386         THIS_ESCAPE("this-escape"),
387 
388         /**
389          * Warn about issues relating to use of try blocks (i.e. try-with-resources)
390          */
391         TRY("try"),
392 
393         /**
394          * Warn about unchecked operations on raw types.
395          */
396         UNCHECKED("unchecked"),
397 
398         /**
399          * Warn about potentially unsafe vararg methods
400          */
401         VARARGS("varargs"),
402 
403         /**
404          * Warn about use of preview features.
405          */
406         PREVIEW("preview"),
407 
408         /**
409          * Warn about use of restricted methods.
410          */
411         RESTRICTED("restricted");
412 
413         LintCategory(String option) {
414             this(option, true);
415         }
416 
417         LintCategory(String option, boolean annotationSuppression, String... aliases) {
418             this.option = option;
419             this.annotationSuppression = annotationSuppression;
420             ArrayList<String> optionList = new ArrayList<>(1 + aliases.length);
421             optionList.add(option);
422             Collections.addAll(optionList, aliases);
423             this.optionList = Collections.unmodifiableList(optionList);
424             this.optionList.forEach(ident -> map.put(ident, this));
425         }
426 
427         /**
428          * Get the {@link LintCategory} having the given command line option.
429          *
430          * @param option lint category option string
431          * @return corresponding {@link LintCategory}, or empty if none exists
432          */
433         public static Optional<LintCategory> get(String option) {
434             return Optional.ofNullable(map.get(option));
435         }
436 
437         /**
438          * Get all lint category option strings and aliases.
439          */
440         public static Set<String> options() {
441             return Collections.unmodifiableSet(map.keySet());
442         }
443 
444         public static EnumSet<LintCategory> newEmptySet() {
445             return EnumSet.noneOf(LintCategory.class);
446         }
447 
448         /** Get the "canonical" string representing this category in @SuppressAnnotations and -Xlint options. */
449         public final String option;
450 
451         /** Get a list containing "option" followed by zero or more aliases. */
452         public final List<String> optionList;
453 
454         /** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */
455         public final boolean annotationSuppression;
456     }
457 
458     /**
459      * Checks if a warning category is enabled. A warning category may be enabled
460      * on the command line, or by default, and can be temporarily disabled with
461      * the SuppressWarnings annotation.
462      */
463     public boolean isEnabled(LintCategory lc) {
464         initializeRootIfNeeded();
465         return values.contains(lc);
466     }
467 
468     /**
469      * Checks is a warning category has been specifically suppressed, by means
470      * of the SuppressWarnings annotation, or, in the case of the deprecated
471      * category, whether it has been implicitly suppressed by virtue of the
472      * current entity being itself deprecated.
473      */
474     public boolean isSuppressed(LintCategory lc) {
475         initializeRootIfNeeded();
476         return suppressedValues.contains(lc);
477     }
478 
479     /**
480      * Helper method. Log a lint warning if its lint category is enabled.
481      *
482      * @param warning key for the localized warning message
483      */
484     public void logIfEnabled(LintWarning warning) {
485         logIfEnabled(null, warning);
486     }
487 
488     /**
489      * Helper method. Log a lint warning if its lint category is enabled.
490      *
491      * @param pos source position at which to report the warning
492      * @param warning key for the localized warning message
493      */
494     public void logIfEnabled(DiagnosticPosition pos, LintWarning warning) {
495         if (isEnabled(warning.getLintCategory())) {
496             log.warning(pos, warning);
497         }
498     }
499 
500     /**
501      * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration.
502      *
503      * <p>
504      * This set can be non-empty only if the symbol is annotated with either
505      * @SuppressWarnings or @Deprecated.
506      *
507      * @param symbol symbol corresponding to a possibly-annotated declaration
508      * @return new warning suppressions applied to sym
509      */
510     public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) {
511         EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream());
512         if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation())
513             suppressions.add(LintCategory.DEPRECATION);
514         return suppressions;
515     }
516 
517     // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions
518     private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) {
519         initializeSymbolsIfNeeded();
520         EnumSet<LintCategory> result = LintCategory.newEmptySet();
521         attributes
522           .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym)
523           .map(this::suppressionsFrom)
524           .forEach(result::addAll);
525         return result;
526     }
527 
528     // Given a @SuppressWarnings annotation, extract the recognized suppressions
529     private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) {
530         EnumSet<LintCategory> result = LintCategory.newEmptySet();
531         Attribute.Array values = (Attribute.Array)suppressWarnings.member(names.value);
532         for (Attribute value : values.values) {
533             Optional.of((String)((Attribute.Constant)value).value)
534               .flatMap(LintCategory::get)
535               .filter(lc -> lc.annotationSuppression)
536               .ifPresent(result::add);
537         }
538         return result;
539     }
540 
541     private void initializeSymbolsIfNeeded() {
542         if (syms == null) {
543             syms = Symtab.instance(context);
544             names = Names.instance(context);
545         }
546     }
547 }