blob: 607f5dc0c55ce04a5fe074ec4ce95af1811c2697 [file] [log] [blame]
package org.checkerframework.framework.flow;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BinaryOperator;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
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.Node;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
import org.checkerframework.dataflow.cfg.visualize.StringCFGVisualizer;
import org.checkerframework.dataflow.expression.ArrayAccess;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.dataflow.util.PurityUtils;
import org.checkerframework.framework.qual.MonotonicQualifier;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.Pair;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.ToStringComparator;
import org.plumelib.util.UniqueId;
/**
* A store for the Checker Framework analysis. It tracks the annotations of memory locations such as
* local variables and fields.
*
* <p>When adding a new field to track values for a code construct (similar to {@code
* localVariableValues} and {@code thisValue}), it is important to review all constructors and
* methods in this class for locations where the new field must be handled (such as the copy
* constructor and {@code clearValue}), as well as all constructors/methods in subclasses of {code
* CFAbstractStore}. Note that this includes not only overridden methods in the subclasses, but new
* methods in the subclasses as well. Also check if
* BaseTypeVisitor#getJavaExpressionContextFromNode(Node) needs to be updated. Failing to do so may
* result in silent failures that are time consuming to debug.
*/
// TODO: Split this class into two parts: one that is reusable generally and
// one that is specific to the Checker Framework.
public abstract class CFAbstractStore<V extends CFAbstractValue<V>, S extends CFAbstractStore<V, S>>
implements Store<S>, UniqueId {
/** The analysis class this store belongs to. */
protected final CFAbstractAnalysis<V, S, ?> analysis;
/** Information collected about local variables (including method parameters). */
protected final Map<LocalVariable, V> localVariableValues;
/** Information collected about the current object. */
protected V thisValue;
/** Information collected about fields, using the internal representation {@link FieldAccess}. */
protected Map<FieldAccess, V> fieldValues;
/**
* Returns information about fields. Clients should not side-effect the returned value, which is
* aliased to internal state.
*
* @return information about fields
*/
public Map<FieldAccess, V> getFieldValues() {
return fieldValues;
}
/**
* Information collected about array elements, using the internal representation {@link
* ArrayAccess}.
*/
protected Map<ArrayAccess, V> arrayValues;
/**
* Information collected about method calls, using the internal representation {@link MethodCall}.
*/
protected Map<MethodCall, V> methodValues;
/**
* Information collected about <i>classname</i>.class values, using the internal representation
* {@link ClassName}.
*/
protected Map<ClassName, V> classValues;
/**
* Should the analysis use sequential Java semantics (i.e., assume that only one thread is running
* at all times)?
*/
protected final boolean sequentialSemantics;
/** The unique ID for the next-created object. */
static final AtomicLong nextUid = new AtomicLong(0);
/** The unique ID of this object. */
final transient long uid = nextUid.getAndIncrement();
@Override
public long getUid() {
return uid;
}
/* --------------------------------------------------------- */
/* Initialization */
/* --------------------------------------------------------- */
/**
* Creates a new CFAbstractStore.
*
* @param analysis the analysis class this store belongs to
* @param sequentialSemantics should the analysis use sequential Java semantics?
*/
protected CFAbstractStore(CFAbstractAnalysis<V, S, ?> analysis, boolean sequentialSemantics) {
this.analysis = analysis;
localVariableValues = new HashMap<>();
thisValue = null;
fieldValues = new HashMap<>();
methodValues = new HashMap<>();
arrayValues = new HashMap<>();
classValues = new HashMap<>();
this.sequentialSemantics = sequentialSemantics;
}
/** Copy constructor. */
protected CFAbstractStore(CFAbstractStore<V, S> other) {
this.analysis = other.analysis;
localVariableValues = new HashMap<>(other.localVariableValues);
thisValue = other.thisValue;
fieldValues = new HashMap<>(other.fieldValues);
methodValues = new HashMap<>(other.methodValues);
arrayValues = new HashMap<>(other.arrayValues);
classValues = new HashMap<>(other.classValues);
sequentialSemantics = other.sequentialSemantics;
}
/**
* Set the abstract value of a method parameter (only adds the information to the store, does not
* remove any other knowledge). Any previous information is erased; this method should only be
* used to initialize the abstract value.
*/
public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) {
if (value != null) {
localVariableValues.put(new LocalVariable(p.getElement()), value);
}
}
/**
* Set the value of the current object. Any previous information is erased; this method should
* only be used to initialize the value.
*/
public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) {
if (a != null) {
thisValue = analysis.createSingleAnnotationValue(a, underlyingType);
}
}
/**
* Indicates whether the given method is side-effect-free as far as the current store is
* concerned. In some cases, a store for a checker allows for other mechanisms to specify whether
* a method is side-effect-free. For example, unannotated methods may be considered
* side-effect-free by default.
*
* @param atypeFactory the type factory used to retrieve annotations on the method element
* @param method the method element
* @return whether the method is side-effect-free
*/
protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
return PurityUtils.isSideEffectFree(atypeFactory, method);
}
/* --------------------------------------------------------- */
/* Handling of fields */
/* --------------------------------------------------------- */
/**
* Remove any information that might not be valid any more after a method call, and add
* information guaranteed by the method.
*
* <ol>
* <li>If the method is side-effect-free (as indicated by {@link
* org.checkerframework.dataflow.qual.SideEffectFree} or {@link
* org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed.
* <li>Otherwise, all information about field accesses {@code a.f} needs to be removed, except
* if the method {@code n} cannot modify {@code a.f} (e.g., if {@code a} is a local variable
* or {@code this}, and {@code f} is final).
* <li>Furthermore, if the field has a monotonic annotation, then its information can also be
* kept.
* </ol>
*
* Furthermore, if the method is deterministic, we store its result {@code val} in the store.
*/
public void updateForMethodCall(
MethodInvocationNode n, AnnotatedTypeFactory atypeFactory, V val) {
ExecutableElement method = n.getTarget().getMethod();
// case 1: remove information if necessary
if (!(analysis.checker.hasOption("assumeSideEffectFree")
|| analysis.checker.hasOption("assumePure")
|| isSideEffectFree(atypeFactory, method))) {
boolean sideEffectsUnrefineAliases =
((GenericAnnotatedTypeFactory) atypeFactory).sideEffectsUnrefineAliases;
// update local variables
// TODO: Also remove if any element/argument to the annotation is not
// isUnmodifiableByOtherCode. Example: @KeyFor("valueThatCanBeMutated").
if (sideEffectsUnrefineAliases) {
localVariableValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode());
}
// update this value
if (sideEffectsUnrefineAliases) {
thisValue = null;
}
// update field values
if (sideEffectsUnrefineAliases) {
fieldValues.entrySet().removeIf(e -> !e.getKey().isUnmodifiableByOtherCode());
} else {
Map<FieldAccess, V> newFieldValues =
new HashMap<>(CollectionsPlume.mapCapacity(fieldValues));
for (Map.Entry<FieldAccess, V> e : fieldValues.entrySet()) {
FieldAccess fieldAccess = e.getKey();
V otherVal = e.getValue();
// case 3: the field has a monotonic annotation
if (!((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
.getSupportedMonotonicTypeQualifiers()
.isEmpty()) {
List<Pair<AnnotationMirror, AnnotationMirror>> fieldAnnotations =
atypeFactory.getAnnotationWithMetaAnnotation(
fieldAccess.getField(), MonotonicQualifier.class);
V newOtherVal = null;
for (Pair<AnnotationMirror, AnnotationMirror> fieldAnnotation : fieldAnnotations) {
AnnotationMirror monotonicAnnotation = fieldAnnotation.second;
@SuppressWarnings("deprecation") // permitted for use in the framework
Name annotation =
AnnotationUtils.getElementValueClassName(monotonicAnnotation, "value", false);
AnnotationMirror target =
AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annotation);
// Make sure the 'target' annotation is present.
if (AnnotationUtils.containsSame(otherVal.getAnnotations(), target)) {
newOtherVal =
analysis
.createSingleAnnotationValue(target, otherVal.getUnderlyingType())
.mostSpecific(newOtherVal, null);
}
}
if (newOtherVal != null) {
// keep information for all hierarchies where we had a
// monotone annotation.
newFieldValues.put(fieldAccess, newOtherVal);
continue;
}
}
// case 2:
if (!fieldAccess.isUnassignableByOtherCode()) {
continue; // remove information completely
}
// keep information
newFieldValues.put(fieldAccess, otherVal);
}
fieldValues = newFieldValues;
}
// update array values
arrayValues.clear();
// update method values
methodValues.keySet().removeIf(e -> !e.isUnmodifiableByOtherCode());
}
// store information about method call if possible
JavaExpression methodCall = JavaExpression.fromNode(n);
replaceValue(methodCall, val);
}
/**
* Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to store
* the information depending on the type of the expression {@code expr}).
*
* <p>This method does not take care of removing other information that might be influenced by
* changes to certain parts of the state.
*
* <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
* and old value are taken (according to the lattice). Note that this happens per hierarchy, and
* if the store already contains information about a hierarchy other than {@code a}s hierarchy,
* that information is preserved.
*
* <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
* store.
*
* @param expr an expression
* @param a an annotation for the expression
*/
public void insertValue(JavaExpression expr, AnnotationMirror a) {
insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType()));
}
/**
* Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic
* expressions to be stored.
*
* <p>For an explanation of when to permit nondeterministic expressions, see {@link
* #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
*
* @param expr an expression
* @param a an annotation for the expression
*/
public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) {
insertValuePermitNondeterministic(
expr, analysis.createSingleAnnotationValue(a, expr.getType()));
}
/**
* Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where to
* store the information depending on the type of the expression {@code expr}).
*
* <p>This method does not take care of removing other information that might be influenced by
* changes to certain parts of the state.
*
* <p>If there is already a value {@code v} present for {@code expr}, then the greatest lower
* bound of the new and old value is inserted into the store.
*
* <p>Note that this happens per hierarchy, and if the store already contains information about a
* hierarchy other than {@code newAnno}'s hierarchy, that information is preserved.
*
* <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
* store.
*
* @param expr an expression
* @param newAnno the expression's annotation
*/
public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) {
insertOrRefine(expr, newAnno, false);
}
/**
* Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic
* expressions to be inserted.
*
* <p>For an explanation of when to permit nondeterministic expressions, see {@link
* #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
*
* @param expr an expression
* @param newAnno the expression's annotation
*/
public final void insertOrRefinePermitNondeterministic(
JavaExpression expr, AnnotationMirror newAnno) {
insertOrRefine(expr, newAnno, true);
}
/**
* Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link
* #insertOrRefinePermitNondeterministic}.
*
* @param expr an expression
* @param newAnno the expression's annotation
* @param permitNondeterministic whether nondeterministic expressions may be inserted into the
* store
*/
protected void insertOrRefine(
JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) {
if (!canInsertJavaExpression(expr)) {
return;
}
if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
return;
}
V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType());
V oldValue = getValue(expr);
if (oldValue == null) {
insertValue(
expr,
analysis.createSingleAnnotationValue(newAnno, expr.getType()),
permitNondeterministic);
return;
}
computeNewValueAndInsert(
expr, newValue, CFAbstractValue<V>::greatestLowerBound, permitNondeterministic);
}
/** Returns true if {@code expr} can be stored in this store. */
public static boolean canInsertJavaExpression(JavaExpression expr) {
if (expr instanceof FieldAccess
|| expr instanceof ThisReference
|| expr instanceof LocalVariable
|| expr instanceof MethodCall
|| expr instanceof ArrayAccess
|| expr instanceof ClassName) {
return !expr.containsUnknown();
}
return false;
}
/**
* Add the abstract value {@code value} for the expression {@code expr} (correctly deciding where
* to store the information depending on the type of the expression {@code expr}).
*
* <p>This method does not take care of removing other information that might be influenced by
* changes to certain parts of the state.
*
* <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
* and old value are taken (according to the lattice). Note that this happens per hierarchy, and
* if the store already contains information about a hierarchy for which {@code value} does not
* contain information, then that information is preserved.
*
* <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
* store.
*
* @param expr the expression to insert in the store
* @param value the value of the expression
*/
public final void insertValue(JavaExpression expr, @Nullable V value) {
insertValue(expr, value, false);
}
/**
* Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if
* {@code expr} is nondeterministic.
*
* <p>Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in
* the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of
* {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed.
*
* <p>However, contracts can mention a nondeterministic JavaExpression. For example, a contract
* might have a postcondition that{@code nondet()} is odd. This means that the next call to{@code
* nondet()} will return odd. Such a postcondition may be evicted from the store by calling a
* side-effecting method.
*
* @param expr the expression to insert in the store
* @param value the value of the expression
*/
public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) {
insertValue(expr, value, true);
}
/**
* Returns true if the given (expression, value) pair can be inserted in the store, namely if the
* value is non-null and the expression does not contain unknown or a nondeterministic expression.
*
* <p>This method returning true does not guarantee that the value will be inserted; the
* implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still
* not insert it.
*
* @param expr the expression to insert in the store
* @param value the value of the expression
* @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if
* true, permits nondeterministic expressions to be placed in the store
* @return true if the given (expression, value) pair can be inserted in the store
*/
@EnsuresNonNullIf(expression = "#2", result = true)
protected boolean shouldInsert(
JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
if (value == null) {
// No need to insert a null abstract value because it represents
// top and top is also the default value.
return false;
}
if (expr.containsUnknown()) {
// Expressions containing unknown expressions are not stored.
return false;
}
if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
// Nondeterministic expressions may not be stored.
// (They are likely to be quickly evicted, as soon as a side-effecting method is called.)
return false;
}
return true;
}
/**
* Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link
* #insertValuePermitNondeterministic}.
*
* <p>Every overriding implementation should start with
*
* <pre>{@code
* if (!shouldInsert) {
* return;
* }
* }</pre>
*
* @param expr the expression to insert in the store
* @param value the value of the expression
* @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
* true, permits nondeterministic expressions to be placed in the store
*/
protected void insertValue(
JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
computeNewValueAndInsert(
expr, value, (old, newValue) -> newValue.mostSpecific(old, null), permitNondeterministic);
}
/**
* Inserts the result of applying {@code merger} to {@code value} and the previous value for
* {@code expr}.
*
* @param expr the JavaExpression
* @param value the value of the JavaExpression
* @param merger the function used to merge {@code value} and the previous value of {@code expr}
* @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
* true, permits nondeterministic expressions to be placed in the store
*/
protected void computeNewValueAndInsert(
JavaExpression expr,
@Nullable V value,
BinaryOperator<V> merger,
boolean permitNondeterministic) {
if (!shouldInsert(expr, value, permitNondeterministic)) {
return;
}
if (expr instanceof LocalVariable) {
LocalVariable localVar = (LocalVariable) expr;
V oldValue = localVariableValues.get(localVar);
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
localVariableValues.put(localVar, newValue);
}
} else if (expr instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) expr;
// Only store information about final fields (where the receiver is
// also fixed) if concurrent semantics are enabled.
boolean isMonotonic = isMonotonicUpdate(fieldAcc, value);
if (sequentialSemantics || isMonotonic || fieldAcc.isUnassignableByOtherCode()) {
V oldValue = fieldValues.get(fieldAcc);
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
fieldValues.put(fieldAcc, newValue);
}
}
} else if (expr instanceof MethodCall) {
MethodCall method = (MethodCall) expr;
// Don't store any information if concurrent semantics are enabled.
if (sequentialSemantics) {
V oldValue = methodValues.get(method);
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
methodValues.put(method, newValue);
}
}
} else if (expr instanceof ArrayAccess) {
ArrayAccess arrayAccess = (ArrayAccess) expr;
if (sequentialSemantics) {
V oldValue = arrayValues.get(arrayAccess);
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
arrayValues.put(arrayAccess, newValue);
}
}
} else if (expr instanceof ThisReference) {
ThisReference thisRef = (ThisReference) expr;
if (sequentialSemantics || thisRef.isUnassignableByOtherCode()) {
V oldValue = thisValue;
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
thisValue = newValue;
}
}
} else if (expr instanceof ClassName) {
ClassName className = (ClassName) expr;
if (sequentialSemantics || className.isUnassignableByOtherCode()) {
V oldValue = classValues.get(className);
V newValue = merger.apply(oldValue, value);
if (newValue != null) {
classValues.put(className, newValue);
}
}
} else {
// No other types of expressions need to be stored.
}
}
/**
* Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier.
* (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is
* true.
*
* @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier
* (e.g. @MonotonicNonNull to @NonNull)
*/
protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) {
if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) {
return false;
}
boolean isMonotonic = false;
// TODO: This check for !sequentialSemantics is an optimization that breaks the contract of
// the method, since the method name and documentation say nothing about sequential
// semantics. This check should be performed by callers of this method when needed.
// TODO: Update the javadoc of this method when the above to-do item is addressed.
if (!sequentialSemantics) { // only compute if necessary
AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory;
List<Pair<AnnotationMirror, AnnotationMirror>> fieldAnnotations =
atypeFactory.getAnnotationWithMetaAnnotation(
fieldAcc.getField(), MonotonicQualifier.class);
for (Pair<AnnotationMirror, AnnotationMirror> fieldAnnotation : fieldAnnotations) {
AnnotationMirror monotonicAnnotation = fieldAnnotation.second;
@SuppressWarnings("deprecation") // permitted for use in the framework
Name annotation =
AnnotationUtils.getElementValueClassName(monotonicAnnotation, "value", false);
AnnotationMirror target =
AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annotation);
// Make sure the 'target' annotation is present.
if (AnnotationUtils.containsSame(value.getAnnotations(), target)) {
isMonotonic = true;
break;
}
}
}
return isMonotonic;
}
public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) {
if (a == null) {
return;
}
V value = analysis.createSingleAnnotationValue(a, underlyingType);
V oldValue = thisValue;
V newValue = value.mostSpecific(oldValue, null);
if (newValue != null) {
thisValue = newValue;
}
}
/**
* Completely replaces the abstract value {@code value} for the expression {@code expr} (correctly
* deciding where to store the information depending on the type of the expression {@code expr}).
* Any previous information is discarded.
*
* <p>This method does not take care of removing other information that might be influenced by
* changes to certain parts of the state.
*/
public void replaceValue(JavaExpression expr, @Nullable V value) {
clearValue(expr);
insertValue(expr, value);
}
/**
* Remove any knowledge about the expression {@code expr} (correctly deciding where to remove the
* information depending on the type of the expression {@code expr}).
*/
public void clearValue(JavaExpression expr) {
if (expr.containsUnknown()) {
// Expressions containing unknown expressions are not stored.
return;
}
if (expr instanceof LocalVariable) {
LocalVariable localVar = (LocalVariable) expr;
localVariableValues.remove(localVar);
} else if (expr instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) expr;
fieldValues.remove(fieldAcc);
} else if (expr instanceof MethodCall) {
MethodCall method = (MethodCall) expr;
methodValues.remove(method);
} else if (expr instanceof ArrayAccess) {
ArrayAccess a = (ArrayAccess) expr;
arrayValues.remove(a);
} else if (expr instanceof ClassName) {
ClassName c = (ClassName) expr;
classValues.remove(c);
} else { // thisValue ...
// No other types of expressions are stored.
}
}
/**
* Returns the current abstract value of a Java expression, or {@code null} if no information is
* available.
*
* @return the current abstract value of a Java expression, or {@code null} if no information is
* available
*/
public @Nullable V getValue(JavaExpression expr) {
if (expr instanceof LocalVariable) {
LocalVariable localVar = (LocalVariable) expr;
return localVariableValues.get(localVar);
} else if (expr instanceof ThisReference) {
return thisValue;
} else if (expr instanceof FieldAccess) {
FieldAccess fieldAcc = (FieldAccess) expr;
return fieldValues.get(fieldAcc);
} else if (expr instanceof MethodCall) {
MethodCall method = (MethodCall) expr;
return methodValues.get(method);
} else if (expr instanceof ArrayAccess) {
ArrayAccess a = (ArrayAccess) expr;
return arrayValues.get(a);
} else if (expr instanceof ClassName) {
ClassName c = (ClassName) expr;
return classValues.get(c);
} else {
throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")");
}
}
/**
* Returns the current abstract value of a field access, or {@code null} if no information is
* available.
*
* @param n the node whose abstract value to return
* @return the current abstract value of a field access, or {@code null} if no information is
* available
*/
public @Nullable V getValue(FieldAccessNode n) {
JavaExpression je = JavaExpression.fromNodeFieldAccess(n);
if (je instanceof FieldAccess) {
return fieldValues.get((FieldAccess) je);
} else if (je instanceof ClassName) {
return classValues.get((ClassName) je);
} else if (je instanceof ThisReference) {
// "return thisValue" is wrong, because the node refers to an outer this.
// So, return null for now. TODO: improve.
return null;
} else {
throw new BugInCF(
"Unexpected JavaExpression %s %s for FieldAccessNode %s",
je.getClass().getSimpleName(), je, n);
}
}
/**
* Returns the current abstract value of a field access, or {@code null} if no information is
* available.
*
* @param fieldAccess the field access to look up in this store
* @return current abstract value of a field access, or {@code null} if no information is
* available
*/
public @Nullable V getFieldValue(FieldAccess fieldAccess) {
return fieldValues.get(fieldAccess);
}
/**
* Returns the current abstract value of a method call, or {@code null} if no information is
* available.
*
* @param n a method call
* @return the current abstract value of a method call, or {@code null} if no information is
* available
*/
public @Nullable V getValue(MethodInvocationNode n) {
JavaExpression method = JavaExpression.fromNode(n);
if (method == null) {
return null;
}
return methodValues.get(method);
}
/**
* Returns the current abstract value of a field access, or {@code null} if no information is
* available.
*
* @param n the node whose abstract value to return
* @return the current abstract value of a field access, or {@code null} if no information is
* available
*/
public @Nullable V getValue(ArrayAccessNode n) {
ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n);
return arrayValues.get(arrayAccess);
}
/**
* Update the information in the store by considering an assignment with target {@code n}.
*
* @param n the left-hand side of an assignment
* @param val the right-hand value of an assignment
*/
public void updateForAssignment(Node n, @Nullable V val) {
JavaExpression je = JavaExpression.fromNode(n);
if (je instanceof ArrayAccess) {
updateForArrayAssignment((ArrayAccess) je, val);
} else if (je instanceof FieldAccess) {
updateForFieldAccessAssignment((FieldAccess) je, val);
} else if (je instanceof LocalVariable) {
updateForLocalVariableAssignment((LocalVariable) je, val);
} else {
throw new BugInCF("Unexpected je of class " + je.getClass());
}
}
/**
* Update the information in the store by considering a field assignment with target {@code n},
* where the right hand side has the abstract value {@code val}.
*
* @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
* abstract value is not known).
*/
protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) {
removeConflicting(fieldAccess, val);
if (!fieldAccess.containsUnknown() && val != null) {
// Only store information about final fields (where the receiver is
// also fixed) if concurrent semantics are enabled.
if (sequentialSemantics
|| isMonotonicUpdate(fieldAccess, val)
|| fieldAccess.isUnassignableByOtherCode()) {
fieldValues.put(fieldAccess, val);
}
}
}
/**
* Update the information in the store by considering an assignment with target {@code n}, where
* the target is an array access.
*
* <p>See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this
* method.
*/
protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) {
removeConflicting(arrayAccess, val);
if (!arrayAccess.containsUnknown() && val != null) {
// Only store information about final fields (where the receiver is
// also fixed) if concurrent semantics are enabled.
if (sequentialSemantics) {
arrayValues.put(arrayAccess, val);
}
}
}
/**
* Set the abstract value of a local variable in the store. Overwrites any value that might have
* been available previously.
*
* @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
* abstract value is not known).
*/
protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) {
removeConflicting(receiver);
if (val != null) {
localVariableValues.put(receiver, val);
}
}
/**
* Remove any information in this store that might not be true any more after {@code fieldAccess}
* has been assigned a new value (with the abstract value {@code val}). This includes the
* following steps (assume that {@code fieldAccess} is of the form <em>a.f</em> for some
* <em>a</em>.
*
* <ol>
* <li value="1">Update the abstract value of other field accesses <em>b.g</em> where the field
* is equal (that is, <em>f=g</em>), and the receiver <em>b</em> might alias the receiver of
* {@code fieldAccess}, <em>a</em>. This update will raise the abstract value for such field
* accesses to at least {@code val} (or the old value, if that was less precise). However,
* this is only necessary if the field <em>g</em> is not final.
* <li value="2">Remove any abstract values for field accesses <em>b.g</em> where {@code
* fieldAccess} might alias any expression in the receiver <em>b</em>.
* <li value="3">Remove any information about method calls.
* <li value="4">Remove any abstract values an array access <em>b[i]</em> where {@code
* fieldAccess} might alias any expression in the receiver <em>a</em> or index <em>i</em>.
* </ol>
*
* @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
* abstract value is not known).
*/
protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) {
final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
fieldValues.entrySet().iterator();
while (fieldValuesIterator.hasNext()) {
Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
FieldAccess otherFieldAccess = entry.getKey();
V otherVal = entry.getValue();
// case 2:
if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) {
fieldValuesIterator.remove(); // remove information completely
}
// case 1:
else if (fieldAccess.getField().equals(otherFieldAccess.getField())) {
if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) {
if (!otherFieldAccess.isFinal()) {
if (val != null) {
V newVal = val.leastUpperBound(otherVal);
entry.setValue(newVal);
} else {
// remove information completely
fieldValuesIterator.remove();
}
}
}
}
}
final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
arrayValues.entrySet().iterator();
while (arrayValuesIterator.hasNext()) {
Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
ArrayAccess otherArrayAccess = entry.getKey();
if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) {
// remove information completely
arrayValuesIterator.remove();
}
}
// case 3:
methodValues.clear();
}
/**
* Remove any information in the store that might not be true any more after {@code arrayAccess}
* has been assigned a new value (with the abstract value {@code val}). This includes the
* following steps (assume that {@code arrayAccess} is of the form <em>a[i]</em> for some
* <em>a</em>.
*
* <ol>
* <li value="1">Remove any abstract value for other array access <em>b[j]</em> where <em>a</em>
* and <em>b</em> can be aliases, or where either <em>b</em> or <em>j</em> contains a
* modifiable alias of <em>a[i]</em>.
* <li value="2">Remove any abstract values for field accesses <em>b.g</em> where <em>a[i]</em>
* might alias any expression in the receiver <em>b</em> and there is an array expression
* somewhere in the receiver.
* <li value="3">Remove any information about method calls.
* </ol>
*
* @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
* abstract value is not known).
*/
protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) {
final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
arrayValues.entrySet().iterator();
while (arrayValuesIterator.hasNext()) {
Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
ArrayAccess otherArrayAccess = entry.getKey();
// case 1:
if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) {
arrayValuesIterator.remove(); // remove information completely
} else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) {
// TODO: one could be less strict here, and only raise the abstract
// value for all array expressions with potentially aliasing receivers.
arrayValuesIterator.remove(); // remove information completely
}
}
// case 2:
final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
fieldValues.entrySet().iterator();
while (fieldValuesIterator.hasNext()) {
Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
FieldAccess otherFieldAccess = entry.getKey();
JavaExpression otherReceiver = otherFieldAccess.getReceiver();
if (otherReceiver.containsModifiableAliasOf(this, arrayAccess)
&& otherReceiver.containsOfClass(ArrayAccess.class)) {
// remove information completely
fieldValuesIterator.remove();
}
}
// case 3:
methodValues.clear();
}
/**
* Remove any information in this store that might not be true any more after {@code localVar} has
* been assigned a new value. This includes the following steps:
*
* <ol>
* <li value="1">Remove any abstract values for field accesses <em>b.g</em> where {@code
* localVar} might alias any expression in the receiver <em>b</em>.
* <li value="2">Remove any abstract values for array accesses <em>a[i]</em> where {@code
* localVar} might alias the receiver <em>a</em>.
* <li value="3">Remove any information about method calls where the receiver or any of the
* parameters contains {@code localVar}.
* </ol>
*/
protected void removeConflicting(LocalVariable var) {
final Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator =
fieldValues.entrySet().iterator();
while (fieldValuesIterator.hasNext()) {
Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
FieldAccess otherFieldAccess = entry.getKey();
// case 1:
if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) {
fieldValuesIterator.remove();
}
}
final Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator =
arrayValues.entrySet().iterator();
while (arrayValuesIterator.hasNext()) {
Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
ArrayAccess otherArrayAccess = entry.getKey();
// case 2:
if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) {
arrayValuesIterator.remove();
}
}
final Iterator<Map.Entry<MethodCall, V>> methodValuesIterator =
methodValues.entrySet().iterator();
while (methodValuesIterator.hasNext()) {
Map.Entry<MethodCall, V> entry = methodValuesIterator.next();
MethodCall otherMethodAccess = entry.getKey();
// case 3:
if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) {
methodValuesIterator.remove();
}
}
}
/**
* Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e.,
* returns {@code true} if not enough information is available to determine aliasing).
*/
@Override
public boolean canAlias(JavaExpression a, JavaExpression b) {
TypeMirror tb = b.getType();
TypeMirror ta = a.getType();
Types types = analysis.getTypes();
return types.isSubtype(ta, tb) || types.isSubtype(tb, ta);
}
/* --------------------------------------------------------- */
/* Handling of local variables */
/* --------------------------------------------------------- */
/**
* Returns the current abstract value of a local variable, or {@code null} if no information is
* available.
*
* @return the current abstract value of a local variable, or {@code null} if no information is
* available
*/
public @Nullable V getValue(LocalVariableNode n) {
Element el = n.getElement();
return localVariableValues.get(new LocalVariable(el));
}
/* --------------------------------------------------------- */
/* Handling of the current object */
/* --------------------------------------------------------- */
/**
* Returns the current abstract value of the current object, or {@code null} if no information is
* available.
*
* @param n a reference to "this"
* @return the current abstract value of the current object, or {@code null} if no information is
* available
*/
public @Nullable V getValue(ThisNode n) {
return thisValue;
}
/* --------------------------------------------------------- */
/* Helper and miscellaneous methods */
/* --------------------------------------------------------- */
@SuppressWarnings("unchecked")
@Override
public S copy() {
return analysis.createCopiedStore((S) this);
}
@Override
public S leastUpperBound(S other) {
return upperBound(other, false);
}
@Override
public S widenedUpperBound(S previous) {
return upperBound(previous, true);
}
private S upperBound(S other, boolean shouldWiden) {
S newStore = analysis.createEmptyStore(sequentialSemantics);
for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
// local variables that are only part of one store, but not the other are discarded, as one of
// store implicitly contains 'top' for that variable.
LocalVariable localVar = e.getKey();
V thisVal = localVariableValues.get(localVar);
if (thisVal != null) {
V otherVal = e.getValue();
V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
if (mergedVal != null) {
newStore.localVariableValues.put(localVar, mergedVal);
}
}
}
// information about the current object
{
V otherVal = other.thisValue;
V myVal = thisValue;
V mergedVal = myVal == null ? null : upperBoundOfValues(otherVal, myVal, shouldWiden);
if (mergedVal != null) {
newStore.thisValue = mergedVal;
}
}
for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
// information about fields that are only part of one store, but not the other are discarded,
// as one store implicitly contains 'top' for that field.
FieldAccess el = e.getKey();
V thisVal = fieldValues.get(el);
if (thisVal != null) {
V otherVal = e.getValue();
V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
if (mergedVal != null) {
newStore.fieldValues.put(el, mergedVal);
}
}
}
for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
// information about arrays that are only part of one store, but not the other are discarded,
// as one store implicitly contains 'top' for that array access.
ArrayAccess el = e.getKey();
V thisVal = arrayValues.get(el);
if (thisVal != null) {
V otherVal = e.getValue();
V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
if (mergedVal != null) {
newStore.arrayValues.put(el, mergedVal);
}
}
}
for (Map.Entry<MethodCall, V> e : other.methodValues.entrySet()) {
// information about methods that are only part of one store, but not the other are discarded,
// as one store implicitly contains 'top' for that field.
MethodCall el = e.getKey();
V thisVal = methodValues.get(el);
if (thisVal != null) {
V otherVal = e.getValue();
V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
if (mergedVal != null) {
newStore.methodValues.put(el, mergedVal);
}
}
}
for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
ClassName el = e.getKey();
V thisVal = classValues.get(el);
if (thisVal != null) {
V otherVal = e.getValue();
V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
if (mergedVal != null) {
newStore.classValues.put(el, mergedVal);
}
}
}
return newStore;
}
private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) {
return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal);
}
/**
* Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the
* argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java equality,
* not by any subtype relationship. This method is used primarily to simplify the equals
* predicate.
*/
protected boolean supersetOf(CFAbstractStore<V, S> other) {
for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
LocalVariable key = e.getKey();
V value = localVariableValues.get(key);
if (value == null || !value.equals(e.getValue())) {
return false;
}
}
for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
FieldAccess key = e.getKey();
V value = fieldValues.get(key);
if (value == null || !value.equals(e.getValue())) {
return false;
}
}
for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
ArrayAccess key = e.getKey();
V value = arrayValues.get(key);
if (value == null || !value.equals(e.getValue())) {
return false;
}
}
for (Map.Entry<MethodCall, V> e : other.methodValues.entrySet()) {
MethodCall key = e.getKey();
V value = methodValues.get(key);
if (value == null || !value.equals(e.getValue())) {
return false;
}
}
for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
ClassName key = e.getKey();
V value = classValues.get(key);
if (value == null || !value.equals(e.getValue())) {
return false;
}
}
return true;
}
@Override
public boolean equals(@Nullable Object o) {
if (o instanceof CFAbstractStore) {
@SuppressWarnings("unchecked")
CFAbstractStore<V, S> other = (CFAbstractStore<V, S>) o;
return this.supersetOf(other) && other.supersetOf(this);
} else {
return false;
}
}
@Override
public int hashCode() {
// What is a good hash code to use?
return 22;
}
@SideEffectFree
@Override
public String toString() {
return visualize(new StringCFGVisualizer<>());
}
@Override
public String visualize(CFGVisualizer<?, S, ?> viz) {
/* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by
* CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() of GenericAnnotatedTypeFactory */
@SuppressWarnings("unchecked")
CFGVisualizer<V, S, ?> castedViz = (CFGVisualizer<V, S, ?>) viz;
String internal = internalVisualize(castedViz);
if (internal.trim().isEmpty()) {
return this.getClassAndUid() + "()";
} else {
return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")";
}
}
/**
* Adds a representation of the internal information of this Store to visualizer {@code viz}.
*
* @param viz the visualizer
* @return a representation of the internal information of this {@link Store}
*/
protected String internalVisualize(CFGVisualizer<V, S, ?> viz) {
StringJoiner res = new StringJoiner(viz.getSeparator());
for (Map.Entry<LocalVariable, V> entry : localVariableValues.entrySet()) {
res.add(viz.visualizeStoreLocalVar(entry.getKey(), entry.getValue()));
}
if (thisValue != null) {
res.add(viz.visualizeStoreThisVal(thisValue));
}
for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) {
res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa)));
}
for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) {
res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa)));
}
for (MethodCall fa : ToStringComparator.sorted(methodValues.keySet())) {
res.add(viz.visualizeStoreMethodVals(fa, methodValues.get(fa)));
}
for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) {
res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa)));
}
return res.toString();
}
}