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 }