diff --git a/.gitignore b/.gitignore index c72330759..3bf77e02f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ build # macOS specific file .DS_Store + +# VSCode files +bin/ \ No newline at end of file diff --git a/src/main/java/org/checkerframework/specimin/AnnotationParameterTypesVisitor.java b/src/main/java/org/checkerframework/specimin/AnnotationParameterTypesVisitor.java new file mode 100644 index 000000000..d31633f3f --- /dev/null +++ b/src/main/java/org/checkerframework/specimin/AnnotationParameterTypesVisitor.java @@ -0,0 +1,360 @@ +package org.checkerframework.specimin; + +import com.github.javaparser.ast.ImportDeclaration; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.AnnotationDeclaration; +import com.github.javaparser.ast.body.AnnotationMemberDeclaration; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.expr.AnnotationExpr; +import com.github.javaparser.ast.expr.ArrayInitializerExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.MarkerAnnotationExpr; +import com.github.javaparser.ast.expr.MemberValuePair; +import com.github.javaparser.ast.expr.NormalAnnotationExpr; +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; +import com.github.javaparser.ast.visitor.Visitable; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.types.ResolvedType; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionAnnotationDeclaration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Preserve annotations and their parameter types for used classes. This will only keep annotations + * if the corresponding class, method, or field declaration is marked to be preserved. If an + * annotation (or its parameters) is not resolvable, it will be removed. + */ +public class AnnotationParameterTypesVisitor extends SpeciminStateVisitor { + /** + * Constructs a new SolveMethodOverridingVisitor with the provided sets of target methods, used + * members, and used classes. + * + * @param previousVisitor the last visitor to run before this one + */ + public AnnotationParameterTypesVisitor(SpeciminStateVisitor previousVisitor) { + super(previousVisitor); + } + + /** Set containing the signatures of classes used by annotations. */ + private Set classesToAdd = new HashSet<>(); + + /** Map containing the signatures of static imports. */ + Map staticImports = new HashMap<>(); + + /** + * Get the set containing the signatures of classes used by annotations. + * + * @return The set containing the signatures of classes used by annotations. + */ + public Set getClassesToAdd() { + return classesToAdd; + } + + @Override + public Node visit(ImportDeclaration decl, Void p) { + if (decl.isStatic()) { + String fullName = decl.getNameAsString(); + String memberName = fullName.substring(fullName.lastIndexOf(".") + 1); + staticImports.put(memberName, fullName); + } + return decl; + } + + @Override + public Visitable visit(AnnotationMemberDeclaration decl, Void p) { + // Ensure that enums/fields that are used by default are included + // Also, preserve method type since a definition with a default value may be + // added, but that value type is never explored by the visit(AnnotationExpr) methods + // For example, when the type is an enum (Foo), the definition may set a default value to + // Foo.VALUE, but Foo.VALUE may never be referenced in an @Annotation() usage (instead, + // other Foo values may) be used, so Foo.VALUE would be removed by PrunerVisitor and result + // in compile errors. + if (usedTypeElements.contains(JavaParserUtil.getEnclosingClassName(decl))) { + // Class<> from jar files may contain other classes + if (decl.getType().toString().startsWith("Class<")) { + // Replace with Class to prevent compile-time errors + String type = "Class"; + if (decl.getType().isArrayType()) { + type += "[]"; + } + decl.setType(type); + } else { + try { + ResolvedType resolved = decl.getType().resolve(); + if (resolved.isArray()) { + resolved = resolved.asArrayType().getComponentType(); + } + if (resolved.isReferenceType()) { + usedTypeElements.add(resolved.asReferenceType().getQualifiedName()); + } + } catch (UnsolvedSymbolException ex) { + // TODO: retrigger synthetic type generation + return super.visit(decl, p); + } + } + Optional defaultValue = decl.getDefaultValue(); + if (defaultValue.isPresent()) { + Set usedClassByCurrentAnnotation = new HashSet<>(); + Set usedMembersByCurrentAnnotation = new HashSet<>(); + boolean resolvable = + handleAnnotationValue( + defaultValue.get(), usedClassByCurrentAnnotation, usedMembersByCurrentAnnotation); + + if (resolvable) { + classesToAdd.addAll(usedClassByCurrentAnnotation); + usedMembers.addAll(usedMembersByCurrentAnnotation); + } + } + } + return super.visit(decl, p); + } + + @Override + public Visitable visit(MarkerAnnotationExpr anno, Void p) { + Node parent = JavaParserUtil.findClosestParentMemberOrClassLike(anno); + + if (isTargetOrUsed(parent)) { + handleAnnotation(anno); + } + return super.visit(anno, p); + } + + @Override + public Visitable visit(SingleMemberAnnotationExpr anno, Void p) { + Node parent = JavaParserUtil.findClosestParentMemberOrClassLike(anno); + + if (isTargetOrUsed(parent)) { + handleAnnotation(anno); + } + return super.visit(anno, p); + } + + @Override + public Visitable visit(NormalAnnotationExpr anno, Void p) { + Node parent = JavaParserUtil.findClosestParentMemberOrClassLike(anno); + + if (isTargetOrUsed(parent)) { + handleAnnotation(anno); + } + return super.visit(anno, p); + } + + /** + * Determines if the given Node is a target/used method or class. + * + * @param node The node to check + */ + private boolean isTargetOrUsed(Node node) { + // TODO: create a visitor superclass that contains this method and other common fields + String qualifiedName; + boolean isClass = false; + if (node instanceof ClassOrInterfaceDeclaration) { + Optional qualifiedNameOptional = + ((ClassOrInterfaceDeclaration) node).getFullyQualifiedName(); + if (qualifiedNameOptional.isEmpty()) { + return false; + } + qualifiedName = qualifiedNameOptional.get(); + isClass = true; + } else if (node instanceof EnumDeclaration) { + Optional qualifiedNameOptional = ((EnumDeclaration) node).getFullyQualifiedName(); + if (qualifiedNameOptional.isEmpty()) { + return false; + } + qualifiedName = qualifiedNameOptional.get(); + isClass = true; + } else if (node instanceof AnnotationDeclaration) { + Optional qualifiedNameOptional = + ((AnnotationDeclaration) node).getFullyQualifiedName(); + if (qualifiedNameOptional.isEmpty()) { + return false; + } + qualifiedName = qualifiedNameOptional.get(); + isClass = true; + } else if (node instanceof ConstructorDeclaration) { + try { + qualifiedName = ((ConstructorDeclaration) node).resolve().getQualifiedSignature(); + } catch (UnsolvedSymbolException | UnsupportedOperationException ex) { + // UnsupportedOperationException: type is a type variable + // See TargetMethodFinderVisitor.visit(MethodDeclaration, Void) for more details + return false; + } catch (RuntimeException e) { + // The current class is employed by the target methods, although not all of its members are + // utilized. It's not surprising for unused members to remain unresolved. + // If this constructor is from the parent of the current class, and it is not resolved, we + // will get a RuntimeException, otherwise just a UnsolvedSymbolException. + // Copied from PrunerVisitor.visit(ConstructorDeclaration, Void) + return false; + } + } else if (node instanceof MethodDeclaration) { + try { + qualifiedName = ((MethodDeclaration) node).resolve().getQualifiedSignature(); + } catch (UnsolvedSymbolException | UnsupportedOperationException ex) { + // UnsupportedOperationException: type is a type variable + // See TargetMethodFinderVisitor.visit(MethodDeclaration, Void) for more details + return false; + } + } else if (node instanceof FieldDeclaration) { + try { + FieldDeclaration decl = (FieldDeclaration) node; + for (VariableDeclarator var : decl.getVariables()) { + qualifiedName = JavaParserUtil.getEnclosingClassName(decl) + "#" + var.getNameAsString(); + if (usedMembers.contains(qualifiedName) || targetFields.contains(qualifiedName)) { + return true; + } + } + } catch (UnsolvedSymbolException ex) { + return false; + } + return false; + } else { + return false; + } + + if (isClass) { + return usedTypeElements.contains(qualifiedName); + } else { + // fields should already be handled at this point + return usedMembers.contains(qualifiedName) || targetMethods.contains(qualifiedName); + } + } + + /** + * Helper method to add an annotation to the usedClass set, including the types used in annotation + * parameters. + * + * @param anno The annotation to process + */ + private void handleAnnotation(AnnotationExpr anno) { + Set usedClassByCurrentAnnotation = new HashSet<>(); + Set usedMembersByCurrentAnnotation = new HashSet<>(); + boolean resolvable = true; + try { + String qualifiedName = anno.resolve().getQualifiedName(); + if (anno.resolve() instanceof ReflectionAnnotationDeclaration + && !qualifiedName.startsWith("java.lang")) { + // This usually means that JavaParser has resolved this through the import, but there + // is no file/CompilationUnit behind it, so we should discard it to prevent compile errors + anno.remove(); + return; + } + } catch (UnsolvedSymbolException ex) { + anno.remove(); + return; + } + + if (anno.isSingleMemberAnnotationExpr()) { + Expression value = anno.asSingleMemberAnnotationExpr().getMemberValue(); + resolvable = + handleAnnotationValue( + value, usedClassByCurrentAnnotation, usedMembersByCurrentAnnotation); + } else if (anno.isNormalAnnotationExpr()) { + for (MemberValuePair pair : anno.asNormalAnnotationExpr().getPairs()) { + Expression value = pair.getValue(); + resolvable = + handleAnnotationValue( + value, usedClassByCurrentAnnotation, usedMembersByCurrentAnnotation); + if (!resolvable) { + break; + } + } + } + + // Only add annotation to the usedClass set if all parameters are resolvable + if (resolvable) { + usedClassByCurrentAnnotation.add(anno.resolve().getQualifiedName()); + classesToAdd.addAll(usedClassByCurrentAnnotation); + usedMembers.addAll(usedMembersByCurrentAnnotation); + } else { + // Remove unsolvable annotations; these parameter types are unsolvable since + // the UnsolvedSymbolVisitor did not create synthetic types for annotations + // included later on + anno.remove(); + } + } + + /** + * Handles annotation parameter value types, adding all used types to the usedByCurrentAnnotation + * set. This method can handle array types as well as annotations referenced in the parameters. If + * the type is a primitive or String, there is no effect. + * + * @return true if value is resolvable, false if not + */ + private boolean handleAnnotationValue( + Expression value, + Set usedClassByCurrentAnnotation, + Set usedMembersByCurrentAnnotation) { + if (value.isArrayInitializerExpr()) { + ArrayInitializerExpr array = value.asArrayInitializerExpr(); + NodeList values = array.getValues(); + + if (values.isEmpty()) { + return true; + } + for (Expression val : values) { + handleAnnotationValue(val, usedClassByCurrentAnnotation, usedMembersByCurrentAnnotation); + } + return true; + } else if (value.isClassExpr()) { + try { + ResolvedType resolved = value.asClassExpr().getType().resolve(); + + if (resolved.isReferenceType()) { + usedClassByCurrentAnnotation.add(resolved.asReferenceType().getQualifiedName()); + } + } catch (UnsolvedSymbolException ex) { + // TODO: retrigger synthetic type generation + return false; + } + return true; + } else if (value.isFieldAccessExpr()) { + try { + ResolvedType resolved = value.asFieldAccessExpr().calculateResolvedType(); + + if (resolved.isReferenceType()) { + String parentName = resolved.asReferenceType().getQualifiedName(); + usedClassByCurrentAnnotation.add(parentName); + String memberName = value.asFieldAccessExpr().getNameAsString(); + // member here could be an enum or a field + usedMembersByCurrentAnnotation.add(parentName + "#" + memberName); + usedMembersByCurrentAnnotation.add(parentName + "." + memberName); + } + } catch (UnsolvedSymbolException ex) { + // TODO: retrigger synthetic type generation + return false; + } + return true; + } else if (value.isNameExpr()) { // variable of some sort + try { + ResolvedType resolved = value.asNameExpr().calculateResolvedType(); + + if (resolved.isReferenceType()) { + usedClassByCurrentAnnotation.add(resolved.asReferenceType().getQualifiedName()); + String fullStaticName = staticImports.get(value.asNameExpr().getNameAsString()); + if (fullStaticName != null) { + String parentName = fullStaticName.substring(0, fullStaticName.lastIndexOf(".")); + String memberName = fullStaticName.substring(fullStaticName.lastIndexOf(".") + 1); + // static import here could be an enum or a field + usedClassByCurrentAnnotation.add(parentName); + usedMembersByCurrentAnnotation.add(parentName + "#" + memberName); + usedMembersByCurrentAnnotation.add(fullStaticName); + } + } + } catch (UnsolvedSymbolException ex) { + // TODO: retrigger synthetic type generation + return false; + } + return true; + } + return true; + } +} diff --git a/src/main/java/org/checkerframework/specimin/JavaParserUtil.java b/src/main/java/org/checkerframework/specimin/JavaParserUtil.java index 76da0831c..26e45133b 100644 --- a/src/main/java/org/checkerframework/specimin/JavaParserUtil.java +++ b/src/main/java/org/checkerframework/specimin/JavaParserUtil.java @@ -3,13 +3,19 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.body.AnnotationDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.ConstructorDeclaration; import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.TypeDeclaration; +import com.github.javaparser.ast.expr.ArrayInitializerExpr; +import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.types.ResolvedReferenceType; -import org.checkerframework.checker.signature.qual.FullyQualifiedName; - +import com.github.javaparser.resolution.types.ResolvedType; import java.util.Optional; +import org.checkerframework.checker.signature.qual.FullyQualifiedName; /** * A class containing useful static functions using JavaParser. @@ -75,69 +81,145 @@ public static ResolvedReferenceType classOrInterfaceTypeToResolvedReferenceType( return type.resolve().asReferenceType(); } - /** - * Searches the ancestors of the given node until it finds a class or interface node, and then - * returns the fully-qualified name of that class or interface. - * - *

This method will fail if it is called on a node that is not contained in a class or - * interface. - * - * @param node a node contained in a class or interface - * @return the fully-qualified name of the inner-most containing class or interface - */ - @SuppressWarnings("signature") // result is a fully-qualified name or else this throws - public static @FullyQualifiedName String getEnclosingClassName(Node node) { - Node parent = getEnclosingClassLike(node); - - if (parent instanceof ClassOrInterfaceDeclaration) { - return ((ClassOrInterfaceDeclaration) parent).getFullyQualifiedName().orElseThrow(); + /** + * Returns the corresponding type name for an Expression within an annotation. + * + * @param value The value to evaluate the type of + * @return The corresponding type name for the value: constrained to a primitive type, String, + * Class<?>, an enum, an annotation, or an array of any of those types, as per + * annotation parameter requirements. + */ + public static String getValueTypeFromAnnotationExpression(Expression value) { + if (value.isBooleanLiteralExpr()) { + return "boolean"; + } else if (value.isStringLiteralExpr()) { + return "String"; + } else if (value.isIntegerLiteralExpr()) { + return "int"; + } else if (value.isLongLiteralExpr()) { + return "long"; + } else if (value.isDoubleLiteralExpr()) { + return "double"; + } else if (value.isCharLiteralExpr()) { + return "char"; + } else if (value.isArrayInitializerExpr()) { + ArrayInitializerExpr array = value.asArrayInitializerExpr(); + if (!array.getValues().isEmpty()) { + Expression firstElement = array.getValues().get(0); + return getValueTypeFromAnnotationExpression(firstElement) + "[]"; } + // Handle empty arrays (i.e. @Anno({})); we have no way of telling + // what it actually is + return "String[]"; + } else if (value.isAnnotationExpr()) { + return value.asAnnotationExpr().getNameAsString(); + } else if (value.isFieldAccessExpr()) { + // Enums are FieldAccessExprs (Enum.SOMETHING) + return value.asFieldAccessExpr().getScope().toString(); + } else if (value.isClassExpr()) { + // Handle all classes + return "Class"; + } else if (value.isNameExpr()) { + // Constant/variable + try { + ResolvedType resolvedType = value.asNameExpr().calculateResolvedType(); - if (parent instanceof EnumDeclaration) { - return ((EnumDeclaration) parent).getFullyQualifiedName().orElseThrow(); + if (resolvedType.isPrimitive()) { + return resolvedType.asPrimitive().describe(); + } else if (resolvedType.isReferenceType()) { + return resolvedType.asReferenceType().getQualifiedName(); + } else { + return resolvedType.describe(); + } + } catch (UnsolvedSymbolException ex) { + return value.toString(); } + } + return value.toString(); + } - if (parent instanceof AnnotationDeclaration) { - return ((AnnotationDeclaration) parent).getFullyQualifiedName().orElseThrow(); - } + /** + * Searches the ancestors of the given node until it finds a class or interface node, and then + * returns the fully-qualified name of that class or interface. + * + *

This method will fail if it is called on a node that is not contained in a class or + * interface. + * + * @param node a node contained in a class or interface + * @return the fully-qualified name of the inner-most containing class or interface + */ + @SuppressWarnings("signature") // result is a fully-qualified name or else this throws + public static @FullyQualifiedName String getEnclosingClassName(Node node) { + Node parent = getEnclosingClassLike(node); - throw new RuntimeException("unexpected kind of node: " + parent.getClass()); + if (parent instanceof ClassOrInterfaceDeclaration) { + return ((ClassOrInterfaceDeclaration) parent).getFullyQualifiedName().orElseThrow(); } - /** - * Returns the innermost enclosing class-like element for the given node. A class-like element is - * a class, interface, or enum (i.e., something that would be a {@link - * javax.lang.model.element.TypeElement} in javac's internal model). This method will throw if no - * such element exists. - * - * @param node a node that is contained in a class-like structure - * @return the nearest enclosing class-like node - */ - public static Node getEnclosingClassLike(Node node) { - Node parent = node.getParentNode().orElseThrow(); - while (!(parent instanceof ClassOrInterfaceDeclaration - || parent instanceof EnumDeclaration - || parent instanceof AnnotationDeclaration)) { - parent = parent.getParentNode().orElseThrow(); - } - return parent; + if (parent instanceof EnumDeclaration) { + return ((EnumDeclaration) parent).getFullyQualifiedName().orElseThrow(); } - /** - * Returns true iff the innermost enclosing class/interface is an enum. - * - * @param node any node - * @return true if the enclosing class is an enum, false otherwise - */ - public static boolean isInEnum(Node node) { - Optional parent = node.getParentNode(); - while (parent.isPresent()) { - Node actualParent = parent.get(); - if (actualParent instanceof EnumDeclaration) { - return true; - } - parent = actualParent.getParentNode(); + if (parent instanceof AnnotationDeclaration) { + return ((AnnotationDeclaration) parent).getFullyQualifiedName().orElseThrow(); + } + + throw new RuntimeException("unexpected kind of node: " + parent.getClass()); + } + + /** + * Returns the innermost enclosing class-like element for the given node. A class-like element is + * a class, interface, or enum (i.e., something that would be a {@link + * javax.lang.model.element.TypeElement} in javac's internal model). This method will throw if no + * such element exists. + * + * @param node a node that is contained in a class-like structure + * @return the nearest enclosing class-like node + */ + public static Node getEnclosingClassLike(Node node) { + Node parent = node.getParentNode().orElseThrow(); + while (!(parent instanceof ClassOrInterfaceDeclaration + || parent instanceof EnumDeclaration + || parent instanceof AnnotationDeclaration)) { + parent = parent.getParentNode().orElseThrow(); + } + return parent; + } + + /** + * Returns true iff the innermost enclosing class/interface is an enum. + * + * @param node any node + * @return true if the enclosing class is an enum, false otherwise + */ + public static boolean isInEnum(Node node) { + Optional parent = node.getParentNode(); + while (parent.isPresent()) { + Node actualParent = parent.get(); + if (actualParent instanceof EnumDeclaration) { + return true; } - return false; + parent = actualParent.getParentNode(); } + return false; + } + + /** + * Finds the closest method, field, or class-like declaration (enums, annos) + * + * @param node The node to find the parent for + * @return the Node of the closest member or class declaration + */ + public static Node findClosestParentMemberOrClassLike(Node node) { + Node parent = node.getParentNode().orElseThrow(); + while (!(parent instanceof ClassOrInterfaceDeclaration + || parent instanceof EnumDeclaration + || parent instanceof AnnotationDeclaration + || parent instanceof ConstructorDeclaration + || parent instanceof MethodDeclaration + || parent instanceof FieldDeclaration)) { + parent = parent.getParentNode().orElseThrow(); + } + return parent; + } } diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 142803336..238c9b188 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -342,11 +342,6 @@ private static void performMinimizationImpl( } } - UnsolvedAnnotationRemoverVisitor annoRemover = new UnsolvedAnnotationRemoverVisitor(jarPaths); - for (CompilationUnit cu : parsedTargetFiles.values()) { - cu.accept(annoRemover, null); - } - EnumVisitor enumVisitor = new EnumVisitor(targetMethodNames); for (CompilationUnit cu : parsedTargetFiles.values()) { cu.accept(enumVisitor, null); @@ -415,8 +410,6 @@ private static void performMinimizationImpl( for (String targetFile : inheritancePreserve.getAddedClasses()) { String directoryOfFile = targetFile.replace(".", "/") + ".java"; File thisFile = new File(root + directoryOfFile); - // classes from JDK are automatically on the classpath, so UnsolvedSymbolVisitor will not - // create synthetic files for them if (thisFile.exists()) { try { parsedTargetFiles.put(directoryOfFile, parseJavaFile(root, directoryOfFile)); @@ -434,13 +427,6 @@ private static void performMinimizationImpl( Set updatedUsedClass = solveMethodOverridingVisitor.getUsedTypeElements(); updatedUsedClass.addAll(totalSetOfAddedInheritedClasses); - // remove the unsolved annotations in the newly added files. - for (CompilationUnit cu : parsedTargetFiles.values()) { - cu.accept(annoRemover, null); - } - - updatedUsedClass.addAll(annoRemover.getSolvedAnnotationFullName()); - MustImplementMethodsVisitor mustImplementMethodsVisitor = new MustImplementMethodsVisitor(solveMethodOverridingVisitor); @@ -448,6 +434,16 @@ private static void performMinimizationImpl( cu.accept(mustImplementMethodsVisitor, null); } + // This is safe to run after MustImplementMethodsVisitor because + // annotations do not inherit + processAnnotationTypes(mustImplementMethodsVisitor, root, parsedTargetFiles); + + // Remove the unsolved annotations (and @Override) in all files. + UnsolvedAnnotationRemoverVisitor annoRemover = new UnsolvedAnnotationRemoverVisitor(jarPaths); + for (CompilationUnit cu : parsedTargetFiles.values()) { + cu.accept(annoRemover, null); + } + PrunerVisitor methodPruner = new PrunerVisitor( mustImplementMethodsVisitor, @@ -508,6 +504,68 @@ private static void performMinimizationImpl( createdClass.addAll(getPathsFromJarPaths(root, jarPaths)); } + /** + * Fully solve all annotations by processing all annotations, annotation parameters, and their + * types. This method also removes any annotations which are not fully solvable and includes all + * necessary files in Specimin's output. + * + * @param last The last SpeciminStateVisitor to run + * @param root The root directory + * @param parsedTargetFiles A map of file names to parsed CompilationUnits + */ + private static SpeciminStateVisitor processAnnotationTypes( + SpeciminStateVisitor last, String root, Map parsedTargetFiles) + throws IOException { + AnnotationParameterTypesVisitor annotationParameterTypesVisitor = + new AnnotationParameterTypesVisitor(last); + + Set relatedClass = new HashSet<>(parsedTargetFiles.keySet()); + Set compilationUnitsToSolveAnnotations = + new HashSet<>(parsedTargetFiles.values()); + + while (!compilationUnitsToSolveAnnotations.isEmpty()) { + for (CompilationUnit cu : compilationUnitsToSolveAnnotations) { + cu.accept(annotationParameterTypesVisitor, null); + } + + // add all files related to the target annotations + for (String annoFullName : annotationParameterTypesVisitor.getClassesToAdd()) { + String directoryOfFile = annoFullName.replace(".", "/") + ".java"; + File thisFile = new File(root + directoryOfFile); + // classes from JDK are automatically on the classpath, so UnsolvedSymbolVisitor will not + // create synthetic files for them + if (thisFile.exists()) { + relatedClass.add(directoryOfFile); + } + } + + compilationUnitsToSolveAnnotations.clear(); + + for (String directory : relatedClass) { + // directories already in parsedTargetFiles are original files in the root directory, we are + // not supposed to update them. + if (!parsedTargetFiles.containsKey(directory)) { + try { + // We need to continue solving annotations and parameters in newly added annotation + // files + CompilationUnit parsed = parseJavaFile(root, directory); + parsedTargetFiles.put(directory, parsed); + compilationUnitsToSolveAnnotations.add(parsed); + } catch (ParseProblemException e) { + // TODO: Figure out why the CI is crashing. + continue; + } + } + } + + annotationParameterTypesVisitor + .getUsedTypeElements() + .addAll(annotationParameterTypesVisitor.getClassesToAdd()); + annotationParameterTypesVisitor.getClassesToAdd().clear(); + } + return annotationParameterTypesVisitor; + } + /** * Helper method to create a human-readable table of the unfound methods and each method in the * same class that was considered. diff --git a/src/main/java/org/checkerframework/specimin/SpeciminStateVisitor.java b/src/main/java/org/checkerframework/specimin/SpeciminStateVisitor.java index bd3c8010c..6f384b244 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminStateVisitor.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminStateVisitor.java @@ -10,8 +10,8 @@ * should not be used directly. * *

This class tracks the following: the lists of target methods and fields, the lists of used - * members and classes, and the set of existing classes to file paths. It may be expanded to - * handle additional state tracking in the future. + * members and classes, and the set of existing classes to file paths. It may be expanded to handle + * additional state tracking in the future. */ public abstract class SpeciminStateVisitor extends ModifierVisitor { diff --git a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java index eb02ac751..0e1d0d6b2 100644 --- a/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java +++ b/src/main/java/org/checkerframework/specimin/TargetMethodFinderVisitor.java @@ -288,6 +288,7 @@ public Visitable visit(ConstructorDeclaration method, Void p) { } else { updateUnfoundMethods(methodName); } + Visitable result = super.visit(method, p); insideTargetMember = oldInsideTargetMember; @@ -319,6 +320,7 @@ public Visitable visit(VariableDeclarator node, Void arg) { Visitable result = super.visit(node, arg); usedTypeElements.add(this.classFQName); insideTargetMember = oldInsideTargetMember; + return result; } } @@ -354,6 +356,7 @@ public Visitable visit(MethodDeclaration method, Void p) { resolvedMethod.getPackageName() + "." + resolvedMethod.getClassName(), usedTypeElements, nonPrimaryClassesToPrimaryClass); + insideTargetMember = true; targetMethods.add(resolvedMethod.getQualifiedSignature()); // make sure that differences in spacing does not interfere with the result @@ -387,6 +390,7 @@ public Visitable visit(MethodDeclaration method, Void p) { } else { updateUnfoundMethods(methodName); } + Visitable result = super.visit(method, p); insideTargetMember = oldInsideTargetMember; return result; @@ -437,6 +441,7 @@ public Visitable visit(Parameter para, Void p) { } } } + return super.visit(para, p); } @@ -629,6 +634,7 @@ public Visitable visit(ExplicitConstructorInvocationStmt expr, Void p) { @Override public Visitable visit(EnumConstantDeclaration enumConstantDeclaration, Void p) { Node parentNode = enumConstantDeclaration.getParentNode().orElseThrow(); + if (parentNode instanceof EnumDeclaration) { if (usedTypeElements.contains( ((EnumDeclaration) parentNode) @@ -641,6 +647,7 @@ public Visitable visit(EnumConstantDeclaration enumConstantDeclaration, Void p) insideTargetMember = true; Visitable result = super.visit(enumConstantDeclaration, p); insideTargetMember = oldInsideTargetMember; + return result; } } @@ -853,7 +860,6 @@ public static void updateUsedClassWithQualifiedClassName( qualifiedClassName = qualifiedClassName.substring(0, qualifiedClassName.indexOf("<")); } usedTypeElement.add(qualifiedClassName); - // in case this class is not a primary class. if (nonPrimaryClassesToPrimaryClass.containsKey(qualifiedClassName)) { updateUsedClassWithQualifiedClassName( diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedAnnotationRemoverVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedAnnotationRemoverVisitor.java index 6dd7207b6..b17cfddca 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedAnnotationRemoverVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedAnnotationRemoverVisitor.java @@ -8,13 +8,14 @@ import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; +import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedAnnotationDeclaration; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionAnnotationDeclaration; import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import java.io.IOException; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; /** A visitor that removes unsolved annotation expressions. */ public class UnsolvedAnnotationRemoverVisitor extends ModifierVisitor { @@ -33,9 +34,6 @@ public class UnsolvedAnnotationRemoverVisitor extends ModifierVisitor { */ Map classToFullClassName = new HashMap<>(); - /** The set of full names of solvable annotations. */ - private Set solvedAnnotationFullName = new HashSet<>(); - /** * Create a new instance of UnsolvedAnnotationRemoverVisitor * @@ -55,17 +53,6 @@ public UnsolvedAnnotationRemoverVisitor(List jarPaths) { } } - /** - * Get a copy of the set of solved annotations. - * - * @return copy a copy of the set of solved annotations. - */ - public Set getSolvedAnnotationFullName() { - Set copy = new HashSet<>(); - copy.addAll(solvedAnnotationFullName); - return copy; - } - @Override public Node visit(ImportDeclaration decl, Void p) { String classFullName = decl.getNameAsString(); @@ -100,21 +87,46 @@ public Visitable visit(SingleMemberAnnotationExpr expr, Void p) { */ public void processAnnotations(AnnotationExpr annotation) { String annotationName = annotation.getNameAsString(); + + // Never preserve @Override, since it causes compile errors but does not fix them. + if ("Override".equals(annotationName)) { + annotation.remove(); + return; + } + + // If the annotation can be resolved, find its qualified name to prevent removal + boolean isResolved = true; + try { + ResolvedAnnotationDeclaration resolvedAnno = annotation.resolve(); + annotationName = resolvedAnno.getQualifiedName(); + + if (resolvedAnno instanceof ReflectionAnnotationDeclaration) { + // These annotations do not have a file corresponding to them, which can cause + // compile errors in the output + // This is fine if it's included in java.lang, but if not, we should treat it as + // if it were unresolved + + if (!annotationName.startsWith("java.lang")) { + isResolved = false; + } + } + } catch (UnsolvedSymbolException ex) { + isResolved = false; + } + if (!UnsolvedSymbolVisitor.isAClassPath(annotationName)) { if (!classToFullClassName.containsKey(annotationName)) { // An annotation not imported and from the java.lang package is not our concern. - // Never preserve @Override, since it causes compile errors but does not fix them. - if (!JavaLangUtils.isJavaLangName(annotationName) || "Override".equals(annotationName)) { + if (!JavaLangUtils.isJavaLangName(annotationName)) { annotation.remove(); } return; } annotationName = classToFullClassName.get(annotationName); } - if (!classToJarPath.containsKey(annotationName)) { + + if (!isResolved && !classToJarPath.containsKey(annotationName)) { annotation.remove(); - } else { - solvedAnnotationFullName.add(annotationName); } } } diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedClassOrInterface.java b/src/main/java/org/checkerframework/specimin/UnsolvedClassOrInterface.java index fafd7fc39..430bf4942 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedClassOrInterface.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedClassOrInterface.java @@ -450,6 +450,15 @@ public String toString() { if (this.getClass() != UnsolvedInnerClass.class) { sb.append("package ").append(packageName).append(";\n"); } + + // Synthetic annotations used within generic types cause compile errors, + // so we need to add this to prevent them + if (isAnAnnotation) { + sb.append( + "@java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE," + + " java.lang.annotation.ElementType.METHOD})\n"); + } + sb.append("public "); if (this.getClass() == UnsolvedInnerClass.class) { // Nested classes that are visible outside their parent class diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index 803fa378e..5038e5d06 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -15,16 +15,21 @@ import com.github.javaparser.ast.body.TypeDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.comments.Comment; +import com.github.javaparser.ast.expr.AnnotationExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.InstanceOfExpr; import com.github.javaparser.ast.expr.LambdaExpr; +import com.github.javaparser.ast.expr.MarkerAnnotationExpr; +import com.github.javaparser.ast.expr.MemberValuePair; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.MethodReferenceExpr; import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.NormalAnnotationExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.expr.PatternExpr; import com.github.javaparser.ast.expr.SimpleName; +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; import com.github.javaparser.ast.expr.SwitchExpr; import com.github.javaparser.ast.expr.ThisExpr; import com.github.javaparser.ast.stmt.BlockStmt; @@ -47,6 +52,7 @@ import com.github.javaparser.ast.visitor.ModifierVisitor; import com.github.javaparser.ast.visitor.Visitable; import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedAnnotationDeclaration; import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedTypeParameterDeclaration; @@ -55,6 +61,7 @@ import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; import com.github.javaparser.resolution.types.ResolvedTypeVariable; +import com.github.javaparser.symbolsolver.reflectionmodel.ReflectionAnnotationDeclaration; import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver; import com.github.javaparser.utils.Pair; import com.google.common.base.Ascii; @@ -899,7 +906,11 @@ else if (!classAndPackageMap.containsKey(typeAsString)) { @Override public Visitable visit(NameExpr node, Void arg) { - if (!insideTargetMember) { + Optional parent = node.getParentNode(); + + boolean insideAnnotation = parent.isPresent() && (parent.get() instanceof AnnotationExpr); + + if (!insideTargetMember && !insideAnnotation) { return super.visit(node, arg); } String name = node.getNameAsString(); @@ -1368,6 +1379,164 @@ public Visitable visit(ObjectCreationExpr newExpr, Void p) { return result; } + @Override + @SuppressWarnings("EmptyCatch") + public Visitable visit(MarkerAnnotationExpr anno, Void p) { + try { + ResolvedAnnotationDeclaration resolvedAnno = anno.resolve(); + if (!(resolvedAnno instanceof ReflectionAnnotationDeclaration)) { + // ResolvedAnnotationDeclaration means no file/CompilationUnit behind anno + // So, we must still generate it even though it's resolved + return super.visit(anno, p); + } + } catch (UnsolvedSymbolException ex) { + + } catch (ClassCastException ex) { + // A ClassCastException is only raised in specific circumstances; for example, when an + // annotation is an inner class (has a dot) and is used in/on a class which references itself: + /* + @Foo.Bar + public class Test { + Test foo() { + return null; + } + } + */ + // In these cases, a JavaParserClassDeclaration is being casted to a + // ResolvedAnnotationDeclaration, thus causing the error. However, through + // testing, this exception was only raised on subsequent resolve()s (not the + // first), so the synthetic type should already be generated. + return super.visit(anno, p); + } + + UnsolvedClassOrInterface unsolvedAnnotation; + + if (isAClassPath(anno.getNameAsString())) { + @SuppressWarnings("signature") // Already guaranteed to be a FQN here + @FullyQualifiedName String qualifiedTypeName = anno.getNameAsString(); + unsolvedAnnotation = getSimpleSyntheticClassFromFullyQualifiedName(qualifiedTypeName); + updateMissingClass(unsolvedAnnotation); + } else { + unsolvedAnnotation = updateUnsolvedClassWithClassName(anno.getNameAsString(), false, false); + } + + unsolvedAnnotation.setIsAnAnnotationToTrue(); + + return super.visit(anno, p); + } + + @Override + @SuppressWarnings("EmptyCatch") + public Visitable visit(NormalAnnotationExpr anno, Void p) { + try { + ResolvedAnnotationDeclaration resolvedAnno = anno.resolve(); + if (!(resolvedAnno instanceof ReflectionAnnotationDeclaration)) { + // ResolvedAnnotationDeclaration means no file/CompilationUnit behind anno + // So, we must still generate it even though it's resolved + return super.visit(anno, p); + } + return super.visit(anno, p); + } catch (UnsolvedSymbolException ex) { + + } catch (ClassCastException ex) { + // A ClassCastException is only raised in specific circumstances; for example, when an + // annotation is an inner class (has a dot) and is used in/on a class which references itself: + /* + @Foo.Bar + public class Test { + Test foo() { + return null; + } + } + */ + // In these cases, a JavaParserClassDeclaration is being casted to a + // ResolvedAnnotationDeclaration, thus causing the error. However, through + // testing, this exception was only raised on subsequent resolve()s (not the + // first), so the synthetic type should already be generated. + return super.visit(anno, p); + } + + UnsolvedClassOrInterface unsolvedAnnotation; + + if (isAClassPath(anno.getNameAsString())) { + @SuppressWarnings("signature") // Already guaranteed to be a FQN here + @FullyQualifiedName String qualifiedTypeName = anno.getNameAsString(); + unsolvedAnnotation = getSimpleSyntheticClassFromFullyQualifiedName(qualifiedTypeName); + updateMissingClass(unsolvedAnnotation); + } else { + unsolvedAnnotation = updateUnsolvedClassWithClassName(anno.getNameAsString(), false, false); + } + + unsolvedAnnotation.setIsAnAnnotationToTrue(); + + // Add annotation parameters and resolve the annotation parameters to their types + for (MemberValuePair pair : anno.getPairs()) { + unsolvedAnnotation.addMethod( + new UnsolvedMethod( + pair.getNameAsString(), + JavaParserUtil.getValueTypeFromAnnotationExpression(pair.getValue()), + Collections.emptyList(), + true)); + } + + return super.visit(anno, p); + } + + @Override + @SuppressWarnings("EmptyCatch") + public Visitable visit(SingleMemberAnnotationExpr anno, Void p) { + try { + ResolvedAnnotationDeclaration resolvedAnno = anno.resolve(); + if (!(resolvedAnno instanceof ReflectionAnnotationDeclaration)) { + // ResolvedAnnotationDeclaration means no file/CompilationUnit behind anno + // So, we must still generate it even though it's resolved + return super.visit(anno, p); + } + return super.visit(anno, p); + } catch (UnsolvedSymbolException ex) { + + } catch (ClassCastException ex) { + // A ClassCastException is only raised in specific circumstances; for example, when an + // annotation is an inner class (has a dot) and is used in/on a class which references itself: + /* + @Foo.Bar + public class Test { + Test foo() { + return null; + } + } + */ + // In these cases, a JavaParserClassDeclaration is being casted to a + // ResolvedAnnotationDeclaration, thus causing the error. However, through + // testing, this exception was only raised on subsequent resolve()s (not the + // first), so the synthetic type should already be generated. + return super.visit(anno, p); + } + + UnsolvedClassOrInterface unsolvedAnnotation; + + if (isAClassPath(anno.getNameAsString())) { + @SuppressWarnings("signature") // Already guaranteed to be a FQN here + @FullyQualifiedName String qualifiedTypeName = anno.getNameAsString(); + unsolvedAnnotation = getSimpleSyntheticClassFromFullyQualifiedName(qualifiedTypeName); + updateMissingClass(unsolvedAnnotation); + } else { + unsolvedAnnotation = updateUnsolvedClassWithClassName(anno.getNameAsString(), false, false); + } + + unsolvedAnnotation.setIsAnAnnotationToTrue(); + + // Add annotation parameters and resolve the annotation parameters to their types + unsolvedAnnotation.addMethod( + new UnsolvedMethod( + "value", + JavaParserUtil.getValueTypeFromAnnotationExpression(anno.getMemberValue()), + Collections.emptyList(), + true)); + + return super.visit(anno, p); + } + /** * Converts a qualified class name into a relative file path. Angle brackets for type variables * are permitted in the input. @@ -1947,7 +2116,7 @@ public boolean isFromAJarFile(Expression expr) { /** * This method updates a synthetic file based on a solvable expression. The input expression is - * solvable because its data is in the jar files that Specimin taks as input. + * solvable because its data is in the jar files that Specimin takes as input. * * @param expr the expression to be used */ diff --git a/src/test/java/org/checkerframework/specimin/RemoveUnsolvedAnnotationsTest.java b/src/test/java/org/checkerframework/specimin/RemoveUnsolvedAnnotationsTest.java deleted file mode 100644 index b66ca3dd5..000000000 --- a/src/test/java/org/checkerframework/specimin/RemoveUnsolvedAnnotationsTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.checkerframework.specimin; - -import java.io.IOException; -import org.junit.Test; - -/** This test checks if Specimin can remove annotations as needed */ -public class RemoveUnsolvedAnnotationsTest { - @Test - public void runTest() throws IOException { - SpeciminTestExecutor.runTest( - "removeunsolvedannotations", - new String[] {"com/example/Simple.java"}, - new String[] {"com.example.Simple#baz()"}, - new String[] {"src/test/resources/shared/checker-qual-3.42.0.jar"}); - } -} diff --git a/src/test/java/org/checkerframework/specimin/SyntheticAnnotationTargetTest.java b/src/test/java/org/checkerframework/specimin/SyntheticAnnotationTargetTest.java new file mode 100644 index 000000000..4d4f6c705 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/SyntheticAnnotationTargetTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** + * This test checks if synthetic annotations used in different locations will compile based + * on @Target. + */ +public class SyntheticAnnotationTargetTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "syntheticannotationtarget", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#baz(U)"}); + } +} diff --git a/src/test/java/org/checkerframework/specimin/SyntheticAnnotationsTest.java b/src/test/java/org/checkerframework/specimin/SyntheticAnnotationsTest.java new file mode 100644 index 000000000..b600564dc --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/SyntheticAnnotationsTest.java @@ -0,0 +1,15 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.Test; + +/** This test checks if synthetic annotations are correctly generated for unsolved annotations. */ +public class SyntheticAnnotationsTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "syntheticannotations", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#baz(String)"}); + } +} diff --git a/src/test/resources/annoingenerictarget/expected/com/example/Simple.java b/src/test/resources/annoingenerictarget/expected/com/example/Simple.java index 86f77135d..8812a5d42 100644 --- a/src/test/resources/annoingenerictarget/expected/com/example/Simple.java +++ b/src/test/resources/annoingenerictarget/expected/com/example/Simple.java @@ -1,9 +1,14 @@ package com.example; import java.util.Collection; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.initialization.qual.Initialized; class Simple { - public static Collection unmodifiableCollection(Collection c) { + @Initialized + @NonNull + @UnknownKeyFor + public static Collection unmodifiableCollection(@Initialized @NonNull @UnknownKeyFor Collection<@Initialized @KeyForBottom @NonNull ? extends T> c) { return null; } } diff --git a/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/initialization/qual/Initialized.java b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/initialization/qual/Initialized.java new file mode 100644 index 000000000..fb2cebd7d --- /dev/null +++ b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/initialization/qual/Initialized.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.initialization.qual; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Initialized { +} diff --git a/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/KeyForBottom.java b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/KeyForBottom.java new file mode 100644 index 000000000..98e490880 --- /dev/null +++ b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/KeyForBottom.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.nullness.qual; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface KeyForBottom { +} diff --git a/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/NonNull.java b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/NonNull.java new file mode 100644 index 000000000..8cd95441a --- /dev/null +++ b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/NonNull.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.nullness.qual; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface NonNull { +} diff --git a/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/Nullable.java b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/Nullable.java new file mode 100644 index 000000000..7547c25db --- /dev/null +++ b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/Nullable.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.nullness.qual; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Nullable { +} diff --git a/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java new file mode 100644 index 000000000..66be6b722 --- /dev/null +++ b/src/test/resources/annoingenerictarget/expected/org/checkerframework/checker/nullness/qual/UnknownKeyFor.java @@ -0,0 +1,5 @@ +package org.checkerframework.checker.nullness.qual; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface UnknownKeyFor { +} diff --git a/src/test/resources/annoingenerictarget/input/com/example/Simple.java b/src/test/resources/annoingenerictarget/input/com/example/Simple.java index 94a92bfc8..bb7ac12ee 100644 --- a/src/test/resources/annoingenerictarget/input/com/example/Simple.java +++ b/src/test/resources/annoingenerictarget/input/com/example/Simple.java @@ -2,6 +2,7 @@ import java.util.Collection; import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.initialization.qual.Initialized; class Simple { public static @Initialized @NonNull @UnknownKeyFor Collection unmodifiableCollection(@Initialized @NonNull @UnknownKeyFor Collection<@Initialized @KeyForBottom @NonNull ? extends T> c) { diff --git a/src/test/resources/issue272/expected/com/example/PostconditionAnnotation.java b/src/test/resources/issue272/expected/com/example/PostconditionAnnotation.java index d8e99cc2d..b0280ae9e 100644 --- a/src/test/resources/issue272/expected/com/example/PostconditionAnnotation.java +++ b/src/test/resources/issue272/expected/com/example/PostconditionAnnotation.java @@ -1,5 +1,6 @@ package com.example; +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) public @interface PostconditionAnnotation { } diff --git a/src/test/resources/issue272/expected/com/example/PreconditionAnnotation.java b/src/test/resources/issue272/expected/com/example/PreconditionAnnotation.java index 6ba07a146..7bfc2d492 100644 --- a/src/test/resources/issue272/expected/com/example/PreconditionAnnotation.java +++ b/src/test/resources/issue272/expected/com/example/PreconditionAnnotation.java @@ -1,5 +1,6 @@ package com.example; +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) public @interface PreconditionAnnotation { } diff --git a/src/test/resources/parameterwithannotations/expected/org/checkerframework/checker/nullness/qual/Nullable.java b/src/test/resources/parameterwithannotations/expected/org/checkerframework/checker/nullness/qual/Nullable.java new file mode 100644 index 000000000..0792cbabf --- /dev/null +++ b/src/test/resources/parameterwithannotations/expected/org/checkerframework/checker/nullness/qual/Nullable.java @@ -0,0 +1,20 @@ +package org.checkerframework.checker.nullness.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultFor; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifierForLiterals; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) +@SubtypeOf({}) +@QualifierForLiterals({ LiteralKind.NULL }) +@DefaultFor(types = { Void.class }) +public @interface Nullable { +} diff --git a/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/DefaultFor.java b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/DefaultFor.java new file mode 100644 index 000000000..b257b8eba --- /dev/null +++ b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/DefaultFor.java @@ -0,0 +1,23 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface DefaultFor { + + TypeUseLocation[] value() default {}; + + TypeKind[] typeKinds() default {}; + + Class[] types() default {}; + + String[] names() default {}; + + String[] namesExceptions() default {}; +} diff --git a/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/LiteralKind.java b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/LiteralKind.java new file mode 100644 index 000000000..87e2f7170 --- /dev/null +++ b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/LiteralKind.java @@ -0,0 +1,6 @@ +package org.checkerframework.framework.qual; + +public enum LiteralKind { + + NULL +} diff --git a/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/QualifierForLiterals.java b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/QualifierForLiterals.java new file mode 100644 index 000000000..ec31e5e7e --- /dev/null +++ b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/QualifierForLiterals.java @@ -0,0 +1,17 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface QualifierForLiterals { + + LiteralKind[] value() default {}; + + String[] stringPatterns() default {}; +} diff --git a/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java new file mode 100644 index 000000000..ed6c05afc --- /dev/null +++ b/src/test/resources/parameterwithannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java @@ -0,0 +1,15 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface SubtypeOf { + + Class[] value(); +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/GTENegativeOne.java b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/GTENegativeOne.java new file mode 100644 index 000000000..05f431ebd --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/GTENegativeOne.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.index.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) +@SubtypeOf({ LowerBoundUnknown.class }) +public @interface GTENegativeOne { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/LowerBoundUnknown.java b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/LowerBoundUnknown.java new file mode 100644 index 000000000..0ba975cb6 --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/LowerBoundUnknown.java @@ -0,0 +1,19 @@ +package org.checkerframework.checker.index.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.InvisibleQualifier; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) +@SubtypeOf({}) +@DefaultQualifierInHierarchy +@InvisibleQualifier +public @interface LowerBoundUnknown { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/NonNegative.java b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/NonNegative.java new file mode 100644 index 000000000..53e2a6475 --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/NonNegative.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.index.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) +@SubtypeOf({ GTENegativeOne.class }) +public @interface NonNegative { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/Positive.java b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/Positive.java new file mode 100644 index 000000000..e021f7dc4 --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/checker/index/qual/Positive.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.index.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) +@SubtypeOf({ NonNegative.class }) +public @interface Positive { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java new file mode 100644 index 000000000..9817aee0d --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/DefaultQualifierInHierarchy.java @@ -0,0 +1,13 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface DefaultQualifierInHierarchy { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/InvisibleQualifier.java b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/InvisibleQualifier.java new file mode 100644 index 000000000..f1709bac4 --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/InvisibleQualifier.java @@ -0,0 +1,13 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface InvisibleQualifier { +} diff --git a/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java new file mode 100644 index 000000000..ed6c05afc --- /dev/null +++ b/src/test/resources/preserveannotations/expected/org/checkerframework/framework/qual/SubtypeOf.java @@ -0,0 +1,15 @@ +package org.checkerframework.framework.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface SubtypeOf { + + Class[] value(); +} diff --git a/src/test/resources/removeunsolvedannotations/expected/com/example/Simple.java b/src/test/resources/removeunsolvedannotations/expected/com/example/Simple.java deleted file mode 100644 index c295b2f33..000000000 --- a/src/test/resources/removeunsolvedannotations/expected/com/example/Simple.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example; - -import org.checkerframework.checker.signature.qual.ClassGetSimpleName; - -class Simple { - - public void baz() { - @ClassGetSimpleName - String className = ""; - } -} diff --git a/src/test/resources/removeunsolvedannotations/input/com/example/Simple.java b/src/test/resources/removeunsolvedannotations/input/com/example/Simple.java deleted file mode 100644 index 8194cfa80..000000000 --- a/src/test/resources/removeunsolvedannotations/input/com/example/Simple.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example; - -import org.checkerframework.checker.signature.qual.ClassGetSimpleName; -import org.example.ToBeDeleted; - -class Simple { - - public void baz() { - @ToBeDeleted - @ClassGetSimpleName - String className = ""; - } -} diff --git a/src/test/resources/syntheticannotations/expected/com/example/Anno.java b/src/test/resources/syntheticannotations/expected/com/example/Anno.java new file mode 100644 index 000000000..826e9848d --- /dev/null +++ b/src/test/resources/syntheticannotations/expected/com/example/Anno.java @@ -0,0 +1,7 @@ +package com.example; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Anno { + + public int value(); +} diff --git a/src/test/resources/syntheticannotations/expected/com/example/Bar.java b/src/test/resources/syntheticannotations/expected/com/example/Bar.java new file mode 100644 index 000000000..533e46255 --- /dev/null +++ b/src/test/resources/syntheticannotations/expected/com/example/Bar.java @@ -0,0 +1,7 @@ +package com.example; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Bar { + + public int value(); +} diff --git a/src/test/resources/syntheticannotations/expected/com/example/Baz.java b/src/test/resources/syntheticannotations/expected/com/example/Baz.java new file mode 100644 index 000000000..f67672865 --- /dev/null +++ b/src/test/resources/syntheticannotations/expected/com/example/Baz.java @@ -0,0 +1,9 @@ +package com.example; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Baz { + + public String foo(); + + public Class bar(); +} diff --git a/src/test/resources/syntheticannotations/expected/com/example/Foo.java b/src/test/resources/syntheticannotations/expected/com/example/Foo.java new file mode 100644 index 000000000..f3a592f2b --- /dev/null +++ b/src/test/resources/syntheticannotations/expected/com/example/Foo.java @@ -0,0 +1,9 @@ +package com.example; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Foo { + + public Deprecated x(); + + public Anno[] y(); +} diff --git a/src/test/resources/syntheticannotations/expected/com/example/Simple.java b/src/test/resources/syntheticannotations/expected/com/example/Simple.java new file mode 100644 index 000000000..464572718 --- /dev/null +++ b/src/test/resources/syntheticannotations/expected/com/example/Simple.java @@ -0,0 +1,11 @@ +package com.example; + +public class Simple { + + @Bar(10) + @Baz(foo = "foo", bar = Simple.class) + @Foo(x = @Deprecated, y = { @Anno(1), @Anno(2) }) + public static int baz(String bar) { + return 1; + } +} diff --git a/src/test/resources/syntheticannotations/input/com/example/Simple.java b/src/test/resources/syntheticannotations/input/com/example/Simple.java new file mode 100644 index 000000000..db4dd3114 --- /dev/null +++ b/src/test/resources/syntheticannotations/input/com/example/Simple.java @@ -0,0 +1,10 @@ +package com.example; + +public class Simple { + @Bar(10) + @Baz(foo = "foo", bar = Simple.class) + @Foo(x = @Deprecated, y = {@Anno(1), @Anno(2)}) + public static int baz(String bar) { + return 1; + } +} diff --git a/src/test/resources/syntheticannotationtarget/expected/com/example/Foo.java b/src/test/resources/syntheticannotationtarget/expected/com/example/Foo.java new file mode 100644 index 000000000..bfe98c61d --- /dev/null +++ b/src/test/resources/syntheticannotationtarget/expected/com/example/Foo.java @@ -0,0 +1,5 @@ +package com.example; + +@java.lang.annotation.Target({ java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD }) +public @interface Foo { +} diff --git a/src/test/resources/syntheticannotationtarget/expected/com/example/Simple.java b/src/test/resources/syntheticannotationtarget/expected/com/example/Simple.java new file mode 100644 index 000000000..d2ad2e147 --- /dev/null +++ b/src/test/resources/syntheticannotationtarget/expected/com/example/Simple.java @@ -0,0 +1,24 @@ +package com.example; + +@Foo +public class Simple<@Foo T> { + @Foo + @AnnoDecl + public <@Foo U> void baz(@Foo U u) { + Simple<@Foo String> simple = new Simple<>(); + @Foo + int x = simple.field; + } + + @Foo + public int field; + + @Foo + public Simple() { + throw new Error(); + } + + @Foo + private @interface AnnoDecl { + } +} diff --git a/src/test/resources/syntheticannotationtarget/input/com/example/Simple.java b/src/test/resources/syntheticannotationtarget/input/com/example/Simple.java new file mode 100644 index 000000000..375b8bc68 --- /dev/null +++ b/src/test/resources/syntheticannotationtarget/input/com/example/Simple.java @@ -0,0 +1,22 @@ +package com.example; + +@Foo +public class Simple<@Foo T> { + @Foo + @AnnoDecl + public <@Foo U> void baz(@Foo U u) { + Simple<@Foo String> simple = new Simple<>(); + @Foo int x = simple.field; + } + + @Foo + public int field; + + @Foo + public Simple() { } + + @Foo + private @interface AnnoDecl { + + } +}