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