package checkers.javari;
import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import com.sun.source.tree.*;
import com.sun.source.util.SimpleTreeVisitor;
import checkers.javari.quals.*;
import checkers.util.*;
import checkers.types.*;
import checkers.types.visitors.AnnotatedTypeScanner;
import checkers.types.visitors.SimpleAnnotatedTypeScanner;
import static checkers.types.AnnotatedTypeMirror.*;
/**
* Adds implicit and default Javari annotations, only if the user does not
* annotate the type explicitly. The default annotations are designed to
* minimize the need to write {@code ReadOnly} in the source code.
* <p>
*
* <b>Implicit Annotations:</b>
* All literals are implicitly treated as {@code Mutable}, including the
* null literal. While they are indeed immutable, this implicit type helps
* interfacing with non-annotated libraries.
* <p>
*
* <b>Default Annotations:</b>
*
* <ul>
* <li>
* This factory will add the {@link ReadOnly} annotation to a type if the
* tree or element is
* <ol>
* <li value="1">a use of a known ReadOnly class (i.e. class whose declaration
* is annotated with {@code ReadOnly}, including a method receiver of a
* ReadOnly class, or
* <li value="2">the upper bound type of a type parameter declaration, or a
* wildcard appearing on a class or method declaration
* <li value="3">an access of a {@link ThisMutable} field using a ReadOnly
* reference (e.g. {@code readOnlyRef.thisMutableField} with the obvious
* declarations)
* </ol>
*
* <li>
* This factory will add the {@link ThisMutable} annotation to a type if the
* input is a field of a mutable class.
*
* <li>
* In all other cases, the {@link Mutable} annotation is inserted by default.
* </ul>
*/
public class JavariAnnotatedTypeFactory extends AnnotatedTypeFactory {
/** Adds annotations from tree context before type resolution. */
private final JavariTreePreAnnotator treePre;
/** Adds annotations from the resulting type after type resolution. */
private final JavariTypePostAnnotator typePost;
/** The Javari annotations. */
private final AnnotationMirror READONLY, THISMUTABLE, MUTABLE, POLYREAD, QREADONLY;
/**
* Creates a new {@link JavariAnnotatedTypeFactory} that operates on a
* particular AST.
*
* @param checker the checker to which this factory belongs
* @param root the AST on which this type factory operates
*/
public JavariAnnotatedTypeFactory(JavariChecker checker,
CompilationUnitTree root) {
super(checker, root);
this.treePre = new JavariTreePreAnnotator();
this.typePost = new JavariTypePostAnnotator();
this.READONLY = checker.READONLY;
this.THISMUTABLE = checker.THISMUTABLE;
this.MUTABLE = checker.MUTABLE;
this.POLYREAD = checker.POLYREAD;
this.QREADONLY = checker.QREADONLY;
}
/**
* Returns the annotation specifying the immutability type of {@code type}.
*/
private AnnotationMirror getImmutabilityAnnotation(/*@ReadOnly*/ AnnotatedTypeMirror type) {
if (!type.isAnnotated())
return null;
return type.getAnnotations().iterator().next();
}
/**
* @param type an annotated type mirror
* @return true iff the type is specified an immutability type
* other than this-mutable, false otherwise
*/
public boolean hasImmutabilityAnnotation(/*@ReadOnly*/ AnnotatedTypeMirror type) {
return type != null && getImmutabilityAnnotation(type) != null;
}
/**
* Adds implicit annotations to a qualified type, based on its
* tree, as follows:
*
* <ul>
*
* <li> 1. Resolves qualified types from MemberSelectTree,
* inheritting from the expression to the identifier if the
* identifier is {@code @ThisMutable}.
*
* <li> 2. Qualified class types without annotations receive the
* {@code @Mutable} annotation.
*
* <li> 3. Qualified executable types receivers without
* annotations are annotated with the qualified executable type
* owner's annotation.
*
* <li> 4. Qualified executable types parameters and return
* values without annotations are annotated with {@code @Mutable}.
*
* <li> 5. Qualified declared types are annotated with their
* underlying type's element annotations.
*
* <li> 6. Qualified types whose elements correspond to fields,
* and all its subtypes, are annotated with {@code @ReadOnly},
* {@code @Mutable} or {@code @PolyRead}, according to the
* qualified type of {@code this}.
*
* </ul>
*
* @param tree an AST node
* @param type the type obtained from {@code tree}
*/
@Override
protected void annotateImplicit(Tree tree, /*@Mutable*/ AnnotatedTypeMirror type) {
// primitives are all the same
if (type.getKind().isPrimitive()
&& !hasImmutabilityAnnotation(type)) {
type.addAnnotation(MUTABLE);
return;
}
// 1 and 2
treePre.visit(tree, type);
// 3, 4 and 5
Element elt = InternalUtils.symbol(tree);
typePost.visit(type, elt != null ? elt.getKind() : ElementKind.OTHER);
// 6 - resolve ThisMutable from fields
if (type.hasAnnotation(THISMUTABLE)) {
AnnotatedDeclaredType selfType = getSelfType(tree);
if (selfType != null) {
if (selfType.hasAnnotation(POLYREAD))
new AnnotatedTypeReplacer(THISMUTABLE, POLYREAD).visit(type);
else if (selfType.hasAnnotation(MUTABLE))
new AnnotatedTypeReplacer(THISMUTABLE, MUTABLE).visit(type);
}
}
}
/**
* Convenience method for annotating two corresponding iterables.
* Both arguments must iterate through the same number of elements.
*/
protected void annotateImplicit(Iterable<? extends Tree> trees,
Iterable<? extends AnnotatedTypeMirror> types) {
Iterator<? extends Tree> iTree = trees.iterator();
Iterator<? extends AnnotatedTypeMirror> iType = types.iterator();
while (iTree.hasNext()) {
assert iType.hasNext();
annotateImplicit(iTree.next(), iType.next());
}
assert !iType.hasNext();
}
/**
* Adds annotations to qualified types according to their provided
* element, as follows:
*
* <ul>
*
* <li> 1. Qualified class types without annotations
* corresponding to class or interface elements receive the
* {@code @Mutable} annotation.
*
* <li> 2. Unannotated receivers of qualified executable types
* are annotated with the qualified type owner's annotation.
*
* <li> 3. Unannotated qualified declared types are annotated
* with their underlying type's element annotations.
*
* <li> 4. Qualified types whose elements correspond to fields,
* and all its subtypes, are annotated with {@code @ReadOnly} or
* {@code @ThisMutable}, according to the supertype.
*
* </ul>
*
* @param element an element
* @param type the type obtained from {@code elt}
*/
@Override
protected void annotateImplicit(Element element, /*@Mutable*/ AnnotatedTypeMirror type) {
if (element.getKind().isClass() || element.getKind().isInterface()) {
if (!hasImmutabilityAnnotation(type))
type.addAnnotation(MUTABLE);
}
typePost.visit(type, element.getKind());
}
protected void postDirectSuperTypes(AnnotatedTypeMirror type,
List<? extends AnnotatedTypeMirror> supertypes) {
super.postDirectSuperTypes(type, supertypes);
for (AnnotatedTypeMirror supertype : supertypes)
typePost.visit(supertype, ElementKind.OTHER);
}
/**
* Returns a singleton collection with the most restrictive immutability
* annotation that is a supertype of the annotations on both collections.
*/
@Override
public Collection<AnnotationMirror> unify(Collection<AnnotationMirror> c1,
Collection<AnnotationMirror> c2) {
Map<String, AnnotationMirror> ann =
new HashMap<String, AnnotationMirror>();
for (AnnotationMirror anno : c1)
ann.put(AnnotationUtils.annotationName(anno).toString(), anno);
for (AnnotationMirror anno : c2)
ann.put(AnnotationUtils.annotationName(anno).toString(), anno);
if (ann.containsKey(QReadOnly.class.getCanonicalName()))
return Collections.singleton(QREADONLY);
else if (ann.containsKey(ReadOnly.class.getCanonicalName()))
return Collections.singleton(READONLY);
else if (ann.containsKey(PolyRead.class.getCanonicalName()))
return Collections.singleton(POLYREAD);
else
return Collections.singleton(MUTABLE);
}
/**
* Determines the type of the constructed object based on the
* parameters passed to the constructor. The new object has the
* same mutability as the annotation marked on the constructor
* receiver, as resolved by this method.
*
* {@code @PolyRead} receiver values are resolved by looking at
* the mutability of any parameters marked as {@code @PolyRead}. The rules
* are similar to the ones applicable to
* method invocation resolution, but without looking at {@code this}.
*
* <ul>
*
* <li> 1. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} arguments, the receiver value is resolved as
* {@code @Mutable}.
*
* <li> 2. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} or {@code @ThisMutable} arguments and the
* condition above is not satisfied, the receiver value is
* resolved as {@code @ThisMutable}.
*
* <li> 3. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} or {@code @PolyRead} arguments and none of
* the condition above is satisfied, the receiver value is
* resolved as {@code @PolyRead}.
*
* <li> 4. If none of the conditions above is satisfied, the
* receiver value is resolved as {@code @ReadOnly}.
*
* </ul>
*
* @param tree the new class tree
* @return AnnotatedExecutableType corresponding to the type being
* constructed, with the resolved type on its receiver.
*/
@Override
public AnnotatedExecutableType constructorFromUse(NewClassTree tree) {
AnnotatedExecutableType exType = super.constructorFromUse(tree);
List<AnnotatedTypeMirror> argumentTypes = atypes.getAnnotatedTypes(tree.getArguments());
List<AnnotatedTypeMirror> parameterTypes = atypes.expandVarArgs(exType, tree.getArguments());
boolean allMutable = true, allPolyRead = true, allThisMutable = true;
// look at parameters and arguments
for (int i = 0; i < parameterTypes.size(); i++) {
AnnotatedTypeMirror pType = parameterTypes.get(i);
if (pType.hasAnnotation(POLYREAD)) {
AnnotatedTypeMirror aType = argumentTypes.get(i);
if (aType.hasAnnotation(THISMUTABLE) || aType.hasAnnotation(POLYREAD))
allMutable = false;
if (aType.hasAnnotation(READONLY) || aType.hasAnnotation(QREADONLY)) {
allMutable = false; allThisMutable = false;
}
if (!(aType.hasAnnotation(POLYREAD)
&& !aType.hasAnnotation(READONLY)
&& !aType.hasAnnotation(THISMUTABLE)
&& !aType.hasAnnotation(QREADONLY)))
allPolyRead = false;
}
}
// replacement: annotation to be put in place of @PolyRead
AnnotationMirror replacement;
if (allMutable) replacement = MUTABLE; // case 1
else if (allThisMutable) replacement = THISMUTABLE; // case 2
else if (allPolyRead) replacement = POLYREAD; // case 3
else replacement = READONLY; // case 4
if (replacement != POLYREAD) // do not replace if replacement is also @PolyRead
new AnnotatedTypeReplacer(POLYREAD, replacement).visit(exType);
return exType;
}
/**
* Determines the type of the invoked method based on the passed method
* invocation tree.
*
* Invokes the super method, then resolves annotations {@code @PolyRead}
* at the raw level on return values by looking at the
* mutability of any parameters marked as {@code @PolyRead}. For
* this purpose, a {@code @PolyRead} annotation on the receiver
* counts as if {@code this} were being passed as an argument to a
* parameter marked as {@code @PolyRead}.
*
* <ul>
*
* <li> 1. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} arguments, the return value is resolved as
* {@code @Mutable}.
*
* <li> 2. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} or {@code @ThisMutable} arguments and the
* condition above is not satisfied, the return value is resolved
* as {@code @ThisMutable}.
*
* <li> 3. If all parameters marked as {@code @PolyRead} receive
* {@code @Mutable} or {@code @PolyRead} arguments and none of
* the condition above is satisfied, the return value is resolved
* as {@code @PolyRead}.
*
* <li> 4. If none of the conditions above is satisfied, the
* return value is resolved as {@code @ReadOnly}.
*
* </ul>
*
* @param tree the method invocation tree
* @return AnnotatedExecutableType with return value resolved as described.
*/
@Override
public AnnotatedExecutableType methodFromUse(MethodInvocationTree tree) {
AnnotatedExecutableType type = super.methodFromUse(tree);
ExecutableElement executableElt = type.getElement();
AnnotatedTypeMirror returnType = type.getReturnType();
List<AnnotatedTypeMirror> argumentTypes = atypes.getAnnotatedTypes(tree.getArguments());
List<AnnotatedTypeMirror> parameterTypes = atypes.expandVarArgs(type, tree.getArguments());
AnnotatedTypeMirror receiverType = type.getReceiverType();
boolean allMutable = true, allPolyRead = true, allThisMutable = true;
// look at parameters and arguments
// TODO: get method properly
for (int i = 0; i < parameterTypes.size(); i++) {
AnnotatedTypeMirror pType = parameterTypes.get(i);
// look at it if parameter is PolyRead
if (pType.hasAnnotation(POLYREAD)) {
AnnotatedTypeMirror aType = argumentTypes.get(i);
if (aType.hasAnnotation(THISMUTABLE) || aType.hasAnnotation(POLYREAD))
allMutable = false;
if (aType.hasAnnotation(READONLY) || aType.hasAnnotation(QREADONLY)) {
allMutable = false; allThisMutable = false;
}
if (!(aType.hasAnnotation(POLYREAD)
&& !aType.hasAnnotation(READONLY)
&& !aType.hasAnnotation(THISMUTABLE)
&& !aType.hasAnnotation(QREADONLY)))
allPolyRead = false;
}
}
// look at receiver type and reference
if (receiverType.hasAnnotation(POLYREAD)) {
// if MemberSelectTree, we can just look at the expression tree
ExpressionTree exprTree = tree.getMethodSelect();
AnnotatedTypeMirror exprReceiver = this.getReceiver(exprTree);
if (exprReceiver.hasAnnotation(READONLY)) {
allMutable = false;
allThisMutable = false;
allPolyRead = false;
} else if (exprReceiver.hasAnnotation(POLYREAD)) {
allMutable = false;
}
}
// replacement: annotation to be put in place of @PolyRead
AnnotationMirror replacement;
if (allMutable) replacement = MUTABLE; // case 1
else if (allThisMutable) replacement = THISMUTABLE; // case 2
else if (allPolyRead) replacement = POLYREAD; // case 3
else replacement = READONLY; // case 4
if (replacement != POLYREAD && returnType.hasAnnotation(POLYREAD))
new AnnotatedTypeReplacer(POLYREAD, replacement).visit(type);
return type;
}
/**
* We modify this callback method to replace {@code @ThisMutable}
* implicit annotations with the qualified supertype annotation,
* if the owner doesn't have a {@code @ReadOnly} annotation.
* <p>
* Note on the given example that, if {@code @ThisMutable tmObject}
* were resolved as {@code @ReadOnly tmObject}, the code snippet
* would be legal. Such a class could then be created to obtain
* {@code @Mutable} access to {@code tmObject} from a {@code @ReadOnly}
* reference to it, without typechecker errors.
*
* <pre>@PolyRead Object breakJavari(@PolyRead Object s) @ReadOnly {
* tmObject = s;
* return null;
* }
* </pre>
*
* @param type the annotated type of the element
* @param owner the annotated type of the receiver of the accessing tree
* @param element the element of the field or method
*/
@Override
public void postAsMemberOf(AnnotatedTypeMirror type,
AnnotatedTypeMirror owner, Element element) {
if (!owner.hasAnnotation(READONLY)) {
final AnnotationMirror ownerAnno = getImmutabilityAnnotation(owner);
if (ownerAnno != THISMUTABLE)
new AnnotatedTypeReplacer(THISMUTABLE, ownerAnno).visit(type);
}
}
/**
* A visitor to get annotations from a tree.
*
* Annotations obtained from a MemberSelectTree are added to the parameter p.
*/
private class JavariTreePreAnnotator extends SimpleTreeVisitor<Void, AnnotatedTypeMirror> {
/**
* Selects the appropriate annotation from the identifier, or
* inherits it from the expression, as follows:
*
* <ul>
*
* <li> 1. If the identifier qualified type is annotated with
* {@code @ReadOnly} or {@code @Mutable}, the parameter
* receives the same annotation.
*
* <li> 2. If not, and if the expression qualified type has
* any annotation, the parameter receives those annotations.
*
* <li> 3. If the expression qualified type has no
* annotation, then the parameter receives a {@code
* @ThisMutable} annotation.
*
* </ul>
*
* @param node MemberSelectTree to be analyzed, of the form {@code expression . identifier}
* @param p AnnotatedTypeMirror parameter to be annotated
*/
@Override
public Void visitMemberSelect(MemberSelectTree node, AnnotatedTypeMirror p) {
AnnotatedTypeMirror exType = getAnnotatedType(node.getExpression());
AnnotatedTypeMirror idType = fromElement(TreeUtils.elementFromUse(node));
p.removeAnnotations(p.getAnnotations());
if (idType.hasAnnotation(READONLY)) // case 1
p.addAnnotation(READONLY);
else if (idType.hasAnnotation(MUTABLE)) // case 1
p.addAnnotation(MUTABLE);
else if (hasImmutabilityAnnotation(exType)) // case 2
p.addAnnotation(getImmutabilityAnnotation(exType));
else // case 3
p.addAnnotation(THISMUTABLE);
return super.visitMemberSelect(node, p);
}
/**
* Inserts {@code @Mutable} annotations on null literal trees.
*/
@Override
public Void visitLiteral(LiteralTree node, AnnotatedTypeMirror p) {
if (node.getKind() == Tree.Kind.NULL_LITERAL)
p.addAnnotation(MUTABLE);
return super.visitLiteral(node, p);
}
@Override
public Void visitNewClass(NewClassTree node, AnnotatedTypeMirror p) {
assert p.getKind() == TypeKind.DECLARED;
if (!hasImmutabilityAnnotation(p)) {
p.addAnnotation(MUTABLE);
}
return super.visitNewClass(node, p);
}
@Override
public Void visitClass(ClassTree node, AnnotatedTypeMirror p) {
if (!hasImmutabilityAnnotation(p))
p.addAnnotation(MUTABLE);
return super.visitClass(node, p);
}
}
/**
* Scanner responsible for adding implicit annotations to qualified types.
*
* <ul>
*
* <li> 1. Annotates unannotated receivers of qualified
* executable types with the qualified type owner's annotation;
* annotated its parameters with {@code @Mutable}, if they have
* no annotation, and annotates its return type with {@code
* @Mutable}, if it has no annotation.
*
* <li> 2. Annotates unannotated qualified declared types with
* their underlying type's element annotations.
*
* <li> 3. Annotates unnanotated qualified types whose elements
* correspond to fields with {@code @ThisMutable}.
*
* <li> 4. Annotated unnanotated qualified types corresponding to arrays
* with {@code @Mutable}.
*
* </ul>
*/
private class JavariTypePostAnnotator extends AnnotatedTypeScanner<Void, ElementKind> {
/**
* Annotates scanned qualified types that correspond to fields
* and have no immutability annotations with {@code @ThisMutable}
*/
@Override
public Void scan(AnnotatedTypeMirror type, ElementKind p) { // case 3
if (type != null && type.getElement() != null
&& !hasImmutabilityAnnotation(type)
&& type.getElement().getKind().isField())
type.addAnnotation(THISMUTABLE);
return super.scan(type, p);
}
/**
* If the receiver has no annotation, annotates it with the
* same annotation as the executable type owner. If the
* parameters or the return type have no annotations, annotate
* it with {@code @Mutable}.
*/
@Override
public Void visitExecutable(AnnotatedExecutableType type, ElementKind p) {
AnnotatedDeclaredType receiver = type.getReceiverType();
if (!hasImmutabilityAnnotation(receiver)) { // case 1
AnnotatedDeclaredType owner = (AnnotatedDeclaredType)
getAnnotatedType(type.getElement().getEnclosingElement());
assert hasImmutabilityAnnotation(owner);
receiver.addAnnotation(getImmutabilityAnnotation(owner));
}
AnnotatedTypeMirror returnType = type.getReturnType();
if (!hasImmutabilityAnnotation(returnType)
&& !returnType.getKind().isPrimitive()
&& returnType.getKind() != TypeKind.VOID
&& returnType.getKind() != TypeKind.TYPEVAR)
returnType.addAnnotation(MUTABLE);
return super.visitExecutable(type, p);
}
/**
* If the declared type has no annotation, annotates it with
* the same annotation as its underlying type's element.
*/
@Override
public Void visitDeclared(AnnotatedDeclaredType type, ElementKind p) {
if (!hasImmutabilityAnnotation(type)) { // case 2
TypeElement tElt = (TypeElement) type.getUnderlyingType().asElement();
AnnotatedTypeMirror tType = fromElement(tElt);
if (hasImmutabilityAnnotation(tType))
type.addAnnotation(getImmutabilityAnnotation(tType));
else
type.addAnnotation(MUTABLE);
}
return super.visitDeclared(type, p);
}
/**
* Ensures that AnnotatedArrayTypes are annotated with {@code
* @Mutable}, if they have no annotation yet.
*/
@Override
public Void visitArray(AnnotatedArrayType type, ElementKind p) {
if (!hasImmutabilityAnnotation(type)) { // case 4
type.addAnnotation(MUTABLE);
}
return super.visitArray(type, p);
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable type, ElementKind p) {
// In a declaration the upperbound is ReadOnly, while
// the upper bound in a use is Mutable
if (type.getUpperBound() != null
&& !hasImmutabilityAnnotation(type.getUpperBound())) {
if (p.isClass() || p.isInterface()
|| p == ElementKind.CONSTRUCTOR
|| p == ElementKind.METHOD)
// case 5: upper bound within a class/method declaration
type.getUpperBound().addAnnotation(READONLY);
else if (TypesUtils.isObject(type.getUnderlyingType()))
// case 10: remaining cases
type.getUpperBound().addAnnotation(MUTABLE);
}
return super.visitTypeVariable(type, p);
}
@Override
public Void visitWildcard(AnnotatedWildcardType type, ElementKind p) {
// In a declaration the upper bound is ReadOnly, while
// the upper bound in a use is Mutable
if (type.getExtendsBound() != null
&& !hasImmutabilityAnnotation(type.getExtendsBound())) {
if (p.isClass() || p.isInterface()
|| p == ElementKind.CONSTRUCTOR
|| p == ElementKind.METHOD)
// case 5: upper bound within a class/method declaration
type.getExtendsBound().addAnnotation(READONLY);
else if (TypesUtils.isObject(type.getUnderlyingType()))
// case 10: remaining cases
type.getExtendsBound().addAnnotation(MUTABLE);
}
return super.visitWildcard(type, p);
}
}
/**
* Type scanner to replace annotations on annotated types.
*/
private class AnnotatedTypeReplacer extends SimpleAnnotatedTypeScanner<Void, Void> {
private AnnotationMirror oldAnnotation, newAnnotation;
/** Initializes the class to replace oldAnnotation with newAnnotation. */
AnnotatedTypeReplacer(AnnotationMirror oldAnnotation, AnnotationMirror newAnnotation) {
this.oldAnnotation = oldAnnotation;
this.newAnnotation = newAnnotation;
}
@Override
protected Void defaultAction(AnnotatedTypeMirror type, Void p) {
if (type.hasAnnotation(oldAnnotation)) {
type.removeAnnotation(oldAnnotation);
type.addAnnotation(newAnnotation);
}
return super.defaultAction(type, p);
}
}
}