it.unive.lisa.analysis.traces.TracePartitioning 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.traces;
import it.unive.lisa.analysis.AbstractState;
import it.unive.lisa.analysis.Lattice;
import it.unive.lisa.analysis.ScopeToken;
import it.unive.lisa.analysis.SemanticDomain;
import it.unive.lisa.analysis.SemanticException;
import it.unive.lisa.analysis.SemanticOracle;
import it.unive.lisa.analysis.lattices.ExpressionSet;
import it.unive.lisa.analysis.lattices.FunctionalLattice;
import it.unive.lisa.analysis.lattices.Satisfiability;
import it.unive.lisa.program.cfg.ProgramPoint;
import it.unive.lisa.program.cfg.controlFlow.ControlFlowStructure;
import it.unive.lisa.program.cfg.controlFlow.IfThenElse;
import it.unive.lisa.program.cfg.controlFlow.Loop;
import it.unive.lisa.symbolic.SymbolicExpression;
import it.unive.lisa.symbolic.value.Identifier;
import it.unive.lisa.type.Type;
import it.unive.lisa.type.Untyped;
import it.unive.lisa.util.representation.MapRepresentation;
import it.unive.lisa.util.representation.StringRepresentation;
import it.unive.lisa.util.representation.StructuredRepresentation;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
/**
* The trace partitioning abstract domain that splits execution traces to
* increase precision of the analysis. Individual traces are identified by
* {@link ExecutionTrace}s composed of tokens representing the conditions
* traversed during the analysis. Note that all {@link TraceToken}s represent
* intraprocedural control-flow constructs, as calls are abstracted away before
* reaching this domain.
*
* Traces are never merged: instead, we limit the size of the traces we can
* track, and we leave the choice of when and where to compact traces to other
* analysis components. Specifically, an {@link ExecutionTrace} will contain at
* most {@link #MAX_CONDITIONS} {@link Branching} tokens, and will track at most
* {@link #MAX_LOOP_ITERATIONS} iterations for each loop (through
* {@link LoopIteration} tokens) before summarizing the next ones with a
* {@link LoopSummary} token. Both values are editable and customizable before
* the analysis starts.
*
* As this class extends {@link FunctionalLattice}, one access individual traces
* and their approximations using {@link #getKeys()}, {@link #getValues()},
* {@link #getMap()} or by iterating over the instance itself. Approximations of
* different traces can instead be collapsed and accessed by querying
* {@link #collapse()}.
*
* @author Luca Negrini
*
* @param the type of {@link AbstractState} that this is being partitioned
*
* @see https://doi.org/10.1145/1275497.1275501
*/
public class TracePartitioning>
extends
FunctionalLattice, ExecutionTrace, A>
implements
AbstractState> {
/**
* The maximum number of {@link LoopIteration} tokens that a trace can
* contain for each loop appearing in it, before collapsing the next ones in
* a single {@link LoopSummary} token.
*/
public static int MAX_LOOP_ITERATIONS = 5;
/**
* The maximum number of {@link Branching} tokens that a trace can contain.
*/
public static int MAX_CONDITIONS = 5;
/**
* Builds a new instance of this domain.
*
* @param lattice a singleton of the underlying abstract states
*/
public TracePartitioning(
A lattice) {
super(lattice);
}
private TracePartitioning(
A lattice,
Map function) {
super(lattice, function);
}
@Override
public > Collection getAllDomainInstances(
Class domain) {
Collection result = AbstractState.super.getAllDomainInstances(domain);
if (isTop())
return lattice.top().getAllDomainInstances(domain);
else if (isBottom() || function == null)
return lattice.bottom().getAllDomainInstances(domain);
for (A dom : getValues())
result.addAll(dom.getAllDomainInstances(domain));
return result;
}
@Override
public TracePartitioning top() {
return new TracePartitioning<>(lattice.top(), null);
}
@Override
public TracePartitioning bottom() {
return new TracePartitioning<>(lattice.bottom(), null);
}
@Override
public TracePartitioning assign(
Identifier id,
SymbolicExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isBottom())
return this;
Map result = mkNewFunction(null, false);
if (isTop() || function == null)
result.put(new ExecutionTrace(), lattice.assign(id, expression, pp, oracle));
else
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().assign(id, expression, pp, oracle));
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning smallStepSemantics(
SymbolicExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isBottom())
return this;
Map result = mkNewFunction(null, false);
if (isTop() || function == null)
result.put(new ExecutionTrace(), lattice.smallStepSemantics(expression, pp, oracle));
else
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().smallStepSemantics(expression, pp, oracle));
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning assume(
SymbolicExpression expression,
ProgramPoint src,
ProgramPoint dest,
SemanticOracle oracle)
throws SemanticException {
if (isBottom())
return this;
ControlFlowStructure struct = src.getCFG().getControlFlowStructureOf(src);
Map result = mkNewFunction(null, false);
if (isTop() || function == null) {
ExecutionTrace trace = new ExecutionTrace();
ExecutionTrace nextTrace = generateTraceFor(trace, struct, src, dest);
result.put(nextTrace, lattice.top());
} else
for (Entry trace : this) {
A state = trace.getValue();
ExecutionTrace tokens = trace.getKey();
A assume = state.assume(expression, src, dest, oracle);
if (assume.isBottom())
// we only keep traces that can escape the loop
continue;
ExecutionTrace nextTrace = generateTraceFor(tokens, struct, src, dest);
// when we hit one of the limits, more traces can get smashed
// into one
A prev = result.get(nextTrace);
result.put(nextTrace, prev == null ? assume : assume.lub(prev));
}
if (result.isEmpty())
// no traces pass the condition, so this branch is unreachable
return bottom();
return new TracePartitioning<>(lattice, result);
}
private static ExecutionTrace generateTraceFor(
ExecutionTrace trace,
ControlFlowStructure struct,
ProgramPoint src,
ProgramPoint dest) {
if (struct instanceof Loop && ((Loop) struct).getBody().contains(dest)) {
// on loop exits we do not generate new traces
TraceToken prev = trace.lastLoopTokenFor(src);
if (prev == null)
if (MAX_LOOP_ITERATIONS > 0)
return trace.push(new LoopIteration(src, 0));
else
return trace.push(new LoopSummary(src));
else if (prev instanceof LoopIteration) {
LoopIteration li = (LoopIteration) prev;
if (li.getIteration() < MAX_LOOP_ITERATIONS)
return trace.push(new LoopIteration(src, li.getIteration() + 1));
else
return trace.push(new LoopSummary(src));
}
// we do nothing on loop summaries as we already reached
// the maximum iterations for this loop
} else if (struct instanceof IfThenElse && trace.numberOfBranches() < MAX_CONDITIONS)
return trace.push(new Branching(src, ((IfThenElse) struct).getTrueBranch().contains(dest)));
// no known conditional structure, or no need to push new tokens
return trace;
}
@Override
public TracePartitioning forgetIdentifier(
Identifier id)
throws SemanticException {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().forgetIdentifier(id));
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning forgetIdentifiersIf(
Predicate test)
throws SemanticException {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().forgetIdentifiersIf(test));
return new TracePartitioning<>(lattice, result);
}
@Override
public Satisfiability satisfies(
SymbolicExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return Satisfiability.UNKNOWN;
if (isBottom() || function == null)
return Satisfiability.BOTTOM;
Satisfiability result = Satisfiability.BOTTOM;
for (Entry trace : this) {
Satisfiability sat = trace.getValue().satisfies(expression, pp, oracle);
if (sat == Satisfiability.BOTTOM)
return sat;
result = result.lub(sat);
}
return result;
}
@Override
public TracePartitioning pushScope(
ScopeToken token)
throws SemanticException {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().pushScope(token));
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning popScope(
ScopeToken token)
throws SemanticException {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().popScope(token));
return new TracePartitioning<>(lattice, result);
}
@Override
public StructuredRepresentation representation() {
if (isTop())
return Lattice.topRepresentation();
if (isBottom())
return Lattice.bottomRepresentation();
if (function == null)
return new StringRepresentation("empty");
return new MapRepresentation(function, StringRepresentation::new, AbstractState::representation);
}
@Override
public TracePartitioning mk(
A lattice,
Map function) {
return new TracePartitioning<>(lattice, function);
}
/**
* Collapses all of the traces contained in this domain, returning a unique
* abstract state that over-approximates all of them.
*
* @return the collapsed state
*/
public A collapse() {
if (isTop())
return lattice.top();
A result = lattice.bottom();
if (isBottom() || function == null)
return result;
try {
for (Entry trace : this)
result = result.lub(trace.getValue());
} catch (SemanticException e) {
return result.bottom();
}
return result;
}
@Override
public String toString() {
return representation().toString();
}
@Override
public ExpressionSet rewrite(
SymbolicExpression expression,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (!expression.mightNeedRewriting())
return new ExpressionSet(expression);
if (isTop())
return lattice.top().rewrite(expression, pp, oracle);
else if (isBottom() || function == null)
return lattice.bottom().rewrite(expression, pp, oracle);
Set result = new HashSet<>();
for (A dom : getValues())
result.addAll(dom.rewrite(expression, pp, oracle).elements());
return new ExpressionSet(result);
}
@Override
public ExpressionSet rewrite(
ExpressionSet expressions,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return lattice.top().rewrite(expressions, pp, oracle);
else if (isBottom() || function == null)
return lattice.bottom().rewrite(expressions, pp, oracle);
Set result = new HashSet<>();
for (A dom : getValues())
result.addAll(dom.rewrite(expressions, pp, oracle).elements());
return new ExpressionSet(result);
}
@Override
public Set getRuntimeTypesOf(
SymbolicExpression e,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return lattice.top().getRuntimeTypesOf(e, pp, oracle);
else if (isBottom() || function == null)
return lattice.bottom().getRuntimeTypesOf(e, pp, oracle);
Set result = new HashSet<>();
for (A dom : getValues())
result.addAll(dom.getRuntimeTypesOf(e, pp, oracle));
return result;
}
@Override
public Type getDynamicTypeOf(
SymbolicExpression e,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return lattice.top().getDynamicTypeOf(e, pp, oracle);
else if (isBottom() || function == null)
return lattice.bottom().getDynamicTypeOf(e, pp, oracle);
Set result = new HashSet<>();
for (A dom : getValues())
result.add(dom.getDynamicTypeOf(e, pp, oracle));
return Type.commonSupertype(result, Untyped.INSTANCE);
}
@Override
public A stateOfUnknown(
ExecutionTrace key) {
return lattice.bottom();
}
@Override
public boolean knowsIdentifier(
Identifier id) {
if (isTop() || isBottom() || function == null)
return false;
for (Entry trace : this)
if (trace.getValue().knowsIdentifier(id))
return true;
return false;
}
@Override
public TracePartitioning withTopMemory() {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().withTopMemory());
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning withTopValues() {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().withTopValues());
return new TracePartitioning<>(lattice, result);
}
@Override
public TracePartitioning withTopTypes() {
if (isTop() || isBottom() || function == null)
return this;
Map result = mkNewFunction(null, false);
for (Entry trace : this)
result.put(trace.getKey(), trace.getValue().withTopTypes());
return new TracePartitioning<>(lattice, result);
}
@Override
public Satisfiability alias(
SymbolicExpression x,
SymbolicExpression y,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return Satisfiability.UNKNOWN;
if (isBottom() || function == null)
return Satisfiability.BOTTOM;
Satisfiability result = Satisfiability.BOTTOM;
for (Entry trace : this) {
Satisfiability sat = trace.getValue().alias(x, y, pp, oracle);
if (sat == Satisfiability.BOTTOM)
return sat;
result = result.lub(sat);
}
return result;
}
@Override
public Satisfiability isReachableFrom(
SymbolicExpression x,
SymbolicExpression y,
ProgramPoint pp,
SemanticOracle oracle)
throws SemanticException {
if (isTop())
return Satisfiability.UNKNOWN;
if (isBottom() || function == null)
return Satisfiability.BOTTOM;
Satisfiability result = Satisfiability.BOTTOM;
for (Entry trace : this) {
Satisfiability sat = trace.getValue().isReachableFrom(x, y, pp, oracle);
if (sat == Satisfiability.BOTTOM)
return sat;
result = result.lub(sat);
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy