| 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(); |
| } |
| } |