From fa79a9f4409d78f19bbbe137758b0e18e8470d68 Mon Sep 17 00:00:00 2001 From: Ankit Date: Sun, 3 May 2026 21:02:11 -0400 Subject: [PATCH 1/2] Add javac equivlaent class for AnnotationEqualityVisitor --- .../ajava/JavacAnnotationEqualityVisitor.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java new file mode 100644 index 00000000000..86450e50d60 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java @@ -0,0 +1,144 @@ +package org.checkerframework.framework.ajava; + +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ModifiersTree; +import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.PackageTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeParameterTree; +import java.util.Collections; +import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Given two javac ASTs representing the same Java file that may differ in annotations, tests if + * they have the same annotations. + * + *

To use this class, call {@link #scan} with the roots of the two ASTs. Then, check {@link + * #getAnnotationsMatch}. + * + *

This is the javac-based replacement for {@link AnnotationEqualityVisitor}. + */ +public class JavacAnnotationEqualityVisitor extends DoubleJavacVisitor { + + /** True if no node with mismatched annotations has been seen. */ + private boolean annotationsMatch; + + /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ + private @MonotonicNonNull Tree mismatchedNode1; + + /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ + private @MonotonicNonNull Tree mismatchedNode2; + + /** Constructs a {@code JavacAnnotationEqualityVisitor}. */ + public JavacAnnotationEqualityVisitor() { + annotationsMatch = true; + mismatchedNode1 = null; + mismatchedNode2 = null; + } + + /** + * Returns true if all visited pairs of nodes had matching annotations. + * + * @return true if all visited pairs of nodes had matching annotations + */ + public boolean getAnnotationsMatch() { + return annotationsMatch; + } + + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the first AST + * where annotations differed, or null otherwise. + * + * @return the node from the first AST with differing annotations, or null + */ + public @Nullable Tree getMismatchedNode1() { + return mismatchedNode1; + } + + /** + * If a visited pair of nodes has had mismatched annotations, returns the node from the second AST + * where annotations differed, or null otherwise. + * + * @return the node from the second AST with differing annotations, or null + */ + public @Nullable Tree getMismatchedNode2() { + return mismatchedNode2; + } + + /** + * Returns the annotation trees on the given tree, or an empty list if the tree type does not + * carry annotations. + * + *

In javac's AST, annotations appear on {@link ModifiersTree} (declaration annotations), + * {@link AnnotatedTypeTree} (type-use annotations), {@link TypeParameterTree}, {@link + * PackageTree}, {@link ModuleTree}, and {@link NewArrayTree}. + * + * @param tree a tree + * @return the annotations on the tree + */ + public static List getAnnotations(Tree tree) { + if (tree instanceof ModifiersTree t) { + return t.getAnnotations(); + } + if (tree instanceof AnnotatedTypeTree t) { + return t.getAnnotations(); + } + if (tree instanceof TypeParameterTree t) { + return t.getAnnotations(); + } + if (tree instanceof PackageTree t) { + return t.getAnnotations(); + } + if (tree instanceof ModuleTree t) { + return t.getAnnotations(); + } + if (tree instanceof NewArrayTree t) { + return t.getAnnotations(); + } + return Collections.emptyList(); + } + + /** + * Compares two lists of annotation trees by their string representations. Javac trees do not + * implement structural {@code equals}, so string comparison is used instead. Javac tree {@code + * toString} does not include comments, so no comment-stripping is needed. + * + * @param annos1 the first list of annotations + * @param annos2 the second list of annotations + * @return true if the two lists represent the same annotations + */ + private static boolean annotationsEqual( + List annos1, List annos2) { + if (annos1.size() != annos2.size()) { + return false; + } + for (int i = 0; i < annos1.size(); i++) { + if (!annos1.get(i).toString().equals(annos2.get(i).toString())) { + return false; + } + } + return true; + } + + @Override + protected Void defaultAction(Tree tree1, Tree tree2) { + if (!annotationsMatch) { + return null; + } + + List annos1 = getAnnotations(tree1); + List annos2 = getAnnotations(tree2); + + if (!annotationsEqual(annos1, annos2)) { + annotationsMatch = false; + mismatchedNode1 = tree1; + mismatchedNode2 = tree2; + } + + return null; + } +} From 8a0f531d3e3c04c3f9034704982d68eee5cf6a7a Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Wed, 6 May 2026 07:25:53 -0700 Subject: [PATCH 2/2] Code review edits --- .../ajava/JavacAnnotationEqualityVisitor.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java b/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java index 86450e50d60..38b17b94b52 100644 --- a/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java +++ b/framework/src/main/java/org/checkerframework/framework/ajava/JavacAnnotationEqualityVisitor.java @@ -19,25 +19,21 @@ * *

To use this class, call {@link #scan} with the roots of the two ASTs. Then, check {@link * #getAnnotationsMatch}. - * - *

This is the javac-based replacement for {@link AnnotationEqualityVisitor}. */ public class JavacAnnotationEqualityVisitor extends DoubleJavacVisitor { /** True if no node with mismatched annotations has been seen. */ - private boolean annotationsMatch; + private boolean annotationsMatch = true; /** If a node with mismatched annotations has been seen, stores the node from the first AST. */ - private @MonotonicNonNull Tree mismatchedNode1; + private @MonotonicNonNull Tree mismatchedNode1 = null; /** If a node with mismatched annotations has been seen, stores the node from the second AST. */ - private @MonotonicNonNull Tree mismatchedNode2; + private @MonotonicNonNull Tree mismatchedNode2 = null; /** Constructs a {@code JavacAnnotationEqualityVisitor}. */ public JavacAnnotationEqualityVisitor() { annotationsMatch = true; - mismatchedNode1 = null; - mismatchedNode2 = null; } /**