package org.checkerframework.checker.index.lowerbound;
import java.util.List;
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.GTENegativeOne;
import org.checkerframework.checker.index.qual.LowerBoundUnknown;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* Implements dataflow refinement rules based on tests: <, >, ==, and their derivatives.
*
* <p>>, <, ≥, ≤, ==, and != nodes are represented as combinations of > and ≥
* (e.g. == is ≥ in both directions in the then branch), and implement refinements based on these
* decompositions.
*
* <pre>
* Refinement/transfer rules for conditionals:
*
* There are two "primitives":
*
* x > y, which implies things about x based on y's type:
*
* y has type: implies x has type:
* gte-1 nn
* nn pos
* pos pos
*
* and x ≥ y:
*
* y has type: implies x has type:
* gte-1 gte-1
* nn nn
* pos pos
*
* These two "building blocks" can be combined to make all
* other conditional expressions:
*
* EXPR THEN ELSE
* x > y x > y y ≥ x
* x ≥ y x ≥ y y > x
* x < y y > x x ≥ y
* x ≤ y y ≥ x x > y
*
* Or, more formally:
*
* EXPR THEN ELSE
* x > y x_refined = GLB(x_orig, promote(y)) y_refined = GLB(y_orig, x)
* x ≥ y x_refined = GLB(x_orig, y) y_refined = GLB(y_orig, promote(x))
* x < y y_refined = GLB(y_orig, promote(x)) x_refined = GLB(x_orig, y)
* x ≤ y y_refined = GLB(y_orig, x) x_refined = GLB(x_orig, promote(y))
*
* where GLB is the greatest lower bound and promote is the increment
* function on types (or, equivalently, the function specified by the "x
* > y" information above).
*
* There's also ==, which is a special case. Only the THEN
* branch is refined:
*
* EXPR THEN ELSE
* x == y x ≥ y && y ≥ x nothing known
*
* or, more formally:
*
* EXPR THEN ELSE
* x == y x_refined = GLB(x_orig, y_orig) nothing known
* y_refined = GLB(x_orig, y_orig)
*
* finally, not equal:
*
* EXPR THEN ELSE
* x != y nothing known x ≥ y && y ≥ x
*
* more formally:
*
* EXPR THEN ELSE
* x != y nothing known x_refined = GLB(x_orig, y_orig)
* y_refined = GLB(x_orig, y_orig)
*
* </pre>
*/
public class LowerBoundTransfer extends IndexAbstractTransfer {
/** The canonical {@link GTENegativeOne} annotation. */
public final AnnotationMirror GTEN1;
/** The canonical {@link NonNegative} annotation. */
public final AnnotationMirror NN;
/** The canonical {@link Positive} annotation. */
public final AnnotationMirror POS;
/** The canonical {@link LowerBoundUnknown} annotation. */
public final AnnotationMirror UNKNOWN;
// The ATF (Annotated Type Factory).
private LowerBoundAnnotatedTypeFactory aTypeFactory;
public LowerBoundTransfer(CFAnalysis analysis) {
super(analysis);
aTypeFactory = (LowerBoundAnnotatedTypeFactory) analysis.getTypeFactory();
// Initialize qualifiers.
GTEN1 = aTypeFactory.GTEN1;
NN = aTypeFactory.NN;
POS = aTypeFactory.POS;
UNKNOWN = aTypeFactory.UNKNOWN;
}
/**
* Refines GTEN1 to NN if it is not equal to -1, and NN to Pos if it is not equal to 0.
*
* @param mLiteral a potential literal
* @param otherNode the node on the other side of the ==/!=
* @param otherAnno the annotation of the other side of the ==/!=
*/
private void notEqualToValue(
Node mLiteral, Node otherNode, AnnotationMirror otherAnno, CFStore store) {
Long integerLiteral =
IndexUtil.getExactValue(
mLiteral.getTree(), aTypeFactory.getValueAnnotatedTypeFactory());
if (integerLiteral == null) {
return;
}
long intLiteral = integerLiteral.longValue();
if (intLiteral == 0) {
if (AnnotationUtils.areSameByClass(otherAnno, NonNegative.class)) {
List<Node> internals = splitAssignments(otherNode);
for (Node internal : internals) {
Receiver rec = FlowExpressions.internalReprOf(aTypeFactory, internal);
store.insertValue(rec, POS);
}
}
} else if (intLiteral == -1) {
if (AnnotationUtils.areSameByClass(otherAnno, GTENegativeOne.class)) {
List<Node> internals = splitAssignments(otherNode);
for (Node internal : internals) {
Receiver rec = FlowExpressions.internalReprOf(aTypeFactory, internal);
store.insertValue(rec, NN);
}
}
}
}
/** Implements the transfer rules for both equal nodes and not-equals nodes. */
@Override
protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo(
TransferResult<CFValue, CFStore> result,
Node firstNode,
Node secondNode,
CFValue firstValue,
CFValue secondValue,
boolean notEqualTo) {
result =
super.strengthenAnnotationOfEqualTo(
result, firstNode, secondNode, firstValue, secondValue, notEqualTo);
IndexRefinementInfo rfi = new IndexRefinementInfo(result, analysis, secondNode, firstNode);
if (rfi.leftAnno == null || rfi.rightAnno == null) {
return result;
}
// There is also special processing to look
// for literals on one side of the equals and a GTEN1 or NN on the other, so that
// those types can be promoted in the branch where their values are not equal to certain
// literals.
CFStore notEqualsStore = notEqualTo ? rfi.thenStore : rfi.elseStore;
notEqualToValue(rfi.left, rfi.right, rfi.rightAnno, notEqualsStore);
notEqualToValue(rfi.right, rfi.left, rfi.leftAnno, notEqualsStore);
return rfi.newResult;
}
/**
* The implementation of the algorithm for refining a > test. Changes the type of left (the
* greater one) to one closer to bottom than the type of right. Can't call the promote function
* from the ATF directly because a new expression isn't introduced here - the modifications have
* to be made to an existing one.
*/
@Override
protected void refineGT(
Node left,
AnnotationMirror leftAnno,
Node right,
AnnotationMirror rightAnno,
CFStore store,
TransferInput<CFValue, CFStore> in) {
if (rightAnno == null || leftAnno == null) {
return;
}
Receiver leftRec = FlowExpressions.internalReprOf(aTypeFactory, left);
if (AnnotationUtils.areSame(rightAnno, GTEN1)) {
store.insertValue(leftRec, NN);
return;
}
if (AnnotationUtils.areSame(rightAnno, NN)) {
store.insertValue(leftRec, POS);
return;
}
if (AnnotationUtils.areSame(rightAnno, POS)) {
store.insertValue(leftRec, POS);
return;
}
}
/**
* Refines left to exactly the level of right, since in the worst case they're equal. Modifies
* an existing type in the store, but has to be careful not to overwrite a more precise existing
* type.
*/
@Override
protected void refineGTE(
Node left,
AnnotationMirror leftAnno,
Node right,
AnnotationMirror rightAnno,
CFStore store,
TransferInput<CFValue, CFStore> in) {
if (rightAnno == null || leftAnno == null) {
return;
}
Receiver leftRec = FlowExpressions.internalReprOf(aTypeFactory, left);
AnnotationMirror newLBType =
aTypeFactory.getQualifierHierarchy().greatestLowerBound(rightAnno, leftAnno);
store.insertValue(leftRec, newLBType);
}
}