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