//
// Copyright (C) 2012 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.vm;
// see mixinJPFStack() comments
import static gov.nasa.jpf.util.OATHash.hashFinalize;
import static gov.nasa.jpf.util.OATHash.hashMixin;
import de.fosd.typechef.featureexpr.FeatureExpr;
import gov.nasa.jpf.Config;
import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;
/**
* an AllocationContext that uses a hash value for comparison. This is
* lossy - heap implementations using this class have to check/handle
* collisions.
*
* However, given that we have very good hash data (search global object
* references), the probability of collisions is low enough that heap
* implementations might simply report this as a problem requiring a
* non-lossy AllocationContext.
*
* Ideally, we would like to hash the host VM thread context too (esp.
* for system allocations), but host VM stack traces are expensive, and it is
* arguable if would be too strict (e.g. when using a dedicated allocator
* method called from alternative branches of the caller)
*
* note - this is a HashMap key type which has to obey the hashCode/equals contract
*/
public class HashedAllocationContext implements AllocationContext {
static final Throwable throwable = new Throwable(); // to avoid frequent allocations
static int mixinSUTStack (FeatureExpr fexpr, int h, ThreadInfo ti) {
h = hashMixin( h, ti.getId());
// we don't want to mixin the stack slots (locals and operands) because this would
// cause state leaks (different hash) if there are changed slot values that do not
// relate to the allocation
for (StackFrame frame = ti.getTopFrame(); frame != null; frame = frame.getPrevious() ) {
if (!(frame instanceof DirectCallStackFrame)) {
Instruction insn = frame.getPC().simplify(fexpr).getValue(true);
//h = hashMixin(h, insn.hashCode()); // this is the Instruction object system hash - not reproducible between runs
h = hashMixin( h, insn.getMethodInfo().getGlobalId()); // the method
h = hashMixin( h, insn.getInstructionIndex()); // the position within the method code
h = hashMixin( h, insn.getByteCode()); // the instruction type
}
}
return h;
}
/*
* this is an optimization to cut down on host VM StackTrace acquisition, since we just need one
* element.
*
* NOTE: this is more fragile than Throwable.getStackTrace() and String.equals() since it assumes
* availability of the sun.misc.JavaLangAccess SharedSecret and invariance of classname strings.
*
* The robust version would be
* ..
* throwable.fillInStackTrace();
* StackTraceElement[] ste = throwable.getStackTrace();
* StackTraceElement e = ste[4];
* if (e.getClassName().equals("gov.nasa.jpf.vm.MJIEnv") && e.getMethodName().startsWith("new")){ ..
*/
static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
static final String ENV_CLSNAME = MJIEnv.class.getName();
// <2do> this method is problematic - we should not assume a fixed stack position
// but we can't just mixin the whole stack since this would cause different class object
// allocation contexts (registerClass can happen from lots of locations).
// At the other end of the spectrum, MJIEnv.newXX() is not differentiating enough since
// those are convenience methods used from a gazillion of places that might share
// the same SUT state
static int mixinJPFStack (int h) {
throwable.fillInStackTrace();
// we know the callstack is at least 4 levels deep:
// 0: mixinJPFStack
// 1: getXAllocationContext
// 2: heap.getXAllocationContext
// 3: heap.newObject/newArray/newString
// 4: <allocating method>
// ...
// note that it is not advisable to mixin more than the immediate newX() caller since
// this would create state leaks for allocations that are triggered by SUT threads and
// have different native paths (e.g. Class object creation caused by different SUT thread context)
StackTraceElement e = JLA.getStackTraceElement(throwable, 4); // see note below regarding fixed call depth fragility
// <2do> this sucks - MJIEnv.newObject/newArray/newString are used from a gazillion of places that might not differ in SUT state
if (e.getClassName() == ENV_CLSNAME && e.getMethodName().startsWith("new")){
// there is not much use to loop, since we don't have a good end condition
e = JLA.getStackTraceElement(throwable, 5);
}
// NOTE - this is fragile since it is implementation dependent and differs
// between JPF runs
// the names are interned string from the class object
// h = hashMixin( h, System.identityHashCode(e.getClassName()));
// h = hashMixin( h, System.identityHashCode(e.getMethodName()));
// this should be reproducible, but the string hash is bad
h = hashMixin(h, e.getClassName().hashCode());
h = hashMixin(h, e.getMethodName().hashCode());
h = hashMixin(h, e.getLineNumber());
return h;
}
/*
* !! NOTE: these always have to be at a fixed call distance of the respective Heap.newX() call:
*
* ConcreteHeap.newX()
* ConcreteHeap.getXAllocationContext()
* ConcreteAllocationContext.getXAllocationContext()
*
* that means the allocation site is at stack depth 4. This is not nice, but there is no
* good heuristic we could use instead, other than assuming there is a newObject/newArray/newString
* call on the stack
*/
/**
* this one is for allocations that should depend on the SUT thread context (such as all
* explicit NEW executions)
*/
public static AllocationContext getSUTAllocationContext (FeatureExpr fexpr, ClassInfo ci, ThreadInfo ti) {
int h = 0;
//--- the type that gets allocated
h = hashMixin(h, ci.getUniqueId()); // ClassInfo instances can change upon backtrack
//--- the SUT execution context (allocating ThreadInfo and its stack)
h = mixinSUTStack(fexpr, h, ti);
//--- the JPF execution context (from where in the JPF code the allocation happens)
h = mixinJPFStack( h);
h = hashFinalize(h);
HashedAllocationContext ctx = new HashedAllocationContext(h);
return ctx;
}
/**
* this one is for allocations that should NOT depend on the SUT thread context (such as
* automatic allocation of java.lang.Class objects by the VM)
*
* @param anchor a value that can be used to provide a context that is heap graph specific (such as
* a classloader or class object reference)
*/
public static AllocationContext getSystemAllocationContext (ClassInfo ci, ThreadInfo ti, int anchor) {
int h = 0;
h = hashMixin(h, ci.getUniqueId()); // ClassInfo instances can change upon backtrack
// in lieu of the SUT stack, add some magic salt and the anchor
h = hashMixin(h, 0x14040118);
h = hashMixin(h, anchor);
//--- the JPF execution context (from where in the JPF code the allocation happens)
h = mixinJPFStack( h);
h = hashFinalize(h);
HashedAllocationContext ctx = new HashedAllocationContext(h);
return ctx;
}
public static boolean init (Config conf) {
//pool = new SparseObjVector<HashedAllocationContext>();
return true;
}
//--- instance data
// rolled up hash value for all context components
protected final int id;
//--- instance methods
protected HashedAllocationContext (int id) {
this.id = id;
}
@Override
public boolean equals (Object o) {
if (o instanceof HashedAllocationContext) {
HashedAllocationContext other = (HashedAllocationContext)o;
return id == other.id;
}
return false;
}
/**
* @pre: must be the same for two objects that result in equals() returning true
*/
@Override
public int hashCode() {
return id;
}
// for automatic field init allocations
public AllocationContext extend (ClassInfo ci, int anchor) {
//int h = hash( id, anchor, ci.hashCode());
int h = hashMixin(id, anchor);
h = hashMixin(h, ci.getUniqueId());
h = hashFinalize(h);
return new HashedAllocationContext(h);
}
}