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 CLASSFILE("classfile"), 218 219 /** 220 * Warn about "dangling" documentation comments, 221 * not attached to any declaration. 222 */ 223 DANGLING_DOC_COMMENTS("dangling-doc-comments"), 224 225 /** 226 * Warn about use of deprecated items. 227 */ 228 DEPRECATION("deprecation"), 229 230 /** 231 * Warn about items which are documented with an {@code @deprecated} JavaDoc 232 * comment, but which do not have {@code @Deprecated} annotation. 233 */ 234 DEP_ANN("dep-ann"), 235 236 /** 237 * Warn about division by constant integer 0. 238 */ 239 DIVZERO("divzero"), 240 241 /** 242 * Warn about empty statement after if. 243 */ 244 EMPTY("empty"), 245 246 /** 247 * Warn about issues regarding module exports. 248 */ 249 EXPORTS("exports"), 250 251 /** 252 * Warn about falling through from one case of a switch statement to the next. 253 */ 254 FALLTHROUGH("fallthrough"), 255 256 /** 257 * Warn about finally clauses that do not terminate normally. 258 */ 259 FINALLY("finally"), 260 261 /** 262 * Warn about use of incubating modules. 263 */ 264 INCUBATING("incubating"), 265 266 /** 267 * Warn about compiler possible lossy conversions. 268 */ 269 LOSSY_CONVERSIONS("lossy-conversions"), 270 271 /** 272 * Warn about compiler generation of a default constructor. 273 */ 274 MISSING_EXPLICIT_CTOR("missing-explicit-ctor"), 275 276 /** 277 * Warn about module system related issues. 278 */ 279 MODULE("module"), 280 281 /** 282 * Warn about issues regarding module opens. 283 */ 284 OPENS("opens"), 285 286 /** 287 * Warn about issues relating to use of command line options 288 */ 289 OPTIONS("options"), 290 291 /** 292 * Warn when any output file is written to more than once. 293 */ 294 OUTPUT_FILE_CLASH("output-file-clash"), 295 296 /** 297 * Warn about issues regarding method overloads. 298 */ 299 OVERLOADS("overloads"), 300 301 /** 302 * Warn about issues regarding method overrides. 303 */ 304 OVERRIDES("overrides"), 305 306 /** 307 * Warn about invalid path elements on the command line. 308 * Such warnings cannot be suppressed with the SuppressWarnings 309 * annotation. 310 */ 311 PATH("path"), 312 313 /** 314 * Warn about issues regarding annotation processing. 315 */ 316 PROCESSING("processing"), 317 318 /** 319 * Warn about unchecked operations on raw types. 320 */ 321 RAW("rawtypes"), 322 323 /** 324 * Warn about use of deprecated-for-removal items. 325 */ 326 REMOVAL("removal"), 327 328 /** 329 * Warn about use of automatic modules in the requires clauses. 330 */ 331 REQUIRES_AUTOMATIC("requires-automatic"), 332 333 /** 334 * Warn about automatic modules in requires transitive. 335 */ 336 REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic"), 337 338 /** 339 * Warn about Serializable classes that do not provide a serial version ID. 340 */ 341 SERIAL("serial"), 342 343 /** 344 * Warn about issues relating to use of statics 345 */ 346 STATIC("static"), 347 348 /** 349 * Warn about unnecessary uses of the strictfp modifier 350 */ 351 STRICTFP("strictfp"), 352 353 /** 354 * Warn about synchronization attempts on instances of @ValueBased classes. 355 */ 356 SYNCHRONIZATION("synchronization"), 357 358 /** 359 * Warn about issues relating to use of text blocks 360 */ 361 TEXT_BLOCKS("text-blocks"), 362 363 /** 364 * Warn about possible 'this' escapes before subclass instance is fully initialized. 365 */ 366 THIS_ESCAPE("this-escape"), 367 368 /** 369 * Warn about issues relating to use of try blocks (i.e. try-with-resources) 370 */ 371 TRY("try"), 372 373 /** 374 * Warn about unchecked operations on raw types. 375 */ 376 UNCHECKED("unchecked"), 377 378 /** 379 * Warn about potentially unsafe vararg methods 380 */ 381 VARARGS("varargs"), 382 383 /** 384 * Warn about use of preview features. 385 */ 386 PREVIEW("preview"), 387 388 /** 389 * Warn about use of restricted methods. 390 */ 391 RESTRICTED("restricted"); 392 393 LintCategory(String option) { 394 this.option = option; 395 map.put(option, this); 396 } 397 398 /** 399 * Get the {@link LintCategory} having the given command line option. 400 * 401 * @param option lint category option string 402 * @return corresponding {@link LintCategory}, or empty if none exists 403 */ 404 public static Optional<LintCategory> get(String option) { 405 return Optional.ofNullable(map.get(option)); 406 } 407 408 public static EnumSet<LintCategory> newEmptySet() { 409 return EnumSet.noneOf(LintCategory.class); 410 } 411 412 /** Get the string representing this category in @SuppressAnnotations and -Xlint options. */ 413 public final String option; 414 } 415 416 /** 417 * Checks if a warning category is enabled. A warning category may be enabled 418 * on the command line, or by default, and can be temporarily disabled with 419 * the SuppressWarnings annotation. 420 */ 421 public boolean isEnabled(LintCategory lc) { 422 initializeRootIfNeeded(); 423 return values.contains(lc); 424 } 425 426 /** 427 * Checks is a warning category has been specifically suppressed, by means 428 * of the SuppressWarnings annotation, or, in the case of the deprecated 429 * category, whether it has been implicitly suppressed by virtue of the 430 * current entity being itself deprecated. 431 */ 432 public boolean isSuppressed(LintCategory lc) { 433 initializeRootIfNeeded(); 434 return suppressedValues.contains(lc); 435 } 436 437 /** 438 * Helper method. Log a lint warning if its lint category is enabled. 439 * 440 * @param warning key for the localized warning message 441 */ 442 public void logIfEnabled(LintWarning warning) { 443 logIfEnabled(null, warning); 444 } 445 446 /** 447 * Helper method. Log a lint warning if its lint category is enabled. 448 * 449 * @param pos source position at which to report the warning 450 * @param warning key for the localized warning message 451 */ 452 public void logIfEnabled(DiagnosticPosition pos, LintWarning warning) { 453 if (isEnabled(warning.getLintCategory())) { 454 log.warning(pos, warning); 455 } 456 } 457 458 /** 459 * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration. 460 * 461 * <p> 462 * This set can be non-empty only if the symbol is annotated with either 463 * @SuppressWarnings or @Deprecated. 464 * 465 * @param symbol symbol corresponding to a possibly-annotated declaration 466 * @return new warning suppressions applied to sym 467 */ 468 public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) { 469 EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream()); 470 if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation()) 471 suppressions.add(LintCategory.DEPRECATION); 472 return suppressions; 473 } 474 475 /** 476 * Retrieve the recognized lint categories suppressed by the given @SuppressWarnings annotation. 477 * 478 * @param annotation @SuppressWarnings annotation, or null 479 * @return set of lint categories, possibly empty but never null 480 */ 481 private EnumSet<LintCategory> suppressionsFrom(JCAnnotation annotation) { 482 initializeSymbolsIfNeeded(); 483 if (annotation == null) 484 return LintCategory.newEmptySet(); 485 Assert.check(annotation.attribute.type.tsym == syms.suppressWarningsType.tsym); 486 return suppressionsFrom(Stream.of(annotation).map(anno -> anno.attribute)); 487 } 488 489 // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions 490 private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) { 491 initializeSymbolsIfNeeded(); 492 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 493 attributes 494 .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym) 495 .map(this::suppressionsFrom) 496 .forEach(result::addAll); 497 return result; 498 } 499 500 // Given a @SuppressWarnings annotation, extract the recognized suppressions 501 private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) { 502 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 503 Attribute.Array values = (Attribute.Array)suppressWarnings.member(names.value); 504 for (Attribute value : values.values) { 505 Optional.of((String)((Attribute.Constant)value).value) 506 .flatMap(LintCategory::get) 507 .ifPresent(result::add); 508 } 509 return result; 510 } 511 512 private void initializeSymbolsIfNeeded() { 513 if (syms == null) { 514 syms = Symtab.instance(context); 515 names = Names.instance(context); 516 } 517 } 518 }