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 // Process command line options on demand to allow use of root Lint early during startup
private void initializeRootIfNeeded() { private void initializeRootIfNeeded() {
if (values == null) {
// Already initialized? values = options.getLintCategoriesOf(Option.XLINT, this::getDefaults);
if (values != null) suppressedValues = LintCategory.newEmptySet();
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();
} }
// 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, // 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.nio.file.ReadOnlyFileSystemException;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -55,6 +56,7 @@ import javax.tools.StandardLocation;
import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskEvent;
import com.sun.tools.javac.api.MultiTaskListener; import com.sun.tools.javac.api.MultiTaskListener;
import com.sun.tools.javac.code.*; 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.Source.Feature;
import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.CompletionFailure; import com.sun.tools.javac.code.Symbol.CompletionFailure;
@ -440,7 +442,8 @@ public class JavaCompiler {
context.get(DiagnosticListener.class) != null; context.get(DiagnosticListener.class) != null;
devVerbose = options.isSet("dev"); devVerbose = options.isSet("dev");
processPcks = options.isSet("process.packages"); 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"); verboseCompilePolicy = options.isSet("verboseCompilePolicy");
@ -513,9 +516,13 @@ public class JavaCompiler {
*/ */
protected boolean processPcks; 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 /** Switch: is annotation processing requested explicitly via
* CompilationTask.setProcessors? * CompilationTask.setProcessors?
@ -581,12 +588,20 @@ public class JavaCompiler {
*/ */
public int errorCount() { public int errorCount() {
log.reportOutstandingWarnings(); 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); log.error(Errors.WarningsAndWerror);
} }
return log.nerrors; 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) { protected final <T> Queue<T> stopIfError(CompileState cs, Queue<T> queue) {
return shouldStop(cs) ? new ListBuffer<T>() : queue; return shouldStop(cs) ? new ListBuffer<T>() : queue;
} }

View File

@ -563,6 +563,8 @@ public enum Option {
// treat warnings as errors // treat warnings as errors
WERROR("-Werror", "opt.Werror", STANDARD, BASIC), WERROR("-Werror", "opt.Werror", STANDARD, BASIC),
WERROR_CUSTOM("-Werror:", "opt.arg.Werror", "opt.Werror.custom", STANDARD, BASIC, ANYOF, getXLintChoices()),
// prompt after each error // prompt after each error
// new Option("-prompt", "opt.prompt"), // new Option("-prompt", "opt.prompt"),
PROMPT("-prompt", null, HIDDEN, BASIC), PROMPT("-prompt", null, HIDDEN, BASIC),
@ -1132,6 +1134,22 @@ public enum Option {
return Option.valueOf(name() + "_CUSTOM"); 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() { public boolean isInBasicOptionGroup() {
return group == BASIC; return group == BASIC;
} }

View File

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

View File

@ -95,7 +95,13 @@ javac.opt.source=\
Provide source compatibility with the specified Java SE release.\n\ Provide source compatibility with the specified Java SE release.\n\
Supported releases: \n {0} Supported releases: \n {0}
javac.opt.Werror=\ 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=\ javac.opt.A=\
Options to pass to annotation processors Options to pass to annotation processors
javac.opt.implicit=\ javac.opt.implicit=\
@ -330,7 +336,7 @@ javac.opt.X=\
javac.opt.help=\ javac.opt.help=\
Print this help message Print this help message
javac.opt.help.lint=\ javac.opt.help.lint=\
Print the supported keys for -Xlint Print the supported keys for -Xlint and -Werror
javac.opt.help.lint.header=\ javac.opt.help.lint.header=\
The supported keys for -Xlint are: The supported keys for -Xlint are:
javac.opt.help.lint.enabled.by.default=\ 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 lint.isEnabled(category) : // then emit if the category is enabled
category.annotationSuppression ? // else emit if the category is not suppressed, where category.annotationSuppression ? // else emit if the category is not suppressed, where
!lint.isSuppressed(category) : // ...suppression happens via @SuppressWarnings !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) if (!emit)
return; return;
} }
@ -553,10 +553,14 @@ public class Log extends AbstractLog {
*/ */
public int nerrors = 0; public int nerrors = 0;
/** The number of warnings encountered so far. /** The total number of warnings encountered so far.
*/ */
public int nwarnings = 0; 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. /** The number of errors encountered after MaxErrors was reached.
*/ */
public int nsuppressederrors = 0; public int nsuppressederrors = 0;
@ -885,6 +889,7 @@ public class Log extends AbstractLog {
public void clear() { public void clear() {
recorded.clear(); recorded.clear();
sourceMap.clear(); sourceMap.clear();
lintWarnings.clear();
nerrors = 0; nerrors = 0;
nwarnings = 0; nwarnings = 0;
nsuppressederrors = 0; nsuppressederrors = 0;
@ -940,7 +945,6 @@ public class Log extends AbstractLog {
// Strict warnings are always emitted // Strict warnings are always emitted
if (diagnostic.isFlagSet(STRICT)) { if (diagnostic.isFlagSet(STRICT)) {
writeDiagnostic(diagnostic); writeDiagnostic(diagnostic);
nwarnings++;
return; return;
} }
@ -948,7 +952,6 @@ public class Log extends AbstractLog {
if (emitWarnings || diagnostic.isMandatory()) { if (emitWarnings || diagnostic.isMandatory()) {
if (nwarnings < MaxWarnings) { if (nwarnings < MaxWarnings) {
writeDiagnostic(diagnostic); writeDiagnostic(diagnostic);
nwarnings++;
} else { } else {
nsuppressedwarns++; nsuppressedwarns++;
} }
@ -959,7 +962,6 @@ public class Log extends AbstractLog {
if (diagnostic.isFlagSet(API) || shouldReport(diagnostic)) { if (diagnostic.isFlagSet(API) || shouldReport(diagnostic)) {
if (nerrors < MaxErrors) { if (nerrors < MaxErrors) {
writeDiagnostic(diagnostic); writeDiagnostic(diagnostic);
nerrors++;
} else { } else {
nsuppressederrors++; 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) { 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) { if (diagListener != null) {
diagListener.report(diag); diagListener.report(diag);
return; return;

View File

@ -173,66 +173,139 @@ public class Options {
/** /**
* Determine if a specific {@link LintCategory} is enabled via a custom * 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> * <p>
* Note: It's possible the category was also disabled; this method does not check that. * 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 * @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) { public boolean isEnabled(Option option, LintCategory lc) {
return isLintExplicitlyEnabled(lc) || Option custom = option.getLintCustom();
isSet(Option.XLINT_CUSTOM) || return isExplicitlyEnabled(option, lc) || isSet(custom) || isSet(custom, Option.LINT_CUSTOM_ALL);
isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_ALL);
} }
/** /**
* Determine if a specific {@link LintCategory} is disabled via a custom * 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> * <p>
* Note: It's possible the category was also enabled; this method does not check that. * 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 * @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) { public boolean isDisabled(Option option, LintCategory lc) {
return isLintExplicitlyDisabled(lc) || isSet(Option.XLINT_CUSTOM, Option.LINT_CUSTOM_NONE); return isExplicitlyDisabled(option, lc) || isSet(option.getLintCustom(), Option.LINT_CUSTOM_NONE);
} }
/** /**
* Determine if a specific {@link LintCategory} is explicitly enabled via a custom * 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> * <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> * <p>
* Note: It's possible the category was also disabled; this method does not check that. * 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 * @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) { public boolean isExplicitlyEnabled(Option option, LintCategory lc) {
return lc.optionList.stream().anyMatch(alias -> isSet(Option.XLINT_CUSTOM, alias)); 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 * 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> * <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> * <p>
* Note: It's possible the category was also enabled; this method does not check that. * 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 * @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) { public boolean isExplicitlyDisabled(Option option, LintCategory lc) {
return lc.optionList.stream().anyMatch(alias -> isSet(Option.XLINT_CUSTOM, "-" + alias)); 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) { 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. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # 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. : Prints version information.
<a id="option-Werror">`-Werror`</a> <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 ### 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