//
// 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;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import cmu.conditional.Conditional;
import cmu.conditional.One;
import de.fosd.typechef.featureexpr.FeatureExpr;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.JPFException;
import gov.nasa.jpf.util.ArrayObjectQueue;
import gov.nasa.jpf.util.IntTable;
import gov.nasa.jpf.util.IntVector;
import gov.nasa.jpf.util.ObjectQueue;
import gov.nasa.jpf.util.Processor;
/**
* this is an abstract root for Heap implementations, providing a standard
* mark&sweep collector, change attribute management, and generic pinDownList,
* weakReference and internString handling
*
* The concrete Heap implementors have to provide the ElementInfo collection
* and associated getters, allocators and iterators
*/
public abstract class GenericHeap implements Heap, Iterable<ElementInfo> {
static abstract class GenericHeapMemento implements Memento<Heap> {
// those can be simply copied
int attributes;
IntVector pinDownList;
IntTable<String> internStrings;
protected GenericHeapMemento (GenericHeap heap){
// these are copy-on-first-write, so we don't have to clone
pinDownList = heap.pinDownList;
internStrings = heap.internStrings;
attributes = heap.attributes & ATTR_STORE_MASK;
heap.setStored();
}
@Override
public Heap restore (Heap inSitu) {
GenericHeap heap = (GenericHeap) inSitu;
heap.pinDownList = pinDownList;
heap.internStrings = internStrings;
heap.attributes = attributes;
heap.liveBitValue = false; // always start with false after a restore
return inSitu;
}
}
protected class ElementInfoMarker implements Processor<ElementInfo>{
public void process (ElementInfo ei) {
ei.markRecursive( GenericHeap.this); // this might in turn call queueMark
}
}
protected VM vm;
// list of pinned down references (this is only efficient for a small number of objects)
// this is copy-on-first-write
protected IntVector pinDownList;
// interned Strings
// this is copy-on-first-write
protected IntTable<String> internStrings;
// the usual drill - the lower 2 bytes are sticky, the upper two ones
// hold change status and transient (transition local) flags
protected int attributes;
static final int ATTR_GC = 0x0001;
static final int ATTR_OUT_OF_MEMORY = 0x0002;
static final int ATTR_RUN_FINALIZER = 0x0004;
static final int ATTR_ELEMENTS_CHANGED = 0x10000;
static final int ATTR_PINDOWN_CHANGED = 0x20000;
static final int ATTR_INTERN_CHANGED = 0x40000;
static final int ATTR_ATTRIBUTE_CHANGED = 0x80000;
// masks and sets
static final int ATTR_STORE_MASK = 0x0000ffff;
static final int ATTR_ANY_CHANGED = (ATTR_ELEMENTS_CHANGED | ATTR_PINDOWN_CHANGED | ATTR_INTERN_CHANGED | ATTR_ATTRIBUTE_CHANGED);
//--- these objects are only used during gc
// used to keep track of marked WeakRefs that might have to be updated (no need to restore, only transient use during gc)
protected ArrayList<ElementInfo> weakRefs;
protected ObjectQueue<ElementInfo> markQueue = new ArrayObjectQueue<ElementInfo>();
// this is set to false upon backtrack/restore
protected boolean liveBitValue;
protected ElementInfoMarker elementInfoMarker = new ElementInfoMarker();
// the number of live objects
// <2do> currently only defined after gc
protected int nLiveObjects;
//--- constructors
public GenericHeap (Config config, KernelState ks){
vm = VM.getVM();
pinDownList = new IntVector(256);
attributes |= ATTR_PINDOWN_CHANGED; // no need to clone on next add
internStrings = new IntTable<String>(8);
attributes |= ATTR_INTERN_CHANGED; // no need to clone on next add
if (config.getBoolean("vm.finalize", true)){
attributes |= ATTR_RUN_FINALIZER;
}
if (config.getBoolean("vm.sweep",true)){
attributes |= ATTR_GC;
}
}
protected DynamicElementInfo createElementInfo (int objref, ClassInfo ci, Fields f, Monitor m, ThreadInfo ti){
return new DynamicElementInfo( objref,ci,f,m,ti);
}
//--- pinDown handling
protected void addToPinDownList (int objref){
if ((attributes & ATTR_PINDOWN_CHANGED) == 0) {
pinDownList = pinDownList.clone();
attributes |= ATTR_PINDOWN_CHANGED;
}
pinDownList.add(objref);
}
protected void removeFromPinDownList (int objref){
if ((attributes & ATTR_PINDOWN_CHANGED) == 0) {
pinDownList = pinDownList.clone();
attributes |= ATTR_PINDOWN_CHANGED;
}
pinDownList.removeFirst(objref);
}
@Override
public void registerPinDown(int objref){
ElementInfo ei = getModifiable(objref);
if (ei != null) {
if (ei.incPinDown()){
addToPinDownList(objref);
}
} else {
throw new JPFException("pinDown reference not a live object: " + objref);
}
}
@Override
public void releasePinDown(int objref){
ElementInfo ei = getModifiable(objref);
if (ei != null) {
if (ei.decPinDown()){
removeFromPinDownList(objref);
}
} else {
throw new JPFException("pinDown reference not a live object: " + objref);
}
}
void markPinDownList (){
if (pinDownList != null){
int len = pinDownList.size();
for (int i=0; i<len; i++){
int objref = pinDownList.get(i);
queueMark(objref);
}
}
}
//--- weak reference handling
public void registerWeakReference (ElementInfo ei) {
if (weakRefs == null) {
weakRefs = new ArrayList<ElementInfo>();
}
weakRefs.add(ei);
}
/**
* reset all weak references that now point to collected objects to 'null'
* NOTE: this implementation requires our own Reference/WeakReference implementation, to
* make sure the 'ref' field is the first one
*/
protected void cleanupWeakRefs () {
if (weakRefs != null) {
for (ElementInfo ei : weakRefs) {
Fields f = ei.getFields();
List<Integer> refs = f.getIntValue(0).toList(); // watch out, the 0 only works with our own WeakReference impl
for (int ref: refs) {
if (ref != MJIEnv.NULL) {
ElementInfo refEi = get(ref);
if ((refEi == null) || (refEi.isNull())) {
ei = ei.getModifiableInstance();
// we need to make sure the Fields are properly state managed
ei.setReferenceField(FeatureExprFactory.True(), ei.getFieldInfo(0), One.MJIEnvNULL);
}
}
}
}
weakRefs = null;
}
}
// NOTE - this is where to assert if this index isn't occupied yet, since only concrete classes know
// if there can be collisions, and how elements are stored
protected abstract AllocationContext getSUTAllocationContext (FeatureExpr ctx, ClassInfo ci, ThreadInfo ti);
protected abstract AllocationContext getSystemAllocationContext (ClassInfo ci, ThreadInfo ti, int anchor);
/**
* this is called for newXX(..) allocations that are SUT thread specific, i.e. in response to
* a explicit NEW or xNEWARRAY instruction that should take the allocating thread into account
*/
protected abstract int getNewElementInfoIndex (AllocationContext ctx);
//--- allocators
protected ElementInfo createObject (FeatureExpr ctx, ClassInfo ci, ThreadInfo ti, int objref) {
// create the thing itself
Fields f = ci.createInstanceFields();
Monitor m = new Monitor();
ElementInfo ei = createElementInfo( objref, ci, f, m, ti);
set(objref, ei);
attributes |= ATTR_ELEMENTS_CHANGED;
// and do the default (const) field initialization
ci.initializeInstanceData(ctx, ei, ti);
vm.notifyObjectCreated(ti, ei);
// note that we don't return -1 if 'outOfMemory' (which is handled in
// the NEWxx bytecode) because our allocs are used from within the
// exception handling of the resulting OutOfMemoryError (and we would
// have to override it, since the VM should guarantee proper exceptions)
return ei;
}
@Override
public ElementInfo newObject(FeatureExpr fexpr, ClassInfo ci, ThreadInfo ti) {
AllocationContext ctx = getSUTAllocationContext(fexpr, ci, ti);
int index = getNewElementInfoIndex( ctx);
ElementInfo ei = createObject( fexpr, ci, ti, index);
return ei;
}
@Override
public ElementInfo newSystemObject (FeatureExpr fexpr, ClassInfo ci, ThreadInfo ti, int anchor) {
AllocationContext ctx = getSystemAllocationContext( ci, ti, anchor);
int index = getNewElementInfoIndex( ctx);
ElementInfo ei = createObject( fexpr, ci, ti, index);
return ei;
}
protected ElementInfo createArray (String elementType, int nElements, ClassInfo ci, ThreadInfo ti, int objref) {
Fields f = ci.createArrayFields(ci.getName(), nElements, Types.getTypeSize(elementType), Types.isReference(elementType));
Monitor m = new Monitor();
DynamicElementInfo ei = createElementInfo( objref, ci, f, m, ti);
set(objref, ei);
attributes |= ATTR_ELEMENTS_CHANGED;
vm.notifyObjectCreated(ti, ei);
return ei;
}
protected ClassInfo getArrayClassInfo (ThreadInfo ti, String elementType) {
String type = "[" + elementType;
SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
ClassInfo ciArray = sysCl.getResolvedClassInfo(null, type);
if (!ciArray.isInitialized()) {
// we do this explicitly here since there are no clinits for array classes
ciArray.registerClass(FeatureExprFactory.True(), ti);
ciArray.setInitialized();
}
return ciArray;
}
@Override
public ElementInfo newArray(FeatureExpr fexpr, String elementType, int nElements, ThreadInfo ti) {
// see newObject for OOM simulation
ClassInfo ci = getArrayClassInfo(ti, elementType);
AllocationContext ctx = getSUTAllocationContext(fexpr, ci, ti);
int index = getNewElementInfoIndex( ctx);
ElementInfo ei = createArray( elementType, nElements, ci, ti, index);
return ei;
}
@Override
public ElementInfo newSystemArray(String elementType, int nElements, ThreadInfo ti, int anchor) {
// see newObject for OOM simulation
ClassInfo ci = getArrayClassInfo(ti, elementType);
AllocationContext ctx = getSystemAllocationContext( ci, ti, anchor);
int index = getNewElementInfoIndex( ctx);
ElementInfo ei = createArray( elementType, nElements, ci, ti, index);
return ei;
}
protected ElementInfo initializeStringObject( FeatureExpr ctx, String str, int index, int vref) {
ElementInfo ei = getModifiable(index);
ei.setReferenceField(FeatureExprFactory.True(), "value", new One<>(vref));
ElementInfo eVal = getModifiable(vref);
CharArrayFields cf = (CharArrayFields)eVal.getFields();
cf.setCharValues(ctx, str.toCharArray());
return ei;
}
protected ElementInfo newString (FeatureExpr fexpr, ClassInfo ciString, ClassInfo ciChars, String str, ThreadInfo ti, AllocationContext ctx) {
// SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
//--- the string object itself
int sRef = getNewElementInfoIndex( ctx);
createObject( fexpr, ciString, ti, sRef);
//--- its char[] array
ctx = ctx.extend(ciChars, sRef);
int vRef = getNewElementInfoIndex( ctx);
createArray( "C", str.length(), ciChars, ti, vRef);
ElementInfo ei = initializeStringObject(fexpr, str, sRef, vRef);
return ei;
}
@Override
public ElementInfo newString(FeatureExpr fexpr, String str, ThreadInfo ti){
if (str != null) {
SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
ClassInfo ciString = sysCl.getStringClassInfo();
ClassInfo ciChars = sysCl.getCharArrayClassInfo();
AllocationContext ctx = getSUTAllocationContext(fexpr, ciString, ti);
return newString( FeatureExprFactory.True(), ciString, ciChars, str, ti, ctx);// TODO jens ???
} else {
return null;
}
}
@Override
public ElementInfo newString(FeatureExpr fexpr, Conditional<String> str, ThreadInfo ti) {
if (str != null) {
SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
ClassInfo ciString = sysCl.getStringClassInfo();
ClassInfo ciChars = sysCl.getCharArrayClassInfo();
AllocationContext ctx = getSUTAllocationContext(fexpr, ciString, ti);
return newString( fexpr, ciString, ciChars, str, ti, ctx);
} else {
return null;
}
}
protected ElementInfo newString(FeatureExpr fexpr, ClassInfo ciString, ClassInfo ciChars, Conditional<String> str, ThreadInfo ti, AllocationContext ctx) {
if (str instanceof One){
return newString(fexpr, str.getValue(), ti);
}
int length = 0;
for (String s : str.toList()) {
if (s != null && s.length() > length) {
length = s.length();
}
}
// SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
// --- the string object itself
int sRef = getNewElementInfoIndex(ctx);
createObject(fexpr, ciString, ti, sRef);
// --- its char[] array
ctx = ctx.extend(ciChars, sRef);
int vRef = getNewElementInfoIndex(ctx);
createArray("C", length, ciChars, ti, vRef);
ElementInfo ei = null;
Map<String, FeatureExpr> map = str.toMap();
for (Entry<String, FeatureExpr> sEntry : map.entrySet()) {
String s = sEntry.getKey();
if (ei == null) {
ei = initializeStringObject(sEntry.getValue(), s, sRef, vRef);
} else {
ElementInfo eVal = getModifiable(vRef);
CharArrayFields cf = (CharArrayFields)eVal.getFields();
cf.setCharValues(sEntry.getValue().and(fexpr), s.toCharArray());
}
}
return ei;
}
@Override
public ElementInfo newSystemString (String str, ThreadInfo ti, int anchor) {
if (str != null) {
SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
ClassInfo ciString = sysCl.getStringClassInfo();
ClassInfo ciChars = sysCl.getCharArrayClassInfo();
AllocationContext ctx = getSystemAllocationContext( ciString, ti, anchor);
return newString( FeatureExprFactory.True(), ciString, ciChars, str, ti, ctx);
} else {
return null;
}
}
@Override
public ElementInfo newInternString (FeatureExpr ctx, String str, ThreadInfo ti) {
IntTable.Entry<String> e = internStrings.get(str);
if (e == null){
ElementInfo ei = newString(ctx, str, ti);
int index = ei.getObjectRef();
// new interned Strings are always pinned down
ei.incPinDown();
addToPinDownList(index);
addToInternStrings(str, index);
return ei;
} else {
return get(e.val);
}
}
protected void addToInternStrings (String str, int objref) {
if ((attributes & ATTR_INTERN_CHANGED) == 0){
internStrings = internStrings.clone();
attributes |= ATTR_INTERN_CHANGED;
}
internStrings.add(str, objref);
}
public ElementInfo newSystemThrowable (FeatureExpr fexpr, ClassInfo ciThrowable, String details, int[] stackSnapshot,
int causeRef, ThreadInfo ti, int anchor) {
SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
ClassInfo ciString = sysCl.getStringClassInfo();
ClassInfo ciChars = sysCl.getCharArrayClassInfo();
//--- the Throwable object itself
AllocationContext ctx = getSystemAllocationContext( ciThrowable, ti, anchor);
int xRef = getNewElementInfoIndex( ctx);
ElementInfo eiThrowable = createObject(fexpr, ciThrowable, ti, xRef);
//--- the detailMsg field
if (details != null) {
AllocationContext ctxString = ctx.extend( ciString, xRef);
ElementInfo eiMsg = newString(fexpr, ciString, ciChars, details, ti, ctxString);
eiThrowable.setReferenceField(fexpr, "detailMessage", new One<>(eiMsg.getObjectRef()));
}
//--- the stack snapshot field
ClassInfo ciSnap = getArrayClassInfo(ti, "I");
AllocationContext ctxSnap = ctx.extend(ciSnap, xRef);
int snapRef = getNewElementInfoIndex( ctxSnap);
ElementInfo eiSnap = createArray( "I", stackSnapshot.length, ciSnap, ti, snapRef);
// Conditional<Integer>[] array = eiSnap.asIntArray();
// int[] snap = new int[array.length];
// for (int i = 0; i < array.length; i++) {
// snap[i] = array[i].getValue();
// }
for (int i = 0; i < stackSnapshot.length; i++) {
eiSnap.setIntElement(fexpr, i, new One<>(stackSnapshot[i]));
}
// System.arraycopy( stackSnapshot, 0, snap, 0, stackSnapshot.length);
eiThrowable.setReferenceField(fexpr, "snapshot", new One<>(snapRef));
//--- the cause field
eiThrowable.setReferenceField(fexpr, "cause", new One<>((causeRef != MJIEnv.NULL)? causeRef : xRef));
return eiThrowable;
}
//--- abstract accessors
/*
* these methods abstract away the container type used in GenericHeap subclasses
*/
/**
* internal setter used during allocation
* @param index
* @param ei
*/
protected abstract void set (int index, ElementInfo ei);
/**
* public getter to access but not change ElementInfos
*/
@Override
public abstract ElementInfo get (int ref);
/**
* public getter to access modifiable ElementInfos;
*/
@Override
public abstract ElementInfo getModifiable (int ref);
/**
* internal remover used by generic sweep
*/
protected abstract void remove (int ref);
//--- iterators
/**
* return Iterator for all non-null ElementInfo entries
*/
public abstract Iterator<ElementInfo> iterator();
@Override
public abstract Iterable<ElementInfo> liveObjects();
//--- garbage collection
public boolean isGcEnabled (){
return (attributes & ATTR_GC) != 0;
}
public void setGcEnabled (boolean doGC) {
if (doGC != isGcEnabled()) {
if (doGC) {
attributes |= ATTR_GC;
} else {
attributes &= ~ATTR_GC;
}
attributes |= ATTR_ATTRIBUTE_CHANGED;
}
}
public void unmarkAll(){
for (ElementInfo ei : liveObjects()){
ei.setUnmarked();
}
}
/**
* add a non-null, not yet marked reference to the markQueue
*
* called from ElementInfo.markRecursive(). We don't want to expose the
* markQueue since a copying collector might not have it
*/
public void queueMark (int objref){
if (objref == MJIEnv.NULL) {
return;
}
ElementInfo ei = get(objref);
if (ei == null) {
System.out.println("no element for " + objref);
}
if (!ei.isMarked()){ // only add objects once
ei.setMarked();
markQueue.add(ei);
}
}
/**
* called during non-recursive phase1 marking of all objects reachable
* from static fields
* @aspects: gc
*/
public void markStaticRoot (int objref) {
if (objref != MJIEnv.NULL) {
queueMark(objref);
}
}
/**
* called during non-recursive phase1 marking of all objects reachable
* from Thread roots
* @aspects: gc
*/
public void markThreadRoot (int objref, int tid) {
if (objref != MJIEnv.NULL) {
queueMark(objref);
}
}
/**
* this implementation uses a generic ElementInfo iterator, it can be replaced
* with a more efficient container specific version
*/
protected void sweep () {
ThreadInfo ti = vm.getCurrentThread();
int tid = ti.getId();
boolean isThreadTermination = ti.isTerminated();
int n = 0;
if(vm.finalizersEnabled()) {
markFinalizableObjects();
}
// now go over all objects, purge the ones that are not live and reset attrs for rest
for (ElementInfo ei : this){
if (ei.isMarked()){ // live object, prepare for next transition & gc cycle
ei.setUnmarked();
ei.setAlive(liveBitValue);
ei.cleanUp(this, isThreadTermination, tid);
n++;
} else {
ei.processReleaseActions();
vm.notifyObjectReleased(ti, ei);
remove(ei.getObjectRef());
JPF.JVMheap.remove(ei.getObjectRef());
}
}
nLiveObjects = n;
}
protected void markFinalizableObjects () {
// Phase 1: find unmarked objects with finalizers that haven't been processed yet
// and mark them recursively to avoid them from being GCed. Their finalizers must
// execute before these objects are removed from the heap.
for (ElementInfo ei : this){
if(!ei.isMarked() && ei.hasFinalizer() && !ei.isFinalized()) {
vm.addToFinalizeQueue(ei);
queueMark(ei.getObjectRef());
ei.markRecursive(this);
}
}
// Phase 2: collect all unmarked objects
}
protected void mark () {
markQueue.clear();
//--- mark everything in our root set
markPinDownList();
vm.getThreadList().markRoots(this); // mark thread stacks
vm.getClassLoaderList().markRoots(this); // mark all static references
//--- trace all entries - this gets recursive
markQueue.process(elementInfoMarker);
}
@Override
public void gc() {
vm.notifyGCBegin();
weakRefs = null;
liveBitValue = !liveBitValue;
mark();
// at this point all live objects are marked
sweep();
cleanupWeakRefs(); // for potential nullification
vm.processPostGcActions();
vm.notifyGCEnd();
}
/**
* clean up reference values that are stored outside of reference fields
* called from KernelState to process live ElementInfos after GC has finished
* and only live objects remain in the heap.
*
* <2do> full heap enumeration is BAD - check if this can be moved into the sweep loop
*/
public void cleanUpDanglingReferences() {
ThreadInfo ti = ThreadInfo.getCurrentThread();
int tid = ti.getId();
boolean isThreadTermination = ti.isTerminated();
for (ElementInfo e : this) {
if (e != null) {
e.cleanUp(this, isThreadTermination, tid);
}
}
}
/**
* check if object is alive. This is here and not in ElementInfo
* because we might own the liveness bit. In fact, the generic
* implementation uses bit-toggle to avoid iteration over all live
* objects at the end of GC
*/
public boolean isAlive (ElementInfo ei){
return (ei == null || ei.isMarkedOrAlive(liveBitValue));
}
//--- state management
// since we can't provide generic implementations, we force concrete subclasses to
// handle volatile information
@Override
public abstract void resetVolatiles();
@Override
public abstract void restoreVolatiles();
public boolean hasChanged() {
return (attributes & ATTR_ANY_CHANGED) != 0;
}
public void markChanged(int objref) {
attributes |= ATTR_ELEMENTS_CHANGED;
}
public void setStored() {
attributes &= ~ATTR_ANY_CHANGED;
}
@Override
public abstract Memento<Heap> getMemento(MementoFactory factory);
@Override
public abstract Memento<Heap> getMemento();
//--- out of memory simulation
public boolean isOutOfMemory() {
return (attributes & ATTR_OUT_OF_MEMORY) != 0;
}
public void setOutOfMemory(boolean isOutOfMemory) {
if (isOutOfMemory != isOutOfMemory()) {
if (isOutOfMemory) {
attributes |= ATTR_OUT_OF_MEMORY;
} else {
attributes &= ~ATTR_OUT_OF_MEMORY;
}
attributes |= ATTR_ATTRIBUTE_CHANGED;
}
}
//--- debugging
@Override
public void checkConsistency(boolean isStateStore) {
for (ElementInfo ei : this){
ei.checkConsistency();
}
}
}