it.unive.lisa.analysis.string.bricks.Bricks Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lisa-analyses Show documentation
Show all versions of lisa-analyses Show documentation
A library for static analysis
The newest version!
package it.unive.lisa.analysis.string.bricks;
import it.unive.lisa.analysis.Lattice;
import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.analysis.SemanticOracle;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.analysis.nonrelational.value.BaseNonRelationalValueDomain;
import it.unive.lisa.analysis.string.ContainsCharProvider;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.symbolic.value.Constant;
import it.unive.lisa.symbolic.value.operator.binary.BinaryOperator;
import it.unive.lisa.symbolic.value.operator.binary.StringConcat;
import it.unive.lisa.symbolic.value.operator.binary.StringContains;
import it.unive.lisa.symbolic.value.operator.binary.StringEndsWith;
import it.unive.lisa.symbolic.value.operator.binary.StringEquals;
import it.unive.lisa.symbolic.value.operator.binary.StringIndexOf;
import it.unive.lisa.symbolic.value.operator.binary.StringStartsWith;
import it.unive.lisa.util.numeric.IntInterval;
import it.unive.lisa.util.numeric.MathNumber;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.StringUtils;
/**
* The bricks string abstract domain.
*
* @author Vincenzo Arceri
* @author Sergio
* Salvatore Evola
*
* @see
* https://link.springer.com/chapter/10.1007/978-3-642-24559-6_34
*/
public class Bricks implements BaseNonRelationalValueDomain, ContainsCharProvider {
private final List bricks;
private final static Bricks TOP = new Bricks();
private final static Bricks BOTTOM = new Bricks(new ArrayList<>());
/**
* The length of the bricks list used in the widening.
*/
public static int kL = 20;
/**
* The indices range of a brick used in the widening.
*/
public static int kI = 20;
/**
* The number of strings in the set of a brick used in the widening.
*/
public static int kS = 50;
/**
* Builds the top brick abstract element.
*/
public Bricks() {
this.bricks = new ArrayList<>(1);
bricks.add(new Brick());
}
/**
* Builds a bricks abstract element.
*
* @param bricks the list of brick
*/
public Bricks(
List bricks) {
this.bricks = bricks;
}
@Override
public Bricks lubAux(
Bricks other)
throws SemanticException {
List thisPaddedList = this.bricks;
List otherPaddedList = other.bricks;
if (this.bricks.size() < other.bricks.size())
thisPaddedList = this.padList(other);
else if (other.bricks.size() < this.bricks.size())
otherPaddedList = other.padList(this);
List resultBricks = new ArrayList<>(thisPaddedList.size());
for (int i = 0; i < thisPaddedList.size(); ++i)
resultBricks.add(thisPaddedList.get(i).lub(otherPaddedList.get(i)));
Bricks result = new Bricks(resultBricks);
result.normBricks();
return result;
}
@Override
public boolean lessOrEqualAux(
Bricks other)
throws SemanticException {
List thisPaddedList = this.bricks;
List otherPaddedList = other.bricks;
if (this.bricks.size() < other.bricks.size())
thisPaddedList = this.padList(other);
else if (other.bricks.size() < this.bricks.size())
otherPaddedList = other.padList(this);
for (int i = 0; i < thisPaddedList.size(); ++i) {
Brick first = thisPaddedList.get(i);
Brick second = otherPaddedList.get(i);
if (!first.lessOrEqual(second))
return false;
}
return true;
}
@Override
public Bricks wideningAux(
Bricks other)
throws SemanticException {
boolean rel = this.lessOrEqual(other);
if (!rel && !other.lessOrEqual(this))
return TOP;
if (this.bricks.size() > kL || other.bricks.size() > kL)
return TOP;
if (rel)
return w(other);
else
return other.w(this);
}
private Bricks w(
Bricks other)
throws SemanticException {
List thisPaddedList = this.bricks;
List otherPaddedList = other.bricks;
if (this.bricks.size() < other.bricks.size())
thisPaddedList = this.padList(other);
else if (other.bricks.size() < this.bricks.size())
otherPaddedList = other.padList(this);
List resultList = new ArrayList<>();
for (int i = 0; i < thisPaddedList.size(); ++i) {
Brick thisCurrent = thisPaddedList.get(i);
Brick otherCurrent = otherPaddedList.get(i);
resultList.add(thisCurrent.widening(otherCurrent));
}
Bricks result = new Bricks(resultList);
result.normBricks();
return result;
}
@Override
public Bricks evalBinaryExpression(
BinaryOperator operator,
Bricks left,
Bricks right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (operator == StringConcat.INSTANCE) {
List resultList = new ArrayList<>(left.bricks);
resultList.addAll(right.bricks);
return new Bricks(resultList);
} else if (operator == StringContains.INSTANCE ||
operator == StringEndsWith.INSTANCE ||
operator == StringEquals.INSTANCE ||
operator == StringIndexOf.INSTANCE ||
operator == StringStartsWith.INSTANCE) {
return TOP;
}
return TOP;
}
@Override
public Bricks evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (constant.getValue() instanceof String) {
String str = (String) constant.getValue();
Set strings = new TreeSet<>();
strings.add(str);
List resultList = new ArrayList<>();
resultList.add(new Brick(1, 1, strings));
return new Bricks(resultList);
}
return TOP;
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
Bricks left,
Bricks right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (left.isTop() || right.isBottom())
return Satisfiability.UNKNOWN;
if (operator == StringContains.INSTANCE)
return left.contains(right);
return Satisfiability.UNKNOWN;
}
private Satisfiability contains(
Bricks right) {
if (right.bricks.size() != 1)
return Satisfiability.UNKNOWN;
if (!right.bricks.get(0).isFinite())
return Satisfiability.UNKNOWN;
Set strings = right.bricks.get(0).getStrings();
if (strings.size() != 1)
return Satisfiability.UNKNOWN;
if (strings.iterator().next().length() != 1)
return Satisfiability.UNKNOWN;
String c = strings.iterator().next();
boolean res = bricks.stream()
.filter(b -> b.getMin().gt(MathNumber.ZERO))
.map(b -> b.getStrings())
.anyMatch(set -> set == null || set.stream().allMatch(s -> s.contains(c)));
if (res)
return Satisfiability.SATISFIED;
res = bricks.stream()
.map(b -> b.getStrings())
.allMatch(set -> set != null && set.stream().allMatch(s -> !s.contains(c)));
if (res)
return Satisfiability.NOT_SATISFIED;
return Satisfiability.UNKNOWN;
}
@Override
public boolean equals(
Object object) {
if (this == object)
return true;
if (object == null || getClass() != object.getClass())
return false;
Bricks bricks1 = (Bricks) object;
return java.util.Objects.equals(bricks, bricks1.bricks);
}
@Override
public int hashCode() {
return Objects.hash(bricks);
}
@Override
public Bricks top() {
return TOP;
}
@Override
public Bricks bottom() {
return BOTTOM;
}
@Override
public String toString() {
return representation().toString();
}
@Override
public StructuredRepresentation representation() {
if (isBottom())
return Lattice.bottomRepresentation();
if (isTop())
return Lattice.topRepresentation();
return new StringRepresentation(StringUtils.join(this.bricks, " "));
}
private void rule2(
int first,
int second) {
Brick firstBrick = this.bricks.get(first);
Brick secondBrick = this.bricks.get(second);
Set resultSet;
if (firstBrick.getStrings() == null || secondBrick.getStrings() == null)
resultSet = null;
else {
resultSet = new TreeSet<>();
firstBrick.getStrings()
.forEach(string -> secondBrick.getStrings().forEach(otherStr -> resultSet.add(string + otherStr)));
}
this.bricks.set(first, new Brick(1, 1, resultSet));
this.bricks.remove(second);
}
private void rule3(
int index) {
Brick brick = this.bricks.get(index);
this.bricks.set(index, new Brick(1, 1, brick.getReps()));
}
private void rule4(
int first,
int second) {
Brick firstBrick = this.bricks.get(first);
Brick secondBrick = this.bricks.get(second);
this.bricks.set(first, new Brick(firstBrick.getMin().add(secondBrick.getMin()),
firstBrick.getMax().add(secondBrick.getMax()),
firstBrick.getStrings()));
this.bricks.remove(second);
}
private void rule5(
int index) {
Brick brick = this.bricks.get(index);
Brick br = new Brick(brick.getMin(), brick.getMin(), brick.getStrings());
this.bricks.set(index, new Brick(1, 1, br.getReps()));
this.bricks.add(index + 1,
new Brick(MathNumber.ZERO, brick.getMax().subtract(brick.getMin()), brick.getStrings()));
}
/**
* The normalization method of the bricks domain. Modify bricks to its
* normalized form.
*/
public void normBricks() {
if (isTop())
return;
List thisBricks = this.bricks;
List tempList = new ArrayList<>(thisBricks);
thisBricks.removeIf(brick -> brick.getMin().equals(MathNumber.ZERO)
&& brick.getMax().equals(MathNumber.ZERO)
&& brick.getStrings() != null
&& brick.getStrings().isEmpty());
for (int i = 0; i < thisBricks.size(); ++i) {
Brick currentBrick = thisBricks.get(i);
Brick nextBrick = null;
boolean lastBrick = i == thisBricks.size() - 1;
if (!lastBrick)
nextBrick = thisBricks.get(i + 1);
if (!lastBrick)
if (currentBrick.getMin().equals(MathNumber.ONE) && currentBrick.getMax().equals(MathNumber.ONE) &&
nextBrick.getMin().equals(MathNumber.ONE) && nextBrick.getMax().equals(MathNumber.ONE)) {
rule2(i, i + 1);
lastBrick = i == thisBricks.size() - 1;
}
if (currentBrick.getMin().equals(currentBrick.getMax())
&& !currentBrick.getMin().equals(MathNumber.ONE)
&& !currentBrick.getMax().equals(MathNumber.ONE)
&& currentBrick.getStrings() != null)
rule3(i);
if (!lastBrick)
if (currentBrick.getStrings() != null && currentBrick.getStrings().equals(nextBrick.getStrings()))
rule4(i, i + 1);
if (MathNumber.ONE.lt(currentBrick.getMin())
&& !currentBrick.getMin().equals(currentBrick.getMax())
&& currentBrick.getStrings() != null)
rule5(i);
}
if (!thisBricks.equals(tempList))
normBricks();
}
/**
* The substring method of the bricks domain.
*
* @param e The beginning index of the substring
* @param b The ending index of the substring
*
* @return A new Bricks with all possible substrings if the conditions are
* met or TOP.
*/
public Bricks substring(
long e,
long b) {
this.normBricks();
Brick first = this.bricks.get(0);
TreeSet result = new TreeSet<>();
if (first.getMin().equals(MathNumber.ONE)
&& first.getMax().equals(MathNumber.ONE)
&& first.getStrings() != null
&& !first.getStrings().isEmpty()) {
first.getStrings().forEach(s -> {
boolean allGreater = s.length() >= e;
if (allGreater)
result.add(s.substring((int) e, (int) b));
});
}
if (result.size() == first.getStrings().size()) {
List resultList = new ArrayList<>();
resultList.add(new Brick(new IntInterval(1, 1), result));
return new Bricks(resultList);
}
return TOP;
}
/**
* Pads the shortest brick list and adds empty brick elements to it, in
* order to make it the same size of the longer brick list, while
* maintaining the same position of equals elements between the two lists.
*
* @param other the other bricks object, which has to yield the longer list
*
* @return the shorter list with empty brick in it
*
* @throws IllegalArgumentException if the other brick list is longer or
* equal than the caller bricks object
*/
public List padList(
final Bricks other) {
if (this.bricks.size() >= other.bricks.size())
throw new IllegalArgumentException("Other bricks list is longer or equal");
List l1 = new ArrayList<>(this.bricks), l2 = new ArrayList<>(other.bricks);
Brick e = new Brick(0, 0, new TreeSet<>());
int n1 = l1.size();
int n2 = l2.size();
int n = n2 - n1;
List lnew = new ArrayList<>();
int emptyBricksAdded = 0;
for (int i = 0; i < n2; i++)
if (emptyBricksAdded >= n) {
lnew.add(l1.get(0));
l1.remove(0);
} else if (l1.isEmpty() || !l1.get(0).equals(l2.get(i))) {
lnew.add(e);
emptyBricksAdded++;
} else {
lnew.add(l1.get(0));
l1.remove(0);
}
return lnew;
}
/**
* Yields the {@link IntInterval} containing the minimum and maximum length
* of this abstract value.
*
* @return the minimum and maximum length of this abstract value
*/
public IntInterval length() {
return new IntInterval(MathNumber.ZERO, MathNumber.PLUS_INFINITY);
}
/**
* Yields the {@link IntInterval} containing the minimum and maximum index
* of {@code s} in {@code this}.
*
* @param s the string to be searched
*
* @return the minimum and maximum index of {@code s} in {@code this}
*/
public IntInterval indexOf(
Bricks s) {
return new IntInterval(MathNumber.MINUS_ONE, MathNumber.PLUS_INFINITY);
}
@Override
public Satisfiability containsChar(
char c)
throws SemanticException {
if (isTop())
return Satisfiability.UNKNOWN;
if (isBottom())
return Satisfiability.BOTTOM;
Satisfiability sat = Satisfiability.BOTTOM;
for (Brick b : this.bricks) {
// surely a string of the brick is contained
if (b.getMin().geq(MathNumber.ONE)) {
Satisfiability bricksat = Satisfiability.BOTTOM;
for (String s : b.getStrings())
if (!s.contains(String.valueOf(c)))
bricksat = bricksat.lub(Satisfiability.NOT_SATISFIED);
else
bricksat = bricksat.lub(Satisfiability.SATISFIED);
if (bricksat == Satisfiability.SATISFIED)
return bricksat;
else
sat = sat.lub(bricksat);
} else {
// the brick can be missing
for (String s : b.getStrings())
if (s.contains(String.valueOf(c)))
sat = sat.lub(Satisfiability.UNKNOWN);
else
sat = sat.lub(Satisfiability.NOT_SATISFIED);
}
}
return sat;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy