blob: 6e0a2759d7bc26068da3526a9440ce86052ffbfc [file] [log] [blame]
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.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
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<CFValue, CFStore, CFTransfer> 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<CFValue, CFStore> 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<CFValue, CFStore> 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<Integer> getStringLengths(Node subNode, TransferInput<CFValue, CFStore> 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<String> getStringValues(Node subNode, TransferInput<CFValue, CFStore> 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<? extends Object> 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<Long> longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class);
values = NumberUtils.castNumbers(subNode.getType(), longValues);
} else {
values = getNumericalValues(subNode, p);
}
if (values == null) {
return null;
}
List<String> 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<Boolean> getBooleanValues(Node subNode, TransferInput<CFValue, CFStore> 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<Character> getCharValues(Node subNode, TransferInput<CFValue, CFStore> 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<CFValue, CFStore> 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<? extends Number> getNumericalValues(
Node subNode, TransferInput<CFValue, CFStore> 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<? extends Number> 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<? extends Number> 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<CFValue, CFStore> 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<Long> values = atypeFactory.getIntValues(val);
range = ValueCheckerUtils.getRangeFromValues(values);
} else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) {
List<Double> 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<CFValue, CFStore> 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<CFValue, CFStore> 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<CFValue, CFStore> createNewResult(
TransferResult<CFValue, CFStore> result, AnnotationMirror resultAnno) {
CFValue newResultValue =
analysis.createSingleAnnotationValue(
resultAnno, result.getResultValue().getUnderlyingType());
return new RegularTransferResult<>(newResultValue, result.getRegularStore());
}
/** Create a boolean transfer result. */
private TransferResult<CFValue, CFStore> createNewResultBoolean(
CFStore thenStore, CFStore elseStore, List<Boolean> 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<CFValue, CFStore> visitFieldAccess(
FieldAccessNode node, TransferInput<CFValue, CFStore> in) {
TransferResult<CFValue, CFStore> result = super.visitFieldAccess(node, in);
refineArrayAtLengthAccess(node, result.getRegularStore());
return result;
}
@Override
public TransferResult<CFValue, CFStore> visitMethodInvocation(
MethodInvocationNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<Long> 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<CFValue, CFStore> visitStringConcatenateAssignment(
StringConcatenateAssignmentNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> result = super.visitStringConcatenateAssignment(n, p);
return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result);
}
@Override
public TransferResult<CFValue, CFStore> visitStringConcatenate(
StringConcatenateNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<Integer> calculateLengthAddition(
List<Integer> leftLengths, List<Integer> rightLengths) {
List<Integer> 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<CFValue, CFStore> p) {
// Try using sets of string values
List<String> leftValues = getStringValues(leftOperand, p);
List<String> 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<String> 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<Integer> leftLengths =
leftValues != null
? ValueCheckerUtils.getLengthsForStringValues(leftValues)
: getStringLengths(leftOperand, p);
List<Integer> 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<Integer> 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.union(Range.create(4, 4)); // "null"
}
if (isNullable(rightOperand)) {
rightLengthRange.union(Range.create(4, 4)); // "null"
}
}
Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange);
return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange);
}
return atypeFactory.UNKNOWNVAL;
}
public TransferResult<CFValue, CFStore> stringConcatenation(
Node leftOperand,
Node rightOperand,
TransferInput<CFValue, CFStore> p,
TransferResult<CFValue, CFStore> 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<CFValue, CFStore> p) {
if (!isIntRangeOrIntegralUnknownVal(leftNode, p)
&& !isIntRangeOrIntegralUnknownVal(rightNode, p)) {
List<Number> 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<CFValue, CFStore> 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 BugInCF("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<Number> calculateValuesBinaryOp(
Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) {
List<? extends Number> lefts = getNumericalValues(leftNode, p);
List<? extends Number> rights = getNumericalValues(rightNode, p);
if (lefts == null || rights == null) {
return null;
}
List<Number> 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 BugInCF("ValueTransfer: unsupported operation: " + op);
}
}
}
return resultValues;
}
@Override
public TransferResult<CFValue, CFStore> visitNumericalAddition(
NumericalAdditionNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitNumericalAddition(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
NumericalSubtractionNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitNumericalSubtraction(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitNumericalMultiplication(
NumericalMultiplicationNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMultiplication(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitIntegerDivision(
IntegerDivisionNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitIntegerDivision(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitFloatingDivision(
FloatingDivisionNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitFloatingDivision(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitIntegerRemainder(
IntegerRemainderNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitIntegerRemainder(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitFloatingRemainder(
FloatingRemainderNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitFloatingRemainder(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitLeftShift(
LeftShiftNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitLeftShift(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitSignedRightShift(
SignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitSignedRightShift(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitUnsignedRightShift(
UnsignedRightShiftNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitUnsignedRightShift(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitBitwiseAnd(
BitwiseAndNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseAnd(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitBitwiseOr(
BitwiseOrNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseOr(n, p);
AnnotationMirror resultAnno =
calculateNumericalBinaryOp(
n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitBitwiseXor(
BitwiseXorNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<CFValue, CFStore> p) {
if (!isIntRange(operand, p)) {
List<Number> 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<CFValue, CFStore> 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 BugInCF("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<Number> calculateValuesUnaryOp(
Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) {
List<? extends Number> lefts = getNumericalValues(operand, p);
if (lefts == null) {
return null;
}
List<Number> 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 BugInCF("ValueTransfer: unsupported operation: " + op);
}
}
return resultValues;
}
@Override
public TransferResult<CFValue, CFStore> visitNumericalMinus(
NumericalMinusNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMinus(n, p);
AnnotationMirror resultAnno =
calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitNumericalPlus(
NumericalPlusNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitNumericalPlus(n, p);
AnnotationMirror resultAnno =
calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p);
return createNewResult(transferResult, resultAnno);
}
@Override
public TransferResult<CFValue, CFStore> visitBitwiseComplement(
BitwiseComplementNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<Boolean> 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<? extends Number> lefts = getNumericalValues(leftNode, leftAnno);
List<? extends Number> 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<Boolean> resultValues = new ArrayList<>();
// These lists are used to refine the values in the store based on the results of the
// comparison.
List<Number> thenLeftVals = new ArrayList<>();
List<Number> elseLeftVals = new ArrayList<>();
List<Number> thenRightVals = new ArrayList<>();
List<Number> 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 BugInCF("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<Boolean> 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 BugInCF("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<CFValue, CFStore> visitLessThan(
LessThanNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitLessThan(n, p);
CFStore thenStore = transferResult.getThenStore();
CFStore elseStore = transferResult.getElseStore();
List<Boolean> 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<CFValue, CFStore> visitLessThanOrEqual(
LessThanOrEqualNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitLessThanOrEqual(n, p);
CFStore thenStore = transferResult.getThenStore();
CFStore elseStore = transferResult.getElseStore();
List<Boolean> 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<CFValue, CFStore> visitGreaterThan(
GreaterThanNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThan(n, p);
CFStore thenStore = transferResult.getThenStore();
CFStore elseStore = transferResult.getElseStore();
List<Boolean> 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<CFValue, CFStore> visitGreaterThanOrEqual(
GreaterThanOrEqualNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThanOrEqual(n, p);
CFStore thenStore = transferResult.getThenStore();
CFStore elseStore = transferResult.getElseStore();
List<Boolean> 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<CFValue, CFStore> strengthenAnnotationOfEqualTo(
TransferResult<CFValue, CFStore> 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<Boolean> 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<Boolean> ALL_BOOLEANS =
Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE});
private List<Boolean> calculateConditionalOperator(
Node leftNode, Node rightNode, ConditionalOperators op, TransferInput<CFValue, CFStore> p) {
List<Boolean> lefts = getBooleanValues(leftNode, p);
if (lefts == null) {
lefts = ALL_BOOLEANS;
}
List<Boolean> 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<Boolean> 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 BugInCF("ValueTransfer: unsupported operation: " + op);
}
@Override
public TransferResult<CFValue, CFStore> visitEqualTo(
EqualToNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<CFValue, CFStore> visitNotEqual(
NotEqualNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> 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<CFValue, CFStore> visitConditionalNot(
ConditionalNotNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitConditionalNot(n, p);
List<Boolean> resultValues =
calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p);
return createNewResultBoolean(
transferResult.getThenStore(),
transferResult.getElseStore(),
resultValues,
transferResult.getResultValue().getUnderlyingType());
}
@Override
public TransferResult<CFValue, CFStore> visitConditionalAnd(
ConditionalAndNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitConditionalAnd(n, p);
List<Boolean> resultValues =
calculateConditionalOperator(
n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p);
return createNewResultBoolean(
transferResult.getThenStore(),
transferResult.getElseStore(),
resultValues,
transferResult.getResultValue().getUnderlyingType());
}
@Override
public TransferResult<CFValue, CFStore> visitConditionalOr(
ConditionalOrNode n, TransferInput<CFValue, CFStore> p) {
TransferResult<CFValue, CFStore> transferResult = super.visitConditionalOr(n, p);
List<Boolean> resultValues =
calculateConditionalOperator(
n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p);
return createNewResultBoolean(
transferResult.getThenStore(),
transferResult.getElseStore(),
resultValues,
transferResult.getResultValue().getUnderlyingType());
}
}