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 
112     // These are initialized lazily to avoid dependency loops
113     private Symtab syms;
114     private Names names;
115 
116     // Invariant: it's never the case that a category is in both "values" and "suppressedValues"
117     private final EnumSet<LintCategory> values;
118     private final EnumSet<LintCategory> suppressedValues;
119 
120     private static final Map<String, LintCategory> map = new ConcurrentHashMap<>(20);
121 
122     @SuppressWarnings("this-escape")
123     protected Lint(Context context) {
124         // initialize values according to the lint options
125         Options options = Options.instance(context);
126 
127         if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, "all")) {
128             // If -Xlint or -Xlint:all is given, enable all categories by default
129             values = EnumSet.allOf(LintCategory.class);
130         } else if (options.isSet(Option.XLINT_CUSTOM, "none")) {
131             // if -Xlint:none is given, disable all categories by default
132             values = LintCategory.newEmptySet();
133         } else {
134             // otherwise, enable on-by-default categories
135             values = LintCategory.newEmptySet();
136 
137             Source source = Source.instance(context);
138             if (source.compareTo(Source.JDK9) >= 0) {
139                 values.add(LintCategory.DEP_ANN);
140             }
141             if (Source.Feature.REDUNDANT_STRICTFP.allowedInSource(source)) {
142                 values.add(LintCategory.STRICTFP);
143             }
144             values.add(LintCategory.REQUIRES_TRANSITIVE_AUTOMATIC);
145             values.add(LintCategory.OPENS);
146             values.add(LintCategory.MODULE);
147             values.add(LintCategory.REMOVAL);
148             if (!options.isSet(Option.PREVIEW)) {
149                 values.add(LintCategory.PREVIEW);
150             }
151             values.add(LintCategory.SYNCHRONIZATION);
152             values.add(LintCategory.INCUBATING);
153         }
154 
155         // Look for specific overrides
156         for (LintCategory lc : LintCategory.values()) {
157             if (options.isSet(Option.XLINT_CUSTOM, lc.option)) {
158                 values.add(lc);
159             } else if (options.isSet(Option.XLINT_CUSTOM, "-" + lc.option)) {
160                 values.remove(lc);
161             }
162         }
163 
164         suppressedValues = LintCategory.newEmptySet();
165 
166         this.context = context;
167         context.put(lintKey, this);
168     }
169 
170     protected Lint(Lint other) {
171         this.context = other.context;
172         this.syms = other.syms;
173         this.names = other.names;
174         this.values = other.values.clone();
175         this.suppressedValues = other.suppressedValues.clone();
176     }
177 
178     @Override
179     public String toString() {
180         return "Lint:[enable" + values + ",suppress" + suppressedValues + "]";
181     }
182 
183     /**
184      * Categories of warnings that can be generated by the compiler.
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         CLASSFILE("classfile"),
203 
204         /**
205          * Warn about "dangling" documentation comments,
206          * not attached to any declaration.
207          */
208         DANGLING_DOC_COMMENTS("dangling-doc-comments"),
209 
210         /**
211          * Warn about use of deprecated items.
212          */
213         DEPRECATION("deprecation"),
214 
215         /**
216          * Warn about items which are documented with an {@code @deprecated} JavaDoc
217          * comment, but which do not have {@code @Deprecated} annotation.
218          */
219         DEP_ANN("dep-ann"),
220 
221         /**
222          * Warn about division by constant integer 0.
223          */
224         DIVZERO("divzero"),
225 
226         /**
227          * Warn about empty statement after if.
228          */
229         EMPTY("empty"),
230 
231         /**
232          * Warn about issues regarding module exports.
233          */
234         EXPORTS("exports"),
235 
236         /**
237          * Warn about falling through from one case of a switch statement to the next.
238          */
239         FALLTHROUGH("fallthrough"),
240 
241         /**
242          * Warn about finally clauses that do not terminate normally.
243          */
244         FINALLY("finally"),
245 
246         /**
247          * Warn about use of incubating modules.
248          */
249         INCUBATING("incubating"),
250 
251         /**
252           * Warn about compiler possible lossy conversions.
253           */
254         LOSSY_CONVERSIONS("lossy-conversions"),
255 
256         /**
257           * Warn about compiler generation of a default constructor.
258           */
259         MISSING_EXPLICIT_CTOR("missing-explicit-ctor"),
260 
261         /**
262          * Warn about module system related issues.
263          */
264         MODULE("module"),
265 
266         /**
267          * Warn about issues regarding module opens.
268          */
269         OPENS("opens"),
270 
271         /**
272          * Warn about issues relating to use of command line options
273          */
274         OPTIONS("options"),
275 
276         /**
277          * Warn when any output file is written to more than once.
278          */
279         OUTPUT_FILE_CLASH("output-file-clash"),
280 
281         /**
282          * Warn about issues regarding method overloads.
283          */
284         OVERLOADS("overloads"),
285 
286         /**
287          * Warn about issues regarding method overrides.
288          */
289         OVERRIDES("overrides"),
290 
291         /**
292          * Warn about invalid path elements on the command line.
293          * Such warnings cannot be suppressed with the SuppressWarnings
294          * annotation.
295          */
296         PATH("path"),
297 
298         /**
299          * Warn about issues regarding annotation processing.
300          */
301         PROCESSING("processing"),
302 
303         /**
304          * Warn about unchecked operations on raw types.
305          */
306         RAW("rawtypes"),
307 
308         /**
309          * Warn about use of deprecated-for-removal items.
310          */
311         REMOVAL("removal"),
312 
313         /**
314          * Warn about use of automatic modules in the requires clauses.
315          */
316         REQUIRES_AUTOMATIC("requires-automatic"),
317 
318         /**
319          * Warn about automatic modules in requires transitive.
320          */
321         REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic"),
322 
323         /**
324          * Warn about Serializable classes that do not provide a serial version ID.
325          */
326         SERIAL("serial"),
327 
328         /**
329          * Warn about issues relating to use of statics
330          */
331         STATIC("static"),
332 
333         /**
334          * Warn about unnecessary uses of the strictfp modifier
335          */
336         STRICTFP("strictfp"),
337 
338         /**
339          * Warn about synchronization attempts on instances of @ValueBased classes.
340          */
341         SYNCHRONIZATION("synchronization"),
342 
343         /**
344          * Warn about issues relating to use of text blocks
345          */
346         TEXT_BLOCKS("text-blocks"),
347 
348         /**
349          * Warn about possible 'this' escapes before subclass instance is fully initialized.
350          */
351         THIS_ESCAPE("this-escape"),
352 
353         /**
354          * Warn about issues relating to use of try blocks (i.e. try-with-resources)
355          */
356         TRY("try"),
357 
358         /**
359          * Warn about unchecked operations on raw types.
360          */
361         UNCHECKED("unchecked"),
362 
363         /**
364          * Warn about potentially unsafe vararg methods
365          */
366         VARARGS("varargs"),
367 
368         /**
369          * Warn about use of preview features.
370          */
371         PREVIEW("preview"),
372 
373         /**
374          * Warn about use of restricted methods.
375          */
376         RESTRICTED("restricted");
377 
378         LintCategory(String option) {
379             this.option = option;
380             map.put(option, this);
381         }
382 
383         /**
384          * Get the {@link LintCategory} having the given command line option.
385          *
386          * @param option lint category option string
387          * @return corresponding {@link LintCategory}, or empty if none exists
388          */
389         public static Optional<LintCategory> get(String option) {
390             return Optional.ofNullable(map.get(option));
391         }
392 
393         public static EnumSet<LintCategory> newEmptySet() {
394             return EnumSet.noneOf(LintCategory.class);
395         }
396 
397         /** Get the string representing this category in @SuppressAnnotations and -Xlint options. */
398         public final String option;
399     }
400 
401     /**
402      * Checks if a warning category is enabled. A warning category may be enabled
403      * on the command line, or by default, and can be temporarily disabled with
404      * the SuppressWarnings annotation.
405      */
406     public boolean isEnabled(LintCategory lc) {
407         return values.contains(lc);
408     }
409 
410     /**
411      * Checks is a warning category has been specifically suppressed, by means
412      * of the SuppressWarnings annotation, or, in the case of the deprecated
413      * category, whether it has been implicitly suppressed by virtue of the
414      * current entity being itself deprecated.
415      */
416     public boolean isSuppressed(LintCategory lc) {
417         return suppressedValues.contains(lc);
418     }
419 
420     /**
421      * Helper method. Log a lint warning if its lint category is enabled.
422      */
423     public void logIfEnabled(Log log, DiagnosticPosition pos, LintWarning warning) {
424         if (isEnabled(warning.getLintCategory())) {
425             log.warning(pos, warning);
426         }
427     }
428 
429     /**
430      * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration.
431      *
432      * <p>
433      * This set can be non-empty only if the symbol is annotated with either
434      * @SuppressWarnings or @Deprecated.
435      *
436      * @param symbol symbol corresponding to a possibly-annotated declaration
437      * @return new warning suppressions applied to sym
438      */
439     public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) {
440         EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream());
441         if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation())
442             suppressions.add(LintCategory.DEPRECATION);
443         return suppressions;
444     }
445 
446     /**
447      * Retrieve the recognized lint categories suppressed by the given @SuppressWarnings annotation.
448      *
449      * @param annotation @SuppressWarnings annotation, or null
450      * @return set of lint categories, possibly empty but never null
451      */
452     private EnumSet<LintCategory> suppressionsFrom(JCAnnotation annotation) {
453         initializeIfNeeded();
454         if (annotation == null)
455             return LintCategory.newEmptySet();
456         Assert.check(annotation.attribute.type.tsym == syms.suppressWarningsType.tsym);
457         return suppressionsFrom(Stream.of(annotation).map(anno -> anno.attribute));
458     }
459 
460     // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions
461     private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) {
462         initializeIfNeeded();
463         EnumSet<LintCategory> result = LintCategory.newEmptySet();
464         attributes
465           .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym)
466           .map(this::suppressionsFrom)
467           .forEach(result::addAll);
468         return result;
469     }
470 
471     // Given a @SuppressWarnings annotation, extract the recognized suppressions
472     private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) {
473         EnumSet<LintCategory> result = LintCategory.newEmptySet();
474         Attribute.Array values = (Attribute.Array)suppressWarnings.member(names.value);
475         for (Attribute value : values.values) {
476             Optional.of((String)((Attribute.Constant)value).value)
477               .flatMap(LintCategory::get)
478               .ifPresent(result::add);
479         }
480         return result;
481     }
482 
483     private void initializeIfNeeded() {
484         if (syms == null) {
485             syms = Symtab.instance(context);
486             names = Names.instance(context);
487         }
488     }
489 }