//
// 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.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.ALOAD;
import gov.nasa.jpf.jvm.bytecode.ArrayStoreInstruction;
import gov.nasa.jpf.jvm.bytecode.FieldInstruction;
import gov.nasa.jpf.jvm.bytecode.GETFIELD;
import gov.nasa.jpf.jvm.bytecode.GETSTATIC;
import gov.nasa.jpf.jvm.bytecode.StoreInstruction;
import gov.nasa.jpf.jvm.bytecode.VariableAccessor;
import gov.nasa.jpf.report.ConsolePublisher;
import gov.nasa.jpf.report.Publisher;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.util.MethodSpec;
import gov.nasa.jpf.util.StringSetMatcher;
import gov.nasa.jpf.vm.ElementInfo;
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.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* simple listener tool to find out which variables (locals and fields) are
* changed how often and from where. This should give a good idea if a state
* space blows up because of some counter/timer vars, and where to apply the
* necessary abstractions to close/shrink it
*
*/
public class VarTracker extends ListenerAdapter {
// our matchers to determine which variables we have to report
StringSetMatcher includeVars = null; // means all
StringSetMatcher excludeVars = null; // means none
// filter methods from which access happens
MethodSpec methodSpec;
int maxVars; // maximum number of variables shown
ArrayList<VarChange> queue = new ArrayList<VarChange>();
ThreadInfo lastThread;
HashMap<String, VarStat> stat = new HashMap<String, VarStat>();
int nStates = 0;
int maxDepth;
public VarTracker (Config config, JPF jpf){
includeVars = StringSetMatcher.getNonEmpty(config.getStringArray("vt.include"));
excludeVars = StringSetMatcher.getNonEmpty(config.getStringArray("vt.exclude",
new String[] {"java.*", "javax.*"} ));
maxVars = config.getInt("vt.max_vars", 25);
methodSpec = MethodSpec.createMethodSpec(config.getString("vt.methods", "!java.*.*"));
jpf.addPublisherExtension(ConsolePublisher.class, this);
}
@Override
public void publishPropertyViolation (Publisher publisher) {
PrintWriter pw = publisher.getOut();
publisher.publishTopicStart("field access ");
report(pw);
}
void print (PrintWriter pw, int n, int length) {
String s = Integer.toString(n);
int l = length - s.length();
for (int i=0; i<l; i++) {
pw.print(' ');
}
pw.print(s);
}
void report (PrintWriter pw) {
pw.println();
pw.println(" change variable");
pw.println("---------------------------------------");
Collection<VarStat> values = stat.values();
List<VarStat> valueList = new ArrayList<VarStat>();
valueList.addAll(values);
Collections.sort(valueList);
int n = 0;
for (VarStat s : valueList) {
if (n++ > maxVars) {
break;
}
print(pw, s.nChanges, 12);
pw.print(" ");
pw.println(s.id);
}
}
@Override
public void stateAdvanced(Search search) {
if (search.isNewState()) { // don't count twice
int stateId = search.getStateId();
nStates++;
int depth = search.getDepth();
if (depth > maxDepth) maxDepth = depth;
if (!queue.isEmpty()) {
for (Iterator<VarChange> it = queue.iterator(); it.hasNext(); ){
VarChange change = it.next();
String id = change.getVariableId();
VarStat s = stat.get(id);
if (s == null) {
s = new VarStat(id, stateId);
stat.put(id, s);
} else {
// no good - we should filter during reg (think of large vectors or loop indices)
if (s.lastState != stateId) { // count only once per new state
s.nChanges++;
s.lastState = stateId;
}
}
}
}
}
queue.clear();
}
// <2do> - general purpose listeners should not use types such as String for storing
// attributes, there is no good way to make sure you retrieve your own attributes
@Override
public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
String varId;
if ( ((((executedInsn instanceof GETFIELD) || (executedInsn instanceof GETSTATIC)))
&& ((FieldInstruction)executedInsn).isReferenceField()) ||
(executedInsn instanceof ALOAD)) {
// a little extra work - we need to keep track of variable names, because
// we cannot easily retrieve them in a subsequent xASTORE, which follows
// a pattern like: ..GETFIELD.. some-stack-operations .. xASTORE
StackFrame frame = ti.getTopFrame();
int objRef = frame.peek(FeatureExprFactory.True()).getValue();
if (objRef != MJIEnv.NULL) {
ElementInfo ei = ti.getElementInfo(objRef);
if (ei.isArray()) {
varId = ((VariableAccessor)executedInsn).getVariableId();
// <2do> unfortunately, we can't filter here because we don't know yet
// how the array ref will be used (we would only need the attr for
// subsequent xASTOREs)
frame = ti.getModifiableTopFrame();
frame.addOperandAttr( varId);
}
}
}
// here come the changes - note that we can't update the stats right away,
// because we don't know yet if the state this leads into has already been
// visited, and we want to detect only var changes that lead to *new* states
// (objective is to find out why we have new states)
else if (executedInsn instanceof StoreInstruction) {
if (executedInsn instanceof ArrayStoreInstruction) {
// did we have a name for the array?
// stack is ".. ref idx [l]value => .."
// <2do> String is not a good attribute type to retrieve
StackFrame frame = ti.getTopFrame();
String attr = frame.getOperandAttr(1, String.class);
if (attr != null) {
varId = attr + "[]";
} else {
varId = "?[]";
}
} else {
varId = ((VariableAccessor)executedInsn).getVariableId();
}
if (isMethodRelevant(executedInsn.getMethodInfo()) && isVarRelevant(varId)) {
queue.add(new VarChange(varId));
lastThread = ti;
}
}
}
boolean isMethodRelevant (MethodInfo mi){
return methodSpec.matches(mi);
}
boolean isVarRelevant (String varId) {
if (!StringSetMatcher.isMatch(varId, includeVars, excludeVars)){
return false;
}
// filter subsequent changes in the same transition (to avoid gazillions of
// VarChanges for loop variables etc.)
// <2do> this is very inefficient - improve
for (int i=0; i<queue.size(); i++) {
VarChange change = queue.get(i);
if (change.getVariableId().equals(varId)) {
return false;
}
}
return true;
}
}
// <2do> expand into types to record value ranges
class VarStat implements Comparable<VarStat> {
String id; // class[@objRef].field || class[@objref].method.local
int nChanges;
int lastState; // this was changed in (<2do> don't think we need this)
// might have more info in the future, e.g. total number of changes vs.
// number of states incl. this var change, source locations, threads etc.
VarStat (String varId, int stateId) {
id = varId;
nChanges = 1;
lastState = stateId;
}
int getChangeCount () {
return nChanges;
}
public int compareTo (VarStat other) {
if (other.nChanges > nChanges) {
return 1;
} else if (other.nChanges == nChanges) {
return 0;
} else {
return -1;
}
}
}
// <2do> expand into types to record values
class VarChange {
String id;
VarChange (String varId) {
id = varId;
}
String getVariableId () {
return id;
}
}