diff --git a/annotation-file-utilities/src/test/resources/annotations/tests/classfile/all-annotations.jaif b/annotation-file-utilities/src/test/resources/annotations/tests/classfile/all-annotations.jaif index b85e148acfd5..d06586d9d022 100644 --- a/annotation-file-utilities/src/test/resources/annotations/tests/classfile/all-annotations.jaif +++ b/annotation-file-utilities/src/test/resources/annotations/tests/classfile/all-annotations.jaif @@ -34,4 +34,3 @@ annotation @G: @Retention(RUNTIME) boolean[] fieldC int fieldD int fieldE - diff --git a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java index 505b0bab2c2f..f9f8cf41c595 100644 --- a/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java +++ b/framework/src/main/java/org/checkerframework/common/wholeprograminference/WholeProgramInferenceJavaParserStorage.java @@ -25,9 +25,15 @@ import com.github.javaparser.ast.expr.ObjectCreationExpr; import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.type.ArrayType; import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.IntersectionType; +import com.github.javaparser.ast.type.PrimitiveType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.type.TypeParameter; +import com.github.javaparser.ast.type.UnionType; +import com.github.javaparser.ast.type.VarType; +import com.github.javaparser.ast.type.WildcardType; import com.github.javaparser.ast.visitor.CloneVisitor; import com.github.javaparser.ast.visitor.VoidVisitor; import com.github.javaparser.printer.DefaultPrettyPrinter; @@ -1094,6 +1100,63 @@ public void writeResultsToFile(OutputFormat outputFormat, BaseTypeChecker checke modifiedFiles.clear(); } + /** + * Returns true if the annotation is relevant (where it appears in the program). This + * implementation is conservative and only returns false if the qualifier is definitely not + * relevant. + * + * @param anno an annotation + * @return true if the annotation might be relevant + */ + boolean annotationIsRelevant(AnnotationExpr anno) { + GenericAnnotatedTypeFactory gatf = (GenericAnnotatedTypeFactory) atypeFactory; + if (gatf.relevantJavaTypes == null) { + return true; + } + + // `aname` is a fully-qualified name. + String aName = anno.getNameAsString(); + if (!atypeFactory.isSupportedQualifier(aName)) { + // The annotation might be a declaration qualifier, such as a side effect specification. + return true; + } + Node parentNode = anno.getParentNode().get(); + + if (parentNode instanceof ArrayType) { + return gatf.arraysAreRelevant(); + } + if (parentNode instanceof ClassOrInterfaceType classType) { + String simpleName = classType.getName().toString(); + String scopedName = classType.getNameWithScope(); + // TODO: Do I need to remove type parameters? + return gatf.isRelevant(simpleName) || gatf.isRelevant(scopedName); + } + if (parentNode instanceof IntersectionType) { + return true; // TODO + } + if (parentNode instanceof Parameter param) { + if (param.isVarArgs()) { + return gatf.arraysAreRelevant(); + } else { + throw new Error("Unexpected type annotation on non-varargs Parameter: " + param); + } + } + if (parentNode instanceof PrimitiveType) { + return gatf.isRelevant(parentNode.toString()); + } + if (parentNode instanceof UnionType) { + return true; // TODO + } + if (parentNode instanceof VarType) { + return gatf.nonprimitivesAreRelevant(); + } + if (parentNode instanceof WildcardType) { + return true; // TODO + } + + throw new Error("What parent? " + parentNode.getClass().getSimpleName() + " " + parentNode); + } + /** * Write an ajava file to disk. * @@ -1109,7 +1172,9 @@ private void writeAjavaFile(Path outputPath, CompilationUnitAnnos root) { // and crashes when adding annotations in certain locations. // LexicalPreservingPrinter.print(root.declaration, writer); - // Do not print invisible qualifiers, to avoid cluttering the output. + // To avoid cluttering the output, do not print: + // * invisible qualifiers + // * irrelevant qualifiers. Set invisibleQualifierNames = getInvisibleQualifierNames(this.atypeFactory); DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter() { @@ -1122,6 +1187,9 @@ public void visit(MarkerAnnotationExpr n, Void arg) { if (invisibleQualifierNames.contains(n.getName().toString())) { return; } + if (!annotationIsRelevant(n)) { + return; + } super.visit(n, arg); } @@ -1130,6 +1198,9 @@ public void visit(SingleMemberAnnotationExpr n, Void arg) { if (invisibleQualifierNames.contains(n.getName().toString())) { return; } + if (!annotationIsRelevant(n)) { + return; + } super.visit(n, arg); } @@ -1138,6 +1209,9 @@ public void visit(NormalAnnotationExpr n, Void arg) { if (invisibleQualifierNames.contains(n.getName().toString())) { return; } + if (!annotationIsRelevant(n)) { + return; + } super.visit(n, arg); } diff --git a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java index 40b017247abc..4637ad032e8c 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/GenericAnnotatedTypeFactory.java @@ -190,6 +190,8 @@ public abstract class GenericAnnotatedTypeFactory< *

Although a {@code Class} object exists for every element, this does not contain those * {@code Class} objects because the elements will be compared to TypeMirrors for which Class * objects may not exist (they might not be on the classpath). + * + *

For their names, see {@link #relevantJavaTypeNames}. */ public final @Nullable Set relevantJavaTypes; @@ -197,8 +199,17 @@ public abstract class GenericAnnotatedTypeFactory< * True if users may write type annotations on arrays. Ignored unless {@link #relevantJavaTypes} * is non-null. */ + public final @Nullable Set relevantJavaTypeNames; + + /** Whether users may write type annotations on arrays. */ protected final boolean arraysAreRelevant; + /** + * Whether users may write type annotations on non-primitives (classes, arrays, etc.). This is + * redundant with the value of {@link #relevantJavaTypes} but is included for efficiency. + */ + protected final boolean nonprimitivesAreRelevant; + // Flow related fields /** Should flow be used by default? */ @@ -368,16 +379,21 @@ protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) checker.getClass().getAnnotation(RelevantJavaTypes.class); if (relevantJavaTypesAnno == null) { this.relevantJavaTypes = null; + this.relevantJavaTypeNames = null; this.arraysAreRelevant = true; + this.nonprimitivesAreRelevant = true; } else { Types types = getChecker().getTypeUtils(); Elements elements = getElementUtils(); Class[] classes = relevantJavaTypesAnno.value(); Set relevantJavaTypesTemp = new HashSet<>(MapsP.mapCapacity(classes.length)); + Set relevantJavaTypeNamesTemp = new HashSet<>(MapsP.mapCapacity(classes.length)); boolean arraysAreRelevantTemp = false; + boolean nonprimitivesAreRelevantTemp = false; for (Class clazz : classes) { if (clazz == Object[].class) { arraysAreRelevantTemp = true; + nonprimitivesAreRelevantTemp = true; } else if (clazz.isArray()) { throw new TypeSystemError( "Don't use arrays other than Object[] in @RelevantJavaTypes on " @@ -386,10 +402,24 @@ protected GenericAnnotatedTypeFactory(BaseTypeChecker checker, boolean useFlow) TypeMirror relevantType = TypesUtils.typeFromClass(clazz, types, elements); TypeMirror erased = types.erasure(relevantType); relevantJavaTypesTemp.add(erased); + String typeString = erased.toString(); + relevantJavaTypeNamesTemp.add(typeString); + if (clazz.isPrimitive()) { + nonprimitivesAreRelevantTemp = true; + } else { + int dotIndex = typeString.lastIndexOf('.'); + if (dotIndex != -1) { + // It's a fully-qualified name. Add the simple name as well. + // TODO: This might not handle a user writing a nested class like "Map.Entry". + relevantJavaTypeNamesTemp.add(typeString.substring(dotIndex + 1)); + } + } } } this.relevantJavaTypes = Collections.unmodifiableSet(relevantJavaTypesTemp); + this.relevantJavaTypeNames = Collections.unmodifiableSet(relevantJavaTypeNamesTemp); this.arraysAreRelevant = arraysAreRelevantTemp; + this.nonprimitivesAreRelevant = nonprimitivesAreRelevantTemp; } contractsUtils = createContractsFromMethod(); @@ -2495,9 +2525,9 @@ public final boolean isRelevant(TypeMirror tm) { if (tm.getKind() != TypeKind.PACKAGE && tm.getKind() != TypeKind.MODULE) { tm = types.erasure(tm); } - Boolean cachedResult = isRelevantCache.get(tm); - if (cachedResult != null) { - return cachedResult; + Boolean resultBoxed = isRelevantCache.get(tm); + if (resultBoxed != null) { + return resultBoxed; } boolean result = isRelevantImpl(tm); isRelevantCache.put(tm, result); @@ -2529,6 +2559,9 @@ public final boolean isRelevant(AnnotatedTypeMirror tm) { *

Clients should never call this. Call {@link #isRelevant} instead. This is a helper method * for {@link #isRelevant}. * + *

This should not be called if {@code relevantJavaTypes == null || + * relevantJavaTypes.contains(tm))}. + * * @param tm a type * @return true if users can write type annotations from this type system on the given Java type */ @@ -2615,6 +2648,43 @@ protected boolean isRelevantImpl(TypeMirror tm) { } } + /** + * Returns true if users can write type annotations from this type system directly on the given + * Java type. + * + *

For a compound type, returns true only if it is permitted to write a type qualifier on the + * top level of the compound type. That is, this method may return false, when it is possible to + * write type qualifiers on elements of the type. + * + *

Subclasses should override {@code #isRelevantImpl} instead of this method. + * + * @param type a fully-qualified or simple type; should not be an array (use {@link + * #arraysAreRelevant} instead) + * @return true if users can write type annotations from this type system directly on the given + * Java type + */ + public final boolean isRelevant(String type) { + return relevantJavaTypeNames == null || relevantJavaTypeNames.contains(type); + } + + /** + * Returns true if users can write type annotations from this type system on array types. + * + * @return true if users can write type annotations from this type system on array types + */ + public final boolean arraysAreRelevant() { + return arraysAreRelevant; + } + + /** + * Returns true if users can write type annotations from this type system on non-primitive types. + * + * @return true if users can write type annotations from this type system on non-primitive types + */ + public final boolean nonprimitivesAreRelevant() { + return nonprimitivesAreRelevant; + } + /** The cached message about relevant types. */ private @MonotonicNonNull String irrelevantExtraMessage = null;