From eb741f4cce37abd5aad8adfac55c442ac04d9b38 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Tue, 7 Oct 2025 16:46:48 -0700 Subject: [PATCH 01/15] Reapply code changes with profile --- .../common/basetype/BaseTypeChecker.java | 18 +++++++ .../util/count/AnnotationStatistics.java | 7 +++ .../common/util/count/JavaCodeStatistics.java | 7 +++ .../common/util/debug/SignaturePrinter.java | 8 +++ .../framework/source/AggregateChecker.java | 11 ++++ .../framework/source/SourceChecker.java | 50 +++---------------- .../framework/type/AnnotatedTypeFactory.java | 46 +++++++++++++++++ .../util/defaults/QualifierDefaults.java | 49 ++---------------- 8 files changed, 106 insertions(+), 90 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 0bb07e6db3f8..2737f15a418a 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; /** * An abstract {@link SourceChecker} that provides a simple {@link @@ -313,4 +314,21 @@ public BaseTypeChecker getUltimateParentChecker() { causeMessage); } } + + /** + * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an + * upstream checker that called this one. + * + * @param elt the source code element to check, or null + * @return true if the element is annotated for this checker or an upstream checker + */ + @Override + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + // The implementation of this method is at AnnotatedTypeFactory because postinit() is called + // in the constructor of ATFs and use the same method before the ATF is fully initialized. + // Implement it here and call this method by atf.getChecker() would fail as it will call + // getTypeFactory() in the body and would result type system error as the visitor is not + // initialized yet. + return getTypeFactory().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index 5415660c9874..8696e35b31fc 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -23,6 +23,7 @@ import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.framework.source.SupportedOptions; import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.BugInCF; import java.util.HashMap; import java.util.Map; @@ -31,6 +32,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; import javax.lang.model.element.Name; import javax.tools.Diagnostic; @@ -326,4 +328,9 @@ public AnnotationProvider getAnnotationProvider() { throw new UnsupportedOperationException( "getAnnotationProvider is not implemented for this class."); } + + @Override + protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + throw new BugInCF("Unexpected call to determine whether this checker is annotated"); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java index 5eed8083588b..2366021b4086 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java @@ -17,6 +17,7 @@ import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; import java.util.List; @@ -24,6 +25,7 @@ import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; /** @@ -203,4 +205,9 @@ public AnnotationProvider getAnnotationProvider() { throw new UnsupportedOperationException( "getAnnotationProvider is not implemented for this class."); } + + @Override + protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + throw new BugInCF("Unexpected call to determine whether this checker is annotated"); + } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java index e27de1e8b599..dec3e9645c51 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java @@ -15,6 +15,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; @@ -100,6 +101,13 @@ private void init(ProcessingEnvironment env, @Nullable @BinaryName String checke checker = new SourceChecker() { + @Override + protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker( + Element elt) { + throw new BugInCF( + "Unexpected call to determine whether this checker is annotated"); + } + @Override protected SourceVisitor createSourceVisitor() { return null; diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index 727e2b8f6d6c..ae1dd72d1f76 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -7,6 +7,7 @@ import java.util.LinkedHashSet; import java.util.Set; +import javax.lang.model.element.Element; import javax.tools.Diagnostic; /** @@ -52,4 +53,14 @@ protected final Set> getImmediateSubcheckerClasse // the checkers in the aggregate checker do. }; } + + /** + * Return false, determine whether an aggregatechecker is annotated for depends on its + * subcheckers. For checkers that have upstream checker and want to handle annotatedfor in both + * this and upstream checker, see InitializationChecker#getUpstreamCheckerNames(). + */ + @Override + protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + return false; + } } 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 0c0c9a37d1a2..a347368e837f 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -32,9 +32,7 @@ import org.checkerframework.checker.signature.qual.FullyQualifiedName; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.reflection.MethodValChecker; -import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.type.AnnotatedTypeFactory; -import org.checkerframework.framework.util.CheckerMain; import org.checkerframework.framework.util.OptionConfiguration; import org.checkerframework.framework.util.TreePathCacher; import org.checkerframework.javacutil.AbstractTypeProcessor; @@ -153,7 +151,7 @@ // only issue errors for code inside the scope of `@NullMarked` annotations. // See // https://github.com/uber/NullAway/wiki/Configuration#only-nullmarked-version-0123-and-after. - // org.checkerframework.framework.source.SourceChecker.isAnnotatedForThisCheckerOrUpstreamChecker + // org.checkerframework.framework.source.SourceChecker.isElementAnnotatedForThisCheckerOrUpstreamChecker "onlyAnnotatedFor", // Unsoundly assume all methods have no side effects, are deterministic, or both. @@ -2778,9 +2776,7 @@ public boolean shouldSuppressWarnings(@Nullable TreePath path, String 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. + if (isElementAnnotatedForThisCheckerOrUpstreamChecker(elt)) { return false; } } else if (TreeUtils.classTreeKinds().contains(decl.getKind())) { @@ -2790,9 +2786,7 @@ public boolean shouldSuppressWarnings(@Nullable TreePath path, String 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. + if (isElementAnnotatedForThisCheckerOrUpstreamChecker(elt)) { return false; } Element packageElement = elt.getEnclosingElement(); @@ -2800,9 +2794,8 @@ public boolean shouldSuppressWarnings(@Nullable TreePath path, String errKey) { if (shouldSuppressWarnings(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. + + if (isElementAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { return false; } } @@ -2880,11 +2873,6 @@ public boolean shouldSuppressWarnings(@Nullable 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; } @@ -3016,33 +3004,7 @@ protected boolean messageKeyMatches( * @param elt the source code element to check, or null * @return true if the element is annotated for this checker or an upstream checker */ - private boolean isAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { - // Return false if elt is null, or if neither useConservativeDefaultsSource nor - // issueErrorsForOnlyAnnotatedForScope is set, since the @AnnotatedFor status is irrelevant - // in that case. - // TODO: Refactor SourceChecker and QualifierDefaults to use a cache for determining if an - // element is annotated for. - if (elt == null || (!useConservativeDefaultsSource && !onlyAnnotatedFor)) { - return false; - } - - AnnotatedFor anno = elt.getAnnotation(AnnotatedFor.class); - - String[] userAnnotatedFors = (anno == null ? null : anno.value()); - - if (userAnnotatedFors != null) { - List<@FullyQualifiedName String> upstreamCheckerNames = getUpstreamCheckerNames(); - - for (String userAnnotatedFor : userAnnotatedFors) { - if (CheckerMain.matchesCheckerOrSubcheckerFromList( - userAnnotatedFor, upstreamCheckerNames)) { - return true; - } - } - } - - return false; - } + protected abstract boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt); /** * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index cb28a2142a5e..ed0416b37996 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -537,6 +537,9 @@ void checkRep(String aliasName) { /** Mapping from a Tree to its TreePath. Shared between all instances. */ private final TreePathCacher treePathCache; + /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ + private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); + /** Whether to ignore type arguments from raw types. */ public final boolean ignoreRawTypeArguments; @@ -6035,6 +6038,49 @@ public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForA return false; } + /** + * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an + * upstream checker that called this one. + * + * @param elt the source code element to check, or null + * @return true if the element is annotated for this checker or an upstream checker + */ + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { + boolean elementAnnotatedForThisChecker = false; + + if (elt == null) { + throw new BugInCF( + "Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); + } + + if (elementAnnotatedFors.containsKey(elt)) { + return elementAnnotatedFors.get(elt); + } + + AnnotationMirror annotatedFor = getDeclAnnotation(elt, AnnotatedFor.class); + + if (annotatedFor != null) { + elementAnnotatedForThisChecker = doesAnnotatedForApplyToThisChecker(annotatedFor); + } + + if (!elementAnnotatedForThisChecker) { + Element parent; + if (elt.getKind() == ElementKind.PACKAGE) { + parent = ElementUtils.parentPackage((PackageElement) elt, elements); + } else { + parent = elt.getEnclosingElement(); + } + + if (parent != null && isElementAnnotatedForThisCheckerOrUpstreamChecker(parent)) { + elementAnnotatedForThisChecker = true; + } + } + + elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); + + return elementAnnotatedForThisChecker; + } + /** * Get the {@code expression} field/element of the given contract annotation. * diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 080d1cec1429..1439dd96eb16 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -14,7 +14,6 @@ import org.checkerframework.checker.interning.qual.FindDistinct; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.DefaultQualifier; import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -121,9 +120,6 @@ public class QualifierDefaults { */ private final IdentityHashMap elementDefaults = new IdentityHashMap<>(); - /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ - private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); - /** CLIMB locations whose standard default is top for a given type system. */ public static final List STANDARD_CLIMB_DEFAULTS_TOP = Collections.unmodifiableList( @@ -642,46 +638,6 @@ private void applyDefaults(Tree tree, AnnotatedTypeMirror type) { } } - private boolean isElementAnnotatedForThisChecker(Element elt) { - boolean elementAnnotatedForThisChecker = false; - - if (elt == null) { - throw new BugInCF( - "Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); - } - - if (elementAnnotatedFors.containsKey(elt)) { - return elementAnnotatedFors.get(elt); - } - - AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); - - if (annotatedFor != null) { - elementAnnotatedForThisChecker = - atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); - } - - if (!elementAnnotatedForThisChecker) { - Element parent; - if (elt.getKind() == ElementKind.PACKAGE) { - // TODO: should AnnotatedFor apply to subpackages?? - // elt.getEnclosingElement() on a package is null; therefore, - // use the dedicated method. - parent = ElementUtils.parentPackage((PackageElement) elt, elements); - } else { - parent = elt.getEnclosingElement(); - } - - if (parent != null && isElementAnnotatedForThisChecker(parent)) { - elementAnnotatedForThisChecker = true; - } - } - - elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); - - return elementAnnotatedForThisChecker; - } - /** * Returns the defaults that apply to the given Element, considering defaults from enclosing * Elements. @@ -810,7 +766,8 @@ public boolean applyConservativeDefaults(Element annotationScope) { && !isFromStubFile; if (isBytecode) { return useConservativeDefaultsBytecode - && !isElementAnnotatedForThisChecker(annotationScope); + && !atypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker( + annotationScope); } else if (isFromStubFile) { // TODO: Types in stub files not annotated for a particular checker should be // treated as unchecked bytecode. For now, all types in stub files are treated as @@ -819,7 +776,7 @@ public boolean applyConservativeDefaults(Element annotationScope) { // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. return false; } else if (useConservativeDefaultsSource) { - return !isElementAnnotatedForThisChecker(annotationScope); + return !atypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker(annotationScope); } return false; } From 187bbf3f0026e5b161d2278ce8f150aee5f91c3e Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Wed, 25 Mar 2026 18:35:24 -0400 Subject: [PATCH 02/15] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../common/basetype/BaseTypeChecker.java | 15 +++++++++------ .../framework/source/AggregateChecker.java | 7 ++++--- .../framework/type/AnnotatedTypeFactory.java | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 2737f15a418a..ca4b14f1ff59 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -323,12 +323,15 @@ public BaseTypeChecker getUltimateParentChecker() { * @return true if the element is annotated for this checker or an upstream checker */ @Override - public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - // The implementation of this method is at AnnotatedTypeFactory because postinit() is called - // in the constructor of ATFs and use the same method before the ATF is fully initialized. - // Implement it here and call this method by atf.getChecker() would fail as it will call - // getTypeFactory() in the body and would result type system error as the visitor is not - // initialized yet. + protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + // The main implementation of this method resides in AnnotatedTypeFactory. The factory's + // postInit() method is invoked from the constructors of AnnotatedTypeFactory instances, + // and during postInit() the factory may need to call + // isElementAnnotatedForThisCheckerOrUpstreamChecker. If this method were implemented + // here and invoked via atf.getChecker(), its body would call getTypeFactory(), which + // could access or create the factory before it is fully initialized and before the + // visitor is set up, leading to a type-system error. To avoid this initialization + // cycle, this checker method simply delegates to the AnnotatedTypeFactory implementation. return getTypeFactory().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt); } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index ae1dd72d1f76..177f54897731 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -55,9 +55,10 @@ protected final Set> getImmediateSubcheckerClasse } /** - * Return false, determine whether an aggregatechecker is annotated for depends on its - * subcheckers. For checkers that have upstream checker and want to handle annotatedfor in both - * this and upstream checker, see InitializationChecker#getUpstreamCheckerNames(). + * Always returns false. Whether an aggregate checker is annotated with {@code @AnnotatedFor} + * depends on its subcheckers. For checkers that have an upstream checker and want to handle + * {@code @AnnotatedFor} in both this and the upstream checker, see + * {@link org.checkerframework.checker.initialization.InitializationChecker#getUpstreamCheckerNames()}. */ @Override protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 51aea82c32f7..b9e6ee9159c6 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -6049,7 +6049,7 @@ public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Eleme if (elt == null) { throw new BugInCF( - "Call of QualifierDefaults.isElementAnnotatedForThisChecker with null"); + "Call of AnnotatedTypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker with null"); } if (elementAnnotatedFors.containsKey(elt)) { From 170c1b507b5f9f8be1747378ddc860a1a088bed0 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Wed, 25 Mar 2026 19:20:55 -0400 Subject: [PATCH 03/15] Wording --- .../common/basetype/BaseTypeChecker.java | 29 +++++++++---------- .../framework/source/AggregateChecker.java | 4 +-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index ca4b14f1ff59..d5cc840578b3 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -315,23 +315,22 @@ public BaseTypeChecker getUltimateParentChecker() { } } - /** - * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an - * upstream checker that called this one. - * - * @param elt the source code element to check, or null - * @return true if the element is annotated for this checker or an upstream checker - */ @Override protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - // The main implementation of this method resides in AnnotatedTypeFactory. The factory's - // postInit() method is invoked from the constructors of AnnotatedTypeFactory instances, - // and during postInit() the factory may need to call - // isElementAnnotatedForThisCheckerOrUpstreamChecker. If this method were implemented - // here and invoked via atf.getChecker(), its body would call getTypeFactory(), which - // could access or create the factory before it is fully initialized and before the - // visitor is set up, leading to a type-system error. To avoid this initialization - // cycle, this checker method simply delegates to the AnnotatedTypeFactory implementation. + // The implementation of this method resides in AnnotatedTypeFactory (atf). + // We want to implement it here to avoid duplication and call + // atypeFactory.getChecker().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt) + // in QualifierDefaults, but implement it here causes type-system error. + // The reason is the implementation requires an atf to call getDeclAnnotation() to get the + // AnnotatedFor annotation on the element. + // However, the method is called during the initialization of the atf when applying + // qualifier defaults. At that time, the atf and the visitor are not fully initialized, so + // calling getTypeFactory() will result in a type-system error. + // The initialization logic is as follows: + // 1. Create the checker. + // 2. Create the visitor, and the visitor's constructor initializes the atf. + // 3. During postInit() of the atf, the qualifier defaults are applied, which need to call + // isElementAnnotatedForThisCheckerOrUpstreamChecker(). return getTypeFactory().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt); } } diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index 177f54897731..579a2e76266b 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -57,8 +57,8 @@ protected final Set> getImmediateSubcheckerClasse /** * Always returns false. Whether an aggregate checker is annotated with {@code @AnnotatedFor} * depends on its subcheckers. For checkers that have an upstream checker and want to handle - * {@code @AnnotatedFor} in both this and the upstream checker, see - * {@link org.checkerframework.checker.initialization.InitializationChecker#getUpstreamCheckerNames()}. + * {@code @AnnotatedFor} in both this and the upstream checker, see {@link + * org.checkerframework.checker.initialization.InitializationChecker#getUpstreamCheckerNames()}. */ @Override protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { From f9368cfe98f1d030d2311c44e352fd89978a8b0d Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 15:54:47 -0400 Subject: [PATCH 04/15] Make comment as javadoc --- .../common/basetype/BaseTypeChecker.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index d5cc840578b3..0cf38ece0dcb 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -315,22 +315,26 @@ public BaseTypeChecker getUltimateParentChecker() { } } + /** + * The implementation of this method resides in AnnotatedTypeFactory (atf), see {@link + * AnnotatedTypeFactory#isElementAnnotatedForThisCheckerOrUpstreamChecker(Element)} We want to + * implement it here to avoid duplication and call + * atypeFactory.getChecker().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt) in + * QualifierDefaults, but implement it here causes type-system error. The reason is the + * implementation requires an atf to call {@link AnnotatedTypeFactory#getDeclAnnotation(Element, + * Class)}to get the relavent {@code @AnnotatedFor} annotation on the element. However, the + * method is called during the initialization of the atf when applying qualifier defaults. At + * that time, the atf and the visitor are not fully initialized, so calling getTypeFactory() + * will result in a type-system error. The initialization logic is as follows: 1. Create the + * checker. 2. Create the visitor, and the visitor's constructor initializes the atf. 3. During + * postInit() of the atf, the qualifier defaults are applied, which need to call + * isElementAnnotatedForThisCheckerOrUpstreamChecker(). + * + * @param elt the source code element to check, or null + * @return true if the element is annotated for this checker or an upstream checker + */ @Override protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - // The implementation of this method resides in AnnotatedTypeFactory (atf). - // We want to implement it here to avoid duplication and call - // atypeFactory.getChecker().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt) - // in QualifierDefaults, but implement it here causes type-system error. - // The reason is the implementation requires an atf to call getDeclAnnotation() to get the - // AnnotatedFor annotation on the element. - // However, the method is called during the initialization of the atf when applying - // qualifier defaults. At that time, the atf and the visitor are not fully initialized, so - // calling getTypeFactory() will result in a type-system error. - // The initialization logic is as follows: - // 1. Create the checker. - // 2. Create the visitor, and the visitor's constructor initializes the atf. - // 3. During postInit() of the atf, the qualifier defaults are applied, which need to call - // isElementAnnotatedForThisCheckerOrUpstreamChecker(). return getTypeFactory().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt); } } From f4943270dca03117e14c292291a508ac894e7e23 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 16:50:57 -0400 Subject: [PATCH 05/15] Empty commit for CI From d80926cc12ae872e318c41952dabd8f99edca40a Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 17:07:42 -0400 Subject: [PATCH 06/15] Empty commit for CI From 572defdc40b58f0b0f7d767f0f23a8597019423d Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 17:42:44 -0400 Subject: [PATCH 07/15] Empty commit for CI From 38c3aac8b6399cd319f4020037e2e0c109e22d52 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 17:57:56 -0400 Subject: [PATCH 08/15] Empty commit for CI From 3672689d115d82be9fc434c10e46c5424a8df0a9 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 19:21:27 -0400 Subject: [PATCH 09/15] Empty commit for CI From 0f9e0f8927c984ccd3a4b958296af4768ad720a9 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 19:25:52 -0400 Subject: [PATCH 10/15] Empty commit for CI From 3021e859da10f29cc154682b25d876e22c3c01c0 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 27 Mar 2026 20:08:05 -0400 Subject: [PATCH 11/15] Empty commit for CI From e91b31e5a1be1a1bd1d8a328f6dbb168089aed0a Mon Sep 17 00:00:00 2001 From: Alex Cook <43047600+thisisalexandercook@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:58:05 -0400 Subject: [PATCH 12/15] Move `@AnnotatedFor` check to `BaseTypeChecker` (#24) * refactor: move AnnotatedFor check to BaseTypeChecker * chore: spotless --- .../common/basetype/BaseTypeChecker.java | 65 +++++++++++++------ .../util/count/AnnotationStatistics.java | 2 +- .../common/util/count/JavaCodeStatistics.java | 2 +- .../common/util/debug/SignaturePrinter.java | 2 +- .../framework/source/AggregateChecker.java | 2 +- .../framework/source/SourceChecker.java | 2 +- .../framework/type/AnnotatedTypeFactory.java | 58 ++++------------- .../util/defaults/QualifierDefaults.java | 13 +++- 8 files changed, 72 insertions(+), 74 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java index 0cf38ece0dcb..771d9e01649f 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeChecker.java @@ -4,6 +4,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.signature.qual.ClassGetName; import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer; +import org.checkerframework.framework.qual.AnnotatedFor; import org.checkerframework.framework.qual.SubtypeOf; import org.checkerframework.framework.source.SourceChecker; import org.checkerframework.framework.type.AnnotatedTypeFactory; @@ -13,6 +14,7 @@ import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TypeSystemError; import org.checkerframework.javacutil.UserError; import org.plumelib.util.CollectionsPlume; @@ -22,10 +24,13 @@ import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collection; +import java.util.IdentityHashMap; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.PackageElement; /** * An abstract {@link SourceChecker} that provides a simple {@link @@ -69,6 +74,13 @@ */ public abstract class BaseTypeChecker extends SourceChecker { + /** + * A mapping from an element to whether it is in an {@code @AnnotatedFor} scope for this checker + * or an upstream checker. + */ + private final IdentityHashMap elementAnnotatedForThisCheckerOrUpstreamCache = + new IdentityHashMap<>(); + /** An array containing just {@code BaseTypeChecker.class}. */ protected static Class[] baseTypeCheckerClassArray = new Class[] {BaseTypeChecker.class}; @@ -315,26 +327,39 @@ public BaseTypeChecker getUltimateParentChecker() { } } - /** - * The implementation of this method resides in AnnotatedTypeFactory (atf), see {@link - * AnnotatedTypeFactory#isElementAnnotatedForThisCheckerOrUpstreamChecker(Element)} We want to - * implement it here to avoid duplication and call - * atypeFactory.getChecker().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt) in - * QualifierDefaults, but implement it here causes type-system error. The reason is the - * implementation requires an atf to call {@link AnnotatedTypeFactory#getDeclAnnotation(Element, - * Class)}to get the relavent {@code @AnnotatedFor} annotation on the element. However, the - * method is called during the initialization of the atf when applying qualifier defaults. At - * that time, the atf and the visitor are not fully initialized, so calling getTypeFactory() - * will result in a type-system error. The initialization logic is as follows: 1. Create the - * checker. 2. Create the visitor, and the visitor's constructor initializes the atf. 3. During - * postInit() of the atf, the qualifier defaults are applied, which need to call - * isElementAnnotatedForThisCheckerOrUpstreamChecker(). - * - * @param elt the source code element to check, or null - * @return true if the element is annotated for this checker or an upstream checker - */ @Override - protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - return getTypeFactory().isElementAnnotatedForThisCheckerOrUpstreamChecker(elt); + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { + if (elt == null) { + throw new BugInCF( + "Call of BaseTypeChecker.isElementAnnotatedForThisCheckerOrUpstreamChecker with null"); + } + + if (elementAnnotatedForThisCheckerOrUpstreamCache.containsKey(elt)) { + return elementAnnotatedForThisCheckerOrUpstreamCache.get(elt); + } + + AnnotatedTypeFactory atypeFactory = getTypeFactory(); + AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class); + boolean elementAnnotatedForThisChecker = + annotatedFor != null + && atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor); + + if (!elementAnnotatedForThisChecker) { + Element parent; + if (elt.getKind() == ElementKind.PACKAGE) { + parent = + ElementUtils.parentPackage( + (PackageElement) elt, atypeFactory.getElementUtils()); + } else { + parent = elt.getEnclosingElement(); + } + + if (parent != null && isElementAnnotatedForThisCheckerOrUpstreamChecker(parent)) { + elementAnnotatedForThisChecker = true; + } + } + + elementAnnotatedForThisCheckerOrUpstreamCache.put(elt, elementAnnotatedForThisChecker); + return elementAnnotatedForThisChecker; } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index 8696e35b31fc..c2a8fec8db08 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -330,7 +330,7 @@ public AnnotationProvider getAnnotationProvider() { } @Override - protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { throw new BugInCF("Unexpected call to determine whether this checker is annotated"); } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java index 2366021b4086..536a0ea72abe 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java @@ -207,7 +207,7 @@ public AnnotationProvider getAnnotationProvider() { } @Override - protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { throw new BugInCF("Unexpected call to determine whether this checker is annotated"); } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java index dec3e9645c51..2e907ddb553f 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java @@ -102,7 +102,7 @@ private void init(ProcessingEnvironment env, @Nullable @BinaryName String checke new SourceChecker() { @Override - protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker( + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker( Element elt) { throw new BugInCF( "Unexpected call to determine whether this checker is annotated"); diff --git a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java index 579a2e76266b..efb03916b8cc 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/AggregateChecker.java @@ -61,7 +61,7 @@ protected final Set> getImmediateSubcheckerClasse * org.checkerframework.checker.initialization.InitializationChecker#getUpstreamCheckerNames()}. */ @Override - protected boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { + public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { return false; } } 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 27e7dcbcf5b4..d6c1624d4da7 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2999,7 +2999,7 @@ protected boolean messageKeyMatches( * @param elt the source code element to check, or null * @return true if the element is annotated for this checker or an upstream checker */ - protected abstract boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt); + public abstract boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt); /** * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index e74d4d9d2586..6eccc26c0d7d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -536,9 +536,6 @@ void checkRep(String aliasName) { /** Mapping from a Tree to its TreePath. Shared between all instances. */ private final TreePathCacher treePathCache; - /** A mapping of Element → Whether or not that element is AnnotatedFor this type system. */ - private final IdentityHashMap elementAnnotatedFors = new IdentityHashMap<>(); - /** Whether to ignore type arguments from raw types. */ public final boolean ignoreRawTypeArguments; @@ -4100,6 +4097,18 @@ protected void parseAnnotationFiles() { ajavaTypes.parseAjavaFiles(); } + /** + * Returns true if this type factory is currently parsing stub files or ajava files. While this + * is true, annotation-file-backed lookup may observe partially loaded annotation-file data. + * + * @return true if stub files or ajava files are currently being parsed + */ + public boolean isParsingAnnotationFiles() { + return stubTypes.isParsing() + || ajavaTypes.isParsing() + || (currentFileAjavaTypes != null && currentFileAjavaTypes.isParsing()); + } + /** * Returns all of the declaration annotations whose name equals the passed annotation class (or * is an alias for it) including annotations: @@ -6051,49 +6060,6 @@ public boolean doesAnnotatedForApplyToThisChecker(AnnotationMirror annotatedForA return false; } - /** - * Return true if the element has an {@code @AnnotatedFor} annotation, for this checker or an - * upstream checker that called this one. - * - * @param elt the source code element to check, or null - * @return true if the element is annotated for this checker or an upstream checker - */ - public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(@Nullable Element elt) { - boolean elementAnnotatedForThisChecker = false; - - if (elt == null) { - throw new BugInCF( - "Call of AnnotatedTypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker with null"); - } - - if (elementAnnotatedFors.containsKey(elt)) { - return elementAnnotatedFors.get(elt); - } - - AnnotationMirror annotatedFor = getDeclAnnotation(elt, AnnotatedFor.class); - - if (annotatedFor != null) { - elementAnnotatedForThisChecker = doesAnnotatedForApplyToThisChecker(annotatedFor); - } - - if (!elementAnnotatedForThisChecker) { - Element parent; - if (elt.getKind() == ElementKind.PACKAGE) { - parent = ElementUtils.parentPackage((PackageElement) elt, elements); - } else { - parent = elt.getEnclosingElement(); - } - - if (parent != null && isElementAnnotatedForThisCheckerOrUpstreamChecker(parent)) { - elementAnnotatedForThisChecker = true; - } - } - - elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker); - - return elementAnnotatedForThisChecker; - } - /** * Get the {@code expression} field/element of the given contract annotation. * diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index ff8855d2eb2f..69e70cd80ec5 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -748,6 +748,10 @@ public boolean applyConservativeDefaults(Element annotationScope) { return false; } + if (atypeFactory.isParsingAnnotationFiles()) { + return false; + } + // TODO: I would expect this: // atypeFactory.isFromByteCode(annotationScope)) { // to work instead of the @@ -761,8 +765,9 @@ public boolean applyConservativeDefaults(Element annotationScope) { && !isFromStubFile; if (isBytecode) { return useConservativeDefaultsBytecode - && !atypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker( - annotationScope); + && !atypeFactory + .getChecker() + .isElementAnnotatedForThisCheckerOrUpstreamChecker(annotationScope); } else if (isFromStubFile) { // TODO: Types in stub files not annotated for a particular checker should be // treated as unchecked bytecode. For now, all types in stub files are treated as @@ -771,7 +776,9 @@ public boolean applyConservativeDefaults(Element annotationScope) { // be treated like unchecked code except for methods in the scope of an @AnnotatedFor. return false; } else if (useConservativeDefaultsSource) { - return !atypeFactory.isElementAnnotatedForThisCheckerOrUpstreamChecker(annotationScope); + return !atypeFactory + .getChecker() + .isElementAnnotatedForThisCheckerOrUpstreamChecker(annotationScope); } return false; } From b9009f77cab7f11bdf112b6d73a50b196ef02cb7 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 13:13:32 -0400 Subject: [PATCH 13/15] Refactor isElementAnnotatedForThisCheckerOrUpstreamChecker methods to return false instead of throwing exceptions, improving stability during annotation processing. --- .../common/util/count/AnnotationStatistics.java | 5 +++-- .../common/util/count/JavaCodeStatistics.java | 5 +++-- .../common/util/debug/SignaturePrinter.java | 7 ++++--- .../framework/source/SourceChecker.java | 13 ++++++++----- .../framework/util/defaults/QualifierDefaults.java | 12 ++++++++++++ 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java index c2a8fec8db08..cf7fe120969e 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/AnnotationStatistics.java @@ -23,7 +23,6 @@ import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.framework.source.SupportedOptions; import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.BugInCF; import java.util.HashMap; import java.util.Map; @@ -331,6 +330,8 @@ public AnnotationProvider getAnnotationProvider() { @Override public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - throw new BugInCF("Unexpected call to determine whether this checker is annotated"); + // This checker only counts annotations; it does not perform any type-checking. Returning + // false keeps callers (e.g., QualifierDefaults) on safe defaults rather than crashing. + return false; } } diff --git a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java index 536a0ea72abe..069d84a50b52 100644 --- a/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java +++ b/framework/src/main/java/org/checkerframework/common/util/count/JavaCodeStatistics.java @@ -17,7 +17,6 @@ import org.checkerframework.framework.source.SourceVisitor; import org.checkerframework.javacutil.AnnotationProvider; import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; import java.util.List; @@ -208,6 +207,8 @@ public AnnotationProvider getAnnotationProvider() { @Override public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt) { - throw new BugInCF("Unexpected call to determine whether this checker is annotated"); + // This checker only counts code; it does not perform any type-checking. Returning false + // keeps callers (e.g., QualifierDefaults) on safe defaults rather than crashing. + return false; } } diff --git a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java index 2e907ddb553f..8d3db881edc8 100644 --- a/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java +++ b/framework/src/main/java/org/checkerframework/common/util/debug/SignaturePrinter.java @@ -15,7 +15,6 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; import org.checkerframework.javacutil.AbstractTypeProcessor; import org.checkerframework.javacutil.AnnotationProvider; -import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.UserError; import org.plumelib.reflection.Signatures; @@ -104,8 +103,10 @@ private void init(ProcessingEnvironment env, @Nullable @BinaryName String checke @Override public boolean isElementAnnotatedForThisCheckerOrUpstreamChecker( Element elt) { - throw new BugInCF( - "Unexpected call to determine whether this checker is annotated"); + // SignaturePrinter only prints signatures; it does not perform any + // type-checking. Returning false keeps callers (e.g., + // QualifierDefaults) on safe defaults rather than crashing. + return false; } @Override 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 d6c1624d4da7..72c78e2ade06 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2789,10 +2789,9 @@ public boolean shouldSuppressWarnings(TreePath path, String errKey) { if (shouldSuppressWarnings(packageElement, errKey)) { return true; } - - if (isElementAnnotatedForThisCheckerOrUpstreamChecker(packageElement)) { - return false; - } + // No need to call isElementAnnotatedForThisCheckerOrUpstreamChecker on the + // package: the call above on the class element already walks enclosing + // elements (including the package) recursively. } } else { throw new BugInCF("Unexpected declaration kind: " + decl.getKind() + " " + decl); @@ -2868,6 +2867,9 @@ public boolean shouldSuppressWarnings(Element elt, String errKey) { return true; } } + if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + return false; + } } return false; } @@ -2999,7 +3001,8 @@ protected boolean messageKeyMatches( * @param elt the source code element to check, or null * @return true if the element is annotated for this checker or an upstream checker */ - public abstract boolean isElementAnnotatedForThisCheckerOrUpstreamChecker(Element elt); + public abstract boolean isElementAnnotatedForThisCheckerOrUpstreamChecker( + @Nullable Element elt); /** * Returns a modifiable set of lower-case strings that are prefixes for SuppressWarnings diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 69e70cd80ec5..24b10d5deeaa 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -748,6 +748,18 @@ public boolean applyConservativeDefaults(Element annotationScope) { return false; } + // Skip the conservative-defaults check while annotation files are being parsed, to avoid + // an initialization cycle. During GenericAnnotatedTypeFactory.postInit(), + // parseAnnotationFiles() + // runs the stub/ajava parser, which asks the type factory for defaulted types. That reaches + // here and would call checker.isElementAnnotatedForThisCheckerOrUpstreamChecker(...), which + // routes through BaseTypeChecker.getTypeFactory() — but the visitor (and thus the type + // factory) is not yet installed on the checker, causing an NPE. Eagerly-parsed annotation + // files (checker @StubFiles, command-line stubs, ajava files, annotated-JDK + // package-info.java) + // are the risky cases; most JDK class stubs are only parsed lazily after init completes. + // Stub-file elements are still treated as checked code by the isFromStubFile branch below + // once parsing has finished. if (atypeFactory.isParsingAnnotationFiles()) { return false; } From 4f5e523c1e9a48a7a278c97edaca87fb0809e69e Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 13:21:05 -0400 Subject: [PATCH 14/15] Replace method isAnnotatedForThisCheckerOrUpstreamChecker to isElementAnnotatedForThisCheckerOrUpstreamChecker --- .../org/checkerframework/framework/source/SourceChecker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 72c78e2ade06..c7ee9781ee2c 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2867,7 +2867,7 @@ public boolean shouldSuppressWarnings(Element elt, String errKey) { return true; } } - if (isAnnotatedForThisCheckerOrUpstreamChecker(elt)) { + if (isElementAnnotatedForThisCheckerOrUpstreamChecker(elt)) { return false; } } From c7e0c73eb8c6914cabb96c6115134b6ceb9dd3e8 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 4 May 2026 22:32:35 -0400 Subject: [PATCH 15/15] Undo revert --- .../org/checkerframework/framework/source/SourceChecker.java | 3 --- 1 file changed, 3 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 c7ee9781ee2c..b48391ffd6a9 100644 --- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java +++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java @@ -2867,9 +2867,6 @@ public boolean shouldSuppressWarnings(Element elt, String errKey) { return true; } } - if (isElementAnnotatedForThisCheckerOrUpstreamChecker(elt)) { - return false; - } } return false; }