package jayhorn.hornify.encoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Verify;
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.ProverFun;
import jayhorn.solver.ProverHornClause;
import jayhorn.solver.ProverTupleExpr;
import jayhorn.solver.ProverTupleType;
import jayhorn.solver.ProverType;
import soottocfg.cfg.expression.Expression;
import soottocfg.cfg.expression.IdentifierExpression;
import soottocfg.cfg.expression.literal.NullLiteral;
import soottocfg.cfg.method.Method;
import soottocfg.cfg.statement.AssertStatement;
import soottocfg.cfg.statement.AssignStatement;
import soottocfg.cfg.statement.AssumeStatement;
import soottocfg.cfg.statement.CallStatement;
import soottocfg.cfg.statement.NewStatement;
import soottocfg.cfg.statement.PullStatement;
import soottocfg.cfg.statement.PushStatement;
import soottocfg.cfg.statement.Statement;
import soottocfg.cfg.type.IntType;
import soottocfg.cfg.type.ReferenceType;
import soottocfg.cfg.type.Type;
import soottocfg.cfg.variable.ClassVariable;
import soottocfg.cfg.variable.Variable;
import soottocfg.soot.transformers.ArrayTransformer;
public class StatementEncoder {
private final Prover p;
private final ExpressionEncoder expEncoder;
private final HornEncoderContext hornContext;
public final static String LP = "_lastpush";
private int nextLP = 0;
public final static String AI = "_ai";
private int nextAI = 0;
public StatementEncoder(Prover p, ExpressionEncoder expEnc) {
this.p = p;
this.expEncoder = expEnc;
this.hornContext = expEncoder.getContext();
}
/**
* A statement "s" is a transition from states described by the
* predicate "prePred" into states described by the predicate "postPred".
* That is s gets translated into at least one Horn clause:
*
* prePred(args) && guard => postPred(args')
*
* Here, guard is the condition for transition to be feasible. The guard is
* only
* used for assume and assert statement.
*
* The effect of a "s" on the program state is encoded by in args'. E.g., a
* statement
* x = y+1
* would be encoded as follows:
* Assume that before and after the statement only x any y are alive.
* Then our prePred will be prePred(x,y) and postPred(x,y).
* The effect of the assignment is now encoded by updating the params in
* postPred:
*
* prePred(x,y) -> postPred(y+1, y)
*
* @param s
* The statement for which we want to generate Horn clauses.
* @param prePred
* The predicated describing the pre-state.
* @param postPred
* The predicated describing the post-state.
* @return The set of Horn clauses that encodes the semantics of "s".
*/
public List<ProverHornClause> statementToClause(Statement s, HornPredicate prePred, HornPredicate postPred, Method m) {
final Map<Variable, ProverExpr> varMap = new HashMap<Variable, ProverExpr>();
// First create the atom for prePred.
HornHelper.hh().findOrCreateProverVar(p, prePred.variables, varMap);
final ProverExpr preAtom = prePred.instPredicate(varMap);
HornHelper.hh().findOrCreateProverVar(p, postPred.variables, varMap);
if (s instanceof AssertStatement) {
List<ProverHornClause> clause = assertToClause((AssertStatement) s, postPred, preAtom, varMap);
//System.out.println("Assert " + clause);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof AssumeStatement) {
List<ProverHornClause> clause = assumeToClause((AssumeStatement) s, postPred, preAtom, varMap);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof AssignStatement) {
List<ProverHornClause> clause = assignToClause((AssignStatement) s, postPred, preAtom, varMap);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof NewStatement) {
List<ProverHornClause> clause = newToClause((NewStatement) s, postPred, preAtom, varMap);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof CallStatement) {
List<ProverHornClause> clause = callToClause((CallStatement) s, postPred, preAtom, varMap);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof PullStatement) {
List<ProverHornClause> clause = pullToClause((PullStatement) s, postPred, preAtom, varMap, m);
S2H.sh().addClause(s, clause);
return clause;
} else if (s instanceof PushStatement) {
List<ProverHornClause> clause = pushToClause((PushStatement) s, postPred, preAtom, varMap, m);
S2H.sh().addClause(s, clause);
return clause;
}
throw new RuntimeException("Statement type " + s + " not implemented!");
}
/**
* for "assert(cond)"
* create two Horn clauses
* pre(...) && !cond -> false
* pre(...) -> post(...)
* where the first Horn clause represents the transition
* into the error state if the assertion doesn't hold.
*
* @param as
* @param prePred
* @param postPred
* @return
*/
public List<ProverHornClause> assertToClause(AssertStatement as, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
final ProverExpr cond = expEncoder.exprToProverExpr(as.getExpression(), varMap);
final ProverExpr errorState; // For now depending on the solver we use false or a predicate
String tag = "ErrorState@line" + as.getJavaSourceLine();
final ProverFun errorPredicate = p.mkHornPredicate(tag, new ProverType[] {});
if (p.solverName().equals("spacer")){
errorState = errorPredicate.mkExpr(new ProverExpr[]{});
S2H.sh().setErrorState(errorState, as.getJavaSourceLine());
}else{
errorState = p.mkLiteral(false);
}
clauses.add(p.mkHornClause(errorState, new ProverExpr[] { preAtom }, p.mkNot(cond)));
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
return clauses;
}
/**
* for "assume(cond)"
* create Horn clause
* pre(...) && cond -> post(...)
*
* @param as
* @param postPred
* @param preAtom
* @param varMap
* @return
*/
public List<ProverHornClause> assumeToClause(AssumeStatement as, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
final ProverExpr cond = expEncoder.exprToProverExpr(as.getExpression(), varMap);
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom }, cond));
return clauses;
}
public List<ProverHornClause> assignToClause(AssignStatement as, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
Verify.verify(as.getLeft() instanceof IdentifierExpression,
"only assignments to variables are supported, not to " + as.getLeft());
final IdentifierExpression idLhs = (IdentifierExpression) as.getLeft();
ProverExpr right = expEncoder.exprToProverExpr(as.getRight(), varMap);
if (as.getRight() instanceof NullLiteral) {
ProverTupleType pttLeft = (ProverTupleType) HornHelper.hh().getProverType(p, idLhs.getType());
right = HornHelper.hh().mkNullExpression(p, pttLeft.getSubTypes());
} else if (right instanceof ProverTupleExpr) {
if (!(idLhs.getType() instanceof ReferenceType)) {
Verify.verify(false, "Chances are that this is a cast from java.lang.Integer to int");
}
ProverTupleType pttLeft = (ProverTupleType) HornHelper.hh().getProverType(p, idLhs.getType());
if (pttLeft.getArity() > ((ProverTupleExpr) right).getArity()) {
ProverTupleExpr pteR = ((ProverTupleExpr) right);
ProverExpr[] expandedRightTuple = new ProverExpr[pttLeft.getArity()];
for (int i=0; i<pttLeft.getArity(); i++) {
if (i<pteR.getArity()) {
expandedRightTuple[i] = pteR.getSubExpr(i);
} else {
expandedRightTuple[i] = p.mkVariable("$dummy"+i, pttLeft.getSubType(i));
}
}
right = new ProverTupleExpr(expandedRightTuple);
} else if (pttLeft.getArity() > ((ProverTupleExpr) right).getArity()) {
throw new RuntimeException("Oops, not implemented");
}
}
varMap.put(idLhs.getVariable(), right);
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
return clauses;
}
public List<ProverHornClause> newToClause(NewStatement ns, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
Verify.verify(ns.getLeft() instanceof IdentifierExpression,
"only assignments to variables are supported, not to " + ns.getLeft());
final IdentifierExpression idLhs = (IdentifierExpression) ns.getLeft();
ReferenceType rightType = new ReferenceType(ns.getClassVariable());
ProverExpr[] tupleElements = new ProverExpr[rightType.getElementTypeList().size()];
tupleElements[0] = expEncoder
.exprToProverExpr(new IdentifierExpression(ns.getSourceLocation(), ns.getCounterVar()), varMap);
tupleElements[1] = p.mkLiteral(hornContext.getTypeID(ns.getClassVariable()));
for (int i = 2; i < tupleElements.length; i++) {
tupleElements[i] = p.mkVariable("$new" + i,
HornHelper.hh().getProverType(p, rightType.getElementTypeList().get(i)));
}
varMap.put(idLhs.getVariable(), p.mkTuple(tupleElements));
// final ProverExpr ctr = expEncoder.exprToProverExpr(new
// IdentifierExpression(ns.getSourceLocation(), ns.getCounterVar()),
// varMap);
// // set the ref to the current heap counter.
// varMap.put(idLhs.getVariable(), ctr);
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
return clauses;
}
/**
* Translates a call statement of the form:
* r1, ... rN = callee(arg1, ... argM)
* We assume that there exists a MethodContract that
* has two predicates:
* precondtion of arity M, and postcondition of arity N+M
* We generate two Horn clauses:
*
* pre(...) -> precondition(...)
* postcondition(...) -> post(...)
*
* @param cs
* @param postPred
* @param preAtom
* @param varMap
* @return
*/
public List<ProverHornClause> callToClause(CallStatement cs, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
final Method calledMethod = cs.getCallTarget();
final MethodContract contract = hornContext.getMethodContract(calledMethod);
List<Expression> callArgs = new LinkedList<Expression>(cs.getArguments());
List<Variable> inParams = new LinkedList<Variable>(calledMethod.getInParams());
Verify.verify(inParams.size() == callArgs.size(), inParams.size() + "!=" + callArgs.size());
Verify.verify(inParams.size() == contract.precondition.variables.size(),
inParams.size() + "!=" + contract.precondition.variables.size());
final List<Variable> receiverVars = new ArrayList<Variable>();
for (Expression e : cs.getReceiver()) {
receiverVars.add(((IdentifierExpression) e).getVariable());
}
// final List<ProverExpr> receiverExprs =
HornHelper.hh().findOrCreateProverVar(p, receiverVars, varMap);
List<Type> retTypes = new LinkedList<Type>(calledMethod.getReturnType());
final ProverExpr[] actualInParams = new ProverExpr[inParams.size()];
final ProverExpr[] actualPostParams = new ProverExpr[inParams.size() + retTypes.size()];
int cnt = 0;
for (Expression e : callArgs) {
ProverExpr expr;
if (e instanceof NullLiteral) {
ReferenceType rt = (ReferenceType) inParams.get(cnt).getType();
ProverTupleType ptt = (ProverTupleType) HornHelper.hh().getProverType(p, rt);
expr = HornHelper.hh().mkNullExpression(p, ptt.getSubTypes());
} else {
expr = expEncoder.exprToProverExpr(e, varMap);
}
if (expr instanceof ProverTupleExpr) {
/* check if the number of elements match. If the arg type
* is a super type of the param type, we have to throw away
* the surplus elements of the tuple.
*/
ProverTupleExpr pte = (ProverTupleExpr)expr;
ReferenceType rt = (ReferenceType) inParams.get(cnt).getType();
ProverTupleType ptt = (ProverTupleType) HornHelper.hh().getProverType(p, rt);
if (pte.getArity()>ptt.getArity()) {
ProverExpr[] smallTuple = new ProverExpr[ptt.getArity()];
for (int i=0;i<ptt.getArity();i++) {
smallTuple[i] = pte.getSubExpr(i);
}
expr = new ProverTupleExpr(smallTuple);
}
}
actualInParams[cnt] = expr;
actualPostParams[cnt] = expr;
++cnt;
}
List<Expression> receiver = new LinkedList<Expression>(cs.getReceiver());
for (int i = 0; i < retTypes.size(); ++i) {
Type tp = retTypes.get(i);
final ProverExpr callRes = HornHelper.hh().createVariable(p, "callRes_", tp);
actualPostParams[cnt++] = callRes;
if (i < receiver.size()) {
Expression lhs = receiver.get(i);
Verify.verify(lhs instanceof IdentifierExpression,
"only assignments to variables are supported, not to " + lhs);
// update the receiver var to the expression that we use in the
// call pred.
varMap.put(((IdentifierExpression) lhs).getVariable(), callRes);
}
}
final ProverExpr preCondAtom = contract.precondition.predicate.mkExpr(actualInParams);
clauses.add(p.mkHornClause(preCondAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
if (actualPostParams.length != contract.postcondition.variables.size()) {
StringBuilder sb = new StringBuilder();
sb.append(actualPostParams.length + "!=" + contract.postcondition.variables.size() + "\n");
sb.append(cs.toString());
sb.append(" [");
String comma = "";
for (Type t : cs.getCallTarget().getReturnType()) {
sb.append(comma);
comma = ", ";
sb.append(t);
}
sb.append("]");
Verify.verify(false, sb.toString());
}
for (int i = 0; i < contract.postcondition.variables.size(); i++) {
ProverType t_ = actualPostParams[i].getType();
ProverType vt = HornHelper.hh().getProverType(p, contract.postcondition.variables.get(i).getType());
if (!vt.equals(t_)) {
System.err.println("*********** at pos "+i+": ");
System.err.println(cs);
System.err.println(t_ + "\t" + vt);
throw new RuntimeException("Return type and receiver type don't match: " + vt + " and " + t_);
}
}
final ProverExpr postCondAtom = contract.postcondition.predicate.mkExpr(actualPostParams);
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom, postCondAtom }, p.mkLiteral(true)));
return clauses;
}
/**
* Translates a pull statement of the form:
* l1, l2, ..., ln = pull(obj)
* into Horn clauses.
* First, we look up all possible subtypes of obj and get the
* Horn Predicate that corresponds to it's invariant. Then,
* for each invariant of the form:
* inv(x1, ... xk) with k>=n
* We create a Horn clause
* pre(l1...ln) && inv(l1,...ln, x_(n+1)...xk) -> post(l1...ln)
*
* Not that k may be greater than n if we look at a subtype invariant
* of obj.
*
* @param pull
* @param postPred
* @param preAtom
* @param varMap
* @return
*/
public List<ProverHornClause> pullToClause(PullStatement pull, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap, Method m) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
Set<PushStatement> affecting = pull.getAffectingPushes();
Verify.verify(!affecting.isEmpty(),
"The set of pushes affecting this pull is empty, this would create an assume false");
for (PushStatement push : pull.getAffectingPushes()) {
ClassVariable sig = push.getClassSignature();
if (sig.subclassOf(pull.getClassSignature())) {
long pushid = -1;
if (soottocfg.Options.v().memPrecision() >= soottocfg.Options.MEMPREC_LASTPUSH)
pushid = push.getID();
HornPredicate invariant = this.hornContext.lookupInvariantPredicate(sig, pushid);
clauses.addAll(pullToIndividualClause(pull, postPred, preAtom, varMap, invariant, m));
}
}
return clauses;
}
private List<ProverHornClause> pullToIndividualClause(PullStatement pull, HornPredicate postPred,
ProverExpr preAtom, Map<Variable, ProverExpr> varMap, HornPredicate invariant, Method m) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
// the first argument is always the reference
// to the object
int i = 0;
// PR: once the reference becomes a vector, we
// have to properly map between the elements
// of the vector and the fields of the object
varMap.put(invariant.variables.get(i++), expEncoder.exprToProverExpr(pull.getObject(), varMap));
// RK: we should probably elimate these ghostExpressions altogether
// MS: add all global ghosts
for (Expression e : pull.getGhostExpressions()) {
// System.err.println(invariant.variables.get(i)+" = "+e);
varMap.put(invariant.variables.get(i++), expEncoder.exprToProverExpr(e, varMap));
}
// RK: add last push variable
if (soottocfg.Options.v().memPrecision() >= soottocfg.Options.MEMPREC_LASTPUSH) {
Variable freshLp = new Variable(LP + nextLP, IntType.instance());
Expression e = new IdentifierExpression(pull.getSourceLocation(), freshLp);
varMap.put(invariant.variables.get(i++), expEncoder.exprToProverExpr(e, varMap));
}
// RK: add index variable for array classes
if (soottocfg.Options.v().arrayInv()
&& pull.getObject().getType().toString().contains(ArrayTransformer.arrayTypeName)) {
if (m.isArrayMethod()) {
Variable arIndex = m.getInParam(1);
Expression e = new IdentifierExpression(pull.getSourceLocation(), arIndex);
varMap.put(invariant.variables.get(i++), expEncoder.exprToProverExpr(e, varMap));
} else {
Variable arIndex = new Variable(AI + nextAI++, IntType.instance());
Expression e = new IdentifierExpression(pull.getSourceLocation(), arIndex);
varMap.put(invariant.variables.get(i++), expEncoder.exprToProverExpr(e, varMap));
}
}
// introduce fresh prover variables for all
// the other invariant parameters, and map
// them to the post-state
int j = 0;
for (; i < invariant.variables.size(); i++) {
final ProverExpr var = HornHelper.hh().createVariable(p, "pullVar_", invariant.variables.get(i).getType());
varMap.put(invariant.variables.get(i), var);
if (j < pull.getLeft().size()) {
varMap.put(pull.getLeft().get(j++).getVariable(), var);
}
/*
* If our current invariant is a subtype
* of what the orginial pull used, it
* might have more fields (that were added
* in the subtype). For this case, we have
* to fill up our args with fresh, unbound
* variables to match the number of
* arguments.
*/
}
// now we can instantiate the invariant.
final ProverExpr invAtom = invariant.instPredicate(varMap);
// System.err.println("Invariant for " + pull + ": " + invAtom);
final ProverExpr postAtom = postPred.instPredicate(varMap);
final ProverHornClause clause = p.mkHornClause(postAtom, new ProverExpr[] { preAtom, invAtom },
p.mkLiteral(true));
clauses.add(clause);
return clauses;
}
/**
* Works like an assert of the invariant of the type that is
* being pushed.
* First we get the invariant HornPredicate and we instantiate it
* with the current object and all expressions that are being pushed.
* Then we generate two Horn clauses. One assertion Horn clause
* pre(...) -> inv(...)
* and one for the transition.
* pre(...) -> post(...)
*
* @param ps
* @param postPred
* @param preAtom
* @param varMap
* @return
*/
public List<ProverHornClause> pushToClause(PushStatement ps, HornPredicate postPred, ProverExpr preAtom,
Map<Variable, ProverExpr> varMap, Method m) {
List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();
final ClassVariable sig = ps.getClassSignature();
Verify.verify(sig.getAssociatedFields().length == ps.getRight().size(),
"Unequal lengths: " + sig + " and " + ps.getRight() + " in " + ps);
int pushid = -1;
final List<Expression> invariantArgs = new LinkedList<Expression>();
// TODO: unpack once we have tuples
invariantArgs.add(ps.getObject());
// RK: eliminate and handle callerID here instead?
// MS: add all global ghosts
invariantArgs.addAll(ps.getGhostExpressions());
if (soottocfg.Options.v().memPrecision() >= soottocfg.Options.MEMPREC_LASTPUSH) {
Variable lp = new Variable(LP + nextLP++, IntType.instance());
Expression lastpush = new IdentifierExpression(ps.getSourceLocation(), lp);
invariantArgs.add(lastpush);
pushid = ps.getID();
}
// Rody: add index variable for array classes
if (soottocfg.Options.v().arrayInv()) {
if (m.isArrayMethod()) {
if (m.isArrayGet() || m.isArraySet()) {
Variable arIndex = m.getInParam(1);
// System.out.println("Vars: " + m.getInParams());
Expression e = new IdentifierExpression(ps.getSourceLocation(), arIndex);
invariantArgs.add(e);
} else {
Verify.verify(m.isArrayConstructor(),
"JayArray class should only contain get, set and constructor, but contains "
+ m.getMethodName());
Variable arIndex = new Variable(AI + nextAI++, IntType.instance());
Expression e = new IdentifierExpression(ps.getSourceLocation(), arIndex);
invariantArgs.add(e);
}
} else if (ps.getObject().getType().toString().contains(ArrayTransformer.arrayTypeName)) {
// System.out.println("Found array push outside array class: " + ps);
Variable arIndex = new Variable(AI + nextAI++, IntType.instance());
Expression e = new IdentifierExpression(ps.getSourceLocation(), arIndex);
invariantArgs.add(e);
}
}
invariantArgs.addAll(ps.getRight());
// get the invariant for the ClassVariable
final HornPredicate invariant = this.hornContext.lookupInvariantPredicate(sig, pushid);
// assign the variables of the invariant pred to the respective value.
for (int i = 0; i < invariantArgs.size(); i++) {
final ProverExpr right;
if (invariantArgs.get(i) instanceof NullLiteral) {
ProverTupleType ptt = (ProverTupleType) HornHelper.hh().getProverType(p,
invariant.variables.get(i).getType());
right = HornHelper.hh().mkNullExpression(p, ptt.getSubTypes());
} else {
right = expEncoder.exprToProverExpr(invariantArgs.get(i), varMap);
}
varMap.put(invariant.variables.get(i), right);
// System.out.println("invariantArgs = " + invariantArgs.get(i));
// System.out.println(invariant.variables.get(i)+ " = "+varMap.get(invariant.variables.get(i)) + "\n");
}
final ProverExpr invAtom = invariant.instPredicate(varMap);
clauses.add(p.mkHornClause(invAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
// System.err.println("Invariant for " + ps + ": " + invAtom);
final ProverExpr postAtom = postPred.instPredicate(varMap);
clauses.add(p.mkHornClause(postAtom, new ProverExpr[] { preAtom }, p.mkLiteral(true)));
return clauses;
}
}