package checkers.jimmu; import checkers.flow.Flow; import checkers.flow.GenKillBits; import checkers.jimmu.quals.*; import checkers.types.AnnotatedTypeMirror; import checkers.types.AnnotatedTypeMirror.AnnotatedArrayType; import checkers.types.AnnotatedTypeMirror.AnnotatedDeclaredType; import checkers.types.AnnotatedTypeMirror.AnnotatedExecutableType; import checkers.types.BasicAnnotatedTypeFactory; import checkers.util.AnnotationUtils; import checkers.util.ElementUtils; import checkers.util.TreeUtils; 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.Tree; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; /** * Determines annotations based on rules governing JimmuChecker's annotations. */ public class JimmuAnnotatedTypeFactory extends BasicAnnotatedTypeFactory<JimmuChecker> { protected final AnnotationUtils annotationFactory; protected JimmuChecker checker; protected JimmuVisitorState state; public JimmuAnnotatedTypeFactory(JimmuChecker checker, CompilationUnitTree root, JimmuVisitorState state) { super(checker, root, true); annotationFactory = checker.getAnnotationFactory(); this.checker = checker; this.state = state; state.setFactory(this); } @Override public AnnotatedTypeMirror getAnnotatedType(Tree tree) { AnnotatedTypeMirror type = super.getAnnotatedType(tree); if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { MethodInvocationTree inv = (MethodInvocationTree) tree; ExecutableElement mElem = TreeUtils.elementFromUse(inv); AnnotatedExecutableType mType = getAnnotatedType(mElem); AnnotatedTypeMirror receiverType = getReceiver(inv); if (receiverType != null && (receiverType.hasAnnotation(checker.THIS) || receiverType.hasAnnotation(checker.MAYBE_THIS) || TreeUtils.isSelfAccess(inv))) { if (mType.hasAnnotation(checker.ANONYMOUS)) { type.addAnnotation(checker.NOT_THIS); } else { type.addAnnotation(checker.MAYBE_THIS); } } else { /* Calling method on a non-this value or a static method. * * The result will not be THIS, provided we did not * call a non-@Anonymous method before or otherwise passed a * reference to 'this' to another object. */ type.addAnnotation(checker.NOT_THIS); } /* Resolve @Myaccess to the access rights of the receiver */ if (receiverType != null && mType.getReturnType().hasAnnotation(checker.MYACCESS)) { type.removeAnnotation(checker.MYACCESS); if (receiverType.hasAnnotation(checker.IMMUTABLE)) { type.addAnnotation(checker.IMMUTABLE); } else if (receiverType.hasAnnotation(checker.MYACCESS)) { type.addAnnotation(checker.MYACCESS); } else if (receiverType.hasAnnotation(checker.MUTABLE)) { type.addAnnotation(checker.MUTABLE); } } /* Refine ownership type of the returned value based on the receiver type */ AnnotatedTypeMirror returnType = mType.getReturnType(); if (receiverType != null) { if (returnType.hasAnnotation(checker.PEER)) { type.removeAnnotation(checker.PEER); if (receiverType.hasAnnotation(checker.REP)) { type.addAnnotation(checker.REP); } else if (receiverType.hasAnnotation(checker.PEER)) { type.addAnnotation(checker.PEER); } else if (receiverType.hasAnnotation(checker.WORLD)) { type.addAnnotation(checker.WORLD); } else if (receiverType.hasAnnotation(checker.OWNEDBY)) { JimmuVisitor.Owner retOwner = new JimmuVisitor.Owner(mElem, this); type.addAnnotation(ownerAnnotation(retOwner.asString())); } } else if (returnType.hasAnnotation(checker.OWNEDBY) && !TreeUtils.isSelfAccess(inv)) { MemberSelectTree mst = (MemberSelectTree) inv.getMethodSelect(); JimmuVisitor.Owner retOwner = new JimmuVisitor.Owner(mElem, this); JimmuVisitor.Owner selOwner = new JimmuVisitor.Owner(mst.getExpression(), this); selOwner.append(retOwner); type.removeAnnotation(checker.OWNEDBY); type.addAnnotation(ownerAnnotation(selOwner.asString())); } } /* Add @Safe annotation on values returned from calls on @Safe objects */ /* Only @Peer objects must be protected as @Rep cannot be returned */ if (receiverType != null && receiverType.hasAnnotation(checker.SAFE) && returnType.hasAnnotation(checker.PEER)) { type.addAnnotation(checker.SAFE); } } else if (tree.getKind() == Tree.Kind.IDENTIFIER) { IdentifierTree ident = (IdentifierTree) tree; if (ident.getName().contentEquals("this")) { type.addAnnotation(checker.THIS); } } else if (tree.getKind() == Tree.Kind.NEW_ARRAY || tree.getKind() == Tree.Kind.NEW_CLASS) { type.addAnnotation(checker.NOT_THIS); } else if (tree.getKind() == Tree.Kind.MEMBER_SELECT) { MemberSelectTree mst = (MemberSelectTree) tree; Element elem = TreeUtils.elementFromUse(mst); AnnotatedTypeMirror elemType = getAnnotatedType(elem); AnnotatedTypeMirror expElemType = getAnnotatedType(mst.getExpression()); /* Resolve @Myaccess to the access rights of expElem */ if (elemType.hasAnnotation(checker.MYACCESS)) { type.removeAnnotation(checker.MYACCESS); if (expElemType.hasAnnotation(checker.IMMUTABLE)) { type.addAnnotation(checker.IMMUTABLE); } else if (expElemType.hasAnnotation(checker.MYACCESS)) { type.addAnnotation(checker.MYACCESS); } else if (expElemType.hasAnnotation(checker.MUTABLE)) { type.addAnnotation(checker.MUTABLE); } } /* Resolve @Peer to the owner of expElem */ if (elemType.hasAnnotation(checker.PEER) && !expElemType.hasAnnotation(checker.THIS)) { if (expElemType.hasAnnotation(checker.REP)) { type.removeAnnotation(checker.PEER); type.addAnnotation(checker.REP); } else if (expElemType.hasAnnotation(checker.WORLD)) { type.removeAnnotation(checker.PEER); type.addAnnotation(checker.WORLD); } else if (expElemType.hasAnnotation(checker.OWNEDBY)) { JimmuVisitor.Owner elOwner = new JimmuVisitor.Owner(elem, this); type.removeAnnotation(checker.PEER); type.addAnnotation(ownerAnnotation(elOwner.asString())); } } else if (elemType.hasAnnotation(checker.OWNEDBY)) { JimmuVisitor.Owner elOwner = new JimmuVisitor.Owner(elem, this); JimmuVisitor.Owner expOwner = new JimmuVisitor.Owner(mst.getExpression(), this); expOwner.append(elOwner); type.removeAnnotation(checker.OWNEDBY); type.addAnnotation(ownerAnnotation(expOwner.asString())); } /* Add @Safe annotation to members of @Safe objects to protect their transitive reach */ /* (Only @Rep and @Peer objects must be protected) */ if (expElemType.hasAnnotation(checker.SAFE) && type.hasAnnotation(checker.PEER)) { type.addAnnotation(checker.SAFE); } } /* Implicit @World on new etc. */ if (tree instanceof ExpressionTree && !type.hasAnnotation(checker.REP) && !type.hasAnnotation(checker.PEER) && !type.hasAnnotation(checker.OWNEDBY) && !type.hasAnnotation(checker.ANYOWNER) && !type.hasAnnotation(checker.SAFE)) { type.addAnnotation(checker.WORLD); } /* Implicit protection of encapsulated values inside read-only methods (Rule 2) */ if (state.isCurrentMethod(checker.READONLY) && (type.hasAnnotation(checker.PEER) || type.hasAnnotation(checker.OWNEDBY)) && !type.hasAnnotation(checker.IMMUTABLE)) { type.addAnnotation(checker.IMMUTABLE); } return type; } @Override public AnnotatedTypeMirror getAnnotatedType(Element elt) { AnnotatedTypeMirror type = super.getAnnotatedType(elt); /* Resolve @Myaccess on @ImmutableClasses to @Immutable */ TypeElement enclosingClass = ElementUtils.enclosingClass(elt); if (enclosingClass.getAnnotation(ImmutableClass.class) != null && type.hasAnnotation(checker.MYACCESS)) { type.removeAnnotation(checker.MYACCESS); type.addAnnotation(checker.IMMUTABLE); } if (elt.getKind() == ElementKind.PARAMETER) { if (state.inConstructor()) { type.addAnnotation(checker.NOT_THIS); } else { type.addAnnotation(checker.MAYBE_THIS); } } /* Implicit ownership */ if (!type.hasAnnotation(checker.REP) && !type.hasAnnotation(checker.PEER) && !type.hasAnnotation(checker.OWNEDBY) && !type.hasAnnotation(checker.ANYOWNER) && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.PARAMETER || elt.getKind() == ElementKind.LOCAL_VARIABLE)) { type.addAnnotation(checker.WORLD); } /* Add implicit @Immutable to objects @OwnedBy @Immutable objects. */ if (type.hasAnnotation(checker.OWNEDBY)) { try { JimmuVisitor.Owner owner = new JimmuVisitor.Owner(elt, this); if (owner.isImmutable()) { type.removeAnnotation(checker.MUTABLE); type.removeAnnotation(checker.MYACCESS); type.removeAnnotation(checker.IMMUTABLE); type.addAnnotation(checker.IMMUTABLE); } else if (owner.isMyaccess()) { if (!type.hasAnnotation(checker.IMMUTABLE)) { type.removeAnnotation(checker.MUTABLE); type.removeAnnotation(checker.MYACCESS); type.addAnnotation(checker.MYACCESS); } } } catch (JimmuVisitor.Owner.OwnerDescriptionError err) { /* Swallow the exception, it should have already been reported */ } } /* Implicit protection of encapsulated values inside read-only methods (Rule 2) */ if (state.isCurrentMethod(checker.READONLY) && (type.hasAnnotation(checker.PEER) || type.hasAnnotation(checker.OWNEDBY)) && !type.hasAnnotation(checker.IMMUTABLE)) { type.addAnnotation(checker.IMMUTABLE); } return type; } @Override protected void annotateImplicit(Tree tree, AnnotatedTypeMirror type) { super.annotateImplicit(tree, type); if (tree.getKind() == Tree.Kind.METHOD) { refineMethodType((AnnotatedExecutableType) type); } if (tree.getKind() != Tree.Kind.CLASS && tree.getKind() != Tree.Kind.COMPILATION_UNIT && tree.getKind() != Tree.Kind.IMPORT) { implicitAccessRights(type); } } @Override protected void annotateImplicit(Element el, AnnotatedTypeMirror type) { super.annotateImplicit(el, type); if (el.getKind() == ElementKind.CONSTRUCTOR || el.getKind() == ElementKind.METHOD) { refineMethodType((AnnotatedExecutableType) type); } if (el.getKind() != ElementKind.CLASS && el.getKind() != ElementKind.ENUM && el.getKind() != ElementKind.INTERFACE && el.getKind() != ElementKind.PACKAGE) { implicitAccessRights(type); } } protected void refineMethodType(AnnotatedExecutableType type) { /* Add implicit annotations to methods of Immutable classes */ ExecutableElement el = type.getElement(); TypeElement enclosingClass = ElementUtils.enclosingClass(el); AnnotatedTypeMirror.AnnotatedDeclaredType enclosingClassType = getAnnotatedType(enclosingClass); if (enclosingClassType.hasAnnotation(checker.IMMUTABLE_CLASS)) { AnnotationMirror implicit = el.getKind() == ElementKind.CONSTRUCTOR ? checker.ANONYMOUS : checker.READONLY; if (!type.hasAnnotation(implicit)) { type.addAnnotation(implicit); state.addImplicitAnnotation(el, implicit); } } /* Add the @Immutable annotation to receivers of @ReadOnly methods */ if (type.hasAnnotation(checker.READONLY)) { AnnotatedTypeMirror.AnnotatedDeclaredType receiver = type.getReceiverType(); if (receiver != null) { receiver.removeAnnotation(checker.MUTABLE); receiver.removeAnnotation(checker.MYACCESS); receiver.addAnnotation(checker.IMMUTABLE); } } } /** * Refine the type of an instance method's receiver by adding the @This annotation. * * @return the type of 'this' in the current location. */ @Override public AnnotatedDeclaredType getSelfType(Tree tree) { AnnotatedDeclaredType type = super.getSelfType(tree); type.addAnnotation(checker.THIS); if (!type.hasAnnotation(checker.IMMUTABLE) && !type.hasAnnotation(checker.MYACCESS)) { type.addAnnotation(checker.MUTABLE); } return type; } /** * Add the implicit @Mutable annotation to avoid unqualified types. * * @param type the type to be refined. */ protected void implicitAccessRights(AnnotatedTypeMirror type) { if (!(type instanceof AnnotatedExecutableType) && !type.hasAnnotation(checker.IMMUTABLE) && !type.hasAnnotation(checker.MYACCESS) && !type.hasAnnotation(checker.MUTABLE)) { type.removeAnnotation(checker.BOTTOM); type.addAnnotation(checker.MUTABLE); } if (type.getKind() == TypeKind.EXECUTABLE) { AnnotatedExecutableType ext = (AnnotatedExecutableType) type; if (type.getElement().getKind() != ElementKind.CONSTRUCTOR) { implicitAccessRights(ext.getReceiverType()); implicitAccessRights(ext.getReturnType()); } for (AnnotatedTypeMirror t : ext.getParameterTypes()) { implicitAccessRights(t); } } else if (type.getKind() == TypeKind.ARRAY) { AnnotatedArrayType art = (AnnotatedArrayType) type; implicitAccessRights(art.getComponentType()); } else if (type.getKind() == TypeKind.DECLARED) { AnnotatedDeclaredType dt = (AnnotatedDeclaredType) type; for (AnnotatedTypeMirror arg : dt.getTypeArguments()) { implicitAccessRights(arg); } } } /** * Resolve the @Myaccess type using the access rights type of an enclosing object. */ protected void resolveMyaccess(AnnotatedTypeMirror child, AnnotatedTypeMirror parent) { if (child.hasAnnotation(checker.MYACCESS)) { child.removeAnnotation(checker.MYACCESS); if (parent.hasAnnotation(checker.IMMUTABLE)) { child.addAnnotation(checker.IMMUTABLE); } else if (parent.hasAnnotation(checker.MYACCESS)) { child.addAnnotation(checker.MYACCESS); } else if (parent.hasAnnotation(checker.MUTABLE)) { child.addAnnotation(checker.MUTABLE); } } } public static class JimmuFlow extends Flow { protected JimmuChecker checker; MethodTree currentMethod; public JimmuFlow(JimmuChecker checker, CompilationUnitTree root, Set<AnnotationMirror> flowQuals, JimmuAnnotatedTypeFactory factory) { super(checker, root, flowQuals, factory); this.checker = checker; } @Override protected void merge(GenKillBits<AnnotationMirror> bits, GenKillBits<AnnotationMirror> other) { bits.or(other); for (int i = 0; i < vars.size(); i++) { if (bits.get(checker.MAYBE_THIS, i) || (bits.get(checker.THIS, i) && bits.get(checker.NOT_THIS, i))) { bits.clear(checker.THIS, i); bits.clear(checker.NOT_THIS, i); bits.set(checker.MAYBE_THIS, i); } } } @Override /* We need the state during Flow analysis */ public Void scan(Tree tree, Void p) { if (tree != null && tree.getKind() == Tree.Kind.METHOD) { try { checker.getState().enterMethodFlow((MethodTree) tree); return super.scan(tree, p); } catch (Exception e) { e.printStackTrace(); return null; } finally { checker.getState().leaveMethodFlow(); } } else { return super.scan(tree, p); } } } @Override protected Flow createFlow(JimmuChecker checker, CompilationUnitTree root, Set<AnnotationMirror> flowQuals) { return new JimmuFlow(checker, root, flowQuals, this); } @Override protected Set<AnnotationMirror> createFlowQualifiers(JimmuChecker checker) { Set<AnnotationMirror> flowQuals = new HashSet<AnnotationMirror>(); flowQuals.add(checker.THIS); flowQuals.add(checker.NOT_THIS); flowQuals.add(checker.MAYBE_THIS); return flowQuals; } /** * Return the value of the OwnedBy annotation on given type. */ public String getOwner(Element el) { List<? extends AnnotationMirror> mirrors = el.getAnnotationMirrors(); for (AnnotationMirror m : mirrors) { if ("OwnedBy".equals(m.getAnnotationType().asElement().getSimpleName().toString())) { return AnnotationUtils.elementValue(m, "value", String.class); } } return null; } public String getOwner(Tree t) { return getOwner(getAnnotatedType(t)); } public String getOwner(AnnotatedTypeMirror t) { AnnotationMirror ob = t.getAnnotation(OwnedBy.class); if (ob != null) { return AnnotationUtils.elementValue(ob, "value", String.class); } else { return null; } } public AnnotationMirror ownerAnnotation(String owner) { AnnotationUtils.AnnotationBuilder builder = new AnnotationUtils.AnnotationBuilder(env, OwnedBy.class.getCanonicalName()); builder.setValue("value", owner); return builder.build(); } }