//
// 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.listener;
import java.util.HashMap;
import de.fosd.typechef.featureexpr.FeatureExpr;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.ArrayInstruction;
import gov.nasa.jpf.jvm.bytecode.ArrayLoadInstruction;
import gov.nasa.jpf.jvm.bytecode.FieldInstruction;
import gov.nasa.jpf.jvm.bytecode.LocalVariableInstruction;
import gov.nasa.jpf.jvm.bytecode.StoreInstruction;
import gov.nasa.jpf.jvm.bytecode.VariableAccessor;
import gov.nasa.jpf.util.StringSetMatcher;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.FieldInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MJIEnv;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.StackFrame;
import gov.nasa.jpf.vm.Step;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.Types;
import gov.nasa.jpf.vm.VM;
/**
* Simple listener tool to record the values of variables as they are accessed.
* Records the information in Step.setComment() so that when the trace is
* written the values can be written too.
*/
public class VarRecorder extends ListenerAdapter {
private final HashMap<ThreadInfo, String> pendingComment = new HashMap<ThreadInfo, String>();
private final StringSetMatcher includes;
private final StringSetMatcher excludes;
private final boolean recordFields;
private final boolean recordLocals;
private final boolean recordArrays;
private ClassInfo lastClass;
private boolean recordClass;
public VarRecorder(Config config) {
includes = StringSetMatcher.getNonEmpty(config.getStringArray("var_recorder.include"));
excludes = StringSetMatcher.getNonEmpty(config.getStringArray("var_recorder.exclude"));
recordFields = config.getBoolean("var_recorder.fields", true);
recordLocals = config.getBoolean("var_recorder.locals", true);
recordArrays = config.getBoolean("var_recorder.arrays", true);
}
@Override
public void executeInstruction(FeatureExpr ctx, VM vm, ThreadInfo ti, Instruction insnToExecute) {
String name, value;
byte type;
if (!canRecord(vm, insnToExecute))
return;
if (!(insnToExecute instanceof StoreInstruction))
if (!(insnToExecute instanceof ArrayLoadInstruction))
return;
type = getType(ti, insnToExecute);
name = getName(ti, insnToExecute, type);
if (insnToExecute instanceof ArrayLoadInstruction) {
setComment(vm, ti, name, "", "", true);
saveVariableType(ti, type);
} else {
value = getValue(ti, insnToExecute, type);
setComment(vm, ti, name, " <- ", value, true);
}
}
@Override
public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
String name, value;
byte type;
if (!canRecord(vm, executedInsn))
return;
if (executedInsn instanceof StoreInstruction) {
name = pendingComment.remove(ti);
setComment(vm, ti, name, "", "", false);
return;
}
type = getType(ti, executedInsn);
value = getValue(ti, executedInsn, type);
if (executedInsn instanceof ArrayLoadInstruction)
name = pendingComment.remove(ti);
else
name = getName(ti, executedInsn, type);
if (isArrayReference(vm, ti))
saveVariableName(ti, name);
else
saveVariableType(ti, type);
setComment(vm, ti, name, " -> ", value, false);
}
private final void setComment(VM vm, ThreadInfo ti, String name, String operator, String value, boolean pending) {
Step step;
String comment;
if (name == null)
return;
if (value == null)
return;
comment = name + operator + value;
if (pending) {
pendingComment.put(ti, comment);
} else {
step = vm.getLastStep();
step.setComment(name + operator + value);
}
}
private final boolean canRecord(VM vm, Instruction inst) {
ClassInfo ci;
MethodInfo mi;
if (vm.getLastStep() == null)
return(false);
if (!(inst instanceof VariableAccessor))
if (!(inst instanceof ArrayInstruction))
return(false);
mi = inst.getMethodInfo();
if (mi == null)
return(false);
ci = mi.getClassInfo();
if (ci == null)
return(false);
if (lastClass != ci) {
lastClass = ci;
recordClass = StringSetMatcher.isMatch(ci.getName(), includes, excludes);
}
return(recordClass);
}
// <2do> general purpose listeners should not use anonymous attribute types such as String
private final void saveVariableName(ThreadInfo ti, String name) {
StackFrame frame = ti.getModifiableTopFrame();
frame.addOperandAttr(name);
}
private final void saveVariableType(ThreadInfo ti, byte type) {
StackFrame frame;
String str;
frame = ti.getModifiableTopFrame();
if (frame.getTopPos() < 0) {
return;
}
str = encodeType(type);
frame.addOperandAttr(str);
}
private final boolean isArrayReference(VM vm, ThreadInfo ti) {
StackFrame frame = ti.getTopFrame();
if (frame.getTopPos() < 0) {
return(false);
}
if (!frame.isOperandRef()) {
return(false);
}
int objRef = frame.peek(FeatureExprFactory.True()).getValue();
if (objRef == MJIEnv.NULL) {
return(false);
}
ElementInfo ei = ti.getElementInfo(objRef);
if (ei == null) {
return(false);
}
return(ei.isArray());
}
private byte getType(ThreadInfo ti, Instruction inst) {
StackFrame frame;
FieldInfo fi;
String type;
frame = ti.getTopFrame();
if ((frame.getTopPos() >= 0) && (frame.isOperandRef())) {
return (Types.T_REFERENCE);
}
type = null;
if (((recordLocals) && (inst instanceof LocalVariableInstruction))
|| ((recordFields) && (inst instanceof FieldInstruction))) {
if (inst instanceof LocalVariableInstruction) {
type = ((LocalVariableInstruction) inst).getLocalVariableType();
} else {
fi = ((FieldInstruction) inst).getFieldInfo(null);
type = fi.getType();
}
}
if ((recordArrays) && (inst instanceof ArrayInstruction)) {
return (getTypeFromInstruction(inst));
}
if (type == null) {
return (Types.T_VOID);
}
return (decodeType(type));
}
private final static byte getTypeFromInstruction(Instruction inst) {
if (inst instanceof ArrayInstruction)
return(getTypeFromInstruction((ArrayInstruction) inst));
return(Types.T_VOID);
}
private final static byte getTypeFromInstruction(ArrayInstruction inst) {
String name;
name = inst.getClass().getName();
name = name.substring(name.lastIndexOf('.') + 1);
switch (name.charAt(0)) {
case 'A': return(Types.T_REFERENCE);
case 'B': return(Types.T_BYTE); // Could be a boolean but it is better to assume a byte.
case 'C': return(Types.T_CHAR);
case 'F': return(Types.T_FLOAT);
case 'I': return(Types.T_INT);
case 'S': return(Types.T_SHORT);
case 'D': return(Types.T_DOUBLE);
case 'L': return(Types.T_LONG);
}
return(Types.T_VOID);
}
private final static String encodeType(byte type) {
switch (type) {
case Types.T_BYTE: return("B");
case Types.T_CHAR: return("C");
case Types.T_DOUBLE: return("D");
case Types.T_FLOAT: return("F");
case Types.T_INT: return("I");
case Types.T_LONG: return("J");
case Types.T_REFERENCE: return("L");
case Types.T_SHORT: return("S");
case Types.T_VOID: return("V");
case Types.T_BOOLEAN: return("Z");
case Types.T_ARRAY: return("[");
}
return("?");
}
private final static byte decodeType(String type) {
if (type.charAt(0) == '?'){
return(Types.T_REFERENCE);
} else {
return Types.getBuiltinType(type);
}
}
private String getName(ThreadInfo ti, Instruction inst, byte type) {
String name;
int index;
boolean store;
if (((recordLocals) && (inst instanceof LocalVariableInstruction)) ||
((recordFields) && (inst instanceof FieldInstruction))) {
name = ((VariableAccessor) inst).getVariableId();
name = name.substring(name.lastIndexOf('.') + 1);
return(name);
}
if ((recordArrays) && (inst instanceof ArrayInstruction)) {
store = inst instanceof StoreInstruction;
name = getArrayName(ti, type, store);
index = getArrayIndex(ti, type, store);
return(name + '[' + index + ']');
}
return(null);
}
private String getValue(ThreadInfo ti, Instruction inst, byte type) {
StackFrame frame;
int lo, hi;
frame = ti.getTopFrame();
if (((recordLocals) && (inst instanceof LocalVariableInstruction)) ||
((recordFields) && (inst instanceof FieldInstruction)))
{
if (frame.getTopPos() < 0)
return(null);
lo = frame.peek(FeatureExprFactory.True()).getValue();
hi = frame.getTopPos() >= 1 ? frame.peek(FeatureExprFactory.True(), 1).getValue() : 0;
return(decodeValue(type, lo, hi));
}
if ((recordArrays) && (inst instanceof ArrayInstruction))
return(getArrayValue(ti, type));
return(null);
}
private String getArrayName(ThreadInfo ti, byte type, boolean store) {
String attr;
int offset;
offset = calcOffset(type, store) + 1;
// <2do> String is really not a good attribute type to retrieve!
StackFrame frame = ti.getTopFrame();
attr = frame.getOperandAttr( offset, String.class);
if (attr != null) {
return(attr);
}
return("?");
}
private int getArrayIndex(ThreadInfo ti, byte type, boolean store) {
int offset;
offset = calcOffset(type, store);
return(ti.getTopFrame().peek(FeatureExprFactory.True(), offset).getValue());
}
private final static int calcOffset(byte type, boolean store) {
if (!store)
return(0);
return(Types.getTypeSize(type));
}
private String getArrayValue(ThreadInfo ti, byte type) {
StackFrame frame;
int lo, hi;
frame = ti.getTopFrame();
lo = frame.peek(FeatureExprFactory.True()).getValue();
hi = frame.getTopPos() >= 1 ? frame.peek(FeatureExprFactory.True(), 1).getValue() : 0;
return(decodeValue(type, lo, hi));
}
private final static String decodeValue(byte type, int lo, int hi) {
switch (type) {
case Types.T_ARRAY: return(null);
case Types.T_VOID: return(null);
case Types.T_BOOLEAN: return(String.valueOf(Types.intToBoolean(lo)));
case Types.T_BYTE: return(String.valueOf(lo));
case Types.T_CHAR: return(String.valueOf((char) lo));
case Types.T_DOUBLE: return(String.valueOf(Types.intsToDouble(lo, hi)));
case Types.T_FLOAT: return(String.valueOf(Types.intToFloat(lo)));
case Types.T_INT: return(String.valueOf(lo));
case Types.T_LONG: return(String.valueOf(Types.intsToLong(lo, hi)));
case Types.T_SHORT: return(String.valueOf(lo));
case Types.T_REFERENCE:
ElementInfo ei = VM.getVM().getHeap().get(lo);
if (ei == null)
return(null);
ClassInfo ci = ei.getClassInfo();
if (ci == null)
return(null);
if (ci.getName().equals("java.lang.String"))
return('"' + ei.asString().getValue() + '"');
return(ei.toString());
default:
System.err.println("Unknown type: " + type);
return(null);
}
}
}