/****************************************************************************** * 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 java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.memsat.Options; import com.ibm.wala.memsat.frontEnd.IRType; 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.engine.Evaluator; import kodkod.instance.Bounds; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; import kodkod.util.collections.Containers; /** * A factory for generating Kodkod Expressions that * represent Wala primitives and instances. These * values are constant in the sense that their relational values * are known a priori and do not have to be solved for. (In * other words, the relations that make up the representations of * Wala primitives and instances are Kodkod constants.) * * @specfield info: WalaInformation * @specfield options: Options // options used for determining bitwidth for int representation, etc. * @specfield primitives: (boolean ->one Formula) + * (int ->one IntExpression) + (float ->one RealExpression) + (null ->one Expression) + * (String ->one Expression) * @specfield interpreters: INTEGER ->one Interpreter<IntExpression> + * REAL ->one Interpreter<RealExpression> + BOOLEAN ->one Interpreter<Formula> + * OBJECT ->one Interpreter<Expression> * @specfield instances: info.relevantClasses() + String + null ->one Expression * @author Emina Torlak */ public final class ConstantFactory { static final Relation TRUE = Relation.unary("true"); static final Relation FALSE = Relation.unary("false"); static final Relation NULL = Relation.unary("null"); private final Interpreter<Formula> bools; private final Interpreter<Expression> objects; private final Interpreter<IntExpression> ints; private final Interpreter<RealExpression> reals; private final int bitwidth; private final Map<InstanceKey, Relation> sets; private final Map<InstanceKey, Relation[]> instances; private final Set<InstanceKey> openWorldKeys = new HashSet<InstanceKey>(); private final int openWorldScopeSize; /** * Constructs a primitive factory for the given options. * @effects this.options' = options */ ConstantFactory(WalaInformation info, Options options) { this.bools = bools(); this.objects = objects(); this.bitwidth = options.kodkodOptions().bitwidth(); this.ints = ints(); this.reals = RealExpression.interpreter(ints); this.sets = new LinkedHashMap<InstanceKey, Relation>(); this.instances = new LinkedHashMap<InstanceKey, Relation[]>(); for(Map.Entry<InstanceKey, String> entry : Strings.instanceNames(info.relevantClasses()).entrySet()) { InstanceKey key = entry.getKey(); String name = entry.getValue(); sets.put(key, Relation.unary(name)); Relation[] rels = new Relation[info.cardinality(key)]; for(int i = 0; i < rels.length; i++) { rels[i] = Relation.unary(name+i); } instances.put(key, rels); if (info.openWorldType(key)) { openWorldKeys.add(key); } } openWorldScopeSize = options.openWorldScopeSize(); } /** * 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(). * @return an ordered set of atom objects needed to represent all primitives * generated by this factory */ Set<?> atoms() { final Set<Object> atoms = new LinkedHashSet<Object>(); atoms.add(TRUE); atoms.add(FALSE); final int msb = bitwidth-1; for(int i = 0, pos = msb; i < pos; i++) { atoms.add(Integer.valueOf(1<<i)); } atoms.add(Integer.valueOf(-(1<<msb))); atoms.add(NULL); for(Map.Entry<InstanceKey,Relation[]> entry : instances.entrySet()) { for(Relation rel : entry.getValue()) { atoms.add(rel); } } return atoms; } /** * Returns a tupleset containing all constants of the given type. * In particular, for type=OBJECT, singleton set containing the null * atom is returned; for type=INTEGER, integer atoms are returned; * for type=BOOLEAN, true and false atoms are returned; and for type=REAL, * all real number representable using available integers are returned. * @requires this.atoms() in factory.universe.atoms[int] * @return tupleset containing all constants atoms of the given type. */ public TupleSet constantAtoms(TupleFactory factory, IRType type) { switch(type) { case BOOLEAN : return factory.setOf(TRUE, FALSE); case OBJECT : return factory.setOf(NULL); case INTEGER : case REAL : final TupleSet ints = factory.noneOf(1); final int msb = bitwidth-1; for(int i = 0; i < msb; i++) { ints.add(factory.tuple(Integer.valueOf(1<<i))); } ints.add(factory.tuple(Integer.valueOf(-(1<<msb)))); return type==INTEGER ? ints : RealExpression.allReals(ints); default : throw new AssertionError("unreachable code"); } } /** * Returns the default atoms for the given type (FALSE for booleans, * NULL for objects, and 0 for integers and reals). * @requires this.atoms() in factory.universe.atoms[int] * @return default atoms for the given type (FALSE for booleans, * NULL for objects, and 0 for integers and reals). */ TupleSet defaultAtoms(TupleFactory factory, IRType type) { switch(type) { case BOOLEAN : return factory.setOf(FALSE); case OBJECT : return factory.setOf(NULL); case INTEGER : case REAL : return factory.noneOf(1); default : throw new AssertionError("unreachable code"); } } /** * Returns all relations that represent all instances. * @return this.instances[univ] */ Collection<? extends Expression> setExpressions() { return sets.values(); } /** * Returns a tupleset containing all instances (atoms) of the given type partition. * @requires this.atoms() in factory.universe.atoms[int] * @return a tupleset containing all instances (atoms) of the given type partition. */ TupleSet instanceAtoms(TupleFactory factory, InstanceKey typeKey) { final TupleSet s = factory.noneOf(1); for(Relation instance : instances.get(typeKey)) { s.add(factory.tuple(instance)); } return s; } /** * Returns a tupleset containing all instances (atoms) of the given type partition * that are in the open world. * @requires this.atoms() in factory.universe.atoms[int] * @return a tupleset containing all instances (atoms) of the given type partition that are in the open world. */ TupleSet openInstanceAtoms(TupleFactory factory, InstanceKey typeKey) { if (openWorldKeys.contains(typeKey)) { final TupleSet s = factory.noneOf(1); Relation[] atoms = instances.get(typeKey); for(int i = 0; i < openWorldScopeSize; i++) { s.add(factory.tuple( atoms[ atoms.length - i - 1 ] )); } return s; } else { return factory.noneOf(1); } } /** * Returns a tupleset containing all instances (atoms) of the given type partition * that are in the closed world. * @requires this.atoms() in factory.universe.atoms[int] * @return a tupleset containing all instances (atoms) of the given type partition that are in the closed world. */ TupleSet closedInstanceAtoms(TupleFactory factory, InstanceKey typeKey) { if (openWorldKeys.contains(typeKey)) { final TupleSet s = factory.noneOf(1); Relation[] atoms = instances.get(typeKey); for(int i = 0; i < atoms.length - openWorldScopeSize; i++) { s.add(factory.tuple( atoms[i] )); } return s; } else { return instanceAtoms(factory, typeKey); } } /** * Returns a tupleset containing all instances (atoms) of the given type partitions. * @requires this.atoms() in factory.universe.atoms[int] * @requires types in this.instances * @return a tupleset containing all instances (atoms) of the given type partitions. */ public TupleSet instanceAtoms(TupleFactory factory, Set<InstanceKey> typeKeys) { final TupleSet s = factory.noneOf(1); for(InstanceKey set : typeKeys) { if (instances.containsKey(set)) { for(Relation instance : instances.get(set)) { s.add(factory.tuple(instance)); } } } return s; } /** * Returns an expression that evaluates to all instances of the given type partition * that are in the closed world. * @return an expression that evaluates to all instances of the given type partition * that are in the closed world. */ Expression closedValuesOf(InstanceKey typeKey) { if (openWorldKeys.contains(typeKey)) { final Relation[] values = instances.get(typeKey); final int closed = values.length - openWorldScopeSize; final Expression[] closedValues = new Expression[closed]; System.arraycopy(values, 0, closedValues, 0, closed); return Expression.union(closedValues); } else { return valueOf(typeKey); } } /** * Returns an expression that evaluates to all instances of the given type partition * that are in the open world. * @return an expression that evaluates to all instances of the given type partition * that are in the open world. */ Expression openValuesOf(InstanceKey typeKey) { if (openWorldKeys.contains(typeKey)) { final Relation[] values = instances.get(typeKey); final Expression[] openValues = new Expression[openWorldScopeSize]; System.arraycopy(values, values.length-openWorldScopeSize, openValues, 0, openWorldScopeSize); return Expression.union(openValues); } else { return Expression.NONE; } } /** * Adds bounds for all relations and integers generated by this factory * to the given bounds. * @requires atoms() in bounds.universe().atoms[int] * @effects adds bounds for all relations and integers to the given bounds */ void boundAll(Bounds bounds) { final TupleFactory f = bounds.universe().factory(); // bound primitives bounds.boundExactly(TRUE, f.setOf(TRUE)); bounds.boundExactly(FALSE, f.setOf(FALSE)); bounds.boundExactly(NULL, f.setOf(NULL)); // bound ints final int msb = bitwidth-1; for(int i = 0; i < msb; i++) { bounds.boundExactly(1<<i, f.setOf(Integer.valueOf(1<<i))); } bounds.boundExactly(-(1<<msb), f.setOf(Integer.valueOf(-(1<<msb)))); // bound instances and instantiables for(InstanceKey key : sets.keySet()) { Relation all = sets.get(key); TupleSet val = f.setOf(NULL); for(Relation rel : instances.get(key)) { val.add(f.tuple(rel)); bounds.boundExactly(rel, f.setOf(rel)); } bounds.boundExactly(all, val); } } /** * Returns an expression that models <tt>null</tt>. * @return this.primitives[null] */ public final Expression nil() { return NULL; } /** * Returns an iterator over the expressions that model * the individual instances in the * set of instances represented by the given instance key. * @requires key in this.info.relevantClasses() * @return an iterator over the expressions that model * the individual instances in the * set of instances represented by the given instance key. */ public final Iterator<Expression> instances(InstanceKey key) { return Containers.iterate(instances.get(key)); } /** * Returns an expression that models the set of all instances of the given type. * @return an expression that models the set of all instances of the given type. */ public final Expression valueOf(TypeReference type) { final List<Expression> parts = new ArrayList<Expression>(); for(InstanceKey key : sets.keySet()) { IClassHierarchy cha = key.getConcreteType().getClassHierarchy(); if (cha.isSubclassOf(key.getConcreteType(), cha.lookupClass(type))) { parts.add(sets.get(key)); } } return parts.isEmpty() ? Expression.NONE : Expression.union(parts); } /** * Returns an expression that models the set of instances represented by the given instance key. * @requires key in this.info.relevantClasses() * @return this.instances[key] */ public final Expression valueOf(InstanceKey key) { return sets.get(key); } /** * Returns an expression that models the given string constant. * @return this.primitives[s] */ public final Expression valueOf(String s) { return NULL; } /** * Returns a formula that models the given boolean constant. * @return this.primitives[b] */ public final Formula valueOf(boolean b) { return b ? Formula.TRUE : Formula.FALSE; } /** * Returns an integer expression that models the given integer constant. * @return this.primitives[i] */ public final IntExpression valueOf(int i) { return IntConstant.constant(i); } /** * Returns an integer expression that models the given integer constant. * @return this.primitives[i] */ public final IntExpression valueOf(long i) { assert Integer.MIN_VALUE <= i; assert i <= Integer.MAX_VALUE; return IntConstant.constant((int)i); } /** * Returns a real expression that models the given float constant. * @return this.primitives[f] */ public final RealExpression valueOf(float f) { return new RealExpression(f); } /** * 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>)interpreter(type)); } /** * Returns the default value for the given type. * @return default value for the given type. */ @SuppressWarnings("unchecked") public <T> T defaultValue(IRType type) { return ((Interpreter<T>)interpreter(type)).defaultValue(); } /** * Returns the interpreter for the given type. The * type of the returned interpreter is Interpreter<Expression> for * OBJECT, Interpreter<Formula> for BOOLEAN, NumberInterpreter<IntExpression> for * INTEGER, and NumberInterpreter<RealExpression> for REAL. * @return this.interpreters[type] */ @SuppressWarnings("unchecked") public final <T> Interpreter<T> interpreter(IRType type) { switch(type) { case OBJECT : return (Interpreter<T>)objects; case BOOLEAN : return (Interpreter<T>)bools; case INTEGER : return (Interpreter<T>)ints; case REAL : return (Interpreter<T>)reals; default : throw new IllegalArgumentException("Unknown type: " + type); } } /** * Returns this factory's interpreter for integers. * @return this.interpreters[INTEGER] */ public final Interpreter<IntExpression> intInterpreter() { return ints; } /** * Returns this factory's interpreter for objects. * @return this.interpreters[OBJECT] */ public final Interpreter<Expression> objInterpreter() { return objects; } /** * Returns this factory's interpreter for reals. * @return this.interpreters[REAL] */ public final Interpreter<RealExpression> realInterpreter() { return reals; } /** * Returns this factory's interpreter for booleans. * @return this.interpreters[BOOLEAN] */ public final Interpreter<Formula> boolInterpreter() { return bools; } /** * Returns a string representation of this value factory. * @return a string representation of this value factory */ public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("instances:\n"); for(Map.Entry<InstanceKey,Relation[]> e : instances.entrySet()) { buf.append(" "); buf.append(e.getKey()); buf.append(" :: {"); for(Relation r : e.getValue()) { buf.append(" "); buf.append(r); } buf.append(" }\n"); } return buf.toString(); } /*------------------ STATIC METHODS ------------------ */ /** @return an interpreter for boolean values. */ private static Interpreter<Formula> bools() { return new Interpreter<Formula>() { public IRType type() { return BOOLEAN; } public Expression toObj(Formula f) { return f.thenElse(TRUE, FALSE); } public Formula fromObj(Expression e) { return e.eq(TRUE); } public Expression defaultObj() { return FALSE; } public Formula guardedValue(Formula guard, Formula value) { return guard.and(value); } public Formula defaultValue() { return Formula.FALSE; } public Boolean evaluate(Formula value, Evaluator eval) { return eval.evaluate(value); } public boolean singletonEncoding() { return true; } Formula phi(Collection<? extends Formula> phis) { assert !phis.isEmpty(); return Formula.or(phis); } }; } /** @return an interpreter for integer values. */ private static Interpreter<IntExpression> ints() { final IntConstant zero = IntConstant.constant(0); return new Interpreter<IntExpression>() { public IRType type() { return INTEGER; } public Expression toObj(IntExpression intExpr) { return intExpr.toBitset(); } public IntExpression fromObj(Expression e) { return e.sum(); } public Expression defaultObj() { return Expression.NONE; } public IntExpression guardedValue(Formula guard, IntExpression value) { return guard.thenElse(value, zero); } public IntExpression defaultValue() { return zero; } public Integer evaluate(IntExpression value, Evaluator eval) { return eval.evaluate(value); } public boolean singletonEncoding() { return false; } IntExpression phi(Collection<? extends IntExpression> phis) { assert !phis.isEmpty(); return IntExpression.or(phis); } }; } /** @return an interpreter for reference values. */ private static Interpreter<Expression> objects() { return new Interpreter<Expression>() { public IRType type() { return OBJECT; } public Expression toObj(Expression t) { return t; } public Expression fromObj(Expression e) { return e; } public Expression defaultObj() { return NULL; } public Expression guardedValue(Formula guard, Expression value) { return guard.thenElse(value, Expression.NONE); } public Expression defaultValue() { return NULL; } public Object evaluate(Expression value, Evaluator eval) { final TupleSet v = eval.evaluate(value); assert v.size() <= 1; assert v.arity() == 1; return v.isEmpty() ? "()" : v.iterator().next().atom(0); } public boolean singletonEncoding() { return true; } Expression phi(Collection<? extends Expression> phis) { assert !phis.isEmpty(); return Expression.union(phis); } }; } }