package soottocfg.soot; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.google.common.base.Preconditions; import soot.Body; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Unit; import soot.Value; import soot.ValueBox; import soot.jimple.IdentityStmt; import soot.jimple.InstanceFieldRef; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.toolkits.scalar.UnreachableCodeEliminator; import soottocfg.Options; import soottocfg.cfg.Program; import soottocfg.cfg.SourceLocation; import soottocfg.cfg.method.CfgBlock; import soottocfg.cfg.method.Method; import soottocfg.cfg.optimization.CfgCallInliner; import soottocfg.cfg.optimization.CfgStubber; import soottocfg.cfg.optimization.dataflow.ConstPropagator; import soottocfg.cfg.optimization.dataflow.CopyPropagator; import soottocfg.cfg.optimization.dataflow.DeadCodeElimination; import soottocfg.cfg.statement.CallStatement; import soottocfg.cfg.statement.Statement; import soottocfg.cfg.variable.Variable; import soottocfg.soot.memory_model.MemoryModel; import soottocfg.soot.memory_model.NewMemoryModel; import soottocfg.soot.memory_model.PushIdentifierAdder; import soottocfg.soot.memory_model.PushPullSimplifier; import soottocfg.soot.transformers.ArrayTransformer; import soottocfg.soot.transformers.AssertionReconstruction; import soottocfg.soot.transformers.ExceptionTransformer; import soottocfg.soot.transformers.SpecClassTransformer; import soottocfg.soot.transformers.StaticInitializerTransformer; import soottocfg.soot.transformers.SwitchStatementRemover; import soottocfg.soot.transformers.VirtualCallResolver; import soottocfg.soot.util.DuplicatedCatchDetection; import soottocfg.soot.util.FlowBasedPointsToAnalysis; import soottocfg.soot.util.MethodInfo; import soottocfg.soot.util.SootTranslationHelpers; import soottocfg.soot.visitors.SootStmtSwitch; /** * This is the main class for the translation. It first invokes Soot to load all * classes and perform points-to analysis and then translates them into * Boogie/Horn. * * @author schaef * @author rodykers * */ public class SootToCfg { public enum MemModel { PullPush } private final List<String> resolvedClassNames; private final Set<SourceLocation> locations = new LinkedHashSet<SourceLocation>(); // Create a new program private final Program program = new Program(); private static FlowBasedPointsToAnalysis pta; public SootToCfg() { this(new ArrayList<String>()); SootTranslationHelpers.initialize(program); } public SootToCfg(List<String> resolvedClassNames) { this.resolvedClassNames = resolvedClassNames; // first reset everything: soot.G.reset(); SootTranslationHelpers.initialize(program); } /** * Run Soot and translate classes into Boogie/Horn * * @param input * class folder, jar file, or apk file * @param classPath * class path, or platform jar folder for apk. see * https://github.com/Sable/android-platforms */ public void run(String input, String classPath) { // run soot to load all classes. SootRunner runner = new SootRunner(); runner.run(input, classPath); /* * Get a reference for the main method. We have to get the * reference before applying the array transformation because * this changes this signature of main. */ final SootMethod mainMethod = Scene.v().getMainMethod(); performBehaviorPreservingTransformations(); performAbstractionTransformations(); constructCfg(); // now set the entry points. Method m = program.lookupMethod(mainMethod.getSignature()); program.setEntryPoint(m); m.isProgramEntryPoint(true); if (Options.v().outDir() != null) { writeFile(".cfg", program.toString()); } // stub CfgStubber stubber = new CfgStubber(); stubber.stubUnboundFieldsAndMethods(program); // inline method calls CfgCallInliner inliner = new CfgCallInliner(program); inliner.inlineFromMain(Options.v().getInlineMaxSize(), Options.v().getInlineCount()); removeUnreachableMethods(program); if (program.getEntryPoint() == null) { System.err.println("WARNING: No entry point found in program!"); SootTranslationHelpers.v().reset(); return; } boolean changed = true; while(changed) { changed = applyPullPushSimplification(); changed = applyDataFlowSimplifications() ? true : changed; } // add push IDs PushIdentifierAdder pia = new PushIdentifierAdder(); pia.addIDs(program); // print CFG if (Options.v().printCFG()) { System.out.println(program); } // reset all the soot stuff. SootTranslationHelpers.v().reset(); } private boolean applyPullPushSimplification() { boolean programChanged = false; // alias analysis setPointsToAnalysis(new FlowBasedPointsToAnalysis()); if (Options.v().memPrecision() >= Options.MEMPREC_PTA) { getPointsToAnalysis().run(program); } // simplify push-pull if (Options.v().memPrecision() >= Options.MEMPREC_SIMPLIFY) { PushPullSimplifier pps = new PushPullSimplifier(); programChanged = pps.simplify(program); if (Options.v().outDir() != null) writeFile(".simpl.cfg", program.toString()); } return programChanged; } private boolean applyDataFlowSimplifications() { boolean programChanged = false; if (Options.v().optimizeMethods) { for (Method method : program.getMethods()) { boolean changed = true; while (changed) { changed = false; while (ConstPropagator.constPropagate(method)) { changed = true; } while (CopyPropagator.copyPropagate(method)) { changed = true; } changed = DeadCodeElimination.eliminateDeadCode(method) ? true : changed ; programChanged = programChanged || changed; } //now remove the locals that have been eliminated. Set<Variable> allVars = new HashSet<Variable>(); for (CfgBlock b : method.vertexSet()) { allVars.addAll(b.getUseVariables()); allVars.addAll(b.getDefVariables()); } method.getLocals().retainAll(allVars); } } return programChanged; } /** * Like run, but only performs the behavior preserving transformations * and does construct a CFG. This method is only needed to test the * soundness of the transformation with randoop. * * @param input * @param classPath */ public void runPreservingTransformationOnly(String input, String classPath) { SootRunner runner = new SootRunner(this.resolvedClassNames); runner.run(input, classPath); performBehaviorPreservingTransformations(); SootTranslationHelpers.v().reset(); } public Program getProgram() { return program; } public Set<SourceLocation> getDuplicatedSourceLocations() { return locations; } private void constructCfg(SootClass sc) { SootTranslationHelpers.v().setCurrentClass(sc); for (SootMethod sm : sc.getMethods()) { if (sm.isConcrete()) { constructCfg(sm); } } } private void constructCfg(SootMethod sm) { if (sm.equals(SootTranslationHelpers.v().getAssertMethod())) { // Do not translate the assertion method. return; } SootTranslationHelpers.v().setCurrentMethod(sm); try { Body body = null; try { body = sm.retrieveActiveBody(); performSootOptimizations(body); } catch (RuntimeException e) { // TODO: print warning that body couldn't be retrieved. return; } MethodInfo mi = new MethodInfo(body.getMethod(), SootTranslationHelpers.v().getCurrentSourceFileName()); // pre-calculate when to pull/push MemoryModel mm = SootTranslationHelpers.v().getMemoryModel(); if (mm instanceof NewMemoryModel) { ((NewMemoryModel) mm).clearFieldToLocalMap(); } // System.err.println(sm.getSignature()+"\n"+body); SootStmtSwitch ss = new SootStmtSwitch(body, mi); mi.setSource(ss.getEntryBlock()); mi.finalizeAndAddToProgram(); } catch (RuntimeException e) { System.err.println("Soot failed to parse " + sm.getSignature()); e.printStackTrace(System.err); // return; throw e; } } private void constructCfg() { List<SootClass> classes = new LinkedList<SootClass>(Scene.v().getClasses()); for (SootClass sc : classes) { if (sc.resolvingLevel() >= SootClass.SIGNATURES && sc.isApplicationClass()) { if ((!sc.isJavaLibraryClass() && !sc.isLibraryClass())) { constructCfg(sc); } } } } private void performAbstractionTransformations() { StaticInitializerTransformer sit = new StaticInitializerTransformer(); sit.applyTransformation(); ArrayTransformer atrans = new ArrayTransformer(); atrans.applyTransformation(); if (Options.v().useBuiltInSpecs()) { SpecClassTransformer spctrans = new SpecClassTransformer(); spctrans.applyTransformation(); } } /** * Perform a sequence of behavior preserving transformations to the body * of each method: * - reconstruct Java asserts. * - transform exceptional flow into regular flow. * - transform switch statements into if-then-else statements. * - de-virtualization. */ private void performBehaviorPreservingTransformations() { // add a field for the dynamic type of an object to each class. // SootTranslationHelpers.createTypeFields(); List<SootClass> classes = new LinkedList<SootClass>(Scene.v().getClasses()); for (SootClass sc : classes) { if (sc == SootTranslationHelpers.v().getAssertionClass()) { continue; // no need to process this guy. } if (sc.resolvingLevel() >= SootClass.SIGNATURES && sc.isApplicationClass()) { SootTranslationHelpers.v().setCurrentClass(sc); for (SootMethod sm : sc.getMethods()) { if (sm.isConcrete()) { addDefaultInitializers(sm, sc); SootTranslationHelpers.v().setCurrentMethod(sm); Body body = sm.retrieveActiveBody(); try { body.validate(); } catch (soot.validation.ValidationException e) { System.out.println("Unable to validate method body. Possible NullPointerException?"); e.printStackTrace(); } try { // System.out.println(body); UnreachableCodeEliminator.v().transform(body); // detect duplicated finally blocks DuplicatedCatchDetection duplicatedUnits = new DuplicatedCatchDetection(); Map<Unit, Set<Unit>> duplicatedFinallyUnits = duplicatedUnits .identifiedDuplicatedUnitsFromFinallyBlocks(body); for (Entry<Unit, Set<Unit>> entry : duplicatedFinallyUnits.entrySet()) { locations.add(SootTranslationHelpers.v().getSourceLocation(entry.getKey())); for (Unit u : entry.getValue()) { locations.add(SootTranslationHelpers.v().getSourceLocation(u)); } } } catch (RuntimeException e) { e.printStackTrace(); throw new RuntimeException("Behavior preserving transformation failed " + sm.getSignature() + " " + e.toString()); } } } } } AssertionReconstruction ar = new AssertionReconstruction(); ar.applyTransformation(); ExceptionTransformer em = new ExceptionTransformer(Options.v().excAsAssert()); em.applyTransformation(); SwitchStatementRemover so = new SwitchStatementRemover(); so.applyTransformation(); if (Options.v().resolveVirtualCalls()) { VirtualCallResolver vc = new VirtualCallResolver(); vc.applyTransformation(); } } // apply some standard Soot optimizations private void performSootOptimizations(Body body) { soot.jimple.toolkits.scalar.CopyPropagator.v().transform(body); // soot.jimple.toolkits.scalar.UnreachableCodeEliminator.v().transform(body); soot.jimple.toolkits.scalar.ConstantCastEliminator.v().transform(body); soot.jimple.toolkits.scalar.ConstantPropagatorAndFolder.v().transform(body); soot.jimple.toolkits.scalar.DeadAssignmentEliminator.v().transform(body); soot.jimple.toolkits.scalar.EmptySwitchEliminator.v().transform(body); } private void addDefaultInitializers(SootMethod constructor, SootClass containingClass) { if (constructor.isConstructor()) { Preconditions.checkArgument(constructor.getDeclaringClass().equals(containingClass)); JimpleBody jbody = (JimpleBody) constructor.retrieveActiveBody(); // TODO: use this guy in instead. // jbody.insertIdentityStmts(); Set<SootField> instanceFields = new LinkedHashSet<SootField>(); for (SootField f : containingClass.getFields()) { if (!f.isStatic()) { instanceFields.add(f); } } for (ValueBox vb : jbody.getDefBoxes()) { if (vb.getValue() instanceof InstanceFieldRef) { Value base = ((InstanceFieldRef) vb.getValue()).getBase(); soot.Type baseType = base.getType(); if (baseType instanceof RefType && ((RefType) baseType).getSootClass().equals(containingClass)) { // remove the final fields that are initialized anyways // from // our staticFields set. SootField f = ((InstanceFieldRef) vb.getValue()).getField(); if (f.isFinal()) { instanceFields.remove(f); } } } } Unit insertPos = null; for (Unit u : jbody.getUnits()) { if (u instanceof IdentityStmt) { insertPos = u; } else { break; // insert after the last IdentityStmt } } for (SootField f : instanceFields) { Unit init; // if (SootTranslationHelpers.isDynamicTypeVar(f)) { // init = Jimple.v().newAssignStmt( // Jimple.v().newInstanceFieldRef(jbody.getThisLocal(), // f.makeRef()), // SootTranslationHelpers.v().getClassConstant(RefType.v(containingClass))); // } else { init = Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(jbody.getThisLocal(), f.makeRef()), SootTranslationHelpers.v().getDefaultValue(f.getType())); // } if (insertPos == null) { jbody.getUnits().addFirst(init); } else { jbody.getUnits().insertAfter(init, insertPos); } } } } private void writeFile(String extension, String text) { if (Options.v().outDir() == null) return; Path file = Paths.get(Options.v().outDir().toString() + Options.v().outBaseName() + extension); LinkedList<String> it = new LinkedList<String>(); it.add(text); try { Files.createDirectories(Options.v().outDir()); Files.write(file, it, Charset.forName("UTF-8")); } catch (Exception e) { System.err.println("Error writing file " + file); } } public static FlowBasedPointsToAnalysis getPointsToAnalysis() { return pta; } private static void setPointsToAnalysis(FlowBasedPointsToAnalysis pointsto) { pta = pointsto; } private void removeUnreachableMethods(Program program) { Set<Method> reachable = reachableMethod(program.getEntryPoint()); Set<Method> toRemove = new HashSet<Method>(); for (Method m : program.getMethods()) { if (!reachable.contains(m)) { toRemove.add(m); } } program.removeMethods(toRemove); } private Set<Method> reachableMethod(Method main) { Set<Method> reachable = new HashSet<Method>(); List<Method> todo = new LinkedList<Method>(); todo.add(main); while (!todo.isEmpty()) { Method m = todo.remove(0); reachable.add(m); for (Method n : calledMethods(m)) { if (!reachable.contains(n) && !todo.contains(n)) { todo.add(n); } } } return reachable; } private List<Method> calledMethods(Method m) { List<Method> res = new LinkedList<Method>(); for (CfgBlock b : m.vertexSet()) { for (Statement s : b.getStatements()) { if (s instanceof CallStatement) { CallStatement cs = (CallStatement) s; res.add(cs.getCallTarget()); } } } return res; } }