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 113 // These are initialized lazily to avoid dependency loops 114 private Symtab syms; 115 private Names names; 116 117 // Invariant: it's never the case that a category is in both "values" and "suppressedValues" 118 private EnumSet<LintCategory> values; 119 private EnumSet<LintCategory> suppressedValues; 120 121 private static final Map<String, LintCategory> map = new ConcurrentHashMap<>(20); 122 123 @SuppressWarnings("this-escape") 124 protected Lint(Context context) { 125 this.context = context; 126 context.put(lintKey, this); 127 options = Options.instance(context); 128 } 129 130 // Instantiate a non-root ("symbol scoped") instance 131 protected Lint(Lint other) { 132 other.initializeRootIfNeeded(); 133 this.context = other.context; 134 this.options = other.options; 135 this.syms = other.syms; 136 this.names = other.names; 137 this.values = other.values.clone(); 138 this.suppressedValues = other.suppressedValues.clone(); 139 } 140 141 // Process command line options on demand to allow use of root Lint early during startup 142 private void initializeRootIfNeeded() { 143 144 // Already initialized? 145 if (values != null) 146 return; 147 148 // Initialize enabled categories based on "-Xlint" flags 149 if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, "all")) { 150 // If -Xlint or -Xlint:all is given, enable all categories by default 151 values = EnumSet.allOf(LintCategory.class); 152 } else if (options.isSet(Option.XLINT_CUSTOM, "none")) { 153 // if -Xlint:none is given, disable all categories by default 154 values = LintCategory.newEmptySet(); 155 } else { 156 // otherwise, enable on-by-default categories 157 values = LintCategory.newEmptySet(); 158 159 Source source = Source.instance(context); 160 if (source.compareTo(Source.JDK9) >= 0) { 161 values.add(LintCategory.DEP_ANN); 162 } 163 if (Source.Feature.REDUNDANT_STRICTFP.allowedInSource(source)) { 164 values.add(LintCategory.STRICTFP); 165 } 166 values.add(LintCategory.REQUIRES_TRANSITIVE_AUTOMATIC); 167 values.add(LintCategory.OPENS); 168 values.add(LintCategory.MODULE); 169 values.add(LintCategory.REMOVAL); 170 if (!options.isSet(Option.PREVIEW)) { 171 values.add(LintCategory.PREVIEW); 172 } 173 values.add(LintCategory.SYNCHRONIZATION); 174 values.add(LintCategory.INCUBATING); 175 } 176 177 // Look for specific overrides 178 for (LintCategory lc : LintCategory.values()) { 179 if (options.isSet(Option.XLINT_CUSTOM, lc.option)) { 180 values.add(lc); 181 } else if (options.isSet(Option.XLINT_CUSTOM, "-" + lc.option)) { 182 values.remove(lc); 183 } 184 } 185 186 suppressedValues = LintCategory.newEmptySet(); 187 } 188 189 @Override 190 public String toString() { 191 initializeRootIfNeeded(); 192 return "Lint:[enable" + values + ",suppress" + suppressedValues + "]"; 193 } 194 195 /** 196 * Categories of warnings that can be generated by the compiler. 197 */ 198 public enum LintCategory { 199 /** 200 * Warn when code refers to a auxiliary class that is hidden in a source file (ie source file name is 201 * different from the class name, and the type is not properly nested) and the referring code 202 * is not located in the same source file. 203 */ 204 AUXILIARYCLASS("auxiliaryclass"), 205 206 /** 207 * Warn about use of unnecessary casts. 208 */ 209 CAST("cast"), 210 211 /** 212 * Warn about issues related to classfile contents 213 */ 214 CLASSFILE("classfile"), 215 216 /** 217 * Warn about "dangling" documentation comments, 218 * not attached to any declaration. 219 */ 220 DANGLING_DOC_COMMENTS("dangling-doc-comments"), 221 222 /** 223 * Warn about use of deprecated items. 224 */ 225 DEPRECATION("deprecation"), 226 227 /** 228 * Warn about items which are documented with an {@code @deprecated} JavaDoc 229 * comment, but which do not have {@code @Deprecated} annotation. 230 */ 231 DEP_ANN("dep-ann"), 232 233 /** 234 * Warn about division by constant integer 0. 235 */ 236 DIVZERO("divzero"), 237 238 /** 239 * Warn about empty statement after if. 240 */ 241 EMPTY("empty"), 242 243 /** 244 * Warn about issues regarding module exports. 245 */ 246 EXPORTS("exports"), 247 248 /** 249 * Warn about falling through from one case of a switch statement to the next. 250 */ 251 FALLTHROUGH("fallthrough"), 252 253 /** 254 * Warn about finally clauses that do not terminate normally. 255 */ 256 FINALLY("finally"), 257 258 /** 259 * Warn about use of incubating modules. 260 */ 261 INCUBATING("incubating"), 262 263 /** 264 * Warn about compiler possible lossy conversions. 265 */ 266 LOSSY_CONVERSIONS("lossy-conversions"), 267 268 /** 269 * Warn about compiler generation of a default constructor. 270 */ 271 MISSING_EXPLICIT_CTOR("missing-explicit-ctor"), 272 273 /** 274 * Warn about module system related issues. 275 */ 276 MODULE("module"), 277 278 /** 279 * Warn about issues regarding module opens. 280 */ 281 OPENS("opens"), 282 283 /** 284 * Warn about issues relating to use of command line options 285 */ 286 OPTIONS("options"), 287 288 /** 289 * Warn when any output file is written to more than once. 290 */ 291 OUTPUT_FILE_CLASH("output-file-clash"), 292 293 /** 294 * Warn about issues regarding method overloads. 295 */ 296 OVERLOADS("overloads"), 297 298 /** 299 * Warn about issues regarding method overrides. 300 */ 301 OVERRIDES("overrides"), 302 303 /** 304 * Warn about invalid path elements on the command line. 305 * Such warnings cannot be suppressed with the SuppressWarnings 306 * annotation. 307 */ 308 PATH("path"), 309 310 /** 311 * Warn about issues regarding annotation processing. 312 */ 313 PROCESSING("processing"), 314 315 /** 316 * Warn about unchecked operations on raw types. 317 */ 318 RAW("rawtypes"), 319 320 /** 321 * Warn about use of deprecated-for-removal items. 322 */ 323 REMOVAL("removal"), 324 325 /** 326 * Warn about use of automatic modules in the requires clauses. 327 */ 328 REQUIRES_AUTOMATIC("requires-automatic"), 329 330 /** 331 * Warn about automatic modules in requires transitive. 332 */ 333 REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic"), 334 335 /** 336 * Warn about Serializable classes that do not provide a serial version ID. 337 */ 338 SERIAL("serial"), 339 340 /** 341 * Warn about issues relating to use of statics 342 */ 343 STATIC("static"), 344 345 /** 346 * Warn about unnecessary uses of the strictfp modifier 347 */ 348 STRICTFP("strictfp"), 349 350 /** 351 * Warn about synchronization attempts on instances of @ValueBased classes. 352 */ 353 SYNCHRONIZATION("synchronization"), 354 355 /** 356 * Warn about issues relating to use of text blocks 357 */ 358 TEXT_BLOCKS("text-blocks"), 359 360 /** 361 * Warn about possible 'this' escapes before subclass instance is fully initialized. 362 */ 363 THIS_ESCAPE("this-escape"), 364 365 /** 366 * Warn about issues relating to use of try blocks (i.e. try-with-resources) 367 */ 368 TRY("try"), 369 370 /** 371 * Warn about unchecked operations on raw types. 372 */ 373 UNCHECKED("unchecked"), 374 375 /** 376 * Warn about potentially unsafe vararg methods 377 */ 378 VARARGS("varargs"), 379 380 /** 381 * Warn about use of preview features. 382 */ 383 PREVIEW("preview"), 384 385 /** 386 * Warn about use of restricted methods. 387 */ 388 RESTRICTED("restricted"); 389 390 LintCategory(String option) { 391 this.option = option; 392 map.put(option, this); 393 } 394 395 /** 396 * Get the {@link LintCategory} having the given command line option. 397 * 398 * @param option lint category option string 399 * @return corresponding {@link LintCategory}, or empty if none exists 400 */ 401 public static Optional<LintCategory> get(String option) { 402 return Optional.ofNullable(map.get(option)); 403 } 404 405 public static EnumSet<LintCategory> newEmptySet() { 406 return EnumSet.noneOf(LintCategory.class); 407 } 408 409 /** Get the string representing this category in @SuppressAnnotations and -Xlint options. */ 410 public final String option; 411 } 412 413 /** 414 * Checks if a warning category is enabled. A warning category may be enabled 415 * on the command line, or by default, and can be temporarily disabled with 416 * the SuppressWarnings annotation. 417 */ 418 public boolean isEnabled(LintCategory lc) { 419 initializeRootIfNeeded(); 420 return values.contains(lc); 421 } 422 423 /** 424 * Checks is a warning category has been specifically suppressed, by means 425 * of the SuppressWarnings annotation, or, in the case of the deprecated 426 * category, whether it has been implicitly suppressed by virtue of the 427 * current entity being itself deprecated. 428 */ 429 public boolean isSuppressed(LintCategory lc) { 430 initializeRootIfNeeded(); 431 return suppressedValues.contains(lc); 432 } 433 434 /** 435 * Helper method. Log a lint warning if its lint category is enabled. 436 * 437 * @param log warning destination 438 * @param warning key for the localized warning message 439 */ 440 public void logIfEnabled(Log log, LintWarning warning) { 441 logIfEnabled(log, null, warning); 442 } 443 444 /** 445 * Helper method. Log a lint warning if its lint category is enabled. 446 * 447 * @param log warning destination 448 * @param pos source position at which to report the warning 449 * @param warning key for the localized warning message 450 */ 451 public void logIfEnabled(Log log, DiagnosticPosition pos, LintWarning warning) { 452 if (isEnabled(warning.getLintCategory())) { 453 log.warning(pos, warning); 454 } 455 } 456 457 /** 458 * Obtain the set of recognized lint warning categories suppressed at the given symbol's declaration. 459 * 460 * <p> 461 * This set can be non-empty only if the symbol is annotated with either 462 * @SuppressWarnings or @Deprecated. 463 * 464 * @param symbol symbol corresponding to a possibly-annotated declaration 465 * @return new warning suppressions applied to sym 466 */ 467 public EnumSet<LintCategory> suppressionsFrom(Symbol symbol) { 468 EnumSet<LintCategory> suppressions = suppressionsFrom(symbol.getDeclarationAttributes().stream()); 469 if (symbol.isDeprecated() && symbol.isDeprecatableViaAnnotation()) 470 suppressions.add(LintCategory.DEPRECATION); 471 return suppressions; 472 } 473 474 /** 475 * Retrieve the recognized lint categories suppressed by the given @SuppressWarnings annotation. 476 * 477 * @param annotation @SuppressWarnings annotation, or null 478 * @return set of lint categories, possibly empty but never null 479 */ 480 private EnumSet<LintCategory> suppressionsFrom(JCAnnotation annotation) { 481 initializeSymbolsIfNeeded(); 482 if (annotation == null) 483 return LintCategory.newEmptySet(); 484 Assert.check(annotation.attribute.type.tsym == syms.suppressWarningsType.tsym); 485 return suppressionsFrom(Stream.of(annotation).map(anno -> anno.attribute)); 486 } 487 488 // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions 489 private EnumSet<LintCategory> suppressionsFrom(Stream<Attribute.Compound> attributes) { 490 initializeSymbolsIfNeeded(); 491 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 492 attributes 493 .filter(attribute -> attribute.type.tsym == syms.suppressWarningsType.tsym) 494 .map(this::suppressionsFrom) 495 .forEach(result::addAll); 496 return result; 497 } 498 499 // Given a @SuppressWarnings annotation, extract the recognized suppressions 500 private EnumSet<LintCategory> suppressionsFrom(Attribute.Compound suppressWarnings) { 501 EnumSet<LintCategory> result = LintCategory.newEmptySet(); 502 Attribute.Array values = (Attribute.Array)suppressWarnings.member(names.value); 503 for (Attribute value : values.values) { 504 Optional.of((String)((Attribute.Constant)value).value) 505 .flatMap(LintCategory::get) 506 .ifPresent(result::add); 507 } 508 return result; 509 } 510 511 private void initializeSymbolsIfNeeded() { 512 if (syms == null) { 513 syms = Symtab.instance(context); 514 names = Names.instance(context); 515 } 516 } 517 } --- EOF ---