/****************************************************************************** * 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.EmptyStackException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import com.ibm.wala.cast.ir.ssa.AstLexicalAccess.Access; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.callgraph.propagation.PointerKey; import com.ibm.wala.ipa.callgraph.propagation.rta.CallSite; import com.ibm.wala.memsat.frontEnd.FieldSSATable; import com.ibm.wala.memsat.frontEnd.WalaCGNodeInformation; import com.ibm.wala.memsat.representation.ArrayExpression; import com.ibm.wala.memsat.representation.ConstantFactory; import com.ibm.wala.memsat.representation.ExpressionFactory; import com.ibm.wala.memsat.representation.FieldExpression; import com.ibm.wala.memsat.representation.HeapExpression; import com.ibm.wala.memsat.representation.RealExpression; import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; import com.ibm.wala.ssa.SymbolTable; import com.ibm.wala.util.collections.Iterator2Collection; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.IntExpression; import kodkod.util.collections.ArrayStack; import kodkod.util.collections.Stack; /** * Stores and manages the state of a symbolic execution * performed by an instance of the {@linkplain Translator Miniatur translator}. * An instance of this class is, conceptually, a stack of {@linkplain Frame context frames}, * one for each method that has been symbolically called so far (analogous to a * stack frame created for a method call in a real execution environment). * * <p>An environment manages both the local (stack) and global (heap and lexical) variables. * Each method has its own view of the heap and the lexical scope, as given by its context frame. * The handling of globally visible writes (i.e. to heap locations) is left unspecified. An * environment only guarantees that after its top frame has been popped off, the exit writes to * the heap and lexical variables will have correctly propagated to the preceding frames. Note that * only the top frame's view of the heap / lexical / local variables is visible at any given time. * </p> * * @specfield frames: Stack<Frame> * @specfield top: lone frames.elems[0] * @specfield factory: ExpressionFactory // factory for creating Kodkod representations of constants, initial heap values, etc. * @specfield instantiated: factory.info.relevantClasses ->one int * * @invariant no top.call // the entry point method doesn't have a call instruction associated with it. * @invariant all c: factory.info.relevantClasses | 0 <= instantiated[c] < factory.info.cardinality(c) * @author Emina Torlak */ public final class Environment { private final Stack<Frame> frames; private final ExpressionFactory factory; private Frame top; private Stack<CallSite> callStack; private final Map<InstanceKey, Iterator<Expression>> nextObj; /** * Creates a new empty environment that will generate initial values using the given expression factory. * @effects no this.frames' and this.factory' = factory and * this.instantiated' = factory.info.relevantClasses -> 0 */ public Environment(ExpressionFactory factory) { assert factory != null; this.factory = factory; this.top = null; this.frames = new ArrayStack<Frame>(); this.callStack = null; this.nextObj = new LinkedHashMap<InstanceKey, Iterator<Expression>>(); for(InstanceKey key : factory.info().relevantClasses()) { nextObj.put(key, factory.constants().instances(key)); } } /** * Returns this.factory. * @return this.factory */ public ExpressionFactory factory() { return factory; } /** * Returns the top frame, if any. Otherwise returns null. * @return this.top */ public Frame top() { return top; } /** * Returns the number of recursive occurrences of the given target in * this environment. * @return # { f: this.frames.elems[int] | f.callInfo.cgNode.equals(target) } */ public int recursionCount(CGNode target) { int count = 0; for(Frame frame : frames) { if (frame.callInfo.cgNode().equals(target)) { count++; } } return count; } /** * Returns an unmodifiable view of this environment's call stack, given * as a stack of call site references. The view * does not include the top-level call (for which there is no * call site reference). The view's hashcode is the sum of the contained * references' hashcodes. * @return an unmodifiable view of this environment's call stack, given as a * stack of call site references. */ public Stack<CallSite> callStack() { if (callStack==null) { callStack = new Stack<CallSite>() { public boolean empty() { return size() > 0; } public int size() { return frames.size() - 1; } public CallSite peek() { if (empty()) throw new EmptyStackException(); return new CallSite(top.call.getCallSite(), top.callInfo.cgNode()); } public int search(Object arg0) { int i = 1; for(CallSite site : this) { if (site.equals(arg0)) return i; i++; } return -1; } public Iterator<CallSite> iterator() { return new Iterator<CallSite>() { final Iterator<Frame> itr = frames.iterator(); Frame caller = null, callee = null; public boolean hasNext() { if (callee==null) { if (itr.hasNext()) { callee = itr.next(); } else { return false; } } if (caller==null) { if (itr.hasNext()) { caller = itr.next(); } else { return false; } } return true; } public CallSite next() { if (!hasNext()) throw new NoSuchElementException(); final CallSite next = new CallSite(callee.call.getCallSite(), caller.callInfo.cgNode()); callee = caller; caller = null; return next; } public void remove() { throw new UnsupportedOperationException(); } }; } public CallSite pop() { throw new UnsupportedOperationException(); } public CallSite push(CallSite arg0) { throw new UnsupportedOperationException(); } }; } return callStack; } /** * @effects puts definitions for the given args and all constant * values in the top frame. */ private void defConstants(Object[] args) { for(int i = 0; i < args.length; i++) { top.localDef(i+1, args[i]); } final SymbolTable table = top.callInfo.cgNode().getIR().getSymbolTable(); final ConstantFactory constants = factory.constants(); for(int v = args.length+1, max = table.getMaxValueNumber(); v <= max; v++) { if (table.isConstant(v)) { if (table.isBooleanConstant(v)) { top.localDef(v, constants.valueOf(table.isTrue(v))); } else if (table.isIntegerConstant(v)) { top.localDef(v, constants.valueOf(table.getIntValue(v))); } else if (table.isNullConstant(v)) { top.localDef(v, constants.nil()); } else if (table.isStringConstant(v)) { top.localDef(v, constants.valueOf(table.getStringValue(v))); } else if (table.isLongConstant(v)) { top.localDef(v, constants.valueOf(table.getLongValue(v))); } else if (table.isFloatConstant(v)) { top.localDef(v, constants.valueOf(table.getFloatValue(v))); } else if (table.isDoubleConstant(v)) { top.localDef(v, constants.valueOf((float)table.getDoubleValue(v))); } else { throw new AssertionError("Unknown constant type for value number " + v); } } } } /** * Returns an expression that represents the next * instance in the set of instances defined by the * given key. * @requires key in this.factory.info.relevantClasses() * @requires this.instantiated[key] < this.factory.info.cardinality(key) * @effects this.instantiated' = this.instantiated ++ key -> (this.instantiated[key]+1) * @return this.factory.instance(key, this.instantiated[key]) */ public Expression instantiate(InstanceKey key) { return nextObj.get(key).next(); } /** * Pushes a new frame for the given entry-point call onto * the empty environment stack, and returns this environment. * The local and heap environment are populated using this.factory. * @requires no this.frames.elems * @requires some i: [0..this.factory.info.threads()) | this.factory.info.entry(i) = node * @effects * this.frames.push(this.top') and * no this.top'.call and this.top'.callInfo = this.factory.info.cgNodeInformation(node) and * (all i: [1..args.length] | this.top'.localEnv[i] = this.factory.arguments(node)[i-1]) and * (all i: [1..this.top'.callInfo.fieldSSATable().getMaxInitialHeapNumber()] | * this.top'.heapEnv[i] = this.factory.initValueOf(this.top'.callInfo.fieldSSATable().getField(i))) * @return this */ public Environment push(CGNode node) { assert frames.empty(); top = frames.push(new Frame(null, factory.info().cgNodeInformation(node))); // define args and constants defConstants(factory.arguments(node)); // initialize the heap final FieldSSATable fieldSSA = top.callInfo.fieldSSA(); for(int i = 1, maxInit = fieldSSA.getMaxInitialHeapNumber(); i <= maxInit; i++) { top.heapDef(i, factory.initValueOf(fieldSSA.getField(i))); } return this; } /** * Pushes a new frame for the given entry-point call onto * the empty environment stack, and returns this environment. * The local environment is populated using this.factory. The * initial values for fields are populated using the given map * and this.factory. In particular, if the map has a value for * a given (pointer key to a) field, that value is used as the * initial value. Otherwise, the {@linkplain ExpressionFactory#initValueOf(PointerKey) initial value} * given by this.factory is used. * @requires no this.frames.elems * @requires some i: [0..this.factory.info.threads()) | this.factory.info.entry(i) = node * @effects * this.frames.push(this.top') and * no this.top'.call and this.top'.callInfo = this.factory.info.cgNodeInformation(node) and * (all i: [1..args.length] | this.top'.localEnv[i] = this.factory.arguments(node)[i-1]) and * (all i: [1..this.top'.callInfo.fieldSSATable().getMaxInitialHeapNumber()] | * let field = this.top'.callInfo.fieldSSATable().getField(i) | * this.top'.heapEnv[i] = override.containsKey(field) => * override.get(field) else this.factory.initValueOf(field)) ) * @return this */ public Environment push(CGNode node, Map<PointerKey, HeapExpression<?>> override) { assert frames.empty(); top = frames.push(new Frame(null, factory.info().cgNodeInformation(node))); // define args and constants defConstants(factory.arguments(node)); // initialize the heap final FieldSSATable fieldSSA = top.callInfo.fieldSSA(); for(int i = 1, maxInit = fieldSSA.getMaxInitialHeapNumber(); i <= maxInit; i++) { final PointerKey key = fieldSSA.getField(i); top.heapDef(i, override.containsKey(key) ? override.get(key) : factory.initValueOf(key)); } return this; } /** * Pushes a new frame for the given entry-point call onto * the existing environment stack and returns this environment. The local and heap environment * are populated using the caller's local and heap environment (i.e. * this.top.localEnv and this.top.heapEnv). * @requires some this.frames.elems * @requires call in this.top.callInfo.cgNode.getIR().getInstructions * @requires callInfo in this.top.callInfo.cgNode.getPossibleTargets(call.getCallSite()) * @effects this.frames.push(this.top') and * this.top'.call = call and this.top'.callInfo = callInfo and * (all i: [1..args.length] | this.top'.localEnv[i] = args[i-1]) and * (all i: this.top.callInfo.getUses(call) | * this.top'.heapEnv[callInfo.fieldSSA.getEntryValue(this.top.callInfo.getField(i))] = * this.top.heapEnv[i])) and * @return this */ public Environment push(SSAAbstractInvokeInstruction call, WalaCGNodeInformation callInfo) { assert !frames.empty(); assert call != null; final Frame caller = top; top = frames.push(new Frame(call, callInfo)); // get args final Object[] args = new Object[call.getNumberOfParameters()]; for(int i = 0; i < args.length; i++) { args[i] = caller.localUse(call.getUse(i)); } // define the args and constants defConstants(args); // initialize heap final FieldSSATable calleeFSSA = callInfo.fieldSSA(); final FieldSSATable callerFSSA = caller.callInfo.fieldSSA(); final Set<PointerKey> used = Iterator2Collection.toSet(calleeFSSA.getFields()); for(int use : callerFSSA.getUses(call)) { // System.out.println("used field: " + callerFSSA.getField(use)); // System.out.println("used field value: " + caller.heapUse(use)); // if (caller.heapUse(use)==null) { // System.out.println("relevant field? " + factory.info().relevantFields().contains(callerFSSA.getField(use))); // System.out.println("factory field value: " + factory.valueOf(callerFSSA.getField(use))); // } // System.out.println("HERE::"+callerFSSA.getField(use)+"::"+calleeFSSA.getEntryValue(callerFSSA.getField(use))); PointerKey field = callerFSSA.getField(use); if (used.contains(field)) { top.heapDef(calleeFSSA.getEntryValue(field), caller.heapUse(use)); } } return this; } /** * Removes the top frame from the stack and returns it. * @requires some this.frames.elems * @effects this.frames.pop() * @effects this.top' = this.frames.elems'[0] * @return this.top */ public Frame pop() { final Frame callee = frames.pop(); top = frames.empty() ? null : frames.peek(); return callee; } /** * Returns the reference value stored at the given local address in the top frame. * @requires use in this.top.localVars * @requires this.top.callInfo.typeOf(use) = IRType.OBJECT * @return this.top.localEnv[use] */ public Expression refUse(int use) { return top.localUse(use); } /** * Returns the integer value stored at the given local address in the top frame. * @requires use in this.top.localVars * @requires this.top.callInfo.typeOf(use) = IRType.INTEGER * @return this.top.localEnv[use] */ public IntExpression intUse(int use) { return top.localUse(use); } /** * Returns the real value stored at the given local address in the top frame. * @requires use in this.top.localVars * @requires this.top.callInfo.typeOf(use) = IRType.REAL * @return this.top.localEnv[use] */ public RealExpression realUse(int use) { return top.localUse(use); } /** * Returns the boolean value stored at the given local address in the top frame. * @requires use in this.top.localVars * @requires this.top.callInfo.typeOf(use) = IRType.BOOLEAN * @return this.top.localEnv[use] */ public Formula boolUse(int use) { return top.localUse(use); } /** * Returns the value of the given local variable in the top context frame, * or null if it hasn't been defined. * @requires some this.top * @requires use in this.top.localVars * @return this.top.localEnv[use] */ public Object localUse(int use) { return top.localUse(use); } /** * Assigns the given value to the specified local variable in the top frame. * @requires value in Formula iff this.top.callInfo.typeOf(use) in IRType.BOOLEAN * @requires value in IntExpression iff this.top.callInfo.typeOf(use) in IRType.INTEGER * @requires value in Expression iff this.top.callInfo.typeOf(use) in IRType.OBJECT * @requires value in RealExpression iff this.top.callInfo.typeOf(use) in IRType.REAL * @requires def in this.top.localVars * @requires no this.top.localEnv[def] * @effects this.top.localEnv' = this.top.localEnv + def -> value */ public <T> void localDef(int def, T value) { top.localDef(def, value); } /** * Returns the top frame's view of the value of the given heap variable, * or null if it hasn't been defined. * @requires !this.top.callInfo.fieldSSA.isArrayNumber(use) * @requires some this.top * @requires use in this.top.heapVars * @return this.top.heapEnv[use] */ @SuppressWarnings("unchecked") public <T> FieldExpression<T> fieldUse(int use) { return (FieldExpression<T>)top.heapUse(use); } /** * Returns the top frame's view of the value of the given heap variable, * or null if it hasn't been defined. * @requires this.top.callInfo.fieldSSA.isArrayNumber(use) * @requires some this.top * @requires use in this.top.heapVars * @return this.top.heapEnv[use] */ @SuppressWarnings("unchecked") public <T> ArrayExpression<T> arrayUse(int use) { return (ArrayExpression<T>)top.heapUse(use); } /** * Returns the top frame's view of the value of the given heap variable, * or null if it hasn't been defined. If the given heap variable is an array, then the * returned value is an ArrayExpression; otherwise, it is an Expression. * @requires some this.top * @requires use in this.top.heapVars * @return this.top.heapEnv[use] */ public <T> HeapExpression<T> heapUse(int use) { return top.heapUse(use); } /** * Assigns the given value to the top frame's view of the specified heap variable. * This method assumes that the given heap variable has not already been defined. * @requires some this.top * @requires this.top.callInfo.fieldSSA.isArrayNumber(use) => value in ArrayExpression * else value in FieldExpression * @requires def in this.top.heapVars * @requires no this.top.heapEnv[def] * @effects this.top.heapEnv' = this.top.heapEnv + def -> value */ public <T> void heapDef(int def, HeapExpression<T> value) { top.heapDef(def, value); } /** * Returns a string representation of this environment. * @return a string representation of this environment */ public String toString() { if (frames.empty()) return "EMPTY ENVIRONMENT\n"; final List<String> fstrings = new ArrayList<String>(frames.size()); for(Frame frame : frames) { fstrings.add(frame.toString()); } final StringBuilder s = new StringBuilder(); s.append("--------------\n"); for(int i = fstrings.size()-1; i >= 0; i--) { s.append(fstrings.get(i)); s.append("--------------\n"); } return s.toString(); } /** * A symbolic execution frame for a method that has been translated * by the {@linkplain Translator Miniatur translator} with respect * to a given {@linkplain Environment environment} and {@linkplain ExpressionFactory initial heap}. * * @specfield call: lone SSAAbstractInvokeInstruction // invoke instruction, if any, that caused this frame to be dropped * @specfield callInfo: one WalaCGNodeInformation // cg node info, if any, for the method being executed * @specfield localVars: callInfo.cgNode.getIR().getSymbolTable().getMaxValueNumber() // SSA value numbers for local variables * @specfield heapVars: callInfo.fieldSSA.getMaxHeapNumber() // SSA value numbers for heap variables * @specfield localEnv: {i: int | 1 <= i <= localVars} ->lone (Node + RealExpression) * @specfield heapEnv: {i: int | 1 <= i <= heapVars} ->lone HeapExpression<?> * * @author Emina Torlak */ public static final class Frame { private final SSAAbstractInvokeInstruction call; private final WalaCGNodeInformation callInfo; private final Object[] localEnv; private final Object[] heapEnv; /** * Constructs a context frame for the given call, info, and entry guard. * @effects this.call' = call and this.callInfo' = callInfo and * no this.localEnv' and no this.heapEnv' and no this.guardedBlocks' and * no this.returnVal' and no this.exceptionVal' */ Frame(SSAAbstractInvokeInstruction call, WalaCGNodeInformation callInfo) { this.call = call; this.callInfo = callInfo; final CGNode node = callInfo.cgNode(); this.heapEnv = new Object[callInfo.fieldSSA().getMaxHeapNumber()]; this.localEnv = new Object[node.getIR().getSymbolTable().getMaxValueNumber()]; } /** * Returns this.callInfo, if any. Otherwise returns null. * @return this.callInfo */ public final WalaCGNodeInformation callInfo() { return callInfo; } /** * Returns this.call, if any. Otherwise returns null. * @return this.call */ public final SSAAbstractInvokeInstruction call() { return call; } /** * Returns true if i is in [1..max] * @return i >= 1 && i <= max */ private static final boolean inSSARange(int i, int max) { return i >= 1 && i <= max; } /** * Returns the definition of the given local variable, or * null if it hasn't been defined. * @requires use in this.localVars * @return this.localEnv[use] */ @SuppressWarnings("unchecked") public <T> T localUse(int use) { assert inSSARange(use, localEnv.length); return (T)localEnv[use-1]; } /** * Assigns the given value to the specified variable. * @requires def in this.stackVars * @requires no this.localEnv[def] * @effects this.localEnv' = this.localEnv + def -> value */ <T> void localDef(int def, T value) { assert inSSARange(def, localEnv.length); assert value != null; assert localEnv[def-1] == null; localEnv[def-1] = value; } /** * Returns the definition of the given heap variable, or null * if it hasn't been defined. If the given heap variable is an array, then the * returned value is an ArrayExpression; otherwise, it is an Expression. * @requires use in this.heapVars * @return this.heapEnv[use] */ @SuppressWarnings("unchecked") public <T> HeapExpression<T> heapUse(int use) { assert inSSARange(use, heapEnv.length); return (HeapExpression<T>)heapEnv[use-1]; } /** * Assigns the given value to the specified variable. * @requires def in this.heapVars * @requires no this.heapEnv[def] * @requires * this.callInfo.fieldSSA.isArrayNumber(def) => * value in ArrayExpression * else * value in Expression * @effects this.heapEnv' = this.heapEnv + def -> value */ void heapDef(int def, HeapExpression<?> value) { assert inSSARange(def, heapEnv.length) : "bad def " + def + " for " + value; assert value != null : "no value for " + callInfo.fieldSSA().getField(def); assert heapEnv[def-1] == null; heapEnv[def-1] = value; } /** * Returns true if the given access is an access to a lexically scoped * variable identified by the specified name and definer. * @return name.equals(access.variableName) && definer.equals(access.variableDefiner); */ static boolean accessToLexVar(Access access, String name, String definer) { return name.equals(access.variableName) && definer.equals(access.variableDefiner); } /** * Returns a string representation of this frame's contents. * @return a string representation of this frame's contents. */ public String toString() { final StringBuilder s = new StringBuilder(); if (call()==null) { s.append("Method: " + callInfo().cgNode().getMethod() + "\n"); } else { s.append("Call: " + call() + "\n"); } s.append(" Local environment:\n"); for(int i = 1, max = callInfo().cgNode().getIR().getSymbolTable().getMaxValueNumber(); i < max; i++) { Object val = localUse(i); if (val!=null) s.append(" " + (i+1) + ": " + val + "\n"); } s.append(" Heap:\n"); final FieldSSATable fieldSSA = callInfo().fieldSSA(); for(int i = 1, max = fieldSSA.getMaxHeapNumber(); i <= max; i++) { Object val = heapUse(i); if (val!=null) s.append(" " + fieldSSA.getField(i) + "_" + (i) + ": " + val + "\n"); } return s.toString(); } } }