support for full CPA, when desired.

This commit is contained in:
Julian Dolby 2016-09-15 21:32:35 -04:00
parent cfbef4fe47
commit 8b2ab22335
12 changed files with 292 additions and 76 deletions

View File

@ -20,6 +20,7 @@ import com.ibm.wala.cast.ipa.callgraph.GlobalObjectKey;
import com.ibm.wala.cast.ir.ssa.AstGlobalRead;
import com.ibm.wala.cast.ir.ssa.AstGlobalWrite;
import com.ibm.wala.cast.ir.ssa.AstIsDefinedInstruction;
import com.ibm.wala.cast.ir.ssa.CAstUnaryOp;
import com.ibm.wala.cast.ir.ssa.EachElementHasNextInstruction;
import com.ibm.wala.cast.js.analysis.typeInference.JSTypeInference;
import com.ibm.wala.cast.js.ipa.callgraph.JSSSAPropagationCallGraphBuilder.JSPointerAnalysisImpl.JSImplicitPointsToSetVisitor;
@ -78,9 +79,12 @@ import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.CancelRuntimeException;
import com.ibm.wala.util.MonitorUtil;
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetAction;
import com.ibm.wala.util.intset.IntSetUtil;
import com.ibm.wala.util.intset.MutableIntSet;
import com.ibm.wala.util.intset.MutableMapping;
import com.ibm.wala.util.intset.MutableSparseIntSet;
import com.ibm.wala.util.intset.OrdinalSet;
@ -301,7 +305,7 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
protected InterestingVisitor makeInterestingVisitor(CGNode node, int vn) {
return new JSInterestingVisitor(vn);
}
// ///////////////////////////////////////////////////////////////////////////
//
// specialized pointer analysis
@ -444,6 +448,8 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
public void visitUnaryOp(SSAUnaryOpInstruction inst) {
if (inst.getOpcode() == IUnaryOpInstruction.Operator.NEG) {
addLvalTypeKeyConstraint(inst, JavaScriptTypes.Boolean);
} else if (inst.getOpcode() == CAstUnaryOp.MINUS) {
addLvalTypeKeyConstraint(inst, JavaScriptTypes.Number);
}
}
@ -594,10 +600,12 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
private AbstractFieldPointerKey getProperty() { return fieldKey; }
private CGNode getNode() { return node; }
private MutableIntSet previous = IntSetUtil.make();
@Override
public byte evaluate(PointsToSetVariable lhs, PointsToSetVariable ptrs) {
if (ptrs.getValue() != null) {
ptrs.getValue().foreach(new IntSetAction() {
ptrs.getValue().foreachExcluding(previous, new IntSetAction() {
@Override
public void act(int x) {
final InstanceKey functionObj = system.getInstanceKey(x);
@ -615,6 +623,7 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
});
}
});
previous.addAll(ptrs.getValue());
}
return NOT_CHANGED;
}
@ -668,6 +677,8 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
}
} else {
class ReceiverForDispatchOp extends UnaryOperator<PointsToSetVariable> {
MutableIntSet previous = IntSetUtil.make();
private JavaScriptInvoke getInstruction() {
return instruction;
}
@ -675,7 +686,7 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
@Override
public byte evaluate(PointsToSetVariable lhs, PointsToSetVariable rhs) {
if (rhs.getValue() != null) {
rhs.getValue().foreach(new IntSetAction() {
rhs.getValue().foreachExcluding(previous, new IntSetAction() {
@Override
public void act(int x) {
try {
@ -687,6 +698,7 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
handleJavascriptDispatch(instruction, ik);
}
});
previous.addAll(rhs.getValue());
}
return NOT_CHANGED;
}
@ -848,6 +860,25 @@ public class JSSSAPropagationCallGraphBuilder extends AstSSAPropagationCallGraph
}
}
if (iks1 == null || iks1.length == 0 || iks2 == null || iks2.length == 0) {
if (iks1 != null) {
for(InstanceKey ik : iks1) {
if (addKey(ik)) {
changed = CHANGED;
}
}
}
if (iks2 != null) {
for(InstanceKey ik : iks2) {
if (addKey(ik)) {
changed = CHANGED;
}
}
}
System.err.println(instruction);
}
return changed;
}

View File

@ -148,8 +148,8 @@ public class JavaScriptFunctionApplyContextInterpreter extends AstContextInsensi
// the commented line is correct, but it doesn't work because
// of our broken handling of int constants as properties.
// TODO fix property handling, and then fix this
// S.addConstant(constVN, new ConstantValue(Integer.toString(i-1)));
S.addConstant(constVN, new ConstantValue(i-1));
S.addConstant(constVN, new ConstantValue(Integer.toString(i-1)));
//S.addConstant(constVN, new ConstantValue(i-1));
int propertyReadResult = curValNum++;
// 4 is position of arguments array
S.addStatement(insts.PropertyRead(S.getNumberOfStatements(), propertyReadResult, 4, constVN));

View File

@ -25,6 +25,7 @@ import com.ibm.wala.ipa.callgraph.propagation.cfa.OneLevelSiteContextSelector;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetUtil;
import com.ibm.wala.util.intset.MutableIntSet;
/**
*
@ -57,11 +58,11 @@ public class JavaScriptFunctionApplyContextSelector implements ContextSelector {
// 0 for function (synthetic apply), 1 for this (function being invoked), 2
// for this arg of function being invoked,
// 3 for arguments array
if (caller.getIR().getCalls(site)[0].getNumberOfUses() >= 4) {
return IntSetUtil.make(new int[] { 3 }).union(base.getRelevantParameters(caller, site));
} else {
return base.getRelevantParameters(caller, site);
MutableIntSet params = IntSetUtil.make();
for(int i = 0; i < 4 && i < caller.getIR().getCalls(site)[0].getNumberOfUses(); i++) {
params.add(i);
}
return params.union(base.getRelevantParameters(caller, site));
}
public static class ApplyContext implements Context {

View File

@ -61,7 +61,7 @@ class ClassNewInstanceContextSelector implements ContextSelector {
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
if (ClassNewInstanceContextInterpreter.CLASS_NEW_INSTANCE_REF.equals(site.getDeclaredTarget())) {
return thisParameter;
} else {
return EmptyIntSet.instance;

View File

@ -51,7 +51,7 @@ public class GetClassContextSelector implements ContextSelector {
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
if (site.getDeclaredTarget().equals(GET_CLASS)) {
return thisParameter;
} else {
return EmptyIntSet.instance;

View File

@ -161,7 +161,7 @@ public class GetMethodContextSelector implements ContextSelector {
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
if (UNDERSTOOD_METHOD_REFS.contains(site.getDeclaredTarget())) {
return thisParameter;
} else {
return EmptyIntSet.instance;

View File

@ -90,7 +90,7 @@ class JavaLangClassContextSelector implements ContextSelector {
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
if (UNDERSTOOD_METHOD_REFS.contains(site.getDeclaredTarget())) {
return thisParameter;
} else {
return EmptyIntSet.instance;

View File

@ -107,7 +107,8 @@ class ReflectiveInvocationSelector implements ContextSelector {
*/
private boolean mayUnderstand(CGNode caller, CallSiteReference site, IMethod targetMethod, InstanceKey instance) {
if (instance instanceof ConstantKey) {
if (targetMethod.getReference().equals(ReflectiveInvocationInterpreter.METHOD_INVOKE) || isConstructorConstant(instance)
if (targetMethod.getReference().equals(ReflectiveInvocationInterpreter.METHOD_INVOKE) ||
isConstructorConstant(instance)
&& targetMethod.getReference().equals(ReflectiveInvocationInterpreter.CTOR_NEW_INSTANCE)) {
return true;
}
@ -129,7 +130,8 @@ class ReflectiveInvocationSelector implements ContextSelector {
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
if (site.isDispatch() || site.getDeclaredTarget().getNumberOfParameters() > 0) {
if (site.getDeclaredTarget().equals(ReflectiveInvocationInterpreter.METHOD_INVOKE) ||
site.getDeclaredTarget().equals(ReflectiveInvocationInterpreter.CTOR_NEW_INSTANCE)) {
return thisParameter;
} else {
return EmptyIntSet.instance;

View File

@ -44,9 +44,13 @@ public interface ContextKey {
public final int index;
public ParameterKey(int index) {
super();
this.index = index;
}
@Override
public String toString() {
return "P" + index;
}
}
/**

View File

@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2007 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.ipa.callgraph.propagation;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ipa.callgraph.ContextSelector;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetUtil;
import com.ibm.wala.util.intset.MutableIntSet;
public class CPAContextSelector implements ContextSelector {
private final ContextSelector base;
public CPAContextSelector(ContextSelector base) {
this.base = base;
}
public static class CPAContext extends SelectiveCPAContext {
public CPAContext(Context base, InstanceKey[] x) {
super(base, x);
}
}
@Override
public Context getCalleeTarget(CGNode caller, CallSiteReference site, IMethod callee, InstanceKey[] actualParameters) {
Context target = base.getCalleeTarget(caller, site, callee, actualParameters);
if (actualParameters != null && actualParameters.length > 0) {
return new CPAContext(target, actualParameters);
} else {
return target;
}
}
private static boolean dispatchIndex(CallSiteReference ref, int i) {
if (ref.isStatic()) {
return ! ref.getDeclaredTarget().getParameterType(i).isPrimitiveType();
} else {
return i==0 || ! ref.getDeclaredTarget().getParameterType(i-1).isPrimitiveType();
}
}
@Override
public IntSet getRelevantParameters(CGNode caller, CallSiteReference site) {
MutableIntSet s = IntSetUtil.make();
for(int i = 0; i < caller.getIR().getCalls(site)[0].getNumberOfUses(); i++) {
if (dispatchIndex(site, i)) {
s.add(i);
}
}
return s;
}
}

View File

@ -78,6 +78,7 @@ import com.ibm.wala.util.MonitorUtil;
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.UnimplementedError;
import com.ibm.wala.util.functions.VoidFunction;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntSet;
@ -232,6 +233,8 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
addNodeInstructionConstraints(node, monitor);
addNodeValueConstraints(node, monitor);
DefUse du = getCFAContextInterpreter().getDU(node);
addNodePassthruExceptionConstraints(node, ir, du);
// conservatively assume something changed
@ -264,6 +267,14 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
}
}
/**
* Hook for aubclasses to add pointer flow constraints based on values in a given node
* @throws CancelException
*/
protected void addNodeValueConstraints(CGNode node, IProgressMonitor monitor) throws CancelException {
}
/**
* Add constraints for a particular basic block.
* @throws CancelException
@ -497,6 +508,74 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
return result;
}
private class CrossProductRec {
private final InstanceKey[][] invariants;
private final VoidFunction<InstanceKey[]> f;
private final SSAAbstractInvokeInstruction call;
private final CGNode caller;
private final int[] params;
private final CallSiteReference site;
private final InstanceKey[] keys;
private CrossProductRec(InstanceKey[][] invariants, SSAAbstractInvokeInstruction call, CGNode caller,
VoidFunction<InstanceKey[]> f) {
this.invariants = invariants;
this.f = f;
this.call = call;
this.caller = caller;
this.site = call.getCallSite();
this.params = IntSetUtil.toArray(getRelevantParameters(caller, site));
this.keys = new InstanceKey[ params.length ];
}
protected void rec(final int pi, final int rhsi) {
if (pi == params.length) {
f.apply(keys);
} else {
final int p = params[pi];
int vn = call.getUse(p);
InstanceKey[] ik = invariants != null ? invariants[p] : null;
if (ik != null) {
if (ik.length > 0) {
for (int i = 0; i < ik.length; i++) {
system.findOrCreateIndexForInstanceKey(ik[i]);
keys[pi] = ik[i];
rec(pi + 1, rhsi);
}
} else {
if (!site.isDispatch() || p != 0) {
keys[pi] = null;
rec(pi + 1, rhsi);
}
}
} else {
IntSet s = getParamObjects(pi, rhsi);
if (s != null && !s.isEmpty()) {
s.foreach(new IntSetAction() {
@Override
public void act(int x) {
keys[pi] = system.getInstanceKey(x);
rec(pi + 1, rhsi + 1);
}
});
} else {
if (!site.isDispatch() || p != 0) {
keys[pi] = null;
rec(pi + 1, rhsi + 1);
}
}
}
}
}
protected IntSet getParamObjects(int paramIndex, int rhsi) {
int paramVn = call.getUse(paramIndex);
PointerKey var = getPointerKeyForLocal(caller, paramVn);
IntSet s = system.findOrCreatePointsToSet(var).getValue();
return s;
}
}
/**
* A visitor that generates constraints based on statements in SSA form.
*/
@ -1045,7 +1124,22 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
}
InstanceKey[][] invariantParameters = invs.computeInvariantParameters(instruction);
if (instruction.getCallSite().isStatic()) {
IntSet params = getBuilder().getContextSelector().getRelevantParameters(node, instruction.getCallSite());
if (!instruction.getCallSite().isStatic() && !params.contains(0) && (invariantParameters == null || invariantParameters[0] == null)) {
params = IntSetUtil.makeMutableCopy(params);
((MutableIntSet)params).add(0);
}
if (invariantParameters != null) {
for(int i = 0; i < invariantParameters.length; i++) {
if (invariantParameters[i] != null) {
params = IntSetUtil.makeMutableCopy(params);
((MutableIntSet)params).remove(i);
}
}
}
if (params.isEmpty()) {
for (CGNode n : getBuilder().getTargetsForCall(node, instruction, invariantParameters)) {
getBuilder().processResolvedCall(node, instruction, n, invariantParameters, uniqueCatch);
if (DEBUG) {
@ -1059,11 +1153,6 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
// Add a side effect that will fire when we determine a value
// for a dispatch parameter. This side effect will create a new node
// and new constraints based on the new callee context.
IntSet params = getBuilder().getContextSelector().getRelevantParameters(node, instruction.getCallSite());
if (! params.contains(0)) {
params = IntSetUtil.makeMutableCopy(params);
((MutableIntSet)params).add(0);
}
final int vns[] = new int[ params.size() ];
params.foreach(new IntSetAction() {
private int i = 0;
@ -1635,15 +1724,65 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
this.uniqueCatch = uniqueCatch;
this.dispatchIndices = IntSetUtil.toArray(dispatchIndices);
// we better always be interested in the receiver
assert this.dispatchIndices[0] == 0;
// assert this.dispatchIndices[0] == 0;
previousPtrs = new MutableIntSet[dispatchIndices.size()];
for(int i = 0; i < previousPtrs.length; i++) {
previousPtrs[i] = IntSetUtil.getDefaultIntSetFactory().make();
}
}
private byte cpa(PointsToSetVariable lhs, final PointsToSetVariable[] rhs) {
final MutableBoolean changed = new MutableBoolean();
for(int rhsIndex = 0; rhsIndex < rhs.length; rhsIndex++) {
final int y = rhsIndex;
IntSet currentObjs = rhs[rhsIndex].getValue();
if (currentObjs != null) {
final IntSet oldObjs = previousPtrs[rhsIndex];
currentObjs.foreachExcluding(oldObjs, new IntSetAction() {
@Override
public void act(final int x) {
new CrossProductRec(constParams, call, node,
new VoidFunction<InstanceKey[]>() {
@Override
public void apply(InstanceKey[] v) {
IClass recv = null;
if (call.getCallSite().isDispatch()) {
recv = v[0].getConcreteType();
}
CGNode target = getTargetForCall(node, call.getCallSite(), recv, v);
if (target != null) {
changed.b = true;
processResolvedCall(node, call, target, constParams, uniqueCatch);
if (!haveAlreadyVisited(target)) {
markDiscovered(target);
}
}
}
}) {
{
rec(0, 0);
}
@Override
protected IntSet getParamObjects(int paramVn, int rhsi) {
if (rhsi == y) {
return IntSetUtil.make(new int[]{ x });
} else {
return previousPtrs[rhsi];
}
}
};
}
});
previousPtrs[rhsIndex].addAll(currentObjs);
}
}
byte sideEffectMask = changed.b ? (byte) SIDE_EFFECT_MASK : 0;
return (byte) (NOT_CHANGED | sideEffectMask);
}
/*
* @see com.ibm.wala.dataflow.fixpoint.UnaryOperator#evaluate(com.ibm.wala.dataflow.fixpoint.IVariable,
* com.ibm.wala.dataflow.fixpoint.IVariable)
@ -1651,6 +1790,10 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
@Override
public byte evaluate(PointsToSetVariable lhs, final PointsToSetVariable[] rhs) {
assert dispatchIndices.length >= rhs.length : "bad operator at " + call;
return cpa(lhs, rhs);
/*
// did evaluating the dispatch operation add a new possible target
// to the call site?
final MutableBoolean addedNewTarget = new MutableBoolean();
@ -1744,6 +1887,7 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
byte sideEffectMask = addedNewTarget.b ? (byte) SIDE_EFFECT_MASK : 0;
return (byte) (NOT_CHANGED | sideEffectMask);
*/
}
private void handleAllReceivers(MutableIntSet receiverVals, InstanceKey[] keys, MutableBoolean sideEffect) {
@ -1864,51 +2008,7 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
protected void iterateCrossProduct(final CGNode caller, final SSAAbstractInvokeInstruction call, IntSet parameters,
final InstanceKey[][] invariants, final VoidFunction<InstanceKey[]> f) {
final int params[] = IntSetUtil.toArray(parameters);
final InstanceKey[] keys = new InstanceKey[call.getNumberOfParameters()];
final CallSiteReference site = call.getCallSite();
new Object() {
private void rec(final int pi) {
if (pi == params.length) {
f.apply(keys);
} else {
final int p = params[pi];
int vn = call.getUse(p);
PointerKey var = getPointerKeyForLocal(caller, vn);
InstanceKey[] ik = invariants != null ? invariants[p] : null;
if (ik != null) {
if (ik.length > 0) {
for (int i = 0; i < ik.length; i++) {
system.findOrCreateIndexForInstanceKey(ik[i]);
keys[p] = ik[i];
rec(pi + 1);
}
} else {
if (!site.isDispatch() || p != 0) {
keys[p] = null;
rec(pi + 1);
}
}
} else {
IntSet s = system.findOrCreatePointsToSet(var).getValue();
if (s != null && !s.isEmpty()) {
s.foreach(new IntSetAction() {
@Override
public void act(int x) {
keys[p] = system.getInstanceKey(x);
rec(pi + 1);
}
});
} else {
if (!site.isDispatch() || p != 0) {
keys[p] = null;
rec(pi + 1);
}
}
}
}
}
}.rec(0);
new CrossProductRec(invariants, call, caller, f).rec(0, 0);
}
protected Set<CGNode> getTargetsForCall(final CGNode caller, final SSAAbstractInvokeInstruction instruction, InstanceKey[][] invs) {
@ -1919,11 +2019,7 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
// to take the invoke instruction as a parameter instead, since invs is
// associated with the instruction
final CallSiteReference site = instruction.getCallSite();
IntSet params = contextSelector.getRelevantParameters(caller, site);
if (!site.isStatic() && !params.contains(0)) {
params = IntSetUtil.makeMutableCopy(params);
((MutableIntSet)params).add(0);
}
IntSet params = getRelevantParameters(caller, site);
final Set<CGNode> targets = HashSetFactory.make();
VoidFunction<InstanceKey[]> f = new VoidFunction<InstanceKey[]>() {
@Override
@ -1942,6 +2038,15 @@ public abstract class SSAPropagationCallGraphBuilder extends PropagationCallGrap
return targets;
}
private IntSet getRelevantParameters(final CGNode caller, final CallSiteReference site) throws UnimplementedError {
IntSet params = contextSelector.getRelevantParameters(caller, site);
if (!site.isStatic() && !params.contains(0)) {
params = IntSetUtil.makeMutableCopy(params);
((MutableIntSet)params).add(0);
}
return params;
}
public boolean hasNoInterestingUses(CGNode node, int vn, DefUse du) {
if (du == null) {

View File

@ -17,6 +17,7 @@ import java.util.Map;
import com.ibm.wala.ipa.callgraph.Context;
import com.ibm.wala.ipa.callgraph.ContextItem;
import com.ibm.wala.ipa.callgraph.ContextKey;
import com.ibm.wala.util.collections.HashMapFactory;
/**
* A selective Cartesian product context that enforces object sensitivity on some set
@ -34,12 +35,13 @@ public class SelectiveCPAContext implements Context {
// helper method for constructing the parameterObjs map
private static Map<ContextKey, InstanceKey> makeMap(InstanceKey[] x) {
Map<ContextKey, InstanceKey> result = new HashMap<ContextKey, InstanceKey>();
Map<ContextKey, InstanceKey> result = HashMapFactory.make();
for(int i = 0; i < x.length; i++) {
if (x[i] != null) {
result.put(ContextKey.PARAMETERS[i], x[i]);
}
}
return result;
}
@ -53,6 +55,11 @@ public class SelectiveCPAContext implements Context {
hashCode = base.hashCode() ^ parameterObjs.hashCode();
}
@Override
public String toString() {
return "cpa:" + parameterObjs;
}
@Override
public ContextItem get(ContextKey name) {
if (parameterObjs.containsKey(name)) {
@ -78,5 +85,4 @@ public class SelectiveCPAContext implements Context {
parameterObjs.equals(((SelectiveCPAContext)other).parameterObjs);
}
}