1 /*
  2  * Copyright (c) 2005, 2026, 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.Collections;
 30 import java.util.EnumSet;
 31 import java.util.LinkedHashMap;
 32 import java.util.List;
 33 import java.util.Optional;
 34 import java.util.SequencedMap;
 35 import java.util.SequencedSet;
 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.util.Assert;
 41 import com.sun.tools.javac.util.Context;
 42 import com.sun.tools.javac.util.JCDiagnostic;
 43 import com.sun.tools.javac.util.Log;
 44 import com.sun.tools.javac.util.Names;
 45 import com.sun.tools.javac.util.Options;
 46 
 47 import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.DEFAULT_ENABLED;
 48 import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.DEPRECATION_SENSITIVE;
 49 
 50 /**
 51  * A class for handling -Xlint suboptions and @SuppressWarnings.
 52  *
 53  *  <p><b>This is NOT part of any supported API.
 54  *  If you write code that depends on this, you do so at your own risk.
 55  *  This code and its internal interfaces are subject to change or
 56  *  deletion without notice.</b>
 57  */
 58 public class Lint {
 59 
 60     /** The context key for the root Lint object. */
 61     protected static final Context.Key<Lint> lintKey = new Context.Key<>();
 62 
 63     /** Get the root Lint instance. */
 64     public static Lint instance(Context context) {
 65         Lint instance = context.get(lintKey);
 66         if (instance == null)
 67             instance = new Lint(context);
 68         return instance;
 69     }
 70 
 71     /**
 72      * Obtain an instance with additional warning supression applied from any
 73      * @SuppressWarnings and/or @Deprecated annotations on the given symbol.
 74      *
 75      * <p>
 76      * The returned instance will be different from this instance if and only if
 77      * {@link #suppressionsFrom} returns a non-empty set.
 78      *
 79      * @param sym symbol
 80      * @return lint instance with new warning suppressions applied, or this instance if none
 81      */
 82     public Lint augment(Symbol sym) {
 83         EnumSet<LintCategory> suppressions = suppressionsFrom(sym);
 84         boolean symWithinDeprecated = withinDeprecated || isDeprecatedDeclaration(sym);
 85         if (!suppressions.isEmpty() || symWithinDeprecated != withinDeprecated) {
 86             Lint lint = new Lint(this);
 87             lint.values.removeAll(suppressions);
 88             lint.suppressedValues.addAll(suppressions);
 89             lint.withinDeprecated = symWithinDeprecated;
 90             return lint;
 91         }
 92         return this;
 93     }
 94 
 95     // Does sym's declaration have a (non-useless) @Deprecated annotation?
 96     public static boolean isDeprecatedDeclaration(Symbol sym) {
 97         return sym.isDeprecated() && sym.isDeprecatableViaAnnotation();
 98     }
 99 
100     private final Context context;
101     private final Options options;
102     private final Log log;
103 
104     // These are initialized lazily to avoid dependency loops
105     private Symtab syms;
106     private Names names;
107 
108     // Invariants:
109     //  - It's never the case that a category is in both "values" and "suppressedValues"
110     //  - All categories in "suppressedValues" have annotationSuppression = true
111     private EnumSet<LintCategory> values;
112     private EnumSet<LintCategory> suppressedValues;
113     private boolean withinDeprecated;
114 
115     @SuppressWarnings("this-escape")
116     protected Lint(Context context) {
117         this.context = context;
118         context.put(lintKey, this);
119         options = Options.instance(context);
120         log = Log.instance(context);
121     }
122 
123     // Copy constructor - used to instantiate a non-root ("symbol scoped") instances
124     protected Lint(Lint other) {
125         other.initializeRootIfNeeded();
126         this.context = other.context;
127         this.options = other.options;
128         this.log = other.log;
129         this.syms = other.syms;
130         this.names = other.names;
131         this.values = other.values.clone();
132         this.suppressedValues = other.suppressedValues.clone();
133         this.withinDeprecated = other.withinDeprecated;
134     }
135 
136     // Process command line options on demand to allow use of root Lint early during startup
137     private void initializeRootIfNeeded() {
138         if (values == null) {
139             values = options.getLintCategoriesOf(Option.XLINT, this::getDefaults);
140             suppressedValues = LintCategory.newEmptySet();
141         }
142     }
143 
144     // Obtain the set of on-by-default categories. Note that for a few categories,
145     // whether the category is on-by-default depends on other compiler options.
146     private EnumSet<LintCategory> getDefaults() {
147         EnumSet<LintCategory> defaults = LintCategory.newEmptySet();
148         Source source = Source.instance(context);
149         Stream.of(LintCategory.values())
150           .filter(lc ->
151             switch (lc) {
152                 case DEP_ANN  -> source.compareTo(Source.JDK9) >= 0;
153                 case STRICTFP -> Source.Feature.REDUNDANT_STRICTFP.allowedInSource(source);
154                 case PREVIEW  -> !options.isSet(Option.PREVIEW);
155                 default       -> lc.enabledByDefault;
156             })
157           .forEach(defaults::add);
158         return defaults;
159     }
160 
161     @Override
162     public String toString() {
163         initializeRootIfNeeded();
164         return "Lint:[enable" + values + ",suppress" + suppressedValues + ",deprecated=" + withinDeprecated + "]";
165     }
166 
167     /**
168      * Categories of warnings that can be generated by the compiler.
169      * Each lint category has a logical name (a string), which is the string used e.g. in a {@code SuppressWarning} annotation.
170      * To ensure automation, the enum field name for a lint category string {@code C} should be obtained by:
171      * <ol>
172      *    <li>capitalize all the letters in {@code C}, and</li>
173      *    <li>replacing any occurrence of {@code -} with {@code _}</li>
174      * </ol>
175      * For instance, the lint category string {@code dangling-doc-comments} corresponds to the enum field
176      * {@code DANGLING_DOC_COMMENTS}.
177      */
178     public enum LintCategory {
179         /**
180          * Warn when code refers to a auxiliary class that is hidden in a source file (ie source file name is
181          * different from the class name, and the type is not properly nested) and the referring code
182          * is not located in the same source file.
183          */
184         AUXILIARYCLASS("auxiliaryclass"),
185 
186         /**
187          * Warn about use of unnecessary casts.
188          */
189         CAST("cast"),
190 
191         /**
192          * Warn about issues related to classfile contents.
193          *
194          * <p>
195          * This category is not supported by {@code @SuppressWarnings}.
196          */
197         CLASSFILE("classfile", Property.NO_ANNOTATION_SUPPRESSION),
198 
199         /**
200          * Warn about "dangling" documentation comments,
201          * not attached to any declaration.
202          */
203         DANGLING_DOC_COMMENTS("dangling-doc-comments"),
204 
205         /**
206          * Warn about use of deprecated items.
207          */
208         DEPRECATION("deprecation"),
209 
210         /**
211          * Warn about items which are documented with an {@code @deprecated} JavaDoc
212          * comment, but which do not have {@code @Deprecated} annotation.
213          */
214         DEP_ANN("dep-ann", Property.ENABLED_BY_DEFAULT),
215 
216         /**
217          * Warn about division by constant integer 0.
218          */
219         DIVZERO("divzero"),
220 
221         /**
222          * Warn about empty statement after if.
223          */
224         EMPTY("empty"),
225 
226         /**
227          * Warn about issues regarding module exports.
228          */
229         EXPORTS("exports"),
230 
231         /**
232          * Warn about falling through from one case of a switch statement to the next.
233          */
234         FALLTHROUGH("fallthrough"),
235 
236         /**
237          * Warn about finally clauses that do not terminate normally.
238          */
239         FINALLY("finally"),
240 
241         /**
242          * Warn about uses of @ValueBased classes where an identity class is expected.
243          */
244         IDENTITY(List.of("identity", "synchronization"), Property.ENABLED_BY_DEFAULT),
245 
246         /**
247          * Warn about use of incubating modules.
248          *
249          * <p>
250          * This category is not supported by {@code @SuppressWarnings}.
251          */
252         INCUBATING("incubating", Property.NO_ANNOTATION_SUPPRESSION, Property.ENABLED_BY_DEFAULT),
253 
254         /**
255           * Warn about compiler possible lossy conversions.
256           */
257         LOSSY_CONVERSIONS("lossy-conversions"),
258 
259         /**
260           * Warn about compiler generation of a default constructor.
261           */
262         MISSING_EXPLICIT_CTOR("missing-explicit-ctor"),
263 
264         /**
265          * Warn about module system related issues.
266          */
267         MODULE("module", Property.ENABLED_BY_DEFAULT),
268 
269         /**
270          * Warn about issues related to migration of JDK classes.
271          */
272         MIGRATION("migration"),
273 
274         /**
275          * Warn about issues regarding module opens.
276          */
277         OPENS("opens", Property.ENABLED_BY_DEFAULT),
278 
279         /**
280          * Warn about issues relating to use of command line options.
281          *
282          * <p>
283          * This category is not supported by {@code @SuppressWarnings}.
284          */
285         OPTIONS("options", Property.NO_ANNOTATION_SUPPRESSION),
286 
287         /**
288          * Warn when any output file is written to more than once.
289          *
290          * <p>
291          * This category is not supported by {@code @SuppressWarnings}.
292          */
293         OUTPUT_FILE_CLASH("output-file-clash", Property.NO_ANNOTATION_SUPPRESSION),
294 
295         /**
296          * Warn about issues regarding method overloads.
297          */
298         OVERLOADS("overloads"),
299 
300         /**
301          * Warn about issues regarding method overrides.
302          */
303         OVERRIDES("overrides"),
304 
305         /**
306          * Warn about invalid path elements on the command line.
307          *
308          * <p>
309          * This category is not supported by {@code @SuppressWarnings}.
310          */
311         PATH("path", Property.NO_ANNOTATION_SUPPRESSION),
312 
313         /**
314          * Warn about issues regarding annotation processing.
315          *
316          * <p>
317          * This category is not supported by {@code @SuppressWarnings}.
318          */
319         PROCESSING("processing", Property.NO_ANNOTATION_SUPPRESSION),
320 
321         /**
322          * Warn about unchecked operations on raw types.
323          */
324         RAWTYPES("rawtypes"),
325 
326         /**
327          * Warn about use of deprecated-for-removal items.
328          */
329         REMOVAL("removal", Property.ENABLED_BY_DEFAULT),
330 
331         /**
332          * Warn about use of automatic modules in the requires clauses.
333          */
334         REQUIRES_AUTOMATIC("requires-automatic"),
335 
336         /**
337          * Warn about automatic modules in requires transitive.
338          */
339         REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic", Property.ENABLED_BY_DEFAULT),
340 
341         /**
342          * Warn about Serializable classes that do not provide a serial version ID.
343          */
344         SERIAL("serial"),
345 
346         /**
347          * Warn about issues relating to use of statics
348          */
349         STATIC("static"),
350 
351         /**
352          * Warn about unnecessary uses of the strictfp modifier
353          */
354         STRICTFP("strictfp", Property.ENABLED_BY_DEFAULT),
355 
356         /**
357          * Warn about issues relating to use of text blocks
358          */
359         TEXT_BLOCKS("text-blocks"),
360 
361         /**
362          * Warn about possible 'this' escapes before subclass instance is fully initialized.
363          */
364         THIS_ESCAPE("this-escape"),
365 
366         /**
367          * Warn about issues relating to use of try blocks (i.e. try-with-resources)
368          */
369         TRY("try"),
370 
371         /**
372          * Warn about unchecked operations on raw types.
373          */
374         UNCHECKED("unchecked"),
375 
376         /**
377          * Warn about potentially unsafe vararg methods
378          */
379         VARARGS("varargs"),
380 
381         /**
382          * Warn about use of preview features.
383          */
384         PREVIEW("preview", Property.ENABLED_BY_DEFAULT),
385 
386         /**
387          * Warn about use of restricted methods.
388          */
389         RESTRICTED("restricted");
390 
391         enum Property { NO_ANNOTATION_SUPPRESSION, ENABLED_BY_DEFAULT }
392 
393         LintCategory(String option, Property... properties) {
394             this(List.of(option), properties);
395         }
396 
397         LintCategory(List<String> options, Property... properties) {
398             this.option = options.getFirst();
399             this.optionList = options;
400             Set<Property> propertySet = properties.length == 0 ? Set.of() : EnumSet.copyOf(Arrays.asList(properties));
401             this.annotationSuppression = !propertySet.contains(Property.NO_ANNOTATION_SUPPRESSION);
402             this.enabledByDefault = propertySet.contains(Property.ENABLED_BY_DEFAULT);
403         }
404 
405         private static final SequencedMap<String, LintCategory> lookup = createLookup();
406 
407         private static SequencedMap<String, LintCategory> createLookup() {
408             var map = new LinkedHashMap<String, LintCategory>();
409             for (var category : values()) {
410                 category.optionList.forEach(id -> map.put(id, category));
411             }
412             return Collections.unmodifiableSequencedMap(map);
413         }
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(lookup.get(option));
424         }
425 
426         /**
427          * Get all lint category option strings and aliases.
428          */
429         public static SequencedSet<String> options() {
430             return lookup.sequencedKeySet();
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      * Determine if the given diagnostic should be emitted given the state of this instance.
455      */
456     public boolean shouldEmit(JCDiagnostic diag) {
457 
458         // Check category
459         LintCategory category = diag.getLintCategory();
460         if (category == null)
461             return true;
462 
463         // Certain warnings within @Deprecated declarations are automatically suppressed (JLS 9.6.4.6)
464         if (withinDeprecated && diag.isFlagSet(DEPRECATION_SENSITIVE)) {
465             Assert.check(diag.isFlagSet(DEFAULT_ENABLED) && category.annotationSuppression);
466             return false;
467         }
468 
469         // If the warning is not enabled by default, then emit only when its lint category is explicitly enabled
470         if (!diag.isFlagSet(DEFAULT_ENABLED))
471             return isEnabled(category);
472 
473         // If the lint category doesn't support @SuppressWarnings, then we just check the -Xlint:category flag
474         if (!category.annotationSuppression)
475             return !options.isDisabled(Option.XLINT, category);
476 
477         // Check whether the lint category is currently suppressed
478         return !isSuppressed(category);
479     }
480 
481     /**
482      * Checks if a warning category is enabled. A warning category may be enabled
483      * on the command line, or by default, and can be temporarily disabled with
484      * the SuppressWarnings annotation.
485      */
486     public boolean isEnabled(LintCategory lc) {
487         initializeRootIfNeeded();
488         return values.contains(lc);
489     }
490 
491     /**
492      * Check if a warning category has been specifically suppressed by means of @SuppressWarnings.
493      *
494      * <p>
495      * Always returns false for categories that are not suppressible by the annotation, even
496      * if they (uselessly) happen to appear in one.
497      */
498     public boolean isSuppressed(LintCategory lc) {
499         initializeRootIfNeeded();
500         return suppressedValues.contains(lc);
501     }
502 
503     /**
504      * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration.
505      *
506      * <p>
507      * This set can be non-empty only if the symbol is annotated with @SuppressWarnings, and only categories
508      * for which {@code annotationSuppression} is true are included.
509      *
510      * @param symbol symbol corresponding to a possibly-annotated declaration
511      * @return new warning suppressions applied to sym
512      */
513     public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) {
514         return suppressionsFrom(symbol.getDeclarationAttributes().stream());
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         if (suppressWarnings.member(names.value) instanceof Attribute.Array values) {
532             for (Attribute value : values.values) {
533                 Optional.of(value)
534                   .filter(val -> val instanceof Attribute.Constant)
535                   .map(val -> (String) ((Attribute.Constant) val).value)
536                   .flatMap(LintCategory::get)
537                   .filter(lc -> lc.annotationSuppression)
538                   .ifPresent(result::add);
539             }
540         }
541         return result;
542     }
543 
544     private void initializeSymbolsIfNeeded() {
545         if (syms == null) {
546             syms = Symtab.instance(context);
547             names = Names.instance(context);
548         }
549     }
550 }
--- EOF ---