package checkers.jimmu;
import checkers.basetype.BaseTypeVisitor;
import checkers.jimmu.quals.Anonymous;
import checkers.jimmu.quals.ImmutableClass;
import checkers.source.Result;
import checkers.types.AnnotatedTypeMirror;
import checkers.types.AnnotatedTypeMirror.AnnotatedDeclaredType;
import checkers.types.AnnotatedTypeMirror.AnnotatedExecutableType;
import checkers.util.ElementUtils;
import checkers.util.InternalUtils;
import checkers.util.TreeUtils;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
/**
* The source code visitor for JimmuChecker.
*
* @author saf
*/
public class JimmuVisitor extends BaseTypeVisitor<Void, Void> {
protected JimmuChecker checker;
protected JimmuAnnotatedTypeFactory atypeFactory;
protected JimmuVisitorState state;
public JimmuVisitor(JimmuChecker checker, CompilationUnitTree root, JimmuVisitorState state) throws IOException {
super(checker, root);
this.checker = checker;
atypeFactory = new JimmuAnnotatedTypeFactory(checker, root, state);
this.state = state;
}
protected enum Protection {
NONE, /* No protection */
IMM, /* Protecting @Immutable object */
MYACC /* Protecting @Myaccess object in @ReadOnly method */
}
protected Protection getProtection(AnnotatedTypeMirror t) {
AnnotatedTypeMirror methodReceiver = state.getCurrentMethod().getReceiverType();
if (t != null && t.hasAnnotation(checker.IMMUTABLE)) {
return Protection.IMM;
} else if (t != null && t.hasAnnotation(checker.MYACCESS)
&& methodReceiver != null
&& methodReceiver.hasAnnotation(checker.IMMUTABLE)) {
return Protection.MYACC;
} else {
return Protection.NONE;
}
}
@Override
public Void visitAssignment(AssignmentTree node, Void p) {
ExpressionTree varTree = node.getVariable();
ExpressionTree valueTree = node.getExpression();
AnnotatedTypeMirror valueType = atypeFactory.getAnnotatedType(valueTree);
AnnotatedTypeMirror receiver = atypeFactory.getReceiver(varTree);
Element varElement = InternalUtils.symbol(varTree);
Protection prot = getProtection(receiver);
/* Prohibit assignment to @Rep fields of @Immutable owners */
if (receiver != null
&& prot != Protection.NONE
&& varElement.getKind().isField()
&& receiver.getKind() != TypeKind.ARRAY) {
if (varElement.getKind() == ElementKind.FIELD) {
if (TreeUtils.isSelfAccess(varTree)) {
checker.report(Result.failure(prot == Protection.IMM
? "assign.readonly.receiver.field"
: "modifying.myaccess.in.readonly",
varTree.toString(), state.getCurrentMethodName()), node);
if (state.inImplicitlyAnnotatedMethod()) {
checker.note(null, "readonly.implicit",
state.getCurrentMethodName(), state.getCurrentClassName());
}
} else {
checker.report(Result.failure("assign.immutable.field",
varTree.toString(), receiver.getElement().toString()), node);
if ((receiver.hasAnnotation(checker.PEER) || receiver.hasAnnotation(checker.OWNEDBY))
&& state.isCurrentMethod(checker.READONLY)) {
checker.note(null, "immutable.implicit.on.encap.in.readonly");
if (state.inImplicitlyAnnotatedMethod()) {
checker.note(null, "readonly.implicit",
state.getCurrentMethodName(), state.getCurrentClassName());
}
}
}
}
}
AnnotatedTypeMirror methodReceiver = state.getCurrentMethod().getReceiverType();
/* Check that @Rep values are not modified */
checkAssignmentRep(node, methodReceiver != null
&& methodReceiver.hasAnnotation(checker.IMMUTABLE));
if (state.isCurrentMethod(checker.ANONYMOUS)) {
checkAssignmentAnonymous(node);
}
/* Cannot create static aliases to @Safe values */
if (varElement.getKind() == ElementKind.FIELD && valueType.hasAnnotation(checker.SAFE)) {
checker.report(Result.failure("static.alias.to.safe",
varElement.toString(), valueTree.toString()), node);
}
return super.visitAssignment(node, p);
}
@Override
public Void visitVariable(VariableTree node, Void p) {
/* Check that no @Rep field is public */
VariableElement el = TreeUtils.elementFromDeclaration(node);
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(el);
AnnotatedExecutableType meth = state.getCurrentMethod();
if (el.getKind().isField()
&& el.getModifiers().contains(Modifier.PUBLIC)
&& type.hasAnnotation(checker.REP)
&& !type.hasAnnotation(checker.IMMUTABLE)) {
checker.report(Result.failure("public.rep.field", el.getSimpleName()), node);
}
if (el.getKind().isField()
&& state.isCurrentClass(checker.IMMUTABLE_CLASS)
&& el.getModifiers().contains(Modifier.PUBLIC)
&& !el.getModifiers().contains(Modifier.FINAL)) {
checker.report(Result.failure("public.field.of.immutable.class",
el.getSimpleName(), state.getCurrentClassName()), el);
}
/* In static methods and constructors, prohibit the use of @Myaccess */
if (meth != null
&& (meth.getReturnType() == null || meth.getElement().getModifiers().contains(Modifier.STATIC))
&& type.hasAnnotation(checker.MYACCESS)) {
checker.report(Result.failure("myaccess.variable.in.static.method",
node.getName().toString(), state.getCurrentMethodName()), node);
}
/* Prohibit @Myaccess static members */
if (el.getKind().isField() && el.getModifiers().contains(Modifier.STATIC)
&& type.hasAnnotation(checker.MYACCESS)) {
checker.report(Result.failure("static.myaccess.field", node.getName().toString()), node);
}
/* Check ownership declaration */
try {
new Owner(el, atypeFactory); /* Constructor performs checking */
} catch (Owner.OwnerDescriptionError err) {
checker.report(err.getResult(), node);
}
/* Add variables to stack, so that they can be owners */
if (el.getKind() == ElementKind.LOCAL_VARIABLE) {
state.addVariable(el.getSimpleName().toString(), type);
} else {
state.shadowVariable(el.getSimpleName().toString());
}
return super.visitVariable(node, p);
}
@Override
protected void commonAssignmentCheck(AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, String errorKey, Void p) {
AnnotatedTypeMirror varCopy = annoTypes.deepCopy(varType);
/* Generally, an object cannot lose or gain the @Immutable annotation. This is
* enforced because @Immutable and @Mutable are uncomparable. However, the user may
* want to allow for upcasting @Mutable values to @Immutable references using the
* "allow.upcast" option. */
if (checker.allowUpcast() && varCopy.hasAnnotation(checker.IMMUTABLE)
&& valueType.hasAnnotation(checker.MUTABLE)) {
varCopy.removeAnnotation(checker.IMMUTABLE);
}
if (varCopy.getKind() == TypeKind.ARRAY && valueType.getKind() == TypeKind.ARRAY) {
AnnotatedTypeMirror.AnnotatedArrayType varArrayType = (AnnotatedTypeMirror.AnnotatedArrayType) varType;
AnnotatedTypeMirror.AnnotatedArrayType valueArrayType = (AnnotatedTypeMirror.AnnotatedArrayType) valueType;
commonAssignmentCheck(varArrayType.getComponentType(), valueArrayType.getComponentType(),
valueTree, errorKey, p);
}
super.commonAssignmentCheck(varCopy, valueType, valueTree, errorKey, p);
}
@Override
public Void visitClass(ClassTree node, Void p) {
state.enterClass(node);
try {
return super.visitClass(node, p);
} finally {
state.leaveClass();
}
}
@Override
public Void visitMethod(MethodTree node, Void p) {
/* Static methods and constructors cannot have @Myaccess parameters
* or return a @Myaccess result */
AnnotatedExecutableType type = atypeFactory.getAnnotatedType(node);
ExecutableElement elt = TreeUtils.elementFromDeclaration(node);
if (elt.getModifiers().contains(Modifier.STATIC) || type.getReturnType() == null) {
if (type.getReturnType() != null
&& type.getReturnType().hasAnnotation(checker.MYACCESS)) {
checker.report(Result.failure("static.method.returns.myaccess",
node.getName()), node);
}
for (AnnotatedTypeMirror pt : type.getParameterTypes()) {
if (pt.hasAnnotation(checker.MYACCESS)) {
checker.report(Result.failure(type.getReturnType() == null
? "constructor.myaccess.parameter"
: "static.method.myaccess.parameter",
node.getName()), node);
}
}
}
state.enterMethod(node);
try {
/* Step into the method */
return super.visitMethod(node, p);
} finally {
state.leaveMethod();
}
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
AnnotatedExecutableType calledMethod = atypeFactory.methodFromUse(node);
AnnotatedTypeMirror receiver = atypeFactory.getReceiver(node);
AnnotatedTypeMirror calledMethodReceiver = calledMethod.getReceiverType();
ExpressionTree methodSelect = node.getMethodSelect();
String methodName;
if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) {
methodName = ((MemberSelectTree) methodSelect).getIdentifier().toString();
} else {
methodName = node.toString();
}
/*
* When inside a @ReadOnly method, we cannot modify @Myaccess objects, as the access
* rights variable could be instantiated to @Immutable.
*/
if (state.isCurrentMethod(checker.READONLY) && receiver != null
&& !calledMethod.hasAnnotation(checker.READONLY)) {
if (receiver.hasAnnotation(checker.MYACCESS)) {
checker.report(Result.failure("nonreadonly.call.on.myaccess.in.readonly",
methodName,
receiver.getElement().getSimpleName().toString()), node);
} else if (receiver.hasAnnotation(checker.REP)) {
checker.report(Result.failure("nonreadonly.call.on.rep",
methodName,
receiver.getElement().getSimpleName().toString()), node);
}
}
/* Disallow passing references to 'this' as arguments of foreign methods.
* and calling non-@Anonymous methods on this */
if (state.isCurrentMethod(checker.ANONYMOUS) && receiver != null) {
if (mayBeThis(receiver)) {
checkCallAnonymous(node);
} else if (mayBeForeign(receiver)) {
checkArgumentsAnonymous(node.getArguments());
}
}
if ((!TreeUtils.isSelfAccess(node) && (receiver != null && !receiver.hasAnnotation(checker.THIS)))) {
/* Check that encapsulated objects are not passed to foreign methods */
checkArgumentsEncap(node.getArguments(), calledMethod.getParameterTypes(), receiver);
}
if (receiver != null && receiver.hasAnnotation(checker.SAFE)
&& calledMethodReceiver != null && !calledMethodReceiver.hasAnnotation(checker.SAFE)) {
String receiverString;
if (TreeUtils.isSelfAccess(node)) {
receiverString = "this";
} else {
MemberSelectTree mst = (MemberSelectTree) node.getMethodSelect();
receiverString = mst.getExpression().toString();
}
checker.report(Result.failure("unsafe.call.on.safe.value", receiverString), node);
}
checkArgumentsSafe(node.getArguments(), calledMethod.getParameterTypes());
state.setCurrentInvocation(node); /* Pass additional info to checkArguments */
return super.visitMethodInvocation(node, p);
}
/**
* Refine the required arguments based on the ownership of the current method receiver,
* stored in the JimmuVisitorState
*/
@Override
protected void checkArguments(List<? extends AnnotatedTypeMirror> requiredArgs, List<? extends ExpressionTree> passedArgs, Void p) {
List<AnnotatedTypeMirror> refinedRequiredArgs = new LinkedList<AnnotatedTypeMirror>();
MethodInvocationTree invocation = state.getCurrentInvocation();
for (AnnotatedTypeMirror arg : requiredArgs) {
AnnotatedTypeMirror refined = annoTypes.deepCopy(arg);
/* Resolve @Myaccess annotations on arguments to the access rights of receiver */
if (refined.hasAnnotation(checker.MYACCESS)) {
if (state.isReceiver(checker.IMMUTABLE)) {
refined.removeAnnotation(checker.MYACCESS);
refined.addAnnotation(checker.IMMUTABLE);
} else if (state.isReceiver(checker.MUTABLE)) {
refined.removeAnnotation(checker.MYACCESS);
refined.addAnnotation(checker.MUTABLE);
}
}
/* Resolve @Peer annotations on arguments relative to the receiver */
if (invocation != null && refined.hasAnnotation(checker.REP)) {
if (!TreeUtils.isSelfAccess(invocation)) {
MemberSelectTree methodSelect = (MemberSelectTree) invocation.getMethodSelect();
refined.removeAnnotation(checker.REP);
Owner rcv = new Owner(methodSelect.getExpression(), atypeFactory);
refined.addAnnotation(atypeFactory.ownerAnnotation(rcv.asString()));
}
} else if (refined.hasAnnotation(checker.PEER)) {
if (state.isReceiver(checker.REP)) {
refined.removeAnnotation(checker.PEER);
refined.addAnnotation(checker.REP);
} else if (state.isReceiver(checker.OWNEDBY)) {
Owner rcvOwner = state.getReceiverOwner();
refined.removeAnnotation(checker.PEER);
if (rcvOwner != null) {
refined.addAnnotation(atypeFactory.ownerAnnotation(rcvOwner.asString()));
} else {
/* Possible on error in owner description. Do not produce spurious errors
stemming from there. */
refined.addAnnotation(checker.ANYOWNER); /* Suppress errors */
}
} else if (!(state.isReceiver(checker.PEER))) {
refined.removeAnnotation(checker.PEER);
refined.addAnnotation(checker.WORLD);
}
} else if (invocation != null && refined.hasAnnotation(checker.OWNEDBY)
&& !TreeUtils.isSelfAccess(invocation)) {
Owner desiredOwner = new Owner(refined.getElement(), atypeFactory);
MemberSelectTree recvSelect = (MemberSelectTree) invocation.getMethodSelect();
Owner rcv = new Owner(recvSelect.getExpression(), atypeFactory);
rcv.append(desiredOwner);
refined.removeAnnotation(checker.OWNEDBY);
refined.addAnnotation(atypeFactory.ownerAnnotation(rcv.asString()));
}
refinedRequiredArgs.add(refined);
}
/* Check that, inside a read-only method, no @Rep value is passed
* as a non-immutable parameter to a method or constructor */
Iterator<? extends ExpressionTree> ait = passedArgs.iterator();
Iterator<AnnotatedTypeMirror> pit = refinedRequiredArgs.iterator();
AnnotatedTypeMirror paramType = null;
while (ait.hasNext()) {
paramType = pit.hasNext() ? pit.next() : paramType; /* Handling varArgs */
ExpressionTree a = ait.next();
AnnotatedTypeMirror at = atypeFactory.getAnnotatedType(a);
if (state.isCurrentMethod(checker.READONLY)
&& at.hasAnnotation(checker.REP)
&& !paramType.hasAnnotation(checker.IMMUTABLE)) {
checker.report(Result.failure("passing.rep.in.readonly", a.toString()), a);
}
}
super.checkArguments(refinedRequiredArgs, passedArgs, p);
}
@Override
public Void visitReturn(ReturnTree node, Void p) {
if (state.isCurrentMethod(checker.ANONYMOUS)
&& mayBeThis(node.getExpression())) {
checker.report(Result.failure("anonymous.returns.this"), node);
}
/* Prohibit returning a read-write reference to a @Rep object. */
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node.getExpression());
if (type.hasAnnotation(checker.REP)
&& !type.hasAnnotation(checker.IMMUTABLE)) {
checker.report(Result.failure("returning.rep", node.getExpression().toString()), node);
}
return super.visitReturn(node, p);
}
@Override
public Void visitNewArray(NewArrayTree node, Void p) {
checkNewReferenceType(node.getType());
checkNewReferenceFromDefaultConstructor(node.getType());
return super.visitNewArray(node, p);
}
/**
* Verify that no immutable reference is created using a non-@Anonymous
* constructor while creating a new array
*
* @param tree The array type specification tree.
*/
protected void checkNewReferenceFromDefaultConstructor(Tree tree) {
AnnotatedTypeMirror type = atypeFactory.fromTypeTree(tree);
if (type.getKind() == TypeKind.DECLARED) {
Element element = ((AnnotatedDeclaredType) type).getUnderlyingType().asElement();
assert element.getKind() == ElementKind.CLASS : "Array initializer not a class";
TypeElement typeElement = (TypeElement) element;
if (type.hasAnnotation(checker.IMMUTABLE) && !isDefaultConstructorAnonymous(typeElement)) {
checker.report(Result.warning("immutable.untrusted.constructor"), tree);
}
} else if (type.getKind() == TypeKind.ARRAY) {
ArrayTypeTree aTree = (ArrayTypeTree) tree;
checkNewReferenceFromDefaultConstructor(aTree.getType());
}
}
protected Boolean isDefaultConstructorAnonymous(TypeElement typeElement) {
for (Element el : typeElement.getEnclosedElements()) {
if (el.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructor = (ExecutableElement) el;
if (constructor.getParameters().isEmpty()) {
return constructor.getAnnotation(Anonymous.class) != null;
}
}
}
TypeMirror superclass = typeElement.getSuperclass();
if (superclass.getKind() == TypeKind.NONE) {
/* The constructor is Object.<init>(). It does nothing, so we
assume that it is anonymous. */
return true;
} else if (superclass.getKind() == TypeKind.DECLARED) {
Element superclassElement = ((DeclaredType) superclass).asElement();
if (superclassElement.getKind() == ElementKind.CLASS) {
/* Proceed with the superclass */
return isDefaultConstructorAnonymous((TypeElement) superclassElement);
} else {
throw new IllegalStateException("Superclass is not a class");
}
} else {
throw new IllegalStateException("Superclass is not a DeclaredType or NoType");
}
}
@Override
public Void visitNewClass(NewClassTree node, Void p) {
if (state.isCurrentMethod(checker.ANONYMOUS)) {
/* Do not pass 'this' to foreign constructors. */
checkArgumentsAnonymous(node.getArguments());
}
AnnotatedTypeMirror type = atypeFactory.fromTypeTree(node.getIdentifier());
AnnotatedExecutableType con = atypeFactory.getAnnotatedType(TreeUtils.elementFromUse(node));
checkArgumentsEncap(node.getArguments(), con.getParameterTypes(), type);
checkNewReferenceType(node.getIdentifier());
AnnotatedExecutableType constructor = atypeFactory.constructorFromUse(node);
if (type.hasAnnotation(checker.IMMUTABLE)
&& constructor.getAnnotation(Anonymous.class) == null) {
checker.report(Result.warning("immutable.untrusted.constructor"), node);
}
state.setCurrentInvocation(node);
return super.visitNewClass(node, p);
}
/**
* Check that the reference type is correct, i.e. that there are no
* mutable references to objects of an ImmutableClass created.
*
* @param tree The Tree representing the declared type of the new object,
*/
protected void checkNewReferenceType(Tree tree) {
AnnotatedTypeMirror type = atypeFactory.fromTypeTree(tree);
if (type.getKind() == TypeKind.DECLARED) {
AnnotatedDeclaredType declaredType = (AnnotatedDeclaredType) type;
Element typeElement = declaredType.getUnderlyingType().asElement();
if (typeElement.getKind() == ElementKind.CLASS) {
if (typeElement.getAnnotation(ImmutableClass.class) != null
&& !type.hasAnnotation(checker.IMMUTABLE)) {
/* Creating a mutable reference to an immutable object */
checker.report(Result.failure("mutable.reference.to.immutable.class"), tree);
}
}
} else if (type.getKind() == TypeKind.ARRAY) {
ArrayTypeTree aTree = (ArrayTypeTree) tree;
checkNewReferenceType(aTree.getType());
}
}
@Override
protected boolean checkMethodInvocability(AnnotatedExecutableType method, MethodInvocationTree node) {
AnnotatedExecutableType mcp = (AnnotatedExecutableType) annoTypes.deepCopy(method);
AnnotatedTypeMirror methodReceiver = mcp.getReceiverType().getErased();
AnnotatedTypeMirror treeReceiver = methodReceiver.getCopy(false);
treeReceiver.addAnnotations(atypeFactory.getReceiver(node).getAnnotations());
if (treeReceiver.hasAnnotation(checker.MUTABLE)
&& mcp.hasAnnotation(checker.READONLY)) {
/* Allow for @ReadOnly calls on @Mutable references. */
mcp.getReceiverType().removeAnnotation(checker.IMMUTABLE);
mcp.getReceiverType().addAnnotation(checker.MUTABLE);
} else if (treeReceiver.hasAnnotation(checker.IMMUTABLE)
&& !mcp.hasAnnotation(checker.READONLY)) {
/* Disallow non-@Readonly calls on @Immutable */
checker.report(Result.failure("immutable.calls.nonreadonly", mcp.getElement().toString()), node);
if ((treeReceiver.hasAnnotation(checker.PEER) || treeReceiver.hasAnnotation(checker.OWNEDBY))
&& state.isCurrentMethod(checker.READONLY)) {
checker.note(null, "immutable.implicit.on.encap.in.readonly");
}
}
return super.checkMethodInvocability(mcp, node);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
/* Prevent access to inner representation of a foreign object. */
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
if (type.hasAnnotation(checker.REP)
&& !type.hasAnnotation(checker.IMMUTABLE)) {
ExpressionTree expression = node.getExpression();
AnnotatedTypeMirror selectType = atypeFactory.getAnnotatedType(expression);
if (selectType.hasAnnotation(checker.MAYBE_THIS)
|| selectType.hasAnnotation(checker.NOT_THIS)) {
checker.report(Result.failure("accessing.foreign.rep", node.toString()), node);
}
}
return super.visitMemberSelect(node, p);
}
/**
* Check that an assignment does not modify @Rep objects.
*/
protected void checkAssignmentRep(AssignmentTree node, boolean receiverImmutable) {
ExpressionTree varTree = node.getVariable();
if (varTree instanceof ArrayAccessTree) {
ArrayAccessTree t = (ArrayAccessTree) varTree;
boolean dig = true;
boolean rep = false;
while (dig) {
ExpressionTree e = t.getExpression();
AnnotatedTypeMirror et = atypeFactory.getAnnotatedType(e);
if (et.hasAnnotation(checker.IMMUTABLE)) {
String errorKey = rep
? "assignment.to.rep.array.of.immutable"
: "assignment.to.field.of.immutable.array";
checker.report(Result.failure(errorKey, node.getVariable().toString(), e.toString()), varTree);
dig = false;
} else if (!(e instanceof ArrayAccessTree)) {
if (et.hasAnnotation(checker.REP)
&& TreeUtils.isSelfAccess(e)
&& receiverImmutable) {
String errorKey = rep
? "assignment.to.rep.array.of.rep.array.receiver"
: "assignment.to.rep.array.of.receiver";
checker.report(Result.failure(errorKey, node.getVariable().toString(), t.toString()), varTree);
}
dig = false;
} else if (et.hasAnnotation(checker.REP)) {
rep = true;
t = (ArrayAccessTree) e;
} else {
dig = false;
}
}
} else {
AnnotatedTypeMirror receiver = atypeFactory.getReceiver(varTree);
if (receiver != null && receiver.hasAnnotation(checker.REP) && receiverImmutable) {
checker.report(Result.failure("assignment.to.field.of.rep",
varTree.toString(), receiver.getElement().toString()), node);
}
}
}
/**
* Check an assignment within an @Anonymous method.
*/
protected void checkAssignmentAnonymous(AssignmentTree node) {
ExpressionTree ex = node.getExpression();
ExpressionTree var = node.getVariable();
if (mayBeThis(ex)) {
try {
Element varElement = TreeUtils.elementFromUse(var);
if (varElement.getKind() == ElementKind.FIELD) {
checker.report(Result.failure("anonymous.assigns.this.to.field",
var.toString()), var);
}
} catch (IllegalArgumentException e) {
/* Tree is not an element use */
if (var.getKind() == Tree.Kind.ARRAY_ACCESS) {
checker.report(Result.failure("anonymous.assigns.this.to.array.field",
var.toString()), var);
}
}
}
}
/**
* Check a function call inside an @Anonymous method.
*/
protected void checkCallAnonymous(MethodInvocationTree node) {
/* Check that non-@Anonymous methods are not called on [this] */
AnnotatedExecutableType method = atypeFactory.methodFromUse(node);
if (!method.hasAnnotation(checker.ANONYMOUS)
&& !isBaseConstructorCall(node)) {
if (TreeUtils.isSelfAccess(node)) {
checker.report(Result.failure("anonymous.calls.non.anonymous",
method.getElement().getSimpleName().toString(),
state.getCurrentMethodName()), node);
if (state.inImplicitlyAnnotatedMethod()) {
checker.note(null, "anonymous.implicit",
state.getCurrentMethodName(), state.getCurrentClassName());
}
} else {
/* Calling via reference */
ExpressionTree select = node.getMethodSelect();
if (select.getKind() == Tree.Kind.MEMBER_SELECT) {
MemberSelectTree selTree = (MemberSelectTree) select;
if (mayBeThis(selTree)) {
checker.report(Result.failure("anonymous.calls.non.anonymous.on.alias"), node);
if (state.inImplicitlyAnnotatedMethod()) {
checker.note(null, "anonymous.implicit", state.getCurrentMethodName());
}
}
}
}
}
}
/**
* Validate arguments of a method/constructor call within an Anonymous
* method by checking that they don't evaluate to a reference to this.
*
* @param args The list of arguments to be checked.
*/
protected void checkArgumentsAnonymous(List<? extends ExpressionTree> args) {
for (ExpressionTree arg : args) {
if (mayBeThis(arg)) {
checker.report(Result.failure("argument.may.be.this", arg.toString()), arg);
if (state.inImplicitlyAnnotatedMethod()) {
checker.note(null, "anonymous.implicit",
state.getCurrentMethodName(), state.getCurrentClassName());
}
}
}
}
/**
* Validate a call of a foreign method/constructor by checking that no
* @Rep or @Peer objects are passed as arguments. They may only be passed
* as @Immutable references, @Safe values or to methods whose receivers
* are @Rep or @Peer.
*
* @param args The list of arguments to be checked.
* @param params The list of
* @param receiver The type of the method's receiver.
*/
protected void checkArgumentsEncap(List<? extends ExpressionTree> args,
List<AnnotatedTypeMirror> params, AnnotatedTypeMirror receiver) {
Iterator<? extends ExpressionTree> ait = args.iterator();
Iterator<AnnotatedTypeMirror> pit = params.iterator();
AnnotatedTypeMirror paramType = null;
while (ait.hasNext()) {
paramType = pit.hasNext() ? pit.next() : paramType; /* Handling varArgs */
ExpressionTree a = ait.next();
AnnotatedTypeMirror at = atypeFactory.getAnnotatedType(a);
if ((receiver == null || !receiver.hasAnnotation(checker.REP))
&& at.hasAnnotation(checker.REP)
&& !paramType.hasAnnotation(checker.IMMUTABLE)
&& !paramType.hasAnnotation(checker.SAFE)) {
checker.report(Result.failure("passing.rep.to.foreign.method", a.toString()), a);
} else if ((receiver == null || (!receiver.hasAnnotation(checker.PEER) && !receiver.hasAnnotation(checker.REP)))
&& at.hasAnnotation(checker.PEER)
&& !paramType.hasAnnotation(checker.IMMUTABLE)
&& !paramType.hasAnnotation(checker.SAFE)) {
checker.report(Result.failure("passing.peer.to.foreign.method", a.toString()), a);
}
}
}
/**
* Check that @Safe values are not passed as unsafe parameters
*/
protected void checkArgumentsSafe(List<? extends ExpressionTree> args, List<AnnotatedTypeMirror> params) {
Iterator<? extends ExpressionTree> ait = args.iterator();
Iterator<AnnotatedTypeMirror> pit = params.iterator();
Boolean safe = false;
while (ait.hasNext()) {
safe = pit.hasNext() ? pit.next().hasAnnotation(checker.SAFE) : safe;
ExpressionTree a = ait.next();
AnnotatedTypeMirror at = atypeFactory.getAnnotatedType(a);
if (at.hasAnnotation(checker.SAFE) && !safe) {
checker.report(Result.failure("passing.safe.to.unsafe.parameter", a.toString()), a);
}
}
}
/**
* @param node a method call tree
* @return true if the called method is the base constructor Object()
*/
protected boolean isBaseConstructorCall(MethodInvocationTree node) {
AnnotatedExecutableType method = atypeFactory.methodFromUse(node);
ExecutableElement methodElement = method.getElement();
if (methodElement.getEnclosingElement().getSimpleName().contentEquals("Object")
&& methodElement.getSimpleName().contentEquals("<init>")) {
return true;
}
return false;
}
@Override
public Void visitBlock(BlockTree node, Void p) {
try {
state.enterBlock();
return super.visitBlock(node, p);
} finally {
state.leaveBlock();
}
}
protected Boolean mayBeThis(ExpressionTree node) {
AnnotatedTypeMirror type = atypeFactory.getAnnotatedType(node);
return mayBeThis(type);
}
protected Boolean mayBeThis(AnnotatedTypeMirror type) {
return type.hasAnnotation(checker.THIS) || type.hasAnnotation(checker.MAYBE_THIS);
}
protected Boolean mayBeForeign(AnnotatedTypeMirror type) {
return type.hasAnnotation(checker.MAYBE_THIS) || type.hasAnnotation(checker.NOT_THIS);
}
/**
* Represents information on an object's owner, based on the @OwnedBy annotation.
*/
public static class Owner {
Element element;
List<PathStep> path;
JimmuAnnotatedTypeFactory af;
public class OwnerDescriptionError extends RuntimeException {
Result result;
public OwnerDescriptionError(Result r) {
super();
result = r;
}
public Result getResult() {
return result;
}
}
protected static class PathStep {
public static enum PathStepKind {
CLASS, FIELD, LOCAL, PARAMETER, UNKNOWN
}
public String name;
public PathStepKind kind;
public Boolean isStatic;
public AnnotatedTypeMirror type; /* Null in CLASS steps */
public PathStep(String name, PathStepKind kind, Boolean isStatic, AnnotatedTypeMirror type) {
this.name = name;
this.kind = kind;
this.isStatic = isStatic;
this.type = type;
}
}
public Owner(Element elt, JimmuAnnotatedTypeFactory af) throws OwnerDescriptionError {
this.af = af;
this.element = elt;
if (element == null) {
/* This kind of error should never be reported */
throw new OwnerDescriptionError(Result.failure("owner.must.be.chain.of.identifiers"));
}
String owner;
if (element.getKind() == ElementKind.METHOD) {
AnnotatedExecutableType type = (AnnotatedExecutableType) af.getAnnotatedType(element);
owner = af.getOwner(type.getReturnType());
} else {
owner = af.getOwner(element);
}
if (owner != null) {
path = new LinkedList<PathStep>();
List<String> parts = Arrays.asList(owner.split("\\."));
List<String> significant = new LinkedList<String>();
for (String p : parts) {
if (!isValidName(p)) {
throw new OwnerDescriptionError(Result.failure("owner.invalid.identifier", p));
} else if (!"this".equals(p)) {
significant.add(p);
}
}
constructPath(significant);
}
}
/**
* Create Owner from a given [ExpressionTree]. We will only handle
* simple-formed MemberSelectTrees, which contain an IdentifierTree at the end
* of the chain.
*/
public Owner(ExpressionTree tree, JimmuAnnotatedTypeFactory af) {
this.af = af;
path = new LinkedList<PathStep>();
Tree t = tree;
while (t.getKind() == Tree.Kind.MEMBER_SELECT) {
MemberSelectTree mst = (MemberSelectTree) t;
AnnotatedTypeMirror type = af.getAnnotatedType(t);
path.add(new PathStep(mst.getIdentifier().toString(),
type.getElement().getKind() == ElementKind.CLASS
? PathStep.PathStepKind.CLASS
: type.getElement().getKind() == ElementKind.LOCAL_VARIABLE
? PathStep.PathStepKind.LOCAL
: PathStep.PathStepKind.FIELD,
type.getElement().getModifiers().contains(Modifier.STATIC), type));
t = TreeUtils.skipParens(mst.getExpression());
}
if (t.getKind() == Tree.Kind.IDENTIFIER) {
IdentifierTree lastId = (IdentifierTree) t;
AnnotatedTypeMirror type = af.getAnnotatedType(t);
if (!"this".equals(lastId.getName().toString())) {
path.add(new PathStep(lastId.getName().toString(),
type.getElement().getKind() == ElementKind.CLASS
? PathStep.PathStepKind.CLASS
: type.getElement().getKind() == ElementKind.LOCAL_VARIABLE
? PathStep.PathStepKind.LOCAL
: PathStep.PathStepKind.FIELD,
type.getElement().getModifiers().contains(Modifier.STATIC), type));
}
} else {
/* Kind of hack... If the MemberSelect is of
* non-identifier-sequence form, e.g.
*
* (new X()).z
*
* then return _ to mark ownership by an unknown object.
* Naturally, the returned OwnedBy annotation will not be a subclass
* of any valid OwnedBy annotation.
*/
path.add(new PathStep(null, PathStep.PathStepKind.UNKNOWN, false, null));
}
Collections.reverse(path);
}
private Boolean isCapitalized(String s) {
return s.matches("^[A-Z][A-Za-z0-9_]*$");
}
private Boolean isValidName(String s) {
return s.matches("^[A-Za-z][A-Za-z0-9_]*$");
}
protected AnnotatedTypeMirror findMember(TypeElement t, String f,
Boolean mustBeStatic, Boolean mustBePublic) {
for (Element e : t.getEnclosedElements()) {
if (e.getSimpleName().toString().equals(f)) {
if (mustBeStatic && !e.getModifiers().contains(Modifier.STATIC)) {
throw new OwnerDescriptionError(Result.failure("owner.nonstatic.member", f));
} else if (mustBePublic && !e.getModifiers().contains(Modifier.PUBLIC)) {
throw new OwnerDescriptionError(Result.failure("owner.nonpublic.member", f));
} else {
return af.getAnnotatedType(e);
}
}
}
TypeMirror s = t.getSuperclass();
if (s.getKind() == TypeKind.NONE) {
return null; /* Top of hierarchy, nothing found */
} else if (s.getKind() == TypeKind.DECLARED) {
Element superclassElement = ((DeclaredType) s).asElement();
return findMember(((TypeElement) superclassElement), f, mustBeStatic, mustBePublic);
} else {
throw new IllegalStateException("Superclass is not a class");
}
}
protected void addMembers(List<String> p, TypeMirror t, Boolean insideClass) {
/* Assuming p is not empty! */
String f = p.remove(0);
if (t.getKind() == TypeKind.DECLARED) {
TypeElement te = (TypeElement) ((DeclaredType) t).asElement();
AnnotatedTypeMirror am = findMember(te, f, insideClass, true);
if (am != null) {
if (am.getElement().getKind() == ElementKind.CLASS) {
path.add(new PathStep(f, PathStep.PathStepKind.CLASS,
am.getElement().getModifiers().contains(Modifier.STATIC), null));
if (p.isEmpty()) {
throw new OwnerDescriptionError(Result.failure("owner.class.cannot.own"));
} else {
addMembers(p, am.getElement().asType(), true);
}
} else {
path.add(new PathStep(f, PathStep.PathStepKind.FIELD,
am.getElement().getModifiers().contains(Modifier.STATIC), am));
if (!p.isEmpty()) {
addMembers(p, am.getUnderlyingType(), false);
}
}
} else {
throw new OwnerDescriptionError(Result.failure("owner.no.such.field", f, am.getUnderlyingType().toString()));
}
} else if (t.getKind() == TypeKind.WILDCARD || t.getKind() == TypeKind.TYPEVAR) {
throw new OwnerDescriptionError(Result.failure("owner.peeking.unsupported"));
} else {
throw new OwnerDescriptionError(Result.failure("owner.simple.type"));
}
}
public void constructPath(List<String> p) throws OwnerDescriptionError {
Element enclosing = element.getKind() == ElementKind.PARAMETER
? ElementUtils.enclosingClass(element)
: element.getEnclosingElement();
List<String> rest = new LinkedList<String>(p);
String base = rest.remove(0);
Boolean found = false;
/* Try to find base among local variables. */
AnnotatedTypeMirror localType = af.checker.getState().localVariable(base);
if (localType != null) {
found = true;
path.add(new PathStep(base, PathStep.PathStepKind.LOCAL, false, localType));
if (!rest.isEmpty()) {
addMembers(rest, localType.getUnderlyingType(), false);
}
}
do {
/*
* Traverse elements enclosing [element] to find one that
* contains the field f accessible from [element]
*/
if (enclosing.getKind() == ElementKind.METHOD) {
ExecutableElement m = (ExecutableElement) enclosing;
for (VariableElement v : m.getParameters()) {
if (v.getSimpleName().toString().equals(base)) {
found = true;
AnnotatedTypeMirror am = af.getAnnotatedType(v);
path.add(new PathStep(base, PathStep.PathStepKind.PARAMETER, false, am));
if (!rest.isEmpty()) {
addMembers(rest, am.getUnderlyingType(), false);
}
}
}
} else if (enclosing.getKind() == ElementKind.CLASS) {
TypeElement t = (TypeElement) enclosing;
AnnotatedTypeMirror ft = findMember(t, base, false, false);
if (ft != null) {
found = true;
if (ft.getElement().getKind() == ElementKind.CLASS) {
if (rest.isEmpty()) {
throw new OwnerDescriptionError(Result.failure("owner.class.cannot.own"));
} else {
path.add(new PathStep(base, PathStep.PathStepKind.CLASS,
ft.getElement().getModifiers().contains(Modifier.STATIC), null));
addMembers(rest, ft.getElement().asType(), true);
}
} else { /* Field */
path.add(new PathStep(base, PathStep.PathStepKind.FIELD,
ft.getElement().getModifiers().contains(Modifier.STATIC), ft));
if (!rest.isEmpty()) {
addMembers(rest, ft.getUnderlyingType(), false);
}
}
}
}
enclosing = enclosing.getEnclosingElement();
} while (!found && enclosing.getKind() != ElementKind.PACKAGE);
if (!found) {
throw new OwnerDescriptionError(Result.failure("owner.no.such.field", base, element.toString()));
}
}
public void append(Owner owner) {
for (PathStep s : owner.path) {
path.add(s);
}
}
public Boolean isImmutable() {
return path != null && path.get(path.size() - 1).type.hasAnnotation(af.checker.IMMUTABLE);
}
public Boolean isMyaccess() {
return path != null && path.get(path.size() - 1).type.hasAnnotation(af.checker.MYACCESS);
}
public Boolean isFullyStatic() {
if (path == null) {
return null;
} else {
for (PathStep ps : path) {
if (!ps.isStatic) {
return false;
}
}
return true;
}
}
public String asString() {
StringBuilder b = new StringBuilder();
for (PathStep s : path) {
b.append(s.name + ".");
}
b.deleteCharAt(b.length() - 1);
return b.toString();
}
@Override
public String toString() {
if (path == null) {
return "[null path]";
} else {
StringBuilder builder = new StringBuilder("[");
for (PathStep s : path) {
builder.append(s.name + ", ");
}
builder.append("]");
return builder.toString();
}
}
}
}