From 2a48175d20c0df40911e31f778613a7f67fe38ff Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 19:15:29 -0400 Subject: [PATCH 1/6] Add SuppressWarnings classes with AnnotatedFor methods --- .../annotatedfornullness/AnnotatedForNullness.java | 8 ++++++++ .../annotatedfor/AnnotatedForTest.java | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index b0c4472ec506..86534cb02189 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -73,4 +73,12 @@ void bar() { annotatedForNullness(initializedField); annotatedForNullnessAndInitialization(initializedField); } + + @SuppressWarnings("nullness") + class SuppressWarningsClassWithAnnotatedForMethod { + @AnnotatedFor("nullness") + @NonNull Object m() { + return null; + } + } } diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index b73ea47242b4..967ca6dca4fe 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -247,4 +247,14 @@ static class staticAnnotatedAndWarningsSuppressedClass { so4 = staticUnannotatedMethod(so1); } } + + // Class-level @SuppressWarnings("subtyping") should override a method-level + // @AnnotatedFor("subtyping") + @SuppressWarnings("subtyping") + class SuppressWarningsClassWithAnnotatedForMethod { + @AnnotatedFor("subtyping") + @SubQual Object m(@SuperQual Object p) { + return p; + } + } } From 66e664f497b198e9b1dcc430ea23cdd2aa1f4368 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 19:28:19 -0400 Subject: [PATCH 2/6] Remove redundant check for AnnotatedFor in SourceChecker --- .../org/checkerframework/framework/source/SourceChecker.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index c3133c10c211..1263e01f931b 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2875,11 +2875,6 @@ public boolean shouldSuppressWarnings(Element elt, String errKey) { return true; } } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the - // enclosing elements, because they may not have an @AnnotatedFor. - return false; - } } return false; } From 90c6889df9af476634622fbfdfeeda95be11a55a Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 22:31:29 -0400 Subject: [PATCH 3/6] Remove redundant check for AnnotatedFor in SourceChecker --- .../annotatedfornullness/AnnotatedForNullness.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index 86534cb02189..4f7b9aa7da62 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -74,6 +74,8 @@ void bar() { annotatedForNullnessAndInitialization(initializedField); } + // Class-level @SuppressWarnings("nullness") should override a method-level + // @AnnotatedFor("nullness") @SuppressWarnings("nullness") class SuppressWarningsClassWithAnnotatedForMethod { @AnnotatedFor("nullness") From 87b149f430341e5f93d7712dda5787c108f331b9 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 5 May 2026 15:26:33 -0400 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Alex Cook <43047600+thisisalexandercook@users.noreply.github.com> --- .../annotatedfornullness/AnnotatedForNullness.java | 4 ++-- .../conservative-defaults/annotatedfor/AnnotatedForTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index 4f7b9aa7da62..bd933cf471e4 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -74,8 +74,8 @@ void bar() { annotatedForNullnessAndInitialization(initializedField); } - // Class-level @SuppressWarnings("nullness") should override a method-level - // @AnnotatedFor("nullness") + // @SuppressWarnings("nullness") should suppress all nullness diagnostics within the annotated declaration's scope, + // including those in nested @AnnotatedFor("nullness") scopes. @SuppressWarnings("nullness") class SuppressWarningsClassWithAnnotatedForMethod { @AnnotatedFor("nullness") diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index 967ca6dca4fe..bc655da0ecc3 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -248,8 +248,8 @@ static class staticAnnotatedAndWarningsSuppressedClass { } } - // Class-level @SuppressWarnings("subtyping") should override a method-level - // @AnnotatedFor("subtyping") + // @SuppressWarnings("subtyping") should suppress all subtying diagnostics within the annotated declaration's scope, + // including those in nested @AnnotatedFor("subtyping") scopes. @SuppressWarnings("subtyping") class SuppressWarningsClassWithAnnotatedForMethod { @AnnotatedFor("subtyping") From 19dfafc1254fe44b4b50b50b920d769981410ad8 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 5 May 2026 17:23:18 -0400 Subject: [PATCH 5/6] Apply formatter --- .../annotatedfornullness/AnnotatedForNullness.java | 4 ++-- .../conservative-defaults/annotatedfor/AnnotatedForTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java index bd933cf471e4..81f2dcafb2e4 100644 --- a/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java +++ b/checker/tests/nulless-conservative-defaults/annotatedfornullness/AnnotatedForNullness.java @@ -74,8 +74,8 @@ void bar() { annotatedForNullnessAndInitialization(initializedField); } - // @SuppressWarnings("nullness") should suppress all nullness diagnostics within the annotated declaration's scope, - // including those in nested @AnnotatedFor("nullness") scopes. + // @SuppressWarnings("nullness") should suppress all nullness diagnostics within the annotated + // declaration's scope, including those in nested @AnnotatedFor("nullness") scopes. @SuppressWarnings("nullness") class SuppressWarningsClassWithAnnotatedForMethod { @AnnotatedFor("nullness") diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index bc655da0ecc3..c7885f64cc29 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -248,8 +248,8 @@ static class staticAnnotatedAndWarningsSuppressedClass { } } - // @SuppressWarnings("subtyping") should suppress all subtying diagnostics within the annotated declaration's scope, - // including those in nested @AnnotatedFor("subtyping") scopes. + // @SuppressWarnings("subtyping") should suppress all subtying diagnostics within the annotated + // declaration's scope, including those in nested @AnnotatedFor("subtyping") scopes. @SuppressWarnings("subtyping") class SuppressWarningsClassWithAnnotatedForMethod { @AnnotatedFor("subtyping") From ca073e683fa1fff2d915684a111e74bf5d865a3e Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 11 May 2026 11:46:44 -0400 Subject: [PATCH 6/6] Enhance documentation and logic for @AnnotatedFor and @SuppressWarnings handling --- docs/manual/annotating-libraries.tex | 8 ++- docs/manual/warnings.tex | 7 +- .../framework/source/SourceChecker.java | 68 ++++++++++++------- .../annotatedfor/AnnotatedForTest.java | 2 +- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/docs/manual/annotating-libraries.tex b/docs/manual/annotating-libraries.tex index db580a20531c..aa0ba05f7645 100644 --- a/docs/manual/annotating-libraries.tex +++ b/docs/manual/annotating-libraries.tex @@ -384,8 +384,12 @@ conservative defaults (see Section~\ref{defaults-classfile}) for any type use with no explicit user-written annotation, \emph{and} the checker issues no warnings. -The command-line argument \code{-AonlyAnnotatedFor} can be used to suppress errors and warnings outside of the scope of an \<@AnnotatedFor> annotation, -but does not change the default qualifiers for source code (See Section~\ref{aonlyannotatedfor}). +Warnings in code with a relevant \<@AnnotatedFor> annotation can still be suppressed by +an in-scope \<@SuppressWarnings> annotation. +The command-line argument \code{-AonlyAnnotatedFor} can be used to suppress +errors and warnings outside of the scope of an \<@AnnotatedFor> annotation, +but does not change the default qualifiers for source code (see +Section~\ref{aonlyannotatedfor}). \end{sloppypar} The \refqualclass{framework/qual}{AnnotatedFor} annotation, written on a diff --git a/docs/manual/warnings.tex b/docs/manual/warnings.tex index 5fbfacc8c911..773b066750d4 100644 --- a/docs/manual/warnings.tex +++ b/docs/manual/warnings.tex @@ -694,8 +694,11 @@ \sectionAndLabel{\code{-AonlyAnnotatedFor} command-line option}{aonlyannotatedfor} -You can suppress all errors and warnings for code outside of a corresponding \code{@AnnotatedFor} by applying this command-line option. -Note that the \code{@AnnotatedFor} annotation must include the checker's name to enable warnings from that checker. +You can suppress all errors and warnings for code outside of a corresponding +\code{@AnnotatedFor} by applying this command-line option. +Note that the \code{@AnnotatedFor} annotation must include the checker's name to +enable warnings from that checker, except for warnings suppressed by another +mechanism such as \code{@SuppressWarnings}. For example, use \code{@AnnotatedFor("nullness")} for the Nullness Checker. This flag only suppresses warnings, compared to \code{-AuseConservativeDefaultsForUncheckedCode=source}, which also applies conservative defaults for code outside the scope of an \code{@AnnotatedFor} annotation. diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java index 1263e01f931b..c49a58670d45 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2756,6 +2756,12 @@ public boolean shouldSuppressWarnings(Tree tree, String errKey) { * otherwise */ public boolean shouldSuppressWarnings(TreePath path, String errKey) { + if (shouldSuppress(getSuppressWarningsStringsFromOption(), errKey)) { + return true; + } + + boolean foundAnnotatedFor = false; + // iterate through the path; continue until path contains no declarations for (TreePath declPath = TreePathUtil.enclosingDeclarationPath(path); declPath != null; @@ -2764,41 +2770,36 @@ public boolean shouldSuppressWarnings(TreePath path, String errKey) { if (decl instanceof VariableTree) { Element elt = TreeUtils.elementFromDeclaration((VariableTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { + if (shouldSuppressWarningsOnElement(elt, errKey)) { return true; } } else if (decl instanceof MethodTree) { Element elt = TreeUtils.elementFromDeclaration((MethodTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { + if (shouldSuppressWarningsOnElement(elt, errKey)) { return true; } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; + if (!foundAnnotatedFor && isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + foundAnnotatedFor = true; } } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { // A class tree Element elt = TreeUtils.elementFromDeclaration((ClassTree) decl); - if (shouldSuppressWarnings(elt, errKey)) { + if (shouldSuppressWarningsOnElement(elt, errKey)) { return true; } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; + if (!foundAnnotatedFor && isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + foundAnnotatedFor = true; } Element packageElement = elt.getEnclosingElement(); if (packageElement != null && packageElement.getKind() == ElementKind.PACKAGE) { - if (shouldSuppressWarnings(packageElement, errKey)) { + if (shouldSuppressWarningsOnElement(packageElement, errKey)) { return true; } - if (isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { - // Return false immediately. Do NOT check for AnnotatedFor in the enclosing - // elements as the closest AnnotatedFor is already found. - return false; + if (!foundAnnotatedFor + && isAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { + foundAnnotatedFor = true; } } } else { @@ -2806,7 +2807,9 @@ public boolean shouldSuppressWarnings(TreePath path, String errKey) { } } - if (useConservativeDefaultsSource || onlyAnnotatedFor) { + if (foundAnnotatedFor) { + return false; + } else if (useConservativeDefaultsSource || onlyAnnotatedFor) { // If we got this far without hitting an @AnnotatedFor and returning // false, we DO suppress the warning. return true; @@ -2865,15 +2868,30 @@ public boolean shouldSuppressWarnings(Element elt, String errKey) { } for (Element currElt = elt; currElt != null; currElt = currElt.getEnclosingElement()) { - SuppressWarnings suppressWarningsAnno = currElt.getAnnotation(SuppressWarnings.class); - if (suppressWarningsAnno != null) { - String[] suppressWarningsStrings = suppressWarningsAnno.value(); - if (shouldSuppress(suppressWarningsStrings, errKey)) { - if (warnUnneededSuppressions) { - elementsWithSuppressedWarnings.add(currElt); - } - return true; + if (shouldSuppressWarningsOnElement(currElt, errKey)) { + return true; + } + } + return false; + } + + /** + * Returns true if the given element has a {@code @SuppressWarnings} annotation that suppresses + * the given error key. + * + * @param elt the element whose annotations to check + * @param errKey the error key the checker is emitting + * @return true if {@code elt} has an corresponding {@code @SuppressWarnings} annotation + */ + private boolean shouldSuppressWarningsOnElement(Element elt, String errKey) { + SuppressWarnings suppressWarningsAnno = elt.getAnnotation(SuppressWarnings.class); + if (suppressWarningsAnno != null) { + String[] suppressWarningsStrings = suppressWarningsAnno.value(); + if (shouldSuppress(suppressWarningsStrings, errKey)) { + if (warnUnneededSuppressions) { + elementsWithSuppressedWarnings.add(elt); } + return true; } } return false; diff --git a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java index c7885f64cc29..b44e03eb3650 100644 --- a/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java +++ b/framework/tests/conservative-defaults/annotatedfor/AnnotatedForTest.java @@ -248,7 +248,7 @@ static class staticAnnotatedAndWarningsSuppressedClass { } } - // @SuppressWarnings("subtyping") should suppress all subtying diagnostics within the annotated + // @SuppressWarnings("subtyping") should suppress all subtyping diagnostics within the annotated // declaration's scope, including those in nested @AnnotatedFor("subtyping") scopes. @SuppressWarnings("subtyping") class SuppressWarningsClassWithAnnotatedForMethod {