//
// Copyright (C) 2007 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 cmu.conditional.One;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.FieldInstruction;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.jvm.bytecode.MONITORENTER;
import gov.nasa.jpf.jvm.bytecode.MONITOREXIT;
import gov.nasa.jpf.jvm.bytecode.ReturnInstruction;
import gov.nasa.jpf.report.ConsolePublisher;
import gov.nasa.jpf.report.Publisher;
import gov.nasa.jpf.report.PublisherExtension;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.vm.BooleanChoiceGenerator;
import gov.nasa.jpf.vm.ChoiceGenerator;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.DoubleChoiceGenerator;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.IntChoiceGenerator;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.ThreadChoiceGenerator;
import gov.nasa.jpf.vm.VM;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* a listener that collects information about ChoiceGenerators, choices and
* where they are used. The purpose is to find out what causes the state space
* size, and to give hints of how to reduce it.
* The interesting part is that this is a listener that doesn't work off traces,
* but needs to collect info up to a point where we want it to report. That's
* state space or resource related, i.e. a combination of
*
* - number of transitions
* - memory consumption
* - elapsed time
*
* once the limit is reached, we stop the search and report.
*
* There are two parts we are interested in:
*
* - what CGs do we have
* - what creates those CGs (thread,insn,source) = last step insn
*/
public class StateSpaceAnalyzer extends ListenerAdapter implements PublisherExtension {
// Search termination conditions
private final long m_maxTime;
private final long m_maxMemory;
private final int m_maxStates;
private final int m_maxChoices;
private final ArrayList<CGGrouper> m_groupers = new ArrayList<CGGrouper>();
private final int m_maxOutputLines; // how many detail lines do we display in the report
private long m_terminateTime;
private int m_choiceCount;
public StateSpaceAnalyzer(Config config, JPF jpf) {
m_maxStates = config.getInt("ssa.max_states", -1);
m_maxTime = config.getDuration("ssa.max_time", -1);
m_maxMemory = config.getMemorySize("ssa.max_memory", -1);
m_maxChoices = config.getInt("ssa.max_choices", -1);
m_maxOutputLines = config.getInt("ssa.max_output_lines", 10);
initGroupers(config);
jpf.addPublisherExtension(ConsolePublisher.class, this);
}
private void initGroupers(Config config) {
HashMap<String, CGAccessor> accessors;
CGGrouper grouper;
int i;
if (config.getStringArray("ssa.sort_order") == null) {
config.setProperty("ssa.sort_order", "type");
config.setProperty("ssa.sort_order2", "package,class,method,instruction,type");
}
accessors = new HashMap<String, CGAccessor>(5);
accessors.put("package", new CGPackageAccessor());
accessors.put("class", new CGClassAccessor());
accessors.put("method", new CGMethodAccessor());
accessors.put("instruction", new CGInstructionAccessor());
accessors.put("type", new CGTypeAccessor());
m_groupers.add(initGrouper(config, "ssa.sort_order", accessors));
for (i = 2; true; i++) {
grouper = initGrouper(config, "ssa.sort_order" + i, accessors);
if (grouper == null) {
break;
}
m_groupers.add(grouper);
}
}
private CGGrouper initGrouper(Config config, String parameter, Map<String, CGAccessor> accessors) {
CGGrouper grouper;
CGAccessor list[];
StringBuilder name;
String key, sortOrder[];
int i;
sortOrder = config.getStringArray(parameter);
if ((sortOrder == null) || (sortOrder.length <= 0)) {
return (null);
}
name = new StringBuilder();
list = new CGAccessor[sortOrder.length];
for (i = 0; i < sortOrder.length; i++) {
key = sortOrder[i].toLowerCase();
name.append(key);
name.append(", ");
list[i] = accessors.get(key);
if (list[i] == null) {
config.exception("Unknown sort key: " + sortOrder[i] + ". Found in parameter: " + parameter);
}
}
name.setLength(name.length() - 2);
grouper = new CGGrouper(list, name.toString());
return (grouper);
}
@Override
public void choiceGeneratorSet(VM vm, ChoiceGenerator<?> newCG) {
int i;
// NOTE: we get this from SystemState.nextSuccessor, i.e. when the CG
// is actually used (which doesn't necessarily mean it produces a new state,
// but it got created from a new state)
// The original code stored each choice generator in an ArrayList. For long
// running tests, this would grow and cause an OutOfMemoryError. Now, the
// generators are dealt with as they are created. This means a bit more
// processing up front but huge memory savings in the long run. If the
// machine has multiple processors, a better solution would be to have a
// background thread process the generators.
m_choiceCount += newCG.getTotalNumberOfChoices();
for (i = m_groupers.size(); --i >= 0; )
m_groupers.get(i).add(newCG);
}
@Override
public void searchStarted(Search search) {
int i;
for (i = m_groupers.size(); --i >= 0; )
m_groupers.get(i).clear();
m_choiceCount = 0;
m_terminateTime = m_maxTime + System.currentTimeMillis();
}
@Override
public void stateAdvanced(Search search) {
if (shouldTerminate(search)) {
search.terminate();
}
}
private boolean shouldTerminate(Search search) {
if ((m_maxStates >= 0) && (search.getVM().getStateCount() >= m_maxStates)) {
return (true);
}
if ((m_maxTime >= 0) && (System.currentTimeMillis() >= m_terminateTime)) {
return (true);
}
if ((m_maxMemory >= 0) && (Runtime.getRuntime().totalMemory() >= m_maxMemory)) {
return (true);
}
if ((m_maxChoices >= 0) && (m_choiceCount >= m_maxChoices)) {
return (true);
}
return (false);
}
@Override
public void publishFinished(Publisher publisher) {
CGGrouper groupers[];
groupers = new CGGrouper[m_groupers.size()];
m_groupers.toArray(groupers);
if (publisher instanceof ConsolePublisher) {
new PublishConsole((ConsolePublisher) publisher, groupers, m_maxOutputLines).publish();
}
}
private enum CGType {
FieldAccess,
ObjectWait,
ObjectNotify,
SyncEnter,
SyncExit,
ThreadStart,
ThreadTerminate,
ThreadSuspend,
ThreadResume,
SyncCall,
SyncReturn,
AtomicOp,
DataChoice
}
private interface CGAccessor {
public Object getValue(ChoiceGenerator<?> generator);
}
private static class CGPackageAccessor implements CGAccessor {
public Object getValue(ChoiceGenerator<?> generator) {
ClassInfo ci;
MethodInfo mi;
Instruction instruction;
if (generator.getInsn() instanceof One) {
instruction = (Instruction)generator.getInsn().getValue();
}
else{
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: " + this);
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
instruction = null;
}
if (instruction == null) {
return (null);
}
mi = instruction.getMethodInfo();
if (mi == null) {
return (null);
}
ci = mi.getClassInfo();
if (ci == null) {
return (null);
}
return (ci.getPackageName());
}
}
private static class CGClassAccessor implements CGAccessor {
public Object getValue(ChoiceGenerator<?> generator) {
ClassInfo ci;
MethodInfo mi;
Instruction instruction;
if (generator.getInsn() instanceof One) {
instruction = (Instruction) generator.getInsn().getValue();
}
else{
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: " + this);
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
instruction = null;
}
if (instruction == null) {
return (null);
}
mi = instruction.getMethodInfo();
if (mi == null) {
return (null);
}
ci = mi.getClassInfo();
if (ci == null) {
return (null);
}
return (ci.getName());
}
}
private static class CGMethodAccessor implements CGAccessor {
public Object getValue(ChoiceGenerator<?> generator) {
MethodInfo mi;
Instruction instruction;
if (generator.getInsn() instanceof One) {
instruction = (Instruction) generator.getInsn().getValue();
}
else{
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: " + this);
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
instruction = null;
}
if (instruction == null) {
return (null);
}
mi = instruction.getMethodInfo();
if (mi == null) {
return (null);
}
return (mi.getFullName());
}
}
private static class CGInstructionAccessor implements CGAccessor {
public Object getValue(ChoiceGenerator<?> generator) {
return (generator.getInsn());
}
}
private static class CGTypeAccessor implements CGAccessor {
private static final String OBJECT_CLASS_NAME = Object.class.getName();
private static final String THREAD_CLASS_NAME = Thread.class.getName();
public Object getValue(ChoiceGenerator<?> generator) {
if (generator instanceof ThreadChoiceGenerator) {
return (getType((ThreadChoiceGenerator) generator));
}
if (generator instanceof BooleanChoiceGenerator) {
return (CGType.DataChoice);
}
if (generator instanceof DoubleChoiceGenerator) {
return (CGType.DataChoice);
}
if (generator instanceof IntChoiceGenerator) {
return (CGType.DataChoice);
}
if (generator instanceof BooleanChoiceGenerator) {
return (CGType.DataChoice);
}
return (null);
}
private static CGType getType(ThreadChoiceGenerator generator) {
Instruction instruction;
if (generator.getInsn() instanceof One) {
instruction = generator.getInsn().getValue();
}
else{
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: StateSpaceAnalyzer:getType(ThreadChoiceGenerator)");
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
instruction = null;
}
if (instruction == null) {
return (null);
}
if (instruction instanceof FieldInstruction) {
return (CGType.FieldAccess);
}
if (instruction instanceof InvokeInstruction) {
return (getType((InvokeInstruction) instruction));
}
if (instruction instanceof ReturnInstruction) {
return (getType(generator, (ReturnInstruction) instruction));
}
if (instruction instanceof MONITORENTER) {
return (CGType.SyncEnter);
}
if (instruction instanceof MONITOREXIT) {
return (CGType.SyncExit);
}
return (null);
}
private static CGType getType(InvokeInstruction instruction) {
MethodInfo mi;
if (is(instruction, OBJECT_CLASS_NAME, "wait")) {
return (CGType.ObjectWait);
}
if (is(instruction, OBJECT_CLASS_NAME, "notify")) {
return (CGType.ObjectNotify);
}
if (is(instruction, OBJECT_CLASS_NAME, "notifyAll")) {
return (CGType.ObjectNotify);
}
if (is(instruction, THREAD_CLASS_NAME, "start")) {
return (CGType.ThreadStart);
}
if (is(instruction, THREAD_CLASS_NAME, "suspend")) {
return (CGType.ThreadSuspend);
}
if (is(instruction, THREAD_CLASS_NAME, "resume")) {
return (CGType.ThreadResume);
}
mi = instruction.getInvokedMethod();
if (mi.getClassName().startsWith("java.util.concurrent.atomic.")) {
return (CGType.AtomicOp);
}
if (mi.isSynchronized()) {
return (CGType.SyncCall);
}
return (null);
}
private static boolean is(InvokeInstruction instruction, String className, String methodName) {
MethodInfo mi;
ClassInfo ci;
mi = instruction.getInvokedMethod();
if (!methodName.equals(mi.getName())) {
return (false);
}
ci = mi.getClassInfo();
if (!className.equals(ci.getName())) {
return (false);
}
return (true);
}
private static CGType getType(ThreadChoiceGenerator generator, ReturnInstruction instruction) {
MethodInfo mi;
if (generator.getThreadInfo().getStackDepth() <= 1) // The main thread has 0 frames. Other threads have 1 frame.
{
return (CGType.ThreadTerminate);
}
mi = instruction.getMethodInfo();
if (mi.isSynchronized()) {
return (CGType.SyncReturn);
}
return (null);
}
}
private static class TreeNode {
private final HashMap<Object, TreeNode> m_childNodes;
private final ArrayList<Object> m_sortedValues;
private final CGAccessor m_accessors[];
private final Object m_value;
private final int m_level;
private String m_sampleGeneratorClassName;
private Instruction m_sampleGeneratorInstruction;
private int m_choiceCount;
private int m_generatorCount;
TreeNode(CGAccessor accessors[], int level, Object value) {
m_accessors = accessors;
m_level = level;
m_value = value;
if (level >= accessors.length) {
m_childNodes = null;
m_sortedValues = null;
} else {
m_sortedValues = new ArrayList<Object>();
m_childNodes = new HashMap<Object, TreeNode>();
}
}
public void add(ChoiceGenerator<?> generator) {
TreeNode child;
Object value;
m_generatorCount++;
m_choiceCount += generator.getTotalNumberOfChoices();
if (isLeaf()) {
if (m_sampleGeneratorClassName == null) {
m_sampleGeneratorClassName = generator.getClass().getName();
if (generator.getInsn() instanceof One) {
m_sampleGeneratorInstruction = (Instruction) generator.getInsn().getValue();
} else {
System.err.println("___________________________________________________");
System.err.println("[WARN] Get value of choice called: " + this);
System.err.println("---------------------------------------------------");
// Let's wait for a NullPointerException
m_sampleGeneratorInstruction = null;
}
}
return;
}
value = m_accessors[m_level].getValue(generator);
child = m_childNodes.get(value);
if (child == null) {
child = new TreeNode(m_accessors, m_level + 1, value);
m_childNodes.put(value, child);
}
child.add(generator);
}
public int getLevel() {
return (m_level);
}
public Object getValue() {
return (m_value);
}
public int getChoiceCount() {
return (m_choiceCount);
}
public int getGeneratorCount() {
return (m_generatorCount);
}
public String getSampleGeneratorClassName() {
return (m_sampleGeneratorClassName);
}
public Instruction getSampleGeneratorInstruction() {
return (m_sampleGeneratorInstruction);
}
public boolean isLeaf() {
return (m_childNodes == null);
}
public void sort() {
Comparator<Object> comparator;
if (isLeaf()) {
return;
}
m_sortedValues.clear();
m_sortedValues.addAll(m_childNodes.keySet());
comparator = new Comparator<Object>() {
public int compare(Object value1, Object value2) {
TreeNode node1, node2;
node1 = m_childNodes.get(value1);
node2 = m_childNodes.get(value2);
return (node2.getChoiceCount() - node1.getChoiceCount()); // Sort descending
}
};
Collections.sort(m_sortedValues, comparator);
for (TreeNode child : m_childNodes.values()) {
child.sort();
}
}
public List<TreeNode> tour() {
List<TreeNode> result;
result = new ArrayList<TreeNode>();
tour(result);
return (result);
}
public void tour(List<TreeNode> list) {
TreeNode child;
Object value;
int i;
list.add(this);
if (isLeaf()) {
return;
}
for (i = 0; i < m_sortedValues.size(); i++) {
value = m_sortedValues.get(i);
child = m_childNodes.get(value);
child.tour(list);
}
}
public String toString() {
StringBuilder result;
result = new StringBuilder();
if (m_value == null) {
result.append("(null)");
} else {
result.append(m_value);
}
result.append(" - L");
result.append(m_level);
result.append(" / C");
result.append(m_choiceCount);
result.append(" / G");
result.append(m_generatorCount);
result.append(" / N");
result.append(m_childNodes.size());
return (result.toString());
}
}
private static class CGGrouper {
private final CGAccessor m_accessors[];
private final String m_name;
private TreeNode m_root;
private boolean m_sorted;
CGGrouper(CGAccessor accessors[], String name) {
if (accessors.length <= 0) {
throw new IllegalArgumentException("accessors.length <= 0");
}
if (name == null) {
throw new NullPointerException("name == null");
}
m_accessors = accessors;
m_name = name;
clear();
}
public void clear() {
m_sorted = false;
m_root = new TreeNode(m_accessors, 0, "All");
}
public String getName() {
return(m_name);
}
public int getLevelCount() {
return(m_accessors.length);
}
public TreeNode getTree() {
if (!m_sorted) {
m_sorted = true;
m_root.sort();
}
return(m_root);
}
public void add(ChoiceGenerator<?> generator) {
m_sorted = false;
m_root.add(generator);
}
}
private static abstract class Publish {
protected final Publisher m_publisher;
protected final CGGrouper m_groupers[];
protected final int m_maxOutputLines;
protected PrintWriter m_output;
Publish(Publisher publisher, CGGrouper groupers[], int maxOutputLines) {
m_publisher = publisher;
m_groupers = groupers;
m_maxOutputLines = maxOutputLines;
}
public abstract void publish();
}
private static class PublishConsole extends Publish {
PublishConsole(ConsolePublisher publisher, CGGrouper[] groupers, int maxOutputLines) {
super(publisher, groupers, maxOutputLines);
m_output = publisher.getOut();
}
public void publish() {
int i;
for (i = 0; i < m_groupers.length; i++) {
publishSortedData(m_groupers[i]);
}
}
private void publishSortedData(CGGrouper grouper) {
List<TreeNode> tour;
TreeNode node;
int i, lines, levelCount;
lines = 0;
levelCount = grouper.getLevelCount();
node = grouper.getTree();
tour = node.tour();
m_publisher.publishTopicStart("Grouped By: " + grouper.getName());
for (i = 0; (i < tour.size()) && (lines < m_maxOutputLines); i++) {
node = tour.get(i);
publishTreeNode(node);
if (node.isLeaf()) {
publishDetails(node, levelCount + 1);
lines++;
}
}
if (lines >= m_maxOutputLines) {
m_output.println("...");
}
}
private void publishTreeNode(TreeNode node) {
Object value;
// Tree
publishPadding(node.getLevel());
value = node.getValue();
if (value == null) {
m_output.print("(null)");
} else {
m_output.print(value);
}
// Choices
m_output.print(" (choices: ");
m_output.print(node.getChoiceCount());
// Generators
m_output.print(", generators: ");
m_output.print(node.getGeneratorCount());
m_output.println(')');
}
private void publishDetails(TreeNode node, int levelCount) {
// ChoiceGenerator<?> generator;
Instruction instruction;
instruction = node.getSampleGeneratorInstruction();
// Location
publishPadding(levelCount);
m_output.print("Location: ");
m_output.println(instruction.getFileLocation());
// Code
publishPadding(levelCount);
m_output.print("Code: ");
m_output.println(instruction.getSourceOrLocation().trim());
// Instruction
publishPadding(levelCount);
m_output.print("Instruction: ");
m_output.println(instruction.getMnemonic());
// Position
publishPadding(levelCount);
m_output.print("Position: ");
m_output.println(instruction.getPosition());
// Generator Class
publishPadding(levelCount);
m_output.print("Generator Class: ");
m_output.println(node.getSampleGeneratorClassName());
}
private void publishPadding(int levelCount) {
int i;
for (i = levelCount; i > 0; i--) {
m_output.print(" ");
}
}
}
}