edu.ksu.cis.indus.common.soot.SootBasedDriver.java Source code

Java tutorial

Introduction

Here is the source code for edu.ksu.cis.indus.common.soot.SootBasedDriver.java

Source

/*******************************************************************************
 * Indus, a program analysis and transformation toolkit for Java.
 * Copyright (c) 2001, 2007 Venkatesh Prasad Ranganath
 * 
 * All rights reserved.  This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 which accompanies 
 * the distribution containing this program, and is available at 
 * http://www.opensource.org/licenses/eclipse-1.0.php.
 * 
 * For questions about the license, copyright, and software, contact 
 *    Venkatesh Prasad Ranganath at venkateshprasad.ranganath@gmail.com
 *                                 
 * This software was developed by Venkatesh Prasad Ranganath in SAnToS Laboratory 
 * at Kansas State University.
 *******************************************************************************/

package edu.ksu.cis.indus.common.soot;

import edu.ksu.cis.indus.annotations.Functional;
import edu.ksu.cis.indus.annotations.Immutable;
import edu.ksu.cis.indus.annotations.NonNull;
import edu.ksu.cis.indus.annotations.NonNullContainer;
import edu.ksu.cis.indus.common.scoping.SpecificationBasedScopeDefinition;
import edu.ksu.cis.indus.interfaces.IEnvironment;
import edu.ksu.cis.indus.processing.Environment;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.jibx.runtime.JiBXException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.Printer;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.options.Options;
import soot.util.Chain;

/**
 * This is generic driver that provides basic support to process a system represented in Jimple.
 * <p>
 * The classes in which to search for root methods can be configured via setting 1 system property named
 * <code>indus.common.soot.SootBasedDriver.rootClasses</code>. The syntax of this property a regular expression which will
 * be matched against the name of the class in which the method is declared. Note if this is unspecified, then root methods
 * are searched only in the classes specified and not the classes which are required. So, "edu.ksu.cis.indus.Processing" will
 * find root methods occurring in classes defined in class <code>edu.ksu.cis.indus.Processing</code>. Likewise, the user
 * can control which methods are picked as root methods based on their signature by specifying the pattern via the system
 * property, <code>indus.common.soot.SootBasedDriver.rootMethods</code>.
 * </p>
 * <p>
 * The user can provide a root method trapper object to be used to identify root methods. However, if the user does not
 * provide one, then an instance of the class named via <code>indus.common.soot.RootMethodTrapper.class</code> property will
 * be used. This named class should be a subclass of <code>edu.ksu.cis.indus.common.soot.RootMethodTrapper</code>. As
 * reflection is used on to instantiate an object, the specified class by the property should have a no-argument constructor.
 * </p>
 * <p>
 * The user can provide a statement graph factory object to be used to identify root methods. However, if the user does not
 * provide one, then an instance of the class named via <code>indus.common.soot.SootBasedDriver.StmtGraphFactory.class</code>
 * property will be used. This named class should be a subclass of
 * <code>edu.ksu.cis.indus.common.soot.IStmtGraphFactory</code>. As reflection is used on to instantiate an object, the
 * specified class by the property should have a no-argument constructor.
 * </p>
 * <p>
 * Please refer to <code>edu.ksu.cis.indus.Constants</code> for a file-based approach to specifying these properties.
 * </p>
 * 
 * @author <a href="http://www.cis.ksu.edu/~rvprasad">Venkatesh Prasad Ranganath</a>
 * @author $Author: rvprasad $
 * @version $Revision: 1.54 $ $Date: 2007/02/10 19:08:36 $
 */
public class SootBasedDriver {

    /**
     * The default object that can be used for trapping root methods.
     */
    private static final RootMethodTrapper DEFAULT_INSTANCE_OF_ROOT_METHOD_TRAPPER;

    /**
     * The default object that can be used for created statement graphs.
     */
    private static final IStmtGraphFactory<?> DEFAULT_INSTANCE_OF_STMT_GRAPH_FACTORY;

    /**
     * The logger used by instances of this class and it's subclasses to log messages.
     */
    private static final Logger LOGGER;

    static {
        LOGGER = LoggerFactory.getLogger(SootBasedDriver.class);

        final String _rmtClassName = Constants.getRootMethodTrapperClassName();

        try {
            final Object _o = SootBasedDriver.class.getClassLoader().loadClass(_rmtClassName).newInstance();

            if (_o instanceof RootMethodTrapper) {
                DEFAULT_INSTANCE_OF_ROOT_METHOD_TRAPPER = (RootMethodTrapper) _o;
            } else {
                final String _msg = _rmtClassName + " is not a subclass of SootBasedDriver.RootMethodTrapper.";
                throw new IllegalArgumentException(_msg);
            }
        } catch (final ClassNotFoundException _e) {
            LOGGER.error("class " + _rmtClassName + " could not be loaded/resolved. Bailing.", _e);
            throw new RuntimeException(_e);
        } catch (final InstantiationException _e) {
            LOGGER.error("An instance of class " + _rmtClassName + " could not be created. Bailing.", _e);
            throw new RuntimeException(_e);
        } catch (final IllegalAccessException _e) {
            LOGGER.error("No-arg constructor of " + _rmtClassName + " cannot be accessed.  Bailing.", _e);
            throw new RuntimeException(_e);
        }

        final String _nameOfStmtGraphFactoryClass = Constants.getStmtGraphFactoryClassName();

        try {
            final Object _o = SootBasedDriver.class.getClassLoader().loadClass(_nameOfStmtGraphFactoryClass)
                    .newInstance();

            if (_o instanceof IStmtGraphFactory) {
                DEFAULT_INSTANCE_OF_STMT_GRAPH_FACTORY = (IStmtGraphFactory) _o;
            } else {
                throw new IllegalArgumentException(
                        _nameOfStmtGraphFactoryClass + " is not a subclass of IStmtGraphFactory.");
            }
        } catch (final ClassNotFoundException _e) {
            LOGGER.error("class " + _nameOfStmtGraphFactoryClass + " could not be loaded/resolved. Bailing.", _e);
            throw new RuntimeException(_e);
        } catch (final InstantiationException _e) {
            LOGGER.error("An instance of class " + _nameOfStmtGraphFactoryClass + " could not be created. Bailing.",
                    _e);
            throw new RuntimeException(_e);
        } catch (final IllegalAccessException _e) {
            LOGGER.error("No-arg constructor of " + _nameOfStmtGraphFactoryClass + " cannot be accessed.  Bailing.",
                    _e);
            throw new RuntimeException(_e);
        }
    }

    /**
     * This manages basic block graphs of the methods being processed. Subclasses should initialize this suitably.
     */
    protected BasicBlockGraphMgr bbm;

    /**
     * This provides <code>UnitGraph</code>s required by the analyses. By defaults this will be initialized to
     * <code>ExceptionFlowSensitiveStmtGraphFactory</code>.
     */
    @NonNull
    protected IStmtGraphFactory<?> cfgProvider;

    /**
     * The list of classes that should be considered as the core of the system.
     */
    @NonNullContainer
    @NonNull
    protected List<String> classNames;

    /**
     * Logger to be used to log information written via <code>writeInfo</code>.
     */
    protected Logger infoLogger;

    /**
     * This is the set of methods which serve as the entry point into the system being analyzed.
     */
    @NonNullContainer
    @NonNull
    protected Collection<SootMethod> rootMethods = new HashSet<SootMethod>();

    /**
     * The scene that contains the classes of the system.
     */
    @NonNull
    protected Scene scene;

    /**
     * The class path that should be added.
     */
    private String classpathToAdd;

    /**
     * The environment.
     */
    @NonNull
    private Environment env;

    /**
     * This traps the root methods.
     */
    @NonNull
    private RootMethodTrapper rootMethodTrapper;

    /**
     * This is used to maintain the execution time of each analysis/transformation. This timing information is printed via
     * <code>printTimingStats</code>.
     */
    @NonNullContainer
    @NonNull
    private final Map<String, Long> times = new LinkedHashMap<String, Long>();

    /**
     * Creates a new Test object. This also initializes <code>cfgProvider</code> to <code>CompleteStmtGraphFactory</code>
     * and <code>bbm</code> to an instance of <code>BasicBlockGraphMgr</code> with <code>cfgProvider</code> as the unit
     * graph provider.
     */
    public SootBasedDriver() {
        cfgProvider = getDefaultStmtGraphFactory();
        bbm = new BasicBlockGraphMgr();
        bbm.setStmtGraphFactory(cfgProvider);
    }

    /**
     * Provides the default statement graph factory as configured by properties at the time of class initialization. By
     * default, it provides an instance of <code>CompleteStmtGraphFactory</code>.
     * 
     * @return a unit graph factory.
     */
    @NonNull
    public static IStmtGraphFactory<?> getDefaultStmtGraphFactory() {
        return DEFAULT_INSTANCE_OF_STMT_GRAPH_FACTORY;
    }

    /**
     * Adds an entry into the time log of this test. The subclasses should use this method to add time logs corresponding to
     * each analysis they test/drive.
     * 
     * @param name of the analysis for which the timing log is being created.
     * @param milliseconds taken by the analysis.
     */
    public final void addTimeLog(@NonNull @Immutable final String name, final long milliseconds) {
        times.put("[" + times.size() + "]" + name, new Long(milliseconds));
    }

    /**
     * Records the given classpath in intention of using it while loading classes into the scene.
     * 
     * @param classpath to be considered.
     */
    public final void addToSootClassPath(@NonNull @Immutable final String classpath) {
        classpathToAdd = classpath;
    }

    /**
     * Retrieves the basic block graph manager used by the application.
     * 
     * @return the basic block graph manager.
     */
    @Functional
    @NonNull
    public final BasicBlockGraphMgr getBbm() {
        return this.bbm;
    }

    /**
     * Retrieves the environment used by the application.
     * 
     * @return the environment.
     */
    @Functional
    @NonNull
    public final IEnvironment getEnvironment() {
        return this.env;
    }

    /**
     * Retrieves the root methods in the system.
     * 
     * @return the collection of root methods.
     */
    @Functional
    @NonNullContainer
    @NonNull
    public final Collection<SootMethod> getRootMethods() {
        return Collections.unmodifiableCollection(rootMethods);
    }

    /**
     * Retrieves the unit graph factory to be used by other processes that are driven by this implementation.
     * 
     * @return an unit graph factory
     */
    @Functional
    @NonNull
    public IStmtGraphFactory<?> getStmtGraphFactory() {
        return cfgProvider;
    }

    /**
     * Initialize the driver with the soot options available from <code>Util.getSootOptions</code> method.
     */
    public final void initialize() {
        initialize(Util.getSootOptions());
    }

    /**
     * Initialize the driver. Loads up the classes and sets up the scene. The given classes are loaded up as application
     * classes.
     * 
     * @param options to be used while setting up Soot infrastructure.
     * @throws RuntimeException when <code>setClassNames()</code> was not called before using this object.
     */
    public final void initialize(@NonNull final String[] options) {
        if (classNames == null) {
            throw new RuntimeException("Please call setClassNames() before using this object.");
        }
        writeInfo("Loading classes....");
        scene = loadupClassesAndCollectMains(options);
        env = new Environment(scene);
    }

    /**
     * Prints the timing statistics into the given stream.
     */
    @Functional
    public final void printTimingStats() {
        writeInfo("Timing statistics:");

        for (final Iterator<String> _i = times.keySet().iterator(); _i.hasNext();) {
            final Object _e = _i.next();
            writeInfo(_e + " => " + times.get(_e) + "ms");
        }
    }

    /**
     * Resets internal data structure.
     */
    public void reset() {
        rootMethods.clear();
        scene = null;
        times.clear();
    }

    /**
     * Set the names of the classes to be loaded.
     * 
     * @param s contains the class names.
     */
    public final void setClassNames(@NonNullContainer @NonNull final Collection<String> s) {
        classNames = new ArrayList<String>(s);
    }

    /**
     * Sets the logger to be used to log information written via <code>writeInfo</code>.
     * 
     * @param myLogger is the logger to be used.
     */
    public final void setInfoLogger(@NonNull final Logger myLogger) {
        infoLogger = myLogger;
    }

    /**
     * Sets the root method trapper.
     * 
     * @param trapper to be used to trap root methods.
     */
    public final void setRootMethodTrapper(@NonNull final RootMethodTrapper trapper) {
        rootMethodTrapper = trapper;
    }

    /**
     * Logs the given object via the logging api. Configure the logging via the logging implementation's configuration
     * support.
     * 
     * @param info to be logged.
     */
    public void writeInfo(final Object info) {
        if (infoLogger != null && infoLogger.isInfoEnabled()) {
            infoLogger.info(String.valueOf(info));
        }
    }

    /**
     * Dumps jimple for the classes in the scene.
     * 
     * @param outputDirectory is the directory in which jimple files will be dumped.
     * @param jimpleFile <code>true</code> indicates if .jimple file should be dumped; <code>false</code>, indicates
     *            otherwise.
     * @param classFile <code>true</code> indicates if .class file should be dumped; <code>false</code>, indicates
     *            otherwise.
     */
    protected void dumpJimpleAndClassFiles(@Immutable @NonNull final String outputDirectory,
            final boolean jimpleFile, final boolean classFile) {
        if (!jimpleFile && !classFile) {
            return;
        }

        final Printer _printer = Printer.v();

        for (@SuppressWarnings("unchecked")
        final Iterator<SootClass> _i = scene.getClasses().iterator(); _i.hasNext();) {
            final SootClass _sc = _i.next();

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Dumping jimple for " + _sc);
            }

            for (@SuppressWarnings("unchecked")
            final Iterator<SootMethod> _j = _sc.getMethods().iterator(); _j.hasNext();) {
                final SootMethod _sm = _j.next();

                if (_sm.isConcrete()) {
                    try {
                        _sm.retrieveActiveBody();
                    } catch (final RuntimeException _e) {
                        LOGGER.error("Failed to retrieve body for method " + _sm, _e);
                    }
                }
            }

            PrintWriter _writer = null;

            try {
                if (jimpleFile) {
                    // write .jimple file
                    final File _file = new File(outputDirectory + File.separator + _sc.getName() + ".jimple");
                    _writer = new PrintWriter(new FileWriter(_file));
                    _printer.printTo(_sc, _writer);
                }

                if (classFile) {
                    // write .class file
                    _printer.write(_sc, outputDirectory);
                }
            } catch (final IOException _e) {
                LOGGER.error("Error while writing " + _sc, _e);
            } catch (final RuntimeException _e) {
                LOGGER.error("Error while writing class file of " + _sc, _e);
            } finally {
                if (_writer != null) {
                    _writer.flush();
                    _writer.close();
                }
            }
        }
    }

    /**
     * Sets the name of the file containing the scope specification.
     * 
     * @param scopeSpecFileName of the scope spec.
     * @return the scope definition stored in the given file.
     */
    @Functional
    protected final SpecificationBasedScopeDefinition setScopeSpecFile(
            @Immutable @NonNull final String scopeSpecFileName) {
        SpecificationBasedScopeDefinition _result = null;

        if (scopeSpecFileName != null) {
            try {
                final InputStream _in = new FileInputStream(scopeSpecFileName);
                final String _contents = IOUtils.toString(_in);
                IOUtils.closeQuietly(_in);
                _result = SpecificationBasedScopeDefinition.deserialize(_contents);
            } catch (final IOException _e) {
                final String _msg = "Error retrieved specification from " + scopeSpecFileName;
                LOGGER.error(_msg, _e);

                final IllegalArgumentException _i = new IllegalArgumentException(_msg);
                _i.initCause(_e);
                throw _i;
            } catch (final JiBXException _e) {
                final String _msg = "JiBX failed during deserialization.";
                LOGGER.error(_msg, _e);

                final IllegalStateException _i = new IllegalStateException(_msg);
                _i.initCause(_e);
                throw _i;
            }
        }
        cfgProvider.setScope(_result, getEnvironment());
        return _result;
    }

    /**
     * Loads up the classes specified via <code>setClassNames()</code> and also collects the possible entry points into the
     * system being analyzed. All <code>public static void main()</code> methods defined in <code>public</code> classes
     * that are named via <code>args</code>are considered as entry points. It uses the classpath set via
     * <code>addToSootClassPath</code>.
     * 
     * @param options to be used while setting up Soot infrastructure.
     * @return a soot scene that provides the classes to be analyzed.
     */
    @NonNull
    private Scene loadupClassesAndCollectMains(@NonNull final String[] options) {
        final Scene _result = Scene.v();
        String _temp = _result.getSootClassPath();
        Options.v().parse(options);

        if (_temp != null) {
            _temp += File.pathSeparator + classpathToAdd + File.pathSeparator
                    + System.getProperty("java.class.path");
        } else {
            _temp = classpathToAdd;
        }
        _result.setSootClassPath(_temp);

        for (final Iterator<String> _i = classNames.iterator(); _i.hasNext();) {
            final SootClass _sc = _result.loadClassAndSupport(_i.next());
            _sc.setApplicationClass();
        }

        final Collection<SootClass> _mc = new HashSet<SootClass>();
        _mc.addAll(_result.getClasses());

        RootMethodTrapper _rmt = rootMethodTrapper;

        if (_rmt == null) {
            _rmt = DEFAULT_INSTANCE_OF_ROOT_METHOD_TRAPPER;
        }

        _rmt.setClassNames(Collections.unmodifiableCollection(classNames));

        for (final Iterator<SootClass> _i = _mc.iterator(); _i.hasNext();) {
            final SootClass _sc = _i.next();

            if (_rmt.considerClassForEntryPoint(_sc)) {
                final Collection<SootMethod> _methods = _sc.getMethods();

                for (final Iterator<SootMethod> _j = _methods.iterator(); _j.hasNext();) {
                    final SootMethod _sm = _j.next();

                    if (_rmt.isThisARootMethod(_sm)) {
                        rootMethods.add(_sm);
                    }
                }
            }
        }
        Util.fixupThreadStartBody(_result);

        if (Constants.shouldLoadMethodBodiesDuringInitialization()) {
            loadupMethodBodies();
        }

        return _result;
    }

    /**
     * Loads the bodies of all methods in the system.
     */
    private void loadupMethodBodies() {
        final Chain _classes = Scene.v().getClasses();
        @SuppressWarnings("unchecked")
        final Iterator<SootClass> _i = _classes.iterator();
        final int _iEnd = _classes.size();

        for (int _iIndex = 0; _iIndex < _iEnd; _iIndex++) {
            final SootClass _sc = _i.next();
            @SuppressWarnings("unchecked")
            final List<SootMethod> _methods = _sc.getMethods();
            final Iterator<SootMethod> _j = _methods.iterator();
            final int _jEnd = _methods.size();

            for (int _jIndex = 0; _jIndex < _jEnd; _jIndex++) {
                final SootMethod _sm = _j.next();

                if (_sm.isConcrete()) {
                    _sm.retrieveActiveBody();
                }
            }
        }
    }
}

// End of File