// // 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.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import gov.nasa.jpf.Config; import gov.nasa.jpf.Error; import gov.nasa.jpf.JPF; import gov.nasa.jpf.ListenerAdapter; import gov.nasa.jpf.search.Search; import gov.nasa.jpf.vm.Step; import gov.nasa.jpf.vm.Transition; import gov.nasa.jpf.vm.VM; /* * Add a state space observer to JPF and build a graph of the state space * that is explored by JPF. The graph can be generated in different formats. * The current formats that are supported are DOT (visualized by a tool * like GraphViz from ATT - http://www.graphviz.org/) and gdf (used by GUESS * from HP - http://www.hpl.hp.com/research/idl/projects/graphs/). * The graph is stored in a file called "jpf-state-space.<extension>" where * extension is ".dot" or ".gdf". By default it generates a DOT graph. * * Options: * -gdf: Generate the graph in GDF format. The default is DOT. * -transition-numbers: Include transition numbers in transition labels. * -show-source: Include source lines in transition labels. * -labelvisible: Indicates if the label on the transitions is visible (used only with the -gdf option) * -help: Prints this help information and stops. * * @date 9/12/03 * @author Owen O'Malley (Initial version generating the DOT graph) * * @date 7/17/05 * @author Masoud Mansouri-Samani (Extended to also generate the gdf graph) */ public class StateSpaceDot extends ListenerAdapter { // NODE styles constants static final int RECTANGLE = 1; static final int ELLIPSE = 2; static final int ROUND_RECTANGLE = 3; static final int RECTANGLE_WITH_TEXT = 4; static final int ELLIPSE_WITH_TEXT = 5; static final int ROUND_RECTANGLE_WITH_TEXT = 6; private static final String DOT_EXT = "dot"; private static final String GDF_EXT = "gdf"; private static final String OUT_FILENAME_NO_EXT = "jpf-state-space"; // State and transition node styles used private static final int state_node_style = ELLIPSE_WITH_TEXT; private static final int transition_node_style = RECTANGLE_WITH_TEXT; // File formats supported private static final int DOT_FORMAT=0; private static final int GDF_FORMAT=1; private BufferedWriter graph = null; private int edge_id = 0; private static boolean transition_numbers=false; private static boolean show_source=false; private static int format=DOT_FORMAT; private String out_filename = OUT_FILENAME_NO_EXT+"."+DOT_EXT; private static boolean labelvisible=false; @SuppressWarnings("unused") private static boolean helpRequested=false; /* In gdf format all the edges must come after all the nodes of the graph * are generated. So we first output the nodes as we come across them but * we store the strings for edges in the gdfEdges list and output them when * the search ends. */ ArrayList<String> gdfEdges=new ArrayList<String>(); private StateInformation prev_state = null; public StateSpaceDot(Config conf, JPF jpf) { VM vm = jpf.getVM(); vm.recordSteps(true); } public void searchStarted(Search search) { try { beginGraph(); } catch (IOException e) {} } public void searchFinished(Search search) { try { endGraph(); } catch (IOException e) {} } public void stateAdvanced(Search search) { int id = search.getStateId(); boolean has_next =search.hasNextState(); boolean is_new = search.isNewState(); try { if (format==DOT_FORMAT) { graph.write("/* searchAdvanced(" + id + ", " + makeDotLabel(search, id) + ", " + has_next + ") */"); graph.newLine(); } if (prev_state != null) { addEdge(prev_state.id, id, search); } else { prev_state = new StateInformation(); } addNode(prev_state); prev_state.reset(id, has_next, is_new); } catch (IOException e) {} } public void stateRestored (Search search) { prev_state.reset(search.getStateId(), false, false); } public void stateProcessed (Search search) { // nothing to do } public void stateBacktracked(Search search) { try { addNode(prev_state); prev_state.reset(search.getStateId(), false, false); if (format==DOT_FORMAT) { graph.write("/* searchBacktracked(" + prev_state + ") */"); graph.newLine(); } } catch (IOException e) {} } public void searchConstraintHit(Search search) { try { if (format==DOT_FORMAT) { graph.write("/* searchConstraintHit(" + search.getStateId() + ") */"); graph.newLine(); } } catch (IOException e) {} } private String getErrorMsg(Search search) { List<Error> errs = search.getErrors(); if (errs.isEmpty()) { return null; } else { return errs.get(0).getDescription(); } } public void propertyViolated(Search search) { try { prev_state.error = getErrorMsg(search); if (format==DOT_FORMAT) { graph.write("/* propertyViolated(" + search.getStateId() + ") */"); graph.newLine(); } } catch (IOException e) {} } /** * Put the header for the graph into the file. */ private void beginGraph() throws IOException { graph = new BufferedWriter(new FileWriter(out_filename)); if (format==GDF_FORMAT) { graph.write("nodedef>name,label,style,color"); } else { // dot graph.write("digraph jpf_state_space {"); } graph.newLine(); } /** * In the case of the DOT graph it is just adding the final "}" at the end. * In the case of GPF format we must output edge definition and all the * edges that we have found. */ private void endGraph() throws IOException { if(prev_state != null) addNode(prev_state); if (format==GDF_FORMAT) { graph.write("edgedef>node1,node2,label,labelvisible,directed,thread INT"); graph.newLine(); // Output all the edges that we have accumulated so far int size=gdfEdges.size(); for (int i=0; i<size; i++) { graph.write(gdfEdges.get(i)); graph.newLine(); } } else { graph.write("}"); graph.newLine(); } graph.close(); } /** * Return the string that will be used to label this state for the user. */ private String makeDotLabel(Search state, int my_id) { Transition trans = state.getTransition(); if (trans == null) { return "-init-"; } Step last_trans_step = trans.getLastStep(); if (last_trans_step == null) { return "?"; } StringBuilder result = new StringBuilder(); if (transition_numbers) { result.append(my_id); result.append("\\n"); } int thread = trans.getThreadIndex(); result.append("Thd"); result.append(thread); result.append(':'); result.append(last_trans_step.toString()); if (show_source) { String source_line=last_trans_step.getLineString(); if ((source_line != null) && !source_line.equals("")) { result.append("\\n"); StringBuilder sb=new StringBuilder(source_line); // We need to precede the dot-specific special characters which appear // in the Java source line, such as ']' and '"', with the '\' escape // characters and also to remove any new lines. replaceString(sb, "\n", ""); replaceString(sb, "]", "\\]"); replaceString(sb, "\"", "\\\""); result.append(sb.toString()); } } return result.toString(); } /** * Return the string that will be used to label this state in the GDF graph. */ private String makeGdfLabel(Search state, int my_id) { Transition trans = state.getTransition(); if (trans == null) { return "-init-"; } StringBuilder result = new StringBuilder(); if (transition_numbers) { result.append(my_id); result.append(':'); } Step last_trans_step = trans.getLastStep(); result.append(last_trans_step.toString()); if (show_source) { String source_line=last_trans_step.getLineString(); if ((source_line != null) && !source_line.equals("")) { // We need to deal with gdf-specific special characters which couls appear // in the Java source line, such as '"'. result.append(source_line); convertGdfSpecial(result); } } return result.toString(); } /** * Locates and replaces all occurrences of a given string with another given * string in an original string buffer. There seems to be a bug in Java * String's replaceAll() method which does not allow us to use it to replace * some special chars so here we use StringBuilder's replace method to do * this. * @param original The original string builder. * @param from The replaced string. * @param to The replacing string. * @return The original string builder with the substring replaced * throughout. */ private StringBuilder replaceString(StringBuilder original, String from, String to) { int indexOfReplaced=0, lastIndexOfReplaced=0; while (indexOfReplaced!=-1) { indexOfReplaced=original.indexOf(from,lastIndexOfReplaced); if (indexOfReplaced!=-1) { original.replace(indexOfReplaced, indexOfReplaced+1, to); lastIndexOfReplaced=indexOfReplaced+to.length(); } } return original; } /** * Locates and replaces all occurrences of a given string with another given * string in an original string buffer. * @param original The original string buffer. * @param from The replaced string. * @param to The replacing string. * @return The original string buffer with the sub-string replaced * throughout. */ private String replaceString(String original, String from, String to) { if ((original!=null) && (from!=null) && (to!=null)) { return replaceString(new StringBuilder(original), from, to).toString(); } else { return original; } } /** * Add a new node to the graph with the relevant properties. */ private void addNode(StateInformation state) throws IOException { if (state.is_new) { if (format==GDF_FORMAT) { graph.write("st" + state.id + ",\"" + state.id); if (state.error != null) { graph.write(":" + state.error); } graph.write("\","+state_node_style); if (state.error != null) { graph.write(",red"); } else if (state.has_next) { graph.write(",black"); } else { graph.write(",green"); } } else { // The dot version graph.write(" st" + state.id + " [label=\"" + state.id); if (state.error != null) { graph.write(":" + state.error); } graph.write("\",shape="); if (state.error != null) { graph.write("diamond,color=red"); } else if (state.has_next) { graph.write("circle,color=black"); } else { graph.write("egg,color=green"); } graph.write("];"); } graph.newLine(); } } private static class StateInformation { public StateInformation() {} public void reset(int id, boolean has_next, boolean is_new) { this.id = id; this.has_next = has_next; this.error = null; this.is_new = is_new; } int id = -1; boolean has_next = true; String error = null; boolean is_new = false; } /** * Creates an GDF edge string. */ private String makeGdfEdgeString(String from_id, String to_id, String label, int thread) { StringBuilder sb=new StringBuilder(from_id); sb.append(',').append(to_id).append(',').append('\"'); if ((label!=null) && (!"".equals(label))) { sb.append(label); } else { sb.append('-'); } sb.append('\"').append(',').append(labelvisible).append(',').append(true). append(',').append(thread); replaceString(sb, "\n", ""); return sb.toString(); } /** * GUESS cannot deal with '\n' chars, so remove them. Also convert all '"' * characters to "''". * @param str The string to perform the conversion on. * @return The converted string. */ private String convertGdfSpecial(String str) { if ((str==null) || "".equals(str)) return ""; StringBuilder sb=new StringBuilder(str); convertGdfSpecial(sb); return sb.toString(); } /** * GUESS cannot deal with '\n' chars, so replace them with a space. Also * convert all '"' characters to "''". * @param sb The string buffer to perform the conversion on. */ private void convertGdfSpecial(StringBuilder sb) { replaceString(sb, "\"", "\'\'"); replaceString(sb, "\n", " "); } /** * Create an edge in the graph file from old_id to new_id. */ private void addEdge(int old_id, int new_id, Search state) throws IOException { int my_id = edge_id++; if (format==GDF_FORMAT) { Transition trans=state.getTransition(); int thread = trans.getThreadIndex(); // edgedef>node1,node2,label,labelvisible,directed,thread INT // Old State -> Transition - labeled with the source info if any. gdfEdges.add( makeGdfEdgeString("st"+old_id, "tr"+my_id, makeGdfLabel(state, my_id), thread)); // Transition node: name,label,style,color graph.write("tr" + my_id + ",\"" +my_id+"\","+transition_node_style); graph.newLine(); // Transition -> New State - labeled with the last output if any. String lastOutputLabel= replaceString(convertGdfSpecial(trans.getOutput()), "\"", "\'\'"); gdfEdges.add( makeGdfEdgeString("tr"+my_id, "st"+new_id, lastOutputLabel, thread)); } else { // DOT graph.write(" st" + old_id + " -> tr" + my_id + ";"); graph.newLine(); graph.write(" tr" + my_id + " [label=\"" + makeDotLabel(state, my_id) + "\",shape=box]"); graph.newLine(); graph.write(" tr" + my_id + " -> st" + new_id + ";"); } } /** * Show the usage message. */ static void showUsage() { System.out .println("Usage: \"java [<vm-options>] gov.nasa.jpf.tools.StateSpaceDot [<graph-options>] [<jpf-options-and-args>]"); System.out.println(" <graph-options> : "); System.out.println(" -gdf: Generate the graph in GDF format. The default is DOT."); System.out.println(" -transition-numbers: Include transition numbers in transition labels."); System.out.println(" -show-source: Include source lines in transition labels."); System.out.println(" -labelvisible: Indicates if the label on the transitions is visible (used only with the -gdf option)"); System.out.println(" -help: Prints this help information and stops."); System.out.println(" <jpf-options-and-args>:"); System.out.println(" Options and command line arguments passed directly to JPF."); System.out.println(" Note: With -gdf option transition edges could also include program output "); System.out.println(" but in order to enable this JPF's vm.path_output option must be set to "); System.out.println(" true."); } void filterArgs (String[] args) { for (int i=0; i<args.length; i++) { if (args[i] != null){ String arg = args[i]; if ("-transition-numbers".equals(arg)) { transition_numbers = true; args[i] = null; } else if ("-show-source".equals(arg)) { show_source = true; args[i] = null; } else if ("-gdf".equals(arg)) { format=GDF_FORMAT; out_filename=OUT_FILENAME_NO_EXT+"."+GDF_EXT; args[i] = null; } else if ("-labelvisible".equals(arg)) { labelvisible=true; args[i] = null; } else if ("-help".equals(args[i])) { showUsage(); helpRequested=true; } } } } }