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 extends Tree> 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 extends Tree> 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 extends Tree> 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 super @Bottom Object> 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 extends @Bottom Object> 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 extends AnnotationMirror> 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"};
+ }
+}