//
// Copyright (C) 2006 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.serialize;
import java.util.HashMap;
import java.util.List;
import cmu.conditional.Conditional;
import gov.nasa.jpf.util.ArrayObjectQueue;
import gov.nasa.jpf.util.BitArray;
import gov.nasa.jpf.util.FinalBitSet;
import gov.nasa.jpf.util.IntVector;
import gov.nasa.jpf.util.ObjVector;
import gov.nasa.jpf.util.ObjectQueue;
import gov.nasa.jpf.util.Processor;
import gov.nasa.jpf.vm.AbstractSerializer;
import gov.nasa.jpf.vm.ArrayFields;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.ClassLoaderInfo;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.FieldInfo;
import gov.nasa.jpf.vm.Fields;
import gov.nasa.jpf.vm.Heap;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MJIEnv;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.ReferenceProcessor;
import gov.nasa.jpf.vm.StackFrame;
import gov.nasa.jpf.vm.StaticElementInfo;
import gov.nasa.jpf.vm.Statics;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.ThreadList;
import gov.nasa.jpf.vm.VM;
/**
* serializer that can ignore marked fields and stackframes for state matching
*
* <2do> rework filter policies
*/
public class FilteringSerializer extends AbstractSerializer implements ReferenceProcessor, Processor<ElementInfo> {
// indexed by method globalId
final ObjVector<FramePolicy> methodCache = new ObjVector<>();
//--- search global bitmask caches
final HashMap<ClassInfo,FinalBitSet> instanceRefMasks = new HashMap<>();
final HashMap<ClassInfo,FinalBitSet> staticRefMasks = new HashMap<>();
final HashMap<ClassInfo,FinalBitSet> instanceFilterMasks = new HashMap<>();
final HashMap<ClassInfo,FinalBitSet> staticFilterMasks = new HashMap<>();
protected FilterConfiguration filter;
protected transient IntVector buf = new IntVector(4096);
// the reference queue for heap traversal
protected ObjectQueue<ElementInfo> refQueue;
Heap heap;
@Override
public void attach(VM vm) {
super.attach(vm);
filter = vm.getConfig().getInstance("filter.class", FilterConfiguration.class);
if (filter == null) {
filter = new DefaultFilterConfiguration();
}
filter.init(vm.getConfig());
}
protected FramePolicy getFramePolicy(MethodInfo mi) {
FramePolicy p = null;
int mid = mi.getGlobalId();
if (mid >= 0){
p = methodCache.get(mid);
if (p == null) {
p = filter.getFramePolicy(mi);
methodCache.set(mid, p);
}
} else {
p = filter.getFramePolicy(mi);
}
return p;
}
protected FinalBitSet getInstanceRefMask(ClassInfo ci) {
FinalBitSet v = instanceRefMasks.get(ci);
if (v == null) {
BitArray b = new BitArray(ci.getInstanceDataSize());
for (FieldInfo fi : filter.getMatchedInstanceFields(ci)) {
if (fi.isReference()) {
b.set(fi.getStorageOffset());
}
}
v = FinalBitSet.create(b);
if (v == null) throw new IllegalStateException("Null BitArray returned.");
instanceRefMasks.put(ci, v);
}
return v;
}
protected FinalBitSet getStaticRefMask(ClassInfo ci) {
FinalBitSet v = staticRefMasks.get(ci);
if (v == null) {
BitArray b = new BitArray(ci.getStaticDataSize());
for (FieldInfo fi : filter.getMatchedStaticFields(ci)) {
if (fi.isReference()) {
b.set(fi.getStorageOffset());
}
}
v = FinalBitSet.create(b);
if (v == null) throw new IllegalStateException("Null BitArray returned.");
staticRefMasks.put(ci, v);
}
return v;
}
protected FinalBitSet getInstanceFilterMask(ClassInfo ci) {
FinalBitSet v = instanceFilterMasks.get(ci);
if (v == null) {
BitArray b = new BitArray(ci.getInstanceDataSize());
b.setAll();
for (FieldInfo fi : filter.getMatchedInstanceFields(ci)) {
int start = fi.getStorageOffset();
int end = start + fi.getStorageSize();
for (int i = start; i < end; i++) {
b.clear(i);
}
}
v = FinalBitSet.create(b);
if (v == null) throw new IllegalStateException("Null BitArray returned.");
instanceFilterMasks.put(ci, v);
}
return v;
}
protected FinalBitSet getStaticFilterMask(ClassInfo ci) {
FinalBitSet v = staticFilterMasks.get(ci);
if (v == null) {
BitArray b = new BitArray(ci.getStaticDataSize());
b.setAll();
for (FieldInfo fi : filter.getMatchedStaticFields(ci)) {
int start = fi.getStorageOffset();
int end = start + fi.getStorageSize();
for (int i = start; i < end; i++) {
b.clear(i);
}
}
v = FinalBitSet.create(b);
if (v == null) throw new IllegalStateException("Null BitArray returned.");
staticFilterMasks.put(ci, v);
}
return v;
}
protected void initReferenceQueue() {
// note - this assumes all heap objects are in an unmarked state, but this
// is true if we enter outside the gc
if (refQueue == null){
refQueue = new ArrayObjectQueue<ElementInfo>();
} else {
refQueue.clear();
}
}
//--- those are the methods that can be overridden by subclasses to implement abstractions
// needs to be public because of ReferenceProcessor interface
public void processReference(int objref) {
if (objref != MJIEnv.NULL) {
ElementInfo ei = heap.get(objref);
if (!ei.isMarked()) { // only add objects once
ei.setMarked();
refQueue.add(ei);
}
}
buf.add(objref);
}
protected void processArrayFields (ArrayFields afields){
buf.add(afields.arrayLength());
if (afields.isReferenceArray()) {
Conditional<Integer>[] values = afields.asReferenceArray();
for (int i = 0; i < values.length; i++) {
for (int v : values[i].toList()) {
processReference(v);
}
}
} else {
afields.appendTo(buf);
}
}
protected void processNamedFields (ClassInfo ci, Fields fields){
FinalBitSet filtered = getInstanceFilterMask(ci);
FinalBitSet refs = getInstanceRefMask(ci);
// using a block operation probably doesn't buy us much here since
// we would have to blank the filtered slots and then visit the
// non-filtered reference slots, i.e. do two iterations over
// the mask bit sets
int[] values = fields.asFieldSlots();
for (int i = 0; i < values.length; i++) {
if (!filtered.get(i)) {
int v = values[i];
if (refs.get(i)) {
processReference(v);
} else {
buf.add(v);
}
}
}
}
// needs to be public because of ElementInfoProcessor interface
// NOTE: we don't serialize the monitor state here since this is
// redundant to the thread locking state (which we will do after the heap).
// <2do> we don't strictly need the lockCount since this has to show in the
// stack frames. However, we should probably add monitor serialization to
// better support specialized subclasses
public void process (ElementInfo ei) {
Fields fields = ei.getFields();
ClassInfo ci = ei.getClassInfo();
buf.add(ci.getUniqueId());
if (fields instanceof ArrayFields) { // not filtered
processArrayFields((ArrayFields)fields);
} else { // named fields, filtered
processNamedFields(ci, fields);
}
}
protected void processReferenceQueue () {
refQueue.process(this);
// this sucks, but we can't do the 'isMarkedOrLive' trick used in gc here
// because gc depends on live bit integrity, and we only mark non-filtered live
// objects here, i.e. we can't just set the Heap liveBitValue subsequently.
heap.unmarkAll();
}
protected void serializeStackFrames() {
ThreadList tl = ks.getThreadList();
for (ThreadInfo ti : tl) {
if (ti.isAlive()) {
serializeStackFrames(ti);
}
}
}
protected void serializeStackFrames(ThreadInfo ti){
// we need to add the thread object itself as a root
processReference( ti.getThreadObjectRef());
for (StackFrame frame = ti.getTopFrame(); frame != null; frame = frame.getPrevious()){
serializeFrame(frame);
}
}
/** more generic, but less efficient because it can't use block operations
protected void _serializeFrame(StackFrame frame){
buf.add(frame.getMethodInfo().getGlobalId());
buf.add(frame.getPC().getInstructionIndex());
int len = frame.getTopPos()+1;
buf.add(len);
// this looks like something we can push into the frame
int[] slots = frame.getSlots();
for (int i = 0; i < len; i++) {
if (frame.isReferenceSlot(i)) {
processReference(slots[i]);
} else {
buf.add(slots[i]);
}
}
}
**/
protected void serializeFrame(StackFrame frame){
buf.add(frame.getMethodInfo().getGlobalId());
// there can be (rare) cases where a listener sets a null nextPc in
// a frame that is still on the stack
Instruction pc = frame.getPC().getValue();
if (pc != null){
buf.add(pc.getInstructionIndex());
} else {
buf.add(-1);
}
int len = frame.getTopPos()+1;
buf.add(len);
int[] slots = frame.getSlots();
buf.append(slots,0,len);
frame.visitReferenceSlots(this);
}
// this is called after the heap got serialized, i.e. we should not use
// processReference() anymore.
protected void serializeThreadState (ThreadInfo ti){
buf.add( ti.getId());
buf.add( ti.getState().ordinal());
buf.add( ti.getStackDepth());
//--- the lock state
// NOTE: both lockRef and lockedObjects can only refer to live objects
// which are already heap-processed at this point (i.e. have a valid 'sid'
// in case we don't want to directly serialize the reference values)
// the object we are waiting for
ElementInfo eiLock = ti.getLockObject();
if (eiLock != null){
buf.add(getSerializedReferenceValue( eiLock));
}
// the objects we hold locks for
// NOTE: this should be independent of lockedObjects order, hence we
// have to factor this out
serializeLockedObjects( ti.getLockedObjects());
}
// NOTE: this should not be called before all live references have been processed
protected int getSerializedReferenceValue (ElementInfo ei){
return ei.getObjectRef();
}
protected void serializeLockedObjects(List<ElementInfo> lockedObjects){
// lockedObjects are already a set since we don't have multiple entries
// (that would just increase the lock count), but our serialization should
// NOT produce different values depending on order of entry. We could achieve this by using
// a canonical order (based on reference or sid values), but this would require
// System.arraycopys and object allocation, which is too much overhead
// given that the number of lockedObjects is small for all but the most
// pathological systems under test.
// We could spend all day to compute the perfect order-independent hash function,
// but since our StateSet isn't guaranteed to be collision free anyway, we
// rather shoot for something that can be nicely JITed
int n = lockedObjects.size();
buf.add(n);
if (n > 0){
if (n == 1){ // no order involved
buf.add( getSerializedReferenceValue( lockedObjects.get(0)));
} else {
// don't burn an iterator on this, 'n' is supposed to be small
int h = (n << 16) + (n % 3);
for (int i=0; i<n; i++){
int rot = (getSerializedReferenceValue( lockedObjects.get(i))) % 31;
h ^= (h << rot) | (h >>> (32 - rot)); // rotate left
}
buf.add( h);
}
}
}
protected void serializeThreadStates (){
ThreadList tl = ks.getThreadList();
for (ThreadInfo ti : tl) {
if (ti.isAlive()) {
serializeThreadState(ti);
}
}
}
protected void serializeClassLoaders(){
buf.add(ks.classLoaders.size());
for (ClassLoaderInfo cl : ks.classLoaders) {
if(cl.isAlive()) {
serializeStatics(cl.getStatics());
}
}
}
protected void serializeStatics(Statics statics){
buf.add(statics.size());
for (StaticElementInfo sei : statics.liveStatics()) {
serializeClass(sei);
}
}
protected void serializeClass (StaticElementInfo sei){
buf.add(sei.getStatus());
Fields fields = sei.getFields();
ClassInfo ci = sei.getClassInfo();
FinalBitSet filtered = getStaticFilterMask(ci);
FinalBitSet refs = getStaticRefMask(ci);
int max = ci.getStaticDataSize();
for (int i = 0; i < max; i++) {
if (!filtered.get(i)) {
Conditional<Integer> v = fields.getIntValue(i);
if (refs.get(i)) {
processReference(v.getValue(true));
} else {
buf.add(v);
}
}
}
}
//--- our main purpose in life
@Override
protected Conditional<Integer>[] computeStoringData() {
buf.clear();
heap = ks.getHeap();
initReferenceQueue();
//--- serialize all live objects and loaded classes
serializeStackFrames();
serializeClassLoaders();
processReferenceQueue();
//--- now serialize the thread states (which might refer to live objects)
// we do this last because threads contain some internal references
// (locked objects etc) that should NOT set the canonical reference serialization
// values (if they are encountered before their first explicit heap reference)
serializeThreadStates();
return buf.toArray();
}
protected void dumpData() {
int n = buf.size();
System.out.print("serialized data: [");
for (int i=0; i<n; i++) {
if (i>0) {
System.out.print(',');
}
System.out.print(buf.get(i));
}
System.out.println(']');
}
}