
it.unive.lisa.analysis.string.fsa.FSA 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.fsa;
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.numeric.Interval;
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.ternary.StringReplace;
import it.unive.lisa.symbolic.value.operator.ternary.TernaryOperator;
import it.unive.lisa.util.collections.workset.FIFOWorkingSet;
import it.unive.lisa.util.collections.workset.WorkingSet;
import it.unive.lisa.util.datastructures.automaton.CyclicAutomatonException;
import it.unive.lisa.util.datastructures.automaton.State;
import it.unive.lisa.util.datastructures.automaton.Transition;
import it.unive.lisa.util.numeric.IntInterval;
import it.unive.lisa.util.numeric.MathNumber;
import it.unive.lisa.util.numeric.MathNumberConversionException;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A class that represent the Finite State Automaton domain for strings,
* exploiting a {@link SimpleAutomaton}.
*
* @author Simone Leoni
* @author Vincenzo Arceri
*/
public class FSA implements BaseNonRelationalValueDomain, ContainsCharProvider {
/**
* Top element of the domain
*/
private static final FSA TOP = new FSA(new SimpleAutomaton("").unknownString());
/**
* The parameter used for the widening operator.
*/
public static final int WIDENING_TH = 5;
/**
* Used to store the string representation
*/
private final SimpleAutomaton a;
/**
* Creates a new FSA object representing the TOP element.
*/
public FSA() {
// we use the empty language as it is memory-efficient
this.a = new SimpleAutomaton("").emptyLanguage();
}
/**
* Creates a new FSA object using a {@link SimpleAutomaton}.
*
* @param a the {@link SimpleAutomaton} used for object construction.
*/
public FSA(
SimpleAutomaton a) {
this.a = a;
}
@Override
public FSA lubAux(
FSA other)
throws SemanticException {
return new FSA(this.a.union(other.a).minimize());
}
@Override
public FSA glbAux(
FSA other)
throws SemanticException {
return new FSA(this.a.intersection(other.a).minimize());
}
@Override
public FSA wideningAux(
FSA other)
throws SemanticException {
return new FSA(this.a.union(other.a).widening(getSizeDiffCapped(other)));
}
/**
* Yields the size of this string, that is, the number of states of the
* underlying automaton.
*
* @return the size of this string
*/
public int size() {
return a.getStates().size();
}
private int getSizeDiffCapped(
FSA other) {
int size = size();
int otherSize = other.size();
if (size > otherSize)
return Math.min(size - otherSize, WIDENING_TH);
else if (size < otherSize)
return Math.min(otherSize - size, WIDENING_TH);
else
return WIDENING_TH;
}
@Override
public boolean lessOrEqualAux(
FSA other)
throws SemanticException {
return this.a.isContained(other.a);
}
@Override
public boolean equals(
Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FSA fsa = (FSA) o;
return Objects.equals(a, fsa.a);
}
@Override
public int hashCode() {
return Objects.hash(a);
}
@Override
public FSA top() {
return TOP;
}
@Override
public boolean isBottom() {
return !isTop() && this.a.acceptsEmptyLanguage();
}
@Override
public FSA bottom() {
SortedSet states = new TreeSet<>();
states.add(new State(0, true, false));
return new FSA(new SimpleAutomaton(states, new TreeSet<>()));
}
@Override
public StructuredRepresentation representation() {
if (isBottom())
return Lattice.bottomRepresentation();
else if (isTop())
return Lattice.topRepresentation();
return new StringRepresentation(this.a.toRegex().simplify());
}
@Override
public FSA evalNonNullConstant(
Constant constant,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (constant.getValue() instanceof String) {
return new FSA(new SimpleAutomaton((String) constant.getValue()));
}
return top();
}
// TODO unary and ternary and all other binary
@Override
public FSA evalBinaryExpression(
BinaryOperator operator,
FSA left,
FSA right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (operator == StringConcat.INSTANCE)
return new FSA(left.a.concat(right.a));
return top();
}
@Override
public FSA evalTernaryExpression(
TernaryOperator operator,
FSA left,
FSA middle,
FSA right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (operator == StringReplace.INSTANCE)
try {
return new FSA(left.a.replace(middle.a, right.a));
} catch (CyclicAutomatonException e) {
return TOP;
}
return TOP;
}
@Override
public Satisfiability satisfiesBinaryExpression(
BinaryOperator operator,
FSA left,
FSA right,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (operator == StringContains.INSTANCE) {
return left.contains(right);
}
return Satisfiability.UNKNOWN;
}
/**
* Yields the FSA automaton corresponding to the substring of this FSA
* automaton abstract value between two indexes.
*
* @param begin where the substring starts
* @param end where the substring ends
*
* @return the FSA automaton corresponding to the substring of this FSA
* automaton between two indexes
*
* @throws CyclicAutomatonException when the automaton is cyclic and its
* language is accessed
*/
public FSA substring(
long begin,
long end)
throws CyclicAutomatonException {
if (isTop() || isBottom())
return this;
if (!a.hasCycle()) {
SimpleAutomaton result = this.a.emptyLanguage();
for (String s : a.getLanguage()) {
if (begin < s.length() && end < s.length())
result = result.union(new SimpleAutomaton(s.substring((int) begin, (int) end)));
else
result = result.union(new SimpleAutomaton(""));
return new FSA(result);
}
}
SimpleAutomaton[] array = this.a.toRegex().substring((int) begin, (int) end)
.parallelStream()
.map(s -> new SimpleAutomaton(s.toString())).toArray(SimpleAutomaton[]::new);
SimpleAutomaton result = this.a.emptyLanguage();
for (int i = 0; i < array.length; i++)
result = result.union(array[i]);
return new FSA(result);
}
/**
* 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(a.toRegex().minLength(), a.lenghtOfLongestString());
}
/**
* 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}
*
* @throws CyclicAutomatonException when the automaton is cyclic and its
* language is accessed
*/
public IntInterval indexOf(
FSA s)
throws CyclicAutomatonException {
if (a.hasCycle())
return mkInterval(-1, null);
if (!a.hasCycle() && !s.a.hasCycle()) {
Set first = a.getLanguage();
Set second = s.a.getLanguage();
IntInterval result = null;
for (String f1 : first) {
for (String f2 : second) {
IntInterval partial;
if (f1.contains(f2)) {
int i = f1.indexOf(f2);
partial = mkInterval(i, i);
} else {
partial = mkInterval(-1, -1);
}
result = result == null ? partial : mkInterval(partial, result);
}
}
return result;
}
HashSet indexesOf = new HashSet<>();
for (State q : a.getStates()) {
SimpleAutomaton build = a.factorsChangingInitialState(q);
if (!build.intersection(s.a).acceptsEmptyLanguage())
indexesOf.add(a.maximumPath(q, q).size() - 1);
}
// No state in the automaton can read search
if (indexesOf.isEmpty())
return mkInterval(-1, -1);
else if (s.a.recognizesExactlyOneString() && a.recognizesExactlyOneString())
return mkInterval(indexesOf.stream().mapToInt(i -> i).min().getAsInt(),
indexesOf.stream().mapToInt(i -> i).max().getAsInt());
else
return mkInterval(-1, indexesOf.stream().mapToInt(i -> i).max().getAsInt());
}
/**
* Yields the concatenation between two automata.
*
* @param other the other automaton
*
* @return the concatenation between two automata
*/
public FSA concat(
FSA other) {
return new FSA(this.a.concat(other.a));
}
private IntInterval mkInterval(
Integer min,
Integer max) {
return new IntInterval(min, max);
}
private IntInterval mkInterval(
IntInterval first,
IntInterval second) {
MathNumber newLow = first.getLow().min(second.getLow());
MathNumber newHigh = first.getHigh().max(second.getHigh());
return new IntInterval(newLow, newHigh);
}
/**
* Yields if this automaton recognizes strings recognized by the other
* automaton.
*
* @param other the other automaton
*
* @return if this automaton recognizes strings recognized by the other
* automaton
*/
public Satisfiability contains(
FSA other) {
if (other.a.isEqualTo(a.emptyString()))
return Satisfiability.SATISFIED;
else if (this.a.factors().intersection(other.a).acceptsEmptyLanguage())
return Satisfiability.NOT_SATISFIED;
else
try {
Set rightLang = other.a.getLanguage();
Set leftLang = a.getLanguage();
// right accepts only the empty string
if (rightLang.size() == 1 && rightLang.contains(""))
return Satisfiability.SATISFIED;
// we can compare languages
boolean atLeastOne = false, all = true;
for (String a : leftLang)
for (String b : rightLang) {
boolean cont = a.contains(b);
atLeastOne = atLeastOne || cont;
all = all && cont;
}
if (all)
return Satisfiability.SATISFIED;
if (atLeastOne)
return Satisfiability.UNKNOWN;
return Satisfiability.NOT_SATISFIED;
} catch (CyclicAutomatonException e) {
return Satisfiability.UNKNOWN;
}
}
/**
* Yields the replacement of occurrences of {@code search} inside
* {@code this} with {@code repl}.
*
* @param search the domain instance containing the automaton to search
* @param repl the domain instance containing the automaton to use as
* replacement
*
* @return the domain instance containing the replaced automaton
*/
public FSA replace(
FSA search,
FSA repl) {
try {
return new FSA(this.a.replace(search.a, repl.a));
} catch (CyclicAutomatonException e) {
return TOP;
}
}
@Override
public Satisfiability containsChar(
char c)
throws SemanticException {
if (isTop())
return Satisfiability.UNKNOWN;
if (isBottom())
return Satisfiability.BOTTOM;
if (!a.hasCycle()) {
Satisfiability sat = Satisfiability.BOTTOM;
try {
for (String s : a.getLanguage())
if (s.contains(String.valueOf(c)))
sat = sat.lub(Satisfiability.SATISFIED);
else
sat = sat.lub(Satisfiability.NOT_SATISFIED);
} catch (CyclicAutomatonException e) {
// can ignore thanks to the guard
}
return sat;
}
WorkingSet ws = FIFOWorkingSet.mk();
Set visited = new TreeSet<>();
for (State q : a.getInitialStates())
ws.push(q);
while (!ws.isEmpty()) {
State top = ws.pop();
for (Transition tr : a.getOutgoingTransitionsFrom(top)) {
if (tr.getSymbol().getSymbol().equals(String.valueOf(c)))
return Satisfiability.SATISFIED;
}
visited.add(top);
for (Transition tr : a.getOutgoingTransitionsFrom(top))
if (visited.contains(tr.getDestination()))
return Satisfiability.UNKNOWN;
else
ws.push(tr.getDestination());
}
return Satisfiability.NOT_SATISFIED;
}
/**
* Yields a new FSA where trailing and leading whitespaces have been removed
* from {@code this}.
*
* @return a new FSA where trailing and leading whitespaces have been
* removed from {@code this}
*/
public FSA trim() {
if (isBottom() || isTop())
return this;
return new FSA(this.a.trim());
}
/**
* Yields a new FSA instance recognizing each string of {@code this}
* automaton repeated k-times, with k belonging to {@code intv}.
*
* @param i the interval
*
* @return a new FSA instance recognizing each string of {@code this}
* automaton repeated k-times, with k belonging to {@code intv}
*
* @throws MathNumberConversionException if {@code intv} is iterated but is
* not finite
*/
public FSA repeat(
Interval i)
throws MathNumberConversionException {
if (isBottom() || isTop())
return this;
return new FSA(this.a.repeat(i));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy