blob: b2a49dfd9a09ed26740a0d4e6f8b17ea75b60609 [file] [log] [blame]
package org.checkerframework.checker.index.upperbound;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueCheckerUtils;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.Unknown;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.TreeUtils;
/**
* An offset equation is 2 sets of Java expression strings, one set of added terms and one set of
* subtracted terms, and a single integer constant. The Java expression strings have been
* standardized and viewpoint-adapted.
*
* <p>An OffsetEquation is mutable.
*/
public class OffsetEquation {
public static final OffsetEquation ZERO = createOffsetForInt(0);
public static final OffsetEquation NEG_1 = createOffsetForInt(-1);
public static final OffsetEquation ONE = createOffsetForInt(1);
private final List<String> addedTerms;
private final List<String> subtractedTerms;
private int intValue = 0;
private String error = null;
private OffsetEquation() {
addedTerms = new ArrayList<>(1);
subtractedTerms = new ArrayList<>(1);
}
/**
* Create a new OffsetEquation that is a copy of the given one.
*
* @param other the OffsetEquation to copy
*/
protected OffsetEquation(OffsetEquation other) {
this.addedTerms = new ArrayList<>(other.addedTerms);
this.subtractedTerms = new ArrayList<>(other.subtractedTerms);
this.error = other.error;
this.intValue = other.intValue;
}
public boolean hasError() {
return error != null;
}
public String getError() {
return error;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OffsetEquation that = (OffsetEquation) o;
if (intValue != that.intValue) {
return false;
}
if (addedTerms.size() != that.addedTerms.size()
|| !addedTerms.containsAll(that.addedTerms)
|| !that.addedTerms.containsAll(addedTerms)) {
return false;
}
if (subtractedTerms.size() != that.subtractedTerms.size()
|| !subtractedTerms.containsAll(that.subtractedTerms)
|| !that.subtractedTerms.containsAll(subtractedTerms)) {
return false;
}
return error != null ? error.equals(that.error) : that.error == null;
}
@Override
public int hashCode() {
return Objects.hash(addedTerms, subtractedTerms, intValue, error);
}
@Override
public String toString() {
if (addedTerms.isEmpty() && subtractedTerms.isEmpty()) {
return String.valueOf(intValue);
}
List<String> sortedAdds = new ArrayList<>(addedTerms);
Collections.sort(sortedAdds);
List<String> sortedSubs = new ArrayList<>(subtractedTerms);
Collections.sort(sortedSubs);
String adds = String.join(" + ", sortedAdds);
String minus = String.join(" - ", sortedSubs);
if (sortedSubs.size() == 1 && sortedAdds.isEmpty()) {
minus = "-" + minus;
} else if (!sortedSubs.isEmpty()) {
minus = " - " + minus;
}
String terms = (adds + minus).trim();
if (intValue != 0) {
char op = intValue > 0 ? '+' : '-';
if (terms.isEmpty()) {
terms += intValue;
} else {
terms += " " + op + " " + Math.abs(intValue);
}
}
return terms;
}
/**
* Makes a copy of this offset and removes any added terms that are accesses to the length of the
* listed sequences. If any terms were removed, then the copy is returned. Otherwise, null is
* returned.
*
* @param sequences list of sequences (arrays or strings)
* @return a copy of this equation with array.length and string.length() removed or null if no
* array.lengths or string.length() could be removed
*/
public OffsetEquation removeSequenceLengths(List<String> sequences) {
OffsetEquation copy = new OffsetEquation(this);
boolean simplified = false;
for (String sequence : sequences) {
String arrayLen = sequence + ".length";
if (addedTerms.contains(arrayLen)) {
copy.addedTerms.remove(arrayLen);
simplified = true;
}
String stringLen = sequence + ".length()";
if (addedTerms.contains(stringLen)) {
copy.addedTerms.remove(stringLen);
simplified = true;
}
}
return simplified ? copy : null;
}
/**
* Adds or subtracts the other equation to a copy of this one.
*
* <p>If subtraction is specified, then every term in other is subtracted.
*
* @param op '-' for subtraction or '+' for addition
* @param other equation to add or subtract
* @return a copy of this equation +/- other
*/
public OffsetEquation copyAdd(char op, OffsetEquation other) {
assert op == '-' || op == '+';
OffsetEquation copy = new OffsetEquation(this);
if (op == '+') {
copy.plus(other);
} else {
copy.minus(other);
}
return copy;
}
private void plus(OffsetEquation eq) {
addInt(eq.intValue);
for (String term : eq.addedTerms) {
addTerm('+', term);
}
for (String term : eq.subtractedTerms) {
addTerm('-', term);
}
}
private void minus(OffsetEquation eq) {
addInt(-1 * eq.intValue);
for (String term : eq.addedTerms) {
addTerm('-', term);
}
for (String term : eq.subtractedTerms) {
addTerm('+', term);
}
}
/**
* Returns whether or not this equation is known to be less than or equal to the other equation.
*
* @param other equation
* @return whether or not this equation is known to be less than or equal to the other equation
*/
public boolean lessThanOrEqual(OffsetEquation other) {
return (isInt() && other.isInt() && intValue <= other.getInt()) || this.equals(other);
}
/**
* Returns true if this equation is a single int value.
*
* @return true if this equation is a single int value
*/
public boolean isInt() {
return addedTerms.isEmpty() && subtractedTerms.isEmpty();
}
/**
* Returns the int value associated with this equation.
*
* <p>The equation may or may not have other terms. Use {@link #isInt()} to determine if the
* equation is only this int value.
*
* @return the int value associated with this equation
*/
public int getInt() {
return intValue;
}
/**
* Returns true if this equation is exactly -1.
*
* @return true if this equation is exactly -1
*/
public boolean isNegOne() {
return isInt() && getInt() == -1;
}
/**
* Returns true if this equation non-negative.
*
* @return true if this equation non-negative
*/
public boolean isNonNegative() {
return isInt() && getInt() >= 0;
}
/**
* Returns true if this equation is negative or zero.
*
* @return true if this equation is negative or zero
*/
public boolean isNegativeOrZero() {
return isInt() && getInt() <= 0;
}
/**
* Adds the term to this equation. If string is an integer, then it is added or subtracted,
* depending on operator, from the int value of this equation. Otherwise, the term is placed in
* the added or subtracted terms set, depending on operator.
*
* @param operator '+' or '-'
* @param term an int value or Java expression to add to this equation
*/
private void addTerm(char operator, String term) {
term = term.trim();
if (operator == '-' && term.equals("2147483648")) {
addInt(-2147483648);
return;
}
if (isInt(term)) {
int literal = parseInt(term);
addInt(operator == '-' ? -1 * literal : literal);
return;
}
if (operator == '-') {
if (addedTerms.contains(term)) {
addedTerms.remove(term);
} else {
subtractedTerms.add(term);
}
} else if (operator == '+') {
if (subtractedTerms.contains(term)) {
subtractedTerms.remove(term);
} else {
addedTerms.add(term);
}
} else {
assert false;
}
}
private void addInt(int value) {
intValue += value;
}
/**
* Returns the offset equation that is an int value or null if there isn't one.
*
* @param equationSet a set of offset equations
* @return the offset equation that is an int value or null if there isn't one
*/
public static OffsetEquation getIntOffsetEquation(Set<OffsetEquation> equationSet) {
for (OffsetEquation eq : equationSet) {
if (eq.isInt()) {
return eq;
}
}
return null;
}
/**
* Creates an offset equation that is only the int value specified.
*
* @param value int value of the equation
* @return an offset equation that is only the int value specified
*/
public static OffsetEquation createOffsetForInt(int value) {
OffsetEquation equation = new OffsetEquation();
equation.intValue = value;
return equation;
}
/**
* Creates an offset equation from the expressionEquation. The expressionEquation may be several
* Java expressions added or subtracted from each other. The expressionEquation may also start
* with + or -. If the expressionEquation is the empty string, then the offset equation returned
* is zero.
*
* @param expressionEquation a Java expression made up of sums and differences
* @return an offset equation created from expressionEquation
*/
public static OffsetEquation createOffsetFromJavaExpression(String expressionEquation) {
expressionEquation = expressionEquation.trim();
OffsetEquation equation = new OffsetEquation();
if (expressionEquation.isEmpty()) {
equation.addTerm('+', "0");
return equation;
}
if (DependentTypesError.isExpressionError(expressionEquation)) {
equation.error = expressionEquation;
return equation;
}
if (indexOf(expressionEquation, '-', '+', 0) == -1) {
equation.addTerm('+', expressionEquation);
return equation;
}
int index = 0;
while (index < expressionEquation.length()) {
char operator = expressionEquation.charAt(index);
if (operator == '-' || operator == '+') {
index++;
} else {
operator = '+';
}
int endIndex = indexOf(expressionEquation, '-', '+', index);
String subexpression;
if (endIndex == -1) {
endIndex = expressionEquation.length();
subexpression = expressionEquation.substring(index);
} else {
subexpression = expressionEquation.substring(index, endIndex);
}
equation.addTerm(operator, subexpression);
index = endIndex;
}
return equation;
}
/** A regular expression that matches an integer literal. */
private static Pattern intPattern = Pattern.compile("[-+]?[0-9]+");
/**
* Returns true if the given string is an integer literal
*
* @param string a string
* @return true if the given string is an integer literal
*/
private static boolean isInt(String string) {
return intPattern.matcher(string).matches();
}
private static int parseInt(String intLiteral) {
if (intLiteral.isEmpty()) {
return 0;
}
return Integer.valueOf(intLiteral);
}
/** Returns the first index of a or b in string, or -1 if neither char is in string. */
private static int indexOf(String string, char a, char b, int index) {
int aIndex = string.indexOf(a, index);
int bIndex = string.indexOf(b, index);
if (aIndex == -1) {
return bIndex;
} else if (bIndex == -1) {
return aIndex;
} else {
return Math.min(aIndex, bIndex);
}
}
/**
* If node is an int value known at compile time, then the returned equation is just the int value
* or if op is '-', the return equation is the negation of the int value.
*
* <p>Otherwise, null is returned.
*
* @param node the Node from which to create an offset equation
* @param factory an AnnotationTypeFactory
* @param op '+' or '-'
* @return an offset equation from value of known or null if the value isn't known
*/
public static OffsetEquation createOffsetFromNodesValue(
Node node, ValueAnnotatedTypeFactory factory, char op) {
assert op == '+' || op == '-';
if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) {
Long i = ValueCheckerUtils.getExactValue(node.getTree(), factory);
if (i != null) {
if (op == '-') {
i = -i;
}
OffsetEquation eq = new OffsetEquation();
eq.addInt(i.intValue());
return eq;
}
}
return null;
}
/**
* Creates an offset equation from the Node.
*
* <p>If node is an addition or subtracted node, then this method is called recursively on the
* left and right hand nodes and the two equations are added/subtracted to each other depending on
* the value of op.
*
* <p>Otherwise the return equation is created by converting the node to a {@link
* org.checkerframework.dataflow.expression.JavaExpression} and then added as a term to the
* returned equation. If op is '-' then it is a subtracted term.
*
* @param node the Node from which to create an offset equation
* @param factory an AnnotationTypeFactory
* @param op '+' or '-'
* @return an offset equation from the Node
*/
public static OffsetEquation createOffsetFromNode(
Node node, AnnotationProvider factory, char op) {
assert op == '+' || op == '-';
OffsetEquation eq = new OffsetEquation();
createOffsetFromNode(node, factory, eq, op);
return eq;
}
/**
* Updates an offset equation from a Node.
*
* @param node the Node from which to create an offset equation
* @param factory an AnnotationTypeFactory
* @param eq an OffsetEquation to update
* @param op '+' or '-'
*/
private static void createOffsetFromNode(
Node node, AnnotationProvider factory, OffsetEquation eq, char op) {
JavaExpression je = JavaExpression.fromNode(node);
if (je instanceof Unknown || je == null) {
if (node instanceof NumericalAdditionNode) {
createOffsetFromNode(((NumericalAdditionNode) node).getLeftOperand(), factory, eq, op);
createOffsetFromNode(((NumericalAdditionNode) node).getRightOperand(), factory, eq, op);
} else if (node instanceof NumericalSubtractionNode) {
createOffsetFromNode(((NumericalSubtractionNode) node).getLeftOperand(), factory, eq, op);
char other = op == '+' ? '-' : '+';
createOffsetFromNode(
((NumericalSubtractionNode) node).getRightOperand(), factory, eq, other);
} else {
eq.error = node.toString();
}
} else {
eq.addTerm(op, je.toString());
}
}
}