/*
* jimple2boogie - Translates Jimple (or Java) Programs to Boogie
* Copyright (C) 2013 Martin Schaeaeaef and Stephan Arlt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.joogie.runners;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.joogie.GlobalsCache;
import org.joogie.Options;
import org.joogie.soot.SootBodyTransformer;
import org.joogie.util.Log;
import soot.Pack;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.Transform;
/**
* The Soot Runner
*
* @author schaef
*/
public class SootRunner extends Runner {
/**
* stderr print stream
*/
private PrintStream stderr;
/**
* stdout print stream
*/
private PrintStream stdout;
/**
* Runs Soot by using a JAR file
*
* @param jarFile
* JAR file
* @param boogieFile
* Boogie file
*/
public void runWithJar(String jarFile, String boogieFile) {
try {
// command-line arguments for Soot
List<String> args = new ArrayList<String>();
fillSootArgs(args);
// extract dependent JARs
List<File> jarFiles = new ArrayList<File>();
extractClassPath(new File(jarFile), jarFiles);
jarFiles.add(new File(jarFile));
fillClassPath(jarFiles);
// additional classpath available?
String cp = buildClassPath(jarFiles);
if (Options.v().hasClasspath()) {
cp += File.pathSeparatorChar + Options.v().getClasspath();
}
// set soot-class-path
args.add("-cp");
args.add(cp);
// set classes
enumClasses(new File(jarFile), args);
// finally, run soot
run(args, boogieFile);
} catch (Exception e) {
Log.error(e.toString());
}
}
public void runWithApk(String apkFile, String boogieFile) {
try {
// command-line arguments for Soot
List<String> args = new ArrayList<String>();
fillSootArgs(args);
String cp = apkFile;
//enforce android
args.add("-src-prec");
args.add("apk");
//add path to android framework stubs
args.add("-android-jars");
String android_path = Options.v().getAndroidStubPath();
if (android_path==null) {
throw new RuntimeException("Need to specify -android-jars when analyzing apk's.");
}
args.add(android_path);
// add soot-class-path
args.add("-cp");
args.add(cp);
//add process-dir
args.add("-process-dir");
args.add(apkFile);
// finally, run soot
run(args, boogieFile);
} catch (Exception e) {
Log.error(e.toString());
}
}
/**
* Runs Soot by using a path (e.g., from Joogie)
*
* @param path
* Path
* @param boogieFile
* Boogie file
*/
public void runWithPath(String path, String boogieFile) {
try {
// dependent JAR files
List<File> jarFiles = new ArrayList<File>();
fillClassPath(jarFiles);
// additional classpath available?
String cp = buildClassPath(jarFiles);
if (Options.v().hasClasspath()) {
cp += File.pathSeparatorChar + Options.v().getClasspath();
}
// command-line arguments for Soot
List<String> args = new ArrayList<String>();
fillSootArgs(args);
// add soot-class-path
args.add("-cp");
args.add(cp);
args.add("-src-prec");
args.add("class");
// add path to be processed
args.add("-process-path");
args.add(path);
String mainClass = Options.v().getMainClassName();
if (mainClass!=null && !mainClass.isEmpty()) {
args.add(mainClass);
}
// finally, run soot
run(args, boogieFile);
} catch (Exception e) {
Log.error(e.toString());
}
}
/**
* Runs Soot
*
* @param args
* Command-line arguments
* @param boogieFile
* Boogie file
*/
protected void run(List<String> args, String boogieFile) {
try {
// init stream redirection
initStream();
// reset & init Soot
soot.G.reset();
//check if we need fullprogram analysis
if (Options.v().useSoundThreads()) {
args.add("-w");
args.add("-p");
args.add("cg.spark");
args.add("enabled:true");
//enable field RW analysis
args.add("-p");
//args.add("jap.fieldrw");
args.add("jap.sea");
args.add("enabled:true");
//enable MayHappenInParallel analysis
args.add("-p");
args.add("wjtp.mhp");
args.add("enabled:true");
} else {
// Iterator Hack
Scene.v().addBasicClass("org.eclipse.jdt.core.compiler.CategorizedProblem",SootClass.HIERARCHY);
Scene.v().addBasicClass("java.lang.Iterable",SootClass.SIGNATURES);
Scene.v().addBasicClass("java.util.Iterator",SootClass.SIGNATURES);
Scene.v().addBasicClass("java.lang.reflect.Array",SootClass.SIGNATURES);
}
Pack pack = PackManager.v().getPack("jtp");
//pack.add(new Transform("jtp.NullCheckEliminator",new NullCheckEliminator()));
pack.add(new Transform("jtp.BoogieTransform",
new SootBodyTransformer()));
StringBuilder sb = new StringBuilder();
for (String s : args) {
sb.append(" "+s);
}
Log.info("Running soot with "+sb.toString());
//log everything soot has to say
PrintStream origOut = System.out;
PrintStream origErr = System.err;
System.setOut(new Interceptor(origOut));
System.setErr(new Interceptor(origErr));
// Finally, run Soot
soot.Main.main(args.toArray(new String[args.size()]));
//reset the pipes
System.setOut(origOut);
System.setErr(origErr);
//CallGraph cg = Scene.v().getCallGraph();
// StringBuilder sb = new StringBuilder();
// sb.append("Entrypoints: \n");
// for (SootMethod m : Scene.v().getEntryPoints()) {
// sb.append("\t");
// sb.append(m.getName());
// sb.append("\n");
// }
// Log.error(sb);
// write boogie program to file
if (null != boogieFile && !boogieFile.isEmpty()) {
GlobalsCache.v().getPf().toFile(boogieFile);
}
if (Options.v().getRunTypeChecker()) {
GlobalsCache.v().getPf().runTypeChecker();
}
Log.info("Done parsing.");
} catch (Exception e) {
Log.error(e);
} finally {
// reset stream redirection
resetStream();
}
}
private static class Interceptor extends PrintStream
{
public Interceptor(OutputStream out) throws UnsupportedEncodingException
{
super(out, true, "UTF-8");
}
@Override
public void print(String s)
{
Log.info(s);
}
}
/**
* Fills a list with the standard command-line arguments needed by Soot
*
* @param args
* Command-line arguments
*/
protected void fillSootArgs(List<String> args) {
args.add("-keep-line-number");
// args.add("-keep-offset");
args.add("-pp");
// args.add("-print-tags");
// args.add("true");
if (org.joogie.Options.v().isDebug()) {
args.add("-f");
args.add("jimple");
args.add("-d");
args.add("./dump");
} else {
args.add("-output-format");
args.add("none");
}
args.add("-allow-phantom-refs");
// args.add("true");
//args.add("-w");
}
/**
* Fills a list with the standard JAR files needed by Soot
*
* @param files
* Standard JAR files needed by Soot
*/
protected void fillClassPath(List<File> files) {
// add Runtime Library
files.add(new File(new File(System.getProperty("java.home"), "lib"),
"rt.jar"));
// add Java Cryptography Extension Library
files.add(new File(new File(System.getProperty("java.home"), "lib"),
"jce.jar"));
}
/**
* Returns the class path argument for Soot
*
* @param files
* Files in the class path
* @return Class path argument for Soot
*/
protected String buildClassPath(List<File> files) {
StringBuilder sb = new StringBuilder();
for (File file : files) {
sb.append(file.getPath() + File.pathSeparatorChar);
}
return sb.toString();
}
/**
* Extracts dependent JARs from the JAR's manifest
*
* @param file
* JAR file object
* @param jarFiles
* List of dependent JARs
*/
protected void extractClassPath(File file, List<File> jarFiles) {
try {
// open JAR file
JarFile jarFile = new JarFile(file);
// get manifest and their main attributes
Attributes mainAttributes = jarFile.getManifest()
.getMainAttributes();
String classPath = mainAttributes
.getValue(Attributes.Name.CLASS_PATH);
// close JAR file
jarFile.close();
// empty class path?
if (null == classPath)
return;
// look for dependent JARs
String[] classPathItems = classPath.split(" ");
for (String classPathItem : classPathItems) {
if (classPathItem.endsWith(".jar")) {
// add jar
Log.debug("Adding " + classPathItem
+ " to Soot's class path");
jarFiles.add(new File(file.getParent(), classPathItem));
}
}
} catch (IOException e) {
Log.error(e.toString());
}
}
/**
* Enumerates all classes in a JAR file
*
* @param file
* JAR file object
* @param classes
* List of classes
*/
protected void enumClasses(File file, List<String> classes) {
try {
// open JAR file
Log.debug("Opening jar " + file.getPath());
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
// iterate JAR entries
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(".class")) {
// get class
String className = entryName.substring(0,
entryName.length() - ".class".length());
className = className.replace('/', '.');
// is class in scope?
if (Options.v().hasScope()) {
if (!className.startsWith(Options.v().getScope())) {
continue;
}
}
// add class
Log.debug("Adding class " + className);
classes.add(className);
}
}
// close JAR file
jarFile.close();
} catch (IOException e) {
Log.error(e.toString());
}
}
/**
* Initializes stream redirection
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="DM_DEFAULT_ENCODING")
protected void initStream() {
// backup stderr and stdout
stderr = System.err;
stdout = System.out;
// redirect stderr and stdout
// if there is at least one receiver registered
if (receivers.size() > 0) {
try {
PrintStream ps = new PrintStream(new FilteredStream(
new ByteArrayOutputStream()),true, "UTF-8");
System.setErr(ps);
System.setOut(ps);
} catch (UnsupportedEncodingException e) {
Log.error(e.toString());
stderr = System.err;
stdout = System.out;
}
}
}
/**
* Resets stream redirection
*/
protected void resetStream() {
// restore stderr and stdout
System.setErr(stderr);
System.setOut(stdout);
}
/**
* FilteredStream
*
* @author schaef
*/
/**
* FilteredStream
*
* @author schaef
*/
class FilteredStream extends FilterOutputStream {
private final Charset UTF8_CHARSET = Charset.forName("UTF-8");
/**
* C-tor
*
* @param stream
* OutputStream
*/
public FilteredStream(OutputStream stream) {
super(stream);
}
@Override
public void write(byte b[]) throws IOException {
String s = new String(b, UTF8_CHARSET);
SootRunner.this.notifyReceivers(s);
}
@Override
public void write(byte b[], int off, int len) throws IOException {
String s = new String(b, off, len, UTF8_CHARSET);
SootRunner.this.notifyReceivers(s);
}
}
}