All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.util.PhaseDumper Maven / Gradle / Ivy

package soot.util;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2003 John Jorgensen
 * %%
 * This program 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 program 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.Body;
import soot.G;
import soot.Printer;
import soot.Scene;
import soot.Singletons;
import soot.SootClass;
import soot.SootMethod;
import soot.options.Options;
import soot.toolkits.graph.DirectedGraph;
import soot.toolkits.graph.ExceptionalGraph;
import soot.util.cfgcmd.CFGToDotGraph;
import soot.util.dot.DotGraph;

/**
 * The PhaseDumper is a debugging aid. It maintains two lists of phases to be debugged. If a phase is on the
 * bodyDumpingPhases list, then the intermediate representation of the bodies being manipulated by the phase is
 * dumped before and after the phase is applied. If a phase is on the cfgDumpingPhases list, then whenever a CFG
 * is constructed during the phase, a dot file is dumped representing the CFG constructed.
 */

public class PhaseDumper {
  private static final Logger logger = LoggerFactory.getLogger(PhaseDumper.class);
  // As a minor optimization, we leave these lists null in the
  // case were no phases at all are to be dumped, which is the
  // most likely case.
  private List bodyDumpingPhases = null;
  private List cfgDumpingPhases = null;

  private class PhaseStack extends ArrayList {
    // We eschew java.util.Stack to avoid synchronization overhead.

    private final static int initialCapacity = 4;
    final static String EMPTY_STACK_PHASE_NAME = "NOPHASE";

    PhaseStack() {
      super(initialCapacity);
    }

    boolean empty() {
      return (this.size() == 0);
    }

    String currentPhase() {
      if (this.size() <= 0) {
        return EMPTY_STACK_PHASE_NAME;
      } else {
        return (String) this.get(this.size() - 1);
      }
    }

    String pop() {
      return (String) this.remove(this.size() - 1);
    }

    String push(String phaseName) {
      this.add(phaseName);
      return phaseName;
    }
  }

  private final PhaseStack phaseStack = new PhaseStack();
  final static String allWildcard = "ALL";

  public PhaseDumper(Singletons.Global g) {
    if (!Options.v().dump_body().isEmpty()) {
      bodyDumpingPhases = Options.v().dump_body();
    }
    if (!Options.v().dump_cfg().isEmpty()) {
      cfgDumpingPhases = Options.v().dump_cfg();
    }
  }

  /**
   * Returns the single instance of PhaseDumper.
   *
   * @return Soot's PhaseDumper.
   */
  public static PhaseDumper v() {
    return G.v().soot_util_PhaseDumper();
  }

  private boolean isBodyDumpingPhase(String phaseName) {
    return ((bodyDumpingPhases != null)
        && (bodyDumpingPhases.contains(phaseName) || bodyDumpingPhases.contains(allWildcard)));
  }

  private boolean isCFGDumpingPhase(String phaseName) {
    if (cfgDumpingPhases == null) {
      return false;
    }
    if (cfgDumpingPhases.contains(allWildcard)) {
      return true;
    } else {
      while (true) { // loop exited by "return" or "break".
        if (cfgDumpingPhases.contains(phaseName)) {
          return true;
        }
        // Go on to check if phaseName is a subphase of a
        // phase in cfgDumpingPhases.
        int lastDot = phaseName.lastIndexOf('.');
        if (lastDot < 0) {
          break;
        } else {
          phaseName = phaseName.substring(0, lastDot);
        }
      }
      return false;
    }
  }

  private static java.io.File makeDirectoryIfMissing(Body b) throws java.io.IOException {
    StringBuffer buf = new StringBuffer(soot.SourceLocator.v().getOutputDir());
    buf.append(File.separatorChar);
    String className = b.getMethod().getDeclaringClass().getName();
    buf.append(className);
    buf.append(File.separatorChar);
    buf.append(b.getMethod().getSubSignature().replace('<', '[').replace('>', ']'));
    java.io.File dir = new java.io.File(buf.toString());
    if (dir.exists()) {
      if (!dir.isDirectory()) {
        throw new java.io.IOException(dir.getPath() + " exists but is not a directory.");
      }
    } else {
      if (!dir.mkdirs()) {
        throw new java.io.IOException("Unable to mkdirs " + dir.getPath());
      }
    }
    return dir;
  }

  private static PrintWriter openBodyFile(Body b, String baseName) throws java.io.IOException {
    File dir = makeDirectoryIfMissing(b);
    String filePath = dir.toString() + File.separatorChar + baseName;
    return new PrintWriter(new java.io.FileOutputStream(filePath));
  }

  /**
   * Returns the next available name for a graph file.
   */

  private static String nextGraphFileName(Body b, String baseName) throws java.io.IOException {
    // We number output files to allow multiple graphs per phase.
    File dir = makeDirectoryIfMissing(b);
    final String prefix = dir.toString() + File.separatorChar + baseName;
    File file = null;
    int fileNumber = 0;
    do {
      file = new File(prefix + fileNumber + DotGraph.DOT_EXTENSION);
      fileNumber++;
    } while (file.exists());
    return file.toString();
  }

  private static void deleteOldGraphFiles(final Body b, final String phaseName) {
    try {
      final File dir = makeDirectoryIfMissing(b);
      final File[] toDelete = dir.listFiles(new java.io.FilenameFilter() {
        public boolean accept(File dir, String name) {
          return name.startsWith(phaseName) && name.endsWith(DotGraph.DOT_EXTENSION);
        }
      });
      if (toDelete != null) {
        for (File element : toDelete) {
          element.delete();
        }
      }
    } catch (java.io.IOException e) {
      // Don't abort execution because of an I/O error, but report
      // the error.
      logger.debug("PhaseDumper.dumpBody() caught: " + e.toString());
      logger.error(e.getMessage(), e);
    }
  }

  // soot.Printer itself needs to create a BriefUnitGraph in order
  // to format the text for a method's instructions, so this flag is
  // a hack to avoid dumping graphs that we create in the course of
  // dumping bodies or other graphs.
  //
  // Note that this hack would not work if a PhaseDumper might be
  // accessed by multiple threads. So long as there is a single
  // active PhaseDumper accessed through soot.G, it seems
  // safe to assume it will be accessed by only a single thread.
  private boolean alreadyDumping = false;

  public void dumpBody(Body b, String baseName) {
    try {
      alreadyDumping = true;
      java.io.PrintWriter out = openBodyFile(b, baseName);
      soot.Printer.v().setOption(Printer.USE_ABBREVIATIONS);
      soot.Printer.v().printTo(b, out);
      out.close();
    } catch (java.io.IOException e) {
      // Don't abort execution because of an I/O error, but let
      // the user know.
      logger.debug("PhaseDumper.dumpBody() caught: " + e.toString());
      logger.error(e.getMessage(), e);
    } finally {
      alreadyDumping = false;
    }
  }

  private void dumpAllBodies(String baseName, boolean deleteGraphFiles) {
    List classes = Scene.v().getClasses(SootClass.BODIES);
    for (SootClass cls : classes) {
      for (Iterator m = cls.getMethods().iterator(); m.hasNext();) {
        SootMethod method = (SootMethod) m.next();
        if (method.hasActiveBody()) {
          Body body = method.getActiveBody();
          if (deleteGraphFiles) {
            deleteOldGraphFiles(body, baseName);
          }
          dumpBody(body, baseName);
        }
      }
    }
  }

  /**
   * Tells the PhaseDumper that a {@link Body} transforming phase has started, so that it can dump the phases's
   * “before” file. If the phase is to be dumped, dumpBefore deletes any old graph files dumped
   * during previous runs of the phase.
   *
   * @param b
   *          the {@link Body} being transformed.
   * @param phaseName
   *          the name of the phase that has just started.
   */
  public void dumpBefore(Body b, String phaseName) {
    phaseStack.push(phaseName);
    if (isBodyDumpingPhase(phaseName)) {
      deleteOldGraphFiles(b, phaseName);
      dumpBody(b, phaseName + ".in");
    }
  }

  /**
   * Tells the PhaseDumper that a {@link Body} transforming phase has ended, so that it can dump the phases's
   * “after” file.
   *
   * @param b
   *          the {@link Body} being transformed.
   *
   * @param phaseName
   *          the name of the phase that has just ended.
   *
   * @throws IllegalArgumentException
   *           if phaseName does not match the PhaseDumper's record of the current phase.
   */
  public void dumpAfter(Body b, String phaseName) {
    String poppedPhaseName = phaseStack.pop();
    if (poppedPhaseName != phaseName) {
      throw new IllegalArgumentException("dumpAfter(" + phaseName + ") when poppedPhaseName == " + poppedPhaseName);
    }
    if (isBodyDumpingPhase(phaseName)) {
      dumpBody(b, phaseName + ".out");
    }
  }

  /**
   * Tells the PhaseDumper that a {@link Scene} transforming phase has started, so that it can dump the phases's
   * “before” files. If the phase is to be dumped, dumpBefore deletes any old graph files dumped
   * during previous runs of the phase.
   *
   * @param phaseName
   *          the name of the phase that has just started.
   */
  public void dumpBefore(String phaseName) {
    phaseStack.push(phaseName);
    if (isBodyDumpingPhase(phaseName)) {
      dumpAllBodies(phaseName + ".in", true);
    }
  }

  /**
   * Tells the PhaseDumper that a {@link Scene} transforming phase has ended, so that it can dump the phases's
   * “after” files.
   *
   * @param phaseName
   *          the name of the phase that has just ended.
   *
   * @throws IllegalArgumentException
   *           if phaseName does not match the PhaseDumper's record of the current phase.
   */
  public void dumpAfter(String phaseName) {
    String poppedPhaseName = phaseStack.pop();
    if (poppedPhaseName != phaseName) {
      throw new IllegalArgumentException("dumpAfter(" + phaseName + ") when poppedPhaseName == " + poppedPhaseName);
    }
    if (isBodyDumpingPhase(phaseName)) {
      dumpAllBodies(phaseName + ".out", false);
    }
  }

  /**
   * Asks the PhaseDumper to dump the passed {@link DirectedGraph} if the current phase is being dumped.
   *
   * @param g
   *          the graph to dump.
   *
   * @param body
   *          the {@link Body} represented by g.
   */
  public void dumpGraph(DirectedGraph g, Body b) {
    if (alreadyDumping) {
      return;
    }
    try {
      alreadyDumping = true;
      String phaseName = phaseStack.currentPhase();
      if (isCFGDumpingPhase(phaseName)) {
        try {
          String outputFile = nextGraphFileName(b, phaseName + "-" + getClassIdent(g) + "-");
          DotGraph dotGraph = new CFGToDotGraph().drawCFG(g, b);
          dotGraph.plot(outputFile);

        } catch (java.io.IOException e) {
          // Don't abort execution because of an I/O error, but
          // report the error.
          logger.debug("PhaseDumper.dumpBody() caught: " + e.toString());
          logger.error(e.getMessage(), e);
        }
      }
    } finally {
      alreadyDumping = false;
    }
  }

  /**
   * Asks the PhaseDumper to dump the passed {@link ExceptionalGraph} if the current phase is being dumped.
   *
   * @param g
   *          the graph to dump.
   */
  public void dumpGraph(ExceptionalGraph g) {
    if (alreadyDumping) {
      return;
    }
    try {
      alreadyDumping = true;
      String phaseName = phaseStack.currentPhase();
      if (isCFGDumpingPhase(phaseName)) {
        try {
          String outputFile = nextGraphFileName(g.getBody(), phaseName + "-" + getClassIdent(g) + "-");
          CFGToDotGraph drawer = new CFGToDotGraph();
          drawer.setShowExceptions(Options.v().show_exception_dests());
          DotGraph dotGraph = drawer.drawCFG(g);
          dotGraph.plot(outputFile);

        } catch (java.io.IOException e) {
          // Don't abort execution because of an I/O error, but
          // report the error.
          logger.debug("PhaseDumper.dumpBody() caught: " + e.toString());
          logger.error(e.getMessage(), e);
        }
      }
    } finally {
      alreadyDumping = false;
    }
  }

  /**
   * A utility routine that returns the unqualified identifier naming the class of an object.
   *
   * @param obj
   *          The object whose class name is to be returned.
   */
  private String getClassIdent(Object obj) {
    String qualifiedName = obj.getClass().getName();
    int lastDotIndex = qualifiedName.lastIndexOf('.');
    return qualifiedName.substring(lastDotIndex + 1);
  }

  /**
   * Prints the current stack trace, as a brute force tool for program understanding. This method appeared in response to the
   * many times dumpGraph() was being called while the phase stack was empty. Turned out that the Printer needs to build a
   * BriefUnitGraph in order to print a graph. Doh!
   */
  public void printCurrentStackTrace() {
    try {
      throw new java.io.IOException("FAKE");
    } catch (java.io.IOException e) {
      logger.error(e.getMessage(), e);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy