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