8349847: Support configuring individual lint categories as errors

Reviewed-by: vromero
This commit is contained in:
Archie Cobbs 2025-10-07 19:32:08 +00:00
parent 6bfd018bea
commit 910bb68e51
13 changed files with 214 additions and 61 deletions

View File

@ -147,33 +147,10 @@ public class Lint {
// Process command line options on demand to allow use of root Lint early during startup
private void initializeRootIfNeeded() {
// Already initialized?
if (values != null)
return;
// Initialize enabled categories based on "-Xlint" flags
if (options.isSet(Option.XLINT) || options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_ALL)) {
// If -Xlint or -Xlint:all is given, enable all categories by default
values = EnumSet.allOf(LintCategory.class);
} else if (options.isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_NONE)) {
// if -Xlint:none is given, disable all categories by default
values = LintCategory.newEmptySet();
} else {
// otherwise, enable on-by-default categories
values = getDefaults();
if (values == null) {
values = options.getLintCategoriesOf(Option.XLINT, this::getDefaults);
suppressedValues = LintCategory.newEmptySet();
}
// Look for specific overrides
for (LintCategory lc : LintCategory.values()) {
if (options.isLintExplicitlyEnabled(lc)) {
values.add(lc);
} else if (options.isLintExplicitlyDisabled(lc)) {
values.remove(lc);
}
}
suppressedValues = LintCategory.newEmptySet();
}
// Obtain the set of on-by-default categories. Note that for a few categories,

View File

@ -31,6 +31,7 @@ import java.nio.file.InvalidPathException;
import java.nio.file.ReadOnlyFileSystemException;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -55,6 +56,7 @@ import javax.tools.StandardLocation;
import com.sun.source.util.TaskEvent;
import com.sun.tools.javac.api.MultiTaskListener;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Lint.LintCategory;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
@ -440,7 +442,8 @@ public class JavaCompiler {
context.get(DiagnosticListener.class) != null;
devVerbose = options.isSet("dev");
processPcks = options.isSet("process.packages");
werror = options.isSet(WERROR);
werrorAny = options.isSet(WERROR) || options.isSet(WERROR_CUSTOM, Option.LINT_CUSTOM_ALL);
werrorLint = options.getLintCategoriesOf(WERROR, LintCategory::newEmptySet);
verboseCompilePolicy = options.isSet("verboseCompilePolicy");
@ -513,9 +516,13 @@ public class JavaCompiler {
*/
protected boolean processPcks;
/** Switch: treat warnings as errors
/** Switch: treat any kind of warning (lint or non-lint) as an error.
*/
protected boolean werror;
protected boolean werrorAny;
/** Switch: treat lint warnings in the specified {@link LintCategory}s as errors.
*/
protected EnumSet<LintCategory> werrorLint;
/** Switch: is annotation processing requested explicitly via
* CompilationTask.setProcessors?
@ -581,12 +588,20 @@ public class JavaCompiler {
*/
public int errorCount() {
log.reportOutstandingWarnings();
if (werror && log.nerrors == 0 && log.nwarnings > 0) {
if (log.nerrors == 0 && log.nwarnings > 0 &&
(werrorAny || werrorLint.clone().removeAll(log.lintWarnings))) {
log.error(Errors.WarningsAndWerror);
}
return log.nerrors;
}
/**
* Should warnings in the given lint category be treated as errors due to a {@code -Werror} flag?
*/
public boolean isWerror(LintCategory lc) {
return werrorAny || werrorLint.contains(lc);
}
protected final <T> Queue<T> stopIfError(CompileState cs, Queue<T> queue) {
return shouldStop(cs) ? new ListBuffer<T>() : queue;
}

View File

@ -563,6 +563,8 @@ public enum Option {
// treat warnings as errors
WERROR("-Werror", "opt.Werror", STANDARD, BASIC),
WERROR_CUSTOM("-Werror:", "opt.arg.Werror", "opt.Werror.custom", STANDARD, BASIC, ANYOF, getXLintChoices()),
// prompt after each error
// new Option("-prompt", "opt.prompt"),
PROMPT("-prompt", null, HIDDEN, BASIC),
@ -1132,6 +1134,22 @@ public enum Option {
return Option.valueOf(name() + "_CUSTOM");
}
/**
* Like {@link #getCustom} but also requires that the custom option supports lint categories.
*
* <p>
* In practice, that means {@code option} must be {@link Option#LINT} or {@link Option#WERROR}.
*
* @param option regular option
* @return corresponding lint custom option
* @throws IllegalArgumentException if no such option exists
*/
public Option getLintCustom() {
if (this == XLINT || this == WERROR)
return getCustom();
throw new IllegalArgumentException();
}
public boolean isInBasicOptionGroup() {
return group == BASIC;
}

View File

@ -211,7 +211,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
}
fatalErrors = options.isSet("fatalEnterError");
showResolveErrors = options.isSet("showResolveErrors");
werror = options.isSet(Option.WERROR);
werror = compiler.isWerror(PROCESSING);
fileManager = context.get(JavaFileManager.class);
platformAnnotations = initPlatformAnnotations();

View File

@ -95,7 +95,13 @@ javac.opt.source=\
Provide source compatibility with the specified Java SE release.\n\
Supported releases: \n {0}
javac.opt.Werror=\
Terminate compilation if warnings occur
Terminate compilation if any warnings occur
javac.opt.arg.Werror=\
<key>(,<key>)*
javac.opt.Werror.custom=\
Specify lint categories for which warnings should terminate compilation,\n\
separated by comma. Precede a key by ''-'' to exclude the specified category.\n\
Use --help-lint to see the supported keys.
javac.opt.A=\
Options to pass to annotation processors
javac.opt.implicit=\
@ -330,7 +336,7 @@ javac.opt.X=\
javac.opt.help=\
Print this help message
javac.opt.help.lint=\
Print the supported keys for -Xlint
Print the supported keys for -Xlint and -Werror
javac.opt.help.lint.header=\
The supported keys for -Xlint are:
javac.opt.help.lint.enabled.by.default=\

View File

@ -171,7 +171,7 @@ public class Log extends AbstractLog {
lint.isEnabled(category) : // then emit if the category is enabled
category.annotationSuppression ? // else emit if the category is not suppressed, where
!lint.isSuppressed(category) : // ...suppression happens via @SuppressWarnings
!options.isLintDisabled(category); // ...suppression happens via -Xlint:-category
!options.isDisabled(Option.XLINT, category); // ...suppression happens via -Xlint:-category
if (!emit)
return;
}
@ -553,10 +553,14 @@ public class Log extends AbstractLog {
*/
public int nerrors = 0;
/** The number of warnings encountered so far.
/** The total number of warnings encountered so far.
*/
public int nwarnings = 0;
/** Tracks whether any warnings have been encountered in each {@link LintCategory}.
*/
public final EnumSet<LintCategory> lintWarnings = LintCategory.newEmptySet();
/** The number of errors encountered after MaxErrors was reached.
*/
public int nsuppressederrors = 0;
@ -885,6 +889,7 @@ public class Log extends AbstractLog {
public void clear() {
recorded.clear();
sourceMap.clear();
lintWarnings.clear();
nerrors = 0;
nwarnings = 0;
nsuppressederrors = 0;
@ -940,7 +945,6 @@ public class Log extends AbstractLog {
// Strict warnings are always emitted
if (diagnostic.isFlagSet(STRICT)) {
writeDiagnostic(diagnostic);
nwarnings++;
return;
}
@ -948,7 +952,6 @@ public class Log extends AbstractLog {
if (emitWarnings || diagnostic.isMandatory()) {
if (nwarnings < MaxWarnings) {
writeDiagnostic(diagnostic);
nwarnings++;
} else {
nsuppressedwarns++;
}
@ -959,7 +962,6 @@ public class Log extends AbstractLog {
if (diagnostic.isFlagSet(API) || shouldReport(diagnostic)) {
if (nerrors < MaxErrors) {
writeDiagnostic(diagnostic);
nerrors++;
} else {
nsuppressederrors++;
}
@ -973,9 +975,25 @@ public class Log extends AbstractLog {
}
/**
* Write out a diagnostic.
* Write out a diagnostic and bump the warning and error counters as needed.
*/
protected void writeDiagnostic(JCDiagnostic diag) {
// Increment counter(s)
switch (diag.getType()) {
case WARNING:
nwarnings++;
Optional.of(diag)
.map(JCDiagnostic::getLintCategory)
.ifPresent(lintWarnings::add);
break;
case ERROR:
nerrors++;
break;
default:
break;
}
if (diagListener != null) {
diagListener.report(diag);
return;

View File

@ -173,66 +173,139 @@ public class Options {
/**
* Determine if a specific {@link LintCategory} is enabled via a custom
* option flag of the form {@code -Xlint}, {@code -Xlint:all}, or {@code -Xlint:key}.
* option flag of the form {@code -Flag}, {@code -Flag:all}, or {@code -Flag:key}.
*
* <p>
* The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}).
*
* <p>
* Note: It's possible the category was also disabled; this method does not check that.
*
* @param option the plain (non-custom) version of the option (e.g., {@link Option#XLINT})
* @param lc the {@link LintCategory} in question
* @return true if {@code lc} has been enabled
* @return true if {@code lc} is enabled via {@code option}'s lint custom variant (e.g., {@link Option#XLINT_CUSTOM})
* @throws IllegalArgumentException if there is no lint custom variant of {@code option}
*/
public boolean isLintEnabled(LintCategory lc) {
return isLintExplicitlyEnabled(lc) ||
isSet(Option.XLINT_CUSTOM) ||
isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_ALL);
public boolean isEnabled(Option option, LintCategory lc) {
Option custom = option.getLintCustom();
return isExplicitlyEnabled(option, lc) || isSet(custom) || isSet(custom, Option.LINT_CUSTOM_ALL);
}
/**
* Determine if a specific {@link LintCategory} is disabled via a custom
* option flag of the form {@code -Xlint:none} or {@code -Xlint:-key}.
* option flag of the form {@code -Flag:none} or {@code -Flag:-key}.
*
* <p>
* The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}).
*
* <p>
* Note: It's possible the category was also enabled; this method does not check that.
*
* @param option the plain (non-custom) version of the option (e.g., {@link Option#XLINT})
* @param lc the {@link LintCategory} in question
* @return true if {@code lc} has been disabled
* @return true if {@code lc} is disabled via {@code option}'s lint custom variant (e.g., {@link Option#XLINT_CUSTOM})
* @throws IllegalArgumentException if there is no lint custom variant of {@code option}
*/
public boolean isLintDisabled(LintCategory lc) {
return isLintExplicitlyDisabled(lc) || isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_NONE);
public boolean isDisabled(Option option, LintCategory lc) {
return isExplicitlyDisabled(option, lc) || isSet(option.getLintCustom(), Option.LINT_CUSTOM_NONE);
}
/**
* Determine if a specific {@link LintCategory} is explicitly enabled via a custom
* option flag of the form {@code -Xlint:key}.
* option flag of the form {@code -Flag:key}.
*
* <p>
* Note: This does not check for option flags of the form {@code -Xlint} or {@code -Xlint:all}.
* The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}).
*
* <p>
* Note: This does not check for option flags of the form {@code -Flag} or {@code -Flag:all}.
*
* <p>
* Note: It's possible the category was also disabled; this method does not check that.
*
* @param option the plain (non-custom) version of the option (e.g., {@link Option#XLINT})
* @param lc the {@link LintCategory} in question
* @return true if {@code lc} has been explicitly enabled
* @return true if {@code lc} is explicitly enabled via {@code option}'s lint custom variant (e.g., {@link Option#XLINT_CUSTOM})
* @throws IllegalArgumentException if there is no lint custom variant of {@code option}
*/
public boolean isLintExplicitlyEnabled(LintCategory lc) {
return lc.optionList.stream().anyMatch(alias -> isSet(Option.XLINT_CUSTOM, alias));
public boolean isExplicitlyEnabled(Option option, LintCategory lc) {
Option customOption = option.getLintCustom();
return lc.optionList.stream().anyMatch(alias -> isSet(customOption, alias));
}
/**
* Determine if a specific {@link LintCategory} is explicitly disabled via a custom
* option flag of the form {@code -Xlint:-key}.
* option flag of the form {@code -Flag:-key}.
*
* <p>
* Note: This does not check for an option flag of the form {@code -Xlint:none}.
* The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}).
*
* <p>
* Note: This does not check for an option flag of the form {@code -Flag:none}.
*
* <p>
* Note: It's possible the category was also enabled; this method does not check that.
*
* @param option the plain (non-custom) version of the option (e.g., {@link Option#XLINT})
* @param lc the {@link LintCategory} in question
* @return true if {@code lc} has been explicitly disabled
* @return true if {@code lc} is explicitly disabled via {@code option}'s lint custom variant (e.g., {@link Option#XLINT_CUSTOM})
* @throws IllegalArgumentException if there is no lint custom variant of {@code option}
*/
public boolean isLintExplicitlyDisabled(LintCategory lc) {
return lc.optionList.stream().anyMatch(alias -> isSet(Option.XLINT_CUSTOM, "-" + alias));
public boolean isExplicitlyDisabled(Option option, LintCategory lc) {
Option customOption = option.getLintCustom();
return lc.optionList.stream().anyMatch(alias -> isSet(customOption, "-" + alias));
}
/**
* Collect the set of {@link LintCategory}s specified by option flag(s) of the form
* {@code -Flag} and/or {@code -Flag:[-]key,[-]key,...}.
*
* <p>
* The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}).
*
* <p>
* The set of categories is calculated as follows. First, an initial set is created:
* <ul>
* <li>If {@code -Flag} or {@code -Flag:all} appears, the initial set contains all categories; otherwise,
* <li>If {@code -Flag:none} appears, the initial set is empty; otherwise,
* <li>The {@code defaults} parameter is invoked to construct an initial set.
* </ul>
* Next, for each lint category key {@code key}:
* <ul>
* <li>If {@code -Flag:key} flag appears, the corresponding category is added to the set; otherwise
* <li>If {@code -Flag:-key} flag appears, the corresponding category is removed to the set
* </ul>
* Unrecognized {@code key}s are ignored.
*
* @param option the plain (non-custom) version of the option (e.g., {@link Option#XLINT})
* @param defaults populates the default set, or null for an empty default set
* @return the specified set of categories
* @throws IllegalArgumentException if there is no lint custom variant of {@code option}
*/
public EnumSet<LintCategory> getLintCategoriesOf(Option option, Supplier<? extends EnumSet<LintCategory>> defaults) {
// Create the initial set
EnumSet<LintCategory> categories;
Option customOption = option.getLintCustom();
if (isSet(option) || isSet(customOption, Option.LINT_CUSTOM_ALL)) {
categories = EnumSet.allOf(LintCategory.class);
} else if (isSet(customOption, Option.LINT_CUSTOM_NONE)) {
categories = EnumSet.noneOf(LintCategory.class);
} else {
categories = defaults.get();
}
// Apply specific overrides
for (LintCategory category : LintCategory.values()) {
if (isExplicitlyEnabled(option, category)) {
categories.add(category);
} else if (isExplicitlyDisabled(option, category)) {
categories.remove(category);
}
}
// Done
return categories;
}
public void put(String name, String value) {

View File

@ -1,5 +1,5 @@
---
# Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@ -448,7 +448,16 @@ file system locations may be directories, JAR files or JMOD files.
: Prints version information.
<a id="option-Werror">`-Werror`</a>
: Terminates compilation when warnings occur.
: Terminates compilation when any warnings occur; this includes warnings in all lint
categories, as well as non-lint warnings.
<a id="option-Werror-custom">`-Werror:`\[`-`\]*key*(`,`\[`-`\]*key*)\*</a>
: Specify lint categories for which warnings should terminate compilation. The keys
`all` and `none` include or exclude all categories (respectively); other keys include
the corresponding category, or exclude it if preceded by a hyphen (`-`). By default,
no categories are included. In order to terminate compilation, the category must also
be enabled (via [`-Xlint`](#option-Xlint-custom), if necessary).
See [`-Xlint`](#option-Xlint-custom) below for the list of lint category keys.
### Extra Options

View File

@ -0,0 +1,4 @@
WerrorLint.java:20:19: compiler.warn.strictfp
- compiler.err.warnings.and.werror
1 error
1 warning

View File

@ -0,0 +1,5 @@
WerrorLint.java:20:19: compiler.warn.strictfp
WerrorLint.java:21:30: compiler.warn.empty.if
- compiler.err.warnings.and.werror
1 error
2 warnings

View File

@ -0,0 +1,23 @@
/*
* @test /nodynamiccopyright/
* @bug 8349847
*
* @compile -XDrawDiagnostics -Xlint:none WerrorLint.java
* @compile -XDrawDiagnostics -Xlint:none -Werror WerrorLint.java
* @compile -XDrawDiagnostics -Xlint:none -Werror:empty WerrorLint.java
* @compile -XDrawDiagnostics -Xlint:none -Werror:strictfp WerrorLint.java
* @compile/ref=WerrorLint.w2.out -XDrawDiagnostics -Xlint:all WerrorLint.java
* @compile/fail/ref=WerrorLint.e2.out -XDrawDiagnostics -Xlint:all -Werror WerrorLint.java
* @compile/fail/ref=WerrorLint.e2.out -XDrawDiagnostics -Xlint:all -Werror:empty WerrorLint.java
* @compile/fail/ref=WerrorLint.e2.out -XDrawDiagnostics -Xlint:all -Werror:strictfp WerrorLint.java
* @compile/ref=WerrorLint.w1.out -XDrawDiagnostics WerrorLint.java
* @compile/fail/ref=WerrorLint.e1.out -XDrawDiagnostics -Werror WerrorLint.java
* @compile/ref=WerrorLint.w1.out -XDrawDiagnostics -Werror:empty WerrorLint.java
* @compile/fail/ref=WerrorLint.e1.out -XDrawDiagnostics -Werror:strictfp WerrorLint.java
*/
class WerrorLint {
strictfp void m() { // [strictfp] - this category is enabled by default
if (hashCode() == 1) ; // [empty] - this category is disabled by default
}
}

View File

@ -0,0 +1,2 @@
WerrorLint.java:20:19: compiler.warn.strictfp
1 warning

View File

@ -0,0 +1,3 @@
WerrorLint.java:20:19: compiler.warn.strictfp
WerrorLint.java:21:30: compiler.warn.empty.if
2 warnings