//
// Copyright (C) 2010 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.Error;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.JPFConfigException;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.Property;
import gov.nasa.jpf.jvm.bytecode.EXECUTENATIVE;
import gov.nasa.jpf.jvm.bytecode.FieldInstruction;
import gov.nasa.jpf.jvm.bytecode.INVOKESTATIC;
import gov.nasa.jpf.jvm.bytecode.InstanceFieldInstruction;
import gov.nasa.jpf.jvm.bytecode.InstanceInvocation;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.jvm.bytecode.LockInstruction;
import gov.nasa.jpf.jvm.bytecode.PUTFIELD;
import gov.nasa.jpf.jvm.bytecode.PUTSTATIC;
import gov.nasa.jpf.jvm.bytecode.StaticFieldInstruction;
import gov.nasa.jpf.report.ConsolePublisher;
import gov.nasa.jpf.report.Publisher;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.util.FileUtils;
import gov.nasa.jpf.util.Misc;
import gov.nasa.jpf.vm.ChoiceGenerator;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.ExceptionInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.NoUncaughtExceptionsProperty;
import gov.nasa.jpf.vm.NotDeadlockedProperty;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
/**
* an alternative Graphviz dot-file generator for simple,educational state graphs
* except of creating funny wallpapers, it doesn't help much in real life if the
* state count gets > 50, but for the small ones it's actually quite readable.
* Good for papers.
*
* normal states are labeled with their numeric ids, end states are double circled.
* start, end and error states are color filled
*
* edges have two labels: the choice value at the beginning and the CG cause
* at the end. Only the first incoming edge into a state shows the CG cause
*
* we only render one backtrack edge per from-state
*
* <2do> GraphViz doesn't seem to handle color or fontname for head/tail labels correctly
*/
public class SimpleDot extends ListenerAdapter {
static final String GRAPH_ATTRS = "pad=0.5";
static final String GENERIC_NODE_ATTRS = "shape=circle,style=filled,fillcolor=white";
static final String GENERIC_EDGE_ATTRS = "fontsize=10,fontname=Helvetica,fontcolor=blue,color=cadetblue,style=\"setlinewidth(0.5)\",arrowhead=empty,arrowsize=0.5";
static final String START_NODE_ATTRS = "fillcolor=green";
static final String END_NODE_ATTRS = "shape=doublecircle,fillcolor=cyan";
static final String ERROR_NODE_ATTRS = "color=red,fillcolor=lightcoral";
static final String BACKTRACK_EDGE_ATTRS = "arrowhead=onormal,color=gray52,style=\"dotted\"";
static final String RESTORED_EDGE_ATTRS = "arrowhead=onormal,color=red,style=\"dotted\"";
static final String NEW_EDGE_ATTRS = "arrowhead=normal";
static final String VISITED_EDGE_ATTRS = "arrowhead=vee";
//--- configurable Graphviz attributes
protected String graphAttrs;
protected String genericNodeAttrs;
protected String genericEdgeAttrs;
protected String startNodeAttrs;
protected String endNodeAttrs;
protected String errorNodeAttrs;
protected String newEdgeAttrs;
protected String visitedEdgeAttrs;
protected String backtrackEdgeAttrs;
protected String restoreEdgeAttrs;
protected boolean showTarget;
protected boolean printFile;
protected VM vm;
protected String app;
protected File file;
protected PrintWriter pw;
protected int lastId = -1; // where we come from
protected String lastErrorId;
protected ElementInfo lastEi;
protected ThreadInfo lastTi; // the last started thread
// helper because GraphViz cannot eliminate duplicate edges
HashSet<Long> seenEdges;
public SimpleDot( Config config, JPF jpf){
graphAttrs = config.getString("dot.graph_attr", GRAPH_ATTRS);
genericNodeAttrs = config.getString("dot.node_attr", GENERIC_NODE_ATTRS);
genericEdgeAttrs = config.getString("dot.edge_attr", GENERIC_EDGE_ATTRS);
newEdgeAttrs = config.getString("dot.new_edge_attr", NEW_EDGE_ATTRS);
visitedEdgeAttrs = config.getString("dot.visited_edge_attr", VISITED_EDGE_ATTRS);
startNodeAttrs = config.getString("dot.start_node_attr", START_NODE_ATTRS);
endNodeAttrs = config.getString("dot.end_node_attr", END_NODE_ATTRS);
errorNodeAttrs = config.getString("dot.error_node_attr", ERROR_NODE_ATTRS);
backtrackEdgeAttrs = config.getString("dot.bt_edge_attr", BACKTRACK_EDGE_ATTRS);
restoreEdgeAttrs = config.getString("dot.restore_edge_attr", RESTORED_EDGE_ATTRS);
printFile = config.getBoolean("dot.print_file", false);
showTarget = config.getBoolean("dot.show_target", false);
// app and filename are not known until the search is started
jpf.addPublisherExtension(ConsolePublisher.class, this);
}
void initialize (VM vm){
Config config = vm.getConfig();
app = vm.getSUTName();
app = app.replace("+", "__");
app = app.replace('.', '_');
String fname = config.getString("dot.file");
if (fname == null){
fname = app + ".dot";
}
try {
file = new File(fname);
FileWriter fw = new FileWriter(file);
pw = new PrintWriter(fw);
} catch (IOException iox){
throw new JPFConfigException("unable to open SimpleDot output file: " + fname);
}
seenEdges = new HashSet<Long>();
}
//--- the listener interface
@Override
public void searchStarted(Search search){
vm = search.getVM();
initialize(vm);
printHeader();
printStartState("S");
}
@Override
public void stateAdvanced(Search search){
int id = search.getStateId();
long edgeId = ((long)lastId << 32) | id;
if (id <0 || seenEdges.contains(edgeId)){
return; // skip the root state and property violations (reported separately)
}
if (search.isErrorState()) {
String eid = "e" + search.getNumberOfErrors();
printTransition(getStateId(lastId), eid, getLastChoice(), getError(search));
printErrorState(eid);
lastErrorId = eid;
} else if (search.isNewState()) {
if (search.isEndState()) {
printTransition(getStateId(lastId), getStateId(id), getLastChoice(), "return");
printEndState(getStateId(id));
} else {
printTransition(getStateId(lastId), getStateId(id), getLastChoice(), getNextCG());
}
} else { // already visited state
printTransition(getStateId(lastId), getStateId(id), getLastChoice(), null);
}
seenEdges.add(edgeId);
lastId = id;
}
@Override
public void stateBacktracked(Search search){
int id = search.getStateId();
long edgeId = ((long)lastId << 32) | id;
if (!seenEdges.contains(edgeId)) {
if(lastErrorId!=null) {
printBacktrack(lastErrorId, getStateId(id));
lastErrorId = null;
} else {
printBacktrack(getStateId(lastId), getStateId(id));
}
seenEdges.add(edgeId);
}
lastId = id;
}
@Override
public void stateRestored(Search search) {
int id = search.getStateId();
long edgeId = ((long)lastId << 32) | id;
if (!seenEdges.contains(edgeId)) {
printRestored(getStateId(lastId), getStateId(id));
seenEdges.add(edgeId);
}
lastId = id;
}
@Override
public void searchFinished (Search search){
pw.println("}");
pw.close();
}
@Override
public void threadStarted (VM vm, ThreadInfo ti){
lastTi = ti;
}
@Override
public void objectWait (VM vm, ThreadInfo ti, ElementInfo ei){
lastEi = ei;
lastTi = ti;
}
@Override
public void publishFinished (Publisher publisher) {
PrintWriter ppw = publisher.getOut();
publisher.publishTopicStart("SimpleDot");
ppw.println("dot file generated: " + file.getPath());
if (printFile){
ppw.println();
FileUtils.printFile(ppw,file);
}
}
//--- data collection
protected String getStateId (int id){
return id < 0 ? "S" : Integer.toString(id);
}
protected String getLastChoice() {
ChoiceGenerator<?> cg = vm.getChoiceGenerator();
Object choice = cg.getNextChoice();
if (choice instanceof ThreadInfo){
int idx = ((ThreadInfo)choice).getId();
return "T"+idx;
} else {
return choice.toString(); // we probably want more here
}
}
// this is the only method that's more tricky - we have to find a balance
// between being conscious enough to not clutter the graph, and expressive
// enough to understand it.
// <2do> this doesn't deal well with custom or data CGs yet
protected String getNextCG(){
ChoiceGenerator<?> cg = vm.getNextChoiceGenerator(); // that's the next one
Instruction insn;
if (cg.getInsn() instanceof One)
{
insn = cg.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
insn = null;
}
if (insn instanceof EXECUTENATIVE) {
return getNativeExecCG((EXECUTENATIVE)insn);
} else if (insn instanceof FieldInstruction) { // shared object field access
return getFieldAccessCG((FieldInstruction) insn);
} else if (insn instanceof LockInstruction){ // monitor_enter
return getLockCG((LockInstruction) insn);
} else if (insn instanceof InvokeInstruction){ // sync method invoke
return getInvokeCG((InvokeInstruction) insn);
}
return insn.getMnemonic(); // our generic fallback
}
protected String getNativeExecCG (EXECUTENATIVE insn){
MethodInfo mi = insn.getExecutedMethod();
String s = mi.getName();
if (s.equals("start")) {
s = "T" + lastTi.getId() + ".start";
} else if (s.equals("wait")) {
s = "T" + lastTi.getId() + ".wait";
}
return s;
}
protected String getFieldAccessCG (FieldInstruction insn){
String s;
if (insn instanceof InstanceFieldInstruction) {
if (insn instanceof PUTFIELD) {
s = "put";
} else /* if (insn instanceof GETFIELD) */ {
s = "get";
}
if (showTarget){
int ref = ((InstanceFieldInstruction) insn).getLastThis();
s = getInstanceRef(ref) + '.' + s;
}
} else /* if (insn instanceof StaticFieldInstruction) */ {
if (insn instanceof PUTSTATIC) {
s = "put";
} else /* if (insn instanceof GETSTATIC) */ {
s = "get";
}
if (showTarget){
String clsName = ((StaticFieldInstruction) insn).getLastClassName();
s = Misc.stripToLastDot(clsName) + '.' + s;
}
}
String varId = Misc.stripToLastDot(((FieldInstruction) insn).getVariableId());
s = s + ' ' + varId;
return s;
}
protected String getLockCG(LockInstruction insn){
String s = "sync";
if (showTarget){
int ref = insn.getLastLockRef();
s = getInstanceRef(ref) + '.' + s;
}
return s;
}
protected String getInvokeCG (InvokeInstruction insn){
MethodInfo mi = insn.getInvokedMethod();
String s = mi.getName() + "()";
if (showTarget){
if (insn instanceof InstanceInvocation) {
int ref = ((InstanceInvocation) insn).getLastObjRef();
s = getInstanceRef(ref) + '.' + s;
} else if (insn instanceof INVOKESTATIC) {
String clsName = ((INVOKESTATIC) insn).getInvokedClassName();
s = Misc.stripToLastDot(clsName) + '.' + s;
}
}
return s;
}
protected String getError (Search search){
Error error = search.getLastError();
Property prop = error.getProperty();
if (prop instanceof NoUncaughtExceptionsProperty){
ExceptionInfo xi = ((NoUncaughtExceptionsProperty)prop).getUncaughtExceptionInfo();
return Misc.stripToLastDot(xi.getExceptionClassname());
} else if (prop instanceof NotDeadlockedProperty){
return "deadlock";
}
// fallback
return Misc.stripToLastDot(prop.getClass().getName());
}
protected static String getInstanceRef (int ref){
return "@" + Integer.toHexString(ref).toUpperCase();
}
protected static String getClassObjectRef (int ref){
return "#" + Integer.toHexString(ref).toUpperCase();
}
//--- dot file stuff
protected void printHeader(){
pw.print("digraph ");
pw.print(app);
pw.println(" {");
pw.print("node [");
pw.print(genericNodeAttrs);
pw.println(']');
pw.print("edge [");
pw.print(genericEdgeAttrs);
pw.println(']');
pw.println(graphAttrs);
pw.println();
pw.print("label=\"");
pw.print(app);
pw.print("\"");
pw.println();
}
protected void printTransition(String fromState, String toState, String choiceVal, String cgCause){
pw.println();
pw.print(fromState);
pw.print(" -> ");
pw.print( toState);
pw.print(" [label=\"");
pw.print(choiceVal);
pw.print('"');
if (cgCause != null){
pw.print(NEW_EDGE_ATTRS);
pw.print(",headlabel=\"");
pw.print(cgCause);
pw.print('"');
} else {
pw.print(VISITED_EDGE_ATTRS);
}
pw.println(']');
}
protected void printBacktrack (String fromState, String toState){
pw.println();
pw.print(fromState);
pw.print(" -> ");
pw.print( toState);
pw.print(" [");
pw.print(backtrackEdgeAttrs);
pw.print(']');
pw.println(" // backtrack");
}
protected void printRestored (String fromState, String toState){
pw.println();
pw.print(fromState);
pw.print(" -> ");
pw.print( toState);
pw.print(" [");
pw.print(restoreEdgeAttrs);
pw.print(']');
pw.println(" // restored");
}
protected void printStartState(String stateId){
pw.print(stateId);
pw.print(" [");
pw.print(startNodeAttrs);
pw.print(']');
pw.println(" // start state");
}
protected void printEndState(String stateId){
pw.print(stateId);
pw.print(" [");
pw.print(endNodeAttrs);
pw.print(']');
pw.println(" // end state");
}
protected void printErrorState(String error){
pw.print(error);
pw.print(" [");
pw.print(errorNodeAttrs);
pw.print(']');
pw.println(" // error state");
}
}