| package org.checkerframework.dataflow.analysis; |
| |
| import com.sun.source.tree.ArrayAccessTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.LiteralTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import com.sun.tools.javac.code.Symbol.VarSymbol; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; |
| import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; |
| import org.checkerframework.dataflow.cfg.node.ClassNameNode; |
| import org.checkerframework.dataflow.cfg.node.ExplicitThisLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.FieldAccessNode; |
| import org.checkerframework.dataflow.cfg.node.LocalVariableNode; |
| import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; |
| import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode; |
| import org.checkerframework.dataflow.cfg.node.Node; |
| import org.checkerframework.dataflow.cfg.node.StringConversionNode; |
| import org.checkerframework.dataflow.cfg.node.SuperNode; |
| import org.checkerframework.dataflow.cfg.node.ThisLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.WideningConversionNode; |
| import org.checkerframework.dataflow.util.HashCodeUtils; |
| import org.checkerframework.dataflow.util.PurityUtils; |
| import org.checkerframework.javacutil.AnnotationProvider; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.ErrorReporter; |
| import org.checkerframework.javacutil.InternalUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypeAnnotationUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| |
| /** |
| * Collection of classes and helper functions to represent Java expressions about which the |
| * org.checkerframework.dataflow analysis can possibly infer facts. Expressions include: |
| * |
| * <ul> |
| * <li>Field accesses (e.g., <em>o.f</em>) |
| * <li>Local variables (e.g., <em>l</em>) |
| * <li>This reference (e.g., <em>this</em>) |
| * <li>Pure method calls (e.g., <em>o.m()</em>) |
| * <li>Unknown other expressions to mark that something else was present. |
| * </ul> |
| * |
| * @author Stefan Heule |
| */ |
| public class FlowExpressions { |
| |
| /** |
| * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. |
| * Can contain {@link Unknown} as receiver. |
| */ |
| public static FieldAccess internalReprOfFieldAccess( |
| AnnotationProvider provider, FieldAccessNode node) { |
| Receiver receiver; |
| Node receiverNode = node.getReceiver(); |
| if (node.isStatic()) { |
| receiver = new ClassName(receiverNode.getType()); |
| } else { |
| receiver = internalReprOf(provider, receiverNode); |
| } |
| return new FieldAccess(receiver, node); |
| } |
| |
| /** |
| * @return the internal representation (as {@link FieldAccess}) of a {@link FieldAccessNode}. |
| * Can contain {@link Unknown} as receiver. |
| */ |
| public static ArrayAccess internalReprOfArrayAccess( |
| AnnotationProvider provider, ArrayAccessNode node) { |
| Receiver receiver = internalReprOf(provider, node.getArray()); |
| Receiver index = internalReprOf(provider, node.getIndex()); |
| return new ArrayAccess(node.getType(), receiver, index); |
| } |
| |
| /** |
| * We ignore operations such as widening and narrowing when computing the internal |
| * representation. |
| * |
| * @return the internal representation (as {@link Receiver}) of any {@link Node}. Might contain |
| * {@link Unknown}. |
| */ |
| public static Receiver internalReprOf(AnnotationProvider provider, Node receiverNode) { |
| return internalReprOf(provider, receiverNode, false); |
| } |
| |
| /** |
| * We ignore operations such as widening and narrowing when computing the internal |
| * representation. |
| * |
| * @return the internal representation (as {@link Receiver}) of any {@link Node}. Might contain |
| * {@link Unknown}. |
| */ |
| public static Receiver internalReprOf( |
| AnnotationProvider provider, Node receiverNode, boolean allowNonDeterministic) { |
| Receiver receiver = null; |
| if (receiverNode instanceof FieldAccessNode) { |
| FieldAccessNode fan = (FieldAccessNode) receiverNode; |
| |
| if (fan.getFieldName().equals("this")) { |
| // For some reason, "className.this" is considered a field access. |
| // We right this wrong here. |
| receiver = new ThisReference(fan.getReceiver().getType()); |
| } else if (fan.getFieldName().equals("class")) { |
| // "className.class" is considered a field access. This makes sense, |
| // since .class is similar to a field access which is the equivalent |
| // of a call to getClass(). However for the purposes of dataflow |
| // analysis, and value stores, this is the equivalent of a ClassNameNode. |
| receiver = new ClassName(fan.getReceiver().getType()); |
| } else { |
| receiver = internalReprOfFieldAccess(provider, fan); |
| } |
| } else if (receiverNode instanceof ExplicitThisLiteralNode) { |
| receiver = new ThisReference(receiverNode.getType()); |
| } else if (receiverNode instanceof ThisLiteralNode) { |
| receiver = new ThisReference(receiverNode.getType()); |
| } else if (receiverNode instanceof SuperNode) { |
| receiver = new ThisReference(receiverNode.getType()); |
| } else if (receiverNode instanceof LocalVariableNode) { |
| LocalVariableNode lv = (LocalVariableNode) receiverNode; |
| receiver = new LocalVariable(lv); |
| } else if (receiverNode instanceof ArrayAccessNode) { |
| ArrayAccessNode a = (ArrayAccessNode) receiverNode; |
| receiver = internalReprOfArrayAccess(provider, a); |
| } else if (receiverNode instanceof StringConversionNode) { |
| // ignore string conversion |
| return internalReprOf(provider, ((StringConversionNode) receiverNode).getOperand()); |
| } else if (receiverNode instanceof WideningConversionNode) { |
| // ignore widening |
| return internalReprOf(provider, ((WideningConversionNode) receiverNode).getOperand()); |
| } else if (receiverNode instanceof NarrowingConversionNode) { |
| // ignore narrowing |
| return internalReprOf(provider, ((NarrowingConversionNode) receiverNode).getOperand()); |
| } else if (receiverNode instanceof ClassNameNode) { |
| ClassNameNode cn = (ClassNameNode) receiverNode; |
| receiver = new ClassName(cn.getType()); |
| } else if (receiverNode instanceof ValueLiteralNode) { |
| ValueLiteralNode vn = (ValueLiteralNode) receiverNode; |
| receiver = new ValueLiteral(vn.getType(), vn); |
| } else if (receiverNode instanceof ArrayCreationNode) { |
| ArrayCreationNode an = (ArrayCreationNode) receiverNode; |
| receiver = new ArrayCreation(an.getType(), an.getDimensions(), an.getInitializers()); |
| } else if (receiverNode instanceof MethodInvocationNode) { |
| MethodInvocationNode mn = (MethodInvocationNode) receiverNode; |
| ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn.getTree()); |
| |
| // check if this represents a boxing operation of a constant, in which |
| // case we treat the method call as deterministic, because there is no way |
| // to behave differently in two executions where two constants are being used. |
| boolean considerDeterministic = false; |
| if (invokedMethod.toString().equals("valueOf(long)") |
| && mn.getTarget().getReceiver().toString().equals("Long")) { |
| Node arg = mn.getArgument(0); |
| if (arg instanceof ValueLiteralNode) { |
| considerDeterministic = true; |
| } |
| } |
| |
| if (PurityUtils.isDeterministic(provider, invokedMethod) |
| || allowNonDeterministic |
| || considerDeterministic) { |
| List<Receiver> parameters = new ArrayList<>(); |
| for (Node p : mn.getArguments()) { |
| parameters.add(internalReprOf(provider, p)); |
| } |
| Receiver methodReceiver; |
| if (ElementUtils.isStatic(invokedMethod)) { |
| methodReceiver = new ClassName(mn.getTarget().getReceiver().getType()); |
| } else { |
| methodReceiver = internalReprOf(provider, mn.getTarget().getReceiver()); |
| } |
| receiver = new MethodCall(mn.getType(), invokedMethod, methodReceiver, parameters); |
| } |
| } |
| |
| if (receiver == null) { |
| receiver = new Unknown(receiverNode.getType()); |
| } |
| return receiver; |
| } |
| |
| /** |
| * @return the internal representation (as {@link Receiver}) of any {@link ExpressionTree}. |
| * Might contain {@link Unknown}. |
| */ |
| public static Receiver internalReprOf( |
| AnnotationProvider provider, ExpressionTree receiverTree) { |
| return internalReprOf(provider, receiverTree, true); |
| } |
| /** |
| * We ignore operations such as widening and narrowing when computing the internal |
| * representation. |
| * |
| * @return the internal representation (as {@link Receiver}) of any {@link ExpressionTree}. |
| * Might contain {@link Unknown}. |
| */ |
| public static Receiver internalReprOf( |
| AnnotationProvider provider, |
| ExpressionTree receiverTree, |
| boolean allowNonDeterministic) { |
| Receiver receiver; |
| switch (receiverTree.getKind()) { |
| case ARRAY_ACCESS: |
| ArrayAccessTree a = (ArrayAccessTree) receiverTree; |
| Receiver arrayAccessExpression = internalReprOf(provider, a.getExpression()); |
| Receiver index = internalReprOf(provider, a.getIndex()); |
| receiver = new ArrayAccess(InternalUtils.typeOf(a), arrayAccessExpression, index); |
| break; |
| case BOOLEAN_LITERAL: |
| case CHAR_LITERAL: |
| case DOUBLE_LITERAL: |
| case FLOAT_LITERAL: |
| case INT_LITERAL: |
| case LONG_LITERAL: |
| case NULL_LITERAL: |
| case STRING_LITERAL: |
| LiteralTree vn = (LiteralTree) receiverTree; |
| receiver = new ValueLiteral(InternalUtils.typeOf(receiverTree), vn.getValue()); |
| break; |
| case NEW_ARRAY: |
| receiver = |
| new ArrayCreation( |
| InternalUtils.typeOf(receiverTree), |
| Collections.<Node>emptyList(), |
| Collections.<Node>emptyList()); |
| break; |
| case METHOD_INVOCATION: |
| MethodInvocationTree mn = (MethodInvocationTree) receiverTree; |
| ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn); |
| if (PurityUtils.isDeterministic(provider, invokedMethod) || allowNonDeterministic) { |
| List<Receiver> parameters = new ArrayList<>(); |
| for (ExpressionTree p : mn.getArguments()) { |
| parameters.add(internalReprOf(provider, p)); |
| } |
| Receiver methodReceiver; |
| if (ElementUtils.isStatic(invokedMethod)) { |
| methodReceiver = new ClassName(InternalUtils.typeOf(mn.getMethodSelect())); |
| } else { |
| methodReceiver = internalReprOf(provider, mn.getMethodSelect()); |
| } |
| TypeMirror type = InternalUtils.typeOf(mn); |
| receiver = new MethodCall(type, invokedMethod, methodReceiver, parameters); |
| } else { |
| receiver = null; |
| } |
| break; |
| case MEMBER_SELECT: |
| receiver = internalRepOfMemberSelect(provider, (MemberSelectTree) receiverTree); |
| break; |
| case IDENTIFIER: |
| IdentifierTree identifierTree = (IdentifierTree) receiverTree; |
| TypeMirror typeOfId = InternalUtils.typeOf(identifierTree); |
| if (identifierTree.getName().contentEquals("this") |
| || identifierTree.getName().contentEquals("super")) { |
| receiver = new ThisReference(typeOfId); |
| break; |
| } |
| Element ele = TreeUtils.elementFromUse(identifierTree); |
| switch (ele.getKind()) { |
| case LOCAL_VARIABLE: |
| case RESOURCE_VARIABLE: |
| case EXCEPTION_PARAMETER: |
| case PARAMETER: |
| receiver = new LocalVariable(ele); |
| break; |
| case FIELD: |
| // Implicit access expression, such as "this" or a class name |
| Receiver fieldAccessExpression; |
| TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); |
| if (ElementUtils.isStatic(ele)) { |
| fieldAccessExpression = new ClassName(enclosingType); |
| } else { |
| fieldAccessExpression = new ThisReference(enclosingType); |
| } |
| receiver = |
| new FieldAccess( |
| fieldAccessExpression, typeOfId, (VariableElement) ele); |
| break; |
| case CLASS: |
| case ENUM: |
| case ANNOTATION_TYPE: |
| case INTERFACE: |
| receiver = new ClassName(ele.asType()); |
| break; |
| default: |
| receiver = null; |
| } |
| break; |
| default: |
| receiver = null; |
| } |
| |
| if (receiver == null) { |
| receiver = new Unknown(InternalUtils.typeOf(receiverTree)); |
| } |
| return receiver; |
| } |
| |
| /** |
| * Returns the implicit receiver of ele. |
| * |
| * <p>Returns either a new ClassName or a new ThisReference depending on whether ele is static |
| * or not. The passed element must be a field, method, or class. |
| * |
| * @param ele field, method, or class |
| * @return either a new ClassName or a new ThisReference depending on whether ele is static or |
| * not |
| */ |
| public static Receiver internalRepOfImplicitReceiver(Element ele) { |
| TypeMirror enclosingType = ElementUtils.enclosingClass(ele).asType(); |
| if (ElementUtils.isStatic(ele)) { |
| return new ClassName(enclosingType); |
| } else { |
| return new ThisReference(enclosingType); |
| } |
| } |
| |
| /** |
| * Returns either a new ClassName or ThisReference Receiver object for the enclosingType |
| * |
| * <p>The Tree should be an expression or a statement that does not have a receiver or an |
| * implicit receiver. For example, a local variable declaration. |
| * |
| * @param path TreePath to tree |
| * @param enclosingType type of the enclosing type |
| * @return a new ClassName or ThisReference that is a Receiver object for the enclosingType |
| */ |
| public static Receiver internalRepOfPseudoReceiver(TreePath path, TypeMirror enclosingType) { |
| if (TreeUtils.isTreeInStaticScope(path)) { |
| return new ClassName(enclosingType); |
| } else { |
| return new ThisReference(enclosingType); |
| } |
| } |
| |
| private static Receiver internalRepOfMemberSelect( |
| AnnotationProvider provider, MemberSelectTree memberSelectTree) { |
| TypeMirror expressionType = InternalUtils.typeOf(memberSelectTree.getExpression()); |
| if (TreeUtils.isClassLiteral(memberSelectTree)) { |
| return new ClassName(expressionType); |
| } |
| Element ele = TreeUtils.elementFromUse(memberSelectTree); |
| switch (ele.getKind()) { |
| case METHOD: |
| case CONSTRUCTOR: |
| return internalReprOf(provider, memberSelectTree.getExpression()); |
| case CLASS: // o instanceof MyClass.InnerClass |
| case ENUM: |
| case INTERFACE: // o instanceof MyClass.InnerInterface |
| case ANNOTATION_TYPE: |
| return new ClassName(expressionType); |
| case ENUM_CONSTANT: |
| case FIELD: |
| Receiver r = internalReprOf(provider, memberSelectTree.getExpression()); |
| return new FieldAccess(r, ElementUtils.getType(ele), (VariableElement) ele); |
| default: |
| ErrorReporter.errorAbort( |
| "Unexpected element kind: %s element: %s", ele.getKind(), ele); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns Receiver objects for the formal parameters of the method in which path is enclosed. |
| * |
| * @param annotationProvider annotationProvider |
| * @param path TreePath that is enclosed by the method |
| * @return list of Receiver objects for the formal parameters of the method in which path is |
| * enclosed |
| */ |
| public static List<Receiver> getParametersOfEnclosingMethod( |
| AnnotationProvider annotationProvider, TreePath path) { |
| MethodTree methodTree = TreeUtils.enclosingMethod(path); |
| if (methodTree == null) { |
| return null; |
| } |
| List<Receiver> internalArguments = new ArrayList<>(); |
| for (VariableTree arg : methodTree.getParameters()) { |
| internalArguments.add(internalReprOf(annotationProvider, new LocalVariableNode(arg))); |
| } |
| return internalArguments; |
| } |
| |
| public abstract static class Receiver { |
| protected final TypeMirror type; |
| |
| public Receiver(TypeMirror type) { |
| assert type != null; |
| this.type = type; |
| } |
| |
| public TypeMirror getType() { |
| return type; |
| } |
| |
| public abstract boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz); |
| |
| public boolean containsUnknown() { |
| return containsOfClass(Unknown.class); |
| } |
| |
| /** |
| * Returns true if and only if the value this expression stands for cannot be changed by a |
| * method call. This is the case for local variables, the self reference as well as final |
| * field accesses for whose receiver {@link #isUnmodifiableByOtherCode} is true. |
| */ |
| public abstract boolean isUnmodifiableByOtherCode(); |
| |
| /** @return true if and only if the two receiver are syntactically identical */ |
| public boolean syntacticEquals(Receiver other) { |
| return other == this; |
| } |
| |
| /** |
| * @return true if and only if this receiver contains a receiver that is syntactically equal |
| * to {@code other}. |
| */ |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other); |
| } |
| |
| /** |
| * Returns true if and only if {@code other} appears anywhere in this receiver or an |
| * expression appears in this receiver such that {@code other} might alias this expression, |
| * and that expression is modifiable. |
| * |
| * <p>This is always true, except for cases where the Java type information prevents |
| * aliasing and none of the subexpressions can alias 'other'. |
| */ |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return this.equals(other) || store.canAlias(this, other); |
| } |
| } |
| |
| public static class FieldAccess extends Receiver { |
| protected Receiver receiver; |
| protected VariableElement field; |
| |
| public Receiver getReceiver() { |
| return receiver; |
| } |
| |
| public VariableElement getField() { |
| return field; |
| } |
| |
| public FieldAccess(Receiver receiver, FieldAccessNode node) { |
| super(node.getType()); |
| this.receiver = receiver; |
| this.field = node.getElement(); |
| } |
| |
| public FieldAccess(Receiver receiver, TypeMirror type, VariableElement fieldElement) { |
| super(type); |
| this.receiver = receiver; |
| this.field = fieldElement; |
| } |
| |
| public boolean isFinal() { |
| return ElementUtils.isFinal(field); |
| } |
| |
| public boolean isStatic() { |
| return ElementUtils.isStatic(field); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof FieldAccess)) { |
| return false; |
| } |
| FieldAccess fa = (FieldAccess) obj; |
| return fa.getField().equals(getField()) && fa.getReceiver().equals(getReceiver()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCodeUtils.hash(getField(), getReceiver()); |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return super.containsModifiableAliasOf(store, other) |
| || receiver.containsModifiableAliasOf(store, other); |
| } |
| |
| @Override |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other) || receiver.containsSyntacticEqualReceiver(other); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| if (!(other instanceof FieldAccess)) { |
| return false; |
| } |
| FieldAccess fa = (FieldAccess) other; |
| return super.syntacticEquals(other) |
| || (fa.getField().equals(getField()) |
| && fa.getReceiver().syntacticEquals(getReceiver())); |
| } |
| |
| @Override |
| public String toString() { |
| if (receiver instanceof ClassName) { |
| return receiver.getType() + "." + field; |
| } else { |
| return receiver + "." + field; |
| } |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz) || receiver.containsOfClass(clazz); |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return isFinal() && getReceiver().isUnmodifiableByOtherCode(); |
| } |
| } |
| |
| public static class ThisReference extends Receiver { |
| public ThisReference(TypeMirror type) { |
| super(type); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj != null && obj instanceof ThisReference; |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCodeUtils.hash(0); |
| } |
| |
| @Override |
| public String toString() { |
| return "this"; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| return other instanceof ThisReference; |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return true; |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return false; // 'this' is not modifiable |
| } |
| } |
| |
| /** |
| * A ClassName represents the occurrence of a class as part of a static field access or method |
| * invocation. |
| */ |
| public static class ClassName extends Receiver { |
| public ClassName(TypeMirror type) { |
| super(type); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof ClassName)) { |
| return false; |
| } |
| ClassName other = (ClassName) obj; |
| return getType().toString().equals(other.getType().toString()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCodeUtils.hash(getType().toString()); |
| } |
| |
| @Override |
| public String toString() { |
| return getType().toString() + ".class"; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| return this.equals(other); |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return true; |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return false; // not modifiable |
| } |
| } |
| |
| public static class Unknown extends Receiver { |
| public Unknown(TypeMirror type) { |
| super(type); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj == this; |
| } |
| |
| @Override |
| public int hashCode() { |
| return System.identityHashCode(this); |
| } |
| |
| @Override |
| public String toString() { |
| return "?"; |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return true; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz); |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return false; |
| } |
| } |
| |
| public static class LocalVariable extends Receiver { |
| protected Element element; |
| |
| public LocalVariable(LocalVariableNode localVar) { |
| super(localVar.getType()); |
| this.element = localVar.getElement(); |
| } |
| |
| public LocalVariable(Element elem) { |
| super(ElementUtils.getType(elem)); |
| this.element = elem; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof LocalVariable)) { |
| return false; |
| } |
| LocalVariable other = (LocalVariable) obj; |
| VarSymbol vs = (VarSymbol) element; |
| VarSymbol vsother = (VarSymbol) other.element; |
| // Use TypeAnnotationUtils.unannotatedType(type).toString().equals(...) instead of Types.isSameType(...) |
| // because Types requires a processing environment, and FlowExpressions is |
| // designed to be independent of processing environment. See also |
| // calls to getType().toString() in FlowExpressions. |
| return vsother.name.contentEquals(vs.name) |
| && TypeAnnotationUtils.unannotatedType(vsother.type) |
| .toString() |
| .equals(TypeAnnotationUtils.unannotatedType(vs.type).toString()) |
| && vsother.owner.toString().equals(vs.owner.toString()); |
| } |
| |
| public Element getElement() { |
| return element; |
| } |
| |
| @Override |
| public int hashCode() { |
| VarSymbol vs = (VarSymbol) element; |
| return HashCodeUtils.hash( |
| vs.name.toString(), |
| TypeAnnotationUtils.unannotatedType(vs.type).toString(), |
| vs.owner.toString()); |
| } |
| |
| @Override |
| public String toString() { |
| return element.toString(); |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| if (!(other instanceof LocalVariable)) { |
| return false; |
| } |
| LocalVariable l = (LocalVariable) other; |
| return l.equals(this); |
| } |
| |
| @Override |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other); |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return true; |
| } |
| } |
| |
| public static class ValueLiteral extends Receiver { |
| |
| protected final Object value; |
| |
| public ValueLiteral(TypeMirror type, ValueLiteralNode node) { |
| super(type); |
| value = node.getValue(); |
| } |
| |
| public ValueLiteral(TypeMirror type, Object value) { |
| super(type); |
| this.value = value; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| return getClass().equals(clazz); |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return true; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof ValueLiteral)) { |
| return false; |
| } |
| ValueLiteral other = (ValueLiteral) obj; |
| if (value == null) { |
| return type.toString().equals(other.type.toString()) && other.value == null; |
| } |
| return type.toString().equals(other.type.toString()) && value.equals(other.value); |
| } |
| |
| @Override |
| public String toString() { |
| if (TypesUtils.isString(type)) { |
| return "\"" + value + "\""; |
| } else if (type.getKind() == TypeKind.LONG) { |
| return value.toString() + "L"; |
| } |
| return value == null ? "null" : value.toString(); |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCodeUtils.hash(value, type.toString()); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| return this.equals(other); |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| return false; // not modifiable |
| } |
| |
| public Object getValue() { |
| return value; |
| } |
| } |
| |
| /** A method call. */ |
| public static class MethodCall extends Receiver { |
| |
| protected final Receiver receiver; |
| protected final List<Receiver> parameters; |
| protected final ExecutableElement method; |
| |
| public MethodCall( |
| TypeMirror type, |
| ExecutableElement method, |
| Receiver receiver, |
| List<Receiver> parameters) { |
| super(type); |
| this.receiver = receiver; |
| this.parameters = parameters; |
| this.method = method; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| if (getClass().equals(clazz)) { |
| return true; |
| } |
| if (receiver.containsOfClass(clazz)) { |
| return true; |
| } |
| for (Receiver p : parameters) { |
| if (p.containsOfClass(clazz)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** @return the method call receiver (for inspection only - do not modify) */ |
| public Receiver getReceiver() { |
| return receiver; |
| } |
| |
| /** |
| * @return the method call parameters (for inspection only - do not modify any of the |
| * parameters) |
| */ |
| public List<Receiver> getParameters() { |
| return Collections.unmodifiableList(parameters); |
| } |
| |
| /** @return the ExecutableElement for the method call */ |
| public ExecutableElement getElement() { |
| return method; |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return false; |
| } |
| |
| @Override |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other) || receiver.syntacticEquals(other); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| if (!(other instanceof MethodCall)) { |
| return false; |
| } |
| MethodCall otherMethod = (MethodCall) other; |
| if (!receiver.syntacticEquals(otherMethod.receiver)) { |
| return false; |
| } |
| if (parameters.size() != otherMethod.parameters.size()) { |
| return false; |
| } |
| int i = 0; |
| for (Receiver p : parameters) { |
| if (!p.syntacticEquals(otherMethod.parameters.get(i))) { |
| return false; |
| } |
| i++; |
| } |
| return method.equals(otherMethod.method); |
| } |
| |
| public boolean containsSyntacticEqualParameter(LocalVariable var) { |
| for (Receiver p : parameters) { |
| if (p.containsSyntacticEqualReceiver(var)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| if (receiver.containsModifiableAliasOf(store, other)) { |
| return true; |
| } |
| for (Receiver p : parameters) { |
| if (p.containsModifiableAliasOf(store, other)) { |
| return true; |
| } |
| } |
| return false; // the method call itself is not modifiable |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof MethodCall)) { |
| return false; |
| } |
| MethodCall other = (MethodCall) obj; |
| int i = 0; |
| for (Receiver p : parameters) { |
| if (!p.equals(other.parameters.get(i))) { |
| return false; |
| } |
| i++; |
| } |
| return receiver.equals(other.receiver) && method.equals(other.method); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = HashCodeUtils.hash(method, receiver); |
| for (Receiver p : parameters) { |
| hash = HashCodeUtils.hash(hash, p); |
| } |
| return hash; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| if (receiver instanceof ClassName) { |
| result.append(receiver.getType()); |
| } else { |
| result.append(receiver); |
| } |
| result.append("."); |
| String methodName = method.getSimpleName().toString(); |
| result.append(methodName); |
| result.append("("); |
| boolean first = true; |
| for (Receiver p : parameters) { |
| if (!first) { |
| result.append(", "); |
| } |
| result.append(p.toString()); |
| first = false; |
| } |
| result.append(")"); |
| return result.toString(); |
| } |
| } |
| |
| /** A deterministic method call. */ |
| public static class ArrayAccess extends Receiver { |
| |
| protected final Receiver receiver; |
| protected final Receiver index; |
| |
| public ArrayAccess(TypeMirror type, Receiver receiver, Receiver index) { |
| super(type); |
| this.receiver = receiver; |
| this.index = index; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { |
| if (getClass().equals(clazz)) { |
| return true; |
| } |
| if (receiver.containsOfClass(clazz)) { |
| return true; |
| } |
| return index.containsOfClass(clazz); |
| } |
| |
| public Receiver getReceiver() { |
| return receiver; |
| } |
| |
| public Receiver getIndex() { |
| return index; |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return false; |
| } |
| |
| @Override |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other) |
| || receiver.syntacticEquals(other) |
| || index.syntacticEquals(other); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| if (!(other instanceof ArrayAccess)) { |
| return false; |
| } |
| ArrayAccess otherArrayAccess = (ArrayAccess) other; |
| if (!receiver.syntacticEquals(otherArrayAccess.receiver)) { |
| return false; |
| } |
| return index.syntacticEquals(otherArrayAccess.index); |
| } |
| |
| @Override |
| public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { |
| if (receiver.containsModifiableAliasOf(store, other)) { |
| return true; |
| } |
| return index.containsModifiableAliasOf(store, other); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof ArrayAccess)) { |
| return false; |
| } |
| ArrayAccess other = (ArrayAccess) obj; |
| return receiver.equals(other.receiver) && index.equals(other.index); |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashCodeUtils.hash(receiver, index); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| result.append(receiver.toString()); |
| result.append("["); |
| result.append(index.toString()); |
| result.append("]"); |
| return result.toString(); |
| } |
| } |
| |
| public static class ArrayCreation extends Receiver { |
| |
| protected List<Node> dimensions; |
| protected List<Node> initializers; |
| |
| public ArrayCreation(TypeMirror type, List<Node> dimensions, List<Node> initializers) { |
| super(type); |
| this.dimensions = dimensions; |
| this.initializers = initializers; |
| } |
| |
| public List<Node> getDimensions() { |
| return dimensions; |
| } |
| |
| public List<Node> getInitializers() { |
| return initializers; |
| } |
| |
| @Override |
| public boolean containsOfClass(Class<? extends Receiver> clazz) { |
| for (Node n : dimensions) { |
| if (n.getClass().equals(clazz)) return true; |
| } |
| for (Node n : initializers) { |
| if (n.getClass().equals(clazz)) return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean isUnmodifiableByOtherCode() { |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((dimensions == null) ? 0 : dimensions.hashCode()); |
| result = prime * result + ((initializers == null) ? 0 : initializers.hashCode()); |
| result = prime * result + HashCodeUtils.hash(getType().toString()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof ArrayCreation)) { |
| return false; |
| } |
| ArrayCreation other = (ArrayCreation) obj; |
| return this.dimensions.equals(other.getDimensions()) |
| && this.initializers.equals(other.getInitializers()) |
| && getType().toString().equals(other.getType().toString()); |
| } |
| |
| @Override |
| public boolean syntacticEquals(Receiver other) { |
| return this.equals(other); |
| } |
| |
| @Override |
| public boolean containsSyntacticEqualReceiver(Receiver other) { |
| return syntacticEquals(other); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("new " + type); |
| if (!dimensions.isEmpty()) { |
| boolean needComma = false; |
| sb.append(" ("); |
| for (Node dim : dimensions) { |
| if (needComma) { |
| sb.append(", "); |
| } |
| sb.append(dim); |
| needComma = true; |
| } |
| sb.append(")"); |
| } |
| if (!initializers.isEmpty()) { |
| boolean needComma = false; |
| sb.append(" = {"); |
| for (Node init : initializers) { |
| if (needComma) { |
| sb.append(", "); |
| } |
| sb.append(init); |
| needComma = true; |
| } |
| sb.append("}"); |
| } |
| return sb.toString(); |
| } |
| } |
| } |