/****************************************************************************** * 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.representation; import static com.ibm.wala.memsat.frontEnd.IRType.BOOLEAN; import static com.ibm.wala.memsat.frontEnd.IRType.INTEGER; import static com.ibm.wala.memsat.frontEnd.IRType.OBJECT; import static com.ibm.wala.memsat.frontEnd.IRType.REAL; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.ibm.wala.cast.java.ipa.callgraph.AstJavaSSAPropagationCallGraphBuilder.EnclosingObjectReferenceKey; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.propagation.ArrayContentsKey; import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldKey; import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldPointerKey; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.callgraph.propagation.PointerKey; import com.ibm.wala.ipa.callgraph.propagation.StaticFieldKey; import com.ibm.wala.ipa.modref.ArrayLengthKey; import com.ibm.wala.memsat.Options; import com.ibm.wala.memsat.frontEnd.IRType; import com.ibm.wala.memsat.frontEnd.WalaCGNodeInformation; import com.ibm.wala.memsat.frontEnd.WalaInformation; import com.ibm.wala.memsat.util.Strings; import com.ibm.wala.types.TypeReference; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.IntConstant; import kodkod.ast.IntExpression; import kodkod.ast.Relation; import kodkod.ast.Variable; import kodkod.instance.Bounds; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; /** * A factory for generating Kodkod Expressions that * represent Wala values, fields, and arrays. These * are unknowns in the sense that their relational values * are not known a priori and have to be solved for. They include * field values and initial arguments to top level methods. * * @specfield info: WalaInformation // info used for constructing representations of instance keys, etc. * @specfield options: Options // options used for determining bitwidth for int representation, etc. * @specfield constants: ConstantFactory * @specfield fields: info.relevantFields() ->one HeapExpression * @specfield systemHashCode: FieldExpression<IntExpression> // binary field that models the system hashcode values * @specfield arguments: CGNode -> seq[ Node + RealExpression ] * * @invariant all f: info.relevantFields() & ArrayContentsKey | fields[f] in FieldExpression * @invariant all f: info.relevantFields() - ArrayContentsKey | fields[f] in ArrayExpression * @invariant constants.info = this.info && constants.options = this.options * * @author Emina Torlak */ public final class ExpressionFactory { private final WalaInformation info; private final Options options; private final ConstantFactory constants; private final Map<PointerKey, HeapExpression<?>> fields; private final FieldExpression<IntExpression> hash; private final Map<CGNode, Relation[]> arguments; /** * Constructs an expression factory from the given info and options * @requires info.options = options * @effects this.info' = info and this.options' = options */ public ExpressionFactory(WalaInformation info, Options options) { this.info = info; this.options = options; this.constants = new ConstantFactory(info,options); final Collection<? extends Expression> setExprs = constants.setExpressions(); this.hash = FieldExpression.field("systemHashCode", setExprs.isEmpty() ? Expression.NONE : Expression.union(setExprs), constants.intInterpreter()); this.fields = new LinkedHashMap<PointerKey, HeapExpression<?>>(); final int card = options.numberOfIndexAtoms(); for(Entry<PointerKey, String> entry : Strings.pointerNames(info.relevantFields()).entrySet()) { PointerKey key = entry.getKey(); String name = entry.getValue(); if (key instanceof ArrayContentsKey) { TypeReference declType = ((ArrayContentsKey)key).getInstanceKey().getConcreteType().getReference(); IRType valType = IRType.convert(declType.getArrayElementType()); Expression dom = constants.valueOf(((ArrayContentsKey)key).getInstanceKey()); fields.put(key, ArrayExpression.array(name, card, dom, constants.intInterpreter(), constants.interpreter(valType))); } else if (key instanceof InstanceFieldKey) { InstanceFieldKey instanceField = (InstanceFieldKey) key; IRType valType = IRType.convert(instanceField.getField().getFieldTypeReference()); fields.put(key, FieldExpression.field(name, constants.valueOf(instanceField.getInstanceKey()), constants.interpreter(valType))); } else if (key instanceof StaticFieldKey) { StaticFieldKey staticField = (StaticFieldKey) key; IRType valType = IRType.convert(staticField.getField().getFieldTypeReference()); fields.put(key, FieldExpression.field(name,null,constants.interpreter(valType))); }else if (key instanceof ArrayLengthKey) { fields.put(key, FieldExpression.field(name, constants.valueOf(((ArrayLengthKey)key).getInstanceKey()), constants.intInterpreter())); } else if (key instanceof EnclosingObjectReferenceKey) { fields.put(key, FieldExpression.field(name, constants.valueOf(((EnclosingObjectReferenceKey)key).getInstanceKey()), constants.objInterpreter())); } else { throw new AssertionError("unrecognized field pointer key: " + key); } } this.arguments = new LinkedHashMap<CGNode, Relation[]>(); for(CGNode node : info.threads()) { WalaCGNodeInformation nodeInfo = info.cgNodeInformation(node); Relation[] args = new Relation[node.getMethod().getNumberOfParameters()]; for(int j = 0; j < args.length; j++) { Interpreter<?> argInterpreter = constants.interpreter(nodeInfo.typeOf(j+1)); args[j] = Relation.nary(node.getMethod().getName()+"_arg"+j, argInterpreter.defaultObj().arity()); } arguments.put(node, args); } // System.out.println(toString()); } /** * Returns this.info. * @return this.info */ public WalaInformation info() { return info; } /** * Returns this.options. * @return this.options */ public Options options() { return options; } /** * Returns the (open world) arugments for the given entry-point node. * @requires some i: [0..this.info.threads()) | this.info.entry(i) = node * @return open world arugments for the given entry-point node */ public Object[] arguments(CGNode node) { final Relation[] rawArgs = arguments.get(node); final Object[] args = new Object[rawArgs.length]; final WalaCGNodeInformation nodeInfo = info.cgNodeInformation(node); for(int i = 0; i < rawArgs.length; i++) { Interpreter<?> argInterpreter = constants.interpreter(nodeInfo.typeOf(i+1)); args[i] = argInterpreter.fromObj(rawArgs[i]); } return args; } /** * Returns an empty PhiExpression of the given type. * @return { phi: PhiExpression | no phi.phis and * [[ phi.phis[Formula] ]] in [[ type ]] } */ @SuppressWarnings("unchecked") public <T> PhiExpression<T> valuePhi(IRType type) { return PhiExpression.valuePhi((Interpreter<T>)constants.interpreter(type)); } /** * Returns a PhiExpression initialized with the given * guard / heap expression pair. All subsequent calls to * {@linkplain #add(Formula, Object)} on the returned * phi expression must be passed a HeapExpression with * the same <tt>walaField</tt> as the given heap expression. * @return a PhiExpression initialized with the given * guard / heap value pair */ public <T> PhiExpression<HeapExpression<T>> heapPhi(Formula guard, HeapExpression<T> heapExpr) { return PhiExpression.heapPhi(guard, heapExpr); } /** * Returns the constant factory used by this expression factory * to manage constant-valued Wala expressions (primitives and instances). * @return this.constants */ public ConstantFactory constants() { return constants; } /** * Returns a field or array expression that models the initial heap value * represented by the given key. The returned object * is an instance of ArrayExpression if the given key is * an ArrayContentsKey, otherwise it is an instance of FieldExpression. * The parameter type of the returned expression's contents is determined by the * type of the key's points-to set (e.g. if key is an InstanceFieldKey * for a boolean field, then the returned expression is an instance of * FieldExpression<Formula>, etc.). * @requires key in this.info.relevantFields() * @return this.fields[key] */ @SuppressWarnings("unchecked") public final <H extends HeapExpression<?>> H initValueOf(PointerKey key) { return (H)fields.get(key); } /** * Returns a field expression that models the system hashcode function. * @return a field expression that models the system hashcode function. */ public final FieldExpression<IntExpression> systemHashCode() { return hash; } /*------------------ REP INVARIANTS ------------------ */ /** * Returns a formula that expresses the representation * invariants over all Kodkod relations that make up * the expressions generated by this factory. * @return a formula that expresses the representation * invariants over all Kodkod relations that make up * the expressions generated in this factory */ public final Formula invariants() { final List<Formula> formulas = new ArrayList<Formula>(); final Map<ArrayContentsKey,FieldExpression<IntExpression>> lengths = lengths(); // System.out.println("lengths: " + lengths); for(Map.Entry<PointerKey, HeapExpression<?>> entry : fields.entrySet()) { final HeapExpression<?> heapExpr = entry.getValue(); if (heapExpr.isArray()) { final InstanceKey dom = ((ArrayContentsKey)entry.getKey()).getInstanceKey(); // only add rep invariants for non-empty arrays in the open world if (info.openWorldType(dom)) { final ArrayExpression<?> array = (ArrayExpression<?>) heapExpr; if (array.cardinality()>0) { final FieldExpression<IntExpression> length = lengths.get(entry.getKey()); formulas.add(invariants(constants.openValuesOf(dom), array, length)); } } } else { final FieldExpression<?> field = (FieldExpression<?>)heapExpr; formulas.add(invariants(field)); } } // argument constraints -- constraint cardinality of refs and bools to one for(Map.Entry<CGNode, Relation[]> entry : arguments.entrySet()) { WalaCGNodeInformation nodeInfo = info.cgNodeInformation(entry.getKey()); Relation[] args = entry.getValue(); for(int i = 0; i < args.length; i++) { if (constants.interpreter(nodeInfo.typeOf(i+1)).singletonEncoding()) { formulas.add(args[i].one()); } } } return Formula.and(formulas); } /** * Returns representation invariants for the given open-world instances of the given array expression and * its corresponding length field * @requires some [[openWorldValues]] and array.cardinality() > 0 * @requires [[openWorldValues]] in [[array.instances()]] * @requires array.getInstanceKey() = length.getInstanceKey(); * @return representation invariants for the given open-world instances of the given array expression and * its corresponding length field */ private Formula invariants(Expression openWorldValues, ArrayExpression<?> array, FieldExpression<IntExpression> length) { final int card = array.cardinality(); final List<Formula> aInvs = new ArrayList<Formula>(2*card+1); final Variable a = Variable.unary("a"+System.identityHashCode(array)); // index constraints for(int i = 0, max = card-1; i < max; i++) { final IntExpression iprev = array.index(a, i); final IntExpression inext = array.index(a, i+1); final Expression vprev = a.join(array.value(i)); final Expression vnext = a.join(array.value(i+1)); aInvs.add(iprev.lt(inext).or(iprev.eq(inext).and(vprev.eq(vnext)))); } final IntExpression aLength = length.read(a); final IntExpression aLast = array.index(a, card-1); aInvs.add((aLast.lt(aLength)).or (aLast.eq(aLength).and(aLength.eq(IntConstant.constant(0))))); // value constraints (function if values are references or booleans) if (array.valueInterpreter().singletonEncoding()) { for(int i = 0; i < card; i++) { aInvs.add(a.join(array.value(i)).one()); } } return Formula.and(aInvs).forAll(a.oneOf(openWorldValues)); } /** * @return representation invariants for the given field */ private Formula invariants(FieldExpression<?> field) { if (field.valueInterpreter().singletonEncoding()) { if (field.isStatic()) { return field.field().one(); } else { Variable v = Variable.unary("v"+System.identityHashCode(field)); return v.join(field.field()).one().forAll(v.oneOf(field.instances().difference(constants.nil()))); } } return Formula.TRUE; } /** @return map from each array instance key in this.fields to its corresponding length field */ @SuppressWarnings("unchecked") private Map<ArrayContentsKey, FieldExpression<IntExpression>> lengths() { final Map<ArrayContentsKey, FieldExpression<IntExpression>> ret = new LinkedHashMap<ArrayContentsKey, FieldExpression<IntExpression>>(); final Map<InstanceKey,ArrayContentsKey> m = new LinkedHashMap<InstanceKey, ArrayContentsKey>(); for(PointerKey key : fields.keySet()) { if (key instanceof ArrayContentsKey) { m.put(((ArrayContentsKey)key).getInstanceKey(), (ArrayContentsKey)key); } } for(PointerKey key : fields.keySet()) { if (key instanceof ArrayLengthKey) { ret.put(m.get(((ArrayLengthKey)key).getInstanceKey()), (FieldExpression<IntExpression>)fields.get(key)); } } return ret; } /** * Returns an ordered set of atom objects needed to represent all primitives * generated by this factory. In particular, the first two atoms represent * the values "true" and "false", * the next <tt>this.options.bitsForIntegers()</tt> atoms are Integer objects * representing the powers two (in the increasing * order of absolute values) needed to represent all <tt>options.bitsForIntegers()</tt>-bit integers * in two's complement; the next atom represents the null value, and the remaining * atoms uniquely represent the instances of this.info.relevantClasses(). * All atoms except Integers are Kodkod relations. * @return an ordered set of atom objects needed to represent all primitives * generated by this factory */ public Set<?> atoms() { return constants.atoms(); } /*------------------ BOUNDS ------------------ */ /** * Updates the given Bounds instance with * upper/lower bounds for the relations that make up * the expressions generated by this factory and this.constants. The bounds * are constructed using the bitwidth, set cardinality, etc., * values given by this.options and this.info. * @requires this.atoms() in bounds.universe.atoms[int] * @effects updates the given Bounds instance with * upper/lower bounds for the relations that make up * the expressions generated by this factory and this.constants */ public void boundAll(Bounds bounds) { final TupleFactory f = bounds.universe().factory(); constants.boundAll(bounds); final EnumMap<IRType,TupleSet> primitives = new EnumMap<IRType, TupleSet>(IRType.class); primitives.put(BOOLEAN, constants.constantAtoms(f, BOOLEAN)); primitives.put(INTEGER, constants.constantAtoms(f, INTEGER)); primitives.put(REAL, constants.constantAtoms(f, REAL)); primitives.put(OBJECT, constants.constantAtoms(f, OBJECT)); final EnumMap<IRType,TupleSet> defaults = new EnumMap<IRType, TupleSet>(IRType.class); defaults.put(BOOLEAN, constants.defaultAtoms(f, BOOLEAN)); defaults.put(INTEGER, constants.defaultAtoms(f, INTEGER)); defaults.put(REAL, constants.defaultAtoms(f, REAL)); defaults.put(OBJECT, constants.defaultAtoms(f, OBJECT)); // bound arguments for(Map.Entry<CGNode, Relation[]> entry : arguments.entrySet()) { WalaCGNodeInformation nodeInfo = info.cgNodeInformation(entry.getKey()); Relation[] args = entry.getValue(); for(int i = 0; i < args.length; i++) { bounds.bound(args[i], range(f, nodeInfo, i+1, primitives)); } } // bound system hashcode bounds.bound((Relation)hash.field(), constants.instanceAtoms(f, info.relevantClasses()).product(primitives.get(INTEGER))); // bound arrays and fields final TupleSet nats = naturals(primitives.get(INTEGER)); for(Map.Entry<PointerKey, ?> entry : fields.entrySet()) { final PointerKey key = entry.getKey(); if (key instanceof ArrayContentsKey) { final InstanceKey domKey = ((ArrayContentsKey) key).getInstanceKey(); final ArrayExpression<?> array = (ArrayExpression<?>)entry.getValue(); final IRType valType = array.valueInterpreter().type(); final TupleSet closedDom = constants.closedInstanceAtoms(f, domKey); final TupleSet openDom = constants.openInstanceAtoms(f, domKey); final TupleSet uidx = union(closedDom, openDom).product(nats); final TupleSet lval = closedDom.product(defaults.get(valType)); final TupleSet uval = union(lval, openDom.product(range(f,key,valType,primitives))); for(int i = 0, max = array.cardinality(); i < max; i++) { bounds.bound((Relation)array.index(i), uidx); bounds.bound((Relation)array.value(i), lval, uval); } } else { final FieldExpression<?> field = (FieldExpression<?>)entry.getValue(); if (field.isStatic()) { // static fields are set to default values bounds.bound((Relation)field.field(), defaults.get(field.valueInterpreter().type())); } else { final InstanceKey domKey = ((InstanceFieldPointerKey) key).getInstanceKey(); if (key instanceof EnclosingObjectReferenceKey) { final TupleSet dom = constants.instanceAtoms(f, domKey); final TupleSet uval = constants.instanceAtoms(f, info.pointsTo(key)); bounds.bound((Relation)field.field(), dom.product(uval)); } else if (key instanceof ArrayLengthKey) { // don't need to bound closed-world instances since they map to 0 (the empty set) final TupleSet openDom = constants.openInstanceAtoms(f, domKey); bounds.bound((Relation)field.field(), openDom.product(nats)); } else { final IRType valType = field.valueInterpreter().type(); final TupleSet closedDom = constants.closedInstanceAtoms(f, domKey); final TupleSet lval = closedDom.product(defaults.get(valType)); final TupleSet openDom = constants.openInstanceAtoms(f, domKey); final TupleSet uval = union(lval, openDom.product(range(f, key, valType, primitives))); bounds.bound((Relation)field.field(), lval, uval); } } } } // System.out.println("BOUNDS: " + bounds); } /** * Returns the non-negative subset of the given set of integers. * @requires ints = constants.constantAtoms(INTEGER) * @return non-negative subset of the given set of integers. */ private TupleSet naturals(TupleSet ints) { final TupleSet nats = ints.clone(); nats.remove(ints.universe().factory().tuple(-(1<<(options.kodkodOptions().bitwidth()-1)))); return nats; } /** * @requires this.info.pointsTo(key) in this.info.relevantClasses() * @return upper bound on the points-to set of the given key (includes null) **/ private TupleSet range(TupleFactory factory, PointerKey key, IRType type, EnumMap<IRType, TupleSet> primitives) { if (type==OBJECT) { final TupleSet instances = constants.instanceAtoms(factory, info.pointsTo(key)); instances.addAll(primitives.get(OBJECT)); return instances; } else { return primitives.get(type); } } /** * @return upper bound on the points-to set of the given value number (includes null). */ private TupleSet range(TupleFactory factory, WalaCGNodeInformation nodeInfo, int valueNumber, EnumMap<IRType, TupleSet> primitives) { return range(factory, nodeInfo.pointerKeyFor(valueNumber), nodeInfo.typeOf(valueNumber), primitives); } /** * Returns a new TupleSet that is the union of the specified tuplesets. * @requires sets.length > 0 and all t: sets[int] | t.arity = sets[0].arity * @return a new TupleSet that is the union of the specified tuplesets. */ private TupleSet union(TupleSet...sets) { final TupleSet ret = sets[0].clone(); for(int i = 1; i < sets.length; i++) { ret.addAll(sets[i]); } return ret; } /** * Returns a string representation of this expression factory. * @return a string representation of this expression factory */ public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("constants:\n"); buf.append(constants); buf.append("\n"); buf.append("fields:\n"); for(Map.Entry<PointerKey,HeapExpression<?>> e : fields.entrySet()) { buf.append(" "); buf.append(e.getKey()); buf.append(" :: "); HeapExpression<?> val = e.getValue(); if (val.isArray()) { ArrayExpression<?> array = (ArrayExpression<?>) val; int card = array.cardinality(); buf.append("{"); for(int i = 0; i < card; i++) { buf.append(" ("); buf.append(array.index(i)); buf.append(", "); buf.append(array.value(i)); buf.append(")"); } buf.append(" }"); } else { buf.append(((FieldExpression<?>)val).field()); } buf.append("\n"); } buf.append("arguments:\n"); for(Map.Entry<CGNode,Relation[]> e : arguments.entrySet()) { buf.append(" "); buf.append(e.getKey().getMethod().getSignature()); buf.append(" :: {"); for(Relation r : e.getValue()) { buf.append(" "); buf.append(r); } buf.append(" }\n"); } return buf.toString(); } }