diff --git a/framework/build.xml b/framework/build.xml index aea57151c4a2..e689bf76363a 100644 --- a/framework/build.xml +++ b/framework/build.xml @@ -488,6 +488,13 @@ + + + + + + diff --git a/framework/src/org/checkerframework/common/basetype/BaseTypeValidator.java b/framework/src/org/checkerframework/common/basetype/BaseTypeValidator.java index 0edbbc81a6b1..949282df218e 100644 --- a/framework/src/org/checkerframework/common/basetype/BaseTypeValidator.java +++ b/framework/src/org/checkerframework/common/basetype/BaseTypeValidator.java @@ -10,6 +10,7 @@ import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import java.util.List; import java.util.Set; @@ -17,6 +18,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import org.checkerframework.framework.qual.PolyAll; +import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.type.AnnotatedTypeFactory; import org.checkerframework.framework.type.AnnotatedTypeMirror; @@ -30,6 +32,8 @@ import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; import org.checkerframework.framework.util.AnnotatedTypes; +import org.checkerframework.framework.util.BoundType; +import org.checkerframework.framework.util.BoundTypeUtil; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.Pair; @@ -353,6 +357,11 @@ public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) { reportInvalidBounds(type, tree); } + if (type.isDeclaration() && tree.getKind() == Kind.TYPE_PARAMETER) { + validateQualifiedLocationsOnBounds( + type, type.getUpperBound(), type.getLowerBound(), tree); + } + // Keep in sync with visitWildcard Set onVar = type.getAnnotations(); if (!onVar.isEmpty()) { @@ -405,6 +414,9 @@ public Void visitWildcard(AnnotatedWildcardType type, Tree tree) { reportInvalidBounds(type, tree); } + validateQualifiedLocationsOnBounds( + type, type.getExtendsBound(), type.getSuperBound(), tree); + // Keep in sync with visitTypeVariable Set onVar = type.getAnnotations(); if (!onVar.isEmpty()) { @@ -479,6 +491,37 @@ public boolean areBoundsValid( return true; } + /** + * Validates the annotation are qualified to be used on the bounds of a type variable or a + * wildcard. + */ + protected void validateQualifiedLocationsOnBounds( + AnnotatedTypeMirror boundedType, + AnnotatedTypeMirror upperBound, + AnnotatedTypeMirror lowerBound, + Tree p) { + + BoundType boundType = BoundTypeUtil.getBoundType(boundedType, atypeFactory); + + // Upper bounds + if (BoundTypeUtil.isOneOf(boundType, BoundType.UPPER)) { + // Explicit upper bound + visitor.checkQualifiedLocation(upperBound, p, TypeUseLocation.EXPLICIT_UPPER_BOUND); + } else { + // Implicit upper bound + visitor.checkQualifiedLocation(upperBound, p, TypeUseLocation.IMPLICIT_UPPER_BOUND); + } + + // Lower bounds + if (BoundTypeUtil.isOneOf(boundType, BoundType.LOWER)) { + // Explicit lower bound + visitor.checkQualifiedLocation(lowerBound, p, TypeUseLocation.EXPLICIT_LOWER_BOUND); + } else { + // Implicit lower bound + visitor.checkQualifiedLocation(lowerBound, p, TypeUseLocation.IMPLICIT_LOWER_BOUND); + } + } + /** * Determines if there are multiple qualifiers from a single hierarchy in type's primary * annotations. If so, report an error. diff --git a/framework/src/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/org/checkerframework/common/basetype/BaseTypeVisitor.java index fc2b68834457..968c2475cb89 100644 --- a/framework/src/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -62,6 +62,7 @@ import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferResult; @@ -76,6 +77,8 @@ import org.checkerframework.framework.flow.CFAbstractStore; import org.checkerframework.framework.flow.CFAbstractValue; import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.QualifiedLocations; +import org.checkerframework.framework.qual.TypeUseLocation; import org.checkerframework.framework.qual.Unused; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.source.SourceVisitor; @@ -317,6 +320,11 @@ public void processClassTree(ClassTree classTree) { checkDefaultConstructor(classTree); } + checkQualifiedLocation( + atypeFactory.getAnnotatedType(classTree), + classTree, + TypeUseLocation.TYPE_DECLARATION); + /* Visit the extends and implements clauses. * The superclass also visits them, but only calls visitParameterizedType, which * loses a main modifier. @@ -324,12 +332,16 @@ public void processClassTree(ClassTree classTree) { Tree ext = classTree.getExtendsClause(); if (ext != null) { validateTypeOf(ext); + checkQualifiedLocation( + atypeFactory.getAnnotatedType(ext), ext, TypeUseLocation.EXTENDS); } List impls = classTree.getImplementsClause(); if (impls != null) { for (Tree im : impls) { validateTypeOf(im); + checkQualifiedLocation( + atypeFactory.getAnnotatedType(im), im, TypeUseLocation.IMPLEMENTS); } } super.visitClass(classTree, null); @@ -537,9 +549,13 @@ public Void visitMethod(MethodTree node, Void p) { // Passing the whole method/constructor validates the return type validateTypeOf(node); + checkQualifiedLocation( + atypeFactory.getMethodReturnType(node), node, TypeUseLocation.RETURN); // Validate types in throws clauses for (ExpressionTree thr : node.getThrows()) { + checkQualifiedLocation( + atypeFactory.getAnnotatedType(thr), thr, TypeUseLocation.THROWS); validateTypeOf(thr); } @@ -820,6 +836,8 @@ public Void visitTypeParameter(TypeParameterTree node, Void p) { @Override public Void visitVariable(VariableTree node, Void p) { + validateQualifiedLocationForVariableTree(node); + Pair preAssCtxt = visitorState.getAssignmentContext(); visitorState.setAssignmentContext( Pair.of((Tree) node, atypeFactory.getAnnotatedType(node))); @@ -844,6 +862,34 @@ public Void visitVariable(VariableTree node, Void p) { } } + private void validateQualifiedLocationForVariableTree(VariableTree node) { + AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node); + Element element = TreeUtils.elementFromDeclaration(node); + switch (element.getKind()) { + case FIELD: + checkQualifiedLocation(type, node, TypeUseLocation.FIELD); + break; + case LOCAL_VARIABLE: + checkQualifiedLocation(type, node, TypeUseLocation.LOCAL_VARIABLE); + break; + case RESOURCE_VARIABLE: + checkQualifiedLocation(type, node, TypeUseLocation.RESOURCE_VARIABLE); + break; + case EXCEPTION_PARAMETER: + checkQualifiedLocation(type, node, TypeUseLocation.EXCEPTION_PARAMETER); + break; + case PARAMETER: + if (element.getSimpleName().contentEquals("this")) { + checkQualifiedLocation(type, node, TypeUseLocation.RECEIVER); + } else { + checkQualifiedLocation(type, node, TypeUseLocation.PARAMETER); + } + break; + default: + break; + } + } + /** * Performs two checks: subtyping and assignability checks, using {@link * #commonAssignmentCheck(Tree, ExpressionTree, String)}. @@ -1231,6 +1277,8 @@ public Void visitNewClass(NewClassTree node, Void p) { checkTypeArguments(node, paramBounds, typeargs, node.getTypeArguments()); + checkQualifiedLocation(atypeFactory.getAnnotatedType(node), node, TypeUseLocation.NEW); + boolean valid = validateTypeOf(node); if (valid) { @@ -1499,6 +1547,8 @@ public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) { public Void visitNewArray(NewArrayTree node, Void p) { boolean valid = validateTypeOf(node); + checkQualifiedLocation(atypeFactory.getAnnotatedType(node), node, TypeUseLocation.NEW); + if (valid && node.getType() != null) { AnnotatedArrayType arrayType = atypeFactory.getAnnotatedType(node); if (atypeFactory.getDependentTypesHelper() != null) { @@ -1638,6 +1688,9 @@ private boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror @Override public Void visitTypeCast(TypeCastTree node, Void p) { + Tree castTree = node.getType(); + AnnotatedTypeMirror castType = atypeFactory.getAnnotatedType(castTree); + checkQualifiedLocation(castType, castTree, TypeUseLocation.CAST); // validate "node" instead of "node.getType()" to prevent duplicate errors. boolean valid = validateTypeOf(node) && validateTypeOf(node.getExpression()); if (valid) { @@ -1654,6 +1707,9 @@ public Void visitTypeCast(TypeCastTree node, Void p) { @Override public Void visitInstanceOf(InstanceOfTree node, Void p) { + Tree typeTree = node.getType(); + AnnotatedTypeMirror instanceOfType = atypeFactory.getAnnotatedType(typeTree); + checkQualifiedLocation(instanceOfType, typeTree, TypeUseLocation.INSTANCE_OF); validateTypeOf(node.getType()); return super.visitInstanceOf(node, p); } @@ -2598,6 +2654,52 @@ private boolean checkMethodReferenceInference( return false; } + protected void checkQualifiedLocation( + AnnotatedTypeMirror type, Tree tree, TypeUseLocation location) { + for (AnnotationMirror am : type.getAnnotations()) { + Element elementOfAnnotation = am.getAnnotationType().asElement(); + QualifiedLocations qualifiedLocations = + elementOfAnnotation.getAnnotation(QualifiedLocations.class); + // Null means no QualifiedLocations annotation => Any usage is correct. + if (qualifiedLocations == null) { + continue; + } + + Set set = new HashSet<>(Arrays.asList(qualifiedLocations.value())); + + if (set.contains(TypeUseLocation.ALL)) continue; + + if (set.contains(TypeUseLocation.LOWER_BOUND)) { + if (location == TypeUseLocation.EXPLICIT_LOWER_BOUND + || location == TypeUseLocation.IMPLICIT_LOWER_BOUND) { + // TypeUseLocation.LOWER_BOUND already covers both explicit and implicit lower + // bounds, so no need to check + continue; + } + } + + if (set.contains(TypeUseLocation.UPPER_BOUND)) { + // TypeUseLocation.UPPER_BOUND already covers both explicit and implicit upper + // bounds, so no need to check + if (location == TypeUseLocation.EXPLICIT_UPPER_BOUND + || location == TypeUseLocation.IMPLICIT_UPPER_BOUND) { + continue; + } + } + + if (!set.contains(location)) { + reportLocationError(type, tree, location); + } + } + } + + private void reportLocationError( + AnnotatedTypeMirror type, Tree tree, TypeUseLocation location) { + @SuppressWarnings("CompilerMessages") + @CompilerMessageKey String errorMessage = location.toString().toLowerCase() + ".annotation.forbidden"; + checker.report(Result.failure(errorMessage, type.getAnnotations(), type.toString()), tree); + } + /** * Class to perform method override and method reference checks. * diff --git a/framework/src/org/checkerframework/common/basetype/messages.properties b/framework/src/org/checkerframework/common/basetype/messages.properties index b8ba2794745f..8194887d0568 100644 --- a/framework/src/org/checkerframework/common/basetype/messages.properties +++ b/framework/src/org/checkerframework/common/basetype/messages.properties @@ -90,3 +90,26 @@ field.invariant.not.subtype=the qualifier for field %s is not a subtype of the d field.invariant.not.wellformed=the field invariant annotation does not have equal numbers of fields and qualifiers. field.invariant.not.found.superclass=the field invariant annotation is missing fields that are listed in the superclass field invariant.\nfields not found: %s field.invariant.not.subtype.superclass=the qualifier for field %s is not a subtype of the qualifier in the superclass field invariant\nfound: %s\nsuperclass type: %s + +field.annotation.forbidden= %s is forbidden on field! +local_variable.annotation.forbidden= %s is forbidden on local variable! +resource_variable.annotation.forbidden= %s is forbidden on resource variable! +exception_parameter.annotation.forbidden= %s is forbidden on exception parameter! +receiver.annotation.forbidden= %s is forbidden on method receiver! +parameter.annotation.forbidden= %s is forbidden on method parameter! +return.annotation.forbidden= %s is forbidden on method return type! +lower_bound.annotation.forbidden= %s is forbidden on lower bound! +explicit_lower_bound.annotation.forbidden= %s is forbidden on explicit lower bound! +implicit_lower_bound.annotation.forbidden= %s is forbidden on implicit lower bound! +upper_bound.annotation.forbidden= %s is forbidden on upper bound! +explicit_upper_bound.annotation.forbidden= %s is forbidden on explicit upper bound! +implicit_upper_bound.annotation.forbidden= %s is forbidden on implicit upper bound! +type_declaration.annotation.forbidden= %s is forbidden on type declaration! +type_argument.annotation.forbidden= %s is forbidden on type argument! +array_component.annotation.forbidden= %s is forbidden on array component! +extends.annotation.forbidden= %s is forbidden on extend clauses! +implements.annotation.forbidden= %s is forbidden on implement clauses! +new.annotation.forbidden= %s is forbidden on new instance creation! +throws.annotation.forbidden= %s is forbidden on throws! +instanceof.annotation.forbidden= %s is forbidden on instanceof type! +cast.annotation.forbidden= %s is forbidden on type cast! diff --git a/framework/src/org/checkerframework/framework/qual/QualifiedLocations.java b/framework/src/org/checkerframework/framework/qual/QualifiedLocations.java new file mode 100644 index 000000000000..f134f9e47ecb --- /dev/null +++ b/framework/src/org/checkerframework/framework/qual/QualifiedLocations.java @@ -0,0 +1,19 @@ +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) +/** + * Applied to the declaration of a type qualifier. It specifies the qualifier is qualified to be + * used on the specified locations. It will be enforced towards all kinds of annotation sources - + * explicitly written, implicit, defaulted etc. + */ +public @interface QualifiedLocations { + TypeUseLocation[] value(); +} diff --git a/framework/src/org/checkerframework/framework/qual/TypeUseLocation.java b/framework/src/org/checkerframework/framework/qual/TypeUseLocation.java index 7a50767265e4..ec4d707d2d40 100644 --- a/framework/src/org/checkerframework/framework/qual/TypeUseLocation.java +++ b/framework/src/org/checkerframework/framework/qual/TypeUseLocation.java @@ -81,6 +81,27 @@ public enum TypeUseLocation { */ IMPLICIT_UPPER_BOUND, + /** Apply default annotations to unannotated type declarations: {@code @HERE class Demo{}} */ + TYPE_DECLARATION, + + /** Represents extends location of a class or interface: {@code class B extends @HERE A {}} */ + EXTENDS, + + /** Represents implements location of a class: {@code class B implements @HERE I {}} */ + IMPLEMENTS, + + /** Represents throws location of a method: {@code void foo throws @HERE Exception {}} */ + THROWS, + + /** Represents instanceof location: {@code o instanceof @HERE Object {}} */ + INSTANCE_OF, + + /** Represents new expression location: {@code new @HERE Object()} */ + NEW, + + /** Represents casts location: {@code (@HERE Object)o} */ + CAST, + /** Apply if nothing more concrete is provided. TODO: clarify relation to ALL. */ OTHERWISE, diff --git a/framework/src/org/checkerframework/framework/util/BoundType.java b/framework/src/org/checkerframework/framework/util/BoundType.java new file mode 100644 index 000000000000..072c1e5d3635 --- /dev/null +++ b/framework/src/org/checkerframework/framework/util/BoundType.java @@ -0,0 +1,21 @@ +package org.checkerframework.framework.util; + +/** + * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an explicit + * lower bound (LOWER), or no explicit bounds (UNBOUNDED). + */ +public enum BoundType { + + /** Indicates an upper bounded type variable or wildcard */ + UPPER, + + /** Indicates a lower bounded type variable or wildcard */ + LOWER, + + /** + * Neither bound is specified, BOTH are implicit. (If a type variable is declared in bytecode + * and the type of the upper bound is Object, then the checker assumes that the bound was not + * explicitly written in source code.) + */ + UNBOUNDED +} diff --git a/framework/src/org/checkerframework/framework/util/BoundTypeUtil.java b/framework/src/org/checkerframework/framework/util/BoundTypeUtil.java new file mode 100644 index 000000000000..3572283bc6ea --- /dev/null +++ b/framework/src/org/checkerframework/framework/util/BoundTypeUtil.java @@ -0,0 +1,139 @@ +package org.checkerframework.framework.util; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeParameterTree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Type.WildcardType; +import java.util.List; +import java.util.Map; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeParameterElement; +import org.checkerframework.framework.type.AnnotatedTypeFactory; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable; +import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType; +import org.checkerframework.javacutil.CollectionUtils; +import org.checkerframework.javacutil.ErrorReporter; +import org.checkerframework.javacutil.TypesUtils; + +/** Utility class to get {@link BoundType} of a type variable or wildcard */ +public class BoundTypeUtil { + + /** Mapping from an Element to the source Tree of the declaration. */ + private static final int CACHE_SIZE = 300; + + protected static final Map elementToBoundType = + CollectionUtils.createLRUCache(CACHE_SIZE); + + public static boolean isOneOf(final BoundType target, final BoundType... choices) { + for (final BoundType choice : choices) { + if (target == choice) { + return true; + } + } + + return false; + } + + /** + * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or + * AnnotatedTypeVariable. + * @return the boundType for type + */ + public static BoundType getBoundType( + final AnnotatedTypeMirror type, final AnnotatedTypeFactory typeFactory) { + if (type instanceof AnnotatedTypeVariable) { + return getTypeVarBoundType((AnnotatedTypeVariable) type, typeFactory); + } + + if (type instanceof AnnotatedWildcardType) { + return getWildcardBoundType((AnnotatedWildcardType) type, typeFactory); + } + + ErrorReporter.errorAbort("Unexpected type kind: type=" + type); + return null; // dead code + } + + /** @return the bound type of the input typeVar */ + private static BoundType getTypeVarBoundType( + final AnnotatedTypeVariable typeVar, final AnnotatedTypeFactory typeFactory) { + return getTypeVarBoundType( + (TypeParameterElement) typeVar.getUnderlyingType().asElement(), typeFactory); + } + + /** @return the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem */ + // Results are cached in {@link elementToBoundType}. + private static BoundType getTypeVarBoundType( + final TypeParameterElement typeParamElem, final AnnotatedTypeFactory typeFactory) { + final BoundType prev = elementToBoundType.get(typeParamElem); + if (prev != null) { + return prev; + } + + TreePath declaredTypeVarEle = typeFactory.getTreeUtils().getPath(typeParamElem); + Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); + + final BoundType boundType; + if (typeParamDecl == null) { + // This is not only for elements from binaries, but also + // when the compilation unit is no-longer available. + if (typeParamElem.getBounds().size() == 1 + && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { + // If the bound was Object, then it may or may not have been explicitly written. + // Assume that it was not. + boundType = BoundType.UNBOUNDED; + } else { + // The bound is not Object, so it must have been explicitly written and thus the + // type variable has an upper bound. + boundType = BoundType.UPPER; + } + + } else { + if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { + final TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; + + List bnds = tptree.getBounds(); + if (bnds != null && !bnds.isEmpty()) { + boundType = BoundType.UPPER; + } else { + boundType = BoundType.UNBOUNDED; + } + } else { + ErrorReporter.errorAbort( + "Unexpected tree type for typeVar Element:\n" + + "typeParamElem=" + + typeParamElem + + "\n" + + typeParamDecl); + boundType = null; // dead code + } + } + + elementToBoundType.put(typeParamElem, boundType); + return boundType; + } + + /** + * @return the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to + * which its an argument. + */ + public static BoundType getWildcardBoundType( + final AnnotatedWildcardType annotatedWildcard, final AnnotatedTypeFactory typeFactory) { + + final WildcardType wildcard = (WildcardType) annotatedWildcard.getUnderlyingType(); + + final BoundType boundType; + if (wildcard.isUnbound() && wildcard.bound != null) { + boundType = + getTypeVarBoundType( + (TypeParameterElement) wildcard.bound.asElement(), typeFactory); + + } else { + // note: isSuperBound will be true for unbounded and lowers, but the unbounded case is + // already handled + boundType = wildcard.isSuperBound() ? BoundType.LOWER : BoundType.UPPER; + } + + return boundType; + } +} diff --git a/framework/src/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/org/checkerframework/framework/util/defaults/QualifierDefaults.java index 6d311459124c..b190922d0c35 100644 --- a/framework/src/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -7,10 +7,8 @@ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree; -import com.sun.source.tree.TypeParameterTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; -import com.sun.tools.javac.code.Type.WildcardType; import java.lang.annotation.Annotation; import java.util.EnumSet; import java.util.IdentityHashMap; @@ -21,7 +19,6 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeKind; import javax.lang.model.util.Elements; @@ -41,16 +38,16 @@ import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; import org.checkerframework.framework.type.QualifierHierarchy; import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; +import org.checkerframework.framework.util.BoundType; +import org.checkerframework.framework.util.BoundTypeUtil; import org.checkerframework.framework.util.CheckerMain; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; -import org.checkerframework.javacutil.CollectionUtils; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.TreeUtils; -import org.checkerframework.javacutil.TypesUtils; /** * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link @@ -87,12 +84,6 @@ public class QualifierDefaults { private final DefaultSet checkedCodeDefaults = new DefaultSet(); private final DefaultSet uncheckedCodeDefaults = new DefaultSet(); - /** Mapping from an Element to the source Tree of the declaration. */ - private static final int CACHE_SIZE = 300; - - protected static final Map elementToBoundType = - CollectionUtils.createLRUCache(CACHE_SIZE); - /** * Defaults that apply for a certain Element. On the one hand this is used for caching (an * earlier name for the field was "qualifierCache"). It can also be used by type systems to set @@ -931,7 +922,8 @@ public Void scan(AnnotatedTypeMirror t, AnnotationMirror qual) { case IMPLICIT_LOWER_BOUND: { if (isLowerBound - && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.UPPER)) { + && BoundTypeUtil.isOneOf( + boundType, BoundType.UNBOUNDED, BoundType.UPPER)) { addAnnotation(t, qual); } break; @@ -939,7 +931,7 @@ public Void scan(AnnotatedTypeMirror t, AnnotationMirror qual) { case EXPLICIT_LOWER_BOUND: { - if (isLowerBound && boundType.isOneOf(BoundType.LOWER)) { + if (isLowerBound && BoundTypeUtil.isOneOf(boundType, BoundType.LOWER)) { addAnnotation(t, qual); } break; @@ -956,14 +948,15 @@ public Void scan(AnnotatedTypeMirror t, AnnotationMirror qual) { case IMPLICIT_UPPER_BOUND: { if (isUpperBound - && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.LOWER)) { + && BoundTypeUtil.isOneOf( + boundType, BoundType.UNBOUNDED, BoundType.LOWER)) { addAnnotation(t, qual); } break; } case EXPLICIT_UPPER_BOUND: { - if (isUpperBound && boundType.isOneOf(BoundType.UPPER)) { + if (isUpperBound && BoundTypeUtil.isOneOf(boundType, BoundType.UPPER)) { addAnnotation(t, qual); } break; @@ -1046,7 +1039,7 @@ protected void visitBounds( final boolean prevIsLowerBound = isLowerBound; final BoundType prevBoundType = boundType; - boundType = getBoundType(boundedType, atypeFactory); + boundType = BoundTypeUtil.getBoundType(boundedType, atypeFactory); try { isLowerBound = true; @@ -1069,136 +1062,4 @@ protected void visitBounds( } } } - - /** - * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an - * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED). - */ - enum BoundType { - - /** Indicates an upper bounded type variable or wildcard */ - UPPER, - - /** Indicates a lower bounded type variable or wildcard */ - LOWER, - - /** - * Neither bound is specified, BOTH are implicit. (If a type variable is declared in - * bytecode and the type of the upper bound is Object, then the checker assumes that the - * bound was not explicitly written in source code.) - */ - UNBOUNDED; - - public boolean isOneOf(final BoundType... choices) { - for (final BoundType choice : choices) { - if (this == choice) { - return true; - } - } - - return false; - } - } - - /** - * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or - * AnnotatedTypeVariable. - * @return the boundType for type - */ - private static BoundType getBoundType( - final AnnotatedTypeMirror type, final AnnotatedTypeFactory typeFactory) { - if (type instanceof AnnotatedTypeVariable) { - return getTypeVarBoundType((AnnotatedTypeVariable) type, typeFactory); - } - - if (type instanceof AnnotatedWildcardType) { - return getWildcardBoundType((AnnotatedWildcardType) type, typeFactory); - } - - ErrorReporter.errorAbort("Unexpected type kind: type=" + type); - return null; // dead code - } - - /** @return the bound type of the input typeVar */ - private static BoundType getTypeVarBoundType( - final AnnotatedTypeVariable typeVar, final AnnotatedTypeFactory typeFactory) { - return getTypeVarBoundType( - (TypeParameterElement) typeVar.getUnderlyingType().asElement(), typeFactory); - } - - /** @return the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem */ - // Results are cached in {@link elementToBoundType}. - private static BoundType getTypeVarBoundType( - final TypeParameterElement typeParamElem, final AnnotatedTypeFactory typeFactory) { - final BoundType prev = elementToBoundType.get(typeParamElem); - if (prev != null) { - return prev; - } - - TreePath declaredTypeVarEle = typeFactory.getTreeUtils().getPath(typeParamElem); - Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf(); - - final BoundType boundType; - if (typeParamDecl == null) { - // This is not only for elements from binaries, but also - // when the compilation unit is no-longer available. - if (typeParamElem.getBounds().size() == 1 - && TypesUtils.isObject(typeParamElem.getBounds().get(0))) { - // If the bound was Object, then it may or may not have been explicitly written. - // Assume that it was not. - boundType = BoundType.UNBOUNDED; - } else { - // The bound is not Object, so it must have been explicitly written and thus the - // type variable has an upper bound. - boundType = BoundType.UPPER; - } - - } else { - if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) { - final TypeParameterTree tptree = (TypeParameterTree) typeParamDecl; - - List bnds = tptree.getBounds(); - if (bnds != null && !bnds.isEmpty()) { - boundType = BoundType.UPPER; - } else { - boundType = BoundType.UNBOUNDED; - } - } else { - ErrorReporter.errorAbort( - "Unexpected tree type for typeVar Element:\n" - + "typeParamElem=" - + typeParamElem - + "\n" - + typeParamDecl); - boundType = null; // dead code - } - } - - elementToBoundType.put(typeParamElem, boundType); - return boundType; - } - - /** - * @return the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to - * which its an argument. - */ - public static BoundType getWildcardBoundType( - final AnnotatedWildcardType annotatedWildcard, final AnnotatedTypeFactory typeFactory) { - - final WildcardType wildcard = (WildcardType) annotatedWildcard.getUnderlyingType(); - - final BoundType boundType; - if (wildcard.isUnbound() && wildcard.bound != null) { - boundType = - getTypeVarBoundType( - (TypeParameterElement) wildcard.bound.asElement(), typeFactory); - - } else { - // note: isSuperBound will be true for unbounded and lowers, but the unbounded case is - // already handled - boundType = wildcard.isSuperBound() ? BoundType.LOWER : BoundType.UPPER; - } - - return boundType; - } } diff --git a/framework/tests/qualifiedlocations/ArrayComponent.java b/framework/tests/qualifiedlocations/ArrayComponent.java new file mode 100644 index 000000000000..e6bba82013f4 --- /dev/null +++ b/framework/tests/qualifiedlocations/ArrayComponent.java @@ -0,0 +1,14 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class ArrayComponent { + //:: error: (array_component.annotation.forbidden) + @Bottom Object[] array; + //:: error: (array_component.annotation.forbidden) + @Bottom Number[] @Bottom [] twoDimensionArray; + //:: error: (array_component.annotation.forbidden) + @Bottom Object[] foo() { + return null; + } +} diff --git a/framework/tests/qualifiedlocations/Cast.java b/framework/tests/qualifiedlocations/Cast.java new file mode 100644 index 000000000000..8cb39f507252 --- /dev/null +++ b/framework/tests/qualifiedlocations/Cast.java @@ -0,0 +1,10 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Cast { + { + //:: error: (cast.annotation.forbidden) + ((@Bottom Object) new Object()).toString(); + } +} diff --git a/framework/tests/qualifiedlocations/ExceptionParameter.java b/framework/tests/qualifiedlocations/ExceptionParameter.java new file mode 100644 index 000000000000..0397da45f680 --- /dev/null +++ b/framework/tests/qualifiedlocations/ExceptionParameter.java @@ -0,0 +1,14 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class ExceptionParameter { + + void foo() { + try { + //:: error: (exception_parameter.annotation.forbidden) + } catch (@Bottom Exception e) { + + } + } +} diff --git a/framework/tests/qualifiedlocations/ExplicitLowerBound.java b/framework/tests/qualifiedlocations/ExplicitLowerBound.java new file mode 100644 index 000000000000..4c659e8b6250 --- /dev/null +++ b/framework/tests/qualifiedlocations/ExplicitLowerBound.java @@ -0,0 +1,12 @@ +package qualifiedlocations; + +import java.util.Set; +import testlib.qualifiedlocations.qual.Bottom; + +public class ExplicitLowerBound { + + //:: error: (explicit_lower_bound.annotation.forbidden) + Set foo() { + return null; + } +} diff --git a/framework/tests/qualifiedlocations/ExplicitUpperBound.java b/framework/tests/qualifiedlocations/ExplicitUpperBound.java new file mode 100644 index 000000000000..3ca77d0d95e5 --- /dev/null +++ b/framework/tests/qualifiedlocations/ExplicitUpperBound.java @@ -0,0 +1,12 @@ +package qualifiedlocations; + +import java.util.Set; +import testlib.qualifiedlocations.qual.Bottom; + +//:: error: (explicit_upper_bound.annotation.forbidden) :: error: (implicit_lower_bound.annotation.forbidden) +public class ExplicitUpperBound { + //:: error: (explicit_upper_bound.annotation.forbidden) :: error: (implicit_lower_bound.annotation.forbidden) + Set foo() { + return null; + } +} diff --git a/framework/tests/qualifiedlocations/Extends.java b/framework/tests/qualifiedlocations/Extends.java new file mode 100644 index 000000000000..1f067b953da2 --- /dev/null +++ b/framework/tests/qualifiedlocations/Extends.java @@ -0,0 +1,6 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +//:: error: (extends.annotation.forbidden) :: error: (type_declaration.annotation.forbidden) :: error: (type.invalid) +public class Extends extends @Bottom Object {} diff --git a/framework/tests/qualifiedlocations/Field.java b/framework/tests/qualifiedlocations/Field.java new file mode 100644 index 000000000000..a3217ea76507 --- /dev/null +++ b/framework/tests/qualifiedlocations/Field.java @@ -0,0 +1,8 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Field { + //:: error: (field.annotation.forbidden) + @Bottom Object o; +} diff --git a/framework/tests/qualifiedlocations/Implements.java b/framework/tests/qualifiedlocations/Implements.java new file mode 100644 index 000000000000..c37428d8dbd4 --- /dev/null +++ b/framework/tests/qualifiedlocations/Implements.java @@ -0,0 +1,14 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +interface Test { + void foo(); +} + +//:: error: (implements.annotation.forbidden) :: error: (type_declaration.annotation.forbidden) :: error: (type.invalid) +public class Implements implements @Bottom Test { + public void foo() { + return; + } +} diff --git a/framework/tests/qualifiedlocations/ImplicitLowerBound.java b/framework/tests/qualifiedlocations/ImplicitLowerBound.java new file mode 100644 index 000000000000..da9eaa7a29eb --- /dev/null +++ b/framework/tests/qualifiedlocations/ImplicitLowerBound.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.TypeUseLocation; +import testlib.qualifiedlocations.qual.Top; + +@DefaultQualifier(value = Top.class, locations = TypeUseLocation.UPPER_BOUND) +//:: error: (implicit_lower_bound.annotation.forbidden) +public class ImplicitLowerBound {} diff --git a/framework/tests/qualifiedlocations/ImplicitUpperBound.java b/framework/tests/qualifiedlocations/ImplicitUpperBound.java new file mode 100644 index 000000000000..dd2e37c73463 --- /dev/null +++ b/framework/tests/qualifiedlocations/ImplicitUpperBound.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.TypeUseLocation; +import testlib.qualifiedlocations.qual.Bottom; + +@DefaultQualifier(value = Bottom.class, locations = TypeUseLocation.UPPER_BOUND) +//:: error: (implicit_upper_bound.annotation.forbidden) :: error: (implicit_lower_bound.annotation.forbidden) +public class ImplicitUpperBound {} diff --git a/framework/tests/qualifiedlocations/InstanceOf.java b/framework/tests/qualifiedlocations/InstanceOf.java new file mode 100644 index 000000000000..155146c360f3 --- /dev/null +++ b/framework/tests/qualifiedlocations/InstanceOf.java @@ -0,0 +1,11 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class InstanceOf { + + void foo(Object o) { + //:: error: (instanceof.annotation.forbidden) + if (o instanceof @Bottom Object) {} + } +} diff --git a/framework/tests/qualifiedlocations/LocalVariable.java b/framework/tests/qualifiedlocations/LocalVariable.java new file mode 100644 index 000000000000..14d39eed6c24 --- /dev/null +++ b/framework/tests/qualifiedlocations/LocalVariable.java @@ -0,0 +1,11 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class LocalVariable { + + void foo() { + //:: error: (local_variable.annotation.forbidden) + @Bottom Object lo; + } +} diff --git a/framework/tests/qualifiedlocations/Locations.java b/framework/tests/qualifiedlocations/Locations.java new file mode 100644 index 000000000000..b790ac3547cd --- /dev/null +++ b/framework/tests/qualifiedlocations/Locations.java @@ -0,0 +1,40 @@ +package qualifiedlocations; + +import java.util.ArrayList; +import java.util.List; +import testlib.qualifiedlocations.qual.Bottom; + +//:: error: (type_declaration.annotation.forbidden) :: error: (type_argument.annotation.forbidden) :: error: (explicit_upper_bound.annotation.forbidden) :: error: (implicit_lower_bound.annotation.forbidden) :: error: (type.invalid) +public class Locations> + //:: error: (type_argument.annotation.forbidden) :: error: (extends.annotation.forbidden) :: error: (implements.annotation.forbidden) + extends @Bottom ArrayList<@Bottom Object> implements @Bottom Iterable<@Bottom Object> { + //:: error: (field.annotation.forbidden) + @Bottom T t; + //:: error: (field.annotation.forbidden) :: error: (type_argument.annotation.forbidden) + @Bottom List<@Bottom ArrayList<@Bottom String>> l; + //:: error: (type_argument.annotation.forbidden) :: error: (array_component.annotation.forbidden) + List<@Bottom String @Bottom [] @Bottom []> + f; // It's strange that array component doesn't show error + + //:: error: (throws.annotation.forbidden) + void foo() throws @Bottom Exception { + //:: error: (local_variable.annotation.forbidden) :: error: (type_argument.annotation.forbidden) :: error: (new.annotation.forbidden) + @Bottom Object l = new @Bottom ArrayList<@Bottom Object>(); + //:: error: (instanceof.annotation.forbidden) :: error: (cast.annotation.forbidden) :: error: (array_component.annotation.forbidden) + boolean b = (@Bottom Object) l instanceof @Bottom Object @Bottom []; + } + + //:: error: (parameter.annotation.forbidden) + void bar(@Bottom Object p) { + try { + foo(); + //:: error: (exception_parameter.annotation.forbidden) + } catch (@Bottom Exception e) { + } + } + + //:: error: (return.annotation.forbidden) :: error: (explicit_upper_bound.annotation.forbidden) :: error: (type_argument.annotation.forbidden) :: error: (receiver.annotation.forbidden) :: error: (implicit_lower_bound.annotation.forbidden) + > @Bottom Object hey(Locations<@Bottom T> this, S s) { + return null; + } +} diff --git a/framework/tests/qualifiedlocations/New.java b/framework/tests/qualifiedlocations/New.java new file mode 100644 index 000000000000..4ebd169de123 --- /dev/null +++ b/framework/tests/qualifiedlocations/New.java @@ -0,0 +1,13 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; +import testlib.qualifiedlocations.qual.Top; + +public class New { + { + //:: error: (new.annotation.forbidden) + new @Bottom Object(); + //:: error: (new.annotation.forbidden) + int a = new @Top String @Bottom [] {"string"}.length; + } +} diff --git a/framework/tests/qualifiedlocations/Parameter.java b/framework/tests/qualifiedlocations/Parameter.java new file mode 100644 index 000000000000..07c59e257253 --- /dev/null +++ b/framework/tests/qualifiedlocations/Parameter.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Parameter { + + //:: error: (parameter.annotation.forbidden) + void foo(@Bottom Object p) {} +} diff --git a/framework/tests/qualifiedlocations/Receiver.java b/framework/tests/qualifiedlocations/Receiver.java new file mode 100644 index 000000000000..cd9715ec6405 --- /dev/null +++ b/framework/tests/qualifiedlocations/Receiver.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Receiver { + + //:: error: (receiver.annotation.forbidden) + void foo(@Bottom Receiver this) {} +} diff --git a/framework/tests/qualifiedlocations/Return.java b/framework/tests/qualifiedlocations/Return.java new file mode 100644 index 000000000000..841e3ab5c41d --- /dev/null +++ b/framework/tests/qualifiedlocations/Return.java @@ -0,0 +1,11 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Return { + + //:: error: (return.annotation.forbidden) + @Bottom Object foo() { + return null; + } +} diff --git a/framework/tests/qualifiedlocations/Throws.java b/framework/tests/qualifiedlocations/Throws.java new file mode 100644 index 000000000000..af09a3839ea0 --- /dev/null +++ b/framework/tests/qualifiedlocations/Throws.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +public class Throws { + + //:: error: (throws.annotation.forbidden) + void foo() throws @Bottom Exception {} +} diff --git a/framework/tests/qualifiedlocations/TypeArgument.java b/framework/tests/qualifiedlocations/TypeArgument.java new file mode 100644 index 000000000000..73cd509c6c89 --- /dev/null +++ b/framework/tests/qualifiedlocations/TypeArgument.java @@ -0,0 +1,9 @@ +package qualifiedlocations; + +import java.util.List; +import testlib.qualifiedlocations.qual.Bottom; + +public class TypeArgument { + //:: error: (type_argument.annotation.forbidden) + List<@Bottom Integer> list; +} diff --git a/framework/tests/qualifiedlocations/TypeDeclaration.java b/framework/tests/qualifiedlocations/TypeDeclaration.java new file mode 100644 index 000000000000..e8dcc591e026 --- /dev/null +++ b/framework/tests/qualifiedlocations/TypeDeclaration.java @@ -0,0 +1,6 @@ +package qualifiedlocations; + +import testlib.qualifiedlocations.qual.Bottom; + +//:: error: (type_declaration.annotation.forbidden) :: error: (type.invalid) +@Bottom public class TypeDeclaration {} diff --git a/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsAnnotatedTypeFactory.java b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsAnnotatedTypeFactory.java new file mode 100644 index 000000000000..cc35605a06e5 --- /dev/null +++ b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsAnnotatedTypeFactory.java @@ -0,0 +1,33 @@ +package testlib.qualifiedlocations; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.javacutil.AnnotationUtils; +import testlib.qualifiedlocations.qual.Bottom; +import testlib.qualifiedlocations.qual.Top; + +/** Created by mier on 05/07/17. */ +public class QualifiedLocationsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { + + public AnnotationMirror TOP, BOTTOM; + + public QualifiedLocationsAnnotatedTypeFactory(BaseTypeChecker checker) { + super(checker); + TOP = AnnotationUtils.fromClass(elements, Top.class); + BOTTOM = AnnotationUtils.fromClass(elements, Bottom.class); + postInit(); + } + + @Override + protected Set> createSupportedTypeQualifiers() { + Set> annotations = + new HashSet<>(Arrays.asList(Top.class, Bottom.class)); + return Collections.unmodifiableSet(annotations); + } +} diff --git a/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsChecker.java b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsChecker.java new file mode 100644 index 000000000000..9fc19bd2d5aa --- /dev/null +++ b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsChecker.java @@ -0,0 +1,6 @@ +package testlib.qualifiedlocations; + +import org.checkerframework.common.basetype.BaseTypeChecker; + +/** Created by mier on 05/07/17. */ +public class QualifiedLocationsChecker extends BaseTypeChecker {} diff --git a/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsVisitor.java b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsVisitor.java new file mode 100644 index 000000000000..1650f2d9ff76 --- /dev/null +++ b/framework/tests/src/testlib/qualifiedlocations/QualifiedLocationsVisitor.java @@ -0,0 +1,25 @@ +package testlib.qualifiedlocations; + +import com.sun.source.tree.TypeCastTree; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import org.checkerframework.common.basetype.BaseTypeChecker; +import org.checkerframework.common.basetype.BaseTypeVisitor; + +/** Created by mier on 05/07/17. */ +public class QualifiedLocationsVisitor + extends BaseTypeVisitor { + public QualifiedLocationsVisitor(BaseTypeChecker checker) { + super(checker); + } + + @Override + protected void checkTypecastSafety(TypeCastTree node, Void p) { + return; + } + + @Override + protected Set getExceptionParameterLowerBoundAnnotations() { + return atypeFactory.getQualifierHierarchy().getBottomAnnotations(); + } +} diff --git a/framework/tests/src/testlib/qualifiedlocations/qual/Bottom.java b/framework/tests/src/testlib/qualifiedlocations/qual/Bottom.java new file mode 100644 index 000000000000..115a096e5966 --- /dev/null +++ b/framework/tests/src/testlib/qualifiedlocations/qual/Bottom.java @@ -0,0 +1,20 @@ +package testlib.qualifiedlocations.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.ImplicitFor; +import org.checkerframework.framework.qual.LiteralKind; +import org.checkerframework.framework.qual.QualifiedLocations; +import org.checkerframework.framework.qual.SubtypeOf; + +/** Created by mier on 05/07/17. */ +@SubtypeOf({Top.class}) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@QualifiedLocations({}) +@ImplicitFor(literals = {LiteralKind.NULL}) +public @interface Bottom {} diff --git a/framework/tests/src/testlib/qualifiedlocations/qual/Top.java b/framework/tests/src/testlib/qualifiedlocations/qual/Top.java new file mode 100644 index 000000000000..29b84b05a7b6 --- /dev/null +++ b/framework/tests/src/testlib/qualifiedlocations/qual/Top.java @@ -0,0 +1,17 @@ +package testlib.qualifiedlocations.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.SubtypeOf; + +/** Created by mier on 05/07/17. */ +@SubtypeOf({}) +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@DefaultQualifierInHierarchy +public @interface Top {} diff --git a/framework/tests/src/tests/QualifiedLocationsTest.java b/framework/tests/src/tests/QualifiedLocationsTest.java new file mode 100644 index 000000000000..09036a3eee15 --- /dev/null +++ b/framework/tests/src/tests/QualifiedLocationsTest.java @@ -0,0 +1,20 @@ +package tests; + +import java.io.File; +import java.util.List; +import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; +import testlib.qualifiedlocations.QualifiedLocationsChecker; + +/** Created by mier on 06/07/17. */ +public class QualifiedLocationsTest extends CheckerFrameworkPerDirectoryTest { + + public QualifiedLocationsTest(List testFiles) { + super(testFiles, QualifiedLocationsChecker.class, "qualifiedlocations", "-Anomsgtext"); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"qualifiedlocations"}; + } +}