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

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

package org.checkerframework.common.value;

import com.sun.source.tree.ExpressionTree;

import org.checkerframework.checker.nullness.qual.Nullable;
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.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;

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;

/** 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 qualHierarchy;

    /** True if -AnonNullStringsConcatenation was passed on the command line. */
    private final boolean nonNullStringsConcatenation;

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

    /** 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 a TransferInput
     * @return a range of possible lengths for {@code subNode}, as casted to a String
     */
    private @Nullable 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 @Nullable 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 a TransferInput
     * @return a list of possible values for {@code subNode} or null
     */
    private @Nullable 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 @Nullable 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 @Nullable 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 =
                    qualHierarchy.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 qualHierarchy.findAnnotationInHierarchy(
                cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL);
    }

    /**
     * Returns a list of possible values, or null if no estimate is available and any value is
     * possible.
     */
    private @Nullable 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 @Nullable 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 a 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 an 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;
        }
        JavaExpression receiverJE = JavaExpression.fromNode(receiverNode);
        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.
            store.insertValue(receiverJE, 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 {
            TypeMirror receiverTM = receiverJE.getType();
            combinedRecAnno =
                    qualHierarchy.greatestLowerBoundShallow(
                            oldRecAnno, receiverTM, newRecAnno, receiverTM);
        }
        store.insertValue(receiverJE, combinedRecAnno);
    }

    @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.elementFromTree((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);

        if (leftValues != null && rightValues != null) {
            // Both operands have known string values, compute set of results
            if (!nonNullStringsConcatenation) {
                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 (!nonNullStringsConcatenation) {
                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 (!nonNullStringsConcatenation) {
                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 @Nullable 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<>(lefts.size() * rights.size());
        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 @Nullable 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 @Nullable 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.
        int numResultValues = lefts.size() * rights.size();
        List resultValues = new ArrayList<>(numResultValues);

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

        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 @Nullable 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 =
                    qualHierarchy.greatestLowerBoundShallow(
                            anno, je.getType(), currentAnno, je.getType());
            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,
            ExpressionTree 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 - 2025 Weber Informatics LLC | Privacy Policy