/* * This is a prototype implementation of the concept of Feature-Sen * sitive Dataflow Analysis. More details in the AOSD'12 paper: * Dataflow Analysis for Software Product Lines * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ /* Soot - a J*va Optimization Framework * Copyright (C) 2003 John Jorgensen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package br.ufal.cideei.soot; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import soot.Body; import soot.BriefUnitPrinter; import soot.LabeledUnitPrinter; import soot.Unit; import soot.tagkit.SourceLnPosTag; import soot.toolkits.exceptions.ThrowableSet; import soot.toolkits.graph.Block; import soot.toolkits.graph.BlockGraph; import soot.toolkits.graph.DirectedGraph; import soot.toolkits.graph.ExceptionalGraph; import soot.toolkits.graph.UnitGraph; import soot.util.cfgcmd.CFGToDotGraph; import soot.util.dot.DotGraph; import soot.util.dot.DotGraphAttribute; import soot.util.dot.DotGraphConstants; import soot.util.dot.DotGraphEdge; import soot.util.dot.DotGraphNode; import br.ufal.cideei.soot.instrument.FeatureTag; /** * Warning: this source code along with its Javadoc was completely copied from * the Soot framework, as it was impossible to extend it because it was * package-visible. * * * Class that creates a {@link DotGraph} visualization of a control flow graph. */ public class SootUnitGraphSerializer extends CFGToDotGraph { private boolean onePage; // in one or several 8.5x11 pages. private boolean isBrief; private boolean showExceptions; private DotGraphAttribute unexceptionalControlFlowAttr; private DotGraphAttribute exceptionalControlFlowAttr; private DotGraphAttribute exceptionEdgeAttr; private DotGraphAttribute headAttr; private DotGraphAttribute tailAttr; /** * <p> * Returns a CFGToDotGraph converter which will draw the graph as a single * arbitrarily-sized page, with full-length node labels. * </p> * * <p> * If asked to draw a <code>ExceptionalGraph</code>, the converter will * identify the exceptions that will be thrown. By default, it will * distinguish different edges by coloring regular control flow edges black, * exceptional control flow edges red, and thrown exception edges light * gray. Head and tail nodes are filled in, head nodes with gray, and tail * nodes with light gray. * </p> */ public SootUnitGraphSerializer() { setOnePage(true); setBriefLabels(false); setShowExceptions(true); setUnexceptionalControlFlowAttr("color", "black"); setExceptionalControlFlowAttr("color", "red"); setExceptionEdgeAttr("color", "lightgray"); setHeadAttr("fillcolor", "lightgray"); setTailAttr("fillcolor", "lightgray"); } /** * Specify whether to split the graph into pages. * * @param onePage * indicates whether to produce the graph as a single, * arbitrarily-sized page (if <code>onePage</code> is * <code>true</code>) or several 8.5x11-inch pages (if * <code>onePage</code> is <code>false</code>). */ public void setOnePage(boolean onePage) { this.onePage = onePage; } /** * Specify whether to abbreviate the text in node labels. This is most * relevant when the nodes represent basic blocks: abbreviated node labels * contain only a numeric label for the block, while unabbreviated labels * contain the code of its instructions. * * @param useBrief * indicates whether to abbreviate the text of node labels. */ public void setBriefLabels(boolean useBrief) { this.isBrief = useBrief; } /** * Specify whether the graph should depict the exceptions which each node * may throw, in the form of an edge from the throwing node to the handler * (if any), labeled with the possible exception types. This parameter has * an effect only when drawing <code>ExceptionalGraph</code>s. * * @param showExceptions * indicates whether to show possible exceptions and their * handlers. */ public void setShowExceptions(boolean showExceptions) { this.showExceptions = showExceptions; } /** * Specify the dot graph attribute to use for regular control flow edges. * This parameter has an effect only when drawing * <code>ExceptionalGraph</code>s. * * @param id * The attribute name, for example "style" or "color". * * @param value * The attribute value, for example "solid" or "black". * * @see <a * href="http://www.research.att.com/sw/tools/graphviz/dotguide.pdf">"Drawing graphs with dot"</a> */ public void setUnexceptionalControlFlowAttr(String id, String value) { unexceptionalControlFlowAttr = new DotGraphAttribute(id, value); } /** * Specify the dot graph attribute to use for exceptional control flow * edges. This parameter has an effect only when drawing * <code>ExceptionalGraph</code>s. * * @param id * The attribute name, for example "style" or "color". * * @param value * The attribute value, for example "dashed" or "red". * * @see <a * href="http://www.research.att.com/sw/tools/graphviz/dotguide.pdf">"Drawing graphs with dot"</a> */ public void setExceptionalControlFlowAttr(String id, String value) { exceptionalControlFlowAttr = new DotGraphAttribute(id, value); } /** * Specify the dot graph attribute to use for edges depicting the exceptions * each node may throw, and their handlers. This parameter has an effect * only when drawing <code>ExceptionalGraph</code>s. * * @param id * The attribute name, for example "style" or "color". * * @param value * The attribute value, for example "dotted" or "lightgray". * * @see <a * href="http://www.research.att.com/sw/tools/graphviz/dotguide.pdf">"Drawing graphs with dot"</a> */ public void setExceptionEdgeAttr(String id, String value) { exceptionEdgeAttr = new DotGraphAttribute(id, value); } /** * Specify the dot graph attribute to use for head nodes (in addition to * filling in the nodes). * * @param id * The attribute name, for example "fillcolor". * * @param value * The attribute value, for example "gray". * * @see <a * href="http://www.research.att.com/sw/tools/graphviz/dotguide.pdf">"Drawing graphs with dot"</a> */ public void setHeadAttr(String id, String value) { headAttr = new DotGraphAttribute(id, value); } /** * Specify the dot graph attribute to use for tail nodes (in addition to * filling in the nodes). * * @param id * The attribute name, for example "fillcolor". * * @param value * The attribute value, for example "lightgray". * * @see <a * href="http://www.research.att.com/sw/tools/graphviz/dotguide.pdf">"Drawing graphs with dot"</a> */ public void setTailAttr(String id, String value) { tailAttr = new DotGraphAttribute(id, value); } /** * Returns an {@link Iterator} over a {@link Collection} which iterates over * its elements in a specified order. Used to order lists of destination * nodes consistently before adding the corresponding edges to the graph. * (Maintaining a consistent ordering of edges makes it easier to diff the * dot files output for different graphs of a given method.) * * @param coll * The collection to iterator over. * * @param comp * The comparator for the ordering. * * @return An iterator which presents the elements of <code>coll</code> in * the order specified by <code>comp</code>. */ private static Iterator sortedIterator(Collection coll, Comparator comp) { if (coll.size() <= 1) { return coll.iterator(); } else { Object array[] = coll.toArray(); Arrays.sort(array, comp); return Arrays.asList(array).iterator(); } } /** * Comparator used to order a list of nodes by the order in which they were * labeled. */ private static class NodeComparator implements java.util.Comparator { private DotNamer namer; NodeComparator(DotNamer namer) { this.namer = namer; } public int compare(Object o1, Object o2) { return (namer.getNumber(o1) - namer.getNumber(o2)); } public boolean equal(Object o1, Object o2) { return (namer.getNumber(o1) == namer.getNumber(o2)); } } /** * Comparator used to order a list of ExceptionDests by the order in which * their handler nodes were labeled. */ private static class ExceptionDestComparator implements java.util.Comparator { private DotNamer namer; ExceptionDestComparator(DotNamer namer) { this.namer = namer; } private int getValue(Object o) { Object handler = ((ExceptionalGraph.ExceptionDest) o).getHandlerNode(); if (handler == null) { return Integer.MAX_VALUE; } else { return namer.getNumber(handler); } } public int compare(Object o1, Object o2) { return (getValue(o1) - getValue(o2)); } public boolean equal(Object o1, Object o2) { return (getValue(o1) == getValue(o2)); } } /** * Create a <code>DotGraph</code> whose nodes and edges depict a control * flow graph without distinguished exceptional edges. * * @param graph * a <code>DirectedGraph</code> representing a CFG (probably an * instance of {@link UnitGraph}, {@link BlockGraph}, or one of * their subclasses). * * @param body * the <code>Body</code> represented by <code>graph</code> (used * to format the text within nodes). If no body is available, * pass <code>null</code>. * * @return a visualization of <code>graph</code>. */ public DotGraph drawCFG(DirectedGraph graph, Body body) { DotGraph canvas = initDotGraph(body); DotNamer namer = new DotNamer((int) (graph.size() / 0.7f), 0.7f); NodeComparator comparator = new NodeComparator(namer); // To facilitate comparisons between different graphs of the same // method, prelabel the nodes in the order they appear // in the iterator, rather than the order that they appear in the // graph traversal (so that corresponding nodes are more likely // to have the same label in different graphs of a given method). for (Iterator nodesIt = graph.iterator(); nodesIt.hasNext();) { String junk = namer.getName(nodesIt.next()); } for (Iterator nodesIt = graph.iterator(); nodesIt.hasNext();) { Object node = nodesIt.next(); canvas.drawNode(namer.getName(node)); for (Iterator succsIt = sortedIterator(graph.getSuccsOf(node), comparator); succsIt.hasNext();) { Object succ = succsIt.next(); canvas.drawEdge(namer.getName(node), namer.getName(succ)); } } setStyle(graph.getHeads(), canvas, namer, DotGraphConstants.NODE_STYLE_FILLED, headAttr); setStyle(graph.getTails(), canvas, namer, DotGraphConstants.NODE_STYLE_FILLED, tailAttr); if (!isBrief) { formatNodeText(body, canvas, namer); } return canvas; } /** * Create a <code>DotGraph</code> whose nodes and edges depict the control * flow in a <code>ExceptionalGraph</code>, with distinguished edges for * exceptional control flow. * * @param graph * the control flow graph * * @return a visualization of <code>graph</code>. */ public <N> DotGraph drawCFG(ExceptionalGraph<N> graph) { Body body = graph.getBody(); DotGraph canvas = initDotGraph(body); DotNamer namer = new DotNamer((int) (graph.size() / 0.7f), 0.7f); NodeComparator nodeComparator = new NodeComparator(namer); // Prelabel nodes in iterator order, to facilitate // comparisons between graphs of a given method. for (Iterator nodesIt = graph.iterator(); nodesIt.hasNext();) { String junk = namer.getName(nodesIt.next()); } for (Iterator<N> nodesIt = graph.iterator(); nodesIt.hasNext();) { N node = nodesIt.next(); canvas.drawNode(namer.getName(node)); for (Iterator succsIt = sortedIterator(graph.getUnexceptionalSuccsOf(node), nodeComparator); succsIt.hasNext();) { Object succ = succsIt.next(); DotGraphEdge edge = canvas.drawEdge(namer.getName(node), namer.getName(succ)); edge.setAttribute(unexceptionalControlFlowAttr); } for (Iterator succsIt = sortedIterator(graph.getExceptionalSuccsOf(node), nodeComparator); succsIt.hasNext();) { Object succ = succsIt.next(); DotGraphEdge edge = canvas.drawEdge(namer.getName(node), namer.getName(succ)); edge.setAttribute(exceptionalControlFlowAttr); } if (showExceptions) { for (Iterator destsIt = sortedIterator(graph.getExceptionDests(node), new ExceptionDestComparator(namer)); destsIt.hasNext();) { ExceptionalGraph.ExceptionDest dest = (ExceptionalGraph.ExceptionDest) destsIt.next(); Object handlerStart = dest.getHandlerNode(); if (handlerStart == null) { // Giving each escaping exception its own, invisible // exceptional exit node produces a less cluttered // graph. handlerStart = new Object() { public String toString() { return "Esc"; } }; DotGraphNode escapeNode = canvas.drawNode(namer.getName(handlerStart)); escapeNode.setStyle(DotGraphConstants.NODE_STYLE_INVISIBLE); } DotGraphEdge edge = canvas.drawEdge(namer.getName(node), namer.getName(handlerStart)); edge.setAttribute(exceptionEdgeAttr); edge.setLabel(formatThrowableSet(dest.getThrowables())); } } } setStyle(graph.getHeads(), canvas, namer, DotGraphConstants.NODE_STYLE_FILLED, headAttr); setStyle(graph.getTails(), canvas, namer, DotGraphConstants.NODE_STYLE_FILLED, tailAttr); if (!isBrief) { formatNodeText(graph.getBody(), canvas, namer); } return canvas; } /** * A utility method that initializes a DotGraph object for use in any * variety of drawCFG(). * * @param body * The <code>Body</code> that the graph will represent (used in * the graph's title). If no <code>Body</code> is available, pass * <code>null</code>. * * @return a <code>DotGraph</code> for visualizing <code>body</code>. */ private DotGraph initDotGraph(Body body) { String graphname = "cfg"; if (body != null) { graphname = body.getMethod().getSubSignature(); } DotGraph canvas = new DotGraph(graphname); canvas.setGraphLabel(graphname); if (!onePage) { canvas.setPageSize(8.5, 11.0); } if (isBrief) { canvas.setNodeShape(DotGraphConstants.NODE_SHAPE_CIRCLE); } else { canvas.setNodeShape(DotGraphConstants.NODE_SHAPE_BOX); } return canvas; } /** * A utility class for assigning unique names to DotGraph entities. It * maintains a mapping from CFG <code>Object</code>s to strings identifying * the corresponding nodes in generated dot source. */ private static class DotNamer extends HashMap { private int nodecount = 0; DotNamer(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); } DotNamer() { super(); } String getName(Object node) { Integer index = (Integer) this.get(node); if (index == null) { index = new Integer(nodecount++); this.put(node, index); } return index.toString(); } int getNumber(Object node) { Integer index = (Integer) this.get(node); if (index == null) { index = new Integer(nodecount++); this.put(node, index); } return index.intValue(); } } /** * A utility method which formats the text for each node in a * <code>DotGraph</code> representing a CFG. * * @param body * the <code>Body</code> whose control flow is visualized in * <code>canvas</code>. * * @param canvas * the <code>DotGraph</code> for visualizing the CFG. * * @param namer * provides a mapping from CFG objects to identifiers in * generated dot source. */ private void formatNodeText(Body body, DotGraph canvas, DotNamer namer) { LabeledUnitPrinter printer = null; if (body != null) { printer = new BriefUnitPrinter(body); printer.noIndent(); } for (Iterator nodesIt = namer.keySet().iterator(); nodesIt.hasNext();) { Object node = nodesIt.next(); DotGraphNode dotnode = canvas.getNode(namer.getName(node)); String nodeLabel = null; if (printer == null) { nodeLabel = node.toString(); } else { if (node instanceof Unit) { Unit unit = (Unit) node; unit.toString(printer); String targetLabel = (String) printer.labels().get(node); if (targetLabel == null) { nodeLabel = printer.toString(); } else { nodeLabel = targetLabel + ": " + printer.toString(); } if (unit.hasTag(FeatureTag.FEAT_TAG_NAME)) { FeatureTag ftag = (FeatureTag) unit.getTag(FeatureTag.FEAT_TAG_NAME); nodeLabel = nodeLabel + "\\n" + ftag.getFeatureRep().toString().replace("[", "{").replace("]", "}"); } if (unit.hasTag("SourceLnPosTag")) { SourceLnPosTag stag = (SourceLnPosTag) unit.getTag("SourceLnPosTag"); nodeLabel = "(" + stag.startLn() + ")\\n" + nodeLabel; } } else if (node instanceof Block) { Iterator units = ((Block) node).iterator(); StringBuffer buffer = new StringBuffer(); while (units.hasNext()) { Unit unit = (Unit) units.next(); String targetLabel = (String) printer.labels().get(unit); if (targetLabel != null) { buffer.append(targetLabel).append(":\\n"); } unit.toString(printer); buffer.append(printer.toString()).append("\\l"); } nodeLabel = buffer.toString(); } else { nodeLabel = node.toString(); } } dotnode.setLabel(nodeLabel); } } /** * Utility routine for setting some common formatting style for the * {@link DotGraphNode}s corresponding to some collection of objects. * * @param objects * is the collection of {@link Object}s whose nodes are to be * styled. * @param canvas * the {@link DotGraph} containing nodes corresponding to the * collection. * @param namer * maps from {@link Object} to the strings used to identify * corresponding {@link DotGraphNode}s. * @param style * the style to set for each of the nodes. * @param attrib * if non-null, an additional attribute to associate with each of * the nodes. */ private void setStyle(Collection objects, DotGraph canvas, DotNamer namer, String style, DotGraphAttribute attrib) { // Fill the entry and exit nodes. for (Iterator it = objects.iterator(); it.hasNext();) { Object object = it.next(); DotGraphNode objectNode = canvas.getNode(namer.getName(object)); objectNode.setStyle(style); objectNode.setAttribute(attrib); } } /** * Utility routine to format the list of names in a ThrowableSet into a * label for the edge showing where those Throwables are handled. */ private String formatThrowableSet(ThrowableSet set) { String input = set.toAbbreviatedString(); // Insert line breaks between individual Throwables (dot seems to // orient these edges more or less vertically, most of the time). int inputLength = input.length(); StringBuffer result = new StringBuffer(inputLength + 5); for (int i = 0; i < inputLength; i++) { char c = input.charAt(i); if (c == '+' || c == '-') { result.append("\\l"); } result.append(c); } return result.toString(); } }