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

org.checkerframework.common.value.ValueTransfer Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.43.0
Show newest version
package org.checkerframework.common.value;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.ArrayLenRange;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.common.value.util.NumberMath;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.BitwiseAndNode;
import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode;
import org.checkerframework.dataflow.cfg.node.BitwiseOrNode;
import org.checkerframework.dataflow.cfg.node.BitwiseXorNode;
import org.checkerframework.dataflow.cfg.node.ConditionalAndNode;
import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
import org.checkerframework.dataflow.cfg.node.ConditionalOrNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode;
import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode;
import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode;
import org.checkerframework.dataflow.cfg.node.LeftShiftNode;
import org.checkerframework.dataflow.cfg.node.LessThanNode;
import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NotEqualNode;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalMinusNode;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.dataflow.cfg.node.NumericalPlusNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode;
import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
import org.checkerframework.dataflow.cfg.node.StringConcatenateNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;

/** The transfer class for the Value Checker. */
public class ValueTransfer extends CFTransfer {
  /** The Value type factory. */
  protected final ValueAnnotatedTypeFactory atypeFactory;
  /** The Value qualifier hierarchy. */
  protected final QualifierHierarchy hierarchy;

  /**
   * Create a new ValueTransfer.
   *
   * @param analysis the corresponding analysis
   */
  public ValueTransfer(CFAbstractAnalysis analysis) {
    super(analysis);
    atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory();
    hierarchy = atypeFactory.getQualifierHierarchy();
  }

  /** Returns a range of possible lengths for an integer from a range, as casted to a String. */
  private Range getIntRangeStringLengthRange(Node subNode, TransferInput p) {
    Range valueRange = getIntRange(subNode, p);

    // Get lengths of the bounds
    int fromLength = Long.toString(valueRange.from).length();
    int toLength = Long.toString(valueRange.to).length();

    int lowerLength = Math.min(fromLength, toLength);
    // In case the range contains 0, the minimum length is 1 even if both bounds are longer
    if (valueRange.contains(0)) {
      lowerLength = 1;
    }

    int upperLength = Math.max(fromLength, toLength);

    return Range.create(lowerLength, upperLength);
  }

  /**
   * Returns a range of possible lengths for {@code subNode}, as casted to a String.
   *
   * @param subNode some subnode of {@code p}
   * @param p TransferInput
   * @return a range of possible lengths for {@code subNode}, as casted to a String
   */
  private Range getStringLengthRange(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);

    AnnotationMirror anno = getValueAnnotation(value);
    if (anno == null) {
      return null;
    }
    String annoName = AnnotationUtils.annotationName(anno);
    if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) {
      return atypeFactory.getRange(anno);
    } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
      return Range.NOTHING;
    }

    TypeKind subNodeTypeKind = subNode.getType().getKind();

    // handle values converted to string (ints, longs, longs with @IntRange)
    if (subNode instanceof StringConversionNode) {
      return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p);
    } else if (isIntRange(subNode, p)) {
      return getIntRangeStringLengthRange(subNode, p);
    } else if (subNodeTypeKind == TypeKind.INT) {
      // ints are between 1 and 11 characters long
      return Range.create(1, 11);
    } else if (subNodeTypeKind == TypeKind.LONG) {
      // longs are between 1 and 20 characters long
      return Range.create(1, 20);
    }

    return Range.create(0, Integer.MAX_VALUE);
  }

  /**
   * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null if
   * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is
   * bottom.
   */
  private List getStringLengths(Node subNode, TransferInput p) {

    CFValue value = p.getValueOfSubNode(subNode);
    AnnotationMirror anno = getValueAnnotation(value);
    if (anno == null) {
      return null;
    }
    String annoName = AnnotationUtils.annotationName(anno);
    if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) {
      return atypeFactory.getArrayLength(anno);
    } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
      return Collections.emptyList();
    }

    TypeKind subNodeTypeKind = subNode.getType().getKind();

    // handle values converted to string (characters, bytes, shorts, ints with @IntRange)
    if (subNode instanceof StringConversionNode) {
      return getStringLengths(((StringConversionNode) subNode).getOperand(), p);
    } else if (subNodeTypeKind == TypeKind.CHAR) {
      // characters always have length 1
      return Collections.singletonList(1);
    } else if (isIntRange(subNode, p)) {
      // Try to get a list of lengths from a range of integer values converted to string @IntVal is
      // not checked for, because if it is present, we would already have the actual string values
      Range lengthRange = getIntRangeStringLengthRange(subNode, p);
      return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class);
    } else if (subNodeTypeKind == TypeKind.BYTE) {
      // bytes are between 1 and 4 characters long
      return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class);
    } else if (subNodeTypeKind == TypeKind.SHORT) {
      // shorts are between 1 and 6 characters long
      return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class);
    } else {
      return null;
    }
  }

  /**
   * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if
   * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is
   * bottom.
   *
   * @param subNode a subNode of p
   * @param p TransferInput
   * @return a list of possible values for {@code subNode} or null
   */
  private List getStringValues(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);
    AnnotationMirror anno = getValueAnnotation(value);
    if (anno == null) {
      return null;
    }
    String annoName = AnnotationUtils.annotationName(anno);
    switch (annoName) {
      case ValueAnnotatedTypeFactory.UNKNOWN_NAME:
        return null;
      case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME:
        return Collections.emptyList();
      case ValueAnnotatedTypeFactory.STRINGVAL_NAME:
        return atypeFactory.getStringValues(anno);
      default:
        // Do nothing.
    }

    // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string)
    List values;
    if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) {
      values = getBooleanValues(subNode, p);
    } else if (subNode.getType().getKind() == TypeKind.CHAR) {
      values = getCharValues(subNode, p);
    } else if (subNode instanceof StringConversionNode) {
      return getStringValues(((StringConversionNode) subNode).getOperand(), p);
    } else if (isIntRange(subNode, p)) {
      Range range = getIntRange(subNode, p);
      List longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class);
      values = NumberUtils.castNumbers(subNode.getType(), longValues);
    } else {
      values = getNumericalValues(subNode, p);
    }
    if (values == null) {
      return null;
    }
    List stringValues = CollectionsPlume.mapList(Object::toString, values);
    // Empty list means bottom value
    return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues;
  }

  /**
   * Create a @BoolVal CFValue for the given boolean value.
   *
   * @param value the value for the @BoolVal annotation
   * @return a @BoolVal CFValue for the given boolean value
   */
  private CFValue createBooleanCFValue(boolean value) {
    return analysis.createSingleAnnotationValue(
        value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE,
        atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN));
  }

  /**
   * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case
   * (including if the CFValue is not @BoolVal).
   *
   * @param value a CFValue
   * @return theboolean if {@code value} represents a single boolean value; otherwise null
   */
  private Boolean getBooleanValue(CFValue value) {
    AnnotationMirror boolAnno =
        AnnotationUtils.getAnnotationByName(
            value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME);
    return atypeFactory.getBooleanValue(boolAnno);
  }

  /**
   * Get possible boolean values for a node. Returns null if there is no estimate, because the
   * node's value is not @BoolVal.
   *
   * @param subNode the node whose value to obtain
   * @param p the transfer input in which to look up values
   * @return the possible boolean values for the node
   */
  private List getBooleanValues(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);
    AnnotationMirror intAnno =
        AnnotationUtils.getAnnotationByName(
            value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME);
    return atypeFactory.getBooleanValues(intAnno);
  }

  /** Get possible char values from annotation @IntRange or @IntVal. */
  private List getCharValues(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);
    AnnotationMirror intAnno;

    intAnno =
        AnnotationUtils.getAnnotationByName(
            value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME);
    if (intAnno != null) {
      return atypeFactory.getCharValues(intAnno);
    }

    if (atypeFactory.isIntRange(value.getAnnotations())) {
      intAnno =
          hierarchy.findAnnotationInHierarchy(value.getAnnotations(), atypeFactory.UNKNOWNVAL);
      Range range = atypeFactory.getRange(intAnno);
      return ValueCheckerUtils.getValuesFromRange(range, Character.class);
    }

    return Collections.emptyList();
  }

  private AnnotationMirror getValueAnnotation(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);
    return getValueAnnotation(value);
  }

  /**
   * Extract the Value Checker annotation from a CFValue object.
   *
   * @param cfValue a CFValue object
   * @return the Value Checker annotation within cfValue
   */
  private AnnotationMirror getValueAnnotation(CFValue cfValue) {
    return hierarchy.findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL);
  }

  /**
   * Returns a list of possible values, or null if no estimate is available and any value is
   * possible.
   */
  private List getNumericalValues(
      Node subNode, TransferInput p) {
    AnnotationMirror valueAnno = getValueAnnotation(subNode, p);
    return getNumericalValues(subNode, valueAnno);
  }

  /**
   * Returns the numerical values in valueAnno casted to the type of subNode.
   *
   * @param subNode node
   * @param valueAnno annotation mirror
   * @return the numerical values in valueAnno casted to the type of subNode
   */
  private List getNumericalValues(Node subNode, AnnotationMirror valueAnno) {

    if (valueAnno == null
        || AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
      return null;
    } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
      return Collections.emptyList();
    }
    List values;
    if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
      values = atypeFactory.getIntValues(valueAnno);
    } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) {
      values = atypeFactory.getDoubleValues(valueAnno);
    } else {
      return null;
    }
    return NumberUtils.castNumbers(subNode.getType(), values);
  }

  /** Get possible integer range from annotation. */
  private Range getIntRange(Node subNode, TransferInput p) {
    AnnotationMirror val = getValueAnnotation(subNode, p);
    return getIntRangeFromAnnotation(subNode, val);
  }

  /**
   * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the type
   * of {@code node}.
   *
   * @param node a node
   * @param val annotation mirror
   * @return the {@link Range} object corresponding to the annotation {@code val} casted to the type
   *     of {@code node}.
   */
  private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) {
    Range range;
    if (val == null || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) {
      range = Range.EVERYTHING;
    } else if (atypeFactory.isIntRange(val)) {
      range = atypeFactory.getRange(val);
    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
      List values = atypeFactory.getIntValues(val);
      range = ValueCheckerUtils.getRangeFromValues(values);
    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) {
      List values = atypeFactory.getDoubleValues(val);
      range = ValueCheckerUtils.getRangeFromValues(values);
    } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
      return Range.NOTHING;
    } else {
      range = Range.EVERYTHING;
    }
    return NumberUtils.castRange(node.getType(), range);
  }

  /**
   * Returns true if subNode is annotated with {@code @IntRange}.
   *
   * @param subNode subNode of {@code p}
   * @param p TransferInput
   * @return true if this subNode is annotated with {@code @IntRange}
   */
  private boolean isIntRange(Node subNode, TransferInput p) {
    CFValue value = p.getValueOfSubNode(subNode);
    return atypeFactory.isIntRange(value.getAnnotations());
  }

  /**
   * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}.
   *
   * @param node a node
   * @param anno annotation mirror
   * @return true if node is annotated with {@code @UnknownVal} and it is an integral type
   */
  private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) {
    return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)
        && TypesUtils.isIntegralPrimitive(node.getType());
  }

  /**
   * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}.
   *
   * @param node the node to inspect
   * @param p storage
   * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}
   */
  private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput p) {
    if (isIntRange(node, p)) {
      return true;
    }
    return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node)));
  }

  /**
   * Create a new transfer result based on the original result and the new annotation.
   *
   * @param result the original result
   * @param resultAnno the new annotation
   * @return the new transfer result
   */
  private TransferResult createNewResult(
      TransferResult result, AnnotationMirror resultAnno) {
    CFValue newResultValue =
        analysis.createSingleAnnotationValue(
            resultAnno, result.getResultValue().getUnderlyingType());
    return new RegularTransferResult<>(newResultValue, result.getRegularStore());
  }

  /** Create a boolean transfer result. */
  private TransferResult createNewResultBoolean(
      CFStore thenStore, CFStore elseStore, List resultValues, TypeMirror underlyingType) {
    AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues);
    CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType);
    if (elseStore != null) {
      return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore);
    } else {
      return new RegularTransferResult<>(newResultValue, thenStore);
    }
  }

  @Override
  public TransferResult visitFieldAccess(
      FieldAccessNode node, TransferInput in) {

    TransferResult result = super.visitFieldAccess(node, in);
    refineArrayAtLengthAccess(node, result.getRegularStore());
    return result;
  }

  @Override
  public TransferResult visitMethodInvocation(
      MethodInvocationNode n, TransferInput p) {
    TransferResult result = super.visitMethodInvocation(n, p);
    refineAtLengthInvocation(n, result.getRegularStore());
    return result;
  }

  /**
   * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation
   * for array.
   */
  private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) {
    if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) {
      return;
    }

    refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store);
  }

  /**
   * If length method is invoked for a sequence, transform its @IntVal annotation into an @ArrayLen
   * annotation.
   *
   * @param lengthNode the length method invocation node
   * @param store the Checker Framework store
   */
  private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) {
    if (atypeFactory
        .getMethodIdentifier()
        .isStringLengthMethod(lengthNode.getTarget().getMethod())) {
      MethodAccessNode methodAccessNode = lengthNode.getTarget();
      refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store);
    } else if (atypeFactory
        .getMethodIdentifier()
        .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) {
      Node node = lengthNode.getArguments().get(0);
      refineAtLengthAccess(lengthNode, node, store);
    }
  }

  /**
   * Gets a value checker annotation relevant for an array or a string.
   *
   * @param arrayOrStringNode the node whose annotation to return
   * @return the value checker annotation for the array or a string
   */
  private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) {
    AnnotationMirror arrayOrStringAnno =
        atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class);
    if (arrayOrStringAnno == null) {
      arrayOrStringAnno =
          atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class);
    }
    if (arrayOrStringAnno == null) {
      arrayOrStringAnno =
          atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLenRange.class);
    }

    return arrayOrStringAnno;
  }

  /**
   * Transform @IntVal or @IntRange annotations of a array or string length into an @ArrayLen
   * or @ArrayLenRange annotation for the array or string.
   *
   * @param lengthNode an invocation of method {@code length} or an access of the {@code length}
   *     field
   * @param receiverNode the receiver of {@code lengthNode}
   * @param store the store to update
   */
  private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) {
    JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode);

    // If the expression is not representable (for example if String.length() for some reason is
    // not marked @Pure, then do not refine.
    if (lengthExpr instanceof Unknown) {
      return;
    }

    CFValue value = store.getValue(lengthExpr);
    if (value == null) {
      return;
    }

    AnnotationMirror lengthAnno = getValueAnnotation(value);
    if (lengthAnno == null) {
      return;
    }
    if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) {
      // If the length is bottom, then this is dead code, so the receiver type
      // should also be bottom.
      JavaExpression receiver = JavaExpression.fromNode(receiverNode);
      store.insertValue(receiver, lengthAnno);
      return;
    }

    RangeOrListOfValues rolv;
    if (atypeFactory.isIntRange(lengthAnno)) {
      rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno));
    } else if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) {
      List lengthValues = atypeFactory.getIntValues(lengthAnno);
      rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues));
    } else {
      return;
    }
    AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory);
    AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode);

    AnnotationMirror combinedRecAnno;
    // If the receiver doesn't have an @ArrayLen annotation, use the new annotation.
    // If it does have an annotation, combine the facts known about the receiver
    // with the facts known about its length using GLB.
    if (oldRecAnno == null) {
      combinedRecAnno = newRecAnno;
    } else {
      combinedRecAnno = hierarchy.greatestLowerBound(oldRecAnno, newRecAnno);
    }
    JavaExpression receiver = JavaExpression.fromNode(receiverNode);
    store.insertValue(receiver, combinedRecAnno);
  }

  @Override
  public TransferResult visitStringConcatenateAssignment(
      StringConcatenateAssignmentNode n, TransferInput p) {
    TransferResult result = super.visitStringConcatenateAssignment(n, p);
    return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result);
  }

  @Override
  public TransferResult visitStringConcatenate(
      StringConcatenateNode n, TransferInput p) {
    TransferResult result = super.visitStringConcatenate(n, p);
    return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result);
  }

  /**
   * Calculates possible lengths of a result of string concatenation of strings with known lengths.
   */
  private List calculateLengthAddition(
      List leftLengths, List rightLengths) {
    List result = new ArrayList<>(leftLengths.size() * rightLengths.size());

    for (int left : leftLengths) {
      for (int right : rightLengths) {
        long resultLength = (long) left + right;
        // Lengths not fitting into int are not allowed
        if (resultLength <= Integer.MAX_VALUE) {
          result.add((int) resultLength);
        }
      }
    }

    return result;
  }

  /**
   * Calculates a range of possible lengths of a result of string concatenation of strings with
   * known ranges of lengths.
   */
  private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) {
    return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING);
  }

  /**
   * Checks whether or not the passed node is nullable. This superficial check assumes that every
   * node is nullable unless it is a primitive, String literal, or compile-time constant.
   *
   * @return false if the node's run-time can't be null; true if the node's run-time value may be
   *     null, or if this method is not precise enough
   */
  private boolean isNullable(Node node) {
    if (node instanceof StringConversionNode) {
      if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) {
        return false;
      }
    } else if (node instanceof StringLiteralNode) {
      return false;
    } else if (node instanceof StringConcatenateNode) {
      return false;
    }

    Element element = TreeUtils.elementFromUse((ExpressionTree) node.getTree());
    return !ElementUtils.isCompileTimeConstant(element);
  }

  /** Creates an annotation for a result of string concatenation. */
  private AnnotationMirror createAnnotationForStringConcatenation(
      Node leftOperand, Node rightOperand, TransferInput p) {

    // Try using sets of string values
    List leftValues = getStringValues(leftOperand, p);
    List rightValues = getStringValues(rightOperand, p);

    boolean nonNullStringConcat =
        atypeFactory.getChecker().hasOption("nonNullStringsConcatenation");

    if (leftValues != null && rightValues != null) {
      // Both operands have known string values, compute set of results
      if (!nonNullStringConcat) {
        if (isNullable(leftOperand)) {
          leftValues = CollectionsPlume.append(leftValues, "null");
        }
        if (isNullable(rightOperand)) {
          rightValues = CollectionsPlume.append(rightValues, "null");
        }
      } else {
        if (leftOperand instanceof StringConversionNode) {
          if (((StringConversionNode) leftOperand).getOperand().getType().getKind()
              == TypeKind.NULL) {
            leftValues = CollectionsPlume.append(leftValues, "null");
          }
        }
        if (rightOperand instanceof StringConversionNode) {
          if (((StringConversionNode) rightOperand).getOperand().getType().getKind()
              == TypeKind.NULL) {
            rightValues = CollectionsPlume.append(rightValues, "null");
          }
        }
      }

      List concatValues = new ArrayList<>(leftValues.size() * rightValues.size());
      for (String left : leftValues) {
        for (String right : rightValues) {
          concatValues.add(left + right);
        }
      }
      return atypeFactory.createStringAnnotation(concatValues);
    }

    // Try using sets of lengths
    List leftLengths =
        leftValues != null
            ? ValueCheckerUtils.getLengthsForStringValues(leftValues)
            : getStringLengths(leftOperand, p);
    List rightLengths =
        rightValues != null
            ? ValueCheckerUtils.getLengthsForStringValues(rightValues)
            : getStringLengths(rightOperand, p);

    if (leftLengths != null && rightLengths != null) {
      // Both operands have known lengths, compute set of result lengths
      if (!nonNullStringConcat) {
        if (isNullable(leftOperand)) {
          leftLengths = new ArrayList<>(leftLengths);
          leftLengths.add(4); // "null"
        }
        if (isNullable(rightOperand)) {
          rightLengths = new ArrayList<>(rightLengths);
          rightLengths.add(4); // "null"
        }
      }
      List concatLengths = calculateLengthAddition(leftLengths, rightLengths);
      return atypeFactory.createArrayLenAnnotation(concatLengths);
    }

    // Try using ranges of lengths
    Range leftLengthRange =
        leftLengths != null
            ? ValueCheckerUtils.getRangeFromValues(leftLengths)
            : getStringLengthRange(leftOperand, p);
    Range rightLengthRange =
        rightLengths != null
            ? ValueCheckerUtils.getRangeFromValues(rightLengths)
            : getStringLengthRange(rightOperand, p);

    if (leftLengthRange != null && rightLengthRange != null) {
      // Both operands have a length from a known range, compute a range of result lengths
      if (!nonNullStringConcat) {
        if (isNullable(leftOperand)) {
          leftLengthRange = leftLengthRange.union(Range.create(4, 4)); // "null"
        }
        if (isNullable(rightOperand)) {
          rightLengthRange = rightLengthRange.union(Range.create(4, 4)); // "null"
        }
      }
      Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange);
      return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange);
    }

    return atypeFactory.UNKNOWNVAL;
  }

  public TransferResult stringConcatenation(
      Node leftOperand,
      Node rightOperand,
      TransferInput p,
      TransferResult result) {

    AnnotationMirror resultAnno =
        createAnnotationForStringConcatenation(leftOperand, rightOperand, p);

    TypeMirror underlyingType = result.getResultValue().getUnderlyingType();
    CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType);
    return new RegularTransferResult<>(newResultValue, result.getRegularStore());
  }

  /** Binary operations that are analyzed by the value checker. */
  enum NumericalBinaryOps {
    ADDITION,
    SUBTRACTION,
    DIVISION,
    REMAINDER,
    MULTIPLICATION,
    SHIFT_LEFT,
    SIGNED_SHIFT_RIGHT,
    UNSIGNED_SHIFT_RIGHT,
    BITWISE_AND,
    BITWISE_OR,
    BITWISE_XOR;
  }

  /**
   * Get the refined annotation after a numerical binary operation.
   *
   * @param leftNode the node that represents the left operand
   * @param rightNode the node that represents the right operand
   * @param op the operator type
   * @param p the transfer input
   * @return the result annotation mirror
   */
  private AnnotationMirror calculateNumericalBinaryOp(
      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) {
    if (!isIntRangeOrIntegralUnknownVal(leftNode, p)
        && !isIntRangeOrIntegralUnknownVal(rightNode, p)) {
      List resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p);
      return atypeFactory.createNumberAnnotationMirror(resultValues);
    } else {
      Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p);
      return atypeFactory.createIntRangeAnnotation(resultRange);
    }
  }

  /** Calculate the result range after a binary operation between two numerical type nodes. */
  private Range calculateRangeBinaryOp(
      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) {
    if (TypesUtils.isIntegralPrimitive(leftNode.getType())
        && TypesUtils.isIntegralPrimitive(rightNode.getType())) {
      Range leftRange = getIntRange(leftNode, p);
      Range rightRange = getIntRange(rightNode, p);
      Range resultRange;
      switch (op) {
        case ADDITION:
          resultRange = leftRange.plus(rightRange);
          break;
        case SUBTRACTION:
          resultRange = leftRange.minus(rightRange);
          break;
        case MULTIPLICATION:
          resultRange = leftRange.times(rightRange);
          break;
        case DIVISION:
          resultRange = leftRange.divide(rightRange);
          break;
        case REMAINDER:
          resultRange = leftRange.remainder(rightRange);
          break;
        case SHIFT_LEFT:
          resultRange = leftRange.shiftLeft(rightRange);
          break;
        case SIGNED_SHIFT_RIGHT:
          resultRange = leftRange.signedShiftRight(rightRange);
          break;
        case UNSIGNED_SHIFT_RIGHT:
          resultRange = leftRange.unsignedShiftRight(rightRange);
          break;
        case BITWISE_AND:
          resultRange = leftRange.bitwiseAnd(rightRange);
          break;
        case BITWISE_OR:
          resultRange = leftRange.bitwiseOr(rightRange);
          break;
        case BITWISE_XOR:
          resultRange = leftRange.bitwiseXor(rightRange);
          break;
        default:
          throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
      }
      // Any integral type with less than 32 bits would be promoted to 32-bit int type during
      // operations.
      return leftNode.getType().getKind() == TypeKind.LONG
              || rightNode.getType().getKind() == TypeKind.LONG
          ? resultRange
          : resultRange.intRange();
    } else {
      return Range.EVERYTHING;
    }
  }

  /** Calculate the possible values after a binary operation between two numerical type nodes. */
  private List calculateValuesBinaryOp(
      Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput p) {
    List lefts = getNumericalValues(leftNode, p);
    List rights = getNumericalValues(rightNode, p);
    if (lefts == null || rights == null) {
      return null;
    }
    List resultValues = new ArrayList<>();
    for (Number left : lefts) {
      NumberMath nmLeft = NumberMath.getNumberMath(left);
      for (Number right : rights) {
        switch (op) {
          case ADDITION:
            resultValues.add(nmLeft.plus(right));
            break;
          case DIVISION:
            Number result = nmLeft.divide(right);
            if (result != null) {
              resultValues.add(result);
            }
            break;
          case MULTIPLICATION:
            resultValues.add(nmLeft.times(right));
            break;
          case REMAINDER:
            Number resultR = nmLeft.remainder(right);
            if (resultR != null) {
              resultValues.add(resultR);
            }
            break;
          case SUBTRACTION:
            resultValues.add(nmLeft.minus(right));
            break;
          case SHIFT_LEFT:
            resultValues.add(nmLeft.shiftLeft(right));
            break;
          case SIGNED_SHIFT_RIGHT:
            resultValues.add(nmLeft.signedShiftRight(right));
            break;
          case UNSIGNED_SHIFT_RIGHT:
            resultValues.add(nmLeft.unsignedShiftRight(right));
            break;
          case BITWISE_AND:
            resultValues.add(nmLeft.bitwiseAnd(right));
            break;
          case BITWISE_OR:
            resultValues.add(nmLeft.bitwiseOr(right));
            break;
          case BITWISE_XOR:
            resultValues.add(nmLeft.bitwiseXor(right));
            break;
          default:
            throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
        }
      }
    }
    return resultValues;
  }

  @Override
  public TransferResult visitNumericalAddition(
      NumericalAdditionNode n, TransferInput p) {
    TransferResult transferResult = super.visitNumericalAddition(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitNumericalSubtraction(
      NumericalSubtractionNode n, TransferInput p) {
    TransferResult transferResult = super.visitNumericalSubtraction(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitNumericalMultiplication(
      NumericalMultiplicationNode n, TransferInput p) {
    TransferResult transferResult = super.visitNumericalMultiplication(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitIntegerDivision(
      IntegerDivisionNode n, TransferInput p) {
    TransferResult transferResult = super.visitIntegerDivision(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitFloatingDivision(
      FloatingDivisionNode n, TransferInput p) {
    TransferResult transferResult = super.visitFloatingDivision(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitIntegerRemainder(
      IntegerRemainderNode n, TransferInput p) {
    TransferResult transferResult = super.visitIntegerRemainder(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitFloatingRemainder(
      FloatingRemainderNode n, TransferInput p) {
    TransferResult transferResult = super.visitFloatingRemainder(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitLeftShift(
      LeftShiftNode n, TransferInput p) {
    TransferResult transferResult = super.visitLeftShift(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitSignedRightShift(
      SignedRightShiftNode n, TransferInput p) {
    TransferResult transferResult = super.visitSignedRightShift(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitUnsignedRightShift(
      UnsignedRightShiftNode n, TransferInput p) {
    TransferResult transferResult = super.visitUnsignedRightShift(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitBitwiseAnd(
      BitwiseAndNode n, TransferInput p) {
    TransferResult transferResult = super.visitBitwiseAnd(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitBitwiseOr(
      BitwiseOrNode n, TransferInput p) {
    TransferResult transferResult = super.visitBitwiseOr(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitBitwiseXor(
      BitwiseXorNode n, TransferInput p) {
    TransferResult transferResult = super.visitBitwiseXor(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalBinaryOp(
            n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p);
    return createNewResult(transferResult, resultAnno);
  }

  /** Unary operations that are analyzed by the value checker. */
  enum NumericalUnaryOps {
    PLUS,
    MINUS,
    BITWISE_COMPLEMENT;
  }

  /**
   * Get the refined annotation after a numerical unary operation.
   *
   * @param operand the node that represents the operand
   * @param op the operator type
   * @param p the transfer input
   * @return the result annotation mirror
   */
  private AnnotationMirror calculateNumericalUnaryOp(
      Node operand, NumericalUnaryOps op, TransferInput p) {
    if (!isIntRange(operand, p)) {
      List resultValues = calculateValuesUnaryOp(operand, op, p);
      return atypeFactory.createNumberAnnotationMirror(resultValues);
    } else {
      Range resultRange = calculateRangeUnaryOp(operand, op, p);
      return atypeFactory.createIntRangeAnnotation(resultRange);
    }
  }

  /**
   * Calculate the result range after a unary operation of a numerical type node.
   *
   * @param operand the node that represents the operand
   * @param op the operator type
   * @param p the transfer input
   * @return the result annotation mirror
   */
  private Range calculateRangeUnaryOp(
      Node operand, NumericalUnaryOps op, TransferInput p) {
    if (TypesUtils.isIntegralPrimitive(operand.getType())) {
      Range range = getIntRange(operand, p);
      Range resultRange;
      switch (op) {
        case PLUS:
          resultRange = range.unaryPlus();
          break;
        case MINUS:
          resultRange = range.unaryMinus();
          break;
        case BITWISE_COMPLEMENT:
          resultRange = range.bitwiseComplement();
          break;
        default:
          throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
      }
      // Any integral type with less than 32 bits would be promoted to 32-bit int type during
      // operations.
      return operand.getType().getKind() == TypeKind.LONG ? resultRange : resultRange.intRange();
    } else {
      return Range.EVERYTHING;
    }
  }

  /** Calculate the possible values after a unary operation of a numerical type node. */
  private List calculateValuesUnaryOp(
      Node operand, NumericalUnaryOps op, TransferInput p) {
    List lefts = getNumericalValues(operand, p);
    if (lefts == null) {
      return null;
    }
    List resultValues = new ArrayList<>(lefts.size());
    for (Number left : lefts) {
      NumberMath nmLeft = NumberMath.getNumberMath(left);
      switch (op) {
        case PLUS:
          resultValues.add(nmLeft.unaryPlus());
          break;
        case MINUS:
          resultValues.add(nmLeft.unaryMinus());
          break;
        case BITWISE_COMPLEMENT:
          resultValues.add(nmLeft.bitwiseComplement());
          break;
        default:
          throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
      }
    }
    return resultValues;
  }

  @Override
  public TransferResult visitNumericalMinus(
      NumericalMinusNode n, TransferInput p) {
    TransferResult transferResult = super.visitNumericalMinus(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitNumericalPlus(
      NumericalPlusNode n, TransferInput p) {
    TransferResult transferResult = super.visitNumericalPlus(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p);
    return createNewResult(transferResult, resultAnno);
  }

  @Override
  public TransferResult visitBitwiseComplement(
      BitwiseComplementNode n, TransferInput p) {
    TransferResult transferResult = super.visitBitwiseComplement(n, p);
    AnnotationMirror resultAnno =
        calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p);
    return createNewResult(transferResult, resultAnno);
  }

  enum ComparisonOperators {
    EQUAL,
    NOT_EQUAL,
    GREATER_THAN,
    GREATER_THAN_EQ,
    LESS_THAN,
    LESS_THAN_EQ;
  }

  private List calculateBinaryComparison(
      Node leftNode,
      CFValue leftValue,
      Node rightNode,
      CFValue rightValue,
      ComparisonOperators op,
      CFStore thenStore,
      CFStore elseStore) {
    AnnotationMirror leftAnno = getValueAnnotation(leftValue);
    AnnotationMirror rightAnno = getValueAnnotation(rightValue);

    if (atypeFactory.isIntRange(leftAnno)
        || atypeFactory.isIntRange(rightAnno)
        || isIntegralUnknownVal(rightNode, rightAnno)
        || isIntegralUnknownVal(leftNode, leftAnno)) {
      // If either is @UnknownVal, then refineIntRanges will treat it as the max range and thus
      // refine it if possible.  Also, if either is an @IntVal, then it will be converted to a
      // range.  This is less precise in some cases, but avoids the complexity of comparing a list
      // of values to a range. (This could be implemented in the future.)
      return refineIntRanges(leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore);
    }

    List lefts = getNumericalValues(leftNode, leftAnno);
    List rights = getNumericalValues(rightNode, rightAnno);

    if (lefts == null || rights == null) {
      // Appropriately handle bottom when something is compared to bottom.
      if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL)
          || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) {
        return Collections.emptyList();
      }
      return null;
    }

    // This is a list of all the values that the expression can evaluate to.
    List resultValues = new ArrayList<>();

    // These lists are used to refine the values in the store based on the results of the
    // comparison.
    List thenLeftVals = new ArrayList<>();
    List elseLeftVals = new ArrayList<>();
    List thenRightVals = new ArrayList<>();
    List elseRightVals = new ArrayList<>();

    for (Number left : lefts) {
      NumberMath nmLeft = NumberMath.getNumberMath(left);
      for (Number right : rights) {
        Boolean result;
        switch (op) {
          case EQUAL:
            result = nmLeft.equalTo(right);
            break;
          case GREATER_THAN:
            result = nmLeft.greaterThan(right);
            break;
          case GREATER_THAN_EQ:
            result = nmLeft.greaterThanEq(right);
            break;
          case LESS_THAN:
            result = nmLeft.lessThan(right);
            break;
          case LESS_THAN_EQ:
            result = nmLeft.lessThanEq(right);
            break;
          case NOT_EQUAL:
            result = nmLeft.notEqualTo(right);
            break;
          default:
            throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
        }
        resultValues.add(result);
        if (result) {
          thenLeftVals.add(left);
          thenRightVals.add(right);
        } else {
          elseLeftVals.add(left);
          elseRightVals.add(right);
        }
      }
    }

    createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode);
    createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode);
    createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode);
    createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode);

    return resultValues;
  }

  /**
   * Calculates the result of a binary comparison on a pair of intRange annotations, and refines
   * annotations appropriately.
   */
  private List refineIntRanges(
      Node leftNode,
      AnnotationMirror leftAnno,
      Node rightNode,
      AnnotationMirror rightAnno,
      ComparisonOperators op,
      CFStore thenStore,
      CFStore elseStore) {

    Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno);
    Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno);

    final Range thenRightRange;
    final Range thenLeftRange;
    final Range elseRightRange;
    final Range elseLeftRange;

    switch (op) {
      case EQUAL:
        thenRightRange = rightRange.refineEqualTo(leftRange);
        thenLeftRange = thenRightRange; // Only needs to be computed once.
        elseRightRange = rightRange.refineNotEqualTo(leftRange);
        elseLeftRange = leftRange.refineNotEqualTo(rightRange);
        break;
      case GREATER_THAN:
        thenLeftRange = leftRange.refineGreaterThan(rightRange);
        thenRightRange = rightRange.refineLessThan(leftRange);
        elseRightRange = rightRange.refineGreaterThanEq(leftRange);
        elseLeftRange = leftRange.refineLessThanEq(rightRange);
        break;
      case GREATER_THAN_EQ:
        thenRightRange = rightRange.refineLessThanEq(leftRange);
        thenLeftRange = leftRange.refineGreaterThanEq(rightRange);
        elseLeftRange = leftRange.refineLessThan(rightRange);
        elseRightRange = rightRange.refineGreaterThan(leftRange);
        break;
      case LESS_THAN:
        thenLeftRange = leftRange.refineLessThan(rightRange);
        thenRightRange = rightRange.refineGreaterThan(leftRange);
        elseRightRange = rightRange.refineLessThanEq(leftRange);
        elseLeftRange = leftRange.refineGreaterThanEq(rightRange);
        break;
      case LESS_THAN_EQ:
        thenRightRange = rightRange.refineGreaterThanEq(leftRange);
        thenLeftRange = leftRange.refineLessThanEq(rightRange);
        elseLeftRange = leftRange.refineGreaterThan(rightRange);
        elseRightRange = rightRange.refineLessThan(leftRange);
        break;
      case NOT_EQUAL:
        thenRightRange = rightRange.refineNotEqualTo(leftRange);
        thenLeftRange = leftRange.refineNotEqualTo(rightRange);
        elseRightRange = rightRange.refineEqualTo(leftRange);
        elseLeftRange = elseRightRange; // Equality only needs to be computed once.
        break;
      default:
        throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
    }

    createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode);
    createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode);
    createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode);
    createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode);

    // TODO: Refine the type of the comparison.
    return null;
  }

  /**
   * Takes a list of result values (i.e. the values possible after the comparison) and creates the
   * appropriate annotation from them, then combines that annotation with the existing annotation on
   * the node. The resulting annotation is inserted into the store.
   *
   * @param store the store
   * @param results the result values
   * @param node the node whose existing annotation to refine
   */
  private void createAnnotationFromResultsAndAddToStore(CFStore store, List results, Node node) {
    AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results);
    addAnnotationToStore(store, anno, node);
  }

  /**
   * Takes a range and creates the appropriate annotation from it, then combines that annotation
   * with the existing annotation on the node. The resulting annotation is inserted into the store.
   *
   * @param store the store
   * @param range the range to create an annotation for
   * @param node the node whose existing annotation to refine
   */
  private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) {
    AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range);
    addAnnotationToStore(store, anno, node);
  }

  private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) {
    // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node.
    for (Node internal : splitAssignments(node)) {
      JavaExpression je = JavaExpression.fromNode(internal);
      CFValue currentValueFromStore;
      if (CFAbstractStore.canInsertJavaExpression(je)) {
        currentValueFromStore = store.getValue(je);
      } else {
        // Don't just `continue;` which would skip the calls to refine{Array,String}...
        currentValueFromStore = null;
      }
      AnnotationMirror currentAnno =
          (currentValueFromStore == null
              ? atypeFactory.UNKNOWNVAL
              : getValueAnnotation(currentValueFromStore));
      // Combine the new annotations based on the results of the comparison with the existing type.
      AnnotationMirror newAnno = hierarchy.greatestLowerBound(anno, currentAnno);
      store.insertValue(je, newAnno);

      if (node instanceof FieldAccessNode) {
        refineArrayAtLengthAccess((FieldAccessNode) internal, store);
      } else if (node instanceof MethodInvocationNode) {
        MethodInvocationNode miNode = (MethodInvocationNode) node;
        refineAtLengthInvocation(miNode, store);
      }
    }
  }

  @Override
  public TransferResult visitLessThan(
      LessThanNode n, TransferInput p) {
    TransferResult transferResult = super.visitLessThan(n, p);
    CFStore thenStore = transferResult.getThenStore();
    CFStore elseStore = transferResult.getElseStore();
    List resultValues =
        calculateBinaryComparison(
            n.getLeftOperand(),
            p.getValueOfSubNode(n.getLeftOperand()),
            n.getRightOperand(),
            p.getValueOfSubNode(n.getRightOperand()),
            ComparisonOperators.LESS_THAN,
            thenStore,
            elseStore);
    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
  }

  @Override
  public TransferResult visitLessThanOrEqual(
      LessThanOrEqualNode n, TransferInput p) {
    TransferResult transferResult = super.visitLessThanOrEqual(n, p);
    CFStore thenStore = transferResult.getThenStore();
    CFStore elseStore = transferResult.getElseStore();
    List resultValues =
        calculateBinaryComparison(
            n.getLeftOperand(),
            p.getValueOfSubNode(n.getLeftOperand()),
            n.getRightOperand(),
            p.getValueOfSubNode(n.getRightOperand()),
            ComparisonOperators.LESS_THAN_EQ,
            thenStore,
            elseStore);
    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
  }

  @Override
  public TransferResult visitGreaterThan(
      GreaterThanNode n, TransferInput p) {
    TransferResult transferResult = super.visitGreaterThan(n, p);
    CFStore thenStore = transferResult.getThenStore();
    CFStore elseStore = transferResult.getElseStore();
    List resultValues =
        calculateBinaryComparison(
            n.getLeftOperand(),
            p.getValueOfSubNode(n.getLeftOperand()),
            n.getRightOperand(),
            p.getValueOfSubNode(n.getRightOperand()),
            ComparisonOperators.GREATER_THAN,
            thenStore,
            elseStore);
    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
  }

  @Override
  public TransferResult visitGreaterThanOrEqual(
      GreaterThanOrEqualNode n, TransferInput p) {
    TransferResult transferResult = super.visitGreaterThanOrEqual(n, p);
    CFStore thenStore = transferResult.getThenStore();
    CFStore elseStore = transferResult.getElseStore();
    List resultValues =
        calculateBinaryComparison(
            n.getLeftOperand(),
            p.getValueOfSubNode(n.getLeftOperand()),
            n.getRightOperand(),
            p.getValueOfSubNode(n.getRightOperand()),
            ComparisonOperators.GREATER_THAN_EQ,
            thenStore,
            elseStore);
    TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
    return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
  }

  @Override
  protected TransferResult strengthenAnnotationOfEqualTo(
      TransferResult transferResult,
      Node firstNode,
      Node secondNode,
      CFValue firstValue,
      CFValue secondValue,
      boolean notEqualTo) {
    if (firstValue == null) {
      return transferResult;
    }
    if (TypesUtils.isNumeric(firstNode.getType()) || TypesUtils.isNumeric(secondNode.getType())) {
      CFStore thenStore = transferResult.getThenStore();
      CFStore elseStore = transferResult.getElseStore();
      // At least one must be a primitive otherwise reference equality is used.
      List resultValues =
          calculateBinaryComparison(
              firstNode,
              firstValue,
              secondNode,
              secondValue,
              notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL,
              thenStore,
              elseStore);
      if (transferResult.getResultValue() == null) {
        // Happens for case labels
        return transferResult;
      }
      TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType();
      return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType);
    }
    return super.strengthenAnnotationOfEqualTo(
        transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo);
  }

  @Override
  protected void processConditionalPostconditions(
      MethodInvocationNode n,
      ExecutableElement methodElement,
      Tree tree,
      CFStore thenStore,
      CFStore elseStore) {
    // For String.startsWith(String) and String.endsWith(String), refine the minimum length
    // of the receiver to the minimum length of the argument.

    ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier();
    if (methodIdentifier.isStartsWithMethod(methodElement)
        || methodIdentifier.isEndsWithMethod(methodElement)) {

      Node argumentNode = n.getArgument(0);
      AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode);
      int minLength = atypeFactory.getMinLenValue(argumentAnno);
      // Update the annotation of the receiver
      if (minLength != 0) {
        JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver());

        AnnotationMirror minLenAnno =
            atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE);
        thenStore.insertValuePermitNondeterministic(receiver, minLenAnno);
      }
    }

    super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore);
  }

  enum ConditionalOperators {
    NOT,
    OR,
    AND;
  }

  private static final List ALL_BOOLEANS =
      Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE});

  private List calculateConditionalOperator(
      Node leftNode, Node rightNode, ConditionalOperators op, TransferInput p) {
    List lefts = getBooleanValues(leftNode, p);
    if (lefts == null) {
      lefts = ALL_BOOLEANS;
    }
    List rights = null;
    if (rightNode != null) {
      rights = getBooleanValues(rightNode, p);
      if (rights == null) {
        rights = ALL_BOOLEANS;
      }
    }
    // This list can contain duplicates.  It is deduplicated later by createBooleanAnnotation.
    List resultValues = new ArrayList<>(2);
    switch (op) {
      case NOT:
        return CollectionsPlume.mapList((Boolean left) -> !left, lefts);
      case OR:
        for (Boolean left : lefts) {
          for (Boolean right : rights) {
            resultValues.add(left || right);
          }
        }
        return resultValues;
      case AND:
        for (Boolean left : lefts) {
          for (Boolean right : rights) {
            resultValues.add(left && right);
          }
        }
        return resultValues;
    }
    throw new TypeSystemError("ValueTransfer: unsupported operation: " + op);
  }

  @Override
  public TransferResult visitEqualTo(
      EqualToNode n, TransferInput p) {
    TransferResult res = super.visitEqualTo(n, p);

    Node leftN = n.getLeftOperand();
    Node rightN = n.getRightOperand();
    CFValue leftV = p.getValueOfSubNode(leftN);
    CFValue rightV = p.getValueOfSubNode(rightN);

    // if annotations differ, use the one that is more precise for both
    // sides (and add it to the store if possible)
    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false);
    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false);

    Boolean leftBoolean = getBooleanValue(leftV);
    if (leftBoolean != null) {
      CFValue notLeftV = createBooleanCFValue(!leftBoolean);
      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true);
      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true);
    }
    Boolean rightBoolean = getBooleanValue(rightV);
    if (rightBoolean != null) {
      CFValue notRightV = createBooleanCFValue(!rightBoolean);
      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true);
      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true);
    }

    return res;
  }

  @Override
  public TransferResult visitNotEqual(
      NotEqualNode n, TransferInput p) {
    TransferResult res = super.visitNotEqual(n, p);

    Node leftN = n.getLeftOperand();
    Node rightN = n.getRightOperand();
    CFValue leftV = p.getValueOfSubNode(leftN);
    CFValue rightV = p.getValueOfSubNode(rightN);

    // if annotations differ, use the one that is more precise for both
    // sides (and add it to the store if possible)
    res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true);
    res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true);

    Boolean leftBoolean = getBooleanValue(leftV);
    if (leftBoolean != null) {
      CFValue notLeftV = createBooleanCFValue(!leftBoolean);
      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false);
      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false);
    }
    Boolean rightBoolean = getBooleanValue(rightV);
    if (rightBoolean != null) {
      CFValue notRightV = createBooleanCFValue(!rightBoolean);
      res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false);
      res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false);
    }

    return res;
  }

  @Override
  public TransferResult visitConditionalNot(
      ConditionalNotNode n, TransferInput p) {
    TransferResult transferResult = super.visitConditionalNot(n, p);
    List resultValues =
        calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p);
    return createNewResultBoolean(
        transferResult.getThenStore(),
        transferResult.getElseStore(),
        resultValues,
        transferResult.getResultValue().getUnderlyingType());
  }

  @Override
  public TransferResult visitConditionalAnd(
      ConditionalAndNode n, TransferInput p) {
    TransferResult transferResult = super.visitConditionalAnd(n, p);
    List resultValues =
        calculateConditionalOperator(
            n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p);
    return createNewResultBoolean(
        transferResult.getThenStore(),
        transferResult.getElseStore(),
        resultValues,
        transferResult.getResultValue().getUnderlyingType());
  }

  @Override
  public TransferResult visitConditionalOr(
      ConditionalOrNode n, TransferInput p) {
    TransferResult transferResult = super.visitConditionalOr(n, p);
    List resultValues =
        calculateConditionalOperator(
            n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p);
    return createNewResultBoolean(
        transferResult.getThenStore(),
        transferResult.getElseStore(),
        resultValues,
        transferResult.getResultValue().getUnderlyingType());
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy