diff --git a/make/CompileToolsJdk.gmk b/make/CompileToolsJdk.gmk index c291dbdba0a76..27a1f01de2a3a 100644 --- a/make/CompileToolsJdk.gmk +++ b/make/CompileToolsJdk.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, 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 @@ -47,7 +47,7 @@ $(eval $(call SetupJavaCompilation, BUILD_TOOLS_JDK, \ build/tools/jigsaw \ build/tools/depend, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes, \ - DISABLED_WARNINGS := dangling-doc-comments options, \ + DISABLED_WARNINGS := dangling-doc-comments options suppression, \ JAVAC_FLAGS := \ --add-exports java.desktop/sun.awt=ALL-UNNAMED \ --add-exports java.base/sun.text=ALL-UNNAMED \ @@ -81,7 +81,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \ SRC := $(TOPDIR)/make/jdk/src/classes, \ INCLUDES := build/tools/depend, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \ - DISABLED_WARNINGS := options, \ + DISABLED_WARNINGS := options suppression, \ JAVAC_FLAGS := \ --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ diff --git a/make/GenerateLinkOptData.gmk b/make/GenerateLinkOptData.gmk index 6f6e1f29b4c16..e44c1603907ae 100644 --- a/make/GenerateLinkOptData.gmk +++ b/make/GenerateLinkOptData.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2026, 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 @@ -40,7 +40,7 @@ $(eval $(call SetupJavaCompilation, CLASSLIST_JAR, \ SMALL_JAVA := false, \ SRC := $(TOPDIR)/make/jdk/src/classes, \ INCLUDES := build/tools/classlist, \ - DISABLED_WARNINGS := dangling-doc-comments, \ + DISABLED_WARNINGS := dangling-doc-comments suppression, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/classlist_classes, \ JAR := $(SUPPORT_OUTPUTDIR)/classlist.jar, \ )) diff --git a/make/JrtfsJar.gmk b/make/JrtfsJar.gmk index 54e2b094318fa..db5db568f7acd 100644 --- a/make/JrtfsJar.gmk +++ b/make/JrtfsJar.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2026, 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 @@ -51,7 +51,7 @@ JIMAGE_PKGS := \ # ends up in the image, this will ensure reproducible classes $(eval $(call SetupJavaCompilation, BUILD_JRTFS, \ COMPILER := interim, \ - DISABLED_WARNINGS := options, \ + DISABLED_WARNINGS := options suppression, \ TARGET_RELEASE := $(TARGET_RELEASE_JDK8), \ SRC := $(TOPDIR)/src/java.base/share/classes, \ EXCLUDE_FILES := module-info.java, \ diff --git a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk index e20db38297d55..0af0f1f81b50e 100644 --- a/make/modules/java.base/Java.gmk +++ b/make/modules/java.base/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -28,8 +28,8 @@ # The base module should be built with all warnings enabled. When a # new warning is added to javac, it can be temporarily added to the # disabled warnings list. -# -# DISABLED_WARNINGS_java += + +DISABLED_WARNINGS_java += suppression DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.desktop/Java.gmk b/make/modules/java.desktop/Java.gmk index bab6186fb0d0b..3126224919505 100644 --- a/make/modules/java.desktop/Java.gmk +++ b/make/modules/java.desktop/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,7 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments lossy-conversions this-escape +DISABLED_WARNINGS_java += dangling-doc-comments lossy-conversions this-escape suppression DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' COPY += .gif .png .wav .txt .xml .css .pf diff --git a/make/modules/java.management/Java.gmk b/make/modules/java.management/Java.gmk index 44e3f328c7fc9..6a1aabd5258ff 100644 --- a/make/modules/java.management/Java.gmk +++ b/make/modules/java.management/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,7 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments this-escape +DISABLED_WARNINGS_java += dangling-doc-comments this-escape suppression DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.xml.crypto/Java.gmk b/make/modules/java.xml.crypto/Java.gmk index 68db8ed817a23..730ee36f86a64 100644 --- a/make/modules/java.xml.crypto/Java.gmk +++ b/make/modules/java.xml.crypto/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,7 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments this-escape +DISABLED_WARNINGS_java += dangling-doc-comments this-escape suppression DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.xml/Java.gmk b/make/modules/java.xml/Java.gmk index 35f66238a7a96..e85cd0a83e68c 100644 --- a/make/modules/java.xml/Java.gmk +++ b/make/modules/java.xml/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,8 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments lossy-conversions this-escape +DISABLED_WARNINGS_java += dangling-doc-comments lossy-conversions this-escape \ + suppression DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:$(call CommaList, javax.xml.catalog javax.xml.datatype \ javax.xml.transform javax.xml.validation javax.xml.xpath)' diff --git a/make/modules/jdk.attach/Java.gmk b/make/modules/jdk.attach/Java.gmk new file mode 100644 index 0000000000000..e813cc9b9cd15 --- /dev/null +++ b/make/modules/jdk.attach/Java.gmk @@ -0,0 +1,30 @@ +# +# Copyright (c) 2026, 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 +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +################################################################################ + +DISABLED_WARNINGS_java += suppression + +################################################################################ diff --git a/make/modules/jdk.internal.le/Java.gmk b/make/modules/jdk.internal.le/Java.gmk index 27c6eaf5f7f96..5067cf677e810 100644 --- a/make/modules/jdk.internal.le/Java.gmk +++ b/make/modules/jdk.internal.le/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,7 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments this-escape +DISABLED_WARNINGS_java += dangling-doc-comments this-escape suppression COPY += .properties .caps .txt diff --git a/make/modules/jdk.jlink/Java.gmk b/make/modules/jdk.jlink/Java.gmk index 4ddd1eab03d92..5349a9ab374d3 100644 --- a/make/modules/jdk.jlink/Java.gmk +++ b/make/modules/jdk.jlink/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025, 2026, 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 @@ -29,4 +29,6 @@ # upgrade_files_.conf files COPY += .conf +DISABLED_WARNINGS_java += suppression + ################################################################################ diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk index da66fc1400901..867ed56e61e54 100644 --- a/make/modules/jdk.jpackage/Java.gmk +++ b/make/modules/jdk.jpackage/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,7 +25,7 @@ ################################################################################ -DISABLED_WARNINGS_java += dangling-doc-comments +DISABLED_WARNINGS_java += dangling-doc-comments suppression COPY += .gif .png .txt .spec .script .prerm .preinst \ .postrm .postinst .list .sh .desktop .copyright .control .plist .template \ diff --git a/make/modules/jdk.jshell/Java.gmk b/make/modules/jdk.jshell/Java.gmk index f4194b23af7e3..4504f608cf447 100644 --- a/make/modules/jdk.jshell/Java.gmk +++ b/make/modules/jdk.jshell/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, 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 @@ -25,6 +25,8 @@ ################################################################################ +DISABLED_WARNINGS_java += suppression + COPY += .jsh .properties ################################################################################ diff --git a/make/modules/jdk.management.agent/Java.gmk b/make/modules/jdk.management.agent/Java.gmk new file mode 100644 index 0000000000000..e813cc9b9cd15 --- /dev/null +++ b/make/modules/jdk.management.agent/Java.gmk @@ -0,0 +1,30 @@ +# +# Copyright (c) 2026, 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 +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +################################################################################ + +DISABLED_WARNINGS_java += suppression + +################################################################################ diff --git a/make/modules/jdk.management.jfr/Java.gmk b/make/modules/jdk.management.jfr/Java.gmk new file mode 100644 index 0000000000000..e813cc9b9cd15 --- /dev/null +++ b/make/modules/jdk.management.jfr/Java.gmk @@ -0,0 +1,30 @@ +# +# Copyright (c) 2026, 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 +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +################################################################################ + +DISABLED_WARNINGS_java += suppression + +################################################################################ diff --git a/make/modules/jdk.management/Java.gmk b/make/modules/jdk.management/Java.gmk index aca47fc97f704..128357ce8a638 100644 --- a/make/modules/jdk.management/Java.gmk +++ b/make/modules/jdk.management/Java.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, 2026, 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 @@ -25,6 +25,6 @@ ################################################################################ -DISABLED_WARNINGS_java += this-escape +DISABLED_WARNINGS_java += this-escape suppression ################################################################################ diff --git a/make/test/BuildFailureHandler.gmk b/make/test/BuildFailureHandler.gmk index b4f3d690b0d75..239b3a85ae761 100644 --- a/make/test/BuildFailureHandler.gmk +++ b/make/test/BuildFailureHandler.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2026, 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 @@ -47,7 +47,7 @@ $(eval $(call SetupJavaCompilation, BUILD_FAILURE_HANDLER, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ SRC := $(FH_BASEDIR)/src/share/classes $(FH_BASEDIR)/src/share/conf, \ BIN := $(FH_SUPPORT)/classes, \ - DISABLED_WARNINGS := options serial try this-escape, \ + DISABLED_WARNINGS := options serial try this-escape suppression, \ COPY := .properties, \ CLASSPATH := $(JTREG_JAR) $(TOOLS_JAR), \ JAR := $(FH_JAR), \ diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 4946263ef4e91..47503112b36fc 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026, 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 @@ -84,7 +84,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ CLASSPATH := $(JMH_COMPILE_JARS), \ CREATE_API_DIGEST := true, \ DISABLED_WARNINGS := restricted this-escape processing rawtypes removal cast \ - serial preview dangling-doc-comments, \ + serial preview dangling-doc-comments suppression, \ SRC := $(MICROBENCHMARK_SRC), \ BIN := $(MICROBENCHMARK_CLASSES), \ JAVAC_FLAGS := \ diff --git a/make/test/BuildTestLib.gmk b/make/test/BuildTestLib.gmk index dc5e0a9bd64bc..748710ee23099 100644 --- a/make/test/BuildTestLib.gmk +++ b/make/test/BuildTestLib.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2026, 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 @@ -63,6 +63,7 @@ $(eval $(call SetupJavaCompilation, BUILD_TEST_LIB_JAR, \ BIN := $(TEST_LIB_SUPPORT)/test-lib_classes, \ HEADERS := $(TEST_LIB_SUPPORT)/test-lib_headers, \ JAR := $(TEST_LIB_SUPPORT)/test-lib.jar, \ + DISABLED_WARNINGS := suppression, \ JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile.attribute=ALL-UNNAMED \ 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 3a8b4e5dbea61..ac4c58827071b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -42,7 +42,6 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.LintWarning; -import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Options; @@ -80,8 +79,9 @@ public static Lint instance(Context context) { */ public Lint augment(Symbol sym) { EnumSet suppressions = suppressionsFrom(sym); + suppressions.removeIf(lc -> !lc.annotationSuppression); // ignore categories that don't support @SuppressWarnings if (!suppressions.isEmpty()) { - Lint lint = new Lint(this); + Lint lint = new Lint(this, sym); lint.values.removeAll(suppressions); lint.suppressedValues.addAll(suppressions); return lint; @@ -94,7 +94,7 @@ public Lint augment(Symbol sym) { * @param lc one or more categories to be enabled */ public Lint enable(LintCategory... lc) { - Lint l = new Lint(this); + Lint l = new Lint(this, symbol); l.values.addAll(Arrays.asList(lc)); l.suppressedValues.removeAll(Arrays.asList(lc)); return l; @@ -105,7 +105,7 @@ public Lint enable(LintCategory... lc) { * @param lc one or more categories to be suppressed */ public Lint suppress(LintCategory... lc) { - Lint l = new Lint(this); + Lint l = new Lint(this, symbol); l.values.removeAll(Arrays.asList(lc)); l.suppressedValues.addAll(Arrays.asList(lc)); return l; @@ -113,7 +113,7 @@ public Lint suppress(LintCategory... lc) { private final Context context; private final Options options; - private final Log log; + private final LintMapper lintMapper; // These are initialized lazily to avoid dependency loops private Symtab syms; @@ -123,22 +123,29 @@ public Lint suppress(LintCategory... lc) { private EnumSet values; private EnumSet suppressedValues; + // The symbol of the declaration this instance was created for, or null for the root instance + private final Symbol symbol; + + // LintCategory lookup by option string private static final Map map = new LinkedHashMap<>(40); + // Instantiate the root instance @SuppressWarnings("this-escape") protected Lint(Context context) { this.context = context; context.put(lintKey, this); options = Options.instance(context); - log = Log.instance(context); + lintMapper = LintMapper.instance(context); + symbol = null; } // Instantiate a non-root ("symbol scoped") instance - protected Lint(Lint other) { - other.initializeRootIfNeeded(); + protected Lint(Lint other, Symbol symbol) { + Assert.check(symbol != null); + this.symbol = symbol; this.context = other.context; this.options = other.options; - this.log = other.log; + this.lintMapper = other.lintMapper; this.syms = other.syms; this.names = other.names; this.values = other.values.clone(); @@ -173,7 +180,11 @@ private EnumSet getDefaults() { @Override public String toString() { initializeRootIfNeeded(); - return "Lint:[enable" + values + ",suppress" + suppressedValues + "]"; + return "Lint[" + + (symbol != null ? "sym=" + symbol : "ROOT") + + ",enable" + values + + ",suppress" + suppressedValues + + "]"; } /** @@ -352,6 +363,11 @@ public enum LintCategory { */ STRICTFP("strictfp", true, true), + /** + * Warn about recognized {@code @SuppressWarnings} lint categories that don't actually suppress any warnings. + */ + SUPPRESSION("suppression"), + /** * Warn about issues relating to use of text blocks */ @@ -439,13 +455,41 @@ public static EnumSet newEmptySet() { public final boolean enabledByDefault; } + /** + * Determine whether warnings in the given category need to be calculated within the current delcaration + * because either (a) the category is enabled, or (b) lint category {@code "suppression"} is enabled. + * + *

+ * In case (b), warnings don't need to be calculated unless/until the category is actually suppressed, + * but that might not happen until some nested declaration, so we can't include a test for that here. + * + *

+ * Use of this method is never required; it simply helps avoid potentially useless work. + */ + public boolean isActive(LintCategory lc) { + initializeRootIfNeeded(); + return values.contains(lc) || values.contains(LintCategory.SUPPRESSION); + } + /** * Checks if a warning category is enabled. A warning category may be enabled * on the command line, or by default, and can be temporarily disabled with * the SuppressWarnings annotation. + * + *

+ * This method also optionally validates any warning suppression currently in scope. + * If you just want to know the configuration of this instance, set {@code validate} to false. + * If you are using the result of this method to control whether a warning is actually + * generated, then set {@code validate} to true to ensure that any suppression of the + * category in scope is validated (i.e., determined to actually be suppressing something). + * + * @param lc lint category + * @param validateSuppression true to also validate any suppression of the category */ - public boolean isEnabled(LintCategory lc) { + public boolean isEnabled(LintCategory lc, boolean validateSuppression) { initializeRootIfNeeded(); + if (validateSuppression) + validateSuppression(lc); return values.contains(lc); } @@ -454,9 +498,21 @@ public boolean isEnabled(LintCategory lc) { * of the SuppressWarnings annotation, or, in the case of the deprecated * category, whether it has been implicitly suppressed by virtue of the * current entity being itself deprecated. + * + *

+ * This method also optionally validates any warning suppression currently in scope. + * If you just want to know the configuration of this instance, set {@code validate} to false. + * If you are using the result of this method to control whether a warning is actually + * generated, then set {@code validate} to true to ensure that any suppression of the + * category in scope is validated (i.e., determined to actually be suppressing something). + * + * @param lc lint category + * @param validateSuppression true to also validate any suppression of the category */ - public boolean isSuppressed(LintCategory lc) { + public boolean isSuppressed(LintCategory lc, boolean validateSuppression) { initializeRootIfNeeded(); + if (validateSuppression) + validateSuppression(lc); return suppressedValues.contains(lc); } @@ -477,6 +533,20 @@ public EnumSet suppressionsFrom(Symbol symbol) { return suppressions; } + /** + * Retrieve the recognized lint categories suppressed by the given @SuppressWarnings annotation. + * + * @param annotation @SuppressWarnings annotation, or null + * @return set of lint categories, possibly empty but never null + */ + public EnumSet suppressionsFrom(JCAnnotation annotation) { + initializeSymbolsIfNeeded(); + if (annotation == null) + return LintCategory.newEmptySet(); + Assert.check(annotation.attribute.type.tsym == syms.suppressWarningsType.tsym); + return suppressionsFrom(Stream.of(annotation).map(anno -> anno.attribute)); + } + // Find the @SuppressWarnings annotation in the given stream and extract the recognized suppressions private EnumSet suppressionsFrom(Stream attributes) { initializeSymbolsIfNeeded(); @@ -497,13 +567,29 @@ private EnumSet suppressionsFrom(Attribute.Compound suppressWarnin .filter(val -> val instanceof Attribute.Constant) .map(val -> (String) ((Attribute.Constant) val).value) .flatMap(LintCategory::get) - .filter(lc -> lc.annotationSuppression) .ifPresent(result::add); } } return result; } + /** + * Validate any suppression of the given lint category currently in scope. + * + *

+ * Such a suppression will therefore not be declared as unnecessary by the + * {@code "suppression"} warning. + * + * @param lc the lint category to be validated + * @return this instance + */ + public Lint validateSuppression(LintCategory lc) { + initializeRootIfNeeded(); + if (values.contains(LintCategory.SUPPRESSION)) + lintMapper.validateSuppression(symbol, lc); + return this; + } + private void initializeSymbolsIfNeeded() { if (syms == null) { syms = Symtab.instance(context); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index 06caa70d478a6..8168acc627cbc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -25,6 +25,8 @@ package com.sun.tools.javac.code; +import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -32,9 +34,14 @@ import java.util.Map; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.tools.JavaFileObject; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.resources.CompilerProperties.LintWarnings; import com.sun.tools.javac.tree.EndPosTable; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.*; @@ -43,6 +50,7 @@ import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.Log; /** * Maps source code positions to the applicable {@link Lint} instance. @@ -57,6 +65,33 @@ * The method {@link #lintAt} returns the {@link Lint} instance applicable to source position; * if it can't be determined yet, an empty {@link Optional} is returned. * + *

+ * This class also tracks which {@code @SuppressWarnings} suppressions actually suppress something; + * any that don't are unnecessary and will generate warnings in the {@code "suppression"} category. + * For this to work, this class must be notified any time a warning that is currently suppressed + * would have been reported; this is termed the "validation" of the suppression. That notification + * happens by invoking {@link #validateSuppression}. + * + *

+ * Validation events "bubble up" the source tree until either they are "caught" by a {@code @SuppressWarnings} + * annotation, or they escape the file entirely. Being caught validates the corresponding suppression. + * A suppression that is never caught (i.e., never validated) is unnecessary. + * + *

+ * Some additional nuances: + *

    + *
  • Lint warnings can be suppressed at a module, package, class, method, or variable declaration + * (via {@code @SuppressWarnings}), or globally on the command line via {@code -Xlint:-key}. + *
  • Consequently, unnecessary suppression warnings can only be emitted at {@code @SuppressWarnings} + * annotations. Currently, we don't report unnecessary suppression via {@code -Xlint:-key} flags. + *
  • Some lint categories don't support suppression via the {@code @SuppressWarnings} annotations + * (e.g., {@code "classfile"}). Specifying such a category in a {@code @SuppressWarnings} annotation + * is always unnecessary and will trigger an unnecessary suppression warning (if enabled). + *
  • {@code @SuppressWarnings("suppression")} is normal and valid: it means unnecessary suppression + * warnings won't occur for that annotation or any other {@code @SuppressWarnings} annotations within + * the scope of the annotated declaration. + *
+ * *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or @@ -75,6 +110,8 @@ public class LintMapper { // These are initialized lazily; see initializeIfNeeded() private Lint rootLint; + private Symtab syms; + private Log log; /** * Obtain the {@link LintMapper} context singleton. @@ -97,8 +134,11 @@ protected LintMapper(Context context) { // Lazy initialization to avoid dependency loops private void initializeIfNeeded() { - if (rootLint == null) + if (rootLint == null) { rootLint = Lint.instance(context); + syms = Symtab.instance(context); + log = Log.instance(context); + } } // Lint Operations @@ -136,7 +176,7 @@ public Optional lintAt(JavaFileObject sourceFile, DiagnosticPosition pos) */ public void calculateLints(JavaFileObject sourceFile, JCTree tree, EndPosTable endPositions) { Assert.check(rootLint != null); - fileInfoMap.get(sourceFile).afterAttr(tree, endPositions); + fileInfoMap.get(sourceFile).afterAttr(tree, endPositions, syms); } /** @@ -164,6 +204,58 @@ public void finishParsingFile(JCCompilationUnit tree) { fileInfoMap.put(tree.sourcefile, new FileInfo(rootLint, tree)); } +// Suppression Tracking + + /** + * Validate the given lint category within the scope of the given symbol's declaration. + * + *

+ * This indicates that any suppression of {@code category} currently in scope is necesssary. + * + * @param symbol innermost {@code @SuppressWarnings}-annotated symbol in scope, or null for global scope + * @param category lint category to validate + */ + public void validateSuppression(Symbol symbol, LintCategory category) { + if (symbol != null) + fileInfoMap.get(log.currentSourceFile()).validationsFor(symbol).add(category); + } + + /** + * Warn about unnecessary {@code @SuppressWarnings} suppressions within the given top-level declaration. + * + *

+ * All warnings within {@code tree} must have already been calculated when this method is invoked. + * + * @param sourceFile source file + * @param tree top level declaration + */ + public void reportUnnecessarySuppressionAnnotations(JavaFileObject sourceFile, JCTree tree) { + + // Anything to do here? + initializeIfNeeded(); + if (!rootLint.isEnabled(LintCategory.SUPPRESSION, false)) + return; + + // Find the LintRange corresponding to "tree" + FileInfo fileInfo = fileInfoMap.get(sourceFile); + LintRange lintRange = fileInfo.rootRange.findChild(tree.pos()); + + // Propagate validations within "tree" to determine which suppressions therein never got validated + fileInfo.propagateValidations(lintRange); + + // Report unvalidated suppresions, except where SUPPRESSION is itself suppressed + lintRange.stream() + .filter(node -> node.lint.isEnabled(LintCategory.SUPPRESSION, false)) + .forEach(node -> { + String unnecessaryCategoryNames = node.unvalidated.stream() + .map(category -> category.option) + .map(name -> "\"" + name + "\"") + .collect(Collectors.joining(", ")); + if (!unnecessaryCategoryNames.isEmpty()) + log.warning(node.annotation.pos(), LintWarnings.UnnecessaryWarningSuppression(unnecessaryCategoryNames)); + }); + } + // FileInfo /** @@ -178,6 +270,8 @@ private static class FileInfo { final LintRange rootRange; // the root LintRange (covering the entire source file) final List unmappedDecls = new LinkedList<>(); // unmapped top-level declarations awaiting attribution + final Map> validationsMap // maps declaration symbol to validations therein + = new HashMap<>(); // After parsing: Add top-level declarations to our "unmappedDecls" list FileInfo(Lint rootLint, JCCompilationUnit tree) { @@ -189,10 +283,10 @@ private static class FileInfo { } // After attribution: Discard the span from "unmappedDecls" and populate the declaration's subtree under "rootRange" - void afterAttr(JCTree tree, EndPosTable endPositions) { + void afterAttr(JCTree tree, EndPosTable endPositions, Symtab syms) { for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { if (i.next().contains(tree.pos())) { - rootRange.populateSubtree(tree, endPositions); + rootRange.populateSubtree(this, tree, endPositions, syms); i.remove(); return; } @@ -209,6 +303,25 @@ Optional lintAt(DiagnosticPosition pos) { return Optional.of(rootRange.bestMatch(pos).lint); } + // Obtain the validation state for the given symbol + EnumSet validationsFor(Symbol symbol) { + return validationsMap.computeIfAbsent(symbol, s -> LintCategory.newEmptySet()); + } + + // Merge the validation sets for two variables declared together, thereby sharing any @SuppressWarnings annotation. + // See "annotationRepresentativeSymbolMap" below for more detail on why this is needed. + void mergeValidations(VarSymbol symbol1, VarSymbol symbol2) { + EnumSet validations1 = validationsFor(symbol1); + EnumSet validations2 = validationsFor(symbol2); + Assert.check(validations1.equals(validations2)); + validationsMap.put(symbol2, validations1); // now the two symbols share the same validation set + } + + // Propagate validations in the given top-level node + EnumSet propagateValidations(LintRange lintRange) { + return lintRange.propagateValidations(validationsMap); + } + boolean isTopLevelDecl(JCTree tree) { return tree.getTag() == Tag.MODULEDEF || tree.getTag() == Tag.PACKAGEDEF @@ -247,17 +360,23 @@ boolean contains(Span that) { private record LintRange( Span span, // declaration's lexical range Lint lint, // the Lint configuration that applies at this declaration + Symbol symbol, // declaration symbol (null for root range) + JCAnnotation annotation, // the @SuppressWarnings on this declaration, if any + EnumSet suppressions, // categories suppressed by @SuppressWarnings + EnumSet unvalidated, // categories suppressed by @SuppressWarnings that were never validated List children // the nested declarations one level below this node ) { // Create a node representing the entire file, using the root lint configuration LintRange(Lint rootLint) { - this(Span.MAXIMAL, rootLint, new LinkedList<>()); + this(Span.MAXIMAL, rootLint, null, null, LintCategory.newEmptySet(), LintCategory.newEmptySet(), new LinkedList<>()); } // Create a node representing the given declaration and its corresponding Lint configuration - LintRange(JCTree tree, EndPosTable endPositions, Lint lint) { - this(new Span(tree, endPositions), lint, new LinkedList<>()); + LintRange(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol, + JCAnnotation annotation, EnumSet suppressions) { + this(new Span(tree, endPositions), lint, symbol, + annotation, suppressions, EnumSet.copyOf(suppressions), new LinkedList<>()); } // Find the most specific node in this tree (including me) that contains the given position, if any @@ -275,35 +394,85 @@ LintRange bestMatch(DiagnosticPosition pos) { return bestMatch; } + // Find the child containing the given position + LintRange findChild(DiagnosticPosition pos) { + return children.stream() + .filter(node -> node.span.contains(pos)) + .findFirst() + .orElseThrow(() -> new AssertionError("child not found")); + } + + // Stream this node and all descendents via pre-order recursive descent + Stream stream() { + return Stream.concat(Stream.of(this), children.stream().flatMap(LintRange::stream)); + } + + // Calculate the unvalidated suppressions in the subtree rooted at this node. We do this by recursively + // propagating validations upward until they are "caught" by some matching suppression; this validates + // the suppression. Validations that are never caught "escape" and are returned to the caller. + public EnumSet propagateValidations(Map> validationsMap) { + + // Recurse on subtrees first and gather their uncaught validations + EnumSet validations = LintCategory.newEmptySet(); + children.stream() + .map(child -> child.propagateValidations(validationsMap)) + .forEach(validations::addAll); + + // Add in the validations that occurred at this node, if any + Optional.of(symbol) + .map(validationsMap::get) + .ifPresent(validations::addAll); + + // Apply (and then discard) validations that match any of this node's suppressions + validations.removeIf(category -> { + if (suppressions.contains(category)) { + unvalidated.remove(category); + return true; + } + return false; + }); + + // Any remaining validations "escape" and propagate upward + return validations; + } + // Populate a sparse subtree corresponding to the given nested declaration. - // Only when the Lint configuration differs from the parent is a node added. - void populateSubtree(JCTree tree, EndPosTable endPositions) { + // Only "interesting" declarations are included: + // - Declarations that have a different Lint configuration from their parent + // - Declarations with a @SuppressWarnings annotation + void populateSubtree(FileInfo fileInfo, JCTree tree, EndPosTable endPositions, Symtab syms) { new TreeScanner() { + // Variables declared together (separated by commas) share any @SuppressWarnings annotation, so they must also + // share the set of validated suppressions. That is, the suppression of a lint category is valid if a warning + // would have been generated by *any* of the variables. We detect this particular scenario using this map. + private final Map annotationRepresentativeSymbolMap = new HashMap<>(); + private LintRange currentNode = LintRange.this; @Override public void visitModuleDef(JCModuleDecl tree) { - scanDecl(tree, tree.sym, super::visitModuleDef); + scanDecl(tree, tree.sym, findAnnotation(tree.mods), super::visitModuleDef); } @Override public void visitPackageDef(JCPackageDecl tree) { - scanDecl(tree, tree.packge, super::visitPackageDef); + scanDecl(tree, tree.packge, findAnnotation(tree.annotations), super::visitPackageDef); } @Override public void visitClassDef(JCClassDecl tree) { - scanDecl(tree, tree.sym, super::visitClassDef); + scanDecl(tree, tree.sym, findAnnotation(tree.mods), super::visitClassDef); } @Override public void visitMethodDef(JCMethodDecl tree) { - scanDecl(tree, tree.sym, super::visitMethodDef); + scanDecl(tree, tree.sym, findAnnotation(tree.mods), super::visitMethodDef); } @Override public void visitVarDef(JCVariableDecl tree) { - scanDecl(tree, tree.sym, super::visitVarDef); + scanDecl(tree, tree.sym, findAnnotation(tree.mods), super::visitVarDef); } - private void scanDecl(T tree, Symbol symbol, Consumer recursor) { + private void scanDecl(T tree, + Symbol symbol, JCAnnotation annotation, Consumer recursor) { // The "symbol" can be null if there were earlier errors; skip this declaration if so if (symbol == null) { @@ -311,16 +480,31 @@ private void scanDecl(T tree, Symbol symbol, Consumer suppressed = Optional.ofNullable(annotation) + .map(anno -> lint.suppressionsFrom(anno)) + .orElseGet(LintCategory::newEmptySet); + + // Merge validation sets for variables that share the same declaration (and therefore @SuppressedWarnings) + if (annotation != null && symbol instanceof VarSymbol varSym) { + annotationRepresentativeSymbolMap.merge(annotation, varSym, (oldSymbol, newSymbol) -> { + fileInfo.mergeValidations(oldSymbol, newSymbol); + return oldSymbol; + }); + } + + // If this declaration is not "interesting", then we don't need a new node here + if (newLint == currentNode.lint && currentNode.symbol != null && suppressed.isEmpty()) { recursor.accept(tree); return; } // Add a new node here and proceed final LintRange previousNode = currentNode; - currentNode = new LintRange(tree, endPositions, newLint); + currentNode = new LintRange(tree, endPositions, newLint, symbol, annotation, suppressed); previousNode.children.add(currentNode); try { recursor.accept(tree); @@ -328,6 +512,24 @@ private void scanDecl(T tree, Symbol symbol, Consumer m.annotations) + .map(this::findAnnotation) + .orElse(null); + } + + // Retrieve the @SuppressWarnings annotation, if any, from the given list of annotations + private JCAnnotation findAnnotation(Collection annotations) { + return Optional.ofNullable(annotations) + .stream() + .flatMap(Collection::stream) + .filter(a -> a.attribute.type.tsym == syms.suppressWarningsType.tsym) + .findFirst() + .orElse(null); + } }.scan(tree); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index cc21113882f69..45e074e71c47f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -5697,7 +5697,7 @@ private void attribClassBody(Env env, ClassSymbol c) { // Check for proper use of serialVersionUID and other // serialization-related fields and methods - if (env.info.lint.isEnabled(LintCategory.SERIAL) + if (env.info.lint.isActive(LintCategory.SERIAL) && rs.isSerializable(c.type) && !c.isAnonymous()) { chk.checkSerialStructure(tree, c); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 94b14f3122f84..725d16d28d4b8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -2124,7 +2124,7 @@ public void checkClassOverrideEqualsAndHashIfNeeded(DiagnosticPosition pos, private void checkClassOverrideEqualsAndHash(DiagnosticPosition pos, ClassSymbol someClass) { - if (lint.isEnabled(LintCategory.OVERRIDES)) { + if (lint.isActive(LintCategory.OVERRIDES)) { MethodSymbol equalsAtObject = (MethodSymbol)syms.objectType .tsym.members().findFirst(names.equals); MethodSymbol hashCodeAtObject = (MethodSymbol)syms.objectType @@ -2176,7 +2176,7 @@ public void checkHasMain(DiagnosticPosition pos, ClassSymbol c) { public void checkModuleName (JCModuleDecl tree) { Name moduleName = tree.sym.name; Assert.checkNonNull(moduleName); - if (lint.isEnabled(LintCategory.MODULE)) { + if (lint.isActive(LintCategory.MODULE)) { JCExpression qualId = tree.qualId; while (qualId != null) { Name componentName; @@ -2647,7 +2647,7 @@ public boolean test(Symbol s) { void checkPotentiallyAmbiguousOverloads(JCClassDecl tree, Type site) { // Skip if warning not enabled - if (!lint.isEnabled(LintCategory.OVERLOADS)) + if (!lint.isActive(LintCategory.OVERLOADS)) return; // Gather all of site's methods, including overridden methods, grouped by name (except Object methods) @@ -2660,10 +2660,6 @@ void checkPotentiallyAmbiguousOverloads(JCClassDecl tree, Type site) { // Now remove overridden methods from each group, leaving only site's actual members methodGroups.forEach(list -> removePreempted(list, (m1, m2) -> m1.overrides(m2, site.tsym, types, false))); - // Allow site's own declared methods (only) to apply @SuppressWarnings("overloads") - methodGroups.forEach(list -> list.removeIf( - m -> m.owner == site.tsym && !lint.augment(m).isEnabled(LintCategory.OVERLOADS))); - // Warn about ambiguous overload method pairs for which site is responsible methodGroups.forEach(list -> compareAndRemove(list, (m1, m2) -> { @@ -2671,6 +2667,16 @@ void checkPotentiallyAmbiguousOverloads(JCClassDecl tree, Type site) { if (!potentiallyAmbiguousOverload(site, m1, m2) || !responsible.test(m1, m2)) return 0; + // Allow the site's own declared methods (only) to apply @SuppressWarnings("overloads"). + // Treat both methods equally so they "share" the validation of the warning suppression, + // but also verify an annotation actually exists on a method before doing that, because + // otherwise we could incorrectly validate an outer annotation. + Predicate methodSuppresses = m -> m.owner == site.tsym && + m.attribute(syms.suppressWarningsType.tsym) != null && + lint.augment(m).isSuppressed(LintCategory.OVERLOADS, true); + if (methodSuppresses.test(m1) | methodSuppresses.test(m2)) // use "|" to validate @SuppressWarnings on BOTH methods + return FIRST | SECOND; + // Locate the warning at one of the methods, if possible DiagnosticPosition pos = m1.owner == site.tsym ? TreeInfo.diagnosticPositionFor(m1, tree) : @@ -3682,14 +3688,14 @@ boolean validateTargetAnnotationValue(JCAnnotation a) { } void checkDeprecatedAnnotation(DiagnosticPosition pos, Symbol s) { - if (lint.isEnabled(LintCategory.DEP_ANN) && s.isDeprecatableViaAnnotation() && + if (lint.isActive(LintCategory.DEP_ANN) && s.isDeprecatableViaAnnotation() && (s.flags() & DEPRECATED) != 0 && !syms.deprecatedType.isErroneous() && s.attribute(syms.deprecatedType.tsym) == null) { log.warning(pos, LintWarnings.MissingDeprecatedAnnotation); } // Note: @Deprecated has no effect on local variables, parameters and package decls. - if (lint.isEnabled(LintCategory.DEPRECATION) && !s.isDeprecatableViaAnnotation()) { + if (lint.isActive(LintCategory.DEPRECATION) && !s.isDeprecatableViaAnnotation()) { if (!syms.deprecatedType.isErroneous() && s.attribute(syms.deprecatedType.tsym) != null) { log.warning(pos, LintWarnings.DeprecatedAnnotationHasNoEffect(Kinds.kindName(s))); } @@ -4248,7 +4254,7 @@ void checkForBadAuxiliaryClassAccess(DiagnosticPosition pos, Env en * Check for a default constructor in an exported package. */ void checkDefaultConstructor(ClassSymbol c, DiagnosticPosition pos) { - if (lint.isEnabled(LintCategory.MISSING_EXPLICIT_CTOR) && + if (lint.isActive(LintCategory.MISSING_EXPLICIT_CTOR) && ((c.flags() & (ENUM | RECORD)) == 0) && !c.isAnonymous() && ((c.flags() & (PUBLIC | PROTECTED)) != 0) && @@ -4466,7 +4472,7 @@ public void visitMethodDef(JCMethodDecl tree) { Lint prevLint = lint; try { lint = lint.augment(tree.sym); - if (lint.isEnabled(LintCategory.EXPORTS)) { + if (lint.isActive(LintCategory.EXPORTS)) { super.visitMethodDef(tree); } } finally { @@ -4480,7 +4486,7 @@ public void visitVarDef(JCVariableDecl tree) { Lint prevLint = lint; try { lint = lint.augment(tree.sym); - if (lint.isEnabled(LintCategory.EXPORTS)) { + if (lint.isActive(LintCategory.EXPORTS)) { scan(tree.mods); scan(tree.vartype); } @@ -4499,7 +4505,7 @@ public void visitClassDef(JCClassDecl tree) { Lint prevLint = lint; try { lint = lint.augment(tree.sym); - if (lint.isEnabled(LintCategory.EXPORTS)) { + if (lint.isActive(LintCategory.EXPORTS)) { scan(tree.mods); scan(tree.typarams); try { @@ -4903,6 +4909,14 @@ private class SerialTypeVisitor extends ElementKindVisitor14 Lint lint; + // Because runUnderLint() uses "augment" to customize the current Lint instance, + // we must check if the warning category is enabled manually before logging a warning. + private void logWarning(DiagnosticPosition pos, LintWarning warningKey) { + if (lint.isEnabled(warningKey.getLintCategory(), true)) { + log.warning(pos, warningKey); + } + } + @Override public Void defaultAction(Element e, JCClassDecl p) { throw new IllegalArgumentException(Objects.requireNonNullElse(e.toString(), "")); @@ -4934,7 +4948,7 @@ public Void visitTypeAsClass(TypeElement e, } if (svuidSym == null) { - log.warning(p.pos(), LintWarnings.MissingSVUID(c)); + logWarning(p.pos(), LintWarnings.MissingSVUID(c)); } // Check for serialPersistentFields to gate checks for @@ -4961,9 +4975,8 @@ public Void visitTypeAsClass(TypeElement e, // Note per JLS arrays are // serializable even if the // component type is not. - log.warning( - TreeInfo.diagnosticPositionFor(enclosed, tree), - LintWarnings.NonSerializableInstanceField); + logWarning(TreeInfo.diagnosticPositionFor(enclosed, tree), + LintWarnings.NonSerializableInstanceField); } else if (varType.hasTag(ARRAY)) { ArrayType arrayType = (ArrayType)varType; Type elementType = arrayType.elemtype; @@ -4972,9 +4985,8 @@ public Void visitTypeAsClass(TypeElement e, elementType = arrayType.elemtype; } if (!canBeSerialized(elementType)) { - log.warning( - TreeInfo.diagnosticPositionFor(enclosed, tree), - LintWarnings.NonSerializableInstanceFieldArray(elementType)); + logWarning(TreeInfo.diagnosticPositionFor(enclosed, tree), + LintWarnings.NonSerializableInstanceFieldArray(elementType)); } } } @@ -5057,8 +5069,7 @@ private void checkCtorAccess(JCClassDecl tree, ClassSymbol c) { } } } - log.warning(tree.pos(), - LintWarnings.ExternalizableMissingPublicNoArgCtor); + logWarning(tree.pos(), LintWarnings.ExternalizableMissingPublicNoArgCtor); } else { // Approximate access to the no-arg constructor up in // the superclass chain by checking that the @@ -5085,8 +5096,8 @@ private void checkCtorAccess(JCClassDecl tree, ClassSymbol c) { // Handle nested classes and implicit this$0 (supertype.getNestingKind() == NestingKind.MEMBER && ((supertype.flags() & STATIC) == 0))) - log.warning(tree.pos(), - LintWarnings.SerializableMissingAccessNoArgCtor(supertype.getQualifiedName())); + logWarning(tree.pos(), + LintWarnings.SerializableMissingAccessNoArgCtor(supertype.getQualifiedName())); } } } @@ -5104,43 +5115,31 @@ private void checkSerialVersionUID(JCClassDecl tree, Element e, VarSymbol svuid) // fields. if ((svuid.flags() & (STATIC | FINAL)) != (STATIC | FINAL)) { - log.warning( - TreeInfo.diagnosticPositionFor(svuid, tree), - LintWarnings.ImproperSVUID((Symbol)e)); + logWarning(TreeInfo.diagnosticPositionFor(svuid, tree), LintWarnings.ImproperSVUID((Symbol)e)); } // check svuid has type long if (!svuid.type.hasTag(LONG)) { - log.warning( - TreeInfo.diagnosticPositionFor(svuid, tree), - LintWarnings.LongSVUID((Symbol)e)); + logWarning(TreeInfo.diagnosticPositionFor(svuid, tree), LintWarnings.LongSVUID((Symbol)e)); } if (svuid.getConstValue() == null) - log.warning( - TreeInfo.diagnosticPositionFor(svuid, tree), - LintWarnings.ConstantSVUID((Symbol)e)); + logWarning(TreeInfo.diagnosticPositionFor(svuid, tree), LintWarnings.ConstantSVUID((Symbol)e)); } private void checkSerialPersistentFields(JCClassDecl tree, Element e, VarSymbol spf) { // To be effective, serialPersisentFields must be private, static, and final. if ((spf.flags() & (PRIVATE | STATIC | FINAL)) != (PRIVATE | STATIC | FINAL)) { - log.warning( - TreeInfo.diagnosticPositionFor(spf, tree), - LintWarnings.ImproperSPF); + logWarning(TreeInfo.diagnosticPositionFor(spf, tree), LintWarnings.ImproperSPF); } if (!types.isSameType(spf.type, OSF_TYPE)) { - log.warning( - TreeInfo.diagnosticPositionFor(spf, tree), - LintWarnings.OSFArraySPF); + logWarning(TreeInfo.diagnosticPositionFor(spf, tree), LintWarnings.OSFArraySPF); } if (isExternalizable((Type)(e.asType()))) { - log.warning( - TreeInfo.diagnosticPositionFor(spf, tree), - LintWarnings.IneffectualSerialFieldExternalizable); + logWarning(TreeInfo.diagnosticPositionFor(spf, tree), LintWarnings.IneffectualSerialFieldExternalizable); } // Warn if serialPersistentFields is initialized to a @@ -5150,8 +5149,7 @@ private void checkSerialPersistentFields(JCClassDecl tree, Element e, VarSymbol JCVariableDecl variableDef = (JCVariableDecl) spfDecl; JCExpression initExpr = variableDef.init; if (initExpr != null && TreeInfo.isNull(initExpr)) { - log.warning(initExpr.pos(), - LintWarnings.SPFNullInit); + logWarning(initExpr.pos(), LintWarnings.SPFNullInit); } } } @@ -5229,24 +5227,19 @@ private void checkReadExternalRecord(JCClassDecl tree, Element e, MethodSymbol m private void checkExternMethodRecord(JCClassDecl tree, Element e, MethodSymbol method, Type argType, boolean isExtern) { if (isExtern && isExternMethod(tree, e, method, argType)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.IneffectualExternalizableMethodRecord(method.getSimpleName().toString())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.IneffectualExternalizableMethodRecord(method.getSimpleName().toString())); } } void checkPrivateNonStaticMethod(JCClassDecl tree, MethodSymbol method) { var flags = method.flags(); if ((flags & PRIVATE) == 0) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodNotPrivate(method.getSimpleName())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.SerialMethodNotPrivate(method.getSimpleName())); } if ((flags & STATIC) != 0) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodStatic(method.getSimpleName())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.SerialMethodStatic(method.getSimpleName())); } } @@ -5269,18 +5262,14 @@ public Void visitTypeAsEnum(TypeElement e, case FIELD -> { var field = (VarSymbol)enclosed; if (serialFieldNames.contains(name)) { - log.warning( - TreeInfo.diagnosticPositionFor(field, tree), - LintWarnings.IneffectualSerialFieldEnum(name)); + logWarning(TreeInfo.diagnosticPositionFor(field, tree), LintWarnings.IneffectualSerialFieldEnum(name)); } } case METHOD -> { var method = (MethodSymbol)enclosed; if (serialMethodNames.contains(name)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.IneffectualSerialMethodEnum(name)); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.IneffectualSerialMethodEnum(name)); } if (isExtern) { @@ -5318,9 +5307,8 @@ private void checkReadExternalEnum(JCClassDecl tree, Element e, MethodSymbol met private void checkExternMethodEnum(JCClassDecl tree, Element e, MethodSymbol method, Type argType) { if (isExternMethod(tree, e, method, argType)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.IneffectualExternMethodEnum(method.getSimpleName().toString())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.IneffectualExternMethodEnum(method.getSimpleName().toString())); } } @@ -5350,9 +5338,7 @@ public Void visitTypeAsInterface(TypeElement e, name = field.getSimpleName().toString(); switch(name) { case "serialPersistentFields" -> { - log.warning( - TreeInfo.diagnosticPositionFor(field, tree), - LintWarnings.IneffectualSerialFieldInterface); + logWarning(TreeInfo.diagnosticPositionFor(field, tree), LintWarnings.IneffectualSerialFieldInterface); } case "serialVersionUID" -> { @@ -5390,9 +5376,7 @@ private void checkPrivateMethod(JCClassDecl tree, Element e, MethodSymbol method) { if ((method.flags() & PRIVATE) == 0) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.NonPrivateMethodWeakerAccess); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.NonPrivateMethodWeakerAccess); } } @@ -5400,9 +5384,7 @@ private void checkDefaultIneffective(JCClassDecl tree, Element e, MethodSymbol method) { if ((method.flags() & DEFAULT) == DEFAULT) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.DefaultIneffective); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.DefaultIneffective); } } @@ -5447,9 +5429,7 @@ public Void visitTypeAsRecord(TypeElement e, var field = (VarSymbol)enclosed; switch(name) { case "serialPersistentFields" -> { - log.warning( - TreeInfo.diagnosticPositionFor(field, tree), - LintWarnings.IneffectualSerialFieldRecord); + logWarning(TreeInfo.diagnosticPositionFor(field, tree), LintWarnings.IneffectualSerialFieldRecord); } case "serialVersionUID" -> { @@ -5471,9 +5451,8 @@ public Void visitTypeAsRecord(TypeElement e, default -> { if (serialMethodNames.contains(name)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.IneffectualSerialMethodRecord(name)); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.IneffectualSerialMethodRecord(name)); } }} }}}); @@ -5485,9 +5464,8 @@ void checkConcreteInstanceMethod(JCClassDecl tree, Element enclosing, MethodSymbol method) { if ((method.flags() & (STATIC | ABSTRACT)) != 0) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialConcreteInstanceMethod(method.getSimpleName())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.SerialConcreteInstanceMethod(method.getSimpleName())); } } @@ -5502,9 +5480,8 @@ private void checkReturnType(JCClassDecl tree, // checking. Type rtype = method.getReturnType(); if (!types.isSameType(expectedReturnType, rtype)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodUnexpectedReturnType(method.getSimpleName(), + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.SerialMethodUnexpectedReturnType(method.getSimpleName(), rtype, expectedReturnType)); } } @@ -5518,17 +5495,15 @@ private void checkOneArg(JCClassDecl tree, var parameters= method.getParameters(); if (parameters.size() != 1) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodOneArg(method.getSimpleName(), parameters.size())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.SerialMethodOneArg(method.getSimpleName(), parameters.size())); return; } Type parameterType = parameters.get(0).asType(); if (!types.isSameType(parameterType, expectedType)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodParameterType(method.getSimpleName(), + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.SerialMethodParameterType(method.getSimpleName(), expectedType, parameterType)); } @@ -5547,18 +5522,16 @@ private boolean hasExactlyOneArgWithType(JCClassDecl tree, private void checkNoArgs(JCClassDecl tree, Element enclosing, MethodSymbol method) { var parameters = method.getParameters(); if (!parameters.isEmpty()) { - log.warning( - TreeInfo.diagnosticPositionFor(parameters.get(0), tree), - LintWarnings.SerialMethodNoArgs(method.getSimpleName())); + logWarning(TreeInfo.diagnosticPositionFor(parameters.get(0), tree), + LintWarnings.SerialMethodNoArgs(method.getSimpleName())); } } private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) { // If the enclosing class is externalizable, warn for the method if (isExternalizable((Type)enclosing.asType())) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.IneffectualSerialMethodExternalizable(method.getSimpleName())); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.IneffectualSerialMethodExternalizable(method.getSimpleName())); } return; } @@ -5585,10 +5558,8 @@ private void checkExceptions(JCClassDecl tree, } } if (!declared) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), - LintWarnings.SerialMethodUnexpectedException(method.getSimpleName(), - thrownType)); + logWarning(TreeInfo.diagnosticPositionFor(method, tree), + LintWarnings.SerialMethodUnexpectedException(method.getSimpleName(), thrownType)); } } } @@ -5600,7 +5571,7 @@ private Void runUnderLint(E symbol, JCClassDecl p, BiConsume try { lint = lint.augment((Symbol) symbol); - if (lint.isEnabled(LintCategory.SERIAL)) { + if (lint.isActive(LintCategory.SERIAL)) { task.accept(symbol, p); } @@ -5702,7 +5673,7 @@ void checkRequiresIdentity(JCTree tree, Lint lint) { private boolean checkIfIdentityIsExpected(DiagnosticPosition pos, Type t, Lint lint) { if (t != null && lint != null && - lint.isEnabled(LintCategory.IDENTITY)) { + lint.isActive(LintCategory.IDENTITY)) { RequiresIdentityVisitor requiresIdentityVisitor = new RequiresIdentityVisitor(); // we need to avoid recursion due to self referencing type vars or captures, this is why we need a set requiresIdentityVisitor.visit(t, new HashSet<>()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java index 5a1fe70dd9b57..ddad25084632c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, 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 @@ -1356,7 +1356,7 @@ private void setupAllModules() { .forEach(result::add); } - if (lint.isEnabled(LintCategory.INCUBATING)) { + if (lint.isActive(LintCategory.INCUBATING)) { String incubatingModules = filterAlreadyWarnedIncubatorModules(result.stream() .filter(msym -> msym.resolutionFlags.contains(ModuleResolutionFlags.WARN_INCUBATING)) .map(msym -> msym.name.toString())) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java index 6fb1feed08dcf..be10388cbf3c4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -262,7 +262,7 @@ private void doAnalyzeTree(Env env) { Assert.check(methodMap.isEmpty()); // we are not prepared to be used more than once // Short circuit if this calculation is unnecessary - if (!lintMapper.lintAt(env.toplevel.sourcefile, env.tree.pos()).get().isEnabled(THIS_ESCAPE)) + if (!lintMapper.lintAt(env.toplevel.sourcefile, env.tree.pos()).get().isActive(THIS_ESCAPE)) return; // Determine which packages are exported by the containing module, if any. @@ -342,7 +342,7 @@ private boolean currentClassIsExternallyExtendable() { .filter(MethodInfo::analyzable) .forEach(this::analyzeConstructor); - // Manually apply any Lint suppression + // Manually apply (and validate) any Lint suppression filterWarnings(warning -> !warning.isSuppressed()); // Field intitializers and initialization blocks will generate a separate warning for each primary constructor. @@ -1722,7 +1722,7 @@ Lint lint() { } boolean isSuppressed() { - return suppressible && !lint().isEnabled(THIS_ESCAPE); + return suppressible && !lint().isEnabled(THIS_ESCAPE, true); } int comparePos(StackFrame that) { @@ -1787,12 +1787,12 @@ static int sortByStackFrames(Warning warning1, Warning warning2) { } } - // Determine whether this warning is suppressed. A single "this-escape" warning involves multiple source code - // positions, so we must determine suppression manually. We do this as follows: A warning is suppressed if - // "this-escape" is disabled at any position in the stack where that stack frame corresponds to a constructor - // or field initializer in the target class. That means, for example, @SuppressWarnings("this-escape") annotations - // on regular methods are ignored. Here we work our way back up the call stack from the point of the leak until - // we encounter a suppressible stack frame. + // Determine whether this warning is suppressed and, if so, validate that suppression. A single "this-escape" + // warning involves multiple source code positions, so we must determine and validate suppression manually. + // We do this as follows: A warning is suppressed if "this-escape" is disabled at any position in the stack + // where that stack frame corresponds to a constructor or field initializer in the target class. That means, + // for example, @SuppressWarnings("this-escape") annotations on regular methods are ignored. We work our way + // back up the call stack from the point of the leak until we encounter a suppressible stack frame. boolean isSuppressed() { for (int index = stack.size() - 1; index >= 0; index--) { if (stack.get(index).isSuppressed()) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/WarningAnalyzer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/WarningAnalyzer.java index 00d1de386dbc3..1b5861c867e13 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/WarningAnalyzer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/WarningAnalyzer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -25,6 +25,7 @@ package com.sun.tools.javac.comp; +import com.sun.tools.javac.code.LintMapper; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; @@ -42,6 +43,7 @@ public class WarningAnalyzer { private final Log log; private final ThisEscapeAnalyzer thisEscapeAnalyzer; + private final LintMapper lintMapper; public static WarningAnalyzer instance(Context context) { WarningAnalyzer instance = context.get(contextKey); @@ -55,9 +57,13 @@ protected WarningAnalyzer(Context context) { context.put(contextKey, this); log = Log.instance(context); thisEscapeAnalyzer = ThisEscapeAnalyzer.instance(context); + lintMapper = LintMapper.instance(context); } public void analyzeTree(Env env) { thisEscapeAnalyzer.analyzeTree(env); + + // This one should go last + lintMapper.reportUnnecessarySuppressionAnnotations(env.toplevel.sourcefile, env.tree); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java index 5964c16c1517b..2e91b306069cb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, 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 @@ -519,7 +519,7 @@ protected static Collection nullCheck(Collection it) { synchronized void newOutputToPath(Path path) throws IOException { // Is output file clash detection enabled? - if (!lint.isEnabled(LintCategory.OUTPUT_FILE_CLASH)) + if (!lint.isActive(LintCategory.OUTPUT_FILE_CLASH)) return; // Get the "canonical" version of the file's path; we are assuming diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 95a7546e2b3a8..ab5858c603573 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1999, 2026, 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 @@ -2272,6 +2272,11 @@ compiler.warn.requires.automatic=\ compiler.warn.requires.transitive.automatic=\ requires transitive directive for an automatic module +# 0: string +# lint: suppression +compiler.warn.unnecessary.warning.suppression=\ + unnecessary warning suppression: {0} + # Warnings related to annotation processing # 0: string compiler.warn.proc.package.does.not.exist=\ 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 7824772b1f3e9..1628367d23c86 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 @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1999, 2026, 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 @@ -219,6 +219,9 @@ javac.opt.Xlint.desc.empty=\ javac.opt.Xlint.desc.exports=\ Warn about issues regarding module exports. +javac.opt.Xlint.desc.suppression=\ + Warn about recognized @SuppressWarnings values that don''t actually suppress any warnings. + javac.opt.Xlint.desc.fallthrough=\ Warn about falling through from one case of a switch statement to the next. 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 a4109a35ccb80..0865b64def843 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -141,12 +141,15 @@ public final void report(JCDiagnostic diag) { LintCategory category = diag.getLintCategory(); if (category != null) { // this is a lint warning; find the applicable Lint DiagnosticPosition pos = diag.getDiagnosticPosition(); + Lint theRootLint = rootLint(); if (pos != null && category.annotationSuppression) { // we should apply the Lint from the warning's position // Optimization: We don't need to go through the trouble of calculating the Lint instance at "pos" if // (a) "category" is disabled at the root level, and (b) the diagnostic doesn't have the DEFAULT_ENABLED // flag: @SuppressWarnings can only disable lint categories, so "category" is disabled in the entire file. - if (!rootLint().isEnabled(category) && + // But if tracking SUPPRESSION, skip this optimization because it might cause a validation to be missed. + if (!theRootLint.isEnabled(category, false) && + !theRootLint.isEnabled(SUPPRESSION, false) && !diag.isFlagSet(DEFAULT_ENABLED) && !diag.getCode().equals(RequiresTransitiveAutomatic.key())) // accommodate the "requires" hack below return; @@ -157,7 +160,7 @@ public final void report(JCDiagnostic diag) { return; } } else // we should apply the root Lint - lint = rootLint(); + lint = theRootLint; } reportWithLint(diag, lint); } @@ -168,7 +171,7 @@ public final void report(JCDiagnostic diag) { public final void reportWithLint(JCDiagnostic diag, Lint lint) { // Apply hackery for REQUIRES_TRANSITIVE_AUTOMATIC (see also Check.checkModuleRequires()) - if (diag.getCode().equals(RequiresTransitiveAutomatic.key()) && !lint.isEnabled(REQUIRES_TRANSITIVE_AUTOMATIC)) { + if (diag.getCode().equals(RequiresTransitiveAutomatic.key()) && !lint.isEnabled(REQUIRES_TRANSITIVE_AUTOMATIC, true)) { reportWithLint( diags.warning(null, diag.getDiagnosticSource(), diag.getDiagnosticPosition(), RequiresAutomatic), lint); return; @@ -178,12 +181,14 @@ public final void reportWithLint(JCDiagnostic diag, Lint lint) { if (lint != null) { LintCategory category = diag.getLintCategory(); boolean emit = !diag.isFlagSet(DEFAULT_ENABLED) ? // is the warning not enabled by default? - lint.isEnabled(category) : // then emit if the category is enabled + lint.isEnabled(category, false) : // 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 + !lint.isSuppressed(category, false) : // ...suppression happens via @SuppressWarnings !options.isDisabled(Option.XLINT, category); // ...suppression happens via -Xlint:-category - if (!emit) + if (!emit) { + validateSuppression(new SuppressionValidation(lint, diag)); // validate any suppression return; + } } // Proceed @@ -195,6 +200,11 @@ public final void reportWithLint(JCDiagnostic diag, Lint lint) { */ protected abstract void reportReady(JCDiagnostic diag); + /** + * Validate a lint suppression. + */ + protected abstract void validateSuppression(SuppressionValidation validation); + protected void addLintWaiter(JavaFileObject sourceFile, JCDiagnostic diagnostic) { lintWaitersMap.computeIfAbsent(sourceFile, s -> new LinkedList<>()).add(diagnostic); } @@ -230,6 +240,13 @@ public void flushLintWaiters() { return diagnosticList.isEmpty(); }); } + + // Represents the operation by which the suppression of a lint category is validated + protected record SuppressionValidation(Lint lint, JCDiagnostic diag) { + void apply() { + lint.validateSuppression(diag.getLintCategory()); + } + } } /** @@ -242,6 +259,9 @@ protected void addLintWaiter(JavaFileObject sourceFile, JCDiagnostic diagnostic) @Override protected void reportReady(JCDiagnostic diag) { } + + @Override + protected void validateSuppression(SuppressionValidation validation) { } } /** @@ -253,6 +273,7 @@ protected void reportReady(JCDiagnostic diag) { } */ public class DeferredDiagnosticHandler extends DiagnosticHandler { private List deferred = new ArrayList<>(); + private List validatedSuppressions = new ArrayList<>(); private final Predicate filter; private final boolean passOnNonDeferrable; @@ -291,6 +312,15 @@ protected void addLintWaiter(JavaFileObject sourceFile, JCDiagnostic diag) { } } + @Override + protected void validateSuppression(SuppressionValidation validation) { + if (deferrable(validation.diag)) { + validatedSuppressions.add(validation); + } else { + prev.validateSuppression(validation); + } + } + public List getDiagnostics() { return deferred; } @@ -315,6 +345,12 @@ public void reportDeferredDiagnostics(Predicate accepter) { .filter(accepter) .forEach(diagnostic -> prev.addLintWaiter(sourceFile, diagnostic))); lintWaitersMap = null; // prevent accidental ongoing use + + // Flush matching suppression validations to the previous handler + validatedSuppressions.stream() + .filter(vs -> accepter.test(vs.diag)) + .forEach(prev::validateSuppression); + validatedSuppressions = null; // prevent accidental ongoing use } /** Report all deferred diagnostics in the specified order. */ @@ -947,9 +983,14 @@ protected void reportReady(JCDiagnostic diagnostic) { // Apply the appropriate mandatory warning aggregator, if needed if (diagnostic.isFlagSet(AGGREGATE)) { LintCategory category = diagnostic.getLintCategory(); - boolean verbose = lintFor(diagnostic).isEnabled(category); - if (!aggregatorFor(category).aggregate(diagnostic, verbose)) + Lint lint = lintFor(diagnostic); + boolean verbose = lint.isEnabled(category, false); + if (!aggregatorFor(category).aggregate(diagnostic, verbose)) { + + // Aggregation effectively suppresses the warning, so validate that suppression + validateSuppression(new SuppressionValidation(lint, diagnostic)); return; + } } // Strict warnings are always emitted @@ -982,6 +1023,11 @@ protected void reportReady(JCDiagnostic diagnostic) { compressedOutput = true; } } + + @Override + protected void validateSuppression(SuppressionValidation validation) { + validation.apply(); // make it real + } } /** diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 4aa60a3dffeef..80b7d011455fb 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -186,6 +186,7 @@ * and interfaces * {@code static} accessing a static member using an instance * {@code strictfp} unnecessary use of the {@code strictfp} modifier + * {@code suppression} unnecessary suppressions in {@code @SuppressWarnings} annotations * {@code synchronization} deprecated alias for {@code identity} with an identical effect. * Users are encouraged to use {@code identity} instead of * {@code synchronization} for all current and future uses. diff --git a/src/jdk.compiler/share/man/javac.md b/src/jdk.compiler/share/man/javac.md index c749ca4da10cf..846be52fbe6d2 100644 --- a/src/jdk.compiler/share/man/javac.md +++ b/src/jdk.compiler/share/man/javac.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1994, 2026, 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 @@ -657,6 +657,9 @@ file system locations may be directories, JAR files or JMOD files. - `strictfp`: Warns about unnecessary use of the `strictfp` modifier. + - `suppression`: Warns about recognized `@SuppressWarnings` values that + don't actually suppress any warnings. + - `synchronization`: Deprecated alias for `identity` with an identical effect. Users are encouraged to use `identity` instead of `synchronization` for all current and future uses. diff --git a/test/langtools/tools/javac/diags/examples/WarnUnnecessaryLintSuppression.java b/test/langtools/tools/javac/diags/examples/WarnUnnecessaryLintSuppression.java new file mode 100644 index 0000000000000..7c36293293611 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/WarnUnnecessaryLintSuppression.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.warn.unnecessary.warning.suppression +// options: -Xlint:suppression + +@SuppressWarnings("unchecked") +class X { +} diff --git a/test/langtools/tools/javac/lint/SuppressionWarningTest.java b/test/langtools/tools/javac/lint/SuppressionWarningTest.java new file mode 100644 index 0000000000000..80ae1619b3619 --- /dev/null +++ b/test/langtools/tools/javac/lint/SuppressionWarningTest.java @@ -0,0 +1,1085 @@ +/* + * Copyright (c) 2026, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8344159 + * @summary Test "suppression" lint warnings + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask + * @run main SuppressionWarningTest + */ + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Source; + +import toolbox.JarTask; +import toolbox.JavacTask; +import toolbox.Task.Mode; +import toolbox.Task; +import toolbox.TestRunner; +import toolbox.ToolBox; + +import static com.sun.tools.javac.code.Lint.LintCategory.*; + +public class SuppressionWarningTest extends TestRunner { + + // Test cases for testSuppressWarnings(). Each test case triggers a warning the the corresponding + // lint category at a point in the source template which is nested inside two possible @SuppressWarnings + // annotations locations which are marked by "@INNER@" and "@OUTER@" markers. + public static final List SUPPRESS_WARNINGS_TEST_CASES = Stream.of(LintCategory.values()) + .map(category -> switch (category) { + case AUXILIARYCLASS -> new SuppressTest(category, + "compiler.warn.auxiliary.class.accessed.from.outside.of.its.source.file", + null, + """ + public class Class1 { } + class AuxClass { } + """, + """ + @OUTER@ + public class Class2 { + @INNER@ + public Object obj = new AuxClass(); + } + """ + ); + + case CAST -> new SuppressTest(category, + "compiler.warn.redundant.cast", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public Object obj = (Object)new Object(); + } + """ + ); + + case CLASSFILE -> null; // skip, too hard to simluate + + case DANGLING_DOC_COMMENTS -> new SuppressTest(category, + "compiler.warn.dangling.doc.comment", + null, + """ + @OUTER@ + public class Test { + /** Dangling comment */ + /** Javadoc comment */ + @INNER@ + public void foo() { + } + } + """ + ); + + case DEPRECATION -> new SuppressTest(category, + "compiler.warn.has.been.deprecated", + null, + """ + public class Super { + @Deprecated + public void foo() { } + } + """, + """ + @OUTER@ + public class Sub extends Super { + @INNER@ + @Override + public void foo() { } + } + """ + ); + + case DEP_ANN -> new SuppressTest(category, + "compiler.warn.missing.deprecated.annotation", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public class TestSub { + /** @deprecated */ + public void method() { } + } + } + """ + ); + + case DIVZERO -> new SuppressTest(category, + "compiler.warn.div.zero", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public int method() { + return 1/0; + } + } + """ + ); + + case EMPTY -> new SuppressTest(category, + "compiler.warn.empty.if", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void method(boolean x) { + if (x); + } + } + """ + ); + + case EXPORTS -> new SuppressTest(category, + "compiler.warn.leaks.not.accessible", + null, + """ + module mod { + exports pkg1; + } + """, + """ + // @MODULE@:mod + package pkg1; + @OUTER@ + public class Class1 { + @INNER@ + public pkg2.Class2 obj2; // warning here + } + """, + """ + // @MODULE@:mod + package pkg2; + public class Class2 { + } + """ + ); + + case FALLTHROUGH -> new SuppressTest(category, + "compiler.warn.possible.fall-through.into.case", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void method(int x) { + switch (x) { + case 1: + System.out.println(1); + default: + System.out.println(0); + } + } + } + """ + ); + + case FINALLY -> new SuppressTest(category, + "compiler.warn.finally.cannot.complete", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void method(int x) { + try { + System.out.println(x); + } finally { + throw new RuntimeException(); + } + } + } + """ + ); + + case INCUBATING -> null; // skip, too hard to simluate reliably over time + + case LOSSY_CONVERSIONS -> new SuppressTest(category, + "compiler.warn.possible.loss.of.precision", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void method() { + long b = 1L; + b += 0.1 * 3L; + } + } + """ + ); + + case MISSING_EXPLICIT_CTOR -> new SuppressTest(category, + "compiler.warn.missing-explicit-ctor", + null, + """ + module mod { + exports pkg1; + } + """, + """ + package pkg1; + @OUTER@ + public class Class1 { + public Class1(int x) { + } + @INNER@ + public static class Sub { + } + } + """ + ); + + case MODULE -> new SuppressTest(category, + "compiler.warn.poor.choice.for.module.name", + null, + """ + @OUTER@ + module mod0 { + } + """ + ); + + case OPENS -> new SuppressTest(category, + "compiler.warn.package.empty.or.not.found", + null, + """ + @OUTER@ + module mod { + opens pkg1; + } + """ + ); + + case OPTIONS -> new SuppressTest(category, + "compiler.warn.addopens.ignored", + new String[] { "--add-opens", "foo/bar=ALL-UNNAMED" }, + """ + @OUTER@ + public class Test { + @INNER@ + public class Test2 { + } + } + """ + ); + + // This test case only works on MacOS + case OUTPUT_FILE_CLASH -> + System.getProperty("os.name").startsWith("Mac") ? + new SuppressTest(category, + "compiler.warn.output.file.clash", + null, + """ + @OUTER@ + public class Test { + interface Cafe\u0301 { // macos normalizes "e" + U0301 -> U00e9 + } + interface Caf\u00e9 { + } + } + """ + ) : null; + + case OVERLOADS -> new SuppressTest(category, + "compiler.warn.potentially.ambiguous.overload", + null, + """ + import java.util.function.*; + @OUTER@ + public class Super { + public void foo(IntConsumer c) { + } + @INNER@ + public void foo(Consumer c) { + } + } + """ + ); + + case OVERRIDES -> new SuppressTest(category, + "compiler.warn.override.equals.but.not.hashcode", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public class Test2 { + public boolean equals(Object obj) { + return false; + } + } + } + """ + ); + + case PATH -> new SuppressTest(category, + "compiler.warn.path.element.not.found", + new String[] { "-classpath", "/nonigzistint" }, + """ + @OUTER@ + public class Test { + @INNER@ + public class Test2 { + } + } + """ + ); + + case PROCESSING -> null; // skip for now + + case RAW -> new SuppressTest(category, + "compiler.warn.raw.class.use", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void foo() { + Iterable i = null; + } + } + """ + ); + + case REMOVAL -> new SuppressTest(category, + "compiler.warn.has.been.deprecated.for.removal", + null, + """ + public class Super { + @Deprecated(forRemoval = true) + public void foo() { } + } + """, + """ + @OUTER@ + public class Sub extends Super { + @INNER@ + @Override + public void foo() { } + } + """ + ); + + // This test case requires special module support; see testSuppressWarnings() + case REQUIRES_AUTOMATIC -> new SuppressTest(category, + "compiler.warn.requires.automatic", + null, + """ + @OUTER@ + module m1x { + requires randomjar; + } + """ + ); + + // This test case requires special module support; see testSuppressWarnings() + case REQUIRES_TRANSITIVE_AUTOMATIC -> new SuppressTest(category, + "compiler.warn.requires.transitive.automatic", + null, + """ + @OUTER@ + module m1x { + requires transitive randomjar; + } + """ + ); + + case SERIAL -> new SuppressTest(category, + "compiler.warn.missing.SVUID", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public static class Inner implements java.io.Serializable { + public int x; + } + } + """ + ); + + case STATIC -> new SuppressTest(category, + "compiler.warn.static.not.qualified.by.type", + null, + """ + @OUTER@ + public class Test { + public static void foo() { + } + @INNER@ + public void bar() { + this.foo(); + } + } + """ + ); + + case STRICTFP -> new SuppressTest(category, + "compiler.warn.strictfp", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public strictfp void foo() { + } + } + """ + ); + + case SUPPRESSION -> new SuppressTest(category, + "compiler.warn.unnecessary.warning.suppression", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public class Inner1 { + @SuppressWarnings("unchecked") + public void foo() { + } + } + } + """ + ); + + case IDENTITY -> new SuppressTest(category, + "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", + null, + """ + @OUTER@ + public class Outer { + @INNER@ + public void foo() { + Integer i = 42; + synchronized (i) { + } + } + } + """ + ); + + case TEXT_BLOCKS -> new SuppressTest(category, + "compiler.warn.trailing.white.space.will.be.removed", + null, + """ + @OUTER@ + public class Test { + public void foo() { + String s = + \"\"\" + add trailing spaces here: + \"\"\"; + } + } + """.replaceAll("add trailing spaces here:", "$0 ") + ); + + case THIS_ESCAPE -> new SuppressTest(category, + "compiler.warn.possible.this.escape", + null, + """ + @OUTER@ + public class Outer { + @INNER@ + public static class Inner { + public Inner() { + leak(); + } + public void leak() { } + } + } + """ + ); + + case TRY -> new SuppressTest(category, + "compiler.warn.try.explicit.close.call", + null, + """ + import java.io.*; + @OUTER@ + public class Outer { + @INNER@ + public void foo() throws IOException { + try (InputStream in = new FileInputStream("x")) { + in.close(); + } + } + } + """ + ); + + case UNCHECKED -> new SuppressTest(category, + "compiler.warn.prob.found.req: (compiler.misc.unchecked.cast.to.type)", + null, + """ + @OUTER@ + public class Test { + public void foo() { + Iterable c = null; + @INNER@ + Iterable t = (Iterable)c, s = null; + } + } + """ + ); + + case VARARGS -> new SuppressTest(category, + "compiler.warn.varargs.unsafe.use.varargs.param", + null, + """ + @OUTER@ + public class Test { + @INNER@ + @SafeVarargs + public static void bar(final T... barArgs) { + baz(barArgs); + } + public static void baz(final T[] bazArgs) { + } + } + """ + ); + + case PREVIEW -> new SuppressTest(category, + "compiler.warn.preview.feature.use", + new String[] { + "--enable-preview", + "-XDforcePreview" + }, + """ + @OUTER@ + public class Test { + @INNER@ + public Test(Object x) { + int value = x instanceof Integer i ? i : -1; + } + } + """ + ); + + case RESTRICTED -> new SuppressTest(category, + "compiler.warn.restricted.method", + null, + """ + @OUTER@ + public class Test { + @INNER@ + public void foo() { + System.load(""); + } + } + """ + ); + + default -> throw new AssertionError("missing test case for " + category); + + }) + .filter(Objects::nonNull) // skip categories with no test case defined + .collect(Collectors.toList()); + + protected final ToolBox tb; + + public SuppressionWarningTest() { + super(System.err); + tb = new ToolBox(); + } + + public static void main(String... args) throws Exception { + new SuppressionWarningTest().runTests(m -> new Object[0]); + } + +// Tests + + @Test + public void testSuppressWarnings() throws Exception { + testAll("testSuppressWarnings", this::testSuppressWarnings, SUPPRESS_WARNINGS_TEST_CASES); + } + + // We are testing all combinations of nested @SuppressWarning annotations and lint flags + public void testSuppressWarnings(SuppressTest test) throws Exception { + + // Setup directories + Path base = Paths.get("testSuppressWarnings"); + resetCompileDirectories(base); + + // Detect if any modules are being compiled; if so we need to create an extra source directory level + Pattern moduleDecl = Pattern.compile("module\\s+(\\S*).*"); + Set moduleNames = test.sources.stream() + .flatMap(source -> Stream.of(source.split("\\n"))) + .map(moduleDecl::matcher) + .filter(Matcher::matches) + .map(matcher -> matcher.group(1)) + .collect(Collectors.toSet()); + + // Special JAR file support for REQUIRES_AUTOMATIC and REQUIRES_TRANSITIVE_AUTOMATIC + Path modulePath = base.resolve("modules"); + resetDirectory(modulePath); + LintCategory category = test.category; + switch (category) { + case REQUIRES_AUTOMATIC: + case REQUIRES_TRANSITIVE_AUTOMATIC: + + // Compile a simple automatic module (randomjar-1.0) + Path randomJarBase = base.resolve("randomjar"); + tb.writeJavaFiles(getSourcesDir(randomJarBase), "package api; public class Api {}"); + List log = compile(randomJarBase, Task.Expect.SUCCESS, "-Werror"); + if (!log.isEmpty()) { + throw new AssertionError(String.format( + "non-empty log output:%n %s", log.stream().collect(Collectors.joining("\n ")))); + } + + // JAR it up + Path automaticJar = modulePath.resolve("randomjar-1.0.jar"); + new JarTask(tb, automaticJar) + .baseDir(getClassesDir(randomJarBase)) + .files("api/Api.class") + .run(); + break; + + default: + modulePath = null; + break; + }; + + // Create a @SuppressWarnings annotation + String annotation = String.format("@SuppressWarnings(\"%s\")", category.option); + + // See which annotation substitutions this test supports + boolean hasOuterAnnotation = test.sources.stream().anyMatch(source -> source.contains("@OUTER@")); + boolean hasInnerAnnotation = test.sources.stream().anyMatch(source -> source.contains("@INNER@")); + + // Try all combinations of inner and outer @SuppressWarnings + boolean[] booleans = new boolean[] { false, true }; + for (boolean outerAnnotation : booleans) { for (boolean innerAnnotation : booleans) { + + // Skip this scenario if not supported by test case + if ((outerAnnotation && !hasOuterAnnotation) || (innerAnnotation && !hasInnerAnnotation)) + continue; + + // Insert or comment out the @SuppressWarnings annotations in the source templates + String[] sources = test.sources.stream() + .map(source -> source.replace("@OUTER@", + String.format("%s@SuppressWarnings(\"%s\")", outerAnnotation ? "" : "//", category.option))) + .map(source -> source.replace("@INNER@", + String.format("%s@SuppressWarnings(\"%s\")", innerAnnotation ? "" : "//", category.option))) + .toArray(String[]::new); + for (String source : sources) { + Path pkgRoot = getSourcesDir(base); + String moduleName = Optional.of("@MODULE@:(\\S+)") + .map(Pattern::compile) + .map(p -> p.matcher(source)) + .filter(Matcher::find) + .map(m -> m.group(1)) + .orElse(null); + if (moduleName != null) { // add an extra directory for module + if (!moduleNames.contains(moduleName)) + throw new AssertionError(String.format("unknown module \"%s\" in %s", moduleName, category)); + pkgRoot = pkgRoot.resolve(moduleName); + } + tb.writeJavaFiles(pkgRoot, source); + } + + // Try all combinations of lint flags for and SUPPRESSION + for (boolean enableCategory : booleans) { // [-]category + for (boolean enableSuppression : booleans) { // [-]suppression + + // Special case when category is SUPPRESSION itself: avoid a contradiction + if (category == LintCategory.SUPPRESSION && enableCategory != enableSuppression) + continue; + + // Should we expect the "test.warningKey" warning to be emitted? + boolean expectCategoryWarning = category.annotationSuppression ? + enableCategory && !outerAnnotation && !innerAnnotation : // the warning must be enabled and not suppressed + enableCategory; // @SuppressWarnings has no effect at all + + // Should we expect the SUPPRESSION warning to be emitted? + boolean expectSuppressionWarning = category.annotationSuppression ? + enableSuppression && outerAnnotation && innerAnnotation : // only if both (then outer is redundant) + enableSuppression && (outerAnnotation || innerAnnotation); // either one is always redundant + + // Prepare command line flags + ArrayList flags = new ArrayList<>(); + if (modulePath != null) { + flags.add("--module-path"); + flags.add(modulePath.toString()); + } + if (!test.compileFlags.contains("--release")) { + flags.add("--release"); + flags.add(Source.DEFAULT.name); + } + flags.addAll(test.compileFlags); + + // Add the -Xlint flag (if any) + LinkedHashSet lints = new LinkedHashSet<>(); + lints.add(String.format("%s%s", enableCategory ? "" : "-", category.option)); + if (enableSuppression) + lints.add(SUPPRESSION.option); + if (!lints.isEmpty()) + flags.add("-Xlint:" + lints.stream().collect(Collectors.joining(","))); + + // Test case description + String description = String.format("[%s] outer=%s inner=%s enable=%s flags=\"%s\"", + category, outerAnnotation, innerAnnotation, enableCategory, flags.stream().collect(Collectors.joining(" "))); + + // Only print log if test case fails + StringWriter buf = new StringWriter(); + PrintWriter log = new PrintWriter(buf); + try { + + // Logging + log.println(String.format(">>> Test START: %s", description)); + Stream.of(sources).forEach(log::println); + log.println(String.format(">>> expectCategoryWarning=%s", expectCategoryWarning)); + log.println(String.format(">>> expectSuppressionWarning=%s", expectSuppressionWarning)); + + // Compile sources and get log output + List output = compile(base, Task.Expect.SUCCESS, flags.toArray(new String[0])); + + // Scrub insignificant log output + output.removeIf(line -> line.matches("[0-9]+ (error|warning)s?")); + output.removeIf(line -> line.contains("compiler.err.warnings.and.werror")); + output.removeIf(line -> line.matches("- compiler\\.note\\..*")); // mandatory warning "recompile" etc. + + // See if the category warning appeared as expected + boolean foundCategoryWarning = output.removeIf(line -> line.contains(test.warningKey)); + if (foundCategoryWarning != expectCategoryWarning) { + throw new AssertionError(String.format("%s: category warning: found=%s but expected=%s", + description, foundCategoryWarning, expectCategoryWarning)); + } + + // See if the suppression warning appeared as expected (but skip redundant check for SUPPRESSION) + if (category != LintCategory.SUPPRESSION) { + boolean foundSuppressionWarning = output.removeIf( + line -> line.contains("compiler.warn.unnecessary.warning.suppression")); + if (foundSuppressionWarning != expectSuppressionWarning) { + throw new AssertionError(String.format("%s: \"%s\" warning: found=%s but expected=%s", + description, SUPPRESSION.option, foundSuppressionWarning, expectSuppressionWarning)); + } + } + + // There shouldn't be any other warnings + if (!output.isEmpty()) { + throw new AssertionError(String.format( + "%s: %d unexpected warning(s): %s", description, output.size(), output)); + } + + // Done + log.println(String.format("<<< Test PASSED: %s", description)); + } catch (AssertionError e) { + log.println(String.format("<<< Test FAILED: %s", description)); + log.flush(); + out.print(buf); + throw e; + } + } + } + } } + } + + @Test + public void testUselessAnnotation() throws Exception { + testAll("testUselessAnnotation", this::testUselessAnnotation, List.of(LintCategory.values())); + } + + // Test a @SuppressWarning annotation that suppresses nothing + private void testUselessAnnotation(LintCategory category) throws Exception { + String warningKey = category != LintCategory.SUPPRESSION ? // @SuppressWarnings("suppression") can never be useless! + "compiler.warn.unnecessary.warning.suppression" : null; + compileAndExpect( + warningKey, + String.format( + """ + @SuppressWarnings(\"%s\") + public class Test { } + """, + category.option), + String.format("-Xlint:%s", SUPPRESSION.option)); + } + + @Test + public void testSelfSuppression() throws Exception { + testAll("testSelfSuppression", this::testSelfSuppression, List.of(LintCategory.values())); + } + + // Test the suppression of SUPPRESSION itself, which should always work, + // even when the same annotation uselessly suppresses some other category. + private void testSelfSuppression(LintCategory category) throws Exception { + + // Test category and SUPPRESSION in the same annotation + compileAndExpectSuccess( + String.format( + """ + @SuppressWarnings({ \"%s\", \"%s\" }) + public class Test { } + """, + category.option, // this is actually a useless suppression + SUPPRESSION.option), // but this prevents us from reporting it + String.format("-Xlint:%s", SUPPRESSION.option)); + + // Test category and SUPPRESSION in nested annotations + compileAndExpectSuccess( + String.format( + """ + @SuppressWarnings(\"%s\") // suppress useless suppression warnings + public class Test { + @SuppressWarnings(\"%s\") // a useless suppression + public class Sub { } + } + """, + SUPPRESSION.option, // this prevents us from reporting the nested useless suppression + category.option), // this is a useless suppression + String.format("-Xlint:%s", SUPPRESSION.option)); + } + + // Test OVERLOADS which has tricky "either-or" suppression + @Test + public void testOverloads() throws Exception { + compileAndExpectSuccess( + """ + import java.util.function.*; + public class Super { + @SuppressWarnings("overloads") + public void foo(IntConsumer c) { + } + @SuppressWarnings("overloads") + public void foo(Consumer c) { + } + } + """, + String.format("-Xlint:%s", OVERLOADS.option), + String.format("-Xlint:%s", SUPPRESSION.option)); + } + + // Test THIS_ESCAPE which has tricky constructor control-flow based suppression + @Test + public void testThisEscape1() throws Exception { + compileAndExpectSuccess( + """ + public class Test { + @SuppressWarnings("this-escape") + private int y = leak(); + public Test() { + this(0); + } + @SuppressWarnings("this-escape") + private Test(int x) { + this.leak(); + } + protected int leak() { + return 0; + } + } + """, + String.format("-Xlint:%s", THIS_ESCAPE.option), + String.format("-Xlint:%s", SUPPRESSION.option)); + } + + @Test + public void testThisEscape2() throws Exception { + compileAndExpect( + "compiler.warn.unnecessary.warning.suppression", + """ + public class Test { + public Test() { + this.leak(); + } + @SuppressWarnings("this-escape") // this does nothing -> "suppression" warning here + protected int leak() { + return 0; + } + } + """, + String.format("-Xlint:%s", THIS_ESCAPE.option), + String.format("-Xlint:%s", SUPPRESSION.option)); + } + +// Support Stuff + + // Run a test on a sequence of test cases + private void testAll(String testName, ThrowingConsumer test, Iterable testCases) throws Exception { + int totalCount = 0; + int errorCount = 0; + for (T testCase : testCases) { + try { + test.accept(testCase); + out.println(String.format("%s: %s: %s", testName, "PASSED", testCase)); + } catch (Exception e) { + out.println(String.format("%s: %s: %s: %s", testName, "FAILED", testCase, e)); + errorCount++; + } + totalCount++; + } + if (errorCount > 0) + throw new Exception(String.format("%s: %d/%d test case(s) failed", testName, errorCount, totalCount)); + } + + public void compileAndExpectSuccess(String source, String... flags) throws Exception { + compileAndExpect(null, source, flags); + } + + public void compileAndExpect(String warningKey, String source, String... flags) throws Exception { + + // Setup source & destination diretories + Path base = Paths.get("compileAndExpect"); + resetCompileDirectories(base); + + // Write source file + tb.writeJavaFiles(getSourcesDir(base), source); + + // Add -Werror flag so any warning causes compilation to fail + flags = Stream.concat(Stream.of(flags), Stream.of("-Werror")).toArray(String[]::new); + + // Compile sources and verify we got the expected result + boolean expectSuccess = warningKey == null; + List log = compile(base, expectSuccess ? Task.Expect.SUCCESS : Task.Expect.FAIL, flags); + + // A successful compilation should not log any warnings + if (expectSuccess && !log.isEmpty()) { + throw new AssertionError(String.format( + "non-empty log output:%n %s", log.stream().collect(Collectors.joining("\n ")))); + } + + // A failed compilation should log the expected warning + if (!expectSuccess && log.stream().noneMatch(line -> line.contains(warningKey))) { + throw new AssertionError(String.format( + "did not find \"%s\" in log output:%n %s", + warningKey, log.stream().collect(Collectors.joining("\n ")))); + } + } + + private List compile(Path base, Task.Expect expectation, String... flags) throws Exception { + ArrayList options = new ArrayList<>(); + options.add("-XDrawDiagnostics"); + Stream.of(flags).forEach(options::add); + List log; + try { + log = new JavacTask(tb, Mode.CMDLINE) + .options(options.toArray(new String[0])) + .files(tb.findJavaFiles(getSourcesDir(base))) + .outdir(getClassesDir(base)) + .run(expectation) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + } catch (Task.TaskError e) { + throw new AssertionError(String.format( + "compile in %s failed: %s", getSourcesDir(base), e.getMessage()), e); + } + log.removeIf(line -> line.trim().isEmpty()); + return log; + } + + private Path getSourcesDir(Path base) { + return base.resolve("sources"); + } + + private Path getClassesDir(Path base) { + return base.resolve("classes"); + } + + private void resetCompileDirectories(Path base) throws IOException { + for (Path dir : List.of(getSourcesDir(base), getClassesDir(base))) + resetDirectory(dir); + } + + private void resetDirectory(Path dir) throws IOException { + if (Files.exists(dir, LinkOption.NOFOLLOW_LINKS)) + Files.walkFileTree(dir, new Deleter()); + Files.createDirectories(dir); + } + +// ThrowingConsumer + + @FunctionalInterface + private interface ThrowingConsumer { + void accept(T testCase) throws Exception; + } + +// SuppressTest + + // A test case for testSuppressWarnings() + private record SuppressTest( + LintCategory category, // The Lint category being tested + String warningKey, // Expected warning message key in compiler.properties + List compileFlags, // Any required compilation flags + List sources // Source files with @MODULE@, @OUTER@ and @INNER@ placeholders + ) { + SuppressTest(LintCategory category, String warningKey, String[] compileFlags, String... sources) { + this(category, warningKey, List.of(compileFlags != null ? compileFlags : new String[0]), List.of(sources)); + } + + @Override + public String toString() { + return "SuppressTest[" + category + "]"; + } + } + +// Deleter + + private static class Deleter extends SimpleFileVisitor { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + } +} diff --git a/test/langtools/tools/javac/warnings/DepAnn.java b/test/langtools/tools/javac/warnings/DepAnn.java index d725258cf3ab7..4209089971140 100644 --- a/test/langtools/tools/javac/warnings/DepAnn.java +++ b/test/langtools/tools/javac/warnings/DepAnn.java @@ -1,7 +1,7 @@ /* * @test /nodynamiccopyright/ * @bug 4986256 - * @compile/ref=DepAnn.out -XDrawDiagnostics -Xlint:all,-dangling-doc-comments DepAnn.java + * @compile/ref=DepAnn.out -XDrawDiagnostics -Xlint:all,-dangling-doc-comments,-suppression DepAnn.java */ // control: this class should generate warnings