001: /* Soot - a J*va Optimization Framework
002: * Copyright (C) 2003 John Jorgensen
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the
016: * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
017: * Boston, MA 02111-1307, USA.
018: */
019:
020: /*
021: * Modified by the Sable Research Group and others 1997-2003.
022: * See the 'credits' file distributed with Soot for the complete list of
023: * contributors. (Soot is distributed at http://www.sable.mcgill.ca/soot)
024: */
025:
026: package soot.util;
027:
028: import java.io.File;
029: import java.io.PrintWriter;
030: import java.util.ArrayList;
031: import java.util.Iterator;
032: import java.util.List;
033: import soot.Body;
034: import soot.G;
035: import soot.Printer;
036: import soot.Scene;
037: import soot.Singletons;
038: import soot.SootClass;
039: import soot.SootMethod;
040: import soot.options.Options;
041: import soot.toolkits.graph.ExceptionalGraph;
042: import soot.toolkits.graph.DirectedGraph;
043: import soot.util.cfgcmd.CFGToDotGraph;
044: import soot.util.dot.DotGraph;
045:
046: /**
047: * The <tt>PhaseDumper</tt> is a debugging aid. It maintains two
048: * lists of phases to be debugged. If a phase is on the
049: * <code>bodyDumpingPhases</code> list, then the intermediate
050: * representation of the bodies being manipulated by the phase is
051: * dumped before and after the phase is applied. If a phase is on the
052: * <code>cfgDumpingPhases</code> list, then whenever a CFG is
053: * constructed during the phase, a dot file is dumped representing the
054: * CFG constructed.
055: */
056:
057: public class PhaseDumper {
058: // As a minor optimization, we leave these lists null in the
059: // case were no phases at all are to be dumped, which is the
060: // most likely case.
061: private List bodyDumpingPhases = null;
062: private List cfgDumpingPhases = null;
063:
064: private class PhaseStack extends ArrayList {
065: // We eschew java.util.Stack to avoid synchronization overhead.
066:
067: private final static int initialCapacity = 4;
068: final static String EMPTY_STACK_PHASE_NAME = "NOPHASE";
069:
070: PhaseStack() {
071: super (initialCapacity);
072: }
073:
074: boolean empty() {
075: return (this .size() == 0);
076: }
077:
078: String currentPhase() {
079: if (this .size() <= 0) {
080: return EMPTY_STACK_PHASE_NAME;
081: } else {
082: return (String) this .get(this .size() - 1);
083: }
084: }
085:
086: String pop() {
087: return (String) this .remove(this .size() - 1);
088: }
089:
090: String push(String phaseName) {
091: this .add(phaseName);
092: return phaseName;
093: }
094: }
095:
096: private final PhaseStack phaseStack = new PhaseStack();
097: final static String allWildcard = "ALL";
098:
099: public PhaseDumper(Singletons.Global g) {
100: if (!Options.v().dump_body().isEmpty()) {
101: bodyDumpingPhases = Options.v().dump_body();
102: }
103: if (!Options.v().dump_cfg().isEmpty()) {
104: cfgDumpingPhases = Options.v().dump_cfg();
105: }
106: }
107:
108: /**
109: * Returns the single instance of <code>PhaseDumper</code>.
110: *
111: * @return Soot's <code>PhaseDumper</code>.
112: */
113: public static PhaseDumper v() {
114: return G.v().soot_util_PhaseDumper();
115: }
116:
117: private boolean isBodyDumpingPhase(String phaseName) {
118: return ((bodyDumpingPhases != null) && (bodyDumpingPhases
119: .contains(phaseName) || bodyDumpingPhases
120: .contains(allWildcard)));
121: }
122:
123: private boolean isCFGDumpingPhase(String phaseName) {
124: if (cfgDumpingPhases == null) {
125: return false;
126: }
127: if (cfgDumpingPhases.contains(allWildcard)) {
128: return true;
129: } else {
130: while (true) { // loop exited by "return" or "break".
131: if (cfgDumpingPhases.contains(phaseName)) {
132: return true;
133: }
134: // Go on to check if phaseName is a subphase of a
135: // phase in cfgDumpingPhases.
136: int lastDot = phaseName.lastIndexOf('.');
137: if (lastDot < 0) {
138: break;
139: } else {
140: phaseName = phaseName.substring(0, lastDot);
141: }
142: }
143: return false;
144: }
145: }
146:
147: private static java.io.File makeDirectoryIfMissing(Body b)
148: throws java.io.IOException {
149: StringBuffer buf = new StringBuffer(soot.SourceLocator.v()
150: .getOutputDir());
151: buf.append(File.separatorChar);
152: String className = b.getMethod().getDeclaringClass().getName();
153: buf.append(className);
154: buf.append(File.separatorChar);
155: buf.append(b.getMethod().getSubSignature());
156: java.io.File dir = new java.io.File(buf.toString());
157: if (dir.exists()) {
158: if (!dir.isDirectory()) {
159: throw new java.io.IOException(dir.getPath()
160: + " exists but is not a directory.");
161: }
162: } else {
163: if (!dir.mkdirs()) {
164: throw new java.io.IOException("Unable to mkdirs "
165: + dir.getPath());
166: }
167: }
168: return dir;
169: }
170:
171: private static PrintWriter openBodyFile(Body b, String baseName)
172: throws java.io.IOException {
173: File dir = makeDirectoryIfMissing(b);
174: String filePath = dir.toString() + File.separatorChar
175: + baseName;
176: return new PrintWriter(new java.io.FileOutputStream(filePath));
177: }
178:
179: /**
180: * Returns the next available name for a graph file.
181: */
182:
183: private static String nextGraphFileName(Body b, String baseName)
184: throws java.io.IOException {
185: // We number output files to allow multiple graphs per phase.
186: File dir = makeDirectoryIfMissing(b);
187: final String prefix = dir.toString() + File.separatorChar
188: + baseName;
189: File file = null;
190: int fileNumber = 0;
191: do {
192: file = new File(prefix + fileNumber
193: + DotGraph.DOT_EXTENSION);
194: fileNumber++;
195: } while (file.exists());
196: return file.toString();
197: }
198:
199: private static void deleteOldGraphFiles(final Body b,
200: final String phaseName) {
201: try {
202: final File dir = makeDirectoryIfMissing(b);
203: final File[] toDelete = dir
204: .listFiles(new java.io.FilenameFilter() {
205: public boolean accept(File dir, String name) {
206: return name.startsWith(phaseName)
207: && name
208: .endsWith(DotGraph.DOT_EXTENSION);
209: }
210: });
211: for (File element : toDelete) {
212: element.delete();
213: }
214: } catch (java.io.IOException e) {
215: // Don't abort execution because of an I/O error, but report
216: // the error.
217: G.v().out.println("PhaseDumper.dumpBody() caught: "
218: + e.toString());
219: e.printStackTrace(G.v().out);
220: }
221: }
222:
223: // soot.Printer itself needs to create a BriefUnitGraph in order
224: // to format the text for a method's instructions, so this flag is
225: // a hack to avoid dumping graphs that we create in the course of
226: // dumping bodies or other graphs.
227: //
228: // Note that this hack would not work if a PhaseDumper might be
229: // accessed by multiple threads. So long as there is a single
230: // active PhaseDumper accessed through soot.G, it seems
231: // safe to assume it will be accessed by only a single thread.
232: private boolean alreadyDumping = false;
233:
234: public void dumpBody(Body b, String baseName) {
235: try {
236: alreadyDumping = true;
237: java.io.PrintWriter out = openBodyFile(b, baseName);
238: soot.Printer.v().setOption(Printer.USE_ABBREVIATIONS);
239: soot.Printer.v().printTo(b, out);
240: out.close();
241: } catch (java.io.IOException e) {
242: // Don't abort execution because of an I/O error, but let
243: // the user know.
244: G.v().out.println("PhaseDumper.dumpBody() caught: "
245: + e.toString());
246: e.printStackTrace(G.v().out);
247: } finally {
248: alreadyDumping = false;
249: }
250: }
251:
252: private void dumpAllBodies(String baseName, boolean deleteGraphFiles) {
253: List<SootClass> classes = Scene.v()
254: .getClasses(SootClass.BODIES);
255: for (SootClass cls : classes) {
256: for (Iterator m = cls.getMethods().iterator(); m.hasNext();) {
257: SootMethod method = (SootMethod) m.next();
258: if (method.hasActiveBody()) {
259: Body body = method.getActiveBody();
260: if (deleteGraphFiles) {
261: deleteOldGraphFiles(body, baseName);
262: }
263: dumpBody(body, baseName);
264: }
265: }
266: }
267: }
268:
269: /**
270: * Tells the <code>PhaseDumper</code> that a {@link Body}
271: * transforming phase has started, so that it can dump the
272: * phases's “before” file. If the phase is to be
273: * dumped, <code>dumpBefore</code> deletes any old
274: * graph files dumped during previous runs of the phase.
275: *
276: * @param b the {@link Body} being transformed.
277: * @param phaseName the name of the phase that has just started.
278: */
279: public void dumpBefore(Body b, String phaseName) {
280: phaseStack.push(phaseName);
281: if (isBodyDumpingPhase(phaseName)) {
282: deleteOldGraphFiles(b, phaseName);
283: dumpBody(b, phaseName + ".in");
284: }
285: }
286:
287: /**
288: * Tells the <code>PhaseDumper</code> that a {@link Body}
289: * transforming phase has ended, so that it can dump the
290: * phases's “after” file.
291: *
292: * @param b the {@link Body} being transformed.
293: *
294: * @param phaseName the name of the phase that has just ended.
295: *
296: * @throws IllegalArgumentException if <code>phaseName</code> does not
297: * match the <code>PhaseDumper</code>'s record of the current phase.
298: */
299: public void dumpAfter(Body b, String phaseName) {
300: String poppedPhaseName = phaseStack.pop();
301: if (poppedPhaseName != phaseName) {
302: throw new IllegalArgumentException("dumpAfter(" + phaseName
303: + ") when poppedPhaseName == " + poppedPhaseName);
304: }
305: if (isBodyDumpingPhase(phaseName)) {
306: dumpBody(b, phaseName + ".out");
307: }
308: }
309:
310: /**
311: * Tells the <code>PhaseDumper</code> that a {@link Scene}
312: * transforming phase has started, so that it can dump the
313: * phases's “before” files. If the phase is to be
314: * dumped, <code>dumpBefore</code> deletes any old
315: * graph files dumped during previous runs of the phase.
316: *
317: * @param phaseName the name of the phase that has just started.
318: */
319: public void dumpBefore(String phaseName) {
320: phaseStack.push(phaseName);
321: if (isBodyDumpingPhase(phaseName)) {
322: dumpAllBodies(phaseName + ".in", true);
323: }
324: }
325:
326: /**
327: * Tells the <code>PhaseDumper</code> that a {@link Scene}
328: * transforming phase has ended, so that it can dump the
329: * phases's “after” files.
330: *
331: * @param phaseName the name of the phase that has just ended.
332: *
333: * @throws IllegalArgumentException if <code>phaseName</code> does not
334: * match the <code>PhaseDumper</code>'s record of the current phase.
335: */
336: public void dumpAfter(String phaseName) {
337: String poppedPhaseName = phaseStack.pop();
338: if (poppedPhaseName != phaseName) {
339: throw new IllegalArgumentException("dumpAfter(" + phaseName
340: + ") when poppedPhaseName == " + poppedPhaseName);
341: }
342: if (isBodyDumpingPhase(phaseName)) {
343: dumpAllBodies(phaseName + ".out", false);
344: }
345: }
346:
347: /**
348: * Asks the <code>PhaseDumper</code> to dump the passed {@link
349: * DirectedGraph} if the current phase is being dumped.
350: *
351: * @param g the graph to dump.
352: *
353: * @param body the {@link Body} represented by <code>g</code>.
354: */
355: public void dumpGraph(DirectedGraph g, Body b) {
356: if (alreadyDumping) {
357: return;
358: }
359: try {
360: alreadyDumping = true;
361: String phaseName = phaseStack.currentPhase();
362: if (isCFGDumpingPhase(phaseName)) {
363: try {
364: String outputFile = nextGraphFileName(b, phaseName
365: + "-" + getClassIdent(g) + "-");
366: DotGraph dotGraph = new CFGToDotGraph().drawCFG(g,
367: b);
368: dotGraph.plot(outputFile);
369:
370: } catch (java.io.IOException e) {
371: // Don't abort execution because of an I/O error, but
372: // report the error.
373: G.v().out.println("PhaseDumper.dumpBody() caught: "
374: + e.toString());
375: e.printStackTrace(G.v().out);
376: }
377: }
378: } finally {
379: alreadyDumping = false;
380: }
381: }
382:
383: /**
384: * Asks the <code>PhaseDumper</code> to dump the passed {@link
385: * ExceptionalGraph} if the current phase is being dumped.
386: *
387: * @param g the graph to dump.
388: */
389: public void dumpGraph(ExceptionalGraph g) {
390: if (alreadyDumping) {
391: return;
392: }
393: try {
394: alreadyDumping = true;
395: String phaseName = phaseStack.currentPhase();
396: if (isCFGDumpingPhase(phaseName)) {
397: try {
398: String outputFile = nextGraphFileName(g.getBody(),
399: phaseName + "-" + getClassIdent(g) + "-");
400: CFGToDotGraph drawer = new CFGToDotGraph();
401: drawer.setShowExceptions(Options.v()
402: .show_exception_dests());
403: DotGraph dotGraph = drawer.drawCFG(g);
404: dotGraph.plot(outputFile);
405:
406: } catch (java.io.IOException e) {
407: // Don't abort execution because of an I/O error, but
408: // report the error.
409: G.v().out.println("PhaseDumper.dumpBody() caught: "
410: + e.toString());
411: e.printStackTrace(G.v().out);
412: }
413: }
414: } finally {
415: alreadyDumping = false;
416: }
417: }
418:
419: /**
420: * A utility routine that returns the unqualified identifier
421: * naming the class of an object.
422: *
423: * @param obj The object whose class name is to be returned.
424: */
425: private String getClassIdent(Object obj) {
426: String qualifiedName = obj.getClass().getName();
427: int lastDotIndex = qualifiedName.lastIndexOf('.');
428: return qualifiedName.substring(lastDotIndex + 1);
429: }
430:
431: /**
432: * Prints the current stack trace, as a brute force tool for
433: * program understanding. This method appeared in response to the
434: * many times dumpGraph() was being called while the phase stack
435: * was empty. Turned out that the Printer needs to
436: * build a BriefUnitGraph in order to print a graph. Doh!
437: */
438: public void printCurrentStackTrace() {
439: try {
440: throw new java.io.IOException("FAKE");
441: } catch (java.io.IOException e) {
442: e.printStackTrace(G.v().out);
443: }
444: }
445: }
|