All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.flow.CFAbstractStore Maven / Gradle / Ivy

package org.checkerframework.framework.flow;

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.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.ElementUtils;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.IPair;
import org.plumelib.util.ToStringComparator;
import org.plumelib.util.UniqueId;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
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.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

/**
 * A store for the Checker Framework analysis. It tracks the annotations of memory locations such as
 * local variables and fields.
 *
 * 

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, S extends CFAbstractStore> implements Store, UniqueId { /** The analysis class this store belongs to. */ protected final CFAbstractAnalysis analysis; /** Information collected about local variables (including method parameters). */ protected final Map localVariableValues; /** Information collected about the current object. */ protected V thisValue; /** * Information collected about fields, using the internal representation {@link FieldAccess}. */ protected Map 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 getFieldValues() { return fieldValues; } /** * Information collected about array elements, using the internal representation {@link * ArrayAccess}. */ protected final Map arrayValues; /** * Information collected about method calls, using the internal representation {@link * MethodCall}. */ protected final Map methodValues; /** * Information collected about classname.class values, using the internal representation * {@link ClassName}. */ protected final Map classValues; /** * Should the analysis use sequential Java semantics (i.e., assume that only one thread is * running at all times)? */ protected final boolean sequentialSemantics; /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */ private final boolean assumeSideEffectFree; /** True if -AassumePureGetters was passed on the command line. */ private final boolean assumePureGetters; /** The unique ID for the next-created object. */ private static final AtomicLong nextUid = new AtomicLong(0); /** The unique ID of this object. */ private 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 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; assumeSideEffectFree = analysis.checker.hasOption("assumeSideEffectFree") || analysis.checker.hasOption("assumePure"); assumePureGetters = analysis.checker.hasOption("assumePureGetters"); } /** * Copy constructor. * * @param other a CFAbstractStore to copy into this */ protected CFAbstractStore(CFAbstractStore 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; assumeSideEffectFree = other.assumeSideEffectFree; assumePureGetters = other.assumePureGetters; } /** * 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); } } /* --------------------------------------------------------- */ /* Handling of fields */ /* --------------------------------------------------------- */ /** * Remove any information that might not be valid any more after a method call, and add * information guaranteed by the method. * *

    *
  1. 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. *
  2. Otherwise, all information about field accesses {@code a.f} needs to be removed, except * if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds * if {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code * a.f} has a {@link MonotonicQualifier} in the current store. Subclasses can change this * behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess, * GenericAnnotatedTypeFactory, CFAbstractValue)}. *
  3. Furthermore, if the field has a monotonic annotation, then its information can also be * kept. *
* * Furthermore, if the method is deterministic, we store its result {@code val} in the store. * * @param methodInvocationNode method whose information is being updated * @param atypeFactory the type factory of the associated checker * @param val abstract value of the method call */ public void updateForMethodCall( MethodInvocationNode methodInvocationNode, GenericAnnotatedTypeFactory atypeFactory, V val) { ExecutableElement method = methodInvocationNode.getTarget().getMethod(); // Case 1: The method is side-effect-free. boolean hasSideEffect = !(assumeSideEffectFree || (assumePureGetters && ElementUtils.isGetter(method)) || atypeFactory.isSideEffectFree(method)); if (hasSideEffect) { boolean sideEffectsUnrefineAliases = 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 { // Case 2 (unassignable fields) and case 3 (monotonic fields) updateFieldValuesForMethodCall(atypeFactory); } // 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(methodInvocationNode); replaceValue(methodCall, val); } /** * Returns the new value of a field after a method call, or {@code null} if the field should be * removed from the store. * *

In this default implementation, the field's value is preserved if it is either * unassignable (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic * qualifier (see {@link #newMonotonicFieldValueAfterMethodCall(FieldAccess, * GenericAnnotatedTypeFactory, CFAbstractValue)}). Otherwise, it is removed from the store. * * @param fieldAccess the field whose value to update * @param atypeFactory AnnotatedTypeFactory of the associated checker * @param value the field's value before the method call * @return the field's value after the method call, or {@code null} if the field should be * removed from the store */ protected V newFieldValueAfterMethodCall( FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { // Handle unassignable fields. if (fieldAccess.isUnassignableByOtherCode()) { return value; } // Handle fields with monotonic annotations. return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); } /** * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code * null} if the field has no monotonic annotation. * *

Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory, * CFAbstractValue)} to handle fields with monotonic annotations. * * @param fieldAccess the field whose value to compute * @param atypeFactory AnnotatedTypeFactory of the associated checker * @param value the field's value before the method call * @return the field's value after the method call, or {@code null} if the field has no * monotonic annotation */ protected V newMonotonicFieldValueAfterMethodCall( FieldAccess fieldAccess, GenericAnnotatedTypeFactory atypeFactory, V value) { // case 3: the field has a monotonic annotation if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) { return null; } List> fieldAnnotationPairs = atypeFactory.getAnnotationWithMetaAnnotation( fieldAccess.getField(), MonotonicQualifier.class); List metaAnnotations = CollectionsPlume.withoutDuplicates( CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs)); List monotonicAnnotations = new ArrayList<>(metaAnnotations.size()); for (AnnotationMirror metaAnnotation : metaAnnotations) { @SuppressWarnings("deprecation") // permitted for use in the framework Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); monotonicAnnotations.add( AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName)); } Collection valueAnnos = value.getAnnotations(); V newValue = null; for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) { // Make sure the target annotation is present. if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) { newValue = analysis.createSingleAnnotationValue( monotonicAnnotation, value.getUnderlyingType()) .mostSpecific(newValue, null); } } return newValue; } /** * Helper for {@link #updateForMethodCall(MethodInvocationNode, GenericAnnotatedTypeFactory, * CFAbstractValue)}. Remove any information about field values that might not be valid any more * after a method call, and add information guaranteed by the method. * *

More specifically, remove all information about fields except for unassignable fields and * fields that have a monotonic annotation. * * @param atypeFactory AnnotatedTypeFactory of the associated checker */ private void updateFieldValuesForMethodCall( GenericAnnotatedTypeFactory atypeFactory) { Map newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues)); for (Map.Entry e : fieldValues.entrySet()) { FieldAccess fieldAccess = e.getKey(); V value = e.getValue(); V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value); if (newValue != null) { // Keep information for all hierarchies where we had a monotonic annotation. newFieldValues.put(fieldAccess, newValue); } } fieldValues = newFieldValues; } /** * 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}). * *

This method does not take care of removing other information that might be influenced by * changes to certain parts of the state. * *

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. * *

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. * *

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}). * *

This method does not take care of removing other information that might be influenced by * changes to certain parts of the state. * *

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. * *

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. * *

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. * *

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::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}). * *

This method does not take care of removing other information that might be influenced by * changes to certain parts of the state. * *

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. * *

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. * *

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. * *

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. * *

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}. * *

Every overriding implementation should start with * *

{@code
     * if (!shouldInsert) {
     *   return;
     * }
     * }
* * @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 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> fieldAnnotations = atypeFactory.getAnnotationWithMetaAnnotation( fieldAcc.getField(), MonotonicQualifier.class); for (IPair fieldAnnotation : fieldAnnotations) { AnnotationMirror metaAnnotation = fieldAnnotation.second; @SuppressWarnings("deprecation") // permitted for use in the framework Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false); AnnotationMirror monotonicAnnotation = AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName); // Make sure the 'target' annotation is present. if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) { 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. * *

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. * *

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 a.f for * some a. * *

    *
  1. Update the abstract value of other field accesses b.g where the * field is equal (that is, f=g), and the receiver b might alias the * receiver of {@code fieldAccess}, a. 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 g is not final. *
  2. Remove any abstract values for field accesses b.g where {@code * fieldAccess} might alias any expression in the receiver b. *
  3. Remove any information about method calls. *
  4. Remove any abstract values an array access b[i] where {@code * fieldAccess} might alias any expression in the receiver a or index i. *
* * @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) { Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); while (fieldValuesIterator.hasNext()) { Map.Entry 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(); } } } } } Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); while (arrayValuesIterator.hasNext()) { Map.Entry 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 a[i] for some * a. * *
    *
  1. Remove any abstract value for other array access b[j] where * a and b can be aliases, or where either b or j * contains a modifiable alias of a[i]. *
  2. Remove any abstract values for field accesses b.g where * a[i] might alias any expression in the receiver b and there is an * array expression somewhere in the receiver. *
  3. Remove any information about method calls. *
* * @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) { Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); while (arrayValuesIterator.hasNext()) { Map.Entry 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: Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); while (fieldValuesIterator.hasNext()) { Map.Entry 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: * *
    *
  1. Remove any abstract values for field accesses b.g where {@code * localVar} might alias any expression in the receiver b. *
  2. Remove any abstract values for array accesses a[i] where {@code * localVar} might alias the receiver a. *
  3. Remove any information about method calls where the receiver or any of the * parameters contains {@code localVar}. *
*/ protected void removeConflicting(LocalVariable var) { Iterator> fieldValuesIterator = fieldValues.entrySet().iterator(); while (fieldValuesIterator.hasNext()) { Map.Entry entry = fieldValuesIterator.next(); FieldAccess otherFieldAccess = entry.getKey(); // case 1: if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) { fieldValuesIterator.remove(); } } Iterator> arrayValuesIterator = arrayValues.entrySet().iterator(); while (arrayValuesIterator.hasNext()) { Map.Entry entry = arrayValuesIterator.next(); ArrayAccess otherArrayAccess = entry.getKey(); // case 2: if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) { arrayValuesIterator.remove(); } } Iterator> methodValuesIterator = methodValues.entrySet().iterator(); while (methodValuesIterator.hasNext()) { Map.Entry 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. * * @param n the local variable * @return the current abstract value of a local variable, or {@code null} if no information is * available */ public @Nullable V getValue(LocalVariableNode n) { VariableElement 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 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 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 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 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 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 other) { for (Map.Entry e : other.localVariableValues.entrySet()) { LocalVariable key = e.getKey(); V value = localVariableValues.get(key); if (value == null || !value.equals(e.getValue())) { return false; } } if (!Objects.equals(thisValue, other.thisValue)) { return false; } for (Map.Entry 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 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 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 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 other = (CFAbstractStore) 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 viz) { /* This cast is guaranteed to be safe, as long as the CFGVisualizer is created by * CFGVisualizer createCFGVisualizer() of GenericAnnotatedTypeFactory */ @SuppressWarnings("unchecked") CFGVisualizer castedViz = (CFGVisualizer) 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 viz) { StringJoiner res = new StringJoiner(viz.getSeparator()); for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) { res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv))); } 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(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy