diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 88c9da5d9e8..3a8b4e5dbea 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -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, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java index ee11304dce9..2469dc9e031 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java @@ -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 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 Queue stopIfError(CompileState cs, Queue queue) { return shouldStop(cs) ? new ListBuffer() : queue; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java index 8d5ad4c4d78..c14767a7a8c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java @@ -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. + * + *

+ * 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; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java index b28f19bd3af..74d082d4b64 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java @@ -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(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 15a63da06eb..6d4276c794b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -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=\ + (,)* +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=\ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java index 95458f339a1..24a77a751fd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Log.java @@ -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 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; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java index 32a31028b68..030e5b21758 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java @@ -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}. + * + *

+ * The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}). * *

* 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}. + * + *

+ * The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}). * *

* 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}. * *

- * 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}). + * + *

+ * Note: This does not check for option flags of the form {@code -Flag} or {@code -Flag:all}. * *

* 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}. * *

- * 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}). + * + *

+ * Note: This does not check for an option flag of the form {@code -Flag:none}. * *

* 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,...}. + * + *

+ * The given {@code option} must have a custom lint variant (available via {@link Option#getLintCustom}). + * + *

+ * The set of categories is calculated as follows. First, an initial set is created: + *

    + *
  • If {@code -Flag} or {@code -Flag:all} appears, the initial set contains all categories; otherwise, + *
  • If {@code -Flag:none} appears, the initial set is empty; otherwise, + *
  • The {@code defaults} parameter is invoked to construct an initial set. + *
+ * Next, for each lint category key {@code key}: + *
    + *
  • If {@code -Flag:key} flag appears, the corresponding category is added to the set; otherwise + *
  • If {@code -Flag:-key} flag appears, the corresponding category is removed to the set + *
+ * 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 getLintCategoriesOf(Option option, Supplier> defaults) { + + // Create the initial set + EnumSet 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) { diff --git a/src/jdk.compiler/share/man/javac.md b/src/jdk.compiler/share/man/javac.md index b8243cc78fb..46246624e53 100644 --- a/src/jdk.compiler/share/man/javac.md +++ b/src/jdk.compiler/share/man/javac.md @@ -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. `-Werror` -: Terminates compilation when warnings occur. +: Terminates compilation when any warnings occur; this includes warnings in all lint + categories, as well as non-lint warnings. + +`-Werror:`\[`-`\]*key*(`,`\[`-`\]*key*)\* +: 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 diff --git a/test/langtools/tools/javac/warnings/WerrorLint.e1.out b/test/langtools/tools/javac/warnings/WerrorLint.e1.out new file mode 100644 index 00000000000..ae99cfa5056 --- /dev/null +++ b/test/langtools/tools/javac/warnings/WerrorLint.e1.out @@ -0,0 +1,4 @@ +WerrorLint.java:20:19: compiler.warn.strictfp +- compiler.err.warnings.and.werror +1 error +1 warning diff --git a/test/langtools/tools/javac/warnings/WerrorLint.e2.out b/test/langtools/tools/javac/warnings/WerrorLint.e2.out new file mode 100644 index 00000000000..1c9bd4d54f8 --- /dev/null +++ b/test/langtools/tools/javac/warnings/WerrorLint.e2.out @@ -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 diff --git a/test/langtools/tools/javac/warnings/WerrorLint.java b/test/langtools/tools/javac/warnings/WerrorLint.java new file mode 100644 index 00000000000..3331a664d55 --- /dev/null +++ b/test/langtools/tools/javac/warnings/WerrorLint.java @@ -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 + } +} diff --git a/test/langtools/tools/javac/warnings/WerrorLint.w1.out b/test/langtools/tools/javac/warnings/WerrorLint.w1.out new file mode 100644 index 00000000000..3e19de51033 --- /dev/null +++ b/test/langtools/tools/javac/warnings/WerrorLint.w1.out @@ -0,0 +1,2 @@ +WerrorLint.java:20:19: compiler.warn.strictfp +1 warning diff --git a/test/langtools/tools/javac/warnings/WerrorLint.w2.out b/test/langtools/tools/javac/warnings/WerrorLint.w2.out new file mode 100644 index 00000000000..bac258706a6 --- /dev/null +++ b/test/langtools/tools/javac/warnings/WerrorLint.w2.out @@ -0,0 +1,3 @@ +WerrorLint.java:20:19: compiler.warn.strictfp +WerrorLint.java:21:30: compiler.warn.empty.if +2 warnings