/******************************************************************************
* Copyright (c) 2009 - 2015 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*****************************************************************************/
/**
*
*/
package com.ibm.wala.memsat.translation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.cast.ir.ssa.AstEchoInstruction;
import com.ibm.wala.cfg.ControlFlowGraph;
import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.cfg.Util;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.memsat.frontEnd.DependenceGraph;
import com.ibm.wala.memsat.frontEnd.FieldSSATable;
import com.ibm.wala.memsat.frontEnd.IRType;
import com.ibm.wala.memsat.representation.ArrayExpression;
import com.ibm.wala.memsat.representation.FieldExpression;
import com.ibm.wala.shrikeBT.ConditionalBranchInstruction;
import com.ibm.wala.ssa.ISSABasicBlock;
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
import com.ibm.wala.ssa.SSAArrayLengthInstruction;
import com.ibm.wala.ssa.SSAArrayReferenceInstruction;
import com.ibm.wala.ssa.SSACFG;
import com.ibm.wala.ssa.SSACheckCastInstruction;
import com.ibm.wala.ssa.SSAConditionalBranchInstruction;
import com.ibm.wala.ssa.SSAFieldAccessInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAMonitorInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSASwitchInstruction;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.intset.IntIterator;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntConstant;
import kodkod.ast.IntExpression;
import kodkod.ast.NotFormula;
/**
* Handles the caching and computation of guards during
* the translation of a given method.
*
* @specfield methodEntryGuard: Formula // the guard for entering this.node
* @specfield node: CGNode // the method being translated
* @specfield frame: Frame // the translation frame
* @specfield callGuards: SSAAbstractCallInstruction ->lone Formula // call guards
* @invariant frame.callInfo.cgNode = node
*
* @author etorlak
*/
final class GuardHandler {
private static enum EdgeType {
EXCEPTIONAL,
NORMAL,
NOT_APPLICABLE;
}
private final Environment env;
private final Formula methodEntryGuard;
private final Map<SSACFG.BasicBlock, Formula> blockEntryGuards;
private final Map<SSAAbstractInvokeInstruction, Formula> callExitGuards;
private final Map<SSAInstruction, SSACFG.BasicBlock> instructionToBlock;
private final Map<Expression, Formula> nonNullGuards;
private final CGNode node;
private final FieldSSATable fieldSSA;
private final Set<TranslationWarning> warnings;
private final Expression nil;
/**
* Creates a GuardHandler for the translation of env.top().callInfo().cgNode().
* Any warnings generated during guard computation will be stored in the provided set.
* @effects this.methodEntryGuard' = entryGuard and this.frame' = env.top and
* this.node = this.frame'.callInfo.cgNode
*/
GuardHandler(Formula entryGuard, Environment env, Set<TranslationWarning> warnings) {
this.methodEntryGuard = entryGuard;
this.env = env;
this.warnings = warnings;
this.node = env.top().callInfo().cgNode();
this.fieldSSA = env.top().callInfo().fieldSSA();
this.blockEntryGuards = new LinkedHashMap<SSACFG.BasicBlock, Formula>();
this.callExitGuards = new LinkedHashMap<SSAAbstractInvokeInstruction, Formula>();
this.instructionToBlock = new LinkedHashMap<SSAInstruction, SSACFG.BasicBlock>();
this.nonNullGuards = new LinkedHashMap<Expression, Formula>();
this.nil = env.factory().constants().nil();
for(Iterator<ISSABasicBlock> bbs = node.getIR().getControlFlowGraph().iterator(); bbs.hasNext(); ) {
SSACFG.BasicBlock bb = (SSACFG.BasicBlock)bbs.next();
for(Iterator<SSAInstruction> insts = bb.iterator(); insts.hasNext(); ) {
instructionToBlock.put(insts.next(), bb);
}
for(Iterator<SSAPhiInstruction> insts = fieldSSA.getPhiNodes(bb); insts.hasNext(); ) {
instructionToBlock.put(insts.next(), bb);
}
}
}
/**
* Adds the given guard to the given call.
* @requires call in this.node.getIR().getInstructions()
* @requires no this.callGuards[call]
* @effects this.callGuards' = this.callGuards + call->guard
*/
final void addCallExitGuard(SSAAbstractInvokeInstruction call, Formula guard) {
assert !callExitGuards.containsKey(call);
callExitGuards.put(call, guard);
}
/**
* Returns the absolute entry guard for the basic block that contains
* the given instruction; the absolute guard is the conjunction of the
* methodEntryGuard and the relativeEntryGuard for the given instruction.
* @requires inst in this.node.getIR().getInstructions()
* @return this.methodEntryGuard && relativeEntryGuard(inst)
*/
final Formula absoluteEntryGuard(SSAInstruction inst) {
return methodEntryGuard.and(relativeEntryGuard(inst));
}
/**
* Returns the relative entry guard for the basic block (from this.cfg)
* that contains the given instruction; the returned formula doesn't
* include the guard for the call to the analyzed method.
* @requires inst in this.node.getIR().getInstructions()
* @return the entry guard for the basic block that contains the
* given instruction
*/
final Formula relativeEntryGuard(SSAInstruction inst) {
return relativeEntryGuard(blockFor(inst));
}
/**
* Returns the normal exit guard for this.node.getIR().getControlFlowGraph().
* @return normal exit guard for this.node.getIR().getControlFlowGraph()
*/
final Formula normalExitGuard() {
final List<Formula> normalExits = new ArrayList<Formula>();
final ControlFlowGraph<SSAInstruction, ISSABasicBlock> cfg = node.getIR().getControlFlowGraph();
final SSACFG.BasicBlock exit = (SSACFG.BasicBlock)cfg.exit();
for(ISSABasicBlock normal : cfg.getNormalPredecessors(exit)) {
if (cfg.getSuccNodeCount(normal) == 1) {
normalExits.add(relativeEntryGuard((SSACFG.BasicBlock)normal));
} else {
normalExits.add(
relativeEntryGuard((SSACFG.BasicBlock)normal)
.and(edgeGuard((SSACFG.BasicBlock)normal,exit,EdgeType.NORMAL)));
}
}
assert !normalExits.isEmpty();
return Formula.or(normalExits);
}
/**
* Returns relative guards for each use of the given phi instruction.
* In particular, the ith Formula in the return array stores the
* guard for inst.getUse(i).
* @requires inst in this.node.getIR().getInstructions()
* @return relative guards for each use of the given phi instruction
*/
final Formula[] phiUseGuards(SSAPhiInstruction inst) {
final Formula[] ret = new Formula[inst.getNumberOfUses()];
final SSACFG.BasicBlock bb = blockFor(inst);
final EdgeType type = bb.isExitBlock()? EdgeType.NORMAL: EdgeType.NOT_APPLICABLE;
final ControlFlowGraph<SSAInstruction, ISSABasicBlock> cfg = node.getIR().getControlFlowGraph();
for(Iterator<? extends IBasicBlock<SSAInstruction>> itr = cfg.getPredNodes(bb); itr.hasNext();) {
SSACFG.BasicBlock pb = (SSACFG.BasicBlock)itr.next();
int which = com.ibm.wala.cast.ir.cfg.Util.whichPred(cfg, bb, pb);
if (cfg.getSuccNodeCount(pb) > 1) {
ret[which] = edgeGuard(pb,bb,type).and(relativeEntryGuard(pb));
} else {
ret[which] = relativeEntryGuard(pb);
}
}
//TODO: need both normal & exceptional edge to exit for the same block.
return ret;
}
/**
* Returns the basic block that contains the given instruction.
* @return the block containing the given instruction
**/
final SSACFG.BasicBlock blockFor(SSAInstruction inst) {
return instructionToBlock.get(inst);
}
/**
* Returns the relative entry guard for the given basic block, if already
* stored in this.blockEntryGuards. Otherwise, computes the relative guard, stores it
* in this.blockEntryGuards, and returns it. The returned formula doesn't
* include the guard for the call to the analyzed method.
* @requires bb in this.env.top.callInfo.controlDependences
* @return the relative entry guard for the given basic block
*/
final Formula relativeEntryGuard(SSACFG.BasicBlock bb) {
Formula guard = blockEntryGuards.get(bb);
if (guard == null) {
final DependenceGraph<SSACFG.BasicBlock> cdg = env.top().callInfo().controlDependences();
if (!cdg.containsNode(bb) || cdg.getPredNodeCount(bb)==0) {
guard = Formula.TRUE;
} else {
final List<Formula> guards = new ArrayList<Formula>(cdg.getPredNodeCount(bb));
for(Iterator<? extends SSACFG.BasicBlock> pbs = cdg.getPredNodes(bb); pbs.hasNext(); ) {
SSACFG.BasicBlock pb = pbs.next();
List<Formula> labelGuards = new ArrayList<Formula>();
for(SSACFG.BasicBlock label : cdg.edgeLabels(pb, bb)) {
labelGuards.add(edgeGuard(pb, label, EdgeType.NOT_APPLICABLE));
}
assert !labelGuards.isEmpty();
guards.add(relativeEntryGuard(pb).and(Formula.or(labelGuards)));
}
guard = Formula.or(guards);
}
blockEntryGuards.put(bb, guard);
}
return guard;
}
/**
* Returns a formula that evaluates to true if the given
* reference expression is not null.
* @return a formula that evaluates to true if the given
* reference expression is not null.
*/
private final Formula nonNullGuard(Expression ref) {
Formula guard = nonNullGuards.get(ref);
if (guard==null) {
guard = ref.eq(nil).not();
nonNullGuards.put(ref, guard);
}
return guard;
}
/**
* Returns a formula that evaluates to true if the given
* reference expression is null.
* @return a formula that evaluates to true if the given
* reference expression is null.
*/
private final Formula nullGuard(Expression ref) {
final Formula nonNullGuard = nonNullGuard(ref);
// nonNullGuard must be either a NotFormula or a ConstantFormula
if (nonNullGuard instanceof NotFormula) {
return ((NotFormula)nonNullGuard).formula();
} else if (nonNullGuard == Formula.TRUE) {
return Formula.FALSE;
} else if (nonNullGuard == Formula.FALSE) {
return Formula.TRUE;
} else {
throw new AssertionError("unreachable");
}
}
/**
* Returns the guard for the CFG edge defined by the given blocks.
* @requires bb in this.env.top.cfg.nodes
* @requires bb in pb.getNormalSuccessors() + pb.getExceptionalSuccessors()
* @return the edge guard for the given CFG edge
*/
private final Formula edgeGuard(final SSACFG.BasicBlock pb, final SSACFG.BasicBlock bb, final EdgeType type) {
assert bb.isExitBlock()?
!type.equals(EdgeType.NOT_APPLICABLE):
type.equals(EdgeType.NOT_APPLICABLE);
final AbstractInstructionVisitor<Formula> guardVisitor = new AbstractInstructionVisitor<Formula>() {
Formula guard = null;
final ControlFlowGraph<SSAInstruction, ISSABasicBlock> cfg = node.getIR().getControlFlowGraph();
/** @return result of applying this visitor to pb.getLastInstruction*/
protected final Formula execute() {
pb.getLastInstruction().visit(this);
assert guard != null : "Null guard for " + pb.getLastInstruction();
return guard;
}
public final void visitEcho(AstEchoInstruction instruction) {
Assertions.UNREACHABLE();
}
/**
* @effects sets this.guard to TRUE if bb is a normal successor of pb;
* otherwise sets the guard to FALSE.
*/
private final void takeNormalSuccessor(SSAInstruction inst) {
assert cfg.getNormalSuccessors(pb).size() >= 1;
final boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
final boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
if (normal && !type.equals(EdgeType.EXCEPTIONAL)) {
guard = Formula.TRUE;
} else {
guard = Formula.FALSE;
}
}
/** @effects sets this.guard to TRUE and adds a warning to this.warnings */
public final void visitInstruction(SSAInstruction inst) {
final String msg = "control dependent and no model of guard";
warnings.add(new TranslationWarning(node.getIR(), inst, msg));
takeNormalSuccessor(inst);
}
public final void visitMonitor(SSAMonitorInstruction inst) {
takeNormalSuccessor(inst);
}
/** @effects sets this.guard to the guard induced by the given switch instruction */
public final void visitSwitch(SSASwitchInstruction inst) {
assert type.equals(EdgeType.NOT_APPLICABLE);
final IntExpression v = env.intUse(inst.getUse(0));
// if v is null, then this switch instruction has been sliced out.
// in that case, it means that inst is irrelevant to the assertion we are checking.
// so, we set the guard to TRUE
if (v==null) {
guard = Formula.TRUE;
} else {
final List<Formula> cases = new ArrayList<Formula>();
if (Util.isSwitchDefault(cfg, pb, bb)) {
List<Formula> negated = new ArrayList<Formula>();
for(IntIterator itr = inst.iterateLabels(); itr.hasNext(); ) {
negated.add(v.eq(IntConstant.constant(itr.next())).not());
}
cases.add(Formula.and(negated));
} else {
cases.add(v.eq(IntConstant.constant(Util.getSwitchLabel(cfg, pb, bb))));
}
assert !cases.isEmpty(); // must be true if bb is a successor of pb
guard = Formula.or(cases);
}
}
/** @effects sets this.guard to the guard induced by the given branch instruction*/
public final void visitConditionalBranch(SSAConditionalBranchInstruction inst) {
assert ! type.equals(EdgeType.EXCEPTIONAL);
final int left = inst.getUse(0), right = inst.getUse(1);
assert node.getIR().getSymbolTable().isZero(right);
assert env.top().callInfo().typeOf(left)==IRType.BOOLEAN;
final boolean trueSucc = (bb==Util.getTakenSuccessor(cfg, pb));
final boolean eqTest = (inst.getOperator()==ConditionalBranchInstruction.Operator.EQ);
assert trueSucc || (bb==Util.getNotTakenSuccessor(cfg, pb));
assert eqTest || (inst.getOperator()==ConditionalBranchInstruction.Operator.NE);
final Formula formula = env.boolUse(left);
// if formula is null, then this conditional branch instruction has been sliced out.
// in that case, it means that this conditional is irrelevant to the assertion we are checking.
// so, we arbitrarily pick the true branch
if (formula==null) {
guard = trueSucc ? Formula.TRUE : Formula.FALSE;
} else {
guard = (trueSucc ^ eqTest) ? env.boolUse(left) : env.boolUse(left).not();
}
}
/** @effects sets this.guard to the guard induced by the given field access instruction */
public final void visitFieldAccess(SSAFieldAccessInstruction inst) {
boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
assert cfg.getNormalSuccessors(pb).size()==1;
if (normal && exceptional) {
assert bb.isExitBlock() && ! type.equals(EdgeType.NOT_APPLICABLE);
if (type.equals(EdgeType.NORMAL)){
normal = true;
exceptional = false;
} else {
normal = false;
exceptional = true;
}
}
final Expression ref = env.refUse(inst.getRef());
// if ref is null, the instruction that defines it has been sliced out.
// hence, we ignore all flows, normal and exceptional, that leave the enclosing basic block.
// if this is an access to a static field, then the guards are the same as if the instruction has
// been sliced out
if (inst.isStatic() || ref==null) {
guard = normal ? Formula.TRUE : Formula.FALSE;
} else {
guard = normal ? nonNullGuard(ref) : nullGuard(ref);
}
}
/** @effects sets this.guard to the guard induced by the given array length instruction */
public final void visitArrayLength(SSAArrayLengthInstruction inst) {
boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
assert cfg.getNormalSuccessors(pb).size()==1;
if (normal && exceptional) {
assert bb.isExitBlock() && ! type.equals(EdgeType.NOT_APPLICABLE);
if (type.equals(EdgeType.NORMAL)){
normal = true;
exceptional = false;
} else {
normal = false;
exceptional = true;
}
}
// if ref is null, the instruction that defines it has been sliced out.
// hence, we ignore all flows, normal and exceptional, that leave the enclosing basic block.
final Expression ref = env.refUse(inst.getUse(0));
if (ref==null) {
guard = normal ? Formula.TRUE : Formula.FALSE;
} else {
guard = normal ? nonNullGuard(ref) : nullGuard(ref);
}
}
/** @effects sets this.guard to the guard induced by the given array access instruction */
public final void visitArrayReferenceInstruction(SSAArrayReferenceInstruction inst) {
boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
assert cfg.getNormalSuccessors(pb).size()==1;
if (normal && exceptional) {
assert bb.isExitBlock() && ! type.equals(EdgeType.NOT_APPLICABLE);
if (type.equals(EdgeType.NORMAL)){
normal = true;
exceptional = false;
} else {
normal = false;
exceptional = true;
}
}
final Expression ref = env.refUse(inst.getArrayRef());
// if ref is null, the instruction that defines it has been sliced out.
// hence, we ignore all flows, normal and exceptional, that leave the enclosing basic block.
if (ref==null) {
guard = normal ? Formula.TRUE : Formula.FALSE;
} else {
final IntExpression idx = env.intUse(inst.getIndex());
final ArrayExpression<IntExpression> array = env.arrayUse(fieldSSA.getUse(inst, 0));
final int card = array.cardinality();
final List<Formula> idxMatches = new ArrayList<Formula>(card);
for(int i = 0; i < card; i++) {
idxMatches.add( array.index(ref, i).eq(idx) );
}
final FieldExpression<IntExpression> length = env.fieldUse(fieldSSA.getUse(inst, 1));
final Formula nonNullGuard = nonNullGuard(ref);
final Formula lengthGuard = idx.lt(length.read(ref));
final Formula accessGuard = Formula.or(idxMatches);
final Formula normalGuard = Formula.and(nonNullGuard, lengthGuard, accessGuard);
guard = normal ? normalGuard : normalGuard.not();
}
}
/** @effects sets this.guard to the guard induced by the given cast checking instruction*/
public final void visitCheckCast(SSACheckCastInstruction inst) {
boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
assert cfg.getNormalSuccessors(pb).size()==1;
if (normal && exceptional) {
assert bb.isExitBlock() && ! type.equals(EdgeType.NOT_APPLICABLE);
if (type.equals(EdgeType.NORMAL)){
normal = true;
exceptional = false;
} else {
normal = false;
exceptional = true;
}
}
// if ref is null, the instruction that defines it has been sliced out.
// hence, we ignore all flows, normal and exceptional, that leave the enclosing basic block.
final Expression ref = env.refUse(inst.getUse(0));
if (ref==null) {
guard = normal ? Formula.TRUE : Formula.FALSE;
} else {
Formula typeGuard = nullGuard(ref);
for(TypeReference t : inst.getDeclaredResultTypes()) {
final Expression type = env.factory().constants().valueOf(t);
typeGuard = typeGuard.or(ref.in(type));
}
guard = normal ? typeGuard : typeGuard.not();
}
}
/** @effects sets this.guard to the guard induced by the given invoke instruction*/
public final void visitAbstractInvoke(SSAAbstractInvokeInstruction inst) {
boolean exceptional = cfg.getExceptionalSuccessors(pb).contains(bb);
boolean normal = cfg.getNormalSuccessors(pb).contains(bb);
assert normal || exceptional;
if (normal && exceptional) {
assert bb.isExitBlock() && ! type.equals(EdgeType.NOT_APPLICABLE);
if (type.equals(EdgeType.NORMAL)){
normal = true;
exceptional = false;
} else {
normal = false;
exceptional = true;
}
}
if (normal){
guard = callExitGuards.get(inst);
if (guard == null){ // this call must have been sliced out
guard = Formula.TRUE;
}
} else if (exceptional){
if (callExitGuards.containsKey(inst)){
guard = callExitGuards.get(inst).not();
} else {
guard = Formula.FALSE;
}
} else {
Assertions.UNREACHABLE();
}
}
};
return guardVisitor.execute();
}
}