package jayhorn.hornify.encoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jayhorn.Log;
import jayhorn.hornify.HornEncoderContext;
import jayhorn.hornify.HornHelper;
import jayhorn.hornify.HornPredicate;
import jayhorn.hornify.MethodContract;
import jayhorn.solver.Prover;
import jayhorn.solver.ProverExpr;
import jayhorn.solver.ProverHornClause;
//import soottocfg.Options;
import soottocfg.cfg.LiveVars;
import soottocfg.cfg.method.CfgBlock;
import soottocfg.cfg.method.CfgEdge;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.Statement;
import soottocfg.cfg.variable.Variable;
public class MethodEncoder {
private final Method method;
private final HornPredicate precondition, postcondition;
private final Prover p;
private final Map<CfgBlock, HornPredicate> blockPredicates = new LinkedHashMap<CfgBlock, HornPredicate>();
private final List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
private final List<ProverHornClause> tsClauses = new LinkedList<ProverHornClause>(); // keep track of
private final ExpressionEncoder expEnc;
public MethodEncoder(Prover p, Method method, HornEncoderContext hornContext) {
this.p = p;
this.method = method;
MethodContract mc = hornContext.getMethodContract(method);
this.precondition = mc.precondition;
this.postcondition = mc.postcondition;
this.expEnc = new ExpressionEncoder(p, hornContext);
}
/**
* Encodes a method into a set of Horn clauses.
*/
public List<ProverHornClause> encode() {
this.clauses.clear();
LiveVars<CfgBlock> liveVariables = method.computeBlockLiveVariables();
makeBlockPredicates(liveVariables);
if (method.getSource() == null) {
encodeEmptyMethod();
return clauses;
}
makeEntryPredicate();
blocksToHorn(liveVariables);
S2H.sh().addClause((Statement)null, tsClauses);
return clauses;
}
/**
* Creates a trivial Horn clause:
* pre(x,y,z) -> post(x,y,z)
* for methods that do not have a body.
*/
private void encodeEmptyMethod() {
Log.debug("No implementation available for " + method.getMethodName());
final Map<Variable, ProverExpr> varMap = new HashMap<Variable, ProverExpr>();
final ProverExpr entryAtom = precondition.instPredicate(varMap);
final ProverExpr exitAtom = postcondition.instPredicate(varMap);
final ProverHornClause clause = p.mkHornClause(exitAtom, new ProverExpr[] { entryAtom }, p.mkLiteral(true));
tsClauses.add(clause);
S2H.sh().addClause((Statement)null, tsClauses);
clauses.add(clause);
}
/**
* Creates a Horn clause that makes from the method precondition to
* the first block of the message body.
* This method precondition args comprise all method parameters and
* other variables that should be visible to the method. The args of
* the predicate of the first block (entryVars) contain all local vars.
*/
private void makeEntryPredicate() {
// add an entry clause connecting with the precondition
Map<Variable, ProverExpr> varMap = new HashMap<Variable, ProverExpr>();
final ProverExpr preAtom = precondition.instPredicate(varMap);
final ProverExpr entryAtom = blockPredicates.get(method.getSource()).instPredicate(varMap);
final ProverHornClause clause = p.mkHornClause(entryAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true));
tsClauses.add(clause);
S2H.sh().addClause((Statement)null, tsClauses);
clauses.add(clause);
}
/**
* Creates one HornPredicate for each block. The predicate contains the
* list of live variables
* for that block sorted by names and a predicate over the types of
* these variables that has
* the same name as the block.
*
* Note that the ProverFun in the predicate also contains all variables
* in precondition.variables. So the arity of the prover fun is
* |precondition.variables| + |sortedVars|
*
* @param p
* @param method
*/
private void makeBlockPredicates(LiveVars<CfgBlock> liveVariables) {
for (Entry<CfgBlock, Set<Variable>> entry : liveVariables.liveIn.entrySet()) {
Set<Variable> allLive = new HashSet<Variable>();
allLive.addAll(entry.getValue());
// sort the list of variables by name to make access
// and reading easier.
List<Variable> sortedVars = new LinkedList<Variable>();
sortedVars.addAll(HornHelper.hh().setToSortedList(allLive));
blockPredicates.put(entry.getKey(),
freshHornPredicate(method.getMethodName() + "_" + entry.getKey().getLabel(), sortedVars));
}
}
/**
* Creates a fresh HornPredicate with arity of
* |precondition.variables| + |sortedVars|.
*
* This is because we need to move the precondition variables through
* all Horn clauses until the exit.
* @param name
* @param sortedVars
* @return Horn Predicate of arity |precondition.variables| + |sortedVars|
*/
private HornPredicate freshHornPredicate(String name, List<Variable> sortedVars) {
// add types for the method arguments, which
// are later needed for the post-conditions
final List<Variable> allArgs = new LinkedList<Variable>();
allArgs.addAll(precondition.variables);
allArgs.addAll(sortedVars);
return new HornPredicate(p, name, allArgs);
}
/**
* Creates Horn clauses for all CfgBlocks in a method.
* @param liveVariables
* @param preVars
*/
private void blocksToHorn(LiveVars<CfgBlock> liveVariables) {
List<CfgBlock> todo = new LinkedList<CfgBlock>();
todo.add(method.getSource());
Set<CfgBlock> done = new HashSet<CfgBlock>();
// translate reachable blocks
while (!todo.isEmpty()) {
CfgBlock current = todo.remove(0);
done.add(current);
/*
* Translate the body of the CfgBlock using blockToHorn.
* This gives us the exitPred which is the last predicate
* used in this basic block.
*/
final HornPredicate exitPred = blockToHorn(current, liveVariables.liveOut.get(current));
//reset the varMap here.
Map<Variable, ProverExpr> varMap = new HashMap<Variable, ProverExpr>();
/*
* Now distinguish two cases:
* THEN-CASE: Our block has no successors and leaves the method.
* In this case, we have to connect exitPred to the predicate
* associated with the postcondition of the method. That is,
* we generate a Horn clause of the form
* exitPred(...) -> postPred(...)
* ELSE-CASE: Our block has at least one successor. In this case,
* we have to create a clause that connects exitPred with the
* predicate associated with the entry into the next block: succPred.
* And we have to add the next block to the todo list if we haven't
* processed it already.
*/
if (method.outgoingEdgesOf(current).isEmpty()) {
// block ends with a return
final ProverExpr postAtom = postcondition.instPredicate(varMap);
final ProverExpr exitAtom = exitPred.instPredicate(varMap);
ProverHornClause clause = p.mkHornClause(postAtom, new ProverExpr[] { exitAtom }, p.mkLiteral(true));
tsClauses.add(clause);
S2H.sh().addClause((Statement)null, tsClauses);
clauses.add(clause);
} else {
// link to the successor blocks
final ProverExpr exitAtom = exitPred.instPredicate(varMap);
for (CfgEdge edge : method.outgoingEdgesOf(current)) {
CfgBlock succ = method.getEdgeTarget(edge);
if (!todo.contains(succ) && !done.contains(succ)) {
todo.add(succ);
}
final ProverExpr exitCondExpr;
if (edge.getLabel().isPresent()) {
exitCondExpr = expEnc.exprToProverExpr(edge.getLabel().get(), varMap);
} else {
exitCondExpr = p.mkLiteral(true);
}
final ProverExpr succAtom = blockPredicates.get(succ).instPredicate(varMap);
ProverHornClause clause = p.mkHornClause(succAtom, new ProverExpr[] { exitAtom }, exitCondExpr);
tsClauses.add(clause);
S2H.sh().addClause((Statement)null, tsClauses);
clauses.add(clause);
}
}
}
}
/**
* Creates the Horn clauses for the statements in a single block.
* @param block The block that is to be translated.
* @param liveOutVars The set of variables that are live after the block.
* @return
*/
private HornPredicate blockToHorn(CfgBlock block, Set<Variable> liveOutVars) {
//get the predicate that is associated with the entry of the block.
final HornPredicate initPred = blockPredicates.get(block);
if (block.getStatements().isEmpty()) {
return initPred;
}
Map<Statement, Set<Variable>> liveAfter = computeLiveAfterVariables(block, liveOutVars);
final String initName = initPred.name;
HornPredicate prePred = initPred;
int counter = 0;
StatementEncoder senc = new StatementEncoder(p, this.expEnc);
List<Statement> stmts = block.getStatements();
for (int i = 0; i < stmts.size(); ++i) {
final Statement s = stmts.get(i);
final String postName = initName + "_" + (++counter);
final List<Variable> interVarList = HornHelper.hh().setToSortedList(liveAfter.get(s));
final HornPredicate postPred = freshHornPredicate(postName, interVarList);
this.clauses.addAll(senc.statementToClause(s, prePred, postPred, this.method));
prePred = postPred;
}
return prePred;
}
/**
* Compute for each statement the set of variables
* that are live after the statement.
* @param block The current CfgBlock.
* @param liveOutVars The set of vars that are live after block.
* @return A map that stores for each statement the
* set of variables that are live after the execution
* of the statement.
*/
private Map<Statement, Set<Variable>> computeLiveAfterVariables(CfgBlock block, Set<Variable> liveOutVars) {
Map<Statement, Set<Variable>> liveMap = new HashMap<Statement, Set<Variable>>();
@SuppressWarnings("unchecked")
final Set<Variable>[] interVars = new Set[block.getStatements().size()];
interVars[interVars.length - 1] = new HashSet<Variable>();
interVars[interVars.length - 1].addAll(liveOutVars);
// add variables used in the outgoing guards, and the
// method arguments
for (CfgEdge edge : method.outgoingEdgesOf(block))
if (edge.getLabel().isPresent())
interVars[interVars.length - 1].addAll(edge.getLabel().get().getUseVariables());
for (int i = interVars.length - 1; i > 0; --i) {
final Statement s = block.getStatements().get(i);
interVars[i - 1] = new HashSet<Variable>();
interVars[i - 1].addAll(interVars[i]);
interVars[i - 1].removeAll(s.getDefVariables());
interVars[i - 1].addAll(s.getUseVariables());
liveMap.put(s, interVars[i]);
}
liveMap.put(block.getStatements().get(0), interVars[0]);
return liveMap;
}
}