package dk.brics.xact.analysis;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import dk.brics.automaton.Automaton;
import dk.brics.misc.Origin;
import dk.brics.xact.XMLException;
import dk.brics.xact.analysis.concurrent.ExecutorTaskRunner;
import dk.brics.xact.analysis.concurrent.SingleThreadedTaskRunner;
import dk.brics.xact.analysis.concurrent.XactTaskRunner;
import dk.brics.xact.analysis.config.Configuration;
import dk.brics.xact.analysis.config.StandardConfiguration;
import dk.brics.xact.analysis.flowgraph.FlowGraph;
import dk.brics.xact.analysis.soot.Jimple2FlowGraph;
import dk.brics.xact.analysis.soot.TranslationResult;
import dk.brics.xact.analysis.transformations.ArrayTransformer;
import dk.brics.xact.analysis.transformations.CallTransformer;
import dk.brics.xact.analysis.transformations.DefUseTransformer;
import dk.brics.xact.analysis.transformations.FieldTransformer;
import dk.brics.xact.analysis.transformations.FlowGraph2Dot;
import dk.brics.xact.analysis.transformations.SchemaTypeLinking;
import dk.brics.xact.analysis.transformations.Splitter;
import dk.brics.xact.analysis.transformations.UnreachableTransformer;
import dk.brics.xact.analysis.xmlgraph.XMLGraphBuilder;
import dk.brics.xact.analysis.xmlgraph.XMLGraphChecker;
import dk.brics.xmlgraph.AttributeNode;
import dk.brics.xmlgraph.ElementNode;
import dk.brics.xmlgraph.Node;
import dk.brics.xmlgraph.NodeProcessor;
import dk.brics.xmlgraph.TextNode;
import dk.brics.xmlgraph.XMLGraph;
/**
* Program analysis for XACT.
* <p/>
* <h3>Running multiple analyses concurrently</h3>
* Separate instances of <code>XMLAnalysis</code> can safely be run in parallel.
* However, because Soot does not support separate instances of itself running,
* the initial phase will lock <tt>soot.G.class</tt> to prevent interference.
* The remaining phases will run concurrently with other analyses.
* <p/>
* <h3>Enabling concurrency in the analysis</h3>
* To enable concurrency within one analysis, use {@link #setTaskRunner(XactTaskRunner)}
* or {@link #setExecutorService(ExecutorService)}. The analysis will be single-threaded
* if neither method is called before starting the analysis.
*/
public class XMLAnalysis {
private List<String> classes;
private List<SootClass> soot_classes = new LinkedList<SootClass>();
private ErrorHandler errors = new ErrorHandler();
private Diagnostics diagnostics = Diagnostics.NULL;
private Configuration config = new StandardConfiguration();
private XactTaskRunner tasks = new SingleThreadedTaskRunner();
private String classpath;
private static void initializeSoot() {
soot.Scene.v().loadBasicClasses();
soot.options.Options.v().set_keep_line_number(true);
}
/**
* Initializes XACT program analysis for the given classes.
* The first class is assumed to be the main class.
* @param soot_classpath the class path for the classes to be analyzed
* (if null, just use the normal class path)
* @param classes names of the classes to be analyzed
*/
public XMLAnalysis(String soot_classpath, List<String> classes) {
this.classes = classes;
this.classpath = soot_classpath;
}
/**
* Sets the {@link Diagnostics} object to receive event notifications.
*/
public void setDiagnostics(Diagnostics diag) {
this.diagnostics = diag;
}
/**
* Sets the {@link Configuration} object to be used in the analysis.
*/
public void setConfiguration(Configuration config) {
this.config = config;
}
/**
* Sets an {@link ExecutorService} used to perform parallelizable tasks.
* This is equivalent to
* <pre>
* setTaskRunner(new ExecutorTaskRunner(executor));
* </pre>
*
* @param executor an executor service
* @see Executors
* @see #setTaskRunner(XactTaskRunner)
*/
public void setExecutorService(ExecutorService executor) {
setTaskRunner(new ExecutorTaskRunner(executor));
}
/**
* Sets the {@link XactTaskRunner} used to perform parallelizable tasks.
* @param tasks the tasks runner
* @see #setExecutorService(ExecutorService)
*/
public void setTaskRunner(XactTaskRunner tasks) {
this.tasks = tasks;
}
/**
* Marks the start of a phase.
* For debug info only.
*/
static void startPhase(String msg) {
Debug.println(1, true, msg);
Debug.inc();
}
/**
* Marks the end of a phase.
* For debug info only.
*/
static void endPhase() {
Debug.dec();
}
public void printMessages() {
printMessages(System.out);
}
public void printMessages(PrintStream out) {
errors.printMessages(out);
}
public void printMessages(PrintWriter out) {
errors.printMessages(out);
}
/**
* Runs the analysis.
* <p/>
* This method will lock <tt>soot.G.class</tt> during the initial phase.
* <p/>
* One cannot use hotpsots when using this method. If hotspot statements are of
* interest, use the other more low-level methods.
*/
public void analyze() {
if (Debug.getLevel() >= 1 && tasks.maybeMultiThreaded()) {
Debug.println(1, false, "Warning: Concurrency can mess up the debug output");
}
try {
FlowGraph g;
synchronized (soot.G.class) {
try {
initializeSoot();
if (classpath != null)
soot.options.Options.v().set_soot_classpath(classpath);
loadClasses();
diagnostics.afterSootLoaded();
g = buildFlowGraph().getGraph();
} finally {
releaseSoot();
}
}
transformFlowGraph(g);
final XMLGraph sharedXg = g.getXMLGraph();
List<FlowGraph> graphs = splitFlowGraph(g);
g = null;
int index=0;
final ThreadLocal<XMLGraph> localXg = new ThreadLocal<XMLGraph>();
for (final FlowGraph g2 : graphs) {
Debug.println(2, true, "Subgraph index..." + index);
Debug.println(2, true, "Node count......." + g2.getNodes().size());
Debug.println(2, true, "Edge count......." + g2.getNumberOfEdges());
tasks.addTask(new Runnable() {
public void run() {
try {
XMLGraph xg = localXg.get();
if (xg == null) {
xg = safeCloneGraph(sharedXg);
localXg.set(xg);
}
g2.setXMLGraph(xg);
XMLGraphBuilder b = buildXMLGraphs(g2);
analyzeXMLGraphs(g2, b);
} catch (XMLException e) {
Origin or = e.getOrigin();
errors.error(or, ErrorType.OTHER, e.getMessage());
System.out.println("Error: " + e.getMessage() +
((or!=null ? " at " + or.getFile() + " line " + or.getLine() +
(or.getColumn()>0 ? " column " + or.getColumn() : ""): "")));
} catch (Throwable t) {
errors.error(t);
}
}
});
index++;
}
tasks.runTasks();
} catch (XMLAnalysisException e) {
Origin or = e.getOrigin();
errors.error(or, ErrorType.OTHER, e.getMessage());
System.out.println("Error: " + e.getMessage() +
((or!=null ? " at " + or.getFile() + " line " + or.getLine() +
(or.getColumn()>0 ? " column " + or.getColumn() : ""): "")));
}
}
/**
* Clones all {@link Automaton} instances in the given XML graph
* so there is no interference with other threads.
* @param xg an XML graph
* @return a new XML graph
*/
private XMLGraph safeCloneGraph(XMLGraph xg) {
final XMLGraph output = xg.clone();
for (int i=0; i<xg.getNodes().size(); i++) {
Node node = output.getNode(i);
node.process(new NodeProcessor<Object>() {
@Override
public Object process(AttributeNode n) {
n.setName(n.getName().clone(), output);
return this;
}
@Override
public Object process(ElementNode n) {
n.setName(n.getName().clone(), output);
return this;
}
@Override
public Object process(TextNode n) {
n.replaceText(n.getText().clone(), output);
return this;
}
});
}
return output;
}
private List<FlowGraph> splitFlowGraph(FlowGraph g) {
startPhase("Splitting flow graph...");
List<FlowGraph> list = new Splitter().split(g);
Debug.println(2, true, "Created " + list.size() + " subgraphs");
diagnostics.afterSplit(g, list);
endPhase();
return list;
}
/**
* Loads the class files.
* This method is <i>not</i> thread-safe (because of Soot).
*/
public void loadClasses() {
startPhase("Loading classes...");
Scene.v().addBasicClass("java.util.Collection");
Scene.v().addBasicClass("java.util.List");
Scene.v().addBasicClass("java.util.Set");
Scene.v().addBasicClass("java.util.SortedSet");
Scene.v().addBasicClass("java.util.Queue");
Scene.v().addBasicClass("java.util.Deque");
Scene.v().addBasicClass("java.util.Iterator");
Scene.v().addBasicClass("java.util.ListIterator");
Scene.v().addBasicClass("java.lang.Iterable");
Scene.v().addBasicClass("java.util.Arrays");
Scene.v().addBasicClass("java.util.Collections");
Scene.v().addBasicClass("java.util.Map");
Scene.v().addBasicClass("java.util.Map$Entry");
Scene.v().addBasicClass("java.util.SortedMap");
Scene.v().addBasicClass("java.util.NavigableMap");
Scene.v().addBasicClass("java.util.ConcurrentMap");
boolean first = true;
for (String classname : classes) {
Debug.println(2, true, classname);
loadClass(classname, first);
first = false;
}
Scene.v().loadBasicClasses();
endPhase();
}
/**
* Loads the named class into the Soot scene, marks it as an application
* class, and generates bodies for all of its concrete methods.
*/
private void loadClass(String name, boolean main) {
SootClass c = Scene.v().loadClassAndSupport(name);
soot_classes.add(c);
c.setApplicationClass();
//if (main)
// Scene.v().setMainClass(c);
for (SootMethod sm : c.getMethods())
if (sm.isConcrete())
sm.retrieveActiveBody();
}
/**
* Builds the flow graph and finds schema URLs.
* This method is <i>not</i> thread-safe (because of Soot).
*/
public TranslationResult buildFlowGraph() {
startPhase("Building flow graph...");
TranslationResult tr = new Jimple2FlowGraph(config, errors).run();
FlowGraph g = tr.getGraph();
Debug.println(2, true, "Flow graph nodes: " + g.getNodes().size()
+ ", edges: " + g.getNumberOfEdges()
+ ", entries: " + g.getEntries().size());
dumpFlowGraph(g, "fg1.dot");
endPhase();
return tr;
}
/**
* Resets Soot.
* Can be invoked after string analysis and flow graph construction.
*/
public void releaseSoot() {
soot.G.reset();
//System.gc();
}
/**
* Performs various transformations of the given flow graph.
* Must be invoked before XML graph construction.
*/
public void transformFlowGraph(FlowGraph g) {
diagnostics.afterControlFlow(g);
startPhase("Fixing arrays...");
new ArrayTransformer().run(g); diagnostics.afterArrays(g);
endPhase();
startPhase("Transforming method calls");
new CallTransformer().run(g); diagnostics.afterCalls(g);
endPhase();
startPhase("Removing unreachable statements...");
new UnreachableTransformer().run(g); diagnostics.afterUnreachable(g);
endPhase();
startPhase("Linking fields...");
new FieldTransformer().run(g);
endPhase();
/*startPhase("Removing dead nodes...");
new IgnoreDeadTransformer().run(g); diagnostics.afterDead(g);
endPhase();*/
startPhase("Reducing flow graph...");
new DefUseTransformer().run(g); diagnostics.afterDefUse(g);
endPhase();
startPhase("Linking SchemaTypes...");
new SchemaTypeLinking().run(g, config); diagnostics.afterSchemaTypeLinking(g);
endPhase();
/*startPhase("Fixing annotations...");
new AnnotationTransformer().run(g);*/
Debug.println(2, true, "Flow graph nodes: " + g.getNodes().size()
+ ", edges: " + g.getNumberOfEdges()
+ ", entries: " + g.getEntries().size());
//dumpFlowGraph(g, "fg3.dot");
//endPhase();
}
/**
* Builds the XML graphs.
*/
public XMLGraphBuilder buildXMLGraphs(FlowGraph g) {
startPhase("Constructing XML graphs...");
XMLGraphBuilder b = new XMLGraphBuilder(g, config);
endPhase();
return b;
}
/**
* Analyzes the XML graphs.
*/
public void analyzeXMLGraphs(FlowGraph g, XMLGraphBuilder b) {
startPhase("Analyzing XML graphs...");
XMLGraphChecker checker = new XMLGraphChecker(errors);
checker.run(g, b);
endPhase();
}
private void dumpFlowGraph(FlowGraph g, String name) {
if (Debug.getLevel() >= 3) {
try {
String path = System.getProperty("java.io.tmpdir");
String sep = System.getProperty("file.separator");
if (!path.endsWith(sep))
path += sep;
path += name;
Debug.println(3, true, "Writing flow graph to: " + path);
FileOutputStream out = new FileOutputStream(path);
PrintStream pout = new PrintStream(out);
new FlowGraph2Dot(pout).print(g);
pout.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
public List<ErrorReport> getErrors() {
return errors.getErrors();
}
}