package org.checkerframework.checker.index.upperbound;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.checkerframework.checker.index.IndexUtil;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.analysis.FlowExpressions.Unknown;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode;
import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.framework.util.PluginUtil;
import org.checkerframework.framework.util.dependenttypes.DependentTypesError;
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 int value. The Java expression strings have been standardized and
* viewpoint adapted.
*/
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 String error = null;
private int intValue = 0;
private OffsetEquation() {
addedTerms = new ArrayList<>();
subtractedTerms = new ArrayList<>();
}
private 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(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.containsAll(that.addedTerms) || !that.addedTerms.containsAll(addedTerms)) {
return false;
}
if (!subtractedTerms.containsAll(that.subtractedTerms)
|| !that.subtractedTerms.containsAll(subtractedTerms)) {
return false;
}
return error != null ? error.equals(that.error) : that.error == null;
}
@Override
public int hashCode() {
int result = addedTerms.hashCode();
result = 31 * result + subtractedTerms.hashCode();
result = 31 * result + (error != null ? error.hashCode() : 0);
result = 31 * result + intValue;
return result;
}
@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 = PluginUtil.join(" + ", sortedAdds);
String minus = PluginUtil.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 arrays. If any terms were removed, then the copy is returned. Otherwise, null is
* returned.
*
* @param arrays List of arrays
* @return a copy of this equation with array.length removed or null if no array.lengths could
* be removed
*/
public OffsetEquation removeArrayLengths(List<String> arrays) {
OffsetEquation copy = new OffsetEquation(this);
boolean simplified = false;
for (String array : arrays) {
String arrayLen = array + ".length";
if (addedTerms.contains(arrayLen)) {
copy.addedTerms.remove(arrayLen);
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;
}
/**
* Standardizes and viewpoint adapts the string terms based us the supplied context.
*
* @param context FlowExpressionContext
* @param scope local scope
* @param useLocalScope whether or not local scope is used
* @throws FlowExpressionParseException if any term isn't able to be parsed this exception is
* thrown. If this happens, no string terms are changed.
*/
public void standardizeAndViewpointAdaptExpressions(
FlowExpressionContext context, TreePath scope, boolean useLocalScope)
throws FlowExpressionParseException {
List<String> newAddterms = new ArrayList<>();
for (String term : addedTerms) {
String standardizedTerm =
FlowExpressionParseUtil.parse(term, context, scope, useLocalScope).toString();
newAddterms.add(standardizedTerm);
}
List<String> newSubTerms = new ArrayList<>();
for (String term : subtractedTerms) {
String standardizedTerm =
FlowExpressionParseUtil.parse(term, context, scope, useLocalScope).toString();
newSubTerms.add(standardizedTerm);
}
addedTerms.clear();
addedTerms.addAll(newAddterms);
subtractedTerms.clear();
subtractedTerms.addAll(newSubTerms);
}
/**
* 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 (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 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 Java expressions add or subtracted from one another
* @return an offset equation created form 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;
}
private static boolean isInt(String string) {
return string.isEmpty() || string.matches("[-+]?[0-9]+");
}
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 Node from which to create offset equation
* @param factory 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, UpperBoundAnnotatedTypeFactory factory, char op) {
assert op == '+' || op == '-';
if (node.getTree() != null && TreeUtils.isExpressionTree(node.getTree())) {
Long i;
if (op == '+') {
i = IndexUtil.getMinValue(node.getTree(), factory.getValueAnnotatedTypeFactory());
} else {
i = IndexUtil.getMaxValue(node.getTree(), factory.getValueAnnotatedTypeFactory());
i = i == null ? null : -i;
}
if (i != null) {
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
* FlowExpressions.Receiver} and then added as a term to the returned equation. If op is '-'
* then it is a subtracted term.
*
* @param node Node from which to create offset equation
* @param factory AnnotationTypeFactory
* @param op '+' or '-'
* @return an offset equation from the Node
*/
public static OffsetEquation createOffsetFromNode(
Node node, UpperBoundAnnotatedTypeFactory factory, char op) {
assert op == '+' || op == '-';
OffsetEquation eq = new OffsetEquation();
createOffsetFromNode(node, factory, eq, op);
return eq;
}
private static void createOffsetFromNode(
Node node, UpperBoundAnnotatedTypeFactory factory, OffsetEquation eq, char op) {
Receiver r = FlowExpressions.internalReprOf(factory, node);
if (r instanceof Unknown || r == 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, r.toString());
}
}
}