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