package org.checkerframework.checker.index.upperbound;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.index.IndexAbstractTransfer;
import org.checkerframework.checker.index.IndexRefinementInfo;
import org.checkerframework.checker.index.IndexUtil;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.index.upperbound.UBQualifier.LessThanLengthOf;
import org.checkerframework.checker.index.upperbound.UBQualifier.UpperBoundUnknownQualifier;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.FieldAccess;
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.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.cfg.node.TypeCastNode;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractStore;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.QualifierHierarchy;
public class UpperBoundTransfer extends IndexAbstractTransfer {
private UpperBoundAnnotatedTypeFactory atypeFactory;
public UpperBoundTransfer(CFAnalysis analysis) {
super(analysis);
atypeFactory = (UpperBoundAnnotatedTypeFactory) analysis.getTypeFactory();
}
// Refine the type of expressions used as an array dimension to be
// less than length of the array to which the new array is assigned.
// For example, in "int[] array = new int[expr];", the type of expr is @LTEqLength("array").
@Override
public TransferResult<CFValue, CFStore> visitAssignment(
AssignmentNode node, TransferInput<CFValue, CFStore> in) {
TransferResult<CFValue, CFStore> result = super.visitAssignment(node, in);
Node expNode = node.getExpression();
// strip off typecast if any
Node expNodeSansCast =
(expNode instanceof TypeCastNode) ? ((TypeCastNode) expNode).getOperand() : expNode;
// null if right-hand-side is not an array creation expression
ArrayCreationNode acNode =
(expNodeSansCast instanceof ArrayCreationNode)
? acNode = (ArrayCreationNode) expNodeSansCast
: null;
if (acNode != null) {
// Right-hand side of assignment is an array creation expression
List<Node> nodeList = acNode.getDimensions();
if (nodeList.size() < 1) {
return result;
}
Node dim = acNode.getDimension(0);
UBQualifier previousQualifier = getUBQualifier(dim, in);
Receiver arrayRec =
FlowExpressions.internalReprOf(analysis.getTypeFactory(), node.getTarget());
String arrayString = arrayRec.toString();
LessThanLengthOf newInfo =
(LessThanLengthOf) UBQualifier.createUBQualifier(arrayString, "-1");
UBQualifier combined = previousQualifier.glb(newInfo);
AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(combined);
Receiver dimRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), dim);
result.getRegularStore().insertValue(dimRec, newAnno);
propagateToOperands(newInfo, dim, in, result.getRegularStore());
}
return result;
}
/**
* {@code node} is known to be {@code typeOfNode}. If the node is a plus or a minus then the
* types of the left and right operands can be refined to include offsets. If the node is a
* multiplication, its operands can also be refined. See {@link
* #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)}, {@link
* #propagateToSubtractionOperands(LessThanLengthOf, NumericalSubtractionNode, TransferInput,
* CFStore)}, and {@link #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node,
* TransferInput, CFStore)} for details.
*/
private void propagateToOperands(
LessThanLengthOf typeOfNode,
Node node,
TransferInput<CFValue, CFStore> in,
CFStore store) {
if (node instanceof NumericalAdditionNode) {
Node right = ((NumericalAdditionNode) node).getRightOperand();
Node left = ((NumericalAdditionNode) node).getLeftOperand();
propagateToAdditionOperand(typeOfNode, left, right, in, store);
propagateToAdditionOperand(typeOfNode, right, left, in, store);
} else if (node instanceof NumericalSubtractionNode) {
propagateToSubtractionOperands(typeOfNode, (NumericalSubtractionNode) node, in, store);
} else if (node instanceof NumericalMultiplicationNode) {
if (atypeFactory.hasLowerBoundTypeByClass(node, NonNegative.class)
|| atypeFactory.hasLowerBoundTypeByClass(node, Positive.class)) {
Node right = ((NumericalMultiplicationNode) node).getRightOperand();
Node left = ((NumericalMultiplicationNode) node).getLeftOperand();
propagateToMultiplicationOperand(typeOfNode, left, right, in, store);
propagateToMultiplicationOperand(typeOfNode, right, left, in, store);
}
}
}
/**
* {@code other} times {@code node} is known to be {@code typeOfMultiplication}.
*
* <p>This implies that if {@code other} is positive, then {@code node} is {@code
* typeOfMultiplication}. If {@code other} is greater than 1, then {@code node} is {@code
* typeOfMultiplication} plus 1.
*/
private void propagateToMultiplicationOperand(
LessThanLengthOf typeOfMultiplication,
Node node,
Node other,
TransferInput<CFValue, CFStore> in,
CFStore store) {
if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
Long minValue =
IndexUtil.getMinValue(
other.getTree(), atypeFactory.getValueAnnotatedTypeFactory());
if (minValue != null && minValue > 1) {
typeOfMultiplication = (LessThanLengthOf) typeOfMultiplication.plusOffset(1);
}
UBQualifier qual = getUBQualifier(node, in);
UBQualifier newQual = qual.glb(typeOfMultiplication);
Receiver rec = FlowExpressions.internalReprOf(atypeFactory, node);
store.insertValue(rec, atypeFactory.convertUBQualifierToAnnotation(newQual));
}
}
/**
* The subtraction node, {@code node}, is known to be {@code typeOfSubtraction}.
*
* <p>This means that the left node is less than or equal to the length of the array when the
* right node is subtracted from the left node. Note that unlike {@link
* #propagateToAdditionOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)} and {@link
* #propagateToMultiplicationOperand(LessThanLengthOf, Node, Node, TransferInput, CFStore)},
* this method takes the NumericalSubtractionNode instead of the two operand nodes.
*
* @param typeOfSubtraction type of node
* @param node subtraction node that has typeOfSubtraction
* @param in TransferInput
* @param store location to store the refined type
*/
private void propagateToSubtractionOperands(
LessThanLengthOf typeOfSubtraction,
NumericalSubtractionNode node,
TransferInput<CFValue, CFStore> in,
CFStore store) {
UBQualifier left = getUBQualifier(node.getLeftOperand(), in);
UBQualifier newInfo = typeOfSubtraction.minusOffset(node.getRightOperand(), atypeFactory);
UBQualifier newLeft = left.glb(newInfo);
Receiver leftRec = FlowExpressions.internalReprOf(atypeFactory, node.getLeftOperand());
store.insertValue(leftRec, atypeFactory.convertUBQualifierToAnnotation(newLeft));
}
/**
* Refines the type of {@code operand} to {@code typeOfAddition} plus {@code other}. If {@code
* other} is non-negative, then {@code operand} also less than the length of the arrays in
* {@code typeOfAddition}. If {@code other} is positive, then {@code operand} also less than the
* length of the arrays in {@code typeOfAddition} plus 1.
*
* @param typeOfAddition type of {@code operand + other}
* @param operand Node to refine
* @param other Node added to {@code operand}
* @param in TransferInput
* @param store location to store the refined types
*/
private void propagateToAdditionOperand(
LessThanLengthOf typeOfAddition,
Node operand,
Node other,
TransferInput<CFValue, CFStore> in,
CFStore store) {
UBQualifier operandQual = getUBQualifier(operand, in);
UBQualifier newQual = operandQual.glb(typeOfAddition.plusOffset(other, atypeFactory));
/** If the node is NN, add an LTEL to the qual. If POS, add an LTL. */
if (atypeFactory.hasLowerBoundTypeByClass(other, Positive.class)) {
newQual = newQual.glb(typeOfAddition.plusOffset(1));
} else if (atypeFactory.hasLowerBoundTypeByClass(other, NonNegative.class)) {
newQual = newQual.glb(typeOfAddition);
}
Receiver operandRec = FlowExpressions.internalReprOf(atypeFactory, operand);
store.insertValue(operandRec, atypeFactory.convertUBQualifierToAnnotation(newQual));
}
@Override
protected void refineGT(
Node larger,
AnnotationMirror largerAnno,
Node smaller,
AnnotationMirror smallerAnno,
CFStore store,
TransferInput<CFValue, CFStore> in) {
// larger > smaller
UBQualifier largerQual = UBQualifier.createUBQualifier(largerAnno);
// larger + 1 >= smaller
UBQualifier largerQualPlus1 = largerQual.plusOffset(1);
UBQualifier rightQualifier = UBQualifier.createUBQualifier(smallerAnno);
UBQualifier refinedRight = rightQualifier.glb(largerQualPlus1);
if (largerQualPlus1.isLessThanLengthQualifier()) {
propagateToOperands((LessThanLengthOf) largerQualPlus1, smaller, in, store);
}
Receiver rightRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), smaller);
store.insertValue(rightRec, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
}
/**
* This method refines the type of the right expression to the glb the previous type of right
* and the type of left.
*
* <p>Also, if the left expression is an array access, then the types of sub expressions of the
* right are refined.
*/
@Override
protected void refineGTE(
Node left,
AnnotationMirror leftAnno,
Node right,
AnnotationMirror rightAnno,
CFStore store,
TransferInput<CFValue, CFStore> in) {
UBQualifier leftQualifier = UBQualifier.createUBQualifier(leftAnno);
UBQualifier rightQualifier = UBQualifier.createUBQualifier(rightAnno);
UBQualifier refinedRight = rightQualifier.glb(leftQualifier);
if (leftQualifier.isLessThanLengthQualifier()) {
propagateToOperands((LessThanLengthOf) leftQualifier, right, in, store);
}
Receiver rightRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), right);
store.insertValue(rightRec, atypeFactory.convertUBQualifierToAnnotation(refinedRight));
}
@Override
protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
TransferResult<CFValue, CFStore> res,
Node firstNode,
Node secondNode,
CFValue firstValue,
CFValue secondValue,
boolean notEqualTo) {
TransferResult<CFValue, CFStore> result =
super.strengthenAnnotationOfEqualTo(
res, firstNode, secondNode, firstValue, secondValue, notEqualTo);
IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, firstNode, secondNode);
if (rfi.leftAnno == null || rfi.rightAnno == null) {
return result;
}
CFStore equalsStore = notEqualTo ? rfi.elseStore : rfi.thenStore;
CFStore notEqualStore = notEqualTo ? rfi.thenStore : rfi.elseStore;
refineEq(rfi.left, rfi.leftAnno, rfi.right, rfi.rightAnno, equalsStore);
refineNeqArrayLength(rfi.left, rfi.right, rfi.rightAnno, notEqualStore);
refineNeqArrayLength(rfi.right, rfi.left, rfi.leftAnno, notEqualStore);
return rfi.newResult;
}
/** Refines the type of the left and right node to glb of the left and right annotation. */
private void refineEq(
Node left,
AnnotationMirror leftAnno,
Node right,
AnnotationMirror rightAnno,
CFStore store) {
UBQualifier leftQualifier = UBQualifier.createUBQualifier(leftAnno);
UBQualifier rightQualifier = UBQualifier.createUBQualifier(rightAnno);
UBQualifier glb = rightQualifier.glb(leftQualifier);
AnnotationMirror glbAnno = atypeFactory.convertUBQualifierToAnnotation(glb);
List<Node> internalsRight = splitAssignments(right);
for (Node internal : internalsRight) {
Receiver rightRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal);
store.insertValue(rightRec, glbAnno);
}
List<Node> internalsLeft = splitAssignments(left);
for (Node internal : internalsLeft) {
Receiver leftRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal);
store.insertValue(leftRec, glbAnno);
}
}
/**
* If arrayLengthAccess node is an array length field access and the other node is less than or
* equal to that array length, then refine the other nodes type to less than the array length.
*/
private void refineNeqArrayLength(
Node arrayLengthAccess, Node otherNode, AnnotationMirror otherNodeAnno, CFStore store) {
if (NodeUtils.isArrayLengthFieldAccess(arrayLengthAccess)) {
UBQualifier otherQualifier = UBQualifier.createUBQualifier(otherNodeAnno);
FieldAccess fa =
FlowExpressions.internalReprOfFieldAccess(
atypeFactory, (FieldAccessNode) arrayLengthAccess);
if (!fa.getReceiver().containsUnknown()) {
String array = fa.getReceiver().toString();
if (otherQualifier.hasArrayWithOffsetNeg1(array)) {
otherQualifier = otherQualifier.glb(UBQualifier.createUBQualifier(array, "0"));
for (Node internal : splitAssignments(otherNode)) {
Receiver leftRec =
FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal);
store.insertValue(
leftRec,
atypeFactory.convertUBQualifierToAnnotation(otherQualifier));
}
}
}
}
}
/**
* If some Node a is known to be less than the length of some array, x, then, the type of a + b,
* is @LTLengthOf(value="x", offset="-b"). If b is known to be less than the length of some
* other array, y, then the type of a + b is @LTLengthOf(value={"x", "y"}, offset={"-b", "-a"}).
*
* <p>Alternatively, if a is known to be less than the length of x when some offset, o, is added
* to a (the type of a is @LTLengthOf(value="x", offset="o")), then the type of a + b
* is @LTLengthOf(value="x",offset="o - b"). (Note, if "o - b" can be computed, then it is and
* the result is used in the annotation.)
*
* <p>In addition, If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int
* and expression j is less than or equal to the length of f1, then the type of i + j is
* .@LTLengthOf("f2").
*/
@Override
public TransferResult<CFValue, CFStore> visitNumericalAddition(
NumericalAdditionNode n, TransferInput<CFValue, CFStore> in) {
// type of leftNode + rightNode is glb(T, S) where
// T = minusOffset(type(leftNode), rightNode) and
// S = minusOffset(type(rightNode), leftNode)
UBQualifier left = getUBQualifier(n.getLeftOperand(), in);
UBQualifier T = left.minusOffset(n.getRightOperand(), atypeFactory);
UBQualifier right = getUBQualifier(n.getRightOperand(), in);
UBQualifier S = right.minusOffset(n.getLeftOperand(), atypeFactory);
UBQualifier glb = T.glb(S);
if (left.isLessThanLengthQualifier() && right.isLessThanLengthQualifier()) {
// If expression i has type @LTLengthOf(value = "f2", offset = "f1.length") int and
// expression j is less than or equal to the length of f1, then the type of i + j is
// @LTLengthOf("f2").
UBQualifier r = removeArrayLengths((LessThanLengthOf) left, (LessThanLengthOf) right);
glb = glb.glb(r);
UBQualifier l = removeArrayLengths((LessThanLengthOf) right, (LessThanLengthOf) left);
glb = glb.glb(l);
}
return createTransferResult(n, in, glb);
}
/**
* Return the result of adding i to j, when expression i has type @LTLengthOf(value = "f2",
* offset = "f1.length") int and expression j is less than or equal to the length of f1, then
* the type of i + j is @LTLengthOf("f2").
*
* <p>Similarly, return the result of adding i to j,when expression i has type @LTLengthOf
* (value = "f2", offset = "f1.length - 1") int and expression j is less than the length of f1,
* then the type of i + j is @LTLengthOf("f2").
*
* @param i the type of the expression added to j
* @param j the type of the expression added to i
* @return the type of i + j
*/
private UBQualifier removeArrayLengths(LessThanLengthOf i, LessThanLengthOf j) {
List<String> lessThan = new ArrayList<>();
List<String> lessThanOrEqaul = new ArrayList<>();
for (String array : i.getArrays()) {
if (i.isLessThanLengthOf(array)) {
lessThan.add(array);
} else if (i.hasArrayWithOffsetNeg1(array)) {
lessThanOrEqaul.add(array);
}
}
// Creates a qualifier that is the same a j with the array.length offsets removed. If
// an offset doesn't have an array.length, then the offset/array pair is removed. If
// there are no such pairs, Unknown is returned.
UBQualifier lessThanEqQ = j.removeArrayLengthAccess(lessThanOrEqaul);
// Creates a qualifier that is the same a j with the array.length - 1 offsets removed. If
// an offset doesn't have an array.length, then the offset/array pair is removed. If
// there are no such pairs, Unknown is returned.
UBQualifier lessThanQ = j.removeArrayLengthAccessAndNeg1(lessThan);
return lessThanEqQ.glb(lessThanQ);
}
/**
* If some Node a is known to be less than the length of some array x, then the type of a - b
* is @LTLengthOf(value="x", offset="b"). If b is known to be less than the length of some other
* array, this doesn't add any information about the type of a - b. But, if b is non-negative or
* positive, then a - b should keep the types of a.
*/
@Override
public TransferResult<CFValue, CFStore> visitNumericalSubtraction(
NumericalSubtractionNode n, TransferInput<CFValue, CFStore> in) {
UBQualifier left = getUBQualifier(n.getLeftOperand(), in);
UBQualifier leftWithOffset = left.plusOffset(n.getRightOperand(), atypeFactory);
if (atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), NonNegative.class)
|| atypeFactory.hasLowerBoundTypeByClass(n.getRightOperand(), Positive.class)) {
// If the right side of the expression is NN or POS, then all the left side's
// annotations should be kept.
if (left.isLessThanLengthQualifier()) {
leftWithOffset = left.glb(leftWithOffset);
}
}
return createTransferResult(n, in, leftWithOffset);
}
/**
* If n is an array length field access, then the type of a.length, is the glb
* of @LTEqLengthOf("a") and the value of a.length in the store.
*/
@Override
public TransferResult<CFValue, CFStore> visitFieldAccess(
FieldAccessNode n, TransferInput<CFValue, CFStore> in) {
if (NodeUtils.isArrayLengthFieldAccess(n)) {
FieldAccess arrayLength = FlowExpressions.internalReprOfFieldAccess(atypeFactory, n);
Receiver arrayRec = arrayLength.getReceiver();
// Look up the SameLen type of the array.
Tree arrayTree = n.getReceiver().getTree();
AnnotationMirror sameLenAnno = atypeFactory.sameLenAnnotationFromTree(arrayTree);
List<String> sameLenArrays =
sameLenAnno == null
? new ArrayList<String>()
: IndexUtil.getValueOfAnnotationWithStringArgument(sameLenAnno);
if (!sameLenArrays.contains(arrayRec.toString())) {
sameLenArrays.add(arrayRec.toString());
}
ArrayList<String> offsets = new ArrayList<>(sameLenArrays.size());
for (String s : sameLenArrays) {
offsets.add("-1");
}
if (CFAbstractStore.canInsertReceiver(arrayRec)) {
UBQualifier qualifier = UBQualifier.createUBQualifier(sameLenArrays, offsets);
UBQualifier previous = getUBQualifier(n, in);
return createTransferResult(n, in, qualifier.glb(previous));
}
}
return super.visitFieldAccess(n, in);
}
/**
* Returns the UBQualifier for node. It does this by finding a {@link CFValue} for node. First
* it checks the store in the transfer input. If one isn't there, the analysis is checked. If
* the UNKNOWN qualifier is returned, then the AnnotatedTypeMirror from the type factory is
* used.
*
* @param n node
* @param in transfer input
* @return the UBQualifier for node
*/
private UBQualifier getUBQualifier(Node n, TransferInput<CFValue, CFStore> in) {
QualifierHierarchy hierarchy = analysis.getTypeFactory().getQualifierHierarchy();
Receiver rec = FlowExpressions.internalReprOf(atypeFactory, n);
CFValue value = null;
if (CFAbstractStore.canInsertReceiver(rec)) {
value = in.getRegularStore().getValue(rec);
}
if (value == null) {
value = analysis.getValue(n);
}
UBQualifier qualifier = getUBQualifier(hierarchy, value);
if (qualifier.isUnknown()) {
// The qualifier from the store or analysis might be UNKNOWN if there was some error.
// For example,
// @LTLength("a") int i = 4; // error
// The type of i in the store is @UpperBoundUnknown, but the type of i as computed by
// the type factory is @LTLength("a"), so use that type.
CFValue valueFromFactory = getValueFromFactory(n.getTree(), n);
return getUBQualifier(hierarchy, valueFromFactory);
}
return qualifier;
}
private UBQualifier getUBQualifier(QualifierHierarchy hierarchy, CFValue value) {
if (value == null) {
return UpperBoundUnknownQualifier.UNKNOWN;
}
Set<AnnotationMirror> set = value.getAnnotations();
AnnotationMirror anno = hierarchy.findAnnotationInHierarchy(set, atypeFactory.UNKNOWN);
if (anno == null) {
return UpperBoundUnknownQualifier.UNKNOWN;
}
return UBQualifier.createUBQualifier(anno);
}
private TransferResult<CFValue, CFStore> createTransferResult(
Node n, TransferInput<CFValue, CFStore> in, UBQualifier qualifier) {
AnnotationMirror newAnno = atypeFactory.convertUBQualifierToAnnotation(qualifier);
CFValue value = analysis.createSingleAnnotationValue(newAnno, n.getType());
if (in.containsTwoStores()) {
CFStore thenStore = in.getThenStore();
CFStore elseStore = in.getElseStore();
return new ConditionalTransferResult<>(
finishValue(value, thenStore, elseStore), thenStore, elseStore);
} else {
CFStore info = in.getRegularStore();
return new RegularTransferResult<>(finishValue(value, info), info);
}
}
}