Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qualified locations #60

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
7 changes: 7 additions & 0 deletions framework/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,13 @@
</antcall>
</target>

<target name="qualifiedlocations-tests" depends="jar,build-tests"
description="Run tests for the QualifiedLocations Checker ">
<antcall target="-run-tests">
<param name="param" value="tests.QualifiedLocationsTest"/>
</antcall>
</target>

<target name="reflection-tests" depends="jar,build-tests"
description="Run tests for reflection resolution">
<antcall target="-run-tests">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
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;
import javax.lang.model.element.AnnotationMirror;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<AnnotationMirror> onVar = type.getAnnotations();
if (!onVar.isEmpty()) {
Expand Down Expand Up @@ -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<AnnotationMirror> onVar = type.getAnnotations();
if (!onVar.isEmpty()) {
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -317,19 +320,28 @@ 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.
*/
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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -820,6 +836,8 @@ public Void visitTypeParameter(TypeParameterTree node, Void p) {

@Override
public Void visitVariable(VariableTree node, Void p) {
validateQualifiedLocationForVariableTree(node);

Pair<Tree, AnnotatedTypeMirror> preAssCtxt = visitorState.getAssignmentContext();
visitorState.setAssignmentContext(
Pair.of((Tree) node, atypeFactory.getAnnotatedType(node)));
Expand All @@ -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)}.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down Expand Up @@ -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<TypeUseLocation> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
21 changes: 21 additions & 0 deletions framework/src/org/checkerframework/framework/util/BoundType.java
Original file line number Diff line number Diff line change
@@ -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
}
Loading