package org.checkerframework.common.value;
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.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.BoolVal;
import org.checkerframework.common.value.qual.BottomVal;
import org.checkerframework.common.value.qual.DoubleVal;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.common.value.qual.UnknownVal;
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.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
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.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.Node;
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.UnsignedRightShiftNode;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.TypesUtils;
public class ValueTransfer extends CFTransfer {
ValueAnnotatedTypeFactory atypefactory;
public ValueTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
super(analysis);
atypefactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory();
}
/**
* 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.
*/
private List<String> getStringValues(Node subNode, TransferInput<CFValue, CFStore> p) {
CFValue value = p.getValueOfSubNode(subNode);
// @StringVal, @UnknownVal, @BottomVal
AnnotationMirror stringAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), StringVal.class);
if (stringAnno != null) {
return AnnotationUtils.getElementValueArray(stringAnno, "value", String.class, true);
}
AnnotationMirror topAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), UnknownVal.class);
if (topAnno != null) {
return null;
}
AnnotationMirror bottomAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), BottomVal.class);
if (bottomAnno != null) {
return new ArrayList<String>();
}
// @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string)
List<? extends Object> values;
AnnotationMirror numberAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), BoolVal.class);
if (numberAnno != null) {
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 = new ArrayList<String>();
for (Object o : values) {
stringValues.add(o.toString());
}
// Empty list means bottom value
return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues;
}
/** Get possible boolean values from @BoolVal. */
private List<Boolean> getBooleanValues(Node subNode, TransferInput<CFValue, CFStore> p) {
CFValue value = p.getValueOfSubNode(subNode);
AnnotationMirror intAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), BoolVal.class);
return ValueAnnotatedTypeFactory.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.getAnnotationByClass(value.getAnnotations(), IntVal.class);
if (intAnno != null) {
return ValueAnnotatedTypeFactory.getCharValues(intAnno);
}
intAnno = AnnotationUtils.getAnnotationByClass(value.getAnnotations(), IntRange.class);
if (intAnno != null) {
Range range = ValueAnnotatedTypeFactory.getRange(intAnno);
return ValueCheckerUtils.getValuesFromRange(range, Character.class);
}
return new ArrayList<Character>();
}
private AnnotationMirror getValueAnnotation(Node subNode, TransferInput<CFValue, CFStore> p) {
CFValue value = p.getValueOfSubNode(subNode);
return getValueAnnotation(value);
}
private AnnotationMirror getValueAnnotation(CFValue cfValue) {
return atypefactory
.getQualifierHierarchy()
.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);
}
private List<? extends Number> getNumericalValues(Node subNode, AnnotationMirror valueAnno) {
if (valueAnno == null || AnnotationUtils.areSameByClass(valueAnno, UnknownVal.class)) {
return null;
} else if (AnnotationUtils.areSameByClass(valueAnno, BottomVal.class)) {
return new ArrayList<>();
}
List<? extends Number> values;
if (AnnotationUtils.areSameByClass(valueAnno, IntVal.class)) {
values = AnnotationUtils.getElementValueArray(valueAnno, "value", Long.class, true);
} else if (AnnotationUtils.areSameByClass(valueAnno, DoubleVal.class)) {
values = AnnotationUtils.getElementValueArray(valueAnno, "value", Double.class, true);
} 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);
}
private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) {
Range range;
if (val == null || AnnotationUtils.areSameByClass(val, UnknownVal.class)) {
range = Range.EVERYTHING;
} else if (AnnotationUtils.areSameByClass(val, IntRange.class)) {
range = ValueAnnotatedTypeFactory.getRange(val);
} else if (AnnotationUtils.areSameByClass(val, IntVal.class)) {
List<Long> values =
AnnotationUtils.getElementValueArray(val, "value", Long.class, true);
range = ValueCheckerUtils.getRangeFromValues(values);
} else if (AnnotationUtils.areSameByClass(val, DoubleVal.class)) {
List<Double> values =
AnnotationUtils.getElementValueArray(val, "value", Double.class, true);
range = ValueCheckerUtils.getRangeFromValues(values);
} else if (AnnotationUtils.areSameByClass(val, BottomVal.class)) {
return Range.NOTHING;
} else {
range = Range.EVERYTHING;
}
return NumberUtils.castRange(node.getType(), range);
}
/** a helper function to determine if this node is annotated with @IntRange */
private boolean isIntRange(Node subNode, TransferInput<CFValue, CFStore> p) {
CFValue value = p.getValueOfSubNode(subNode);
return AnnotationUtils.containsSameByClass(value.getAnnotations(), IntRange.class);
}
/** a helper function to determine if this node is annotated with @UnknownVal */
private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) {
return AnnotationUtils.areSameByClass(anno, UnknownVal.class)
&& TypesUtils.isIntegral(node.getType());
}
/**
* 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;
}
/**
* 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;
}
CFValue value =
store.getValue(
FlowExpressions.internalReprOf(analysis.getTypeFactory(), arrayLengthNode));
if (value == null) {
return;
}
boolean isIntRange = false;
AnnotationMirror lengthAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), IntVal.class);
if (lengthAnno == null) {
lengthAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), IntRange.class);
isIntRange = true;
}
if (lengthAnno != null) {
RangeOrListOfValues rolv;
if (isIntRange) {
rolv = new RangeOrListOfValues(ValueAnnotatedTypeFactory.getRange(lengthAnno));
} else {
List<Long> lengthValues = ValueAnnotatedTypeFactory.getIntValues(lengthAnno);
rolv =
new RangeOrListOfValues(
RangeOrListOfValues.convertLongsToInts(lengthValues));
}
AnnotationMirror newArrayAnno = rolv.createAnnotation(atypefactory);
AnnotationMirror oldArrayAnno =
atypefactory.getAnnotationMirror(
arrayLengthNode.getReceiver().getTree(), ArrayLen.class);
if (oldArrayAnno == null) {
oldArrayAnno =
atypefactory.getAnnotationMirror(
arrayLengthNode.getReceiver().getTree(), ArrayLenRange.class);
}
AnnotationMirror combinedAnno;
// If the array doesn't have an @ArrayLen annotation, use the new annotation.
// If it does have an annotation, combine the facts known about the array
// with the facts known about its length using GLB.
if (oldArrayAnno == null) {
combinedAnno = newArrayAnno;
} else {
combinedAnno =
atypefactory
.getQualifierHierarchy()
.greatestLowerBound(oldArrayAnno, newArrayAnno);
}
Receiver arrayRec =
FlowExpressions.internalReprOf(
analysis.getTypeFactory(), arrayLengthNode.getReceiver());
store.insertValue(arrayRec, combinedAnno);
} else {
// If the array's length is bottom, then this is dead code, so the array's type
// should also be bottom.
lengthAnno =
AnnotationUtils.getAnnotationByClass(value.getAnnotations(), BottomVal.class);
if (lengthAnno != null) {
Receiver arrayRec =
FlowExpressions.internalReprOf(
analysis.getTypeFactory(), arrayLengthNode.getReceiver());
store.insertValue(arrayRec, lengthAnno);
}
}
}
@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);
}
public TransferResult<CFValue, CFStore> stringConcatenation(
Node leftOperand,
Node rightOperand,
TransferInput<CFValue, CFStore> p,
TransferResult<CFValue, CFStore> result) {
List<String> lefts = getStringValues(leftOperand, p);
List<String> rights = getStringValues(rightOperand, p);
List<String> concat;
if (lefts == null || rights == null) {
concat = null;
} else {
concat = new ArrayList<>();
if (lefts.isEmpty()) {
lefts = Collections.singletonList("null");
}
if (rights.isEmpty()) {
rights = Collections.singletonList("null");
}
for (String left : lefts) {
for (String right : rights) {
concat.add(left + right);
}
}
}
AnnotationMirror stringVal = atypefactory.createStringAnnotation(concat);
TypeMirror underlyingType = result.getResultValue().getUnderlyingType();
CFValue newResultValue = analysis.createSingleAnnotationValue(stringVal, 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 (!isIntRange(leftNode, p) && !isIntRange(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.isIntegral(leftNode.getType())
&& TypesUtils.isIntegral(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:
ErrorReporter.errorAbort("ValueTransfer: unsupported operation: " + op);
throw new RuntimeException("this can't happen");
}
// 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:
ErrorReporter.errorAbort("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 */
private Range calculateRangeUnaryOp(
Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) {
if (TypesUtils.isIntegral(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:
ErrorReporter.errorAbort("ValueTransfer: unsupported operation: " + op);
throw new RuntimeException("this can't happen");
}
// 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<>();
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:
ErrorReporter.errorAbort("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 (AnnotationUtils.areSameByClass(leftAnno, IntRange.class)
|| AnnotationUtils.areSameByClass(rightAnno, IntRange.class)
|| 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<Boolean> resultValues = new ArrayList<>();
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 new ArrayList<>();
}
return null;
}
// 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:
ErrorReporter.errorAbort("ValueTransfer: unsupported operation: " + op);
throw new RuntimeException("this can't happen");
}
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:
ErrorReporter.errorAbort("ValueTransfer: unsupported operation: " + op);
throw new RuntimeException("this is impossible, but javac issues a warning");
}
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.
*/
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.
*/
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) {
for (Node internal : splitAssignments(node)) {
AnnotationMirror currentAnno =
atypefactory
.getAnnotatedType(internal.getTree())
.getAnnotationInHierarchy(atypefactory.BOTTOMVAL);
Receiver rec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal);
// Combine the new annotations based on the results of the comparison with the existing type.
store.insertValue(
rec,
atypefactory.getQualifierHierarchy().greatestLowerBound(anno, currentAnno));
if (node instanceof FieldAccessNode) {
refineArrayAtLengthAccess((FieldAccessNode) internal, 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);
}
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> resultValues = new ArrayList<>();
List<Boolean> rights = null;
if (rightNode != null) {
rights = getBooleanValues(rightNode, p);
if (rights == null) {
rights = ALL_BOOLEANS;
}
}
switch (op) {
case NOT:
for (Boolean left : lefts) {
resultValues.add(!left);
}
return resultValues;
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;
}
ErrorReporter.errorAbort("ValueTransfer: unsupported operation: " + op);
throw new RuntimeException("this can't happen");
}
@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());
}
}