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.isLintExplicitlyEnabled(lc)) { 187 values.add(lc); 188 } else if (options.isLintExplicitlyDisabled(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 code in identity classes that wouldn't be allowed in early 283 * construction due to a this dependency. 284 */ 285 INITIALIZATION("initialization"), 286 287 /** 288 * Warn about compiler possible lossy conversions. 289 */ 290 LOSSY_CONVERSIONS("lossy-conversions"), 291 292 /** 293 * Warn about compiler generation of a default constructor. 294 */ 295 MISSING_EXPLICIT_CTOR("missing-explicit-ctor"), 296 297 /** 298 * Warn about module system related issues. 299 */ 300 MODULE("module"), 301 302 /** 303 * Warn about issues related to migration of JDK classes. 304 */ 305 MIGRATION("migration"), 306 307 /** 308 * Warn about issues regarding module opens. 309 */ 310 OPENS("opens"), 311 312 /** 313 * Warn about issues relating to use of command line options. 314 * 315 * <p> 316 * This category is not supported by {@code @SuppressWarnings}. 317 */ 318 OPTIONS("options", false), 319 320 /** 321 * Warn when any output file is written to more than once. 322 * 323 * <p> 324 * This category is not supported by {@code @SuppressWarnings}. 325 */ 326 OUTPUT_FILE_CLASH("output-file-clash", false), 327 328 /** 329 * Warn about issues regarding method overloads. 330 */ 331 OVERLOADS("overloads"), 332 333 /** 334 * Warn about issues regarding method overrides. 335 */ 336 OVERRIDES("overrides"), 337 338 /** 339 * Warn about invalid path elements on the command line. 340 * 341 * <p> 342 * This category is not supported by {@code @SuppressWarnings}. 343 */ 344 PATH("path", false), 345 346 /** 347 * Warn about issues regarding annotation processing. 348 */ 349 PROCESSING("processing"), 350 351 /** 352 * Warn about unchecked operations on raw types. 353 */ 354 RAW("rawtypes"), 355 356 /** 357 * Warn about use of deprecated-for-removal items. 358 */ 359 REMOVAL("removal"), 360 361 /** 362 * Warn about use of automatic modules in the requires clauses. 363 */ 364 REQUIRES_AUTOMATIC("requires-automatic"), 365 366 /** 367 * Warn about automatic modules in requires transitive. 368 */ 369 REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic"), 370 371 /** 372 * Warn about Serializable classes that do not provide a serial version ID. 373 */ 374 SERIAL("serial"), 375 376 /** 377 * Warn about issues relating to use of statics 378 */ 379 STATIC("static"), 380 381 /** 382 * Warn about unnecessary uses of the strictfp modifier 383 */ 384 STRICTFP("strictfp"), 385 386 /** 387 * Warn about issues relating to use of text blocks 388 */ 389 TEXT_BLOCKS("text-blocks"), 390 391 /** 392 * Warn about possible 'this' escapes before subclass instance is fully initialized. 393 */ 394 THIS_ESCAPE("this-escape"), 395 396 /** 397 * Warn about issues relating to use of try blocks (i.e. try-with-resources) 398 */ 399 TRY("try"), 400 401 /** 402 * Warn about unchecked operations on raw types. 403 */ 404 UNCHECKED("unchecked"), 405 406 /** 407 * Warn about potentially unsafe vararg methods 408 */ 409 VARARGS("varargs"), 410 411 /** 412 * Warn about use of preview features. 413 */ 414 PREVIEW("preview"), 415 416 /** 417 * Warn about use of restricted methods. 418 */ 419 RESTRICTED("restricted"); 420 421 LintCategory(String option) { 422 this(option, true); 423 } 424 425 LintCategory(String option, boolean annotationSuppression, String... aliases) { 426 this.option = option; 427 this.annotationSuppression = annotationSuppression; 428 ArrayList<String> optionList = new ArrayList<>(1 + aliases.length); 429 optionList.add(option); 430 Collections.addAll(optionList, aliases); 431 this.optionList = Collections.unmodifiableList(optionList); 432 this.optionList.forEach(ident -> map.put(ident, this)); 433 } 434 435 /** 436 * Get the {@link LintCategory} having the given command line option. 437 * 438 * @param option lint category option string 439 * @return corresponding {@link LintCategory}, or empty if none exists 440 */ 441 public static Optional<LintCategory> get(String option) { 442 return Optional.ofNullable(map.get(option)); 443 } 444 445 /** 446 * Get all lint category option strings and aliases. 447 */ 448 public static Set<String> options() { 449 return Collections.unmodifiableSet(map.keySet()); 450 } 451 452 public static EnumSet<LintCategory> newEmptySet() { 453 return EnumSet.noneOf(LintCategory.class); 454 } 455 456 /** Get the "canonical" string representing this category in @SuppressAnnotations and -Xlint options. */ 457 public final String option; 458 459 /** Get a list containing "option" followed by zero or more aliases. */ 460 public final List<String> optionList; 461 462 /** Does this category support being suppressed by the {@code @SuppressWarnings} annotation? */ 463 public final boolean annotationSuppression; 464 } 465 466 /** 467 * Checks if a warning category is enabled. A warning category may be enabled 468 * on the command line, or by default, and can be temporarily disabled with 469 * the SuppressWarnings annotation. 470 */ 471 public boolean isEnabled(LintCategory lc) { 472 initializeRootIfNeeded(); 473 return values.contains(lc); 474 } 475 476 /** 477 * Checks is a warning category has been specifically suppressed, by means 478 * of the SuppressWarnings annotation, or, in the case of the deprecated 479 * category, whether it has been implicitly suppressed by virtue of the 480 * current entity being itself deprecated. 481 */ 482 public boolean isSuppressed(LintCategory lc) { 483 initializeRootIfNeeded(); 484 return suppressedValues.contains(lc); 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 // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions 505 private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) { 506 initializeSymbolsIfNeeded(); 507 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 508 attributes 509 .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym) 510 .map(this::suppressionsFrom) 511 .forEach(result::addAll); 512 return result; 513 } 514 515 // Given a @SuppressWarnings annotation, extract the recognized suppressions 516 private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) { 517 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 518 if (suppressWarnings.member(names.value) instanceof Attribute.Array values) { 519 for (Attribute value : values.values) { 520 Optional.of(value) 521 .filter(val -> val instanceof Attribute.Constant) 522 .map(val -> (String) ((Attribute.Constant) val).value) 523 .flatMap(LintCategory::get) 524 .filter(lc -> lc.annotationSuppression) 525 .ifPresent(result::add); 526 } 527 } 528 return result; 529 } 530 531 private void initializeSymbolsIfNeeded() { 532 if (syms == null) { 533 syms = Symtab.instance(context); 534 names = Names.instance(context); 535 } 536 } 537 }