blob: 85ad7b4b00ecdedd8a7d8d373a3a5daa0d487aab [file] [log] [blame]
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();
}
}
}