diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..71586e3 --- /dev/null +++ b/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 0f182a0..4d6556c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.jar *.war *.ear +/bin diff --git a/.project b/.project new file mode 100644 index 0000000..fad0791 --- /dev/null +++ b/.project @@ -0,0 +1,28 @@ + + + soot-ifds + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/README.md b/README.md index 68db45b..99220e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,40 @@ -incremental-ifds -================ +incremental-ifds / REVISER +=========================== + +This repository contains REVISER as presented in our paper submitted to OOPSLA 2013. REVISER +is an extended version of the HEROS IDE solver which is able to incrementally update analysis +results. + +If you have any questions or concerns, please feel free to use the issue tracker or contact us +at Steven.Arzt@ec-spride.de. + +Running the Benchmarks +------------------------ + +The simplest way to run the JUnit test cases and benchmarks we conducted for out OOPSLA paper+ +is to run the "runDynamicTest.sh" shell script. It creates one output log file and one error +log file for each test case. + +The test cases carrying a "_Rerun" suffix first run the solver on the old version of the target +code, then replace the code with the modified version and finally run the solver again. This can +be seen as the base case. + +The test cases ending with "_Propagate" first run the initial computation on the old version of +the target code, then replace the code with the modfified version before they incrementally +update the analysis results. These test cases run much faster than the "_Rerun" ones or than +computing the analysis results twice with the old unchanged version of the HEROS solver. + +For running the PDFsam tests, please use the "runPdfsam.sh" script. + +Note that our test cases are configured to run with a maximum heap size of 35 GB by default. +Depending on your machine configuration, you may have to change the scripts. We recommend giving +the test cases as much memory as possible for not obfuscating the performance results with +unnecessary garbage collector cycles. + +Important Version Note +------------------------ + +Note that REVISER is built on top of Soot 2.5.0 and may not work with newer versions. We plan +to integrate REVISER into the official HEROS branch as soon as possible. Our scripts thus +include the Soot 2.5.0 JAR file which is also part of this repository. -Work repository for the incremental IFDS solver \ No newline at end of file diff --git a/runDynamicTests.sh b/runDynamicTests.sh new file mode 100644 index 0000000..d61dd56 --- /dev/null +++ b/runDynamicTests.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#simpleTestJU_Rerun > output_simpleTestJU_Rerun.log 2> error_simpleTestJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#simpleTestJU_Propagate > output_simpleTestJU_Propagate.log 2> error_simpleTestJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addLocalJU_Rerun > output_addLocalJU_Rerun.log 2> error_addLocalJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addLocalJU_Propagate > output_addLocalJU_Propagate.log 2> error_addLocalJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#redefineVarJU_Rerun > output_redefineVarJU_Rerun.log 2> error_redefineVarJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#redefineVarJU_Propagate > output_redefineVarJU_Propagate.log 2> error_redefineVarJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeStmtJU_Rerun > output_removeStmtJU_Rerun.log 2> error_removeStmtJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeStmtJU_Propagate > output_removeStmtJU_Propagate.log 2> error_removeStmtJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeAssignmentJU_Rerun > output_removeAssignmentJU_Rerun.log 2> error_removeAssignmentJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeAssignmentJU_Propagate > output_removeAssignmentJU_Propagate.log 2> error_removeAssignmentJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addCallNoAssignmentJU_Rerun > output_addCallNoAssignmentJU_Rerun.log 2> error_addCallNoAssignmentJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addCallNoAssignmentJU_Propagate > output_addCallNoAssignmentJU_Propagate.log 2> error_addCallNoAssignmentJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addCallAssignmentJU_Rerun > output_addCallAssignmentJU_Rerun.log 2> error_addCallAssignmentJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#addCallAssignmentJU_Propagate > output_addCallAssignmentJU_Propagate.log 2> error_addCallAssignmentJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeStmtFromLoopJU_Rerun > output_removeStmtFromLoopJU_Rerun.log 2> error_removeStmtFromLoopJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#removeStmtFromLoopJU_Propagate > output_removeStmtFromLoopJU_Propagate.log 2> error_removeStmtFromLoopJU_Propagate.log + +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#redefineReturnJU_Rerun > output_redefineReturnJU_Rerun.log 2> error_redefineReturnJU_Rerun.log +java -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestReachingDefinitionsDynamic#redefineReturnJU_Propagate > output_redefineReturnJU_Propagate.log 2> error_redefineReturnJU_Propagate.log diff --git a/runPdfsam.sh b/runPdfsam.sh new file mode 100644 index 0000000..92c63bc --- /dev/null +++ b/runPdfsam.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +java -ea -Xmx35g -cp bin:guava-13.0.jar:soot-2.5.0.jar:junit-4.10.jar soot.jimple.interproc.ifds.test.SingleJUnitTestRunner soot.jimple.interproc.ifds.test.IFDSTestPDFsam#newVersionSH_Propagate > output_newVersionSH_Propagate.log 2> error_newVersionSH_Propagate.log diff --git a/src/soot/jimple/interproc/ifds/DontSynchronize.java b/src/soot/jimple/interproc/ifds/DontSynchronize.java new file mode 100644 index 0000000..3d76483 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/DontSynchronize.java @@ -0,0 +1,10 @@ +package soot.jimple.interproc.ifds; + +import static java.lang.annotation.ElementType.FIELD; + +import java.lang.annotation.Target; + +/** Semantic annotation stating that the annotated field can remain unsynchronized. + * This annotation is meant as a structured comment only, and has no immediate effect. */ +@Target(FIELD) +public @interface DontSynchronize{ String value() default ""; } \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/EdgeFunction.java b/src/soot/jimple/interproc/ifds/EdgeFunction.java new file mode 100644 index 0000000..f6e2a02 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/EdgeFunction.java @@ -0,0 +1,51 @@ +package soot.jimple.interproc.ifds; + + +/** + * An edge function computes how a V-type value changes when flowing from one + * super-graph node to another. See Sagiv, Reps, Horwitz 1996. + * + * NOTE: Methods defined on this type may be called simultaneously by different threads. + * Hence, classes implementing this interface should synchronize accesses to + * any mutable shared state. + * + * @param The type of values to be computed along flow edges. + */ +public interface EdgeFunction { + + /** + * Computes the value resulting from applying this function to source. + */ + V computeTarget(V source); + + /** + * Composes this function with the secondFunction, effectively returning + * a summary function that maps sources to targets exactly as if + * first this function had been applied and then the secondFunction. + */ + EdgeFunction composeWith(EdgeFunction secondFunction); + + /** + * Inverts this edge function. The semantics is: e1.composeWith + * (e2).composeWith(e2.invert()).equals(e1)==true. Implementors + * must provide functions "composeWith" and "invert" that adhere + * to this invariant. + * @return The inverse of this edge function + */ + EdgeFunction invert(); + + /** + * Returns a function that represents that (element-wise) join + * of this function with otherFunction. Naturally, this is a + * symmetric operation. + * @see JoinLattice#join(Object, Object) + */ + EdgeFunction joinWith(EdgeFunction otherFunction); + + /** + * Returns true is this function represents exactly the same + * source to target mapping as other. + */ + public boolean equalTo(EdgeFunction other); + +} diff --git a/src/soot/jimple/interproc/ifds/EdgeFunctionCache.java b/src/soot/jimple/interproc/ifds/EdgeFunctionCache.java new file mode 100644 index 0000000..7f49cf3 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/EdgeFunctionCache.java @@ -0,0 +1,278 @@ +package soot.jimple.interproc.ifds; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +public class EdgeFunctionCache implements EdgeFunctions { + + protected final EdgeFunctions delegate; + + protected final LoadingCache> normalCache; + + protected final LoadingCache> callCache; + + protected final LoadingCache> returnCache; + + protected final LoadingCache> callToReturnCache; + + @SuppressWarnings("unchecked") + public EdgeFunctionCache(final EdgeFunctions delegate, @SuppressWarnings("rawtypes") CacheBuilder builder) { + this.delegate = delegate; + + normalCache = builder.build(new CacheLoader>() { + public EdgeFunction load(NDNDKey key) throws Exception { + return delegate.getNormalEdgeFunction(key.getN1(), key.getD1(), key.getN2(), key.getD2()); + } + }); + + callCache = builder.build(new CacheLoader>() { + public EdgeFunction load(CallKey key) throws Exception { + return delegate.getCallEdgeFunction(key.getCallSite(), key.getD1(), key.getCalleeMethod(), key.getD2()); + } + }); + + returnCache = builder.build(new CacheLoader>() { + public EdgeFunction load(ReturnKey key) throws Exception { + return delegate.getReturnEdgeFunction(key.getCallSite(), key.getCalleeMethod(), key.getExitStmt(), key.getD1(), key.getReturnSite(), key.getD2()); + } + }); + + callToReturnCache = builder.build(new CacheLoader>() { + public EdgeFunction load(NDNDKey key) throws Exception { + return delegate.getCallToReturnEdgeFunction(key.getN1(), key.getD1(), key.getN2(), key.getD2()); + } + }); + } + + public EdgeFunction getNormalEdgeFunction(N curr, D currNode, N succ, D succNode) { + return normalCache.getUnchecked(new NDNDKey(curr, currNode, succ, succNode)); + } + + public EdgeFunction getCallEdgeFunction(N callStmt, D srcNode, M destinationMethod, D destNode) { + return callCache.getUnchecked(new CallKey(callStmt, srcNode, destinationMethod, destNode)); + } + + public EdgeFunction getReturnEdgeFunction(N callSite, M calleeMethod, N exitStmt, D exitNode, N returnSite, D retNode) { + return returnCache.getUnchecked(new ReturnKey(callSite, calleeMethod, exitStmt, exitNode, returnSite, retNode)); + } + + public EdgeFunction getCallToReturnEdgeFunction(N callSite, D callNode, N returnSite, D returnSideNode) { + return callToReturnCache.getUnchecked(new NDNDKey(callSite, callNode, returnSite, returnSideNode)); + } + + /** + * Invalidates all cache contents + */ + public void invalidateAll() { + this.normalCache.invalidateAll(); + this.callCache.invalidateAll(); + this.returnCache.invalidateAll(); + this.callToReturnCache.invalidateAll(); + } + + private class NDNDKey { + private final N n1, n2; + private final D d1, d2; + + public NDNDKey(N n1, D d1, N n2, D d2) { + this.n1 = n1; + this.n2 = n2; + this.d1 = d1; + this.d2 = d2; + } + + public N getN1() { + return n1; + } + + public D getD1() { + return d1; + } + + public N getN2() { + return n2; + } + + public D getD2() { + return d2; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((d1 == null) ? 0 : d1.hashCode()); + result = prime * result + ((d2 == null) ? 0 : d2.hashCode()); + result = prime * result + ((n1 == null) ? 0 : n1.hashCode()); + result = prime * result + ((n2 == null) ? 0 : n2.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + NDNDKey other = (NDNDKey) obj; + if (d1 == null) { + if (other.d1 != null) + return false; + } else if (!d1.equals(other.d1)) + return false; + if (d2 == null) { + if (other.d2 != null) + return false; + } else if (!d2.equals(other.d2)) + return false; + if (n1 == null) { + if (other.n1 != null) + return false; + } else if (!n1.equals(other.n1)) + return false; + if (n2 == null) { + if (other.n2 != null) + return false; + } else if (!n2.equals(other.n2)) + return false; + return true; + } + } + + private class CallKey { + private final N callSite; + private final M calleeMethod; + private final D d1, d2; + + public CallKey(N callSite, D d1, M calleeMethod, D d2) { + this.callSite = callSite; + this.calleeMethod = calleeMethod; + this.d1 = d1; + this.d2 = d2; + } + + public N getCallSite() { + return callSite; + } + + public D getD1() { + return d1; + } + + public M getCalleeMethod() { + return calleeMethod; + } + + public D getD2() { + return d2; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((d1 == null) ? 0 : d1.hashCode()); + result = prime * result + ((d2 == null) ? 0 : d2.hashCode()); + result = prime * result + ((callSite == null) ? 0 : callSite.hashCode()); + result = prime * result + ((calleeMethod == null) ? 0 : calleeMethod.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + CallKey other = (CallKey) obj; + if (d1 == null) { + if (other.d1 != null) + return false; + } else if (!d1.equals(other.d1)) + return false; + if (d2 == null) { + if (other.d2 != null) + return false; + } else if (!d2.equals(other.d2)) + return false; + if (callSite == null) { + if (other.callSite != null) + return false; + } else if (!callSite.equals(other.callSite)) + return false; + if (calleeMethod == null) { + if (other.calleeMethod != null) + return false; + } else if (!calleeMethod.equals(other.calleeMethod)) + return false; + return true; + } + } + + + private class ReturnKey extends CallKey { + + private final N exitStmt, returnSite; + + public ReturnKey(N callSite, M calleeMethod, N exitStmt, D exitNode, N returnSite, D retNode) { + super(callSite, exitNode, calleeMethod, retNode); + this.exitStmt = exitStmt; + this.returnSite = returnSite; + } + + public N getExitStmt() { + return exitStmt; + } + + public N getReturnSite() { + return returnSite; + } + + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((exitStmt == null) ? 0 : exitStmt.hashCode()); + result = prime * result + ((returnSite == null) ? 0 : returnSite.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + ReturnKey other = (ReturnKey) obj; + if (exitStmt == null) { + if (other.exitStmt != null) + return false; + } else if (!exitStmt.equals(other.exitStmt)) + return false; + if (returnSite == null) { + if (other.returnSite != null) + return false; + } else if (!returnSite.equals(other.returnSite)) + return false; + return true; + } + } + + + public void printStats() { + System.err.println("Stats for edge-function cache:"); + System.err.print("Normal: "); + System.err.println(normalCache.stats()); + System.err.print("Call: "); + System.err.println(callCache.stats()); + System.err.print("Return: "); + System.err.println(returnCache.stats()); + System.err.print("Call-to-return: "); + System.err.println(callToReturnCache.stats()); + } + +} diff --git a/src/soot/jimple/interproc/ifds/EdgeFunctions.java b/src/soot/jimple/interproc/ifds/EdgeFunctions.java new file mode 100644 index 0000000..f5c06a3 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/EdgeFunctions.java @@ -0,0 +1,96 @@ +package soot.jimple.interproc.ifds; + +/** + * Classes implementing this interface provide a range of edge functions used to + * compute a V-type value for each of the finitely many D-type values reachable + * in the program. + * + * @param + * The type of nodes in the interprocedural control-flow graph. + * Typically {@link Unit}. + * @param + * The type of data-flow facts to be computed by the tabulation + * problem. + * @param + * The type of objects used to represent methods. Typically + * {@link SootMethod}. + * @param + * The type of values to be computed along flow edges. + */ +public interface EdgeFunctions { + + /** + * Returns the function that computes how the V-typed value changes when + * being propagated from srcNode at statement src to tgtNode at statement + * tgt. + * + * @param curr + * The statement from which the flow originates. + * @param currNode + * The D-type value with which the source value is associated. + * @param succ + * The target statement of the flow. + * @param succNode + * The D-type value with which the target value will be + * associated. + */ + public EdgeFunction getNormalEdgeFunction(N curr, D currNode, N succ, D succNode); + + /** + * Returns the function that computes how the V-typed value changes when + * being propagated along a method call. + * + * @param callStmt + * The call statement from which the flow originates. + * @param srcNode + * The D-type value with which the source value is associated. + * @param destinationMethod + * A concrete destination method of the call. + * @param destNode + * The D-type value with which the target value will be + * associated at the side of the callee. + */ + public EdgeFunction getCallEdgeFunction(N callStmt, D srcNode, M destinationMethod, D destNode); + + /** + * Returns the function that computes how the V-typed value changes when + * being propagated along a method exit (return or throw). + * + * @param callSite + * One of all the call sites in the program that called the + * method from which the exitStmt is actually returning. This + * information can be exploited to compute a value that depend on + * information from before the call. + * @param calleeMethod + * The method from which we are exiting. + * @param exitStmt + * The exit statement from which the flow originates. + * @param exitNode + * The D-type value with which the source value is associated. + * @param returnSite + * One of the possible successor statements of a caller to the + * method we are exiting from. + * @param tgtNode + * The D-type value with which the target value will be + * associated at the returnSite. + */ + public EdgeFunction getReturnEdgeFunction(N callSite, M calleeMethod, N exitStmt, D exitNode, N returnSite, D retNode); + + /** + * Returns the function that computes how the V-typed value changes when + * being propagated from a method call to one of its intraprocedural + * successor. + * + * @param callSite + * The call statement from which the flow originates. + * @param callNode + * The D-type value with which the source value is associated. + * @param returnSite + * One of the possible successor statements of a call statement. + * @param returnSideNode + * The D-type value with which the target value will be + * associated at the returnSite. + */ + public EdgeFunction getCallToReturnEdgeFunction(N callSite, D callNode, N returnSite, D returnSideNode); + +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/FlowFunction.java b/src/soot/jimple/interproc/ifds/FlowFunction.java new file mode 100644 index 0000000..6982770 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/FlowFunction.java @@ -0,0 +1,30 @@ +package soot.jimple.interproc.ifds; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * A flow function computes which of the finitely many D-type values are reachable + * from the current source values. Typically there will be one such function + * associated with every possible control flow. + * + * NOTE: To be able to produce deterministic benchmarking results, we have found that + * it helps to return {@link LinkedHashSet}s from {@link #computeTargets(Object)}. This is + * because the duration of IDE's fixed point iteration may depend on the iteration order. + * Within the solver, we have tried to fix this order as much as possible, but the + * order, in general, does also depend on the order in which the result set + * of {@link #computeTargets(Object)} is traversed. + * + * NOTE: Methods defined on this type may be called simultaneously by different threads. + * Hence, classes implementing this interface should synchronize accesses to + * any mutable shared state. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + */ +public interface FlowFunction { + + /** + * Returns the target values reachable from the source. + */ + Set computeTargets(D source); +} diff --git a/src/soot/jimple/interproc/ifds/FlowFunctionCache.java b/src/soot/jimple/interproc/ifds/FlowFunctionCache.java new file mode 100644 index 0000000..092b99e --- /dev/null +++ b/src/soot/jimple/interproc/ifds/FlowFunctionCache.java @@ -0,0 +1,236 @@ +package soot.jimple.interproc.ifds; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +public class FlowFunctionCache implements FlowFunctions { + + protected final FlowFunctions delegate; + + protected final LoadingCache> normalCache; + + protected final LoadingCache> callCache; + + protected final LoadingCache> returnCache; + + protected final LoadingCache> callToReturnCache; + + @SuppressWarnings("unchecked") + public FlowFunctionCache(final FlowFunctions delegate, @SuppressWarnings("rawtypes") CacheBuilder builder) { + this.delegate = delegate; + + normalCache = builder.build(new CacheLoader>() { + public FlowFunction load(NNKey key) throws Exception { + return delegate.getNormalFlowFunction(key.getCurr(), key.getSucc()); + } + }); + + callCache = builder.build(new CacheLoader>() { + public FlowFunction load(CallKey key) throws Exception { + return delegate.getCallFlowFunction(key.getCallStmt(), key.getDestinationMethod()); + } + }); + + returnCache = builder.build(new CacheLoader>() { + public FlowFunction load(ReturnKey key) throws Exception { + return delegate.getReturnFlowFunction(key.getCallStmt(), key.getDestinationMethod(), key.getExitStmt(), key.getReturnSite()); + } + }); + + callToReturnCache = builder.build(new CacheLoader>() { + public FlowFunction load(NNKey key) throws Exception { + return delegate.getCallToReturnFlowFunction(key.getCurr(), key.getSucc()); + } + }); + } + + @Override + public FlowFunction getNormalFlowFunction(N curr, N succ) { + return normalCache.getUnchecked(new NNKey(curr, succ)); + } + + @Override + public FlowFunction getCallFlowFunction(N callStmt, M destinationMethod) { + return callCache.getUnchecked(new CallKey(callStmt, destinationMethod)); + } + + @Override + public FlowFunction getReturnFlowFunction(N callSite, M calleeMethod, N exitStmt, N returnSite) { + return returnCache.getUnchecked(new ReturnKey(callSite, calleeMethod, exitStmt, returnSite)); + } + + @Override + public FlowFunction getCallToReturnFlowFunction(N callSite, N returnSite) { + return callToReturnCache.getUnchecked(new NNKey(callSite, returnSite)); + } + + /** + * Invalidates all items in the cache + */ + public void invalidateAll() { + this.normalCache.invalidateAll(); + this.callCache.invalidateAll(); + this.returnCache.invalidateAll(); + this.callToReturnCache.invalidateAll(); + } + + private class NNKey { + private final N curr, succ; + + private NNKey(N curr, N succ) { + this.curr = curr; + this.succ = succ; + } + + public N getCurr() { + return curr; + } + + public N getSucc() { + return succ; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((curr == null) ? 0 : curr.hashCode()); + result = prime * result + ((succ == null) ? 0 : succ.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + NNKey other = (NNKey) obj; + if (curr == null) { + if (other.curr != null) + return false; + } else if (!curr.equals(other.curr)) + return false; + if (succ == null) { + if (other.succ != null) + return false; + } else if (!succ.equals(other.succ)) + return false; + return true; + } + } + + private class CallKey { + private final N callStmt; + private final M destinationMethod; + + private CallKey(N callStmt, M destinationMethod) { + this.callStmt = callStmt; + this.destinationMethod = destinationMethod; + } + + public N getCallStmt() { + return callStmt; + } + + public M getDestinationMethod() { + return destinationMethod; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((callStmt == null) ? 0 : callStmt.hashCode()); + result = prime * result + ((destinationMethod == null) ? 0 : destinationMethod.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + CallKey other = (CallKey) obj; + if (callStmt == null) { + if (other.callStmt != null) + return false; + } else if (!callStmt.equals(other.callStmt)) + return false; + if (destinationMethod == null) { + if (other.destinationMethod != null) + return false; + } else if (!destinationMethod.equals(other.destinationMethod)) + return false; + return true; + } + } + + private class ReturnKey extends CallKey { + + private final N exitStmt, returnSite; + + private ReturnKey(N callStmt, M destinationMethod, N exitStmt, N returnSite) { + super(callStmt, destinationMethod); + this.exitStmt = exitStmt; + this.returnSite = returnSite; + } + + public N getExitStmt() { + return exitStmt; + } + + public N getReturnSite() { + return returnSite; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((exitStmt == null) ? 0 : exitStmt.hashCode()); + result = prime * result + ((returnSite == null) ? 0 : returnSite.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("unchecked") + ReturnKey other = (ReturnKey) obj; + if (exitStmt == null) { + if (other.exitStmt != null) + return false; + } else if (!exitStmt.equals(other.exitStmt)) + return false; + if (returnSite == null) { + if (other.returnSite != null) + return false; + } else if (!returnSite.equals(other.returnSite)) + return false; + return true; + } + } + + public void printStats() { + System.err.println("Stats for flow-function cache:"); + System.err.print("Normal: "); + System.err.println(normalCache.stats()); + System.err.print("Call: "); + System.err.println(callCache.stats()); + System.err.print("Return: "); + System.err.println(returnCache.stats()); + System.err.print("Call-to-return: "); + System.err.println(callToReturnCache.stats()); + } + +} diff --git a/src/soot/jimple/interproc/ifds/FlowFunctions.java b/src/soot/jimple/interproc/ifds/FlowFunctions.java new file mode 100644 index 0000000..d96daef --- /dev/null +++ b/src/soot/jimple/interproc/ifds/FlowFunctions.java @@ -0,0 +1,90 @@ +package soot.jimple.interproc.ifds; + +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; + +/** + * Classes implementing this interface provide a factory for a + * range of flow functions used to compute which D-type values + * are reachable along the program's control flow. + * + * @param + * The type of nodes in the interprocedural control-flow graph. + * Typically {@link Unit}. + * @param + * The type of data-flow facts to be computed by the tabulation + * problem. + * @param + * The type of objects used to represent methods. Typically + * {@link SootMethod}. + */ +public interface FlowFunctions { + + /** + * Returns the flow function that computes the flow for a normal statement, + * i.e., a statement that is neither a call nor an exit statement. + * + * @param curr + * The current statement. + * @param succ + * The successor for which the flow is computed. This value can + * be used to compute a branched analysis that propagates + * different values depending on where control0flow branches. + */ + public FlowFunction getNormalFlowFunction(N curr, N succ); + + /** + * Returns the flow function that computes the flow for a call statement. + * + * @param callStmt + * The statement containing the invoke expression giving rise to + * this call. + * @param destinationMethod + * The concrete target method for which the flow is computed. + */ + public FlowFunction getCallFlowFunction(N callStmt, M destinationMethod); + + /** + * Returns the flow function that computes the flow for a an exit from a + * method. An exit can be a return or an exceptional exit. The + * {@link JimpleBasedInterproceduralCFG} defines an exit simply as a statement + * without successors in the intraprocedural control-flow graph. + * + * @param callSite + * One of all the call sites in the program that called the + * method from which the exitStmt is actually returning. This + * information can be exploited to compute a value that depend on + * information from before the call. + * @param calleeMethod + * The method from which exitStmt returns. + * @param exitStmt + * The statement exiting the method, typically a return or throw + * statement. + * @param returnSite + * One of the successor statements of the callSite. There may be + * multiple successors in case of possible exceptional flow. This + * method will be called for each such successor. + * @return + */ + public FlowFunction getReturnFlowFunction(N callSite, M calleeMethod, N exitStmt, N returnSite); + + /** + * Returns the flow function that computes the flow from a call site to a + * successor statement just after the call. There may be multiple successors + * in case of exceptional control flow. In this case this method will be + * called for every such successor. Typically, one will propagate into a + * method call, using {@link #getCallFlowFunction(Object, Object)}, only + * such information that actually concerns the callee method. All other + * information, e.g. information that cannot be modified by the call, is + * passed along this call-return edge. + * + * @param callSite + * The statement containing the invoke expression giving rise to + * this call. + * @param returnSite + * The return site to which the information is propagated. For + * exceptional flow, this may actually be the start of an + * exception handler. + */ + public FlowFunction getCallToReturnFlowFunction(N callSite, N returnSite); + +} diff --git a/src/soot/jimple/interproc/ifds/IDETabulationProblem.java b/src/soot/jimple/interproc/ifds/IDETabulationProblem.java new file mode 100644 index 0000000..4acbe28 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/IDETabulationProblem.java @@ -0,0 +1,39 @@ +package soot.jimple.interproc.ifds; + +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * Defines an IDE tabulation problem as presented in the Sagiv, Reps, Horwitz 1996 + * (SRH96) paper. An IDE tabulation problem extends an {@link IFDSTabulationProblem} + * by allowing additional values to be computed along flow functions: each domain value + * of type D maps at any program point to a value of type V. The functions describe how + * values are transformed when moving from one statement to another. + * + * The problem further defines a {@link JoinLattice}, which describes how values of + * type V are joined (merged) when multiple values are possible. + * + * @param The type of nodes in the interprocedural control-flow graph. Typically {@link Unit}. + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of objects used to represent methods. Typically {@link SootMethod}. + * @param The type of values to be computed along flow edges. + * @param The type of inter-procedural control-flow graph being used. + */ +public interface IDETabulationProblem,D extends UpdatableWrapper,M extends UpdatableWrapper, + V,I extends InterproceduralCFG> extends IFDSTabulationProblem{ + + /** + * Returns the edge functions that describe how V-values are transformed along + * flow function edges. + */ + EdgeFunctions edgeFunctions(); + + /** + * Returns the lattice describing how values of type V need to be joined. + */ + JoinLattice joinLattice(); + + /** + * Returns a function mapping everything to top. + */ + EdgeFunction allTopFunction(); +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/IFDSTabulationProblem.java b/src/soot/jimple/interproc/ifds/IFDSTabulationProblem.java new file mode 100644 index 0000000..c821567 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/IFDSTabulationProblem.java @@ -0,0 +1,64 @@ +package soot.jimple.interproc.ifds; + +import java.util.Set; + +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * A tabulation problem for solving in an {@link IFDSSolver} as described + * by the Reps, Horwitz, Sagiv 1995 (RHS95) paper. + * + * @param The type of nodes in the interprocedural control-flow graph. Typically {@link Unit}. + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of objects used to represent methods. Typically {@link SootMethod}. + * @param The type of inter-procedural control-flow graph being used. + */ +public interface IFDSTabulationProblem,D extends UpdatableWrapper, + M extends UpdatableWrapper, I extends InterproceduralCFG> { + + /** + * Returns a set of flow functions. Those functions are used to compute data-flow facts + * along the various kinds of control flows. + * + * NOTE: this method could be called many times. Implementations of this + * interface should therefore cache the return value! + */ + FlowFunctions flowFunctions(); + + /** + * Returns the interprocedural control-flow graph which this problem is computed over. + * Typically this will be a {@link JimpleBasedInterproceduralCFG}. + * + * NOTE: this method could be called many times. Implementations of this + * interface should therefore cache the return value! + */ + I interproceduralCFG(); + + /** + * Updates the locally cached control-flow graph over which the problem is computed. + * Typically this will be a {@link JimpleBasedInterproceduralCFG}. + * @param cfg The new control-flow graph + */ + void updateCFG(I cfg); + + /** + * Returns initial seeds to be used for the analysis. (a set of statements) + */ + Set initialSeeds(); + + /** + * This must be a data-flow fact of type {@link D}, but must not + * be part of the domain of data-flow facts. Typically this will be a + * singleton object of type {@link D} that is used for nothing else. + * It must holds that this object does not equals any object + * within the domain. + * + * NOTE: this method could be called many times. Implementations of this + * interface should therefore cache the return value! + */ + D zeroValue(); +} diff --git a/src/soot/jimple/interproc/ifds/InterproceduralCFG.java b/src/soot/jimple/interproc/ifds/InterproceduralCFG.java new file mode 100644 index 0000000..47f1291 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/InterproceduralCFG.java @@ -0,0 +1,236 @@ +package soot.jimple.interproc.ifds; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import soot.jimple.interproc.incremental.CFGChangeProvider; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * An interprocedural control-flow graph. + * + * @param Nodes in the CFG, typically {@link Unit} or {@link Block} + * @param Method representation + */ +public interface InterproceduralCFG extends CFGChangeProvider { + + /** + * Returns the method containing a node. + * @param n The node for which to get the parent method + */ + public M getMethodOf(N n); + + /** + * Returns the successor nodes. + */ + public List getSuccsOf(N n); + + /** + * Returns the predecessor nodes. + */ + public List getPredsOf(N n); + + /** + * Returns all callee methods for a given call. + */ + public Set getCalleesOfCallAt(N n); + + /** + * Returns all caller statements/nodes of a given method. + */ + public Set getCallersOf(M m); + + /** + * Returns all call sites within a given method. + */ + public Set getCallsFromWithin(M m); + + /** + * Returns all start points of a given method. There may be + * more than one start point in case of a backward analysis. + */ + public Set getStartPointsOf(M m); + + /** + * Returns all statements to which a call could return. + * In the RHS paper, for every call there is just one return site. + * We, however, use as return site the successor statements, of which + * there can be many in case of exceptional flow. + */ + public List getReturnSitesOfCallAt(N n); + + /** + * Returns true if the given statement is a call site. + */ + public boolean isCallStmt(N stmt); + + /** + * Returns true if the given statement leads to a method return + * (exceptional or not). For backward analyses may also be start statements. + */ + public boolean isExitStmt(N stmt); + + /** + * Returns true is this is a method's start statement. For backward analyses + * those may also be return or throws statements. + */ + public boolean isStartPoint(N stmt); + + /** + * Returns the set of all nodes that are neither call nor start nodes. + */ + public Set allNonCallStartNodes(); + + /** + * Returns whether succ is the fall-through successor of stmt, + * i.e., the unique successor that is be reached when stmt + * does not branch. + */ + public boolean isFallThroughSuccessor(N stmt, N succ); + + /** + * Returns whether succ is a branch target of stmt. + */ + public boolean isBranchTarget(N stmt, N succ); + + /** + * Returns whether this control-flow graph contains the given statement. + * This function is used for matching graphs. + */ + public boolean containsStmt(N stmt); + + /** + * Returns all nodes in this control-flow graph + */ + public List getAllNodes(); + + /** + * Computes a change set of added and removed edges in the control-flow + * graph + * @param newCFG The control flow graph after the update + * @param expiredEdges A list which receives the edges that are no longer + * present in the updated CFG + * @param newEdges A list which receives the edges that have been newly + * introduced in the updated CFG + * @param newNodes A list which receives the nodes that have been newly + * introduced in the updated CFG + * @param expiredNodes A list which receives the nodes that have been newly + * introduced in the updated CFG + */ + public void computeCFGChangeset(InterproceduralCFG newCFG, + Map> expiredEdges, + Map> newEdges, + Set newNodes, + Set expiredNodes); + + /** + * Finds a statement equivalent to the given one in the given method. The + * equivalence relation is left implicit here - in case multiple statements + * are equivalent to the given one, the "best/closest" pick shall be chosen. + * @param oldMethod The method in which to look for the statement + * @param newStmt The statement for which to look + * @return A statement in the given method which is equivalent to the + * given statement or NULL if no such statement could be found. + */ + public N findStatement(M oldMethod, N newStmt); + + /** + * Finds a statement equivalent to the given one in the given list of + * statements. The equivalence relation is left implicit here - in case + * multiple statements are equivalent to the given one, the "best/closest" + * pick shall be chosen which is usually the first option (the statement + * that will be executed first). + * @param oldMethod The list in which to look for the statement + * @param newStmt The statement for which to look + * @return A statement in the given list which is equivalent to the + * given statement or NULL if no such statement could be found. + */ + public N findStatement(Iterable oldMethod, N newStmt); + + /** + * Wraps an object and registers a listener so that the reference can be + * updated automatically when necessary. Implementations are required to + * return stable objects, i.e.: + *
    + *
  • Two calls to wrap(x) must return the same object for the same x
  • + *
  • The hash code of the returned wrapper must not depend on the + * wrapped object. More specifically, the hash code of y = wrap(x) + * must not change when y.notifyReferenceChanged(z) is called.
  • + *
+ * This function creates a weak reference so that both the wrapped object + * and the wrapper can be garbage-collected. + * @param obj The object to be wrapped + * @return The wrapped object + */ + public UpdatableWrapper wrapWeak(X obj); + + /** + * Wraps an object and registers a listener so that the reference can be + * updated automatically when necessary. Implementations are required to + * return stable objects, i.e.: + *
    + *
  • Two calls to wrap(x) must return the same object for the same x
  • + *
  • The hash code of the returned wrapper must not depend on the + * wrapped object. More specifically, the hash code of y = wrap(x) + * must not change when y.notifyReferenceChanged(z) is called.
  • + *
+ * This function creates a weak reference so that both the wrapped object + * and the wrapper can be garbage-collected. + * @param obj The list of objects to be wrapped + * @return The list of wrapped objects + */ + public List> wrapWeak(List obj); + + /** + * Wraps an object and registers a listener so that the reference can be + * updated automatically when necessary. Implementations are required to + * return stable objects, i.e.: + *
    + *
  • Two calls to wrap(x) must return the same object for the same x
  • + *
  • The hash code of the returned wrapper must not depend on the + * wrapped object. More specifically, the hash code of y = wrap(x) + * must not change when y.notifyReferenceChanged(z) is called.
  • + *
+ * This function creates a weak reference so that both the wrapped object + * and the wrapper can be garbage-collected. + * @param obj The set of objects to be wrapped + * @return The set of wrapped objects + */ + public Set> wrapWeak(Set obj); + + /** + * Merges the wrappers controlled by this control flow graph with the + * ones of another CFG. + *

+ * This means that this.wrap(x) must return the same object as b.wrap(x) + * after this.mergeWrappers(b) has been called. If both CFGs originally + * produce different wrappers for the same object, implementors may + * resolve to either value (or a completely new one) as long as both + * objects afterwards agree on the same wrapper. + *

+ * @param otherCfg The other control flow graph with which to merge + * the wrappers. + */ + public void mergeWrappers(InterproceduralCFG otherCfg); + + /** + * Gets the start point of the outermost loop containing the given + * statement. This functions only considers intraprocedural loops. + * @param stmt The statement for which to get the loop start point. + * @return The start point of the outermost loop containing the given + * statement, or NULL if the given statement is not contained in a + * loop. + */ + public N getLoopStartPointFor(N stmt); + + /** + * Gets all exit nodes that can transfer the control flow to the given + * return site. + * @param stmt The return site for which to get the exit nodes + * @return The set of exit nodes that transfer the control flow to the + * given return site. + */ + public Set getExitNodesForReturnSite(N stmt); + +} diff --git a/src/soot/jimple/interproc/ifds/JoinLattice.java b/src/soot/jimple/interproc/ifds/JoinLattice.java new file mode 100644 index 0000000..9d0762d --- /dev/null +++ b/src/soot/jimple/interproc/ifds/JoinLattice.java @@ -0,0 +1,17 @@ +package soot.jimple.interproc.ifds; + +/** + * This class defines a lattice in terms of its top and bottom elements + * and a join operation. + * + * @param The domain type for this lattice. + */ +public interface JoinLattice { + + V topElement(); + + V bottomElement(); + + V join(V left, V right); + +} diff --git a/src/soot/jimple/interproc/ifds/Lattice.java b/src/soot/jimple/interproc/ifds/Lattice.java new file mode 100644 index 0000000..7d9c4a8 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/Lattice.java @@ -0,0 +1,13 @@ +package soot.jimple.interproc.ifds; + +/** + * Interface that extends joinable half-lattices to full latices supporting + * both meet and join operations. + * + * @author Steven Arzt + */ +public interface Lattice extends JoinLattice { + + V meet(V left, V right); + +} diff --git a/src/soot/jimple/interproc/ifds/Main.java b/src/soot/jimple/interproc/ifds/Main.java new file mode 100644 index 0000000..9f8f10d --- /dev/null +++ b/src/soot/jimple/interproc/ifds/Main.java @@ -0,0 +1,146 @@ +package soot.jimple.interproc.ifds; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import soot.Local; +import soot.MethodOrMethodContext; +import soot.PackManager; +import soot.Scene; +import soot.SceneTransformer; +import soot.SootMethod; +import soot.Transform; +import soot.Unit; +import soot.jimple.AssignStmt; +import soot.jimple.Jimple; +import soot.jimple.StaticInvokeExpr; +import soot.jimple.interproc.ifds.problems.IFDSLocalInfoFlow; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.jimple.toolkits.callgraph.ReachableMethods; + +public class Main { + + /** + * @param args + */ + public static void main(String[] args) { + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + System.out.println("Running IFDS on initial CFG..."); + IFDSTabulationProblem,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> problem = + new IFDSLocalInfoFlow(new JimpleBasedInterproceduralCFG()); + + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + solver.solve(false); + + for (Unit u : Scene.v().getMainMethod().getActiveBody().getUnits()) + System.out.println(u); + + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + for (UpdatableWrapper l: solver.ifdsResultsAt(problem.interproceduralCFG().wrapWeak(ret))) + System.err.println(l); + System.out.println("Done."); + + // Patch the control-flow graph. We insert a new call from the main() method + // to our artificial helper method "otherMethod". + System.out.println("Patching cfg..."); + SootMethod helperMethod = Scene.v().getMainClass().getMethodByName("otherMethod"); + helperMethod.retrieveActiveBody(); + + Local localTestMe = null; + Local localFoo = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) { + if (l.getName().equals("r0")) + localTestMe = l; + else if (l.getName().equals("r1")) + localFoo = l; + } + StaticInvokeExpr invokeExpr = Jimple.v().newStaticInvokeExpr(helperMethod.makeRef(), localTestMe); + assert localTestMe != null; + assert localFoo != null; + + AssignStmt invokeStmt = Jimple.v().newAssignStmt(localFoo, invokeExpr); + for (Unit u : Scene.v().getMainMethod().getActiveBody().getUnits()) + if (u.toString().contains("doFoo")) { + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore (invokeStmt, u); + break; + } + + CallGraph cg = Scene.v().getCallGraph(); + Edge edge = new Edge(Scene.v().getMainMethod(), invokeStmt, helperMethod); + cg.addEdge(edge); + + // Check that our new method is indeed reachable + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(cg, eps.iterator()); + reachableMethods.update(); + boolean found = false; + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + if (m.getName().equals("otherMethod")) + found = true; + } + if (found) + System.out.println("Patched method found"); + else + System.err.println("Patched method NOT found"); + + // Patch the control-flow graph. We add an assignment to the + // "foo" variable inside the loop + /* + System.out.println("Patching cfg..."); + Local fooLocal = null; + Local argsLocal = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) + if (l.getName().equals("r1")) + fooLocal = l; + else if (l.getName().equals("r0")) + argsLocal = l; + AssignStmt assignStmt = Jimple.v().newAssignStmt(fooLocal, argsLocal); + JAssignStmt point = null; + for (Unit unit : Scene.v().getMainMethod().getActiveBody().getUnits()) { + if (unit instanceof JAssignStmt) { + JAssignStmt stmt = (JAssignStmt) unit; + if (stmt.getLeftOp().toString().equals("i0")) + if (stmt.getRightOp().toString().equals("i0 + -1")) { + point = stmt; + break; + } + } + } + if (point == null) { + System.err.println("Injection point not found"); + return; + } + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore (assignStmt, point); + */ + + System.out.println("Running IFDS on patched CFG..."); + + JimpleBasedInterproceduralCFG cfg = new JimpleBasedInterproceduralCFG(); + solver.update(cfg); + + ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + for (UpdatableWrapper l: solver.ifdsResultsAt(problem.interproceduralCFG().wrapWeak(ret))) { + System.err.println(l); + } + System.out.println("Done."); + } + })); + + soot.Main.main(args); + } + +} diff --git a/src/soot/jimple/interproc/ifds/MustSynchronize.java b/src/soot/jimple/interproc/ifds/MustSynchronize.java new file mode 100644 index 0000000..b3ffa05 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/MustSynchronize.java @@ -0,0 +1,10 @@ +package soot.jimple.interproc.ifds; + +import static java.lang.annotation.ElementType.FIELD; + +import java.lang.annotation.Target; + +/** Semantic annotation stating that the annotated field must be synchronized. + * This annotation is meant as a structured comment only, and has no immediate effect. */ +@Target(FIELD) +public @interface MustSynchronize{ String value() default ""; } \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/ProfiledFlowFunctions.java b/src/soot/jimple/interproc/ifds/ProfiledFlowFunctions.java new file mode 100644 index 0000000..512bba3 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/ProfiledFlowFunctions.java @@ -0,0 +1,48 @@ +package soot.jimple.interproc.ifds; + +/** + * A wrapper that can be used to profile flow functions. + */ +public class ProfiledFlowFunctions implements FlowFunctions { + + protected final FlowFunctions delegate; + + public long durationNormal, durationCall, durationReturn, durationCallReturn; + + public ProfiledFlowFunctions(FlowFunctions delegate) { + this.delegate = delegate; + } + + public FlowFunction getNormalFlowFunction(N curr, N succ) { + long before = System.currentTimeMillis(); + FlowFunction ret = delegate.getNormalFlowFunction(curr, succ); + long duration = System.currentTimeMillis() - before; + durationNormal += duration; + return ret; + } + + public FlowFunction getCallFlowFunction(N callStmt, M destinationMethod) { + long before = System.currentTimeMillis(); + FlowFunction res = delegate.getCallFlowFunction(callStmt, destinationMethod); + long duration = System.currentTimeMillis() - before; + durationCall += duration; + return res; + } + + public FlowFunction getReturnFlowFunction(N callSite, M calleeMethod, N exitStmt, N returnSite) { + long before = System.currentTimeMillis(); + FlowFunction res = delegate.getReturnFlowFunction(callSite, calleeMethod, exitStmt, returnSite); + long duration = System.currentTimeMillis() - before; + durationReturn += duration; + return res; + } + + public FlowFunction getCallToReturnFlowFunction(N callSite, N returnSite) { + long before = System.currentTimeMillis(); + FlowFunction res = delegate.getCallToReturnFlowFunction(callSite, returnSite); + long duration = System.currentTimeMillis() - before; + durationCallReturn += duration; + return res; + } + +} diff --git a/src/soot/jimple/interproc/ifds/SynchronizedBy.java b/src/soot/jimple/interproc/ifds/SynchronizedBy.java new file mode 100644 index 0000000..a4c6245 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/SynchronizedBy.java @@ -0,0 +1,10 @@ +package soot.jimple.interproc.ifds; + +import static java.lang.annotation.ElementType.FIELD; + +import java.lang.annotation.Target; + +/** Semantic annotation that the annotated field is synchronized. + * This annotation is meant as a structured comment only, and has no immediate effect. */ +@Target(FIELD) +public @interface SynchronizedBy{ String value() default ""; } \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/ThreadSafe.java b/src/soot/jimple/interproc/ifds/ThreadSafe.java new file mode 100644 index 0000000..70f3985 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/ThreadSafe.java @@ -0,0 +1,10 @@ +package soot.jimple.interproc.ifds; + +/** + * This annotation tells that the class was designed to be used by multiple threads, with concurrent updates. + */ +public @interface ThreadSafe { + + String value() default ""; + +} diff --git a/src/soot/jimple/interproc/ifds/ZeroedFlowFunctions.java b/src/soot/jimple/interproc/ifds/ZeroedFlowFunctions.java new file mode 100644 index 0000000..52d4eb9 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/ZeroedFlowFunctions.java @@ -0,0 +1,55 @@ +package soot.jimple.interproc.ifds; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +public class ZeroedFlowFunctions implements FlowFunctions { + + protected final FlowFunctions delegate; + protected final D zeroValue; + + public ZeroedFlowFunctions(FlowFunctions delegate, D zeroValue) { + this.delegate = delegate; + this.zeroValue = zeroValue; + } + + public FlowFunction getNormalFlowFunction(N curr, N succ) { + return new ZeroedFlowFunction(delegate.getNormalFlowFunction(curr, succ)); + } + + public FlowFunction getCallFlowFunction(N callStmt, M destinationMethod) { + return new ZeroedFlowFunction(delegate.getCallFlowFunction(callStmt, destinationMethod)); + } + + public FlowFunction getReturnFlowFunction(N callSite, M calleeMethod, N exitStmt, N returnSite) { + return new ZeroedFlowFunction(delegate.getReturnFlowFunction(callSite, calleeMethod, exitStmt, returnSite)); + } + + public FlowFunction getCallToReturnFlowFunction(N callSite, N returnSite) { + return new ZeroedFlowFunction(delegate.getCallToReturnFlowFunction(callSite, returnSite)); + } + + protected class ZeroedFlowFunction implements FlowFunction { + + protected FlowFunction del; + + private ZeroedFlowFunction(FlowFunction del) { + this.del = del; + } + + @Override + public Set computeTargets(D source) { + if (source == zeroValue || source.equals(zeroValue)) { + HashSet res = new LinkedHashSet(del.computeTargets(source)); + res.add(zeroValue); + return res; + } else { + return del.computeTargets(source); + } + } + + } + + +} diff --git a/src/soot/jimple/interproc/ifds/edgefunc/AllBottom.java b/src/soot/jimple/interproc/ifds/edgefunc/AllBottom.java new file mode 100644 index 0000000..75222ba --- /dev/null +++ b/src/soot/jimple/interproc/ifds/edgefunc/AllBottom.java @@ -0,0 +1,74 @@ +package soot.jimple.interproc.ifds.edgefunc; + +import soot.jimple.interproc.ifds.EdgeFunction; + + +public class AllBottom implements EdgeFunction { + + private final V bottomElement; + + public AllBottom(V bottomElement){ + this.bottomElement = bottomElement; + } + + @Override + public V computeTarget(V source) { + return bottomElement; + } + + @SuppressWarnings("rawtypes") + @Override + public EdgeFunction composeWith(EdgeFunction secondFunction) { + if (secondFunction instanceof AllBottomInverse) + if (((AllBottomInverse) secondFunction).getBottomElement() == this.bottomElement) + return EdgeIdentity.v(); + + return secondFunction; + } + + @SuppressWarnings("rawtypes") + @Override + public EdgeFunction joinWith(EdgeFunction otherFunction) { + // For otherFunction in {allTop, allBottom, id}, we always get allBottom + if(otherFunction == this || otherFunction.equalTo(this)) return this; + if(otherFunction instanceof AllBottomInverse) + if (((AllBottomInverse) otherFunction).getBottomElement() == this.bottomElement) + return this; + if(otherFunction instanceof AllTop) { + return this; + } + if(otherFunction instanceof EdgeIdentity) { + return this; + } + throw new IllegalStateException("(AllBottom) unexpected edge function: "+otherFunction); + } + + @Override + public boolean equalTo(EdgeFunction other) { + if(other instanceof AllBottom) { + @SuppressWarnings("rawtypes") + AllBottom allBottom = (AllBottom) other; + return allBottom.bottomElement.equals(bottomElement); + } + return false; + } + + @Override + public String toString() { + return "allbottom"; + } + + @Override + public EdgeFunction invert() { + return new AllBottomInverse(bottomElement); + } + + /** + * Gets the bottom element on which this function was constructed + * @return The bottom element on which this function was constructed + */ + V getBottomElement() { + return this.bottomElement; + } + +} diff --git a/src/soot/jimple/interproc/ifds/edgefunc/AllBottomInverse.java b/src/soot/jimple/interproc/ifds/edgefunc/AllBottomInverse.java new file mode 100644 index 0000000..f53eaa8 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/edgefunc/AllBottomInverse.java @@ -0,0 +1,76 @@ +package soot.jimple.interproc.ifds.edgefunc; + +import soot.jimple.interproc.ifds.EdgeFunction; + +/** + * Inverse function of {@link AllBottom} + */ +public class AllBottomInverse implements EdgeFunction { + + private final V bottomElement; + + public AllBottomInverse(V bottomElement){ + this.bottomElement = bottomElement; + } + + @Override + public V computeTarget(V source) { + return bottomElement; + } + + @SuppressWarnings("rawtypes") + @Override + public EdgeFunction composeWith(EdgeFunction secondFunction) { + if (secondFunction instanceof AllBottom) + if (((AllBottom) secondFunction).getBottomElement() == this.bottomElement) + return this; + if (secondFunction instanceof EdgeIdentity) + return this; + + throw new RuntimeException("Unknown function composition"); + } + + @Override + public EdgeFunction joinWith(EdgeFunction otherFunction) { + // For otherFunction in {allTop, allBottom, id}, we always get allBottom + if(otherFunction == this || otherFunction.equalTo(this)) return this; + if(otherFunction instanceof AllBottom) + return EdgeIdentity.v(); + if(otherFunction instanceof AllTopInverse) { + return this; + } + if(otherFunction instanceof EdgeIdentity) { + return this; + } + throw new IllegalStateException("(AllBottomInverse) unexpected edge function: "+otherFunction); + } + + @Override + public boolean equalTo(EdgeFunction other) { + if(other instanceof AllBottomInverse) { + @SuppressWarnings("rawtypes") + AllBottomInverse allBottom = (AllBottomInverse) other; + return allBottom.bottomElement.equals(bottomElement); + } + return false; + } + + @Override + public String toString() { + return "allbottominverse"; + } + + @Override + public EdgeFunction invert() { + return new AllBottom(bottomElement); + } + + /** + * Gets the bottom element on which this function was constructed + * @return The bottom element on which this function was constructed + */ + V getBottomElement() { + return this.bottomElement; + } + +} diff --git a/src/soot/jimple/interproc/ifds/edgefunc/AllTop.java b/src/soot/jimple/interproc/ifds/edgefunc/AllTop.java new file mode 100644 index 0000000..451e40a --- /dev/null +++ b/src/soot/jimple/interproc/ifds/edgefunc/AllTop.java @@ -0,0 +1,62 @@ +package soot.jimple.interproc.ifds.edgefunc; + +import soot.jimple.interproc.ifds.EdgeFunction; + + +public class AllTop implements EdgeFunction { + + private final V topElement; + + public AllTop(V topElement){ + this.topElement = topElement; + } + + @Override + public V computeTarget(V source) { + return topElement; + } + + @SuppressWarnings("rawtypes") + @Override + public EdgeFunction composeWith(EdgeFunction secondFunction) { + if (secondFunction instanceof AllTopInverse) + if (((AllTopInverse) secondFunction).getTopElement() == this.topElement) + return EdgeIdentity.v(); + + return secondFunction; + } + + @Override + public EdgeFunction joinWith(EdgeFunction otherFunction) { + return otherFunction; + } + + @Override + public boolean equalTo(EdgeFunction other) { + if(other instanceof AllTop) { + @SuppressWarnings("rawtypes") + AllTop allTop = (AllTop) other; + return allTop.topElement.equals(topElement); + } + return false; + } + + @Override + public String toString() { + return "alltop"; + } + + @Override + public EdgeFunction invert() { + return new AllTopInverse(this.topElement); + } + + /** + * Gets the top element on which this function was constructed + * @return The top element on which this function was constructed + */ + V getTopElement() { + return this.topElement; + } + +} diff --git a/src/soot/jimple/interproc/ifds/edgefunc/AllTopInverse.java b/src/soot/jimple/interproc/ifds/edgefunc/AllTopInverse.java new file mode 100644 index 0000000..0842884 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/edgefunc/AllTopInverse.java @@ -0,0 +1,62 @@ +package soot.jimple.interproc.ifds.edgefunc; + +import soot.jimple.interproc.ifds.EdgeFunction; + + +public class AllTopInverse implements EdgeFunction { + + private final V topElement; + + public AllTopInverse(V topElement){ + this.topElement = topElement; + } + + @Override + public V computeTarget(V source) { + return topElement; + } + + @SuppressWarnings("rawtypes") + @Override + public EdgeFunction composeWith(EdgeFunction secondFunction) { + if (secondFunction instanceof AllTop) + if (((AllTop) secondFunction).getTopElement() == this.topElement) + return EdgeIdentity.v(); + + throw new RuntimeException("Unknown function composition"); + } + + @Override + public EdgeFunction joinWith(EdgeFunction otherFunction) { + return otherFunction; + } + + @Override + public boolean equalTo(EdgeFunction other) { + if(other instanceof AllTopInverse) { + @SuppressWarnings("rawtypes") + AllTopInverse allTop = (AllTopInverse) other; + return allTop.topElement.equals(topElement); + } + return false; + } + + @Override + public String toString() { + return "alltop"; + } + + @Override + public EdgeFunction invert() { + return new AllTop(this.topElement); + } + + /** + * Gets the top element on which this function was constructed + * @return The top element on which this function was constructed + */ + V getTopElement() { + return this.topElement; + } + +} diff --git a/src/soot/jimple/interproc/ifds/edgefunc/EdgeIdentity.java b/src/soot/jimple/interproc/ifds/edgefunc/EdgeIdentity.java new file mode 100644 index 0000000..1bf4344 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/edgefunc/EdgeIdentity.java @@ -0,0 +1,60 @@ +package soot.jimple.interproc.ifds.edgefunc; + +import soot.jimple.interproc.ifds.EdgeFunction; + +/** + * The identity function on graph edges + * @param The type of values to be computed along flow edges. + */ +public class EdgeIdentity implements EdgeFunction { + + @SuppressWarnings("rawtypes") + private final static EdgeIdentity instance = new EdgeIdentity(); + + private EdgeIdentity(){} //use v() instead + + @Override + public V computeTarget(V source) { + return source; + } + + @Override + public EdgeFunction composeWith(EdgeFunction secondFunction) { + return secondFunction; + } + + @Override + public EdgeFunction joinWith(EdgeFunction otherFunction) { + if(otherFunction == this || otherFunction.equalTo(this)) return this; + if(otherFunction instanceof AllBottom) { + return otherFunction; + } + if(otherFunction instanceof AllTop) { + return this; + } + //do not know how to join; hence ask other function to decide on this + return otherFunction.joinWith(this); + } + + @Override + public boolean equalTo(EdgeFunction other) { + //singleton + return other==this; + } + + @SuppressWarnings("unchecked") + public static EdgeIdentity v() { + return instance; + } + + @Override + public String toString() { + return "id"; + } + + @Override + public EdgeFunction invert() { + return this; + } + +} diff --git a/src/soot/jimple/interproc/ifds/flowfunc/Gen.java b/src/soot/jimple/interproc/ifds/flowfunc/Gen.java new file mode 100644 index 0000000..090f5b9 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/flowfunc/Gen.java @@ -0,0 +1,38 @@ +package soot.jimple.interproc.ifds.flowfunc; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import soot.jimple.interproc.ifds.FlowFunction; + +/** + * Function that creates a new value (e.g. returns a set containing a fixed value when given + * a specific parameter), but acts like the identity function for all other parameters. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + */ +public class Gen implements FlowFunction { + + private final D genValue; + private final D zeroValue; + + public Gen(D genValue, D zeroValue){ + assert genValue != null; + assert zeroValue != null; + + this.genValue = genValue; + this.zeroValue = zeroValue; + } + + public Set computeTargets(D source) { + if (source.equals(zeroValue)) { + HashSet res = new HashSet(); + res.add(source); + res.add(genValue); + return res; + } else + return Collections.singleton(source); + } + +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/flowfunc/Identity.java b/src/soot/jimple/interproc/ifds/flowfunc/Identity.java new file mode 100644 index 0000000..6a97ec6 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/flowfunc/Identity.java @@ -0,0 +1,24 @@ +package soot.jimple.interproc.ifds.flowfunc; + +import java.util.Collections; +import java.util.Set; + +import soot.jimple.interproc.ifds.FlowFunction; + +public class Identity implements FlowFunction { + + @SuppressWarnings("rawtypes") + private final static Identity instance = new Identity(); + + private Identity(){} //use v() instead + + public Set computeTargets(D source) { + return Collections.singleton(source); + } + + @SuppressWarnings("unchecked") + public static Identity v() { + return instance; + } + +} diff --git a/src/soot/jimple/interproc/ifds/flowfunc/Kill.java b/src/soot/jimple/interproc/ifds/flowfunc/Kill.java new file mode 100644 index 0000000..9f55b02 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/flowfunc/Kill.java @@ -0,0 +1,30 @@ +package soot.jimple.interproc.ifds.flowfunc; + +import java.util.Collections; +import java.util.Set; + +import soot.jimple.interproc.ifds.FlowFunction; + +/** + * Function that kills a specific value (i.e. returns an empty set for when given this + * value as an argument), but behaves like the identity function for all other values. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + */ +public class Kill implements FlowFunction { + + private final D killValue; + + public Kill(D killValue){ + assert killValue != null; + this.killValue = killValue; + } + + public Set computeTargets(D source) { + if (source == killValue || source.equals(killValue)) { + return Collections.emptySet(); + } else + return Collections.singleton(source); + } + +} diff --git a/src/soot/jimple/interproc/ifds/flowfunc/KillAll.java b/src/soot/jimple/interproc/ifds/flowfunc/KillAll.java new file mode 100644 index 0000000..6b0deb5 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/flowfunc/KillAll.java @@ -0,0 +1,30 @@ +package soot.jimple.interproc.ifds.flowfunc; + +import java.util.Collections; +import java.util.Set; + +import soot.jimple.interproc.ifds.FlowFunction; + +/** + * The empty function, i.e. a function which returns an empty set for all points + * in the definition space. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + */ +public class KillAll implements FlowFunction { + + @SuppressWarnings("rawtypes") + private final static KillAll instance = new KillAll(); + + private KillAll(){} //use v() instead + + public Set computeTargets(D source) { + return Collections.emptySet(); + } + + @SuppressWarnings("unchecked") + public static KillAll v() { + return instance; + } + +} diff --git a/src/soot/jimple/interproc/ifds/flowfunc/Transfer.java b/src/soot/jimple/interproc/ifds/flowfunc/Transfer.java new file mode 100644 index 0000000..1eb8262 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/flowfunc/Transfer.java @@ -0,0 +1,35 @@ +package soot.jimple.interproc.ifds.flowfunc; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import soot.jimple.interproc.ifds.FlowFunction; + +public class Transfer implements FlowFunction { + + private final D toValue; + private final D fromValue; + + public Transfer(D toValue, D fromValue){ + assert toValue != null; + assert fromValue != null; + + this.toValue = toValue; + this.fromValue = fromValue; + } + + public Set computeTargets(D source) { + if (source == fromValue || source.equals(fromValue)) { + HashSet res = new HashSet(); + res.add(source); + res.add(toValue); + return res; + } else if (source.equals(toValue)) { + return Collections.emptySet(); + } else { + return Collections.singleton(source); + } + } + +} diff --git a/src/soot/jimple/interproc/ifds/problems/IFDSLocalInfoFlow.java b/src/soot/jimple/interproc/ifds/problems/IFDSLocalInfoFlow.java new file mode 100644 index 0000000..f14688c --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/IFDSLocalInfoFlow.java @@ -0,0 +1,149 @@ +package soot.jimple.interproc.ifds.problems; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import soot.Local; +import soot.NullType; +import soot.Scene; +import soot.SootMethod; +import soot.Unit; +import soot.Value; +import soot.jimple.AssignStmt; +import soot.jimple.DefinitionStmt; +import soot.jimple.IdentityStmt; +import soot.jimple.InvokeExpr; +import soot.jimple.ParameterRef; +import soot.jimple.ReturnStmt; +import soot.jimple.Stmt; +import soot.jimple.internal.JimpleLocal; +import soot.jimple.interproc.ifds.FlowFunction; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.flowfunc.Gen; +import soot.jimple.interproc.ifds.flowfunc.Identity; +import soot.jimple.interproc.ifds.flowfunc.Kill; +import soot.jimple.interproc.ifds.flowfunc.KillAll; +import soot.jimple.interproc.ifds.flowfunc.Transfer; +import soot.jimple.interproc.ifds.template.DefaultIFDSTabulationProblem; +import soot.jimple.interproc.incremental.DefaultUpdatableWrapper; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +public class IFDSLocalInfoFlow extends + DefaultIFDSTabulationProblem, + InterproceduralCFG, UpdatableWrapper>> { + + private static final UpdatableWrapper zeroValue = new DefaultUpdatableWrapper + (new JimpleLocal("zero", NullType.v())); + + public IFDSLocalInfoFlow(InterproceduralCFG,UpdatableWrapper> icfg) { + super(icfg); + } + + public FlowFunctions, UpdatableWrapper, UpdatableWrapper> createFlowFunctionsFactory() { + return new FlowFunctions,UpdatableWrapper,UpdatableWrapper>() { + + @Override + public FlowFunction> getNormalFlowFunction + (UpdatableWrapper src, UpdatableWrapper dest) { + if (src.getContents() instanceof IdentityStmt + && interproceduralCFG().getMethodOf(src)==interproceduralCFG().wrapWeak(Scene.v().getMainMethod())) { + IdentityStmt is = (IdentityStmt) src.getContents(); + Local leftLocal = (Local) is.getLeftOp(); + Value right = is.getRightOp(); + if (right instanceof ParameterRef) { + return new Gen> + (interproceduralCFG().wrapWeak(leftLocal), zeroValue()); + } + } + + if(src.getContents() instanceof AssignStmt) { + AssignStmt assignStmt = (AssignStmt) src.getContents(); + Value right = assignStmt.getRightOp(); + if(assignStmt.getLeftOp() instanceof Local) { + final Local leftLocal = (Local) assignStmt.getLeftOp(); + if(right instanceof Local) { + final Local rightLocal = (Local) right; + return new Transfer> + (interproceduralCFG().wrapWeak(leftLocal), interproceduralCFG().wrapWeak(rightLocal)); + } else { + return new Kill>(interproceduralCFG().wrapWeak(leftLocal)); + } + } + } + return Identity.v(); + } + + @Override + public FlowFunction> getCallFlowFunction + (UpdatableWrapper src, final UpdatableWrapper dest) { + Stmt stmt = (Stmt) src.getContents(); + InvokeExpr ie = stmt.getInvokeExpr(); + final List> callArgs = interproceduralCFG().wrapWeak(ie.getArgs()); + final List> paramLocals = new ArrayList>(); + for (int i = 0; i < dest.getContents().getParameterCount(); i++) + paramLocals.add(interproceduralCFG().wrapWeak(dest.getContents().getActiveBody().getParameterLocal(i))); + + return new FlowFunction>() { + + public Set> computeTargets(UpdatableWrapper source) { + int argIndex = callArgs.indexOf(interproceduralCFG().wrapWeak(source.getContents())); + if(argIndex>-1) { + return Collections.singleton(paramLocals.get(argIndex)); + } + return Collections.emptySet(); + } + }; + } + + @Override + public FlowFunction> getReturnFlowFunction + (UpdatableWrapper callSite, UpdatableWrapper callee, + UpdatableWrapper exitStmt, UpdatableWrapper retSite) { + if (exitStmt.getContents() instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) exitStmt.getContents(); + Value op = returnStmt.getOp(); + if(op instanceof Local) { + if(callSite.getContents() instanceof DefinitionStmt) { + DefinitionStmt defnStmt = (DefinitionStmt) callSite.getContents(); + Value leftOp = defnStmt.getLeftOp(); + if(leftOp instanceof Local) { + final UpdatableWrapper tgtLocal = interproceduralCFG().wrapWeak((Local) leftOp); + final UpdatableWrapper retLocal = interproceduralCFG().wrapWeak((Local) op); + return new FlowFunction>() { + + public Set> computeTargets(UpdatableWrapper source) { + if(source == retLocal) + return Collections.singleton(tgtLocal); + return Collections.emptySet(); + } + + }; + } + } + } + } + return KillAll.v(); + } + + @Override + public FlowFunction> getCallToReturnFlowFunction + (UpdatableWrapper call, UpdatableWrapper returnSite) { + return Identity.v(); + } + }; + } + + @Override + public UpdatableWrapper createZeroValue() { + return zeroValue; + } + + @Override + public Set> initialSeeds() { + return Collections.singleton(interproceduralCFG().wrapWeak + (Scene.v().getMainMethod().getActiveBody().getUnits().getFirst())); + } +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/problems/IFDSPossibleTypes.java b/src/soot/jimple/interproc/ifds/problems/IFDSPossibleTypes.java new file mode 100644 index 0000000..ec01482 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/IFDSPossibleTypes.java @@ -0,0 +1,219 @@ +package soot.jimple.interproc.ifds.problems; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import soot.Local; +import soot.PointsToAnalysis; +import soot.PointsToSet; +import soot.PrimType; +import soot.Scene; +import soot.SootMethod; +import soot.Type; +import soot.Unit; +import soot.UnknownType; +import soot.Value; +import soot.jimple.ArrayRef; +import soot.jimple.Constant; +import soot.jimple.DefinitionStmt; +import soot.jimple.InstanceFieldRef; +import soot.jimple.InvokeExpr; +import soot.jimple.Jimple; +import soot.jimple.NewExpr; +import soot.jimple.Ref; +import soot.jimple.ReturnStmt; +import soot.jimple.Stmt; +import soot.jimple.interproc.ifds.FlowFunction; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.flowfunc.Identity; +import soot.jimple.interproc.ifds.flowfunc.KillAll; +import soot.jimple.interproc.ifds.template.DefaultIFDSTabulationProblem; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.toolkits.scalar.Pair; + +@SuppressWarnings("serial") +public class IFDSPossibleTypes extends DefaultIFDSTabulationProblem, UpdatableWrapper>> { + + private Map, UpdatableWrapper>, UpdatablePossibleType> wrapperObjects = + new HashMap, UpdatableWrapper>, UpdatablePossibleType>(); + + public IFDSPossibleTypes(InterproceduralCFG,UpdatableWrapper> icfg) { + super(icfg); + } + + private UpdatablePossibleType createPossibleType(Value value, Type type) { + UpdatableWrapper wrappedValue = this.interproceduralCFG().wrapWeak(value); + UpdatableWrapper wrappedType = this.interproceduralCFG().wrapWeak(type); + Pair, UpdatableWrapper> pair = + new Pair, UpdatableWrapper>(wrappedValue, wrappedType); + + UpdatablePossibleType upt = this.wrapperObjects.get(pair); + if (upt == null) + synchronized (this.wrapperObjects) { + // Fetch again while we have the lock. This shall ensure that no + // other object has been created in the meantime. + upt = this.wrapperObjects.get(pair); + if (upt == null) { + upt = new UpdatablePossibleType(value, type); + this.wrapperObjects.put(pair, upt); + this.interproceduralCFG().registerListener(upt, value); + this.interproceduralCFG().registerListener(upt, type); + } + } + return upt; + } + + public FlowFunctions, UpdatablePossibleType, UpdatableWrapper> createFlowFunctionsFactory() { + return new FlowFunctions,UpdatablePossibleType,UpdatableWrapper>() { + + public FlowFunction getNormalFlowFunction(UpdatableWrapper src, UpdatableWrapper dest) { + if(src.getContents() instanceof DefinitionStmt) { + final DefinitionStmt defnStmt = (DefinitionStmt) src.getContents(); + if(defnStmt.containsInvokeExpr()) return Identity.v(); + + final Value right = defnStmt.getRightOp(); + final Value left = defnStmt.getLeftOp(); + //won't track primitive-typed variables + if(right.getType() instanceof PrimType) return Identity.v(); + + if(right instanceof Constant || right instanceof NewExpr) { + return new FlowFunction() { + public Set computeTargets(UpdatablePossibleType source) { + if(source.getContents().equals(new Pair(Jimple.v().newLocal("", UnknownType.v()), UnknownType.v()))) { + Set res = new LinkedHashSet(); + res.add(createPossibleType(left,right.getType())); + res.add(createPossibleType(Jimple.v().newLocal("", UnknownType.v()), UnknownType.v())); + return res; + } else if(source.getValue() instanceof Local && source.getValue().equivTo(left)) { + //strong update for local variables + return Collections.emptySet(); + } else { + return Collections.singleton(source); + } + } + }; + } else if(right instanceof Ref || right instanceof Local) { + return new FlowFunction() { + public Set computeTargets(final UpdatablePossibleType source) { + Value value = source.getValue(); + if(value instanceof Local && value.equivTo(left)) { + //strong update for local variables + return Collections.emptySet(); + } else if(maybeSameLocation(value,right)) { + return new LinkedHashSet() {{ + add(createPossibleType(left,source.getType())); + add(source); + }}; + } else { + return Collections.singleton(source); + } + } + + private boolean maybeSameLocation(Value v1, Value v2) { + if(!(v1 instanceof InstanceFieldRef && v2 instanceof InstanceFieldRef) && + !(v1 instanceof ArrayRef && v2 instanceof ArrayRef)) { + return v1.equivTo(v2); + } + if(v1 instanceof InstanceFieldRef && v2 instanceof InstanceFieldRef) { + InstanceFieldRef ifr1 = (InstanceFieldRef) v1; + InstanceFieldRef ifr2 = (InstanceFieldRef) v2; + if(!ifr1.getField().getName().equals(ifr2.getField().getName())) return false; + + Local base1 = (Local) ifr1.getBase(); + Local base2 = (Local) ifr2.getBase(); + PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); + PointsToSet pts1 = pta.reachingObjects(base1); + PointsToSet pts2 = pta.reachingObjects(base2); + return pts1.hasNonEmptyIntersection(pts2); + } else { //v1 instanceof ArrayRef && v2 instanceof ArrayRef + ArrayRef ar1 = (ArrayRef) v1; + ArrayRef ar2 = (ArrayRef) v2; + + Local base1 = (Local) ar1.getBase(); + Local base2 = (Local) ar2.getBase(); + PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); + PointsToSet pts1 = pta.reachingObjects(base1); + PointsToSet pts2 = pta.reachingObjects(base2); + return pts1.hasNonEmptyIntersection(pts2); + } + } + }; + } + } + return Identity.v(); + } + + public FlowFunction getCallFlowFunction + (final UpdatableWrapper src, final UpdatableWrapper dest) { + Stmt stmt = (Stmt) src.getContents(); + InvokeExpr ie = stmt.getInvokeExpr(); + final List> callArgs = interproceduralCFG().wrapWeak(ie.getArgs()); + final List> paramLocals = new ArrayList>(); + for(int i=0;i() { + public Set computeTargets(UpdatablePossibleType source) { + Value value = source.getValue(); + int argIndex = callArgs.indexOf(interproceduralCFG().wrapWeak(value)); + if(argIndex>-1) { + return Collections.singleton(createPossibleType(paramLocals.get(argIndex).getContents(), source.getType())); + } + return Collections.emptySet(); + } + }; + } + + public FlowFunction getReturnFlowFunction + (UpdatableWrapper callSite, + UpdatableWrapper callee, + UpdatableWrapper exitStmt, + UpdatableWrapper retSite) { + if (exitStmt.getContents() instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) exitStmt.getContents(); + Value op = returnStmt.getOp(); + if(op instanceof Local) { + if(callSite.getContents() instanceof DefinitionStmt) { + DefinitionStmt defnStmt = (DefinitionStmt) callSite.getContents(); + Value leftOp = defnStmt.getLeftOp(); + if(leftOp instanceof Local) { + final UpdatableWrapper tgtLocal = interproceduralCFG().wrapWeak((Local) leftOp); + final UpdatableWrapper retLocal = interproceduralCFG().wrapWeak((Local) op); + return new FlowFunction() { + + public Set computeTargets(UpdatablePossibleType source) { + if(source.getContents() == retLocal.getContents()) + return Collections.singleton(createPossibleType(tgtLocal.getContents(), source.getType())); + return Collections.emptySet(); + } + + }; + } + } + } + } + return KillAll.v(); + } + + public FlowFunction getCallToReturnFlowFunction + (UpdatableWrapper call, UpdatableWrapper returnSite) { + return Identity.v(); + } + }; + } + + public Set> initialSeeds() { + return Collections.singleton(interproceduralCFG().wrapWeak(Scene.v().getMainMethod().getActiveBody().getUnits().getFirst())); + } + + public UpdatablePossibleType createZeroValue() { + return createPossibleType(Jimple.v().newLocal("", UnknownType.v()), UnknownType.v()); + } +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/problems/IFDSReachingDefinitions.java b/src/soot/jimple/interproc/ifds/problems/IFDSReachingDefinitions.java new file mode 100644 index 0000000..96150f3 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/IFDSReachingDefinitions.java @@ -0,0 +1,194 @@ +package soot.jimple.interproc.ifds.problems; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import soot.EquivalentValue; +import soot.Scene; +import soot.SootMethod; +import soot.Unit; +import soot.Value; +import soot.jimple.DefinitionStmt; +import soot.jimple.InvokeExpr; +import soot.jimple.Jimple; +import soot.jimple.ReturnStmt; +import soot.jimple.ReturnVoidStmt; +import soot.jimple.Stmt; +import soot.jimple.interproc.ifds.FlowFunction; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.flowfunc.Identity; +import soot.jimple.interproc.ifds.flowfunc.KillAll; +import soot.jimple.interproc.ifds.template.DefaultIFDSTabulationProblem; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.toolkits.scalar.Pair; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +public class IFDSReachingDefinitions extends DefaultIFDSTabulationProblem + , UpdatableWrapper>> { + + private static final UpdatableReachingDefinition zeroValue = UpdatableReachingDefinition.zero; + private final LoadingCache, Set>>, + UpdatableReachingDefinition> wrapperObjects; + + public IFDSReachingDefinitions(final InterproceduralCFG, UpdatableWrapper> icfg) { + super(icfg); + + CacheBuilder cb = CacheBuilder.newBuilder().concurrencyLevel + (Runtime.getRuntime().availableProcessors()).initialCapacity(100000); //.weakKeys(); + this.wrapperObjects = cb.build(new CacheLoader, Set>>, UpdatableReachingDefinition>() { + + @Override + public UpdatableReachingDefinition load + (Pair, Set>> key) throws Exception { + UpdatableReachingDefinition urd = new UpdatableReachingDefinition(key.getO1(), key.getO2()); + return urd; + } + + }); + } + + private UpdatableReachingDefinition createReachingDefinition(Value value, Set definitions) { + UpdatableWrapper wrappedValue = this.interproceduralCFG().wrapWeak(value); + Set> wrappedDefs = this.interproceduralCFG().wrapWeak(definitions); + Pair, Set>> pair = + new Pair, Set>>(wrappedValue, wrappedDefs); + + try { + return this.wrapperObjects.get(pair); + } catch (ExecutionException e) { + System.err.println("Could not wrap object"); + e.printStackTrace(); + return null; + } + } + + @Override + public FlowFunctions, UpdatableReachingDefinition, UpdatableWrapper> createFlowFunctionsFactory() { + return new FlowFunctions, UpdatableReachingDefinition, UpdatableWrapper>() { + + @Override + public FlowFunction getNormalFlowFunction + (final UpdatableWrapper curr, UpdatableWrapper succ) { + if (curr.getContents() instanceof DefinitionStmt) { + final UpdatableWrapper assignment = interproceduralCFG().wrapWeak + ((DefinitionStmt) curr.getContents()); + + return new FlowFunction() { + @Override + public Set computeTargets(UpdatableReachingDefinition source) { + if (!source.equals(zeroValue())) { + if (source.getContents().getO1().equivTo(assignment.getContents().getLeftOp())) { + return Collections.emptySet(); + } + return Collections.singleton(source); + } else { + LinkedHashSet res = new LinkedHashSet(); + res.add(createReachingDefinition(assignment.getContents().getLeftOp(), + Collections. singleton(assignment.getContents()))); + return res; + } + } + }; + } + + return Identity.v(); + } + + @Override + public FlowFunction getCallFlowFunction + (UpdatableWrapper callStmt, + final UpdatableWrapper destinationMethod) { + Stmt stmt = (Stmt) callStmt.getContents(); + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + final List> args = interproceduralCFG().wrapWeak(invokeExpr.getArgs()); + + return new FlowFunction() { + + @Override + public Set computeTargets(UpdatableReachingDefinition source) { + UpdatableWrapper value = interproceduralCFG().wrapWeak(source.getValue()); + if(args.contains(value)) { + int paramIndex = args.indexOf(value); + UpdatableReachingDefinition pair = createReachingDefinition + (new EquivalentValue(Jimple.v().newParameterRef + (destinationMethod.getContents().getParameterType(paramIndex), paramIndex)), + source.getContents().getO2()); + return Collections.singleton(pair); + } + + return Collections.emptySet(); + } + }; + } + + @Override + public FlowFunction getReturnFlowFunction + (final UpdatableWrapper callSite, + UpdatableWrapper calleeMethod, + final UpdatableWrapper exitStmt, + UpdatableWrapper returnSite) { + if (!(callSite.getContents() instanceof DefinitionStmt)) + return KillAll.v(); + + if (exitStmt.getContents() instanceof ReturnVoidStmt) + return KillAll.v(); + + return new FlowFunction() { + + @Override + public Set computeTargets(UpdatableReachingDefinition source) { + if (exitStmt.getContents() instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) exitStmt.getContents(); + if (returnStmt.getOp().equivTo(source.getContents().getO1())) { + DefinitionStmt definitionStmt = (DefinitionStmt) callSite.getContents(); + UpdatableReachingDefinition pair = createReachingDefinition + (definitionStmt.getLeftOp(), source.getContents().getO2()); + return Collections.singleton(pair); + } + } + return Collections.emptySet(); + } + }; + } + + @Override + public FlowFunction getCallToReturnFlowFunction + (UpdatableWrapper callSite, UpdatableWrapper returnSite) { + if (!(callSite.getContents() instanceof DefinitionStmt)) + return Identity.v(); + + final UpdatableWrapper definitionStmt = interproceduralCFG().wrapWeak + ((DefinitionStmt) callSite.getContents()); + return new FlowFunction() { + + @Override + public Set computeTargets(UpdatableReachingDefinition source) { + if(source.getContents().getO1().equivTo(definitionStmt.getContents().getLeftOp())) { + return Collections.emptySet(); + } else { + return Collections.singleton(source); + } + } + }; + } + }; + } + + @Override + public Set> initialSeeds() { + return Collections.singleton(this.interproceduralCFG().wrapWeak + (Scene.v().getMainMethod().getActiveBody().getUnits().getFirst())); + } + + public UpdatableReachingDefinition createZeroValue() { + return zeroValue; + } + +} diff --git a/src/soot/jimple/interproc/ifds/problems/IFDSUninitializedVariables.java b/src/soot/jimple/interproc/ifds/problems/IFDSUninitializedVariables.java new file mode 100644 index 0000000..71da882 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/IFDSUninitializedVariables.java @@ -0,0 +1,205 @@ +package soot.jimple.interproc.ifds.problems; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import soot.Local; +import soot.NullType; +import soot.Scene; +import soot.SootMethod; +import soot.Unit; +import soot.Value; +import soot.ValueBox; +import soot.jimple.DefinitionStmt; +import soot.jimple.InvokeExpr; +import soot.jimple.ReturnStmt; +import soot.jimple.Stmt; +import soot.jimple.ThrowStmt; +import soot.jimple.internal.JimpleLocal; +import soot.jimple.interproc.ifds.FlowFunction; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.flowfunc.Identity; +import soot.jimple.interproc.ifds.flowfunc.Kill; +import soot.jimple.interproc.ifds.flowfunc.KillAll; +import soot.jimple.interproc.ifds.template.DefaultIFDSTabulationProblem; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +public class IFDSUninitializedVariables extends DefaultIFDSTabulationProblem, + InterproceduralCFG, UpdatableWrapper>> { + + public IFDSUninitializedVariables(InterproceduralCFG, UpdatableWrapper> icfg) { + super(icfg); + } + + @Override + public FlowFunctions, UpdatableWrapper, UpdatableWrapper> + createFlowFunctionsFactory() { + return new FlowFunctions, UpdatableWrapper, UpdatableWrapper>() { + + @Override + public FlowFunction> getNormalFlowFunction(UpdatableWrapper curr, UpdatableWrapper succ) { + final UpdatableWrapper m = interproceduralCFG().getMethodOf(curr); + + if(Scene.v().getEntryPoints().contains(m) && interproceduralCFG().isStartPoint(curr)) { + return new FlowFunction>() { + + @Override + public Set> computeTargets(UpdatableWrapper source) { + if (source == zeroValue()) { + Set> res = new LinkedHashSet>(); + for (Local l : m.getContents().getActiveBody().getLocals()) + res.add(interproceduralCFG().wrapWeak(l)); + for(int i=0;i leftOpLocal = interproceduralCFG().wrapWeak((Local) leftOp); + return new FlowFunction>() { + + @Override + public Set> computeTargets(final UpdatableWrapper source) { + List useBoxes = definition.getUseBoxes(); + for (ValueBox valueBox : useBoxes) { + if (valueBox.getValue().equivTo(source.getContents())) { + LinkedHashSet> res = new LinkedHashSet>(); + res.add(source); + res.add(leftOpLocal); + return res; + } + } + + if (leftOp.equivTo(source)) + return Collections.emptySet(); + + return Collections.singleton(source); + } + + }; + } + } + + return Identity.v(); + } + + @Override + public FlowFunction> getCallFlowFunction + (UpdatableWrapper callStmt, final UpdatableWrapper destinationMethod) { + Stmt stmt = (Stmt) callStmt.getContents(); + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + final List args = invokeExpr.getArgs(); + + final List> localArguments = new ArrayList>(); + for (Value value : args) + if (value instanceof Local) + localArguments.add(interproceduralCFG().wrapWeak((Local) value)); + + return new FlowFunction>() { + + @Override + public Set> computeTargets(final UpdatableWrapper source) { + for (UpdatableWrapper localArgument : localArguments) { + if (source.getContents().equivTo(localArgument.getContents())) { + return Collections.>singleton(interproceduralCFG().wrapWeak + (destinationMethod.getContents().getActiveBody().getParameterLocal(args.indexOf(localArgument)))); + } + } + + if (source == zeroValue()) { + //gen all locals that are not parameter locals + LinkedHashSet> uninitializedLocals = new LinkedHashSet>(); + for (Local l : destinationMethod.getContents().getActiveBody().getLocals()) + uninitializedLocals.add(interproceduralCFG().wrapWeak(l)); + for(int i=0;i> getReturnFlowFunction + (final UpdatableWrapper callSite, + UpdatableWrapper calleeMethod, + final UpdatableWrapper exitStmt, + UpdatableWrapper returnSite) { + if (callSite.getContents() instanceof DefinitionStmt) { + final DefinitionStmt definition = (DefinitionStmt) callSite.getContents(); + if(definition.getLeftOp() instanceof Local) { + final UpdatableWrapper leftOpLocal = interproceduralCFG().wrapWeak((Local) definition.getLeftOp()); + if (exitStmt.getContents() instanceof ReturnStmt) { + final UpdatableWrapper returnStmt = + interproceduralCFG().wrapWeak((ReturnStmt) exitStmt.getContents()); + return new FlowFunction>() { + + @Override + public Set> computeTargets(UpdatableWrapper source) { + if (returnStmt.getContents().getOp().equivTo(source.getContents())) + return Collections.singleton(leftOpLocal); + return Collections.emptySet(); + } + + }; + } else if (exitStmt.getContents() instanceof ThrowStmt) { + //if we throw an exception, LHS of call is undefined + return new FlowFunction>() { + + @Override + public Set> computeTargets(final UpdatableWrapper source) { + if (source == zeroValue()) + return Collections.singleton(leftOpLocal); + else + return Collections.emptySet(); + } + + }; + } + } + } + + return KillAll.v(); + } + + @Override + public FlowFunction> getCallToReturnFlowFunction + (UpdatableWrapper callSite, UpdatableWrapper returnSite) { + if (callSite.getContents() instanceof DefinitionStmt) { + DefinitionStmt definition = (DefinitionStmt) callSite.getContents(); + if(definition.getLeftOp() instanceof Local) { + final Local leftOpLocal = (Local) definition.getLeftOp(); + return new Kill>(interproceduralCFG().wrapWeak(leftOpLocal)); + } + } + return Identity.v(); + } + }; + } + @Override + public Set> initialSeeds() { + return Collections.singleton(interproceduralCFG().wrapWeak(Scene.v().getMainMethod().getActiveBody().getUnits().getFirst())); + } + + @Override + public UpdatableWrapper createZeroValue() { + return interproceduralCFG().wrapWeak((Local) new JimpleLocal("<>", NullType.v())); + } + +} diff --git a/src/soot/jimple/interproc/ifds/problems/UpdatablePossibleType.java b/src/soot/jimple/interproc/ifds/problems/UpdatablePossibleType.java new file mode 100644 index 0000000..79ede0f --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/UpdatablePossibleType.java @@ -0,0 +1,63 @@ +package soot.jimple.interproc.ifds.problems; + +import soot.Type; +import soot.Value; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.toolkits.scalar.Pair; + +public class UpdatablePossibleType implements UpdatableWrapper> { + + private Value value; + private Type type; + + private Value previousValue = null; + private Type previousType = null; + + public UpdatablePossibleType(Pair possibleType) { + this.value = possibleType.getO1(); + this.type = possibleType.getO2(); + } + + public UpdatablePossibleType(Value value, Type type) { + this.value = value; + this.type = type; + } + + public Value getValue() { + return this.value; + } + + public Type getType() { + return this.type; + } + + @Override + public void notifyReferenceChanged(Object oldObject, Object newObject) { + if (oldObject == this.value) + this.value = (Value) newObject; + if (oldObject == this.type) + this.type = (Type) newObject; + } + + @Override + public Pair getContents() { + return new Pair(this.value, this.type); + } + + @Override + public Pair getPreviousContents() { + return new Pair(this.previousValue, this.previousType); + } + + @Override + public boolean hasPreviousContents() { + return this.previousValue != null && this.previousType != null; + } + + @Override + public void setSafepoint() { + this.previousValue = this.value; + this.previousType = this.type; + } + +} diff --git a/src/soot/jimple/interproc/ifds/problems/UpdatableReachingDefinition.java b/src/soot/jimple/interproc/ifds/problems/UpdatableReachingDefinition.java new file mode 100644 index 0000000..a8b0553 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/problems/UpdatableReachingDefinition.java @@ -0,0 +1,111 @@ +package soot.jimple.interproc.ifds.problems; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import soot.NullType; +import soot.Value; +import soot.jimple.DefinitionStmt; +import soot.jimple.internal.JimpleLocal; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.toolkits.scalar.Pair; + +public class UpdatableReachingDefinition implements UpdatableWrapper>> { + + private UpdatableWrapper value = null; + private Set> definitions; + + public static final UpdatableReachingDefinition zero = new UpdatableReachingDefinition(); + + private static final Value EMPTY_VALUE = new JimpleLocal("<>", NullType.v()); + + private UpdatableReachingDefinition() { + } + + public UpdatableReachingDefinition(Pair, Set>> n) { + this.value = n.getO1(); + this.definitions = n.getO2(); + } + + public UpdatableReachingDefinition(UpdatableWrapper value, Set> definitions) { + this.value = value; + this.definitions = definitions; + } + + @Override + public void notifyReferenceChanged(Object oldObject, Object newObject) { + this.value.notifyReferenceChanged(oldObject, newObject); + for (UpdatableWrapper def : this.definitions) + def.notifyReferenceChanged(oldObject, newObject); + } + + @Override + public Pair> getContents() { + return new Pair>(getValue(), getDefinitions()); + } + + @Override + public String toString() { + return value + " -> " + definitions; + } + + @Override + public Pair> getPreviousContents() { + Set defs = new HashSet(this.definitions.size()); + for (UpdatableWrapper ds : this.definitions) + defs.add(ds.getPreviousContents()); + return new Pair>(this.value.getPreviousContents(), defs); + } + + @Override + public void setSafepoint() { + this.value.setSafepoint(); + for (UpdatableWrapper ds : this.definitions) + ds.setSafepoint(); + } + + @Override + public boolean equals(Object another) { + if (super.equals(another)) + return true; + + if (another == null) + return false; + if (!(another instanceof UpdatableReachingDefinition)) + return false; + UpdatableReachingDefinition urd = (UpdatableReachingDefinition) another; + + if (this.value == null) + { if (urd.value != null) return false; } + else { if (!this.value.equals(urd.value)) return false; } + + if (this.definitions == null) + { if (urd.definitions != null) return false; } + else { if (!this.definitions.equals(urd.value)) return false; } + + return true; + } + + @Override + public boolean hasPreviousContents() { + return this.value.hasPreviousContents(); + } + + public Value getValue() { + if (value == null) + return EMPTY_VALUE; + return this.value.getContents(); + } + + public Set getDefinitions() { + if (this.definitions == null) + return Collections.emptySet(); + + Set defs = new HashSet(this.definitions.size()); + for (UpdatableWrapper ds : this.definitions) + defs.add(ds.getContents()); + return defs; + } + +} diff --git a/src/soot/jimple/interproc/ifds/solver/IDESolver.java b/src/soot/jimple/interproc/ifds/solver/IDESolver.java new file mode 100644 index 0000000..e5c4d01 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/solver/IDESolver.java @@ -0,0 +1,1493 @@ +package soot.jimple.interproc.ifds.solver; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +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 java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import soot.PatchingChain; +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.DontSynchronize; +import soot.jimple.interproc.ifds.EdgeFunction; +import soot.jimple.interproc.ifds.EdgeFunctionCache; +import soot.jimple.interproc.ifds.EdgeFunctions; +import soot.jimple.interproc.ifds.FlowFunction; +import soot.jimple.interproc.ifds.FlowFunctionCache; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.IDETabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.JoinLattice; +import soot.jimple.interproc.ifds.SynchronizedBy; +import soot.jimple.interproc.ifds.ZeroedFlowFunctions; +import soot.jimple.interproc.ifds.edgefunc.EdgeIdentity; +import soot.jimple.interproc.ifds.utils.Utils; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.toolkits.scalar.Pair; + +import com.google.common.base.Predicate; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import com.google.common.collect.Table.Cell; + +/** + * Solves the given {@link IDETabulationProblem} as described in the 1996 paper by Sagiv, + * Horwitz and Reps. To solve the problem, call {@link #solve()}. Results can then be + * queried by using {@link #resultAt(Object, Object)} and {@link #resultsAt(Object)}. + * + * Note that this solver and its data structures internally use mostly {@link LinkedHashSet}s + * instead of normal {@link HashSet}s to fix the iteration order as much as possible. This + * is to produce, as much as possible, reproducible benchmarking results. We have found + * that the iteration order can matter a lot in terms of speed. + * + * @param The type of nodes in the interprocedural control-flow graph. Typically {@link Unit}. + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of objects used to represent methods. Typically {@link SootMethod}. + * @param The type of values to be computed along flow edges. + * @param The type of inter-procedural control-flow graph being used. + */ +public class IDESolver,D extends UpdatableWrapper,M extends UpdatableWrapper,V, + I extends InterproceduralCFG> { + + /** + * Enumeration containing all possible modes in which this solver can work + */ + private enum OperationMode + { + /** + * The data is computed from scratch, either initially or because of a + * full re-computation + */ + Compute, + /** + * An incremental update is performed on the data + */ + Update + }; + + @DontSynchronize("only used by single thread") + private OperationMode operationMode = OperationMode.Compute; + + public static CacheBuilder DEFAULT_CACHE_BUILDER = + CacheBuilder.newBuilder().concurrencyLevel(Runtime.getRuntime().availableProcessors()).initialCapacity(10000).softValues(); + + private static final boolean DEBUG = false; + + private static final boolean DUMP_RESULTS = false; + + //executor for dispatching individual compute jobs (may be multi-threaded) + @DontSynchronize("only used by single thread") + private ExecutorService executor; + + @DontSynchronize("only used by single thread") + private int numThreads; + + //the number of currently running tasks + private final AtomicInteger numTasks = new AtomicInteger(); + + @SynchronizedBy("consistent lock on field") + //We are using a LinkedHashSet here to enforce FIFO semantics, which leads to a breath-first construction + //of the exploded super graph. As we observed in experiments, this can speed up the construction. + private final Collection> pathWorklist = new LinkedHashSet>(); + + @SynchronizedBy("thread safe data structure, consistent locking when used") + private final JumpFunctions jumpFn; + private Table>> jumpSave = null; + + @SynchronizedBy("thread safe data structure, consistent locking when used") + private final SummaryFunctions summaryFunctions = new SummaryFunctions(); + + @SynchronizedBy("thread safe data structure, only modified internally") + private I icfg; // not final, see update(I newCFG) method + + @SynchronizedBy("thread safe data structure, only modified internally") + private I oldcfg = null; // not final, see update(I newCFG) method + + //stores summaries that were queried before they were computed + //see CC 2010 paper by Naeem, Lhotak and Rodriguez + @SynchronizedBy("consistent lock on 'incoming'") + private final Table>> endSummary = HashBasedTable.create(); + + //edges going along calls + //see CC 2010 paper by Naeem, Lhotak and Rodriguez + @SynchronizedBy("consistent lock on field") + private final Table>> incoming = HashBasedTable.create(); + + private Set changedNodes = null; + + @DontSynchronize("stateless") + private final FlowFunctions flowFunctions; + + @DontSynchronize("stateless") + private final EdgeFunctions edgeFunctions; + + @DontSynchronize("only used by single thread") + // can be changed during update() + private Set initialSeeds; + + @DontSynchronize("stateless") + private final JoinLattice valueLattice; + + @DontSynchronize("stateless") + private final EdgeFunction allTop; + + @DontSynchronize("only used by single thread - phase II not parallelized (yet)") + private final List> nodeWorklist = new LinkedList>(); + + @DontSynchronize("only used by single thread - phase II not parallelized (yet)") + private final Table val = HashBasedTable.create(); + + @DontSynchronize("benign races") + public long flowFunctionApplicationCount; + + @DontSynchronize("benign races") + public long flowFunctionConstructionCount; + + @DontSynchronize("benign races") + public long propagationCount; + + @DontSynchronize("benign races") + public long durationFlowFunctionConstruction; + + @DontSynchronize("benign races") + public long durationFlowFunctionApplication; + + @DontSynchronize("stateless") + private final D zeroValue; + + @DontSynchronize("readOnly") + private final FlowFunctionCache ffCache; + + @DontSynchronize("readOnly") + private final EdgeFunctionCache efCache; + + @DontSynchronize("readOnly") + private final IDETabulationProblem tabulationProblem; + + private Map> changeSet = null; + + /** + * Creates a solver for the given problem, which caches flow functions and edge functions. + * The solver must then be started by calling {@link #solve()}. + */ + public IDESolver(IDETabulationProblem tabulationProblem) { + this(tabulationProblem, DEFAULT_CACHE_BUILDER, DEFAULT_CACHE_BUILDER); + } + + /** + * Creates a solver for the given problem, constructing caches with the given {@link CacheBuilder}. The solver must then be started by calling + * {@link #solve()}. + * @param flowFunctionCacheBuilder A valid {@link CacheBuilder} or null if no caching is to be used for flow functions. + * @param edgeFunctionCacheBuilder A valid {@link CacheBuilder} or null if no caching is to be used for edge functions. + */ + public IDESolver(IDETabulationProblem tabulationProblem, @SuppressWarnings("rawtypes") CacheBuilder flowFunctionCacheBuilder, @SuppressWarnings("rawtypes") CacheBuilder edgeFunctionCacheBuilder) { + if(DEBUG) { + flowFunctionCacheBuilder = flowFunctionCacheBuilder.recordStats(); + edgeFunctionCacheBuilder = edgeFunctionCacheBuilder.recordStats(); + } + this.icfg = tabulationProblem.interproceduralCFG(); + FlowFunctions flowFunctions = new ZeroedFlowFunctions + (tabulationProblem.flowFunctions(), tabulationProblem.zeroValue()); + EdgeFunctions edgeFunctions = tabulationProblem.edgeFunctions(); + if(flowFunctionCacheBuilder!=null) { + ffCache = new FlowFunctionCache(flowFunctions, flowFunctionCacheBuilder); + flowFunctions = ffCache; + } else { + ffCache = null; + } + if(edgeFunctionCacheBuilder!=null) { + efCache = new EdgeFunctionCache(edgeFunctions, edgeFunctionCacheBuilder); + edgeFunctions = efCache; + } else { + efCache = null; + } + this.flowFunctions = flowFunctions; + this.edgeFunctions = edgeFunctions; + this.initialSeeds = tabulationProblem.initialSeeds(); + this.valueLattice = tabulationProblem.joinLattice(); + this.zeroValue = tabulationProblem.zeroValue(); + this.allTop = tabulationProblem.allTopFunction(); + this.jumpFn = new JumpFunctions(allTop); + this.tabulationProblem = tabulationProblem; + } + + /** + * Runs the solver on the configured problem. This can take some time. + * Uses a number of threads equal to the return value of + * Runtime.getRuntime().availableProcessors(). + */ + public void solve() { + solve(false); + } + + /** + * Runs the solver on the configured problem. This can take some time. + * Uses a number of threads equal to the return value of + * Runtime.getRuntime().availableProcessors(). + * @param enableUpdates Specifies whether indices for dynamic program graph + * updates shall be generated. + */ + public void solve(boolean enableUpdates) { + solve(Runtime.getRuntime().availableProcessors(), enableUpdates); + } + + /** + * Runs the solver on the configured problem. This can take some time. + * @param numThreads The number of threads to use. + */ + public void solve(int numThreads) { + solve(numThreads, true); + } + + /** + * Runs the solver on the configured problem. This can take some time. + * @param numThreads The number of threads to use. + * @param enableUpdates Specifies whether indices for dynamic program graph + * updates shall be generated. + */ + public void solve(int numThreads, boolean enableUpdates) { + System.out.println("IDE solver started."); + + // Clean up any leftovers from previous runs on a problem that might have been + // updated in the meantime. + this.jumpFn.clear(); + this.summaryFunctions.table.clear(); + this.endSummary.clear(); + this.incoming.clear(); + this.val.clear(); + this.ffCache.invalidateAll(); + this.efCache.invalidateAll(); + this.propagationCount = 0; + this.operationMode = OperationMode.Compute; + + System.out.println("Running with " + numThreads + " threads"); + + for(N startPoint: initialSeeds) { + assert icfg.containsStmt(startPoint); + propagate(zeroValue, startPoint, zeroValue, allTop); + pathWorklist.add(new PathEdge(zeroValue, startPoint, zeroValue)); + jumpFn.addFunction(zeroValue, startPoint, zeroValue, EdgeIdentity.v()); + } + + solveOnWorklist(numThreads, true, true); + System.out.println("IDE solver done, " + propagationCount + " edges propagated."); + } + + /** + * Runs the solver based on the already filled work list. This can take some + * time. + * Note that the caller is responsible for filling the path work list with the + * initial seeds / edges to be processed. This function can be used for graph + * updates, it therefore does not perform any cleanup on the control-flow + * graph prior to execution. + * @param numThreads The number of threads to use. + * @param computeEdges Specifies if the edges (jump functions) shall be computed + * @param computeValues Specifies if the values (phase 2) shall be computed + */ + private void solveOnWorklist(int numThreads, boolean computeEdges, boolean computeValues) { + if(numThreads<2) { + this.executor = Executors.newSingleThreadExecutor(); + this.numThreads = 1; + } else { + this.executor = Executors.newFixedThreadPool(numThreads); + this.numThreads = numThreads; + } + if (computeEdges) { + final long before = System.currentTimeMillis(); + forwardComputeJumpFunctionsSLRPs(); + durationFlowFunctionConstruction = System.currentTimeMillis() - before; + } + val.clear(); + if (computeValues) { + final long before = System.currentTimeMillis(); + computeValues(); + durationFlowFunctionApplication = System.currentTimeMillis() - before; + } + if(DEBUG) + printStats(); + + if(DUMP_RESULTS) + dumpResults("ideSolverDump"+System.currentTimeMillis()+".csv"); + + executor.shutdown(); + if (DEBUG) + System.out.println(propagationCount + " edges propagated"); + } + + /** + * Forward-tabulates the same-level realizable paths and associated functions. + * Note that this is a little different from the original IFDS formulations because + * we can have statements that are, for instance, both "normal" and "exit" statements. + * This is for instance the case on a "throw" statement that may on the one hand + * lead to a catch block but on the other hand exit the method depending + * on the exception being thrown. + */ + private void forwardComputeJumpFunctionsSLRPs() { + //final Table>> oldJumpFn = this.jumpFn.getAllFunctions(); + + if (this.changedNodes != null) + this.changedNodes.clear(); + + forwardComputeJumpFunctionsSLRPs(pathWorklist); + if (operationMode == OperationMode.Compute) + return; + + /* START OF Check-and-continue */ + /* + boolean processing = true; + while (processing) { + processing = false; + + // Make sure to also process new entries for which we don't have any + // saved facts yet. + for (N n : jumpFn.getTargets()) + for (Cell> cell : jumpFn.lookupByTarget(n)) + if (!oldJumpFn.contains(cell.getRowKey(), n)) { + addToWorkList(new PathEdge(cell.getRowKey(), n, cell.getColumnKey())); + processing = true; + + Map> functions = new HashMap>(); + oldJumpFn.put(cell.getRowKey(), n, functions); + functions.put(cell.getColumnKey(), cell.getValue()); + } + + for (Cell>> cell : oldJumpFn.cellSet()) { + // Check whether the new facts at this node are the same as the old ones. + Map> newFacts = new HashMap> + (jumpFn.forwardLookup(cell.getRowKey(), cell.getColumnKey())); + Map> oldFacts = cell.getValue(); + + if (!oldFacts.equals(newFacts)) { + if (DEBUG) + System.out.println("Updating " + cell.getColumnKey() + " at " + cell.getRowKey() + + " in method " + icfg.getMethodOf(cell.getColumnKey())); + + // The facts at this node have changed in the last round, so we + // need to propagate the changes down + for (D d : newFacts.keySet()) + addToWorkList(new PathEdge(cell.getRowKey(), cell.getColumnKey(), d)); + if (newFacts.isEmpty()) + addToWorkList(new PathEdge(cell.getRowKey(), cell.getColumnKey(), null)); + oldJumpFn.put(cell.getRowKey(), cell.getColumnKey(), newFacts); + processing = true; + } + } + + if (processing) { + this.summaryFunctions.clear(); + forwardComputeJumpFunctionsSLRPs(pathWorklist); + } + } + */ + /* END OF Check-and-continue */ + + // Make sure that all incoming edges to join points are considered + long prePhase2 = System.nanoTime(); + operationMode = OperationMode.Compute; + for (N n : this.changedNodes) { + // If this is an exit node and we have an old end summary, we + // need to delete it as well + if (icfg.isExitStmt(n)) + for (N sP : icfg.getStartPointsOf(icfg.getMethodOf(n))) { + Map>> endRow = endSummary.row(sP); + for (D d1 : endRow.keySet()) { + Table> entryTbl = endRow.get(d1); + Utils.removeElementFromTable(entryTbl, n); + } + } + + // Get all predecessors of the changed node. Predecessors include + // direct ones (the statement above, goto origins) as well as return + // edges for which the current statement is the return site. + Set preds = null; + Set exitStmts = icfg.getExitNodesForReturnSite(n); + if (exitStmts != null) + preds = new HashSet(exitStmts); + if (icfg.containsStmt(n)) + if (preds == null) + preds = new HashSet(icfg.getPredsOf(n)); + else + preds.addAll(icfg.getPredsOf(n)); + + // If we have only one predecessor, there is no second path we need + // to consider. We have already recreated all facts at the return + // site. + if (preds == null || preds.size() < 2) + continue; + + List rmList = new ArrayList(); + for (N pred : preds) { + if (!icfg.containsStmt(pred)) { + rmList.add(pred); + continue; + } + for (Cell> cell : jumpFn.lookupByTarget(pred)) + addToWorkList(new PathEdge(cell.getRowKey(), pred, cell.getColumnKey())); + } + if (exitStmts != null) + for (N rm : rmList) + exitStmts.remove(rm); + } + this.summaryFunctions.clear(); + forwardComputeJumpFunctionsSLRPs(pathWorklist); + System.out.println("Phase 2 took " + (System.nanoTime() - prePhase2) / 1E9 + " seconds"); + } + + /** + * Forward-tabulates the same-level realizable paths and associated functions. + * Note that this is a little different from the original IFDS formulations because + * we can have statements that are, for instance, both "normal" and "exit" statements. + * This is for instance the case on a "throw" statement that may on the one hand + * lead to a catch block but on the other hand exit the method depending + * on the exception being thrown. + * @param workList A list containing the edges still to be processed + */ + private void forwardComputeJumpFunctionsSLRPs(Collection> workList) { + while(true) { + synchronized (pathWorklist) { + if (!workList.isEmpty()) { + //pop edge + Iterator> iter = workList.iterator(); + PathEdge edge = iter.next(); + iter.remove(); + numTasks.getAndIncrement(); + + //dispatch processing of edge (potentially in a different thread) + executor.execute(new PathEdgeProcessingTask(edge)); + propagationCount++; + } else if(numTasks.intValue()==0){ + //path worklist is empty; no running tasks, we are done + return; + } else { + //the path worklist is empty but we still have running tasks + //wait until woken up, then try again + try { + pathWorklist.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + } + + /** + * Computes the final values for edge functions. + */ + private void computeValues() { + //Phase II(i) + for(N startPoint: initialSeeds) { + assert icfg.containsStmt(startPoint); + setVal(startPoint, zeroValue, valueLattice.bottomElement()); + Pair superGraphNode = new Pair(startPoint, zeroValue); + nodeWorklist.add(superGraphNode); + } + while(true) { + synchronized (nodeWorklist) { + if(!nodeWorklist.isEmpty()) { + //pop job + Pair nAndD = nodeWorklist.remove(0); + numTasks.getAndIncrement(); + + //dispatch processing of job (potentially in a different thread) + executor.execute(new ValuePropagationTask(nAndD)); + } else if(numTasks.intValue()==0) { + //node worklist is empty; no running tasks, we are done + break; + } else { + //the node worklist is empty but we still have running tasks + //wait until woken up, then try again + try { + nodeWorklist.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + //Phase II(ii) + //we create an array of all nodes and then dispatch fractions of this array to multiple threads + Set allNonCallStartNodes = icfg.allNonCallStartNodes(); + if (allNonCallStartNodes.size() > 0) { + @SuppressWarnings("unchecked") + N[] nonCallStartNodesArray = (N[]) Array.newInstance + (allNonCallStartNodes.iterator().next().getClass(), allNonCallStartNodes.size()); + int i=0; + for (N n : allNonCallStartNodes) { + nonCallStartNodesArray[i] = n; + i++; + } + for(int t=0;t nAndD, N n) { + assert icfg.containsStmt(n); + D d = nAndD.getO2(); + M p = icfg.getMethodOf(n); + for(N c: icfg.getCallsFromWithin(p)) { + assert icfg.containsStmt(c) : "Statement does not exist in graph: " + c; + Set>> entries; + entries = jumpFn.forwardLookup(d,c).entrySet(); + for(Map.Entry> dPAndFP: entries) { + D dPrime = dPAndFP.getKey(); + EdgeFunction fPrime = dPAndFP.getValue(); + N sP = n; + propagateValue(c,dPrime,fPrime.computeTarget(val(sP,d))); + flowFunctionApplicationCount++; + } + } + } + + private void propagateValueAtCall(Pair nAndD, N n) { + D d = nAndD.getO2(); + for(M q: icfg.getCalleesOfCallAt(n)) { + FlowFunction callFlowFunction = flowFunctions.getCallFlowFunction(n, q); + flowFunctionConstructionCount++; + for(D dPrime: callFlowFunction.computeTargets(d)) { + EdgeFunction edgeFn = edgeFunctions.getCallEdgeFunction(n, d, q, dPrime); + for(N startPoint: icfg.getStartPointsOf(q)) { + assert icfg.containsStmt(startPoint); + propagateValue(startPoint,dPrime, edgeFn.computeTarget(val(n,d))); + flowFunctionApplicationCount++; + } + } + } + } + + private void propagateValue(N nHashN, D nHashD, V v) { + synchronized (val) { + V valNHash = val(nHashN, nHashD); + V vPrime = valueLattice.join(valNHash,v); + if(!vPrime.equals(valNHash)) { + setVal(nHashN, nHashD, vPrime); + synchronized (nodeWorklist) { + nodeWorklist.add(new Pair(nHashN,nHashD)); + } + } + } + } + + private V val(N nHashN, D nHashD){ + V l = val.get(nHashN, nHashD); + if(l==null) return valueLattice.topElement(); //implicitly initialized to top; see line [1] of Fig. 7 in SRH96 paper + else return l; + } + + /** + * Sets the computed value for the given pair of statement and flow fact + * @param nHashN The statement/node for which to set the value + * @param nHashD The flow fact for which to set the value + * @param l The value to set + */ + private void setVal(N nHashN, D nHashD,V l){ + assert icfg.containsStmt(nHashN); + if (l == valueLattice.topElement()) // do not save the default element + return; + val.put(nHashN, nHashD,l); + if(DEBUG) + System.err.println("VALUE: "+((SootMethod)icfg.getMethodOf(nHashN).getContents()).getSignature() + +" "+nHashN+" "+nHashD+ " " + l); + } + + + /** + * Lines 13-20 of the algorithm; processing a call site in the caller's context. + * This overload allows to restrict processing to a specific return site. + * @param edge an edge whose target node resembles a method call + * @param returnSite The return site to which to restrict processing. Pass null + * to process all return sites. + */ + private void processCall(PathEdge edge) { + final D d1 = edge.factAtSource(); + final N n = edge.getTarget(); // a call node; line 14... + final D d2 = edge.factAtTarget(); + + assert icfg.containsStmt(edge.getTarget()); + + Collection returnSites = icfg.getReturnSitesOfCallAt(n); + if (d2 == null) { + for (N retSite : returnSites) + clearAndPropagate(d1, retSite); + return; + } + + Set callees = icfg.getCalleesOfCallAt(n); + for(M sCalledProcN: callees) { //still line 14 + FlowFunction function = flowFunctions.getCallFlowFunction(n, sCalledProcN); + flowFunctionConstructionCount++; + Set res = function.computeTargets(d2); + for(N sP: icfg.getStartPointsOf(sCalledProcN)) { + for(D d3: res) { +// if (operationMode == OperationMode.Update) +// clearAndPropagate(d3, sP, d3, EdgeIdentity.v()); //line 15 +// else + propagate(d3, sP, d3, EdgeIdentity.v()); //line 15 + + Set>> endSumm; + synchronized (incoming) { + //line 15.1 of Naeem/Lhotak/Rodriguez + addIncoming(sP,d3,n,d2); + + //line 15.2, copy to avoid concurrent modification exceptions by other threads + endSumm = new HashSet>>(endSummary(sP, d3)); + } + + //still line 15.2 of Naeem/Lhotak/Rodriguez + for(Cell> entry: endSumm) { + N eP = entry.getRowKey(); + D d4 = entry.getColumnKey(); + EdgeFunction fCalleeSummary = entry.getValue(); + for(N retSiteN: returnSites) { + FlowFunction retFunction = flowFunctions.getReturnFlowFunction(n, sCalledProcN, eP, retSiteN); + assert retFunction != null; + flowFunctionConstructionCount++; + for(D d5: retFunction.computeTargets(d4)) { + EdgeFunction f4 = edgeFunctions.getCallEdgeFunction(n, d2, sCalledProcN, d3); + EdgeFunction f5 = edgeFunctions.getReturnEdgeFunction(n, sCalledProcN, eP, d4, retSiteN, d5); + synchronized (summaryFunctions) { + EdgeFunction summaryFunction = summaryFunctions.summariesFor(n, d2, retSiteN).get(d5); + if(summaryFunction==null) summaryFunction = allTop; //SummaryFn initialized to all-top, see line [4] in SRH96 paper + EdgeFunction fPrime = f4.composeWith(fCalleeSummary).composeWith(f5).joinWith(summaryFunction); + if(!fPrime.equalTo(summaryFunction)) + summaryFunctions.insertFunction(n,d2,retSiteN,d5,fPrime); + } + } + } + } + } + } + } + //line 17-19 of Naeem/Lhotak/Rodriguez + EdgeFunction f = jumpFunction(edge); + for (N returnSiteN : returnSites) { + assert icfg.containsStmt(returnSiteN); + FlowFunction callToReturnFlowFunction = flowFunctions.getCallToReturnFlowFunction(n, returnSiteN); + assert callToReturnFlowFunction != null; + flowFunctionConstructionCount++; + Set targets = callToReturnFlowFunction.computeTargets(d2); + for(D d3: targets) { + EdgeFunction edgeFnE = edgeFunctions.getCallToReturnEdgeFunction(n, d2, returnSiteN, d3); + if (operationMode == OperationMode.Update) + clearAndPropagate(d1, returnSiteN, d3, f.composeWith(edgeFnE)); + else + propagate(d1, returnSiteN, d3, f.composeWith(edgeFnE)); + } + if (operationMode == OperationMode.Update && targets.isEmpty()) + clearAndPropagate(d1, returnSiteN); + + synchronized (summaryFunctions) { // TODO : Locking + Map> d3sAndF3s = summaryFunctions.summariesFor(n, d2, returnSiteN); + for (Map.Entry> d3AndF3 : d3sAndF3s.entrySet()) { + D d3 = d3AndF3.getKey(); + EdgeFunction f3 = d3AndF3.getValue(); + if(f3==null) f3 = allTop; //SummaryFn initialized to all-top, see line [4] in SRH96 paper + if (operationMode == OperationMode.Update) + clearAndPropagate(d1, returnSiteN, d3, f.composeWith(f3)); + else + propagate(d1, returnSiteN, d3, f.composeWith(f3)); + } + } + } + } + + private EdgeFunction jumpFunction(PathEdge edge) { + EdgeFunction function = jumpFn.forwardLookup(edge.factAtSource(), edge.getTarget()).get(edge.factAtTarget()); + if(function==null) return allTop; //JumpFn initialized to all-top, see line [2] in SRH96 paper + return function; + } + + /** + * Lines 21-32 of the algorithm. + */ + private void processExit(PathEdge edge) { + final N n = edge.getTarget(); // an exit node; line 21... + assert n != null; + assert icfg.containsStmt(n); + + M methodThatNeedsSummary = icfg.getMethodOf(n); + assert methodThatNeedsSummary != null; + + final EdgeFunction f = jumpFunction(edge); + assert f != null; + + final D d1 = edge.factAtSource(); + assert d1 != null; + + final D d2 = edge.factAtTarget(); + + assert icfg.containsStmt(edge.target); + + for(N sP: icfg.getStartPointsOf(methodThatNeedsSummary)) { + assert icfg.containsStmt(sP); + //line 21.1 of Naeem/Lhotak/Rodriguez + Set>> inc; + synchronized (incoming) { + if (d2 != null) + addEndSummary(sP, d1, n, d2, f); + //copy to avoid concurrent modification exceptions by other threads + inc = new HashSet>>(incoming(d1, sP)); + } + + for (Entry> entry: inc) { + //line 22 + N c = entry.getKey(); + assert icfg.containsStmt(c); + for(N retSiteC: icfg.getReturnSitesOfCallAt(c)) { + boolean doPropagate = true; + // Do not return into a method if there is a predecessor that + // will be changed later anyway + if (operationMode == OperationMode.Update) + if (predecessorRepropagated(this.changeSet.get(icfg.getMethodOf(retSiteC)), retSiteC)) + doPropagate = false; + + assert icfg.containsStmt(retSiteC); + if (d2 == null) { + clearAndPropagate(d1, retSiteC); + continue; + } + + assert icfg.containsStmt(retSiteC); + FlowFunction retFunction = flowFunctions.getReturnFlowFunction(c, methodThatNeedsSummary,n,retSiteC); + flowFunctionConstructionCount++; + Set targets = retFunction.computeTargets(d2); + for(D d4: entry.getValue()) { + //line 23 + for(D d5: targets) { + EdgeFunction f4 = edgeFunctions.getCallEdgeFunction(c, d4, icfg.getMethodOf(n), d1); + EdgeFunction f5 = edgeFunctions.getReturnEdgeFunction(c, icfg.getMethodOf(n), n, d2, retSiteC, d5); + EdgeFunction fPrime; + synchronized (summaryFunctions) { + EdgeFunction summaryFunction = summaryFunctions.summariesFor(c,d4,retSiteC).get(d5); + if(summaryFunction==null) summaryFunction = allTop; //SummaryFn initialized to all-top, see line [4] in SRH96 paper + fPrime = f4.composeWith(f).composeWith(f5).joinWith(summaryFunction); + if(!fPrime.equalTo(summaryFunction)) { + summaryFunctions.insertFunction(c,d4,retSiteC,d5,fPrime); + } + } + if (doPropagate) + for(Map.Entry> valAndFunc: jumpFn.reverseLookup(c,d4).entrySet()) { + EdgeFunction f3 = valAndFunc.getValue(); + if(!f3.equalTo(allTop)) { + D d3 = valAndFunc.getKey(); + + String meth = icfg.getMethodOf(retSiteC).toString(); + + if (DEBUG) + System.out.println("leaving method " + methodThatNeedsSummary + " for" + + " return site " + meth + "." + retSiteC + " on " + d3 + + " called by " + c); + + if (operationMode == OperationMode.Update) + clearAndPropagate(d3, retSiteC, d5, f3.composeWith(fPrime)); + else + propagate(d3, retSiteC, d5, f3.composeWith(fPrime)); + } + } + } + if (operationMode == OperationMode.Update && targets.isEmpty() && doPropagate) + for(D d3 : jumpFn.reverseLookup(c,d4).keySet()) + clearAndPropagate(d3, retSiteC); + } + } + } + } + } + + /** + * Lines 33-37 of the algorithm. + * @param edge + */ + private void processNormalFlow(PathEdge edge) { + assert edge != null; + + final D d1 = edge.factAtSource(); + final N n = edge.getTarget(); + final D d2 = edge.factAtTarget(); + + if (d2 == null) { + assert operationMode == OperationMode.Update; + for (N m : icfg.getSuccsOf(edge.getTarget())) + clearAndPropagate(d1, m); + return; + } + + assert d1 != null; + assert n != null; + assert d2 != null; + + EdgeFunction f = jumpFunction(edge); + for (N m : icfg.getSuccsOf(edge.getTarget())) { + FlowFunction flowFunction = flowFunctions.getNormalFlowFunction(n,m); + flowFunctionConstructionCount++; + Set res = flowFunction.computeTargets(d2); + for (D d3 : res) { + EdgeFunction fprime = f.composeWith(edgeFunctions.getNormalEdgeFunction(n, d2, m, d3)); + assert fprime != null; + if (operationMode == OperationMode.Update) + clearAndPropagate(d1, m, d3, fprime); + else + propagate(d1, m, d3, fprime); + } + if (operationMode == OperationMode.Update && res.isEmpty()) + clearAndPropagate(d1, m); + } + } + + private void propagate(D sourceVal, N target, D targetVal, EdgeFunction f) { + assert sourceVal != null; + assert target != null; + assert targetVal != null; + assert f != null; + + assert icfg.containsStmt(target) : "Propagated statement not found in graph. Is your " + + "call graph valid? Offending statement: " + target; +// assert operationMode == OperationMode.Compute; + + // Check whether we have changed a path edge. If so, immediately update it + // and release the monitor + boolean added = false; + synchronized (jumpFn) { + EdgeFunction jumpFnE = jumpFn.reverseLookup(target, targetVal).get(sourceVal); + if(jumpFnE==null) jumpFnE = allTop; //JumpFn is initialized to all-top (see line [2] in SRH96 paper) + EdgeFunction fPrime = jumpFnE.joinWith(f); + if (!fPrime.equalTo(jumpFnE)) { + jumpFn.addFunction(sourceVal, target, targetVal, fPrime); // synchronized function + added = true; + + if(DEBUG) { + if(targetVal!=zeroValue) { + StringBuilder result = new StringBuilder(); + result.append("EDGE: <"); + result.append(icfg.getMethodOf(target)); + result.append(","); + result.append(sourceVal); + result.append("> -> <"); + result.append(target); + result.append(","); + result.append(targetVal); + result.append("> - "); + result.append(fPrime); + System.out.println(result.toString()); + } + } + } + } + + if (added) { + PathEdge edge = new PathEdge(sourceVal, target, targetVal); + addToWorkList(edge); // thread-safe, includes all necessary synchronization + } + } + + private void clearAndPropagate(D sourceVal, N target, D targetVal, EdgeFunction f) { + assert icfg.containsStmt(target) : "Propagated statement not found in graph. Is your " + + "call graph valid? Offending statement: " + target; + assert operationMode == OperationMode.Update; + + boolean added = false; + synchronized (jumpFn) { + synchronized (jumpSave) { + Map> savedFacts = this.jumpSave.get(target, sourceVal); + if (savedFacts == null) { + // We have not processed this edge yet. Record the original data + // so that we can later check whether our re-processing has changed + // anything + Map> targetDs = new HashMap> + (this.jumpFn.forwardLookup(sourceVal, target)); + this.jumpSave.put(target, sourceVal, targetDs); + + // Delete the original facts + for (D d : targetDs.keySet()) + this.jumpFn.removeFunction(sourceVal, target, d); + synchronized (changedNodes) { + this.changedNodes.add(target); + } + } + + // If the function with which we are coming in right now is different + // from what we already have as the "new" jump function, we need to + // record it. + EdgeFunction jumpFnE = jumpFn.reverseLookup(target, targetVal).get(sourceVal); + if(jumpFnE==null) jumpFnE = allTop; //JumpFn is initialized to all-top (see line [2] in SRH96 paper) + EdgeFunction fPrime = jumpFnE.joinWith(f); + if (!fPrime.equalTo(jumpFnE)) { + jumpFn.addFunction(sourceVal, target, targetVal, fPrime); + added = true; + } + } + } + if (added) + addToWorkList(new PathEdge(sourceVal, target, targetVal)); // thread-safe function + } + + private void clearAndPropagate(D sourceVal, N target) { + assert icfg.containsStmt(target) : "Propagated statement not found in graph. Is your " + + "call graph valid? Offending statement: " + target; + assert operationMode == OperationMode.Update; + + synchronized (jumpFn) { + synchronized (jumpSave) { + if (!this.jumpSave.contains(target, sourceVal)) { + // We have not processed this edge yet. Record the original data + // so that we can later check whether our re-processing has changed + // anything + Map> targetDs = new HashMap> + (this.jumpFn.forwardLookup(sourceVal, target)); + this.jumpSave.put(target, sourceVal, targetDs); + + // Delete the original facts + for (D d : targetDs.keySet()) + this.jumpFn.removeFunction(sourceVal, target, d); + synchronized (changedNodes) { + this.changedNodes.add(target); + addToWorkList(new PathEdge(sourceVal, target, null)); + } + } + } + } + } + + private void addToWorkList(PathEdge edge) { + assert icfg.containsStmt(edge.getTarget()) : + "Statement not found in graph: " + edge.getTarget(); + synchronized (pathWorklist) { +// if (!pathWorklist.contains(edge)) + pathWorklist.add(edge); + } + } + + private Set>> endSummary(N sP, D d3) { + Table> map = endSummary.get(sP, d3); + if(map==null) return Collections.emptySet(); + return map.cellSet(); + } + + private void addEndSummary(N sP, D d1, N eP, D d2, EdgeFunction f) { + assert icfg.containsStmt(sP); + assert icfg.containsStmt(eP); + + Table> summaries = endSummary.get(sP, d1); + if(summaries==null) { + summaries = HashBasedTable.create(); + endSummary.put(sP, d1, summaries); + } + summaries.put(eP,d2,f); + } + + private Set>> incoming(D d1, N sP) { + Map> map = incoming.get(sP, d1); + if(map==null) return Collections.emptySet(); + return map.entrySet(); + } + + private void addIncoming(N sP, D d3, N n, D d2) { + Map> summaries = incoming.get(sP, d3); + if(summaries==null) { + summaries = new HashMap>(); + incoming.put(sP, d3, summaries); + } + Set set = summaries.get(n); + if(set==null) { + set = new HashSet(); + summaries.put(n,set); + } + set.add(d2); + } + + /** + * Returns the V-type result for the given value at the given statement. + */ + public V resultAt(N stmt, D value) { + return val.get(stmt, value); + } + + /** + * Returns the resulting environment for the given statement. + * The artificial zero value is automatically stripped. + */ + public Map resultsAt(N stmt) { + //filter out the artificial zero-value + return Maps.filterKeys(val.row(stmt), new Predicate() { + + public boolean apply(D val) { + return val!=zeroValue; + } + }); + } + + public void dumpResults(String fileName) { + try { + PrintWriter out = new PrintWriter(new FileOutputStream(fileName)); + List res = new ArrayList(); + for(Cell entry: val.cellSet()) { + SootMethod methodOf = (SootMethod) icfg.getMethodOf(entry.getRowKey()).getContents(); + PatchingChain units = methodOf.getActiveBody().getUnits(); + int i=0; + for (Unit unit : units) { + if(unit==entry.getRowKey()) + break; + i++; + } + + res.add(methodOf+";"+entry.getRowKey()+"@"+i+";"+entry.getColumnKey()+";"+entry.getValue()); + } + Collections.sort(res); + for (String string : res) { + out.println(string); + } + out.flush(); + out.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + public void printStats() { + if(DEBUG) { + if(ffCache!=null) + ffCache.printStats(); + if(efCache!=null) + efCache.printStats(); + } else { + System.err.println("No statistics were collected, as DEBUG is disabled."); + } + } + + /** + * Worker class for processing a single edge in the control-flow graph + */ + private class PathEdgeProcessingTask implements Runnable { + private final PathEdge edge; + + /** + * Creates a new instance of the PathEdgeProcessingTask class. + * @param edge The edge that shall be processed by this worker object + */ + public PathEdgeProcessingTask + (PathEdge edge) { + assert edge != null; + + this.edge = edge; + } + + public void run() { + processSingleEdge(this.edge); + synchronized (pathWorklist) { + numTasks.getAndDecrement(); + //potentially wake up waiting broker thread + //(see forwardComputeJumpFunctionsSLRPs()) + pathWorklist.notify(); + } + } + } + + private void processSingleEdge(PathEdge edge) { + if (edge.getTarget().toString().equals("$r3 = virtualinvoke listener.()")) + System.out.println("x"); + + assert icfg.containsStmt(edge.getTarget()); + if(icfg.isCallStmt(edge.getTarget())) { + try { + processCall(edge); + } + catch (Exception ex) { + System.out.println(ex); + ex.printStackTrace(); + } + } else { + //note that some statements, such as "throw" may be + //both an exit statement and a "normal" statement + try { + if(icfg.isExitStmt(edge.getTarget())) { + processExit(edge); + } + } catch (Exception ex) { + System.out.println(ex); + ex.printStackTrace(); + } + try { + if(!icfg.getSuccsOf(edge.getTarget()).isEmpty()) + processNormalFlow(edge); + } catch (Exception ex) { + System.out.println(ex + " - " + edge); + ex.printStackTrace(); + } + } + } + + private class ValuePropagationTask implements Runnable { + private final Pair nAndD; + + public ValuePropagationTask(Pair nAndD) { + this.nAndD = nAndD; + } + + public void run() { + N n = nAndD.getO1(); + assert icfg.containsStmt(n); + + if(icfg.isStartPoint(n)) { + propagateValueAtStart(nAndD, n); + } + if(icfg.isCallStmt(n)) { + propagateValueAtCall(nAndD, n); + } + synchronized (nodeWorklist) { + numTasks.getAndDecrement(); + //potentially wake up waiting broker thread + //(see forwardComputeJumpFunctionsSLRPs()) + nodeWorklist.notify(); + } + } + } + + private class ValueComputationTask implements Runnable { + private final N[] values; + final int num; + + public ValueComputationTask(N[] values, int num) { + this.values = values; + this.num = num; + } + + public void run() { + int sectionSize = (int) Math.floor(values.length / numThreads) + numThreads; + for(int i = sectionSize * num; i < Math.min(sectionSize * (num+1),values.length); i++) { + N n = values[i]; + + Set startPoints = icfg.getStartPointsOf(icfg.getMethodOf(n)); + assert !startPoints.isEmpty(); + for(N sP: startPoints) { + Set>> lookupByTarget; + lookupByTarget = jumpFn.lookupByTarget(n); + for(Cell> sourceValTargetValAndFunction : lookupByTarget) { + D dPrime = sourceValTargetValAndFunction.getRowKey(); + D d = sourceValTargetValAndFunction.getColumnKey(); + EdgeFunction fPrime = sourceValTargetValAndFunction.getValue(); + + V vP = val(sP,dPrime); + if (vP == valueLattice.topElement()) + continue; + V v2 = fPrime.computeTarget(vP); + if (v2 == valueLattice.topElement()) + continue; + + synchronized (val) { + setVal(n,d,valueLattice.join(val(n,d),fPrime.computeTarget(val(sP,dPrime)))); + } + flowFunctionApplicationCount++; + } + } + } + } + } + + /** + * Updates an already generated solution based on changes to the underlying + * control flow graph + * @param newCFG The new control flow graph with which to update the + * analysis results + * @param numThreads The number of threads to use. + */ + public void update(I newCFG) { + update(Runtime.getRuntime().availableProcessors(), newCFG); + } + + /** + * Updates an already generated solution based on changes to the underlying + * control flow graph + * @param numThreads The number of threads to use. + * @param newCFG The new control flow graph with which to update the + * analysis results + */ + public void update(int numTreads, I newCFG) { + assert newCFG != null; + System.out.println("Performing IDE update..."); + + this.jumpSave = HashBasedTable.create(this.jumpFn.targetCount(), this.jumpFn.sourceFactCount()); + this.numThreads = numTreads; + System.out.println("Running with " + numThreads + " threads"); + + // Update the stored control-flow graph. We save the old CFG so that + // we are still able to inversely propagate the expired facts along + // the deleted edges. + oldcfg = icfg; + icfg = newCFG; + tabulationProblem.updateCFG(newCFG); + + // Next, we need to create a changeset on the control flow graph + Map> expiredEdges = new HashMap>(5000); + Map> newEdges = new HashMap>(5000); + Set newNodes = new HashSet(100); + Set expiredNodes = new HashSet(100); + if (DEBUG) + System.out.println("Computing changeset..."); + computeCFGChangeset(expiredEdges, newEdges, newNodes, expiredNodes); + if (DEBUG) + System.out.println("Changeset computed."); + + // If we have not computed any graph changes, we are done + if (expiredEdges.size() == 0 && newEdges.size() == 0) { + System.out.println("CFG is unchanged, aborting update..."); + + // Nevertheless, update the object references + icfg = newCFG; + tabulationProblem.updateCFG(newCFG); + return; + } + + this.changedNodes = new HashSet((int) this.propagationCount); + this.propagationCount = new Long(0); + + // Make sure we don't cache any expired nodes + long beforeRemove = System.nanoTime(); + System.out.println("Removing " + expiredNodes.size() + " expired nodes..."); + for (N n : expiredNodes) { + this.jumpFn.removeByTarget(n); + Utils.removeElementFromTable(this.incoming, n); + Utils.removeElementFromTable(this.endSummary, n); + + for (Cell>> cell : incoming.cellSet()) + cell.getValue().remove(n); + for (Cell>> cell : endSummary.cellSet()) + Utils.removeElementFromTable(cell.getValue(), n); + } + System.out.println("Expired nodes removed in " + + (System.nanoTime() - beforeRemove) / 1E9 + + " seconds."); + + // Process edge insertions. This will only do the incoming edges of new + // nodes as the outgoing ones will only be available after the incoming + // ones have been processed and the graph has been extended. + this.operationMode = OperationMode.Update; + changeSet = new HashMap>(newEdges.size() + expiredEdges.size()); + if (!newEdges.isEmpty()) { + System.out.println("Updating " + newEdges.size() + " new edges..."); + changeSet.putAll(updateNewEdges(newEdges, newNodes)); + System.out.println("New edges updated"); + } + + // Process edge deletions + if (!expiredEdges.isEmpty()) { + System.out.println("Deleting " + expiredEdges.size() + " expired edges..."); + changeSet.putAll(deleteExpiredEdges(expiredNodes, expiredEdges)); + System.out.println("Expired edges deleted."); + } + + Set totalChangedNodes = new HashSet((int) this.propagationCount); + System.out.println("Processing worklist for edges..."); + int edgeIdx = 0; + long beforeEdges = System.nanoTime(); + for (M m : changeSet.keySet()) + for (N preLoop : changeSet.get(m)) { + // If a predecessor in the same method has already been + // the start point of a propagation, we can skip this one. + if (this.predecessorRepropagated(changeSet.get(m), preLoop)) + continue; + // If another propagation has already visited this node, + // starting a new propagation from here cannot create + // any fact changes. + if (totalChangedNodes.contains(preLoop)) + continue; + edgeIdx++; + + for (Cell> srcEntry : jumpFn.lookupByTarget(preLoop)) { + D srcD = srcEntry.getRowKey(); + D tgtD = srcEntry.getColumnKey(); + + if (DEBUG) + System.out.println("Reprocessing edge: <" + srcD + + "> -> <" + preLoop + ", " + tgtD + ">"); + addToWorkList(new PathEdge(srcD, preLoop, tgtD)); + } + + if (DEBUG) + System.out.println("Processing worklist for method " + m + "..."); + this.operationMode = OperationMode.Update; + this.jumpSave.clear(); + solveOnWorklist(numThreads, true, false); + + totalChangedNodes.addAll(this.changedNodes); + } + + System.out.println("Actually processed " + edgeIdx + " of " + + (newEdges.size() + expiredEdges.size()) + " expired edges in " + + (System.nanoTime() - beforeEdges) / 1E9 + " seconds"); + + System.out.println("Processing worklist for values..."); + this.operationMode = OperationMode.Compute; + solveOnWorklist(numThreads, false, true); + System.out.println("Worklist processing done, " + propagationCount + " edges processed."); + + this.oldcfg = null; // allow for garbage collection + this.changedNodes = null; + } + + /** + * Deletes the expired edges from the program analysis results and updates + * the DAG accordingly + * @param expiredNodes The nodes that have been deleted from the program + * graph + * @param expiredEdges A list containing all expired edges + * @return A mapping from changed methods to all statements that need to + * be reprocessed in the method + */ + private Map> deleteExpiredEdges(Set expiredNodes, Map> expiredEdges) { + // Start a new propagation on the deleted edges' source node + Map> methodExpiredEdges = new HashMap>(); + for (Entry> entry : expiredEdges.entrySet()) { + N srcN = entry.getKey(); + if (expiredNodes.contains(srcN)) + continue; + + N loopStart = icfg.getLoopStartPointFor(srcN); + if (loopStart != null && expiredNodes.contains(loopStart)) + continue; + + List preds = new ArrayList(); + if (loopStart == null) + preds.add(srcN); + else + preds.addAll(icfg.getPredsOf(loopStart)); + + for (N preLoop : preds) { + // Do not propagate a node more than once + M m = icfg.getMethodOf(preLoop); + Utils.addElementToMapSet(methodExpiredEdges, m, preLoop); + } + } + return methodExpiredEdges; + } + + private boolean predecessorRepropagated(Set srcNodes, N srcN) { + if (srcNodes == null) + return false; + List curNodes = new ArrayList(); + Set doneSet = new HashSet(100); + curNodes.addAll(icfg.getPredsOf(srcN)); + while (!curNodes.isEmpty()) { + N n = curNodes.remove(0); + if (!doneSet.add(n)) + continue; + + if (srcNodes.contains(n) && n != srcN) + return true; + curNodes.addAll(icfg.getPredsOf(n)); + } + return false; + } + + /** + * Adds a given list of new edges to the DAG graph. This can require a re- + * evaluation of one or more edges to check whether new (N,D) nodes are + * introduced subsequently. + * @param newEdges The list of edges to add to the DAG. + * @param newNodes The list of new nodes in the program graph + * @return A mapping from changed methods to all statements that need to + * be reprocessed in the method + */ + private Map> updateNewEdges(Map> newEdges, Set newNodes) { + // Process edge insertions. Nodes are processed along with their edges + // which implies that new unconnected nodes (unreachable statements) + // will automatically be ignored. + Map> newMethodEdges = new HashMap>(newEdges.size()); + for (N srcN : newEdges.keySet()) { + if (newNodes.contains(srcN)) + continue; + + N loopStart = icfg.getLoopStartPointFor(srcN); + + List preds = new ArrayList(); + if (loopStart == null) + preds.add(srcN); + else + preds.addAll(icfg.getPredsOf(loopStart)); + + for (N preLoop : preds) { + // Do not propagate a node more than once + M m = icfg.getMethodOf(preLoop); + Utils.addElementToMapSet(newMethodEdges, m, preLoop); + } + } + return newMethodEdges; + } + + /** + * Computes a change set of added and removed edges in the control-flow + * graph and updates the flow functions to remap unchanged old nodes to + * new ones and remove expired nodes. + * @param expiredEdges A list which receives the edges that are no longer + * present in the updated CFG + * @param newEdges A list which receives the edges that have been newly + * introduced in the updated CFG + * @param newNodes A list which receives the nodes that have been newly + * introduced in the updated CFG + * @param expiredNodes A list which receives the nodes that have been + * removed from the graph. + */ + private void computeCFGChangeset(Map> expiredEdges, + Map> newEdges, Set newNodes, Set expiredNodes) { + long startTime = System.nanoTime(); + + oldcfg.computeCFGChangeset(icfg, expiredEdges, newEdges, newNodes, + expiredNodes); + + // Print out the expired edges +// if (DEBUG) { + for (N key : expiredEdges.keySet()) + for (N val : expiredEdges.get(key)) + System.out.println("expired edge: (" + (oldcfg.containsStmt(key) ? oldcfg.getMethodOf(key) + : icfg.getMethodOf(key)) + ") " + key + " -> " + val); + for (N key : newEdges.keySet()) + for (N val : newEdges.get(key)) + System.out.println("new edge: (" + icfg.getMethodOf(key) + ") " + key + " -> " + val); + for (N key : expiredNodes) { + if (!oldcfg.containsStmt(key)) + System.out.println("foo " + icfg.getMethodOf(key)); + System.out.println("expired node: (" + oldcfg.getMethodOf(key) + ") " + key); + } + for (N key : newNodes) + System.out.println("new node: (" + icfg.getMethodOf(key) + ") " + key); +// } + + // Merge the wrap lists + icfg.mergeWrappers(oldcfg); + + + // Invalidate all cached functions + ffCache.invalidateAll(); + efCache.invalidateAll(); + + System.out.println("CFG changeset computation took " + (System.nanoTime() - startTime) / 1E9 + + " seconds"); + } + +} diff --git a/src/soot/jimple/interproc/ifds/solver/IFDSSolver.java b/src/soot/jimple/interproc/ifds/solver/IFDSSolver.java new file mode 100644 index 0000000..94ca0c5 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/solver/IFDSSolver.java @@ -0,0 +1,144 @@ +package soot.jimple.interproc.ifds.solver; + +import static soot.jimple.interproc.ifds.solver.IFDSSolver.BinaryDomain.BOTTOM; +import static soot.jimple.interproc.ifds.solver.IFDSSolver.BinaryDomain.TOP; + +import java.util.Set; + +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.EdgeFunction; +import soot.jimple.interproc.ifds.EdgeFunctions; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.IDETabulationProblem; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.JoinLattice; +import soot.jimple.interproc.ifds.edgefunc.AllBottom; +import soot.jimple.interproc.ifds.edgefunc.AllTop; +import soot.jimple.interproc.ifds.edgefunc.EdgeIdentity; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * A solver for an {@link IFDSTabulationProblem}. This solver in effect uses the {@link IDESolver} + * to solve the problem, as any IFDS problem can be intepreted as a special case of an IDE problem. + * See Section 5.4.1 of the SRH96 paper. In effect, the IFDS problem is solved by solving an IDE + * problem in which the environments (D to N mappings) represent the set's characteristic function. + * + * @param The type of nodes in the interprocedural control-flow graph. Typically {@link Unit}. + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of objects used to represent methods. Typically {@link SootMethod}. + * @param The type of inter-procedural control-flow graph being used. + * @see IFDSTabulationProblem + */ +public class IFDSSolver,D extends UpdatableWrapper,M extends UpdatableWrapper, + I extends InterproceduralCFG> extends IDESolver { + + static enum BinaryDomain { TOP,BOTTOM } + + private final static EdgeFunction ALL_BOTTOM = new AllBottom(BOTTOM); + + /** + * Creates a solver for the given problem. The solver must then be started by calling + * {@link #solve()}. + */ + public IFDSSolver(final IFDSTabulationProblem ifdsProblem) { + super(new IDETabulationProblem() { + + @Override + public FlowFunctions flowFunctions() { + return ifdsProblem.flowFunctions(); + } + + @Override + public I interproceduralCFG() { + return ifdsProblem.interproceduralCFG(); + } + + @Override + public Set initialSeeds() { + return ifdsProblem.initialSeeds(); + } + + @Override + public D zeroValue() { + return ifdsProblem.zeroValue(); + } + + @Override + public EdgeFunctions edgeFunctions() { + return new IFDSEdgeFunctions(); + } + + @Override + public JoinLattice joinLattice() { + return new JoinLattice() { + + @Override + public BinaryDomain topElement() { + return BinaryDomain.TOP; + } + + @Override + public BinaryDomain bottomElement() { + return BinaryDomain.BOTTOM; + } + + @Override + public BinaryDomain join(BinaryDomain left, BinaryDomain right) { + if(left==TOP && right==TOP) { + return TOP; + } else { + return BOTTOM; + } + } + }; + } + + @Override + public EdgeFunction allTopFunction() { + return new AllTop(TOP); + } + + class IFDSEdgeFunctions implements EdgeFunctions { + + public EdgeFunction getNormalEdgeFunction(N src,D srcNode,N tgt,D tgtNode) { + if(srcNode==ifdsProblem.zeroValue()) return ALL_BOTTOM; + return EdgeIdentity.v(); + } + + @Override + public EdgeFunction getCallEdgeFunction(N callStmt,D srcNode,M destinationMethod,D destNode) { + if(srcNode==ifdsProblem.zeroValue()) return ALL_BOTTOM; + return EdgeIdentity.v(); + } + + @Override + public EdgeFunction getReturnEdgeFunction(N callSite, M calleeMethod,N exitStmt,D exitNode,N returnSite,D retNode) { + if(exitNode==ifdsProblem.zeroValue()) return ALL_BOTTOM; + return EdgeIdentity.v(); + } + + @Override + public EdgeFunction getCallToReturnEdgeFunction(N callStmt,D callNode,N returnSite,D returnSideNode) { + if(callNode==ifdsProblem.zeroValue()) return ALL_BOTTOM; + return EdgeIdentity.v(); + } + } + + @Override + public void updateCFG(I cfg) { + ifdsProblem.updateCFG(cfg); + } + + }); + } + + /** + * Returns the set of facts that hold at the given statement. + */ + public Set ifdsResultsAt(N statement) { + return resultsAt(statement).keySet(); + } + +} diff --git a/src/soot/jimple/interproc/ifds/solver/JumpFunctions.java b/src/soot/jimple/interproc/ifds/solver/JumpFunctions.java new file mode 100644 index 0000000..d4c0a92 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/solver/JumpFunctions.java @@ -0,0 +1,271 @@ +package soot.jimple.interproc.ifds.solver; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import soot.jimple.interproc.ifds.DontSynchronize; +import soot.jimple.interproc.ifds.EdgeFunction; +import soot.jimple.interproc.ifds.SynchronizedBy; +import soot.jimple.interproc.ifds.ThreadSafe; +import soot.jimple.interproc.ifds.utils.Utils; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.common.collect.Table.Cell; + +/** + * The IDE algorithm uses a list of jump functions. Instead of a list, we use a set of three + * maps that are kept in sync. This allows for efficient indexing: the algorithm accesses + * elements from the list through three different indices. + */ +@ThreadSafe +public class JumpFunctions { + + /** + * mapping from target node and value to a list of all source values and associated functions + * where the list is implemented as a mapping from the source value to the function + * we exclude empty default functions + */ + @SynchronizedBy("consistent lock on this") + protected Table>> nonEmptyReverseLookup = HashBasedTable.create(); + + /** + * mapping from source value and target node to a list of all target values and associated functions + * where the list is implemented as a mapping from the source value to the function + * we exclude empty default functions + */ + @SynchronizedBy("consistent lock on this") + protected Table>> nonEmptyForwardLookup = HashBasedTable.create(); + + /** + * a mapping from target node to a list of triples consisting of source value, + * target value and associated function; the triple is implemented by a table + * we exclude empty default functions + */ + @SynchronizedBy("consistent lock on this") + protected Map>> nonEmptyLookupByTargetNode = new HashMap>>(); + + @DontSynchronize("immutable") + private final EdgeFunction allTop; + + public JumpFunctions(EdgeFunction allTop) { + this.allTop = allTop; + } + + /** + * Records a jump function. The source statement is implicit. + * @see PathEdge + */ + public synchronized void addFunction(D sourceVal, N target, D targetVal, EdgeFunction function) { + assert sourceVal!=null; + assert target!=null; + assert targetVal!=null; + assert function!=null; + + //we do not store the default function (all-top) + if(function.equalTo(allTop)) return; + + Map> sourceValToFunc = nonEmptyReverseLookup.get(target, targetVal); + if(sourceValToFunc==null) { + sourceValToFunc = new LinkedHashMap>(); + nonEmptyReverseLookup.put(target,targetVal,sourceValToFunc); + } + sourceValToFunc.put(sourceVal, function); + + Map> targetValToFunc = nonEmptyForwardLookup.get(sourceVal, target); + if(targetValToFunc==null) { + targetValToFunc = new LinkedHashMap>(); + nonEmptyForwardLookup.put(sourceVal,target,targetValToFunc); + } + targetValToFunc.put(targetVal, function); + + Table> table = nonEmptyLookupByTargetNode.get(target); + if(table==null) { + table = HashBasedTable.create(); + nonEmptyLookupByTargetNode.put(target,table); + } + table.put(sourceVal, targetVal, function); + } + + /** + * Removes a jump function. The source statement is implicit. + * @see PathEdge + * @return True if the function has actually been removed. False if it was not + * there anyway. + */ + public synchronized boolean removeFunction(D sourceVal, N target, D targetVal) { + assert sourceVal!=null; + assert target!=null; + assert targetVal!=null; + + Map> sourceValToFunc = nonEmptyReverseLookup.get(target, targetVal); + if (sourceValToFunc == null) + return false; + if (sourceValToFunc.remove(sourceVal) == null) + return false; + if (sourceValToFunc.isEmpty()) + nonEmptyReverseLookup.remove(targetVal, targetVal); + + Map> targetValToFunc = nonEmptyForwardLookup.get(sourceVal, target); + if (targetValToFunc == null) + return false; + if (targetValToFunc.remove(targetVal) == null) + return false; + if (targetValToFunc.isEmpty()) + nonEmptyForwardLookup.remove(sourceVal, target); + + Table> table = nonEmptyLookupByTargetNode.get(target); + if (table == null) + return false; + if (table.remove(sourceVal, targetVal) == null) + return false; + if (table.isEmpty()) + nonEmptyLookupByTargetNode.remove(target); + + return true; + } + + /** + * Removes all jump function with the given target + * @see target The target for which to remove all jump functions + */ + public synchronized void removeByTarget(N target) { + Map> rmList = new HashMap>(); + for (Cell> cell : lookupByTarget(target)) + Utils.addElementToMapList(rmList, cell.getRowKey(), cell.getColumnKey()); + for (Entry> entry : rmList.entrySet()) + for (D d : entry.getValue()) + removeFunction(entry.getKey(), target, d); + } + + /** + * Returns, for a given target statement and value all associated + * source values, and for each the associated edge function. + * The return value is a mapping from source value to function. + */ + public synchronized Map> reverseLookup(N target, D targetVal) { + assert target!=null; + assert targetVal!=null; + Map> res = nonEmptyReverseLookup.get(target,targetVal); + if(res==null) return Collections.emptyMap(); + return res; + } + + /** + * Returns, for a given source value and target statement all + * associated target values, and for each the associated edge function. + * The return value is a mapping from target value to function. + */ + public synchronized Map> forwardLookup(D sourceVal, N target) { + assert sourceVal!=null; + assert target!=null; + Map> res = nonEmptyForwardLookup.get(sourceVal, target); + if(res==null) return Collections.emptyMap(); + return res; + } + + /** + * Returns for a given target statement all jump function records with this target. + * The return value is a set of records of the form (sourceVal,targetVal,edgeFunction). + */ + public synchronized Set>> lookupByTarget(N target) { + assert target!=null; + Table> table = nonEmptyLookupByTargetNode.get(target); + if(table==null) return Collections.emptySet(); + Set>> res = table.cellSet(); + if(res==null) return Collections.emptySet(); + return res; + } + + /** + * Clears all elements in this index. + */ + public void clear() { + nonEmptyReverseLookup.clear(); + nonEmptyForwardLookup.clear(); + nonEmptyLookupByTargetNode.clear(); + } + + /** + * Replaces an old statement object with a new one without impacting + * semantics. This method is intended for graph updates that exchange all + * nodes in the program graph even if they are semantically unchanged. + * You can then use this method to fix the references. + * @param oldStmt The old statement object to be replaced + * @param newStmt The replacement for the old object + */ + public void replaceNode(N oldStmt, N newStmt) { + assert oldStmt != null; + assert newStmt != null; + + // nonEmptyReverseLookup + Map>> nerl = new HashMap>>(); + for (Map.Entry>> entry : + nonEmptyReverseLookup.row(oldStmt).entrySet()) + nerl.put(entry.getKey(), entry.getValue()); + for (Entry>> entry : nerl.entrySet()) { + nonEmptyReverseLookup.put(newStmt, entry.getKey(), entry.getValue()); + nonEmptyReverseLookup.remove(oldStmt, entry.getKey()); + } + + // nonEmptyForwardLookup + nerl.clear(); + for (Entry>> entry : + nonEmptyForwardLookup.column(oldStmt).entrySet()) + nerl.put(entry.getKey(), entry.getValue()); + for (Entry>> entry : nerl.entrySet()) { + nonEmptyForwardLookup.put(entry.getKey(), newStmt, entry.getValue()); + nonEmptyForwardLookup.remove(entry.getKey(), oldStmt); + } + + // nonEmptyLookupByTargetNode + Table> oldByTarget = nonEmptyLookupByTargetNode.get(oldStmt); + if (oldByTarget != null) { + nonEmptyLookupByTargetNode.put(newStmt, oldByTarget); + nonEmptyLookupByTargetNode.remove(oldStmt); + } + } + + /** + * Gets all jump functions registered in this object. + * @return A table containing all jump functions in this object. The row key + * is the source fact, the column key is the target statement and value is + * a mapping from target facts to the respective edge functions. + */ + public Table>> getAllFunctions() { + return HashBasedTable.create(this.nonEmptyForwardLookup); + } + + /** + * Gets the set of target statements for which this object contains jump + * functions. + * @return The set of target statements for which this object contains jump + * functions. + */ + public Set getTargets() { + return this.nonEmptyLookupByTargetNode.keySet(); + } + + /** + * Gets the number of target statements for which there are fact mappings. + * @return The number of target statements + */ + public int targetCount() { + return this.nonEmptyLookupByTargetNode.size(); + } + + /** + * Gets the number of distinct source facts for which there are fact + * mappings. + * @return The number of distinct source facts + */ + public int sourceFactCount() { + return this.nonEmptyForwardLookup.size(); + } + +} diff --git a/src/soot/jimple/interproc/ifds/solver/PathEdge.java b/src/soot/jimple/interproc/ifds/solver/PathEdge.java new file mode 100644 index 0000000..6f675aa --- /dev/null +++ b/src/soot/jimple/interproc/ifds/solver/PathEdge.java @@ -0,0 +1,94 @@ +package soot.jimple.interproc.ifds.solver; + +import soot.jimple.interproc.ifds.InterproceduralCFG; + +/** + * A path edge as described in the IFDS/IDE algorithms. + * The source node is implicit: it can be computed from the target by using the {@link InterproceduralCFG}. + * Hence, we don't store it. + * + * @param The type of nodes in the interprocedural control-flow graph. Typically {@link Unit}. + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of objects used to represent methods. Typically {@link SootMethod}. + */ +public class PathEdge { + + protected final N target; + protected final D dSource, dTarget; + + /** + * @param dSource The fact at the source. + * @param target The target statement. + * @param dTarget The fact at the target. + */ + public PathEdge(D dSource, N target, D dTarget) { + super(); + this.target = target; + this.dSource = dSource; + this.dTarget = dTarget; + } + + public N getTarget() { + return target; + } + + public D factAtSource() { + return dSource; + } + + public D factAtTarget() { + return dTarget; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dSource == null) ? 0 : dSource.hashCode()); + result = prime * result + ((dTarget == null) ? 0 : dTarget.hashCode()); + result = prime * result + ((target == null) ? 0 : target.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("rawtypes") + PathEdge other = (PathEdge) obj; + if (dSource == null) { + if (other.dSource != null) + return false; + } else if (!dSource.equals(other.dSource)) + return false; + if (dTarget == null) { + if (other.dTarget != null) + return false; + } else if (!dTarget.equals(other.dTarget)) + return false; + if (target == null) { + if (other.target != null) + return false; + } else if (!target.equals(other.target)) + return false; + return true; + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(); + result.append("<"); + result.append(dSource); + result.append("> -> <"); + result.append(target.toString()); + result.append(","); + result.append(dTarget); + result.append(">"); + return result.toString(); + } + +} diff --git a/src/soot/jimple/interproc/ifds/solver/SummaryFunctions.java b/src/soot/jimple/interproc/ifds/solver/SummaryFunctions.java new file mode 100644 index 0000000..40133ff --- /dev/null +++ b/src/soot/jimple/interproc/ifds/solver/SummaryFunctions.java @@ -0,0 +1,150 @@ +package soot.jimple.interproc.ifds.solver; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import soot.jimple.interproc.ifds.EdgeFunction; +import soot.jimple.interproc.ifds.SynchronizedBy; +import soot.jimple.interproc.ifds.ThreadSafe; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; + +/** + * A data structure to record summary functions in an indexed fashion, for fast retrieval. + */ +@ThreadSafe +public class SummaryFunctions { + + @SynchronizedBy("consistent lock on this") + protected Table>> table = HashBasedTable.create(); + + /** + * Inserts a summary function. + * @param callSite The call site with which this function is associated. + * @param sourceVal The source value at the call site. + * @param retSite The return site (in the caller) with which this function is associated. + * @param targetVal The target value at the return site. + * @param function The edge function used to compute V-type values from the source node to the target node. + */ + public synchronized void insertFunction(N callSite,D sourceVal, N retSite, + D targetVal, EdgeFunction function) { + assert callSite!=null; + assert sourceVal!=null; + assert retSite!=null; + assert targetVal!=null; + assert function!=null; + + Table> targetAndTargetValToFunction = table.get(callSite,sourceVal); + if(targetAndTargetValToFunction==null) { + targetAndTargetValToFunction = HashBasedTable.create(); + table.put(callSite,sourceVal,targetAndTargetValToFunction); + } + targetAndTargetValToFunction.put(retSite, targetVal, function); + } + + /** + * Removes a summary function. + * @param callSite The call site with which this function is associated. + * @param sourceVal The source value at the call site. + * @param retSite The return site (in the caller) with which this function is associated. + * @param targetVal The target value at the return site. + */ + public synchronized void removeFunction(N callSite,D sourceVal, N retSite, D targetVal) { + assert callSite!=null; + assert sourceVal!=null; + assert retSite!=null; + assert targetVal!=null; + + Table> targetAndTargetValToFunction = table.get(callSite, sourceVal); + if (targetAndTargetValToFunction != null) { + targetAndTargetValToFunction.remove(retSite, targetVal); + } + } + + /** + * Retrieves all summary functions for a given call site, source value and + * return site (in the caller). + * The result contains a mapping from target value to associated edge function. + */ + public synchronized Map> summariesFor(N callSite, D sourceVal, N returnSite) { + assert callSite!=null; + assert sourceVal!=null; + assert returnSite!=null; + + Table> res = table.get(callSite,sourceVal); + if(res==null) return Collections.emptyMap(); + else { + return res.row(returnSite); + } + } + + /** + * Removes all summary functions linking the specified call site and fact with + * the given return site. + * @param callSite The call site, i.e. the statement that performs the function + * call + * @param sourceVal The fact at the call statement + * @param returnSite The statement to which the control flow returns after the + * function has been executed + * @return The number of summary functions that have been deleted + */ + public synchronized int removeFunctions(N callSite, D sourceVal, N returnSite) { + assert callSite!=null; + assert sourceVal!=null; + assert returnSite!=null; + + Table> targetAndTargetValToFunction = table.get(callSite,sourceVal); + if (targetAndTargetValToFunction == null) + return 0; + + int delCnt = 0; + Map> row = targetAndTargetValToFunction.row(returnSite); + while (!row.isEmpty()) { + targetAndTargetValToFunction.remove(returnSite, row.keySet().iterator().next()); + delCnt++; + } + return delCnt; + } + + /** + * Replaces an old statement object with a new one without impacting + * semantics. This method is intended for graph updates that exchange all + * nodes in the program graph even if they are semantically unchanged. + * You can then use this method to fix the references. + * @param oldStmt The old statement object to be replaced + * @param newStmt The replacement for the old object + */ + public void replaceNode(N oldStmt, N newStmt) { + Map>> addMap = new HashMap>>(); + for (Table.Cell>> cell : table.cellSet()) { + // Update the outer table to have the correct source statement + if (cell.getRowKey() == oldStmt) { + addMap.put(cell.getColumnKey(), cell.getValue()); + } + + // Update the inner table to have the correct target statement + Table> tblInner = cell.getValue(); + Map> innerMap = new HashMap>(); + for (Entry> entry : tblInner.row(oldStmt).entrySet()) + innerMap.put(entry.getKey(), entry.getValue()); + for (Entry> entry : innerMap.entrySet()) { + tblInner.remove(oldStmt, entry.getKey()); + tblInner.put(newStmt, entry.getKey(), entry.getValue()); + } + } + for (Entry>> entry : addMap.entrySet()) { + table.remove(oldStmt, entry.getKey()); + table.put(newStmt, entry.getKey(), entry.getValue()); + } + } + + /** + * Removes all summary functions + */ + public void clear() { + this.table.clear(); + } +} diff --git a/src/soot/jimple/interproc/ifds/template/BackwardsInterproceduralCFG.java b/src/soot/jimple/interproc/ifds/template/BackwardsInterproceduralCFG.java new file mode 100644 index 0000000..32a09e6 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/template/BackwardsInterproceduralCFG.java @@ -0,0 +1,19 @@ +package soot.jimple.interproc.ifds.template; + +import soot.Body; +import soot.Unit; +import soot.toolkits.graph.DirectedGraph; +import soot.toolkits.graph.InverseGraph; + +/** + * Same as {@link JimpleBasedInterproceduralCFG} but based on inverted unit graphs. + * This should be used for backward analyses. + */ +public class BackwardsInterproceduralCFG extends JimpleBasedInterproceduralCFG { + + @Override + protected DirectedGraph makeGraph(Body body) { + return new InverseGraph(super.makeGraph(body)); + } + +} diff --git a/src/soot/jimple/interproc/ifds/template/DefaultIDETabulationProblem.java b/src/soot/jimple/interproc/ifds/template/DefaultIDETabulationProblem.java new file mode 100644 index 0000000..60dfd96 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/template/DefaultIDETabulationProblem.java @@ -0,0 +1,60 @@ +package soot.jimple.interproc.ifds.template; + +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.EdgeFunction; +import soot.jimple.interproc.ifds.EdgeFunctions; +import soot.jimple.interproc.ifds.IDETabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.JoinLattice; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * This is a template for {@link IDETabulationProblem}s that automatically caches values + * that ought to be cached. This class uses the Factory Method design pattern. + * The {@link InterproceduralCFG} is passed into the constructor so that it can be conveniently + * reused for solving multiple different {@link IDETabulationProblem}s. + * This class is specific to Soot. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + * @param The type of values to be computed along flow edges. + * @param The type of inter-procedural control-flow graph being used. + */ +public abstract class DefaultIDETabulationProblem,V, + I extends InterproceduralCFG, UpdatableWrapper>> + extends DefaultIFDSTabulationProblem + implements IDETabulationProblem, D, UpdatableWrapper, V, I>{ + + private final EdgeFunction allTopFunction; + private final JoinLattice joinLattice; + private final EdgeFunctions,D,UpdatableWrapper,V> edgeFunctions; + + public DefaultIDETabulationProblem(I icfg) { + super(icfg); + this.allTopFunction = createAllTopFunction(); + this.joinLattice = createJoinLattice(); + this.edgeFunctions = createEdgeFunctionsFactory(); + } + + protected abstract EdgeFunction createAllTopFunction(); + + protected abstract JoinLattice createJoinLattice(); + + protected abstract EdgeFunctions, D, UpdatableWrapper, V> createEdgeFunctionsFactory(); + + @Override + public final EdgeFunction allTopFunction() { + return allTopFunction; + } + + @Override + public final JoinLattice joinLattice() { + return joinLattice; + } + + @Override + public final EdgeFunctions, D, UpdatableWrapper, V> edgeFunctions() { + return edgeFunctions; + } + +} diff --git a/src/soot/jimple/interproc/ifds/template/DefaultIFDSTabulationProblem.java b/src/soot/jimple/interproc/ifds/template/DefaultIFDSTabulationProblem.java new file mode 100644 index 0000000..ee66d8c --- /dev/null +++ b/src/soot/jimple/interproc/ifds/template/DefaultIFDSTabulationProblem.java @@ -0,0 +1,57 @@ +package soot.jimple.interproc.ifds.template; + +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.FlowFunctions; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * This is a template for {@link IFDSTabulationProblem}s that automatically caches values + * that ought to be cached. This class uses the Factory Method design pattern. + * The {@link InterproceduralCFG} is passed into the constructor so that it can be conveniently + * reused for solving multiple different {@link IFDSTabulationProblem}s. + * This class is specific to Soot. + * + * @param The type of data-flow facts to be computed by the tabulation problem. + */ +public abstract class DefaultIFDSTabulationProblem + , I extends InterproceduralCFG,UpdatableWrapper>> + implements IFDSTabulationProblem, D, UpdatableWrapper,I> { + + private final FlowFunctions, D, UpdatableWrapper> flowFunctions; + private I icfg; + private final D zeroValue; + + public DefaultIFDSTabulationProblem(I icfg) { + this.icfg = icfg; + this.flowFunctions = createFlowFunctionsFactory(); + this.zeroValue = createZeroValue(); + } + + protected abstract FlowFunctions, D, UpdatableWrapper> createFlowFunctionsFactory(); + + protected abstract D createZeroValue(); + + @Override + public final FlowFunctions, D, UpdatableWrapper> flowFunctions() { + return flowFunctions; + } + + @Override + public final I interproceduralCFG() { + return icfg; + } + + @Override + public final void updateCFG(I cfg) { + this.icfg = cfg; + } + + @Override + public final D zeroValue() { + return zeroValue; + } + +} diff --git a/src/soot/jimple/interproc/ifds/template/JimpleBasedInterproceduralCFG.java b/src/soot/jimple/interproc/ifds/template/JimpleBasedInterproceduralCFG.java new file mode 100644 index 0000000..432e995 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/template/JimpleBasedInterproceduralCFG.java @@ -0,0 +1,772 @@ +package soot.jimple.interproc.ifds.template; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import soot.Body; +import soot.Local; +import soot.MethodOrMethodContext; +import soot.PatchingChain; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Unit; +import soot.UnitBox; +import soot.jimple.Stmt; +import soot.jimple.interproc.ifds.DontSynchronize; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.SynchronizedBy; +import soot.jimple.interproc.ifds.ThreadSafe; +import soot.jimple.interproc.ifds.solver.IDESolver; +import soot.jimple.interproc.ifds.utils.Utils; +import soot.jimple.interproc.incremental.AbstractUpdatableInterproceduralCFG; +import soot.jimple.interproc.incremental.DefaultUpdatableWrapper; +import soot.jimple.interproc.incremental.SceneDiff; +import soot.jimple.interproc.incremental.SceneDiff.ClassDiffNode; +import soot.jimple.interproc.incremental.SceneDiff.DiffType; +import soot.jimple.interproc.incremental.SceneDiff.MethodDiffNode; +import soot.jimple.interproc.incremental.SceneDiff.ProgramDiffNode; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.annotation.logic.Loop; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.jimple.toolkits.callgraph.EdgePredicate; +import soot.jimple.toolkits.callgraph.Filter; +import soot.jimple.toolkits.callgraph.ReachableMethods; +import soot.toolkits.exceptions.UnitThrowAnalysis; +import soot.toolkits.graph.DirectedGraph; +import soot.toolkits.graph.ExceptionalUnitGraph; +import soot.toolkits.graph.LoopNestTree; +import soot.toolkits.scalar.Pair; +import soot.util.Chain; + +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +/** + * Default implementation for the {@link InterproceduralCFG} interface. + * Includes all statements reachable from {@link Scene#getEntryPoints()} through + * explicit call statements or through calls to {@link Thread#start()}. + * + * This class is designed to be thread safe, and subclasses of this class must be designed + * in a thread-safe way, too. + */ +@ThreadSafe +public class JimpleBasedInterproceduralCFG extends AbstractUpdatableInterproceduralCFG + ,UpdatableWrapper> { + + private static final boolean DEBUG = true; + + //retains only callers that are explicit call sites or Thread.start() + protected static class EdgeFilter extends Filter { + protected EdgeFilter() { + super(new EdgePredicate() { + public boolean want(Edge e) { + return e.kind().isExplicit() || e.kind().isThread(); + } + }); + } + } + + @DontSynchronize("readonly") + protected final CallGraph cg; + + @DontSynchronize("written by single thread; read afterwards") + protected final Map unitToOwner = new HashMap(); + + @SynchronizedBy("by use of synchronized LoadingCache class") + protected final LoadingCache> bodyToUnitGraph = + IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader>() { + public DirectedGraph load(Body body) throws Exception { + return makeGraph(body); + } + }); + + @SynchronizedBy("by use of synchronized LoadingCache class") + protected final LoadingCache> unitToCallees = + IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader>() { + public Set load(Unit u) throws Exception { + Set res = new LinkedHashSet(); + //only retain callers that are explicit call sites or Thread.start() + Iterator edgeIter = new EdgeFilter().wrap(cg.edgesOutOf(u)); + while(edgeIter.hasNext()) { + Edge edge = edgeIter.next(); + if(edge.getTgt()==null) { + System.err.println(); + } + SootMethod m = edge.getTgt().method(); + if(m.hasActiveBody()) { + assert m.getDeclaringClass().isInScene(); + res.add(m); + } + } + return res; + } + }); + + @SynchronizedBy("by use of synchronized LoadingCache class") + protected final LoadingCache> methodToCallers = + IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader>() { + public Set load(SootMethod m) throws Exception { + Set res = new LinkedHashSet(); + //only retain callers that are explicit call sites or Thread.start() + Iterator edgeIter = new EdgeFilter().wrap(cg.edgesInto(m)); + while(edgeIter.hasNext()) { + Edge edge = edgeIter.next(); + res.add(edge.srcUnit()); + } + return res; + } + }); + + @SynchronizedBy("by use of synchronized LoadingCache class") + protected final LoadingCache> methodToCallsFromWithin = + IDESolver.DEFAULT_CACHE_BUILDER.build( new CacheLoader>() { + public Set load(SootMethod m) throws Exception { + Set res = new LinkedHashSet(); + //only retain calls that are explicit call sites or Thread.start() + Iterator edgeIter = new EdgeFilter().wrap(cg.edgesOutOf(m)); + while(edgeIter.hasNext()) { + Edge edge = edgeIter.next(); + res.add(edge.srcUnit()); + } + return res; + } + }); + + @DontSynchronize("written by single thread only") + protected final SceneDiff sceneDiff = new SceneDiff(); + + protected final Map> applicationMethods = new HashMap>(); + + /** + * Set to true after references have been updated. Operations working on + * Soot objects must from then on use the previous contents of mutable + * objects. + */ + protected boolean afterUpdate = false; + + public JimpleBasedInterproceduralCFG() { + this(true); + } + + /** + * Creates a new interprocedural program graph based on the information in + * Soot's scene objects and CFG. + * @param updatable True if this program graph shall provide differential + * analysis information. This is necessary when using the CFG in a solver + * and then calling the solver's "update" function. + */ + public JimpleBasedInterproceduralCFG(boolean updatable) { + System.out.println("Obtaining call graph..."); + cg = Scene.v().getCallGraph(); + + System.out.println("Computing reachable methods..."); + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(cg, eps.iterator(), new EdgeFilter()); + reachableMethods.update(); + + System.out.println("Collecting bodies for reachable methods..."); + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + if(m.hasActiveBody()) { + Body b = m.getActiveBody(); + PatchingChain units = b.getUnits(); + for (Unit unit : units) { + unitToOwner.put(unit, b); + } + assert m.getDeclaringClass().isInScene(); + Utils.addElementToMapList(this.applicationMethods, m.getDeclaringClass(), m); + } + } + System.out.println("Interprocedural CFG created."); + + if (updatable) { + System.out.println("Building scene diff information..."); + this.sceneDiff.fullBuild(); + System.out.println("Scene diff information created."); + } + } + + @Override + public UpdatableWrapper getMethodOf(UpdatableWrapper u) { + assert u != null; + Body body = unitToOwner.get(afterUpdate ? u.getPreviousContents() : u.getContents()); + if (body == null) + throw new RuntimeException("Unit has no associated body: " + u); + return wrapWeak(body.getMethod()); + } + + @Override + public List> getSuccsOf(UpdatableWrapper u) { + assert u != null; + Body body = unitToOwner.get(afterUpdate ? u.getPreviousContents() : u.getContents()); + if (body == null) + throw new RuntimeException("Unit has no associated body: " + u); + DirectedGraph unitGraph = getOrCreateUnitGraph(body); + return wrapWeak(unitGraph.getSuccsOf(afterUpdate ? u.getPreviousContents() : u.getContents())); + } + + @Override + public List> getPredsOf(UpdatableWrapper u) { + Body body = unitToOwner.get(afterUpdate ? u.getPreviousContents() : u.getContents()); + if (body == null) + throw new RuntimeException("Unit has no associated body: " + u); + DirectedGraph unitGraph = getOrCreateUnitGraph(body); + return wrapWeak(unitGraph.getPredsOf(afterUpdate ? u.getPreviousContents() : u.getContents())); + } + + private DirectedGraph getOrCreateUnitGraph(Body body) { + assert body != null; + return bodyToUnitGraph.getUnchecked(body); + } + + protected synchronized DirectedGraph makeGraph(Body body) { + return new ExceptionalUnitGraph(body, UnitThrowAnalysis.v() ,true); + } + + @Override + public Set> getCalleesOfCallAt(UpdatableWrapper u) { + return wrapWeak(unitToCallees.getUnchecked(afterUpdate ? u.getPreviousContents() : u.getContents())); + } + + @Override + public List> getReturnSitesOfCallAt(UpdatableWrapper u) { + return getSuccsOf(u); + } + + @Override + public boolean isCallStmt(UpdatableWrapper u) { + return u == null ? false : ((Stmt)u.getContents()).containsInvokeExpr(); + } + + @Override + public boolean isExitStmt(UpdatableWrapper u) { + if (u == null) return false; + Body body = unitToOwner.get(afterUpdate ? u.getPreviousContents() : u.getContents()); + assert body != null; + DirectedGraph unitGraph = getOrCreateUnitGraph(body); + return unitGraph.getTails().contains(u.getContents()); + } + + @Override + public Set> getCallersOf(UpdatableWrapper m) { + return wrapWeak(methodToCallers.getUnchecked(afterUpdate ? m.getPreviousContents() : m.getContents())); + } + + @Override + public Set> getCallsFromWithin(UpdatableWrapper m) { + return wrapWeak(methodToCallsFromWithin.getUnchecked(afterUpdate ? m.getPreviousContents() : m.getContents())); + } + + @Override + public Set> getStartPointsOf(UpdatableWrapper m) { + if(m.getContents().hasActiveBody()) { + Body body = m.getContents().getActiveBody(); + DirectedGraph unitGraph = getOrCreateUnitGraph(body); + return wrapWeak(new LinkedHashSet(unitGraph.getHeads())); + } + return null; + } + + @Override + public boolean isStartPoint(UpdatableWrapper u) { + return unitToOwner.get(afterUpdate ? u.getPreviousContents() : u.getContents()).getUnits().getFirst()==u.getContents(); + } + + @Override + public Set> allNonCallStartNodes() { + Set res = new LinkedHashSet(unitToOwner.keySet()); + for (Iterator iter = res.iterator(); iter.hasNext();) { + Unit u = iter.next(); + if(isStartPoint(new DefaultUpdatableWrapper(u)) + || isCallStmt(new DefaultUpdatableWrapper(u))) iter.remove(); + } + return wrapWeak(res); + } + + @Override + public boolean isFallThroughSuccessor(UpdatableWrapper u, UpdatableWrapper succ) { + assert getSuccsOf(u).contains(succ); + if(!u.getContents().fallsThrough()) return false; + Body body = unitToOwner.get(u.getContents()); + return body.getUnits().getSuccOf(u.getContents()) == succ.getContents(); + } + + @Override + public boolean isBranchTarget(UpdatableWrapper u, UpdatableWrapper succ) { + assert getSuccsOf(u).contains(succ); + if(!u.getContents().branches()) return false; + for (UnitBox ub : succ.getContents().getUnitBoxes()) { + if(ub.getUnit()==succ.getContents()) return true; + } + return false; + } + + @Override + public boolean containsStmt(UpdatableWrapper stmt) { + return unitToOwner.containsKey(afterUpdate ? stmt.getPreviousContents() : stmt.getContents()); + } + + @Override + public List> getAllNodes() { + return wrapWeak(new ArrayList(unitToOwner.keySet())); + } + + @Override + public boolean equals(Object another) { + if (super.equals(another)) + return true; + if (!(another instanceof JimpleBasedInterproceduralCFG)) + return false; + + JimpleBasedInterproceduralCFG anotherCFG = (JimpleBasedInterproceduralCFG) another; + return this.cg == anotherCFG.cg || this.cg.equals(anotherCFG.cg); + } + + @Override + public int hashCode() { + return super.hashCode() * 31 + cg.hashCode(); + } + + @Override + public void computeCFGChangeset + (InterproceduralCFG, UpdatableWrapper> newCFG, + Map, List>> expiredEdges, + Map, List>> newEdges, + Set> newNodes, + Set> expiredNodes) { + + if (!(newCFG instanceof JimpleBasedInterproceduralCFG)) + throw new RuntimeException("Cannot compare graphs of different type"); + + System.out.println("Computing code diff..."); + ProgramDiffNode diffRoot = sceneDiff.incrementalBuild(); + if (diffRoot.isEmpty()) + System.out.println("Program is unchanged"); + System.out.println("Incremental build done."); + + // Check for removed classes. All statements in all methods in all + // removed classes are automatically expired + for (ClassDiffNode cd : diffRoot) + if (cd.getDiffType() == DiffType.REMOVED) { + System.out.println("Removed class: " + cd.getOldClass().getName()); + for (SootMethod sm : this.applicationMethods.get(cd.getOldClass())) + if (sm.hasActiveBody()) + for (Unit u : sm.getActiveBody().getUnits()) { + UpdatableWrapper wrapper = wrapWeak(u); + expiredNodes.add(wrapper); + assert this.containsStmt(wrapper); + } + } + + // Iterate over the new scene to find the changes inside the retained + // classes. This includes finding new methods in existing classes. We + // don't find any removed classes as we start with the new ones. + this.setSafepoint(); + for (SootClass newClass : ((JimpleBasedInterproceduralCFG) newCFG).applicationMethods.keySet()) { + List newMethods = ((JimpleBasedInterproceduralCFG) newCFG).applicationMethods.get(newClass); + ClassDiffNode cd = diffRoot.getClassChanges(newClass); + + // If the class diff is null, the class has not changed + if (cd == null) { + for (SootMethod sm : newMethods) + updateUnchangedMethodPointers(diffRoot.getOldMethodFor(sm), sm); + } + else { + // If the class is new, all its methods are new + if (cd.getDiffType() == DiffType.ADDED) { + System.out.println("Added class: " + cd.getNewClass().getName()); + for (SootMethod sm : newMethods) + if (sm.hasActiveBody()) + for (Unit u : sm.getActiveBody().getUnits()) { + UpdatableWrapper wrapper = wrapWeak(u); + newNodes.add(wrapper); + assert newCFG.containsStmt(wrapper); + wrapper.setSafepoint(); + } + continue; + } + + // The class has been retained. + assert cd.getDiffType() == DiffType.CHANGED; + List oldMethods = this.applicationMethods.get(cd.getOldClass()); + + // Check for removed methods. All statements in removed methods are + // automatically expired + for (SootMethod oldMethod : oldMethods) { + MethodDiffNode md = cd.getMethodDiff(oldMethod); + // If we have no diff, the method is retained unchanged. We handle + // that below, so we can ignore the case here. + if (md != null && md.getDiffType() == DiffType.REMOVED) { + System.out.println("Removed method: " + + md.getOldMethod().getDeclaringClass().getName() + ": " + + md.getOldMethod().getSubSignature() + + " (" + md.getDiffType() + ")"); + if (md.getOldMethod().hasActiveBody()) + for (Unit u : md.getOldMethod().getActiveBody().getUnits()) { + UpdatableWrapper wrapper = wrapWeak(u); + expiredNodes.add(wrapper); + assert this.containsStmt(wrapper); + wrapper.setSafepoint(); + } + } + } + + // Check for added and changed methods + for (SootMethod newMethod : newMethods) { + MethodDiffNode md = cd.getMethodDiff(newMethod); + if (md == null) { + // This method has been retained, we need to update the pointers + updateUnchangedMethodPointers(diffRoot.getOldMethodFor(newMethod), newMethod); + } + else if (md.getDiffType() == DiffType.ADDED) { + System.out.println("Added method: " + + md.getNewMethod().getDeclaringClass().getName() + ": " + + md.getNewMethod().getSubSignature() + + " (" + md.getDiffType() + ")"); + if (md.getNewMethod().hasActiveBody()) + for (Unit u : md.getNewMethod().getActiveBody().getUnits()) { + UpdatableWrapper wrapper = wrapWeak(u); + newNodes.add(wrapWeak(u)); + assert newCFG.containsStmt(wrapper); + wrapper.setSafepoint(); + } + } + else { + // The method has been changed + assert md.getDiffType() == DiffType.CHANGED; + System.out.println("Changed method: " + + md.getNewMethod().getDeclaringClass().getName() + ": " + + md.getNewMethod().getSubSignature() + + " (" + md.getDiffType() + ")"); + + // For changed methods, we need to find the edges that have + // been added or removed + computeMethodChangeset(newCFG, md.getOldMethod(), md.getNewMethod(), + expiredEdges, newEdges, newNodes, expiredNodes); + + // Compute the changes to the local variables + for (Local lold : md.getOldMethod().getActiveBody().getLocals()) + for (Local lnew : md.getNewMethod().getActiveBody().getLocals()) + if (lold.getName().equals(lnew.getName())) { + notifyReferenceChanged(lold, lnew); + break; + } + } + } + } + } + + // Iterate over the old classes and mark all statements in all methods + // of removed classes as expired + for (SootClass oldClass : applicationMethods.keySet()) { + SootClass newClass = null; + for (SootClass nc : ((JimpleBasedInterproceduralCFG) newCFG).applicationMethods.keySet()) + if (oldClass.getName().equals(nc.getName())) { + newClass = nc; + break; + } + + if (newClass == null) { + assert diffRoot.getClassChanges(oldClass) != null; + continue; + } + + for (SootMethod oldMethod : oldClass.getMethods()) + if (this.applicationMethods.get(oldClass).contains(oldMethod)) { + boolean found = false; + for (SootMethod newMethod : newClass.getMethods()) + if (oldMethod.getName().equals(newMethod.getName())) { + found = true; + break; + } + if (!found) + assert diffRoot.getClassChanges(oldClass).getMethodDiff(oldMethod) != null; + } + } + /* + for (SootMethod oldMethod : applicationMethods.get(oldClass)) { + boolean found = false; + for (SootClass newClass : ((JimpleBasedInterproceduralCFG) newCFG).applicationMethods.keySet()) + if (newMethod.getName().equals(oldMethod.getName())) { + found = true; + break; + } + + if (!found) + for (Unit u : oldMethod.getActiveBody().getUnits()) { + UpdatableWrapper wrapper = wrapWeak(u); + assert this.containsStmt(wrapper); + expiredNodes.add(wrapper); + wrapper.setSafepoint(); + } + } + */ + this.afterUpdate = true; + } + + /** + * Updates the statement points for an unchanged method. All updateable + * references to statements in the old method are changed to point to + * their counterparts in the new method. + * @param oldMethod The old method before the update + * @param newMethod The new method after the update + */ + private void updateUnchangedMethodPointers(SootMethod oldMethod, SootMethod newMethod) { + // If one of the two methods has no body, we cannot match anything + if (oldMethod == null || !oldMethod.hasActiveBody() + || newMethod == null || !newMethod.hasActiveBody() + || oldMethod == newMethod) + return; + + // As we expect the method to be unchanged, there should be the same + // number of statements in both the old and the new version + assert oldMethod.getActiveBody().getUnits().size() == + newMethod.getActiveBody().getUnits().size(); + + // Update the statement references + updatePointsFromChain(oldMethod.getActiveBody().getUnits(), + newMethod.getActiveBody().getUnits()); + updatePointsFromChain(oldMethod.getActiveBody().getLocals(), + newMethod.getActiveBody().getLocals()); + + } + + private void updatePointsFromChain(Chain oldChain, Chain newChain) { + if (oldChain.isEmpty() || newChain.isEmpty()) + return; + + X uold = oldChain.getFirst(); + X unew = newChain.getFirst(); + while (uold != null && unew != null) { + assert uold.toString().contains("tmp$") || uold.toString().contains("access$") + || uold.toString().equals(unew.toString()); + notifyReferenceChanged(uold, unew); + uold = oldChain.getSuccOf(uold); + unew = newChain.getSuccOf(unew); + } + } + + /** + * Computes the edge differences between two Soot methods. + * @param newCFG The new program graph. The current object is assumed to + * hold the old program graph. + * @param oldMethod The method before the changes were made + * @param newMethod The method after the changes have been made + * @param expiredEdges The map that receives the expired edge targets. If + * two edges a->b and a->c have expired, the entry (a,(b,c)) will be put in + * the map. + * @param newEdges The map that receives the new edge targets. + * @param newNodes The list that receives all nodes which have been added to + * the method + * @param expiredNodes The list that receives all nodes which have been + * deleted from the method + * @param nodeReplaceSet The map that receives the mapping between old nodes + * and new ones. If a statement is left unchanged by the modifications to the + * method, it may nevertheless be represented by a new object in the program + * graph. Use this map to update any references you might hold to the old + * objects. + */ + private void computeMethodChangeset + (InterproceduralCFG, UpdatableWrapper> newCFG, + SootMethod oldMethod, + SootMethod newMethod, + Map, List>> expiredEdges, + Map, List>> newEdges, + Set> newNodes, + Set> expiredNodes) { + + assert newCFG != null; + assert oldMethod != null; + assert newMethod != null; + assert expiredEdges != null; + assert newEdges != null; + assert newNodes != null; + + // Delay reference changes until the end for not having to cope with + // changing references inside our analysis + Map refChanges = new HashMap(); + + // For all entry points of the new method, try to find the corresponding + // statements in the old method. If we don't a corresponding statement, + // we record a NULL value. + List,UpdatableWrapper>> workQueue = + new ArrayList,UpdatableWrapper>>(); + List> doneList = new ArrayList>(10000); + for (UpdatableWrapper spNew : newCFG.getStartPointsOf + (new DefaultUpdatableWrapper(newMethod))) { + UpdatableWrapper spOld = findStatement + (new DefaultUpdatableWrapper(oldMethod), spNew); + workQueue.add(new Pair,UpdatableWrapper>(spNew, spOld)); + if (spOld == null) + newNodes.add(spNew); + else + refChanges.put(spOld.getContents(), spNew.getContents()); + } + + while (!workQueue.isEmpty()) { + // Dequeue the current element and make sure we don't run in circles + Pair,UpdatableWrapper> ns = workQueue.remove(0); + UpdatableWrapper newStmt = ns.getO1(); + UpdatableWrapper oldStmt = ns.getO2(); + if (doneList.contains(newStmt)) + continue; + doneList.add(newStmt); + + // If the current point is unreachable, we skip the remainder of the method + if (!newCFG.containsStmt(newStmt)) + continue; + + // Find the outgoing edges and check whether they are new + boolean isNewStmt = newNodes.contains(newStmt); + for (UpdatableWrapper newSucc : newCFG.getSuccsOf(newStmt)) { + UpdatableWrapper oldSucc = oldStmt == null ? null : findStatement(getSuccsOf(oldStmt), newSucc); + if (oldSucc == null || !getSuccsOf(oldStmt).contains(oldSucc) || isNewStmt) + Utils.addElementToMapList(newEdges, oldStmt == null ? newStmt : oldStmt, + oldSucc == null ? newSucc : oldSucc); + if (oldSucc == null) + newNodes.add(newSucc); + workQueue.add(new Pair,UpdatableWrapper> + (newSucc, oldSucc == null ? oldStmt : oldSucc)); +// workQueue.add(new Pair,UpdatableWrapper>(newSucc, oldSucc)); + + if (oldSucc != null) + refChanges.put(oldSucc.getContents(), newSucc.getContents()); + } + } + + // For all entry points of the old method, check whether we can reach a + // statement that is no longer present in the new method. + doneList.clear(); + for (UpdatableWrapper spOld : getStartPointsOf(new DefaultUpdatableWrapper(oldMethod))) { + UpdatableWrapper spNew = newCFG.findStatement(new DefaultUpdatableWrapper(newMethod), spOld); + workQueue.add(new Pair,UpdatableWrapper>(spNew, spOld)); + if (spNew == null) + expiredNodes.add(spOld); + } + + while (!workQueue.isEmpty()) { + // Dequeue the current element and make sure we don't run in circles + Pair,UpdatableWrapper> ns = workQueue.remove(0); + UpdatableWrapper newStmt = ns.getO1(); + UpdatableWrapper oldStmt = ns.getO2(); + if (doneList.contains(oldStmt)) + continue; + doneList.add(oldStmt); + + // If the current point is unreachable, we skip the remainder of the method + if (!containsStmt(oldStmt)) + continue; + + // Find the outgoing edges and check whether they are expired + boolean isExpiredStmt = expiredNodes.contains(oldStmt); + for (UpdatableWrapper oldSucc : getSuccsOf(oldStmt)) { + UpdatableWrapper newSucc = newStmt == null ? null : newCFG.findStatement + (newCFG.getSuccsOf(newStmt), oldSucc); + if (newSucc == null || !newCFG.getSuccsOf(newStmt).contains(newSucc) || isExpiredStmt) + Utils.addElementToMapList(expiredEdges, oldStmt, oldSucc); + if (newSucc == null) + expiredNodes.add(oldSucc); + workQueue.add(new Pair,UpdatableWrapper> + (newSucc == null ? newStmt : newSucc, oldSucc)); +// workQueue.add(new Pair,UpdatableWrapper>(newSucc, oldSucc)); + + if (newSucc != null) + refChanges.put(oldSucc.getContents(), newSucc.getContents()); + } + } + + // Make sure that every statement is either added or removed or remapped + if (DEBUG) { + doneList.clear(); + List> checkQueue = new ArrayList>(); + checkQueue.addAll(this.getStartPointsOf(wrapWeak(oldMethod))); + while (!checkQueue.isEmpty()) { + UpdatableWrapper curUnit = checkQueue.remove(0); + if (doneList.contains(curUnit)) + continue; + doneList.add(curUnit); + assert expiredNodes.contains(curUnit) + || newNodes.contains(curUnit) + || refChanges.containsKey(curUnit.getContents()); + } + } + + for (Entry entry : refChanges.entrySet()) + notifyReferenceChanged(entry.getKey(), entry.getValue()); + } + + @Override + public UpdatableWrapper findStatement + (UpdatableWrapper oldMethod, + UpdatableWrapper newStmt) { + return findStatement(getStartPointsOf(oldMethod), newStmt); + } + + @Override + public UpdatableWrapper findStatement + (Iterable> oldMethod, + UpdatableWrapper newStmt) { + List> doneList = new ArrayList>(); + return findStatement(oldMethod, newStmt, doneList); + } + + private UpdatableWrapper findStatement + (Iterable> oldMethod, + UpdatableWrapper newStmt, + List> doneList) { + List> workList = new ArrayList>(); + for (UpdatableWrapper u : oldMethod) + workList.add(u); + + while (!workList.isEmpty()) { + UpdatableWrapper sp = workList.remove(0); + if (doneList.contains(sp)) + continue; + doneList.add(sp); + + if (sp == newStmt || sp.equals(newStmt) || sp.toString().equals + (newStmt.toString())) + return sp; + workList.addAll(getSuccsOf(sp)); + } + return null; + } + + @Override + public UpdatableWrapper getLoopStartPointFor + (UpdatableWrapper stmt) { + Body body = this.unitToOwner.get(afterUpdate ? stmt.getPreviousContents() : stmt.getContents()); + assert body != null; + LoopNestTree loopTree = new LoopNestTree(body); + Unit loopHead = null; + for (Loop loop : loopTree) { + if (loop.getLoopStatements().contains(stmt.getContents())) + loopHead = loop.getHead(); + } + return loopHead == null ? null : wrapWeak(loopHead); + } + + @Override + public Set> getExitNodesForReturnSite + (UpdatableWrapper stmt) { + List> preds = this.getPredsOf(stmt); + Set> exitNodes = new HashSet>(preds.size() * 2); + for (UpdatableWrapper pred : preds) + for (UpdatableWrapper sm : this.getCalleesOfCallAt(pred)) + if (sm.getContents().hasActiveBody()) + for (Unit u : sm.getContents().getActiveBody().getUnits()) + if (this.isExitStmt(this.wrapWeak(u))) + exitNodes.add(this.wrapWeak(u)); + return exitNodes; + } +} diff --git a/src/soot/jimple/interproc/ifds/test/IFDSTest.java b/src/soot/jimple/interproc/ifds/test/IFDSTest.java new file mode 100644 index 0000000..7ff4e3f --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/IFDSTest.java @@ -0,0 +1,1554 @@ +package soot.jimple.interproc.ifds.test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import junit.framework.Assert; + +import org.junit.Test; + +import soot.ArrayType; +import soot.Body; +import soot.Local; +import soot.MethodOrMethodContext; +import soot.PackManager; +import soot.RefType; +import soot.Scene; +import soot.SceneTransformer; +import soot.SootClass; +import soot.SootFieldRef; +import soot.SootMethod; +import soot.SootMethodRef; +import soot.Transform; +import soot.Unit; +import soot.jimple.AssignStmt; +import soot.jimple.IntConstant; +import soot.jimple.InvokeExpr; +import soot.jimple.InvokeStmt; +import soot.jimple.Jimple; +import soot.jimple.NewExpr; +import soot.jimple.StaticInvokeExpr; +import soot.jimple.Stmt; +import soot.jimple.VirtualInvokeExpr; +import soot.jimple.internal.JAssignStmt; +import soot.jimple.internal.JInvokeStmt; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.problems.IFDSLocalInfoFlow; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.jimple.toolkits.callgraph.ReachableMethods; + +/** + * Test class for the analysis IFDS. The test cases depend on the "SimpleTest" + * class in the "test" directory. + */ +public class IFDSTest { + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method uses dynamic updates on the solver to avoid recomputations. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestUpdate(final ITestHandler> handler, final String className) { + soot.G.reset(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + // Force method load to avoid spurious changes + for (SootClass sc : Scene.v().getApplicationClasses()) + for (SootMethod sm : sc.getMethods()) + sm.retrieveActiveBody(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> problem = + new IFDSLocalInfoFlow(icfg); + + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + solver.solve(); + + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(className, results); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + handler.patchGraph(i); + solver.update(icfg = new JimpleBasedInterproceduralCFG()); + handler.performExtendedTest(icfg, solver, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java.", + className } ); + + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method does not create indices for dynamic updates. Instead, updates are + * just propagated along the edges until a fix point is reached. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestDirect(final ITestHandler> handler, final String className) { + soot.G.reset(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + // Force method load to avoid spurious changes + for (SootClass sc : Scene.v().getApplicationClasses()) + for (SootMethod sm : sc.getMethods()) + sm.retrieveActiveBody(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> problem = + new IFDSLocalInfoFlow(icfg); + + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + solver.solve(false); + + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(className, results); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + handler.patchGraph(i); + solver.update(icfg = new JimpleBasedInterproceduralCFG()); + handler.performExtendedTest(icfg, solver, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java.", + className } ); + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method reruns the complete analysis with the updated data. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestRerun(final ITestHandler> handler, final String className) { + soot.G.reset(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> problem = + new IFDSLocalInfoFlow(icfg); + + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + solver.solve(false); + + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(className, results); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + handler.patchGraph(i); + IFDSTabulationProblem,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> problem2 = + new IFDSLocalInfoFlow(icfg = new JimpleBasedInterproceduralCFG()); + + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver2 = + new IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem2); + solver2.solve(false); + + if (handler != null) + handler.performExtendedTest(icfg, solver2, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java.", + className } ); + } + + private void checkInitialLeaks + (final String className, + Set> results) { + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) { + String name = l.getContents().getName(); + if (!name.contains("temp$")) + leaks.add(name); + } + + if (className.equals("SimpleTest") || className.equals("DynamicTest") + || className.equals("org.junit.runner.JUnitCore")) { + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + String leakName = results.iterator().next().getContents().getName(); + if (!leakName.equals("r0") && !leakName.equals("args")) + Assert.fail("Invalid information leak found"); + } else if (className.equals("TestRemoveLeak") || className.equals("TestRemoveLeakInFunction") + || className.equals("TestRemoveLeakingCall")) { + Assert.assertEquals("Invalid number of information leaks found", 2, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + Assert.assertTrue("Invalid information leak found", leaks.contains("var")); + } else if (className.equals("TestRemoveNoLeakCall")) { + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + } + else + Assert.fail("Invalid test class"); + } + + private ITestHandler> ITestHandlerSimpleTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks("SimpleTest", results); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + + } + }; + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + */ + @Test + public void simpleTest_Update() { + System.out.println("Starting simpleTest_Update..."); + performTestUpdate(ITestHandlerSimpleTest(), "SimpleTest"); + System.out.println("simpleTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + */ + @Test + public void simpleTest_Propagate() { + System.out.println("Starting simpleTest_Propagate..."); + performTestDirect(ITestHandlerSimpleTest(), "SimpleTest"); + System.out.println("simpleTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + */ + @Test + public void simpleTest_Rerun() { + System.out.println("Starting simpleTest_Rerun..."); + performTestRerun(ITestHandlerSimpleTest(), "SimpleTest"); + System.out.println("simpleTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Rerun() { + System.out.println("Starting simpleTest_Rerun..."); + performTestRerun(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + * @throws ClassNotFoundException + */ + @Test + public void simpleTestJU_Update() throws ClassNotFoundException { + System.out.println("Starting simpleTest_Update..."); + performTestUpdate(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Propagate() { + System.out.println("Starting simpleTest_Propagate..."); + performTestDirect(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTest_Propagate finished."); + } + + private ITestHandler> ITestHandlerLeakUpdate () { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + System.out.println("Patching graph in phase " + phase + "..."); + + // Patch the control-flow graph. We add an assignment to the + // "foo" variable inside the loop + Local fooLocal = null; + Local argsLocal = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) + if (l.getName().equals("foo")) + fooLocal = l; + else if (l.getName().equals("args")) + argsLocal = l; + Assert.assertNotNull(fooLocal); + Assert.assertNotNull(argsLocal); + + if (phase == 0) { + AssignStmt assignStmt = Jimple.v().newAssignStmt(fooLocal, argsLocal); + JAssignStmt point = null; + for (Unit unit : Scene.v().getMainMethod().getActiveBody().getUnits()) { + if (unit instanceof JAssignStmt) { + JAssignStmt stmt = (JAssignStmt) unit; + if (stmt.getLeftOp().toString().equals("temp$5")) + if (stmt.getRightOp().toString().equals("temp$4 + -1")) { + point = stmt; + break; + } + } + } + if (point == null) + Assert.fail("Injection point not found"); + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore (assignStmt, point); + + for (Unit u : Scene.v().getMainMethod().getActiveBody().getUnits()) + System.out.println(u); + } + else if (phase == 1) { + // We add another assignment after the loop + Local newLocal = Jimple.v().newLocal("assignTest", ArrayType.v(RefType.v("java.lang.String"), 1)); + Scene.v().getMainMethod().getActiveBody().getLocals().add(newLocal); + AssignStmt invokeStmt = Jimple.v().newAssignStmt(newLocal, fooLocal); + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore + (invokeStmt, Scene.v().getMainMethod().getActiveBody().getUnits().getLast()); + } + else + Assert.fail("Invalid phase: " + phase); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + if (phase == 1) { + Assert.assertEquals("Invalid number of information leaks found", 2, results.size()); + List locals = new ArrayList(); + for (UpdatableWrapper l : results) + locals.add(l.getContents().getName()); + Assert.assertTrue("Invalid information leak found", locals.contains("r0")); + Assert.assertTrue("Invalid information leak found", locals.contains("r1")); + } + else if (phase == 2) { + Assert.assertEquals("Invalid number of information leaks found", 3, results.size()); + List locals = new ArrayList(); + for (UpdatableWrapper l : results) + locals.add(l.getContents().getName()); + Assert.assertTrue("Invalid information leak found", locals.contains("assignTest")); + Assert.assertTrue("Invalid information leak found", locals.contains("r0")); + Assert.assertTrue("Invalid information leak found", locals.contains("r1")); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTest_Update() { + System.out.println("Starting addLeakUpdateTest_Update..."); + performTestUpdate(ITestHandlerLeakUpdate(), "SimpleTest"); + System.out.println("addLeakUpdateTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTest_Propagate() { + System.out.println("Starting addLeakUpdateTest_Propagate..."); + performTestDirect(ITestHandlerLeakUpdate(), "SimpleTest"); + System.out.println("addLeakUpdateTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTest_Rerun() { + System.out.println("Starting addLeakUpdateTest_Rerun..."); + performTestRerun(ITestHandlerLeakUpdate(), "SimpleTest"); + System.out.println("addLeakUpdateTest_Rerun finished."); + } + + private ITestHandler> ITestHandlerLeakUpdateJU () { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We add an assignment to the + // "foo" variable inside the loop + Local argsLocal = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) + if (l.getName().equals("r0")) { + argsLocal = l; + break; + } + Assert.assertNotNull(argsLocal); + + Body mb = Scene.v().getMainMethod().getActiveBody(); + Stmt point = null; + for (Unit unit : mb.getUnits()) { + if (unit instanceof JInvokeStmt) { + JInvokeStmt stmt = (JInvokeStmt) unit; + if (stmt.getInvokeExpr().getMethod().getName().equals("runMainAndExit")) { + point = stmt; + break; + } + } + } + if (point == null) + Assert.fail("Injection point not found"); + + Local varFoo = Jimple.v().newLocal("foo", ArrayType.v(RefType.v("java.lang.String"), 1)); + mb.getLocals().addFirst(varFoo); + AssignStmt assignStmt = Jimple.v().newAssignStmt(varFoo, argsLocal); + mb.getUnits().insertBefore(assignStmt, point); + + SootFieldRef sysoRef = Scene.v().getSootClass("java.lang.System").getField + ("out", RefType.v("java.io.PrintStream")).makeRef(); + SootClass psClass = RefType.v("java.io.PrintStream").getSootClass(); + SootMethodRef methRef = psClass.getMethod("void println(java.lang.String)").makeRef(); + + Local varOut = Jimple.v().newLocal("out", RefType.v("java.io.PrintStream")); + mb.getLocals().insertAfter(varOut, varFoo); + AssignStmt assignOutStmt = Jimple.v().newAssignStmt(varOut, Jimple.v().newStaticFieldRef(sysoRef)); + mb.getUnits().insertAfter(assignOutStmt, assignStmt); + + SootClass sClass = RefType.v("java.lang.Object").getSootClass(); + SootMethodRef toStringRef = sClass.getMethod("java.lang.String toString()").makeRef(); + + VirtualInvokeExpr toStringExpr = Jimple.v().newVirtualInvokeExpr + (varFoo, toStringRef); + Local varS = Jimple.v().newLocal("s", RefType.v("java.lang.String")); + mb.getLocals().insertAfter(varS, varOut); + AssignStmt assignSStmt = Jimple.v().newAssignStmt(varS, toStringExpr); + mb.getUnits().insertAfter(assignSStmt, assignOutStmt); + + VirtualInvokeExpr virtualInvokeExpr = Jimple.v().newVirtualInvokeExpr + (varOut, methRef, varS); + InvokeStmt invokeStmt = Jimple.v().newInvokeStmt(virtualInvokeExpr); + mb.getUnits().insertAfter(invokeStmt, assignSStmt); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + Assert.assertEquals("Invalid number of information leaks found", 2, results.size()); + List locals = new ArrayList(); + for (UpdatableWrapper l : results) + locals.add(l.getContents().getName()); + Assert.assertTrue("Invalid information leak found", locals.contains("r0")); + Assert.assertTrue("Invalid information leak found", locals.contains("foo")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTestJU_Update() { + System.out.println("Starting addLeakUpdateTest_Update..."); + performTestUpdate(ITestHandlerLeakUpdateJU(), "org.junit.runner.JUnitCore"); + System.out.println("addLeakUpdateTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTestJU_Propagate() { + System.out.println("Starting addLeakUpdateTest_Propagate..."); + performTestDirect(ITestHandlerLeakUpdateJU(), "org.junit.runner.JUnitCore"); + System.out.println("addLeakUpdateTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then adds a statement that introduces + * a new information leak into a loop within the main function + */ + @Test + public void addLeakUpdateTestJU_Rerun() { + System.out.println("Starting addLeakUpdateTest_Rerun..."); + performTestRerun(ITestHandlerLeakUpdateJU(), "org.junit.runner.JUnitCore"); + System.out.println("addLeakUpdateTest_Rerun finished."); + } + + private ITestHandler> ITestHandlerCallNoLeakageTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We insert a new call from the main() method + // to our artificial helper method "otherMethod". + System.out.println("Patching cfg..."); + SootMethod helperMethod = Scene.v().getMainClass().getMethodByName("otherMethod"); + Local localFoo = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) { + if (l.getName().equals("foo")) + localFoo = l; + } + Assert.assertNotNull(localFoo); + + Local newLocal = Jimple.v().newLocal("retVal", ArrayType.v(RefType.v("java.lang.String"), 1)); + Scene.v().getMainMethod().getActiveBody().getLocals().add(newLocal); + + AssignStmt initStmt = Jimple.v().newAssignStmt(newLocal, IntConstant.v(42)); + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore + (initStmt, Scene.v().getMainMethod().getActiveBody().getUnits().getLast()); + + StaticInvokeExpr invokeExpr = Jimple.v().newStaticInvokeExpr(helperMethod.makeRef(), newLocal); + + AssignStmt invokeStmt = Jimple.v().newAssignStmt(localFoo, invokeExpr); + for (Unit u : Scene.v().getMainMethod().getActiveBody().getUnits()) + if (u.toString().contains("doFoo")) { + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore (invokeStmt, u); + break; + } + + helperMethod.retrieveActiveBody(); + + CallGraph cg = Scene.v().getCallGraph(); + Edge edge = new Edge(Scene.v().getMainMethod(), invokeStmt, helperMethod); + cg.addEdge(edge); + + // Check that our new method is indeed reachable + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(Scene.v().getCallGraph(), eps.iterator()); + reachableMethods.update(); + boolean found = false; + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + if (m.getName().contains("otherMethod")) + found = true; + } + Assert.assertTrue("Patched method NOT found", found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) + leaks.add(l.getContents().getName()); + + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTest_Update() { + System.out.println("Starting addCallNoLeakageTest_Update..."); + performTestUpdate(ITestHandlerCallNoLeakageTest(), "SimpleTest"); + System.out.println("addCallNoLeakageTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTest_Propagate() { + System.out.println("Starting addCallNoLeakageTest_Propagate..."); + performTestDirect(ITestHandlerCallNoLeakageTest(), "SimpleTest"); + System.out.println("addCallNoLeakageTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTest_Rerun() { + System.out.println("Starting addCallNoLeakageTest_Rerun..."); + performTestRerun(ITestHandlerCallNoLeakageTest(), "SimpleTest"); + System.out.println("addCallNoLeakageTest_Rerun finished."); + } + + private ITestHandler> ITestHandlerCallNoLeakageTestJU() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We duplicate the call to "runMain" in the + // "runMainAndExit" method + System.out.println("Patching cfg..."); + SootMethod method = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Body mb = method.getActiveBody(); + + Local argsLocal = null; + for (Local l : mb.getLocals()) + if (l.getName().equals("r0")) { + argsLocal = l; + break; + } + + Stmt point = null; + for (Unit unit : mb.getUnits()) { + if (unit instanceof JAssignStmt) { + JAssignStmt stmt = (JAssignStmt) unit; + if (stmt.getRightOp().toString().contains("runMain")) { + point = stmt; + break; + } + } + } + if (point == null) + Assert.fail("Injection point not found"); + + Local rsLocal = Jimple.v().newLocal("obj", RefType.v("org.junit.internal.JUnitSystem")); + mb.getLocals().add(rsLocal); + + NewExpr newSysExpr = Jimple.v().newNewExpr(RefType.v("org.junit.internal.RealSystem")); + AssignStmt initRsStmt = Jimple.v().newAssignStmt(rsLocal, newSysExpr); + mb.getUnits().insertAfter(initRsStmt, point); + + Local newLocal = Jimple.v().newLocal("obj", RefType.v("org.junit.runner.JUnitCore")); + mb.getLocals().add(newLocal); + + NewExpr newExpr = Jimple.v().newNewExpr(RefType.v("org.junit.runner.JUnitCore")); + AssignStmt initStmt = Jimple.v().newAssignStmt(newLocal, newExpr); + mb.getUnits().insertAfter(initStmt, initRsStmt); + + // Important: Call init before doing anything else with the newly created object!!! + // Otherwise the VM will throw a java.lang.VerifyError: Expecting to find object/array on stack + // specialinvoke $r2.()>() + InvokeStmt invokeInitStmt = Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(newLocal, + RefType.v("org.junit.runner.JUnitCore").getSootClass().getMethodByName("").makeRef())); + mb.getUnits().insertAfter(invokeInitStmt, initStmt); + + SootMethodRef methRef = Scene.v().getMainClass().getMethod + ("org.junit.runner.Result runMain(org.junit.internal.JUnitSystem,java.lang.String[])").makeRef(); + InvokeStmt invokeStmt = Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(newLocal, methRef, rsLocal, + argsLocal)); + mb.getUnits().insertAfter(invokeStmt, invokeInitStmt); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) + leaks.add(l.getContents().getName()); + + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("r0")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTestJU_Update() { + System.out.println("Starting addCallNoLeakageTestJU_Update..."); + performTestUpdate(ITestHandlerCallNoLeakageTestJU(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoLeakageTestJU_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTestJU_Propagate() { + System.out.println("Starting addCallNoLeakageTestJU_Propagate..."); + performTestDirect(ITestHandlerCallNoLeakageTestJU(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoLeakageTestJU_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call that should not + * change information leakage + */ + @Test + public void addCallNoLeakageTestJU_Rerun() { + System.out.println("Starting addCallNoLeakageTestJU_Rerun..."); + performTestRerun(ITestHandlerCallNoLeakageTestJU(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoLeakageTestJU_Rerun finished."); + } + + private ITestHandler> ITestHandlerAddLeakTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We insert a new call from the main() method + // to our artificial helper method "otherMethod". + System.out.println("Patching cfg..."); + SootMethod helperMethod = Scene.v().getMainClass().getMethodByName("otherMethod"); + Local localTestMe = null; + Local localFoo = null; + for (Local l : Scene.v().getMainMethod().getActiveBody().getLocals()) { + if (l.getName().equals("args")) + localTestMe = l; + else if (l.getName().equals("foo")) + localFoo = l; + } + Assert.assertNotNull("Parameter local was null", localTestMe); + Assert.assertNotNull("Variable foo was null", localFoo); + StaticInvokeExpr invokeExpr = Jimple.v().newStaticInvokeExpr(helperMethod.makeRef(), localTestMe); + + AssignStmt invokeStmt = Jimple.v().newAssignStmt(localFoo, invokeExpr); + for (Unit u : Scene.v().getMainMethod().getActiveBody().getUnits()) + if (u.toString().contains("doFoo")) { + Scene.v().getMainMethod().getActiveBody().getUnits().insertBefore (invokeStmt, u); + break; + } + + helperMethod.retrieveActiveBody(); + + CallGraph cg = Scene.v().getCallGraph(); + Edge edge = new Edge(Scene.v().getMainMethod(), invokeStmt, helperMethod); + cg.addEdge(edge); + + // Check that our new method is indeed reachable + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(cg, eps.iterator()); + reachableMethods.update(); + boolean found = false; + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + if (m.getName().equals("otherMethod")) + found = true; + } + Assert.assertTrue("Patched method NOT found", found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + Assert.assertEquals("Invalid number of information leaks found", 2, results.size()); + + List locals = new ArrayList(); + for (UpdatableWrapper l : results) + locals.add(l.getContents().getName()); + Assert.assertTrue("Invalid information leak found", locals.contains("args")); + Assert.assertTrue("Invalid information leak found", locals.contains("foo")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then adds function call which introduces + * a new information leak + */ + @Test + public void addCallAddLeakTest_Update() { + System.out.println("Starting addCallAddLeakTest_Update..."); + performTestUpdate(ITestHandlerAddLeakTest(), "SimpleTest"); + System.out.println("addCallAddLeakTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call which introduces + * a new information leak + */ + @Test + public void addCallAddLeakTest_Propagate() { + System.out.println("Starting addCallAddLeakTest_Propagate..."); + performTestDirect(ITestHandlerAddLeakTest(), "SimpleTest"); + System.out.println("addCallAddLeakTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then adds function call which introduces + * a new information leak + */ + @Test + public void addCallAddLeakTest_Rerun() { + System.out.println("Starting addCallAddLeakTest_Rerun..."); + performTestRerun(ITestHandlerAddLeakTest(), "SimpleTest"); + System.out.println("addCallAddLeakTest_Rerun finished."); + } + + private ITestHandler> ITestHandlerAddLeakInFunctionTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We change the "bar" to add a new assignment + System.out.println("Patching cfg..."); + SootMethod helperMethod = Scene.v().getMainClass().getMethodByName("bar"); + helperMethod.retrieveActiveBody(); + + Local localParam = null; + Local localVariable = null; + for (Local l : helperMethod.getActiveBody().getLocals()) { + if (l.getName().equals("args")) + localParam = l; + else if (l.getName().equals("foo")) + localVariable = l; + } + Assert.assertNotNull("Parameter local was null", localParam); + Assert.assertNotNull("Variable foo was null", localVariable); + + AssignStmt invokeStmt = Jimple.v().newAssignStmt(localVariable, localParam); + for (Unit u : helperMethod.getActiveBody().getUnits()) + if (u.toString().contains("return")) { + helperMethod.getActiveBody().getUnits().insertBefore (invokeStmt, u); + break; + } + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) { + String name = l.getContents().getName(); + if (!name.contains("temp$")) + leaks.add(name); + } + + Assert.assertEquals("Invalid number of information leaks found", 2, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + Assert.assertTrue("Invalid information leak found", leaks.contains("barRes")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then changes a function called from + * main() such that a new leak is introduced in this function + */ + @Test + public void addLeakInFunctionTest_Update() { + System.out.println("Starting addLeakInFunctionTest_Update..."); + performTestUpdate(ITestHandlerAddLeakInFunctionTest(), "SimpleTest"); + System.out.println("addLeakInFunctionTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then changes a function called from + * main() such that a new leak is introduced in this function + */ + @Test + public void addLeakInFunctionTest_Propagate() { + System.out.println("Starting addLeakInFunctionTest_Propagate..."); + performTestDirect(ITestHandlerAddLeakInFunctionTest(), "SimpleTest"); + System.out.println("addLeakInFunctionTest_Propagate finished."); + } + + /** + * Performs a simple information leakage analysis, then changes a function called from + * main() such that a new leak is introduced in this function + */ + @Test + public void addLeakInFunctionTest_Rerun() { + System.out.println("Starting addLeakInFunctionTest_Rerun..."); + performTestRerun(ITestHandlerAddLeakInFunctionTest(), "SimpleTest"); + System.out.println("addLeakInFunctionTest_Rerun finished."); + } + + private ITestHandler> ITestHandlerRemoveLeakTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We remove the assignment to "bar" + System.out.println("Patching cfg..."); + SootMethod mainMethod = Scene.v().getMainMethod(); + mainMethod.retrieveActiveBody(); + + List rmList = new ArrayList(); + for (Unit u : mainMethod.getActiveBody().getUnits()) + if (u instanceof JAssignStmt) { + if (((JAssignStmt) u).getLeftOp().toString().equals("var")) + rmList.add(u); + } + for (Unit u : rmList) + mainMethod.getActiveBody().getUnits().remove(u); + System.out.println("Deleted " + rmList.size() + " statements"); + Assert.assertTrue("No statements found to delete", rmList.size() > 0); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) { + String name = l.getContents().getName(); + if (!name.contains("temp$")) + leaks.add(name); + } + + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then removes a leak from + * the code + */ + @Test + public void removeLeakTest_Rerun() { + System.out.println("Starting removeLeakTest_Rerun..."); + performTestRerun(ITestHandlerRemoveLeakTest(), "TestRemoveLeak"); + System.out.println("removeLeakTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a leak from + * the code + */ + @Test + public void removeLeakTest_Update() { + System.out.println("Starting removeLeakTest_Update..."); + performTestUpdate(ITestHandlerRemoveLeakTest(), "TestRemoveLeak"); + System.out.println("removeLeakTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a leak from + * the code + */ + @Test + public void removeLeakTest_Propagate() { + System.out.println("Starting removeLeakTest_Propagate..."); + performTestDirect(ITestHandlerRemoveLeakTest(), "TestRemoveLeak"); + System.out.println("removeLeakTest_Propagate finished."); + } + + private ITestHandler> ITestHandlerRemoveLeakInFunctionTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We remove the assignment to "bar" + System.out.println("Patching cfg..."); + SootMethod fooMethod = Scene.v().getMainClass().getMethodByName("foo"); + Assert.assertNotNull("Method not found", fooMethod); + fooMethod.retrieveActiveBody(); + + List rmList = new ArrayList(); + for (Unit u : fooMethod.getActiveBody().getUnits()) + if (u instanceof JAssignStmt) { + JAssignStmt as = (JAssignStmt) u; + if (as.getLeftOp().toString().equals("res") + && as.getRightOp().toString().equals("args")) { + rmList.add(u); + } + } + for (Unit u : rmList) + fooMethod.getActiveBody().getUnits().remove(u); + System.out.println("Deleted " + rmList.size() + " statements"); + Assert.assertTrue("No statements found to delete", rmList.size() > 0); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) + leaks.add(l.getContents().getName()); + + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then removes a leak within + * a called function from the code + */ + @Test + public void removeLeakInFunctionTest_Rerun() { + System.out.println("Starting removeLeakInFunctionTest_Rerun..."); + performTestRerun(ITestHandlerRemoveLeakInFunctionTest(), "TestRemoveLeakInFunction"); + System.out.println("removeLeakInFunctionTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a leak within + * a called function from the code + */ + @Test + public void removeLeakInFunctionTest_Update() { + System.out.println("Starting removeLeakInFunctionTest_Update..."); + performTestUpdate(ITestHandlerRemoveLeakInFunctionTest(), "TestRemoveLeakInFunction"); + System.out.println("removeLeakInFunctionTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a leak within + * a called function from the code + */ + @Test + public void removeLeakInFunctionTest_Propagate() { + System.out.println("Starting removeLeakInFunctionTest_Propagate..."); + performTestDirect(ITestHandlerRemoveLeakInFunctionTest(), "TestRemoveLeakInFunction"); + System.out.println("removeLeakInFunctionTest_Propagate finished."); + } + + private ITestHandler> ITestHandlerRemoveLeakingCallTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We remove the call to the "foo" function + System.out.println("Patching cfg..."); + SootMethod mainMethod = Scene.v().getMainMethod(); + mainMethod.retrieveActiveBody(); + + for (Unit u : mainMethod.getActiveBody().getUnits()) + System.out.println(u); + + List rmList = new ArrayList(); + for (Unit u : mainMethod.getActiveBody().getUnits()) + if (u instanceof JAssignStmt) { + JAssignStmt as = (JAssignStmt) u; + if (as.getLeftOp().toString().contains("temp") + && as.getRightOp().toString().contains("foo")) { + rmList.add(u); + if (as.getRightOp() instanceof InvokeExpr) { + InvokeExpr inv = (InvokeExpr) as.getRightOp(); + Edge edge = Scene.v().getCallGraph().findEdge(u, inv.getMethod()); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + } + } + } + for (Unit u : rmList) { + mainMethod.getActiveBody().getUnits().remove(u); + System.out.println("Deleting: " + u); + } + System.out.println("Deleted " + rmList.size() + " statements"); + Assert.assertTrue("No statements found to delete", rmList.size() > 0); + + for (Unit u : mainMethod.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) + leaks.add(l.getContents().getName()); + + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then removes a function call + * containing an information leak + */ + @Test + public void removeLeakingCallTest_Rerun() { + System.out.println("Starting removeLeakingCallTest_Rerun..."); + performTestRerun(ITestHandlerRemoveLeakingCallTest(), "TestRemoveLeakingCall"); + System.out.println("removeLeakingCallTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a function call + * containing an information leak + */ + @Test + public void removeLeakingCallTest_Update() { + System.out.println("Starting removeLeakingCallTest_Update..."); + performTestUpdate(ITestHandlerRemoveLeakingCallTest(), "TestRemoveLeakingCall"); + System.out.println("removeLeakingCallTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a function call + * containing an information leak + */ + @Test + public void removeLeakingCallTest_Propagate() { + System.out.println("Starting removeLeakingCallTest_Propagate..."); + performTestDirect(ITestHandlerRemoveLeakingCallTest(), "TestRemoveLeakingCall"); + System.out.println("removeLeakingCallTest_Propagate finished."); + } + + private ITestHandler> ITestHandlerRemoveNoLeakCallTest() { + return new ITestHandler>() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + // Patch the control-flow graph. We remove the call to the "foo" function + System.out.println("Patching cfg..."); + SootMethod mainMethod = Scene.v().getMainMethod(); + mainMethod.retrieveActiveBody(); + + List rmList = new ArrayList(); + for (Unit u : mainMethod.getActiveBody().getUnits()) + if (u instanceof JAssignStmt) { + JAssignStmt as = (JAssignStmt) u; + if (as.getLeftOp().toString().contains("temp") + && as.getRightOp().toString().contains("bar")) { + rmList.add(u); + if (as.getRightOp() instanceof InvokeExpr) { + InvokeExpr inv = (InvokeExpr) as.getRightOp(); + Edge edge = Scene.v().getCallGraph().findEdge(u, inv.getMethod()); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + } + } + } + for (Unit u : mainMethod.getActiveBody().getUnits()) + System.out.println(u); + + for (Unit u : rmList) + mainMethod.getActiveBody().getUnits().remove(u); + System.out.println("Deleted " + rmList.size() + " statements"); + Assert.assertTrue("No statements found to delete", rmList.size() > 0); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableWrapper,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + Unit ret = Scene.v().getMainMethod().getActiveBody().getUnits().getLast(); + Set> results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + List leaks = new ArrayList(); + for (UpdatableWrapper l : results) { + String name = l.getContents().getName(); + if (!name.contains("temp$")) + leaks.add(name); + } + + Assert.assertEquals("Invalid number of information leaks found", 2, results.size()); + Assert.assertEquals("Invalid number of information leaks found", 1, leaks.size()); + Assert.assertTrue("Invalid information leak found", leaks.contains("args")); + // the newly produced leak is a temporary variable + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple information leakage analysis, then removes a function call not + * containing any information leak + */ + @Test + public void removeNoLeakCallTest_Rerun() { + System.out.println("Starting removeNoLeakCallTest_Rerun..."); + performTestRerun(ITestHandlerRemoveNoLeakCallTest(), "TestRemoveNoLeakCall"); + System.out.println("removeNoLeakCallTest_Rerun finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a function call not + * containing any information leak + */ + @Test + public void removeNoLeakCallTest_Update() { + System.out.println("Starting removeNoLeakCallTest_Update..."); + performTestUpdate(ITestHandlerRemoveNoLeakCallTest(), "TestRemoveNoLeakCall"); + System.out.println("removeNoLeakCallTest_Update finished."); + } + + /** + * Performs a simple information leakage analysis, then removes a function call not + * containing any information leak + */ + @Test + public void removeNoLeakCallTest_Propagate() { + System.out.println("Starting removeNoLeakCallTest_Propagate..."); + performTestDirect(ITestHandlerRemoveNoLeakCallTest(), "TestRemoveNoLeakCall"); + System.out.println("removeNoLeakCallTest_Propagate finished."); + } + +} diff --git a/src/soot/jimple/interproc/ifds/test/IFDSTestPDFsam.java b/src/soot/jimple/interproc/ifds/test/IFDSTestPDFsam.java new file mode 100644 index 0000000..f6c70c5 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/IFDSTestPDFsam.java @@ -0,0 +1,544 @@ +package soot.jimple.interproc.ifds.test; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +import soot.G; +import soot.MethodOrMethodContext; +import soot.PackManager; +import soot.Scene; +import soot.SceneTransformer; +import soot.Singletons; +import soot.SootClass; +import soot.SootMethod; +import soot.SootResolver; +import soot.Transform; +import soot.Unit; +import soot.JastAddJ.CompilationUnit; +import soot.JastAddJ.Program; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.problems.IFDSReachingDefinitions; +import soot.jimple.interproc.ifds.problems.UpdatableReachingDefinition; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.ifds.utils.Utils; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.callgraph.ReachableMethods; + +public class IFDSTestPDFsam { + + private abstract class DynamicTestHandler implements ITestHandler { + + private final boolean PATCH_LIBRARIES = false; + + private final String originalFile; + private final String patchedFile; + private final String targetFile; + + public DynamicTestHandler(String originalFile, String patchedFile, String targetFile) { + this.originalFile = originalFile; + this.patchedFile = patchedFile; + this.targetFile = targetFile; + } + + @Override + public void initialize() { + try { + String curDir = System.getProperty("user.dir"); + Utils.copyFile(curDir + "/test/" + originalFile, curDir + "/test/" + targetFile); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } + } + + @Override + public void patchGraph(int phase) { + final boolean AGGRESSIVE_CHECKS = true; + + // Get the original call graph size before we change anything + System.out.println("Original call graph has " + Scene.v().getCallGraph().size() + " edges"); + + try { + String curDir = System.getProperty("user.dir"); + Utils.copyFile(curDir + "/test/" + patchedFile, curDir + "/test/" + targetFile); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } + + // Mark all existing compilation units as unresolved + Program program = SootResolver.v().getProgram(); + for (CompilationUnit cu : program.getCompilationUnits()) + program.releaseCompilationUnitForFile(cu.pathName()); + + // Load a new version of the source file into Soot + + // Release some stale scene information + try { + Field vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_callgraph_VirtualCalls"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_DumbPointerAnalysis"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_FullObjectSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_EntryPoints"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Scene.class.getDeclaredField("doneResolving"); + vcField.setAccessible(true); + vcField.set(Scene.v(), false); + + // Spark data + Method methClear = HashMap.class.getMethod("clear"); + vcField = G.class.getDeclaredField("Parm_pairToElement"); + vcField.setAccessible(true); + methClear.invoke(vcField.get(G.v()), (Object[]) null); + + vcField = G.class.getDeclaredField("MethodPAG_methodToPag"); + vcField.setAccessible(true); + methClear.invoke(vcField.get(G.v()), (Object[]) null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_AllSharedListNodes"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_AllSharedHybridNodes"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldTagger"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_pag_ArrayElement"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldReadTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldWriteTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_EmptyPointsToSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_SparkTransformer"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_FullObjectSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = G.class.getDeclaredField("newSetFactory"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = G.class.getDeclaredField("oldSetFactory"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + } catch (NoSuchFieldException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (SecurityException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + Scene.v().setDefaultThrowAnalysis(null); + Scene.v().releaseFastHierarchy(); + Scene.v().releaseReachableMethods(); + Scene.v().releaseActiveHierarchy(); + Scene.v().releasePointsToAnalysis(); + Scene.v().releaseCallGraph(); + Scene.v().setEntryPoints(null); + + // Force a resolve of all soot classes in the scene. We + // need to copy the list to avoid ConcurrentModificationExceptions. + Set ac = new HashSet(); + Set lc = new HashSet(); + Set allClasses = new HashSet(); + for (SootClass sc : Scene.v().getApplicationClasses()) { + System.out.println("Marking class for reload: " + sc.getName()); + ac.add(sc); + allClasses.add(sc); + } + if (PATCH_LIBRARIES) + for (SootClass sc : Scene.v().getLibraryClasses()) { + lc.add(sc); + allClasses.add(sc); + } + if (PATCH_LIBRARIES) + for (SootClass sc : Scene.v().getClasses()) + allClasses.add(sc); + for (SootClass sc : allClasses) { + // Remove the class from the scene so that it can be + // added anew. This helps fixing Soot's internal caches. + Scene.v().removeClass(sc); + assert !Scene.v().containsClass(sc.getName()); + + // Let the class think it has not been resolved yet. This + // is important as resolving is aborted if the current + // resolving level is greater than or equal to the requested + // one. + sc.setResolvingLevel(SootClass.DANGLING); + } + + if (PATCH_LIBRARIES) { + // Make sure that we load all class dependencies of the new version + Scene.v().loadNecessaryClasses(); + } + + // Reload all application classes + List newClasses = new ArrayList(); + for (SootClass sc : allClasses) { + // Force a new class resolving + Scene.v().forceResolve(sc.getName(), SootClass.SIGNATURES); + SootClass scNew = Scene.v().forceResolve(sc.getName(), SootClass.BODIES); + assert scNew != null; + if (ac.contains(sc)) + scNew.setApplicationClass(); + if (lc.contains(sc)) + scNew.setLibraryClass(); + assert !AGGRESSIVE_CHECKS || scNew != ac; + assert scNew.isInScene(); + assert Scene.v().getSootClass(sc.getName()) == scNew; + newClasses.add(scNew); + + for (SootMethod sm : scNew.getMethods()) + if (sm.isConcrete()) + sm.retrieveActiveBody(); + } + + // Fix cached main class - this will automatically fix the main method + SootClass oldMainClass = Scene.v().getMainClass(); + SootClass mainClass = Scene.v().getSootClass(oldMainClass.getName()); + Scene.v().setMainClass(mainClass); + System.out.println("Old main class: " + oldMainClass + " - new: " + mainClass); + assert !AGGRESSIVE_CHECKS || !oldMainClass.isInScene(); + + // Patch the entry points + long timeBeforeEP = System.nanoTime(); + Scene.v().getEntryPoints(); + System.out.println("Updating entry points took " + + ((System.nanoTime() - timeBeforeEP) / 1E9) + " seconds"); + + // Recreate the exception throw analysis + Scene.v().getDefaultThrowAnalysis(); + + // Update the call graph + long timeBeforeCG = System.nanoTime(); + PackManager.v().getPack("cg").apply(); + int cgSize = Scene.v().getCallGraph().size(); + System.out.println("Updating callgraph took " + + ((System.nanoTime() - timeBeforeCG) / 1E9) + " seconds, " + + "callgraph now has " + cgSize + " edges."); + + // Invalidate the list of reachable methods. It will automatically be recreated + // on the next call to "getReachableMethods". + long timeBeforeRM = System.nanoTime(); + Scene.v().getReachableMethods(); + System.out.println("Updating reachable methods took " + + ((System.nanoTime() - timeBeforeRM) / 1E9) + " seconds"); + + // Update the class hierarchy + Scene.v().getActiveHierarchy(); + + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(Scene.v().getCallGraph(), eps.iterator()); + reachableMethods.update(); + + // Fix the resolving state for the old classes. Otherwise, access to the + // fields and methods will be blocked and no diff can be performed. + for (SootClass sc : allClasses) + sc.setResolvingLevel(SootClass.BODIES); + } + + @Override + public void initApplicationClasses() { + } + + @Override + public int getPhaseCount() { + return 1; + } + }; + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method does not create indices for dynamic updates. Instead, updates are + * just propagated along the edges until a fix point is reached. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestDirect(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + Scene.v().getSootClass(className).setApplicationClass(); + + handler.initApplicationClasses(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + long nanoBeforeCFG = System.nanoTime(); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG created in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long beforeSolver = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + ((System.nanoTime() - beforeSolver) / 1E9) + "seconds."); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 1E9 + " seconds."); + + handler.initApplicationClasses(); + Scene.v().getSootClass(className).setApplicationClass(); + + nanoBeforeCFG = System.nanoTime(); + icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG updated in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + long nanoBeforeUpdate = System.nanoTime(); + solver.update(icfg); + System.out.println("IDE results updated in " + (System.nanoTime() - nanoBeforeUpdate) / 1E9 + " seconds."); + + handler.performExtendedTest(icfg, solver, i); +// solver.dumpResults(className + "_Propagate.csv"); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String os = System.getProperty("os.name"); + String cpSep = ":"; + if (os.contains("Windows")) + cpSep = ";"; + + String udir = System.getProperty("user.dir"); + String sootcp = udir + File.separator + "test/pdfsam.jar" + cpSep +// + udir + File.separator + "hamcrest-core-1.3.jar" + cpSep +// + udir + File.separator + "bin" + cpSep +// + udir + File.separator + "javaws.jar" + cpSep +// + udir + File.separator + "j3dcore.jar" + cpSep +// + udir + File.separator + "j3dutils.jar" + cpSep +// + udir + File.separator + "vecmath.jar" + cpSep +// + udir + File.separator + "AppleJavaExtensions.jar" + cpSep +// + udir + File.separator + "jmf.jar" + cpSep +// + udir + File.separator + "sunflow.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/rt.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/jce.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\rt.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\jce.jar"; + System.out.println("Soot classpath: " + sootcp); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", +// "-pp", + "-cp", sootcp, + "-allow-phantom-refs", +// "-no-bodies-for-excluded", + "-exclude", "java", + "-exclude", "javax", + "-output-format", "none", + "-p", "jb", "use-original-names:true", + "-p", "cg.spark", "on", + "-app", className } ); + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method runs the analysis once, then modifies the program and afterwards + * dynamically updates the analysis results. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestRerun(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + Scene.v().getSootClass(className).setApplicationClass(); + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + long nanoBeforeCFG = System.nanoTime(); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG created in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long beforeSolver = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + ((System.nanoTime() - beforeSolver) / 1E9) + "seconds."); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 1E9 + " seconds."); + + Scene.v().getSootClass(className).setApplicationClass(); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem2 = + new IFDSReachingDefinitions(icfg = new JimpleBasedInterproceduralCFG()); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver2 = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem2); + + solver2.solve(false); + if (handler != null) + handler.performExtendedTest(icfg, solver2, i); + +// solver2.dumpResults(className + "_Rerun.csv"); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String os = System.getProperty("os.name"); + String cpSep = ":"; + if (os.contains("Windows")) + cpSep = ";"; + + String udir = System.getProperty("user.dir"); + String sootcp = udir + File.separator + "test/pdfsam.jar" + cpSep +// + udir + File.separator + "hamcrest-core-1.3.jar" + cpSep +// + udir + File.separator + "bin" + cpSep +// + udir + File.separator + "javaws.jar" + cpSep +// + udir + File.separator + "j3dcore.jar" + cpSep +// + udir + File.separator + "j3dutils.jar" + cpSep +// + udir + File.separator + "vecmath.jar" + cpSep +// + udir + File.separator + "AppleJavaExtensions.jar" + cpSep +// + udir + File.separator + "jmf.jar" + cpSep +// + udir + File.separator + "sunflow.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/rt.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/jce.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\rt.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\jce.jar"; + System.out.println("Soot classpath: " + sootcp); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", +// "-pp", + "-cp", sootcp, + "-allow-phantom-refs", +// "-no-bodies-for-excluded", + "-exclude", "java", + "-exclude", "javax", + "-output-format", "none", + "-p", "jb", "use-original-names:true", + "-p", "cg.spark", "on", + "-app", className } ); + } + + private ITestHandler ITestHandlerNewVersion() { + return new DynamicTestHandler("pdfsam-2.2.1.jar", "pdfsam-2.2.2.jar", "pdfsam.jar") { + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + // + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + // + } + }; + } + + /** + * Performs a simple analysis and then replaces the PDFsam application with a + * newer version + */ + @Test + public void newVersionSH_Rerun() { + System.out.println("Starting newVersionSH_Rerun..."); + performTestRerun(ITestHandlerNewVersion(), "org.pdfsam.guiclient.GuiClient"); + System.out.println("newVersionSH_Rerun finished."); + } + + /** + * Performs a simple analysis and then replaces the PDFsam application with a + * newer version + */ + @Test + public void newVersionSH_Propagate() { + System.out.println("Starting newVersionSH_Propagate..."); + performTestDirect(ITestHandlerNewVersion(), "org.pdfsam.guiclient.GuiClient"); + System.out.println("newVersionSH_Propagate finished."); + } + +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitions.java b/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitions.java new file mode 100644 index 0000000..5133e9f --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitions.java @@ -0,0 +1,2241 @@ +package soot.jimple.interproc.ifds.test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +import soot.Local; +import soot.PackManager; +import soot.RefType; +import soot.Scene; +import soot.SceneTransformer; +import soot.SootClass; +import soot.SootMethod; +import soot.Transform; +import soot.Unit; +import soot.Value; +import soot.jimple.AssignStmt; +import soot.jimple.DefinitionStmt; +import soot.jimple.InvokeStmt; +import soot.jimple.Jimple; +import soot.jimple.NewExpr; +import soot.jimple.NullConstant; +import soot.jimple.ReturnStmt; +import soot.jimple.StringConstant; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.problems.IFDSReachingDefinitions; +import soot.jimple.interproc.ifds.problems.UpdatableReachingDefinition; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; + +public class IFDSTestReachingDefinitions { + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method does not create indices for dynamic updates. Instead, updates are + * just propagated along the edges until a fix point is reached. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestDirect(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + // Make sure to load the bodies of all methods in the old version so + // that we can diff later + for (SootClass sc : Scene.v().getApplicationClasses()) + for (SootMethod sm : sc.getMethods()) + sm.retrieveActiveBody(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long nanoBeforeSolve = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + (System.nanoTime() - nanoBeforeSolve) / 10E9 + " seconds."); + + if (className.contains("junit")) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + UpdatableWrapper ret = icfg.wrapWeak(meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast())); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + } + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 10E9 + " seconds."); + + solver.update(icfg = new JimpleBasedInterproceduralCFG()); + handler.performExtendedTest(icfg, solver, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java", + className } ); + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method runs the analysis once, then modifies the program and afterwards + * dynamically updates the analysis results. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestRerun(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + // Make sure to load the bodies of all methods in the old version so + // that we can diff later + for (SootClass sc : Scene.v().getApplicationClasses()) + for (SootMethod sm : sc.getMethods()) + sm.retrieveActiveBody(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long nanoBeforeSolve = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + (System.nanoTime() - nanoBeforeSolve) / 10E9 + " seconds."); + + if (className.contains("junit")) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + UpdatableWrapper ret = icfg.wrapWeak(meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast())); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + } + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 10E9 + " seconds."); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem2 = + new IFDSReachingDefinitions(icfg = new JimpleBasedInterproceduralCFG()); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver2 = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem2); + + solver2.solve(false); + if (handler != null) + handler.performExtendedTest(icfg, solver2, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + try { + assert Class.forName(className) != null; + } catch (ClassNotFoundException e) { + Assert.fail(e.getMessage()); + } + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java", + className } ); + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method runs the analysis once, then modifies the program and afterwards + * dynamically updates the analysis results using strongly connected components + * (Yilgrim's method). + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestUpdate(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + // Make sure to load the bodies of all methods in the old version so + // that we can diff later + for (SootClass sc : Scene.v().getApplicationClasses()) + for (SootMethod sm : sc.getMethods()) + sm.retrieveActiveBody(); + + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long nanoBeforeSolve = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(true); + System.out.println("Solver done in " + (System.nanoTime() - nanoBeforeSolve) / 10E9 + " seconds."); + + if (className.contains("junit")) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + UpdatableWrapper ret = icfg.wrapWeak(meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast())); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + } + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 10E9 + " seconds."); + + solver.update(icfg = new JimpleBasedInterproceduralCFG()); + handler.performExtendedTest(icfg, solver, i); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String udir = System.getProperty("user.dir"); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", + "-pp", + "-cp", "junit-4.10.jar", + "-no-bodies-for-excluded", + "-exclude", "java", + className } ); + } + + protected void checkInitialLeaks(Set results) { + boolean found = false; + for (UpdatableReachingDefinition p : results) { + if (p.getContents().getO1() instanceof Local && ((Local) p.getContents().getO1()).getName().equals("$r2")) + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().equals(("$r2 = new org.junit.runner.JUnitCore"))) { + found = true; + break; + } + if (found) break; + } + Assert.assertTrue(found); + } + + private ITestHandler ITestHandlerSimpleTest() { + return new ITestHandler() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(results); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Rerun() { + System.out.println("Starting simpleTestJU_Rerun..."); + performTestRerun(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTestJU_Rerun finished."); + } + + /** + * Performs a simple analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Propagate() { + System.out.println("Starting simpleTestJU_Propagate..."); + performTestDirect(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTestJU_Propagate finished."); + } + + /** + * Performs a simple analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Update() { + System.out.println("Starting simpleTestJU_Update..."); + performTestUpdate(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTestJU_Update finished."); + } + + private ITestHandler ITestHandlerAddVarTest() { + return new ITestHandler() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("---OLD RESULTS"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Local newLocal = Jimple.v().newLocal("foo", RefType.v("java.lang.String")); + meth.getActiveBody().getLocals().add(newLocal); + + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + + AssignStmt assignStmt = Jimple.v().newAssignStmt(newLocal, StringConstant.v("Hello World")); + meth.getActiveBody().getUnits().insertBefore(assignStmt, ret); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(results); + + System.out.println("---NEW RESULTS"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + boolean found = false; + for (UpdatableReachingDefinition p : results) { + if (p.getContents().getO1() instanceof Local + && ((Local) p.getContents().getO1()).getName().equals("foo")) { + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().equals(("foo = \"Hello World\""))) { + found = true; + break; + } + } + if (found) break; + } + Assert.assertTrue(found); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a local with an assignment + */ + @Test + public void addLocalJU_Rerun() { + System.out.println("Starting addLocalJU_Rerun..."); + performTestRerun(ITestHandlerAddVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("addLocalJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then adds a local with an assignment + */ + @Test + public void addLocalJU_Propagate() { + System.out.println("Starting addLocalJU_Propagate..."); + performTestDirect(ITestHandlerAddVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("addLocalJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then adds a local with an assignment + */ + @Test + public void addLocalJU_Update() { + System.out.println("Starting addLocalJU_Propagate..."); + performTestUpdate(ITestHandlerAddVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("addLocalJU_Propagate finished."); + } + + private ITestHandler ITestHandlerRedefineVarTest() { + return new ITestHandler() { + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + Local listener = null; + for (Local l : meth.getActiveBody().getLocals()) + if (l.getName().equals("$r27")) { + listener = l; + break; + } + + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + + NewExpr newExpr = Jimple.v().newNewExpr(RefType.v("org.junit.runner.notification.RunListener")); + AssignStmt initStmt = Jimple.v().newAssignStmt(listener, newExpr); + meth.getActiveBody().getUnits().insertBefore(initStmt, ret); + + InvokeStmt invokeInitStmt = Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(listener, + RefType.v("org.junit.runner.notification.RunListener").getSootClass().getMethodByName("").makeRef())); + meth.getActiveBody().getUnits().insertAfter(invokeInitStmt, initStmt); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + boolean found = false; + for (UpdatableReachingDefinition p : results) { + if (p.getContents().getO1() instanceof Local + && ((Local) p.getContents().getO1()).getName().equals("$r27")) { + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().equals(("$r27 = new org.junit.runner.notification.RunListener"))) { + found = true; + break; + } + } + if (found) break; + } + Assert.assertTrue(found); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then overwrites a local variable + */ + @Test + public void redefineVarJU_Rerun() { + System.out.println("Starting redefineVarJU_Rerun..."); + performTestRerun(ITestHandlerRedefineVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineVarJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then overwrites a local variable + */ + @Test + public void redefineVarJU_Propagate() { + System.out.println("Starting redefineVarJU_Propagate..."); + performTestDirect(ITestHandlerRedefineVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineVarJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then overwrites a local variable + */ + @Test + public void redefineVarJU_Update() { + System.out.println("Starting redefineVarJU_Update..."); + performTestUpdate(ITestHandlerRedefineVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineVarJU_Update finished."); + } + + private ITestHandler ITestHandlerRemoveStmtTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u.toString().equals("virtualinvoke r0.(r28)")) { + meth.getActiveBody().getUnits().remove(u); + + Edge edge = Scene.v().getCallGraph().findEdge(u, Scene.v().getMethod + ("")); + Scene.v().getCallGraph().removeEdge(edge); + + found = true; + break; + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + // The next line does not hold when running with Java libraries as + // a whole chunk of code is no longer reachable with removes many + // candidates from the points-to analysis, e.g. for the interator + // invocation in r34. Therefore, we only check a weaker condition. + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (po.getContents().equals(pr.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes a non-assignment statement + */ + @Test + public void removeStmtJU_Rerun() { + System.out.println("Starting removeStmtJU_Rerun..."); + performTestRerun(ITestHandlerRemoveStmtTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then removes a non-assignment statement + */ + @Test + public void removeStmtJU_Propagate() { + System.out.println("Starting removeStmtJU_Propagate..."); + performTestDirect(ITestHandlerRemoveStmtTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then removes a non-assignment statement + */ + @Test + public void removeStmtJU_Update() { + System.out.println("Starting removeStmtJU_Update..."); + performTestUpdate(ITestHandlerRemoveStmtTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtJU_Update finished."); + } + + private ITestHandler ITestHandlerRemoveAssignmentTest() { + return new ITestHandler() { + + Set original = new HashSet(); + Set originalHN = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + System.out.println("----OLD RESULTS"); + for (UpdatableReachingDefinition p : results) { + original.add(p); + System.out.println(p); + } + + Unit hasNext = null; + for (Unit u : meth.getActiveBody().getUnits()) + if (u.toString().contains("boolean hasNext()")) { + hasNext = u; + break; + } + Assert.assertNotNull(hasNext); + + System.out.println("----OLD RESULTS HN"); + solver.ifdsResultsAt(icfg.wrapWeak(hasNext)); + for (UpdatableReachingDefinition p : results) { + originalHN.add(p); + System.out.println(p); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + Value leftSide = null; + Unit oldAssignment = null; + Unit oldInit = null; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof AssignStmt) { + if (u.toString().equals("$r27 = new org.junit.internal.TextListener")) { + leftSide = ((AssignStmt) u).getLeftOp(); + oldAssignment = u; + } + } + else if (u instanceof InvokeStmt && u.toString().contains + ("specialinvoke $r27.(org.junit.internal.JUnitSystem)>")) + oldInit = u; + Assert.assertNotNull(oldAssignment); + Assert.assertNotNull(oldInit); + Assert.assertNotNull(leftSide); + + AssignStmt assignStmt = Jimple.v().newAssignStmt(leftSide, NullConstant.v()); + meth.getActiveBody().getUnits().insertBefore(assignStmt, oldAssignment); + meth.getActiveBody().getUnits().remove(oldAssignment); + meth.getActiveBody().getUnits().remove(oldInit); + + Edge edge = Scene.v().getCallGraph().findEdge(oldInit, Scene.v().getMethod + ("(org.junit.internal.JUnitSystem)>")); + Assert.assertNotNull(edge); + Assert.assertTrue(Scene.v().getCallGraph().removeEdge(edge)); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + Unit hasNext = null; + for (Unit u : meth.getActiveBody().getUnits()) + if (u.toString().contains("boolean hasNext()")) { + hasNext = u; + break; + } + Assert.assertNotNull(hasNext); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(hasNext)); + + System.out.println("----NEW RESULTS HN"); + for (UpdatableReachingDefinition pr : results) + System.out.println(pr); + Assert.assertEquals(originalHN.size(), results.size()); + + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----NEW RESULTS"); + for (UpdatableReachingDefinition pr : results) + System.out.println(pr); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) { + System.out.println("FOOO"); + Assert.assertEquals("$r27", pr.getContents().getO1().toString()); + Assert.assertEquals("[$r27 = null]", pr.getContents().getO2().toString()); + } + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes an assignment statement + */ + @Test + public void removeAssignmentJU_Rerun() { + System.out.println("Starting removeAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerRemoveAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeAssignmentJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then removes an assignment statement + */ + @Test + public void removeAssignmentJU_Propagate() { + System.out.println("Starting removeAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerRemoveAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeAssignmentJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then removes an assignment statement + */ + @Test + public void removeAssignmentJU_Update() { + System.out.println("Starting removeAssignmentJU_Update..."); + performTestUpdate(ITestHandlerRemoveAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeAssignmentJU_Update finished."); + } + + private ITestHandler ITestHandlerAddCallNoAssignmentTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + + Local r0 = null; + Local r1 = null; + Local r2 = null; + for (Local l : meth.getActiveBody().getLocals()) { + if (l.getName().equals("r0")) + r0 = l; + else if (l.getName().equals("r1")) + r1 = l; + else if (l.getName().equals("$r2")) + r2 = l; + } + Assert.assertNotNull(r0); + Assert.assertNotNull(r1); + Assert.assertNotNull(r2); + + SootMethod runMainMethod = meth.getDeclaringClass().getMethodByName("runMain"); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof AssignStmt) + if (u.toString().equals("r3 = virtualinvoke $r2.(r0, r1)")) { + found = true; + + InvokeStmt invStmt = Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr + (r2, runMainMethod.makeRef(), r0, r1)); + meth.getActiveBody().getUnits().insertAfter(invStmt, u); + + CallGraph cg = Scene.v().getCallGraph(); + Edge edge = new Edge(meth, invStmt, runMainMethod); + cg.addEdge(edge); + + break; + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a call without creating a new assignment + */ + @Test + public void addCallNoAssignmentJU_Rerun() { + System.out.println("Starting addCallNoAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerAddCallNoAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoAssignmentJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then adds a call without creating a new assignment + */ + @Test + public void addCallNoAssignmentJU_Propagate() { + System.out.println("Starting addCallNoAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerAddCallNoAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoAssignmentJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then adds a call without creating a new assignment + */ + @Test + public void addCallNoAssignmentJU_Update() { + System.out.println("Starting addCallNoAssignmentJU_Update..."); + performTestUpdate(ITestHandlerAddCallNoAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoAssignmentJU_Update finished."); + } + + private ITestHandler ITestHandlerAddCallAssignmentTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + System.out.println("Original size: " + results.size()); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + SootMethod getVersionMethod = meth.getDeclaringClass().getMethodByName("getVersion"); + + Local newLocal = Jimple.v().newLocal("ver", RefType.v("java.lang.String")); + meth.getActiveBody().getLocals().add(newLocal); + Local r2 = null; + for (Local l : meth.getActiveBody().getLocals()) + if (l.getName().equals("$r2")) { + r2 = l; + break; + } + Assert.assertNotNull(r2); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof AssignStmt) + if (u.toString().equals("r3 = virtualinvoke $r2.(r0, r1)")) { + found = true; + + AssignStmt assignStmt = Jimple.v().newAssignStmt(newLocal, + Jimple.v().newVirtualInvokeExpr(r2, getVersionMethod.makeRef())); + meth.getActiveBody().getUnits().insertAfter(assignStmt, u); + + CallGraph cg = Scene.v().getCallGraph(); + Edge edge = new Edge(meth, assignStmt, getVersionMethod); + cg.addEdge(edge); + + break; + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + for (UpdatableReachingDefinition p : original) + if (!results.contains(p)) + System.out.println("Missing: " + p); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a call inside a new assignment + */ + @Test + public void addCallAssignmentJU_Rerun() { + System.out.println("Starting addCallAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerAddCallAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallAssignmentJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then adds a call inside a new assignment + */ + @Test + public void addCallAssignmentJU_Propagate() { + System.out.println("Starting addCallAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerAddCallAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallAssignmentJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then adds a call inside a new assignment + */ + @Test + public void addCallAssignmentJU_Update() { + System.out.println("Starting addCallAssignmentJU_Update..."); + performTestUpdate(ITestHandlerAddCallAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallAssignmentJU_Update finished."); + } + + private ITestHandler ITestHandlerRemoveStmtFromLoopTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + System.out.println("---OLD RESULTS"); + for (UpdatableReachingDefinition p : results) { + original.add(p); + System.out.println(p); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + Local r18 = null; + for (Local l : meth.getActiveBody().getLocals()) + if (l.getName().equals("$r18")) { + r18 = l; + break; + } + + Local newLocal = Jimple.v().newLocal("foo", RefType.v("java.lang.Class")); + meth.getActiveBody().getLocals().add(newLocal); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u.toString().equals("interfaceinvoke r4.($r18)")) { + AssignStmt assignStmt = Jimple.v().newAssignStmt(newLocal, r18); + meth.getActiveBody().getUnits().insertBefore(assignStmt, u); + + Edge edge = Scene.v().getCallGraph().findEdge(u, Scene.v().getMethod + ("")); + Scene.v().getCallGraph().removeEdge(edge); + meth.getActiveBody().getUnits().remove(u); + + found = true; + break; + } + Assert.assertTrue(found); + + // Print the last value of r18 to the console + SootMethod getNameMethod = Scene.v().getSootClass("java.lang.Class").getMethodByName("getName"); + Assert.assertNotNull(getNameMethod); + InvokeStmt getNameStmt = Jimple.v().newInvokeStmt + (Jimple.v().newVirtualInvokeExpr(r18, getNameMethod.makeRef())); + found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof ReturnStmt) { + meth.getActiveBody().getUnits().insertBefore(getNameStmt, + meth.getActiveBody().getUnits().getPredOf(u)); + found = true; + break; + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("---NEW RESULTS"); + for (UpdatableReachingDefinition p : results) + System.out.println(p); + + for (UpdatableReachingDefinition po : original) + { + boolean found = false; + for (UpdatableReachingDefinition pr : results) + if (po.getContents().getO1().toString().equals(pr.getContents().getO1().toString())) + if (po.getContents().getO2().toString().equals(pr.getContents().getO2().toString())) + found = true; + + if (!found) + System.out.println("Missing: " + po); + } + + Assert.assertEquals(original.size() + 1, results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) { + Assert.assertEquals("foo", pr.getContents().getO1().toString()); + Assert.assertEquals("[foo = $r18]", pr.getContents().getO2().toString()); + } + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes a call from a loop and adds an + * assignment to a new variable instead + */ + @Test + public void removeStmtFromLoopJU_Rerun() { + System.out.println("Starting removeStmtFromLoopJU_Rerun..."); + performTestRerun(ITestHandlerRemoveStmtFromLoopTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtFromLoopJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then removes a call from a loop and adds an + * assignment to a new variable instead + */ + @Test + public void removeStmtFromLoopJU_Propagate() { + System.out.println("Starting removeStmtFromLoopJU_Propagate..."); + performTestDirect(ITestHandlerRemoveStmtFromLoopTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtFromLoopJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then removes a call from a loop and adds an + * assignment to a new variable instead + */ + @Test + public void removeStmtFromLoopJU_Update() { + System.out.println("Starting removeStmtFromLoopJU_Update..."); + performTestUpdate(ITestHandlerRemoveStmtFromLoopTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtFromLoopJU_Update finished."); + } + + private ITestHandler ITestHandlerRedefineReturnTest() { + return new ITestHandler() { + + Set original = new HashSet(); + Set originalRet = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nORIGINAL RESULTS\n----------------"); + for (UpdatableReachingDefinition p : results) { + original.add(p); + System.out.println(p); + } + + meth = Scene.v().getMainClass().getMethodByName("runMain"); + ret = meth.getActiveBody().getUnits().getLast(); + results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nORIGINAL RESULTS AT RESULTS\n----------------"); + for (UpdatableReachingDefinition p : results) { + originalRet.add(p); + System.out.println(p); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + + Local r32 = null; + for (Local l : meth.getActiveBody().getLocals()) + if (l.getName().equals("r32")) { + r32 = l; + break; + } + Assert.assertNotNull(r32); + + AssignStmt assignStmt = Jimple.v().newAssignStmt(r32, NullConstant.v()); + ReturnStmt ret = null; + for (Unit u : meth.getActiveBody().getUnits()) { + if (u instanceof ReturnStmt) { + u.redirectJumpsToThisTo(assignStmt); + meth.getActiveBody().getUnits().insertBefore(assignStmt, u); + ret = (ReturnStmt) u; + break; + } + } + Assert.assertNotNull(ret); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nNEW RESULTS AT RETURN\n----------------"); + for (UpdatableReachingDefinition p : results) + System.out.println(p); + + Assert.assertEquals(originalRet.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : originalRet) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) { + Assert.assertEquals("r32", pr.getContents().getO1().toString()); + Assert.assertEquals("[r32 = null]", pr.getContents().getO2().toString()); + } + } + + meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nNEW RESULTS\n----------------"); + for (UpdatableReachingDefinition p : results) + System.out.println(p); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) { + Assert.assertEquals("r3", pr.getContents().getO1().toString()); + Assert.assertEquals("[r32 = null]", pr.getContents().getO2().toString()); + } + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then overwrites the return value of a called + * function and checks whether it is returned correctly + */ + @Test + public void redefineReturnJU_Rerun() { + System.out.println("Starting redefineReturnJU_Rerun..."); + performTestRerun(ITestHandlerRedefineReturnTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineReturnJU_Rerun finished."); + } + + /** + * Performs a simple analysis, then overwrites the return value of a called + * function and checks whether it is returned correctly + */ + @Test + public void redefineReturnJU_Propagate() { + System.out.println("Starting redefineReturnJU_Propagate..."); + performTestDirect(ITestHandlerRedefineReturnTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineReturnJU_Propagate finished."); + } + + /** + * Performs a simple analysis, then overwrites the return value of a called + * function and checks whether it is returned correctly + */ + @Test + public void redefineReturnJU_Update() { + System.out.println("Starting redefineReturnJU_Update..."); + performTestUpdate(ITestHandlerRedefineReturnTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineReturnJU_Update finished."); + } + + private ITestHandler ITestHandlerExitTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) + original.add(p); + + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + + System.out.println("----------------\nOld results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("a"); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof InvokeStmt) { + InvokeStmt inv = (InvokeStmt) u; + if (inv.getInvokeExpr().toString().contains("println")) { + meth.getActiveBody().getUnits().remove(u); + + Edge edge = Scene.v().getCallGraph().findEdge + (u, Scene.v().getMainClass().getMethodByName("println")); + Scene.v().getCallGraph().removeEdge(edge); + + found = true; + break; + } + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nNew results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void exitTestJU_Rerun() { + System.out.println("Starting exitTestJU_Rerun..."); + performTestRerun(ITestHandlerExitTest(), "ExitTest"); + System.out.println("exitTestJU_Rerun finished."); + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void exitTestJU_Propagate() { + System.out.println("Starting exitTestJU_Propagate..."); + performTestDirect(ITestHandlerExitTest(), "ExitTest"); + System.out.println("exitTestJU_Propagate finished."); + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void exitTestJU_Update() { + System.out.println("Starting exitTestJU_Update..."); + performTestUpdate(ITestHandlerExitTest(), "ExitTest"); + System.out.println("exitTestJU_Update finished."); + } + + private ITestHandler ITestHandlerFuncTypeTest() { + return new ITestHandler() { + + Set original = new HashSet(); + UpdatableReachingDefinition valueDef = null; + int originalCount = 0; + + @Override + public void initialize() { + } + + private UpdatableReachingDefinition findDefinition + (Set results, String varName) { + for (UpdatableReachingDefinition p : results) + if (p.getValue().toString().equals(varName)) + for (DefinitionStmt def : p.getDefinitions()) { + String newVar = def.getRightOp().toString(); + if (newVar.equals(varName)) // make sure not to run in loops + return null; + UpdatableReachingDefinition urd = findDefinition(results, newVar); + return urd == null ? p : urd; + } + return null; + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + valueDef = findDefinition(results, "o"); + Assert.assertNotNull(valueDef); + Assert.assertEquals(1, valueDef.getDefinitions().size()); + Assert.assertTrue(valueDef.getDefinitions().iterator().next().toString().contains("new java.lang.Integer")); + + System.out.println("----------------\nOld results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + originalCount = results.size(); + + for (Unit u : meth.getActiveBody().getUnits()) { + if (u.toString().equals("o = temp$1")) { + results = solver.ifdsResultsAt(icfg.wrapWeak(u)); + System.out.println("----------------\nOld results at RETURN SITE:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + break; + } + } + + meth = Scene.v().getMainClass().getMethodByName("foo"); + ret = meth.getActiveBody().getUnits().getLast(); + results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nOld results at RETURN:\n------------------"); + for (UpdatableReachingDefinition urd : results) { + System.out.println(urd); + original.add(urd); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("foo"); + + Unit returnStmt = null; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof ReturnStmt) { + returnStmt = u; + break; + } + Assert.assertNotNull(returnStmt); + + Local strLocal = Jimple.v().newLocal("x", RefType.v("java.lang.String")); + meth.getActiveBody().getLocals().add(strLocal); + + NewExpr newExpr = Jimple.v().newNewExpr(RefType.v("java.lang.String")); + AssignStmt initStmt = Jimple.v().newAssignStmt(strLocal, newExpr); + meth.getActiveBody().getUnits().insertBefore(initStmt, returnStmt); + + List args = Collections.singletonList(StringConstant.v("Hello World")); + + InvokeStmt invokeInitStmt = Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(strLocal, + RefType.v("java.lang.String").getSootClass().getMethod("void (java.lang.String)").makeRef(), args)); + meth.getActiveBody().getUnits().insertAfter(invokeInitStmt, initStmt); + + ReturnStmt ret = Jimple.v().newReturnStmt(strLocal); + meth.getActiveBody().getUnits().insertBefore(ret, returnStmt); + meth.getActiveBody().getUnits().remove(returnStmt); + + System.out.println("----CHANGED METHOD"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("foo"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nNew results at RETURN:\n------------------"); + for (UpdatableReachingDefinition pr : results) + System.out.println(pr); + Assert.assertTrue(results.size() > original.size()); + + meth = Scene.v().getMainClass().getMethodByName("main"); + + System.out.println("---------------\nmain() method body\n---------------"); + for (Unit u : Scene.v().getMainClass().getMethodByName("foo").getActiveBody().getUnits()) + System.out.println(u); + + for (Unit u : meth.getActiveBody().getUnits()) { + if (u.toString().equals("o = temp$1")) { + results = solver.ifdsResultsAt(icfg.wrapWeak(u)); + System.out.println("----------------\nNew results at RETURN SITE:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + break; + } + } + + ret = meth.getActiveBody().getUnits().getLast(); + results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + System.out.println("----------------\nNew results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + Assert.assertEquals(originalCount, results.size()); + + UpdatableReachingDefinition urd = findDefinition(results, "o"); + Assert.assertNotNull(urd); + Assert.assertEquals(1, urd.getDefinitions().size()); + Assert.assertTrue(urd.getDefinitions().iterator().next().toString().contains("new java.lang.String")); + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void funcTypeTestJU_Rerun() { + System.out.println("Starting funcTypeTestJU_Rerun..."); + performTestRerun(ITestHandlerFuncTypeTest(), "FuncTypeTest"); + System.out.println("funcTypeTestJU_Rerun finished."); + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void funcTypeTestJU_Propagate() { + System.out.println("Starting funcTypeTestJU_Propagate..."); + performTestDirect(ITestHandlerFuncTypeTest(), "FuncTypeTest"); + System.out.println("funcTypeTestJU_Propagate finished."); + } + + /** + * Deletes a statement from a called function and checks whether propagation + * still works in the caller afterwards. + */ + @Test + public void funcTypeTestJU_Update() { + System.out.println("Starting funcTypeTestJU_Update..."); + performTestUpdate(ITestHandlerFuncTypeTest(), "FuncTypeTest"); + System.out.println("funcTypeTestJU_Update finished."); + } + + private ITestHandler ITestHandlerCallerChangeTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nOld results at RETURN:\n------------------"); + for (UpdatableReachingDefinition urd : results) { + System.out.println(urd); + original.add(urd); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainMethod(); + + List rmList = new ArrayList(); + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof AssignStmt || u instanceof InvokeStmt) + if (u.toString().contains("temp$0") || u.toString().contains("temp$3") || u.toString().contains("(z)")) + rmList.add(u); + Assert.assertEquals(5, rmList.size()); + for (Unit u : rmList) { + if (u instanceof InvokeStmt) { + InvokeStmt inv = (InvokeStmt) u; + Edge edge = Scene.v().getCallGraph().findEdge(u, inv.getInvokeExpr().getMethod()); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + } + meth.getActiveBody().getUnits().remove(u); + } + + System.out.println("----CHANGED METHOD"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("---------------\nmain() method body\n---------------"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + + System.out.println("----------------\nNew results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + Assert.assertEquals(original.size() - 3, results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + for (UpdatableReachingDefinition po : original) { + boolean found = false; + for (UpdatableReachingDefinition pr : results) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) { + Assert.assertTrue(po.getValue().toString().equals("temp$0") + || po.getValue().toString().equals("temp$3") + || po.getValue().toString().equals("z")); + } + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Deletes a statement in front of a method call and checks whether + * propagation still works - both straight and through the function + */ + @Test + public void callerChangeTestJU_Rerun() { + System.out.println("Starting callerChangeTestJU_Rerun..."); + performTestRerun(ITestHandlerCallerChangeTest(), "FuncTypeTest"); + System.out.println("callerChangeTestJU_Rerun finished."); + } + + /** + * Deletes a statement in front of a method call and checks whether + * propagation still works - both straight and through the function + */ + @Test + public void callerChangeTestJU_Propagate() { + System.out.println("Starting callerChangeTestJU_Propagate..."); + performTestDirect(ITestHandlerCallerChangeTest(), "FuncTypeTest"); + System.out.println("callerChangeTestJU_Propagate finished."); + } + + /** + * Deletes a statement in front of a method call and checks whether + * propagation still works - both straight and through the function + */ + @Test + public void callerChangeTestJU_Update() { + System.out.println("Starting callerChangeTestJU_Update..."); + performTestUpdate(ITestHandlerCallerChangeTest(), "FuncTypeTest"); + System.out.println("callerChangeTestJU_Update finished."); + } + + private ITestHandler ITestHandlerDeleteInOutLoopTest(final boolean newType) { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nOld results at RETURN:\n------------------"); + for (UpdatableReachingDefinition urd : results) { + System.out.println(urd); + original.add(urd); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainMethod(); + + Unit initStmt = null; + AssignStmt assignStmt = null; + Unit printStmt = null; + Unit inLoopStmt = null; + for (Unit u : meth.getActiveBody().getUnits()) { + if (u instanceof InvokeStmt) { + if (u.toString().contains("specialinvoke temp$0.(int)>")) { + // Fix the call graph + Edge edge = Scene.v().getCallGraph().findEdge(u, Scene.v().getMethod + ("(int)>")); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + + // Remove the statement + initStmt = u; + } + else if (u.toString().contains("(i)")) { + // Fix the call graph + Edge edge = Scene.v().getCallGraph().findEdge(u, Scene.v().getMethod + ("")); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + + // Remove the statement + printStmt = u; + } + else if (u.toString().contains("(\"ok\")")) { + // Fix the call graph + Edge edge = Scene.v().getCallGraph().findEdge(u, Scene.v().getMethod + ("")); + Assert.assertNotNull(edge); + Scene.v().getCallGraph().removeEdge(edge); + + // Remove the statement + inLoopStmt = u; + } + } + else if (u instanceof AssignStmt) + if (u.toString().equals("temp$0 = new java.lang.Integer")) + assignStmt = (AssignStmt) u; + } + Assert.assertNotNull(initStmt); + Assert.assertNotNull(assignStmt); + Assert.assertNotNull(printStmt); + Assert.assertNotNull(inLoopStmt); + + if (newType) { + AssignStmt assi = Jimple.v().newAssignStmt(assignStmt.getLeftOp(), Jimple.v().newNewExpr + (RefType.v("java.lang.String"))); + meth.getActiveBody().getUnits().insertBefore(assi, assignStmt); + + InvokeStmt invokeInitStmt = Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr((Local) assignStmt.getLeftOp(), + RefType.v("java.lang.String").getSootClass().getMethod("void ()").makeRef())); + meth.getActiveBody().getUnits().insertAfter(invokeInitStmt, assi); + } + + meth.getActiveBody().getUnits().remove(initStmt); + meth.getActiveBody().getUnits().remove(assignStmt); + meth.getActiveBody().getUnits().remove(printStmt); + meth.getActiveBody().getUnits().remove(inLoopStmt); + + System.out.println("----CHANGED METHOD"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("---------------\nmain() method body\n---------------"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + + System.out.println("----------------\nNew results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + if (newType) + Assert.assertEquals(original.size(), results.size()); + else + Assert.assertEquals(original.size() - 1, results.size()); + boolean temp0def = false; + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!newType) + Assert.assertTrue(found); + else if (!found) { + Assert.assertFalse(temp0def); + Assert.assertTrue(pr.getValue().toString().equals("temp$0")); + Assert.assertTrue(pr.getDefinitions().iterator().next().toString().contains("java.lang.String")); + Assert.assertTrue(pr.getDefinitions().size() == 1); + temp0def = true; + } + } + for (UpdatableReachingDefinition po : original) { + boolean found = false; + for (UpdatableReachingDefinition pr : results) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + if (!found) + Assert.assertTrue(po.getValue().toString().equals("temp$0")); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + @Test + public void deleteInOutLoopTestJU_Rerun() { + System.out.println("Starting deleteInOutLoopTestJU_Rerun..."); + performTestRerun(ITestHandlerDeleteInOutLoopTest(false), "DeleteInOutLoopTest"); + System.out.println("deleteInOutLoopTestJU_Rerun finished."); + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + @Test + public void deleteInOutLoopTestJU_Propagate() { + System.out.println("Starting deleteInOutLoopTestJU_Propagate..."); + performTestDirect(ITestHandlerDeleteInOutLoopTest(false), "DeleteInOutLoopTest"); + System.out.println("deleteInOutLoopTestJU_Propagate finished."); + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + /* + @Test + public void deleteInOutLoopTestJU_Update() { + System.out.println("Starting deleteInOutLoopTestJU_Update..."); + performTestUpdate(ITestHandlerDeleteInOutLoopTest(false), "DeleteInOutLoopTest"); + System.out.println("deleteInOutLoopTestJU_Update finished."); + } + */ + + /** + * Exchanges a variable assignment in front of a loop and removes a + * statement from inside the loop to trigger propagation from two sides. + */ + @Test + public void changeInOutLoopTestJU_Rerun() { + System.out.println("Starting changeInOutLoopTestJU_Rerun..."); + performTestRerun(ITestHandlerDeleteInOutLoopTest(true), "DeleteInOutLoopTest"); + System.out.println("changeInOutLoopTestJU_Rerun finished."); + } + + /** + * Exchanges a variable assignment in front of a loop and removes a + * statement from inside the loop to trigger propagation from two sides. + */ + @Test + public void changeInOutLoopTestJU_Propagate() { + System.out.println("Starting changeInOutLoopTestJU_Propagate..."); + performTestDirect(ITestHandlerDeleteInOutLoopTest(true), "DeleteInOutLoopTest"); + System.out.println("changeInOutLoopTestJU_Propagate finished."); + } + + /** + * Exchanges a variable assignment in front of a loop and removes a + * statement from inside the loop to trigger propagation from two sides. + */ + /* + @Test + public void changeInOutLoopTestJU_Update() { + System.out.println("Starting changeInOutLoopTestJU_Update..."); + performTestUpdate(ITestHandlerDeleteInOutLoopTest(true), "DeleteInOutLoopTest"); + System.out.println("changeInOutLoopTestJU_Update finished."); + } + */ + + private ITestHandler ITestHandlerDeleteAssignmentInLoopTest() { + return new ITestHandler() { + + Set original = new HashSet(); + + @Override + public void initialize() { + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainMethod(); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("----------------\nOld results at RETURN:\n------------------"); + for (UpdatableReachingDefinition urd : results) { + System.out.println(urd); + original.add(urd); + } + } + + @Override + public void patchGraph(int phase) { + SootMethod meth = Scene.v().getMainMethod(); + + boolean found = false; + for (Unit u : meth.getActiveBody().getUnits()) + if (u instanceof AssignStmt) { + AssignStmt assi = (AssignStmt) u; + if (assi.getLeftOp().toString().equals("bar") + && !assi.getRightOp().toString().equals("0")) { + meth.getActiveBody().getUnits().remove(assi); + found = true; + break; + } + } + Assert.assertTrue(found); + + System.out.println("----CHANGED METHOD"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainMethod(); + Unit ret = meth.getActiveBody().getUnits().getLast(); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("---------------\nmain() method body\n---------------"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + + System.out.println("----------------\nNew results:\n------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + + Assert.assertEquals(original.size() - 1, results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().equals(po.getContents())) { + found = true; + break; + } + Assert.assertTrue(found); + } + } + + @Override + public int getPhaseCount() { + return 1; + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + @Test + public void deleteAssignmentInLoopTestJU_Rerun() { + System.out.println("Starting deleteAssignmentInLoopTestJU_Rerun..."); + performTestRerun(ITestHandlerDeleteAssignmentInLoopTest(), "DeleteAssignmentInLoopTest"); + System.out.println("deleteAssignmentInLoopTestJU_Rerun finished."); + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + @Test + public void deleteAssignmentInLoopTestJU_Propagate() { + System.out.println("Starting deleteAssignmentInLoopTestJU_Propagate..."); + performTestDirect(ITestHandlerDeleteAssignmentInLoopTest(), "DeleteAssignmentInLoopTest"); + System.out.println("deleteAssignmentInLoopTestJU_Propagate finished."); + } + + /** + * Deletes a statement before and in a loop and checks whether the new + * facts are propagated correctly. + */ + /* + @Test + public void deleteAssignmentInLoopTestJU_Update() { + System.out.println("Starting deleteAssignmentInLoopTestJU_Update..."); + performTestUpdate(ITestHandlerDeleteAssignmentInLoopTest(), "DeleteAssignmentInLoopTest"); + System.out.println("deleteAssignmentInLoopTestJU_Update finished."); + } + */ + +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitionsDynamic.java b/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitionsDynamic.java new file mode 100644 index 0000000..79de78d --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/IFDSTestReachingDefinitionsDynamic.java @@ -0,0 +1,1480 @@ +package soot.jimple.interproc.ifds.test; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +import soot.G; +import soot.Local; +import soot.MethodOrMethodContext; +import soot.PackManager; +import soot.Scene; +import soot.SceneTransformer; +import soot.Singletons; +import soot.SootClass; +import soot.SootMethod; +import soot.SootResolver; +import soot.Transform; +import soot.Unit; +import soot.JastAddJ.CompilationUnit; +import soot.JastAddJ.Program; +import soot.jimple.DefinitionStmt; +import soot.jimple.interproc.ifds.IFDSTabulationProblem; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.problems.IFDSReachingDefinitions; +import soot.jimple.interproc.ifds.problems.UpdatableReachingDefinition; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.ifds.template.JimpleBasedInterproceduralCFG; +import soot.jimple.interproc.ifds.utils.Utils; +import soot.jimple.interproc.incremental.UpdatableWrapper; +import soot.jimple.toolkits.callgraph.ReachableMethods; + +public class IFDSTestReachingDefinitionsDynamic { + + private abstract class DynamicTestHandler implements ITestHandler { + + private final boolean PATCH_LIBRARIES = false; + + private final String originalFile; + private final String patchedFile; + private final String targetFile; + + public DynamicTestHandler(String originalFile, String patchedFile, String targetFile) { + this.originalFile = originalFile; + this.patchedFile = patchedFile; + this.targetFile = targetFile; + } + + @Override + public void initialize() { + try { + String curDir = System.getProperty("user.dir"); + Utils.copyFile(curDir + "/test/" + originalFile, curDir + "/test/" + targetFile); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } + } + + @Override + public void patchGraph(int phase) { + final boolean AGGRESSIVE_CHECKS = true; + + // Get the original call graph size before we change anything + System.out.println("Original call graph has " + Scene.v().getCallGraph().size() + " edges"); + + try { + String curDir = System.getProperty("user.dir"); + Utils.copyFile(curDir + "/test/" + patchedFile, curDir + "/test/" + targetFile); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } + + // Mark all existing compilation units as unresolved + Program program = SootResolver.v().getProgram(); + for (CompilationUnit cu : program.getCompilationUnits()) + program.releaseCompilationUnitForFile(cu.pathName()); + + // Load a new version of the source file into Soot + + // Release some stale scene information + try { + Field vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_callgraph_VirtualCalls"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_DumbPointerAnalysis"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_FullObjectSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_EntryPoints"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Scene.class.getDeclaredField("doneResolving"); + vcField.setAccessible(true); + vcField.set(Scene.v(), false); + + // Spark data + Method methClear = HashMap.class.getMethod("clear"); + vcField = G.class.getDeclaredField("Parm_pairToElement"); + vcField.setAccessible(true); + methClear.invoke(vcField.get(G.v()), (Object[]) null); + + vcField = G.class.getDeclaredField("MethodPAG_methodToPag"); + vcField.setAccessible(true); + methClear.invoke(vcField.get(G.v()), (Object[]) null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_AllSharedListNodes"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_AllSharedHybridNodes"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldTagger"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_pag_ArrayElement"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldReadTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldWriteTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_fieldrw_FieldTagAggregator"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_sets_EmptyPointsToSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_spark_SparkTransformer"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = Singletons.class.getDeclaredField("instance_soot_jimple_toolkits_pointer_FullObjectSet"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + + vcField = G.class.getDeclaredField("newSetFactory"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + vcField = G.class.getDeclaredField("oldSetFactory"); + vcField.setAccessible(true); + vcField.set(G.v(), null); + } catch (NoSuchFieldException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (SecurityException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + Scene.v().setDefaultThrowAnalysis(null); + Scene.v().releaseFastHierarchy(); + Scene.v().releaseReachableMethods(); + Scene.v().releaseActiveHierarchy(); + Scene.v().releasePointsToAnalysis(); + Scene.v().releaseCallGraph(); + Scene.v().setEntryPoints(null); + + // Force a resolve of all soot classes in the scene. We + // need to copy the list to avoid ConcurrentModificationExceptions. + Set ac = new HashSet(); + Set lc = new HashSet(); + Set allClasses = new HashSet(); + for (SootClass sc : Scene.v().getApplicationClasses()) { + ac.add(sc); + allClasses.add(sc); + } + if (PATCH_LIBRARIES) + for (SootClass sc : Scene.v().getLibraryClasses()) { + lc.add(sc); + allClasses.add(sc); + } + if (PATCH_LIBRARIES) + for (SootClass sc : Scene.v().getClasses()) + allClasses.add(sc); + for (SootClass sc : allClasses) { + // Remove the class from the scene so that it can be + // added anew. This helps fixing Soot's internal caches. + Scene.v().removeClass(sc); + assert !Scene.v().containsClass(sc.getName()); + + // Let the class think it has not been resolved yet. This + // is important as resolving is aborted if the current + // resolving level is greater than or equal to the requested + // one. + sc.setResolvingLevel(SootClass.DANGLING); + } + + if (PATCH_LIBRARIES) { + // Make sure that we load all class dependencies of the new version + Scene.v().loadNecessaryClasses(); + } + + // Reload all application classes + List newClasses = new ArrayList(); + for (SootClass sc : allClasses) { + // Force a new class resolving + Scene.v().forceResolve(sc.getName(), SootClass.SIGNATURES); + SootClass scNew = Scene.v().forceResolve(sc.getName(), SootClass.BODIES); + assert scNew != null; + if (ac.contains(sc)) + scNew.setApplicationClass(); + if (lc.contains(sc)) + scNew.setLibraryClass(); + assert !AGGRESSIVE_CHECKS || scNew != ac; + assert scNew.isInScene(); + assert Scene.v().getSootClass(sc.getName()) == scNew; + newClasses.add(scNew); + + for (SootMethod sm : scNew.getMethods()) + if (sm.isConcrete()) + sm.retrieveActiveBody(); + } + + // Fix cached main class - this will automatically fix the main method + SootClass oldMainClass = Scene.v().getMainClass(); + SootClass mainClass = Scene.v().getSootClass(oldMainClass.getName()); + Scene.v().setMainClass(mainClass); + System.out.println("Old main class: " + oldMainClass + " - new: " + mainClass); + assert !AGGRESSIVE_CHECKS || !oldMainClass.isInScene(); + + // Patch the entry points + long timeBeforeEP = System.nanoTime(); + Scene.v().getEntryPoints(); + System.out.println("Updating entry points took " + + ((System.nanoTime() - timeBeforeEP) / 1E9) + " seconds"); + + // Recreate the exception throw analysis + Scene.v().getDefaultThrowAnalysis(); + + // Update the call graph + long timeBeforeCG = System.nanoTime(); + PackManager.v().getPack("cg").apply(); + int cgSize = Scene.v().getCallGraph().size(); + System.out.println("Updating callgraph took " + + ((System.nanoTime() - timeBeforeCG) / 1E9) + " seconds, " + + "callgraph now has " + cgSize + " edges."); + + // Invalidate the list of reachable methods. It will automatically be recreated + // on the next call to "getReachableMethods". + long timeBeforeRM = System.nanoTime(); + Scene.v().getReachableMethods(); + System.out.println("Updating reachable methods took " + + ((System.nanoTime() - timeBeforeRM) / 1E9) + " seconds"); + + // Update the class hierarchy + Scene.v().getActiveHierarchy(); + + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(Scene.v().getCallGraph(), eps.iterator()); + reachableMethods.update(); + + // Fix the resolving state for the old classes. Otherwise, access to the + // fields and methods will be blocked and no diff can be performed. + for (SootClass sc : allClasses) + sc.setResolvingLevel(SootClass.BODIES); + } + + @Override + public int getPhaseCount() { + return 1; + } + }; + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method does not create indices for dynamic updates. Instead, updates are + * just propagated along the edges until a fix point is reached. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestDirect(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + Scene.v().getSootClass(className).setApplicationClass(); + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + long nanoBeforeCFG = System.nanoTime(); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG created in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long beforeSolver = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + ((System.nanoTime() - beforeSolver) / 1E9) + "seconds."); + + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + UpdatableWrapper ret = icfg.wrapWeak(meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast())); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 1E9 + " seconds."); + + Scene.v().getSootClass(className).setApplicationClass(); + + nanoBeforeCFG = System.nanoTime(); + icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG updated in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + long nanoBeforeUpdate = System.nanoTime(); + solver.update(icfg); + System.out.println("IDE results updated in " + (System.nanoTime() - nanoBeforeUpdate) / 1E9 + " seconds."); + + handler.performExtendedTest(icfg, solver, i); +// solver.dumpResults(className + "_Propagate.csv"); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String os = System.getProperty("os.name"); + String cpSep = ":"; + if (os.contains("Windows")) + cpSep = ";"; + + String udir = System.getProperty("user.dir"); + String sootcp = udir + File.separator + "test/junit-4.10.jar" + cpSep + + udir + File.separator + "hamcrest-core-1.3.jar" + cpSep + + udir + File.separator + "bin" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/rt.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/jce.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\rt.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\jce.jar"; + System.out.println("Soot classpath: " + sootcp); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", +// "-pp", + "-cp", sootcp, +// "-no-bodies-for-excluded", +// "-exclude", "java", +// "-exclude", "javax", + "-output-format", "none", + "-p", "jb", "use-original-names:true", + "-p", "cg.spark", "on", +// "-p", "cg.spark", "verbose:true", + /*"-app",*/ className } ); + } + + /** + * Performs a generic test and calls the extension handler when it is complete. + * This method runs the analysis once, then modifies the program and afterwards + * dynamically updates the analysis results. + * @param handler The handler to call after finishing the generic information + * leakage analysis + * @param className The name of the test class to use + */ + private void performTestRerun(final ITestHandler handler, final String className) { + soot.G.reset(); + handler.initialize(); + + PackManager.v().getPack("wjtp").add(new Transform("wjtp.ifds", new SceneTransformer() { + protected void internalTransform(String phaseName, @SuppressWarnings("rawtypes") Map options) { + Scene.v().getSootClass(className).setApplicationClass(); + long timeBefore = System.nanoTime(); + System.out.println("Running IFDS on initial CFG..."); + + long nanoBeforeCFG = System.nanoTime(); + InterproceduralCFG, UpdatableWrapper> icfg = new JimpleBasedInterproceduralCFG(); + System.out.println("ICFG created in " + (System.nanoTime() - nanoBeforeCFG) / 1E9 + " seconds."); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem = + new IFDSReachingDefinitions(icfg); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem); + + long beforeSolver = System.nanoTime(); + System.out.println("Running solver..."); + solver.solve(false); + System.out.println("Solver done in " + ((System.nanoTime() - beforeSolver) / 1E9) + "seconds."); + + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + UpdatableWrapper ret = icfg.wrapWeak(meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast())); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + + if (handler != null) { + handler.extendBasicTest(icfg, solver); + for (int i = 0; i < handler.getPhaseCount(); i++) { + long nanoBeforePatch = System.nanoTime(); + handler.patchGraph(i); + System.out.println("Graph patched in " + (System.nanoTime() - nanoBeforePatch) / 1E9 + " seconds."); + + Scene.v().getSootClass(className).setApplicationClass(); + + IFDSTabulationProblem, UpdatableReachingDefinition, UpdatableWrapper, + InterproceduralCFG, UpdatableWrapper>> problem2 = + new IFDSReachingDefinitions(icfg = new JimpleBasedInterproceduralCFG()); + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver2 = + new IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>>(problem2); + + solver2.solve(false); + if (handler != null) + handler.performExtendedTest(icfg, solver2, i); + +// solver2.dumpResults(className + "_Rerun.csv"); + } + } + System.out.println("Time elapsed: " + ((double) (System.nanoTime() - timeBefore)) / 1E9); + } + })); + + String os = System.getProperty("os.name"); + String cpSep = ":"; + if (os.contains("Windows")) + cpSep = ";"; + + String udir = System.getProperty("user.dir"); + String sootcp = udir + File.separator + "test/junit-4.10.jar" + cpSep + + udir + File.separator + "hamcrest-core-1.3.jar" + cpSep + + udir + File.separator + "bin" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/rt.jar" + cpSep + + "/usr/lib/jvm/java-6-sun/jre/lib/jce.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\rt.jar" + cpSep + + "C:\\Program Files\\Java\\jre7\\lib\\jce.jar"; + System.out.println("Soot classpath: " + sootcp); + soot.Main.v().run(new String[] { + "-W", + "-main-class", className, + "-process-path", udir + File.separator + "test", + "-src-prec", "java", +// "-pp", + "-cp", sootcp, +// "-no-bodies-for-excluded", +// "-exclude", "java", +// "-exclude", "javax", + "-output-format", "none", + "-p", "jb", "use-original-names:true", + "-p", "cg.spark", "on", +// "-p", "cg.spark", "verbose:true", + /*"-app",*/ className } ); + } + + protected void checkInitialLeaks(Set results) { + boolean found = false; + for (UpdatableReachingDefinition p : results) { + System.out.println(p.getContents()); + if (p.getContents().getO1() instanceof Local && ((Local) p.getContents().getO1()).getName().contains("$")) + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().contains(("new org.junit.runner.JUnitCore"))) { + found = true; + break; + } + if (found) break; + } + Assert.assertTrue(found); + + for (UpdatableReachingDefinition p : results) + if (p.getContents().getO1() instanceof Local) + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().contains("new org.junit.runner.notification.RunListener")) + Assert.fail("Invalid initial leak found"); + } + + private ITestHandler ITestHandlerSimpleTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-original.jar", "junit-4.10.jar") { + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit u = meth.getActiveBody().getUnits().getPredOf(meth.getActiveBody().getUnits().getLast()); + UpdatableWrapper ret = icfg.wrapWeak(u); + Set results = solver.ifdsResultsAt(ret); + checkInitialLeaks(results); + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting simpleTestJU_Rerun..."); + performTestRerun(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTestJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis without any updates to the program graph + */ + @Test + public void simpleTestJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting simpleTestJU_Propagate..."); + performTestDirect(ITestHandlerSimpleTest(), "org.junit.runner.JUnitCore"); + System.out.println("simpleTestJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerAddVarTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-addVarTest.jar", "junit-4.10.jar") { + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + checkInitialLeaks(results); + + boolean found = false; + for (UpdatableReachingDefinition p : results) { + if (p.getContents().getO1() instanceof Local) + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().contains(("= \"Hello World\""))) { + found = true; + break; + } + if (found) break; + } + Assert.assertTrue(found); + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a local with an assignment + */ + @Test + public void addLocalJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addLocalJU_Rerun..."); + performTestRerun(ITestHandlerAddVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("addLocalJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then adds a local with an assignment + */ + @Test + public void addLocalJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addLocalJU_Propagate..."); + performTestDirect(ITestHandlerAddVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("addLocalJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerRedefineVarTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-redefineVarTest.jar", "junit-4.10.jar") { + + private UpdatableWrapper originalPoint; + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + originalPoint = icfg.wrapWeak(ret); + Set results = solver.ifdsResultsAt(originalPoint); + + System.out.println("---------------------\nOld results:\n---------------------"); + for (UpdatableReachingDefinition p : results) + System.out.println(p); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + UpdatableWrapper stmt = icfg.wrapWeak(ret); + + System.out.println("--------------------------"); + + for (Unit u : meth.getActiveBody().getUnits()) { + boolean found = false; + List> unitQueue = new ArrayList>(); + Set> doneSet = new HashSet>(); + unitQueue.add(icfg.wrapWeak(meth.getActiveBody().getUnits().getFirst())); + while (!unitQueue.isEmpty()) { + UpdatableWrapper curUnit = unitQueue.remove(0); + if (!doneSet.add(curUnit)) + continue; + if (curUnit.getContents() == u) { + found = true; + break; + } + for (UpdatableWrapper succ : icfg.getSuccsOf(curUnit)) + unitQueue.add(succ); + } + Assert.assertTrue("Statement not found: " + u, found); +// System.out.println(u + "\t" + solver.ifdsResultsAt(icfg.wrapWeak(u)).size()); + } + + Set results = solver.ifdsResultsAt(stmt); + Assert.assertTrue(icfg.containsStmt(stmt)); + + System.out.println("---------------------\nNew results:\n---------------------"); + for (UpdatableReachingDefinition p : results) + System.out.println(p); + + boolean found = false; + for (UpdatableReachingDefinition p : results) { + System.out.println(p); + if (p.getContents().getO1() instanceof Local + && ((Local) p.getContents().getO1()).getName().contains("$")) { + for (DefinitionStmt def : p.getContents().getO2()) + if (def.toString().contains("new org.junit.runner.notification.RunListener")) { + found = true; + break; + } + } + if (found) break; + } + Assert.assertTrue(found); + } + + @Override + public void initApplicationClasses() { + } + }; + } + + /** + * Performs a simple analysis, then overwrites a local variable + */ + @Test + public void redefineVarJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting redefineVarJU_Rerun..."); + performTestRerun(ITestHandlerRedefineVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineVarJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then overwrites a local variable + */ + @Test + public void redefineVarJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting redefineVarJU_Propagate..."); + performTestDirect(ITestHandlerRedefineVarTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineVarJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerRemoveStmtTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-removeStmtTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) { + original.add(p); + } + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + // The next line does not hold when running with Java libraries as + // a whole chunk of code is no longer reachable with removes many + // candidates from the points-to analysis, e.g. for the interator + // invocation in r34. Therefore, we only check a weaker condition. +// Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (po.getContents().toString().equals(pr.getContents().toString())) { + found = true; + break; + } + Assert.assertTrue("Missing result: " + pr, found); + } + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes a non-assignment statement + */ + @Test + public void removeStmtJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeStmtJU_Rerun..."); + performTestRerun(ITestHandlerRemoveStmtTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then removes a non-assignment statement + */ + @Test + public void removeStmtJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeStmtJU_Propagate..."); + performTestDirect(ITestHandlerRemoveStmtTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerRemoveAssignmentTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-removeAssignmentTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("-----------------------------\nOld results\n-----------------------------"); + for (UpdatableReachingDefinition p : results) { + original.add(p); + System.out.println(p); + } + System.out.println("-----------------------------"); + + System.out.println("-----------------------------\nOld runMain() function\n-----------------------------"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + System.out.println("-----------------------------"); + } + + @Override + public void patchGraph(int phase) { + super.patchGraph(phase); + + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + System.out.println("-----------------------------\nNew runMain() function\n-----------------------------"); + for (Unit u : meth.getActiveBody().getUnits()) + System.out.println(u); + System.out.println("-----------------------------"); + + Scene.v().getSootClass("org.junit.internal.TextListener").setApplicationClass(); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + System.out.println("-----------------------------\nNew results\n-----------------------------"); + for (UpdatableReachingDefinition urd : results) + System.out.println(urd); + System.out.println("-----------------------------"); + + Assert.assertEquals(original.size() - 1, results.size()); + for (UpdatableReachingDefinition pr : results) + if (pr.getContents().getO1().toString().contains("listener")) + Assert.assertTrue(pr.getContents().getO2().toString().contains(" = null")); + /* + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().toString().equals(po.getContents().toString())) { + found = true; + break; + } + if (!found) { + // Doesn't work out as Soot assigns new names to the + // temporary variables +// Assert.assertTrue(pr.getContents().getO1().toString().contains("listener")); +// Assert.assertTrue(pr.getContents().getO2().toString().contains(" = null]")); + } + } + */ + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes an assignment statement + */ + @Test + public void removeAssignmentJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerRemoveAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeAssignmentJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then removes an assignment statement + */ + @Test + public void removeAssignmentJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerRemoveAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeAssignmentJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerAddCallNoAssignmentTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-addCallNoAssignmentTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + System.out.println(pr); + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().toString().equals(po.getContents().toString())) { + found = true; + break; + } + Assert.assertTrue("Statement not found: " + pr, found); + } + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a call without creating a new assignment + */ + @Test + public void addCallNoAssignmentJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addCallNoAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerAddCallNoAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoAssignmentJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then adds a call without creating a new assignment + */ + @Test + public void addCallNoAssignmentJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addCallNoAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerAddCallNoAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallNoAssignmentJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerAddCallAssignmentTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-addCallAssignmentTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + System.out.println("Original size: " + results.size()); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition po : original) { + boolean found = false; + for (UpdatableReachingDefinition pr : results) + if (pr.getContents().toString().equals(po.getContents().toString())) { + found = true; + break; + } + if (!found) { + System.out.println("FAILED: " + po); + Assert.fail("Statement not found: " + po); + } + } + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then adds a call inside a new assignment + */ + @Test + public void addCallAssignmentJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addCallAssignmentJU_Rerun..."); + performTestRerun(ITestHandlerAddCallAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallAssignmentJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then adds a call inside a new assignment + */ + @Test + public void addCallAssignmentJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting addCallAssignmentJU_Propagate..."); + performTestDirect(ITestHandlerAddCallAssignmentTest(), "org.junit.runner.JUnitCore"); + System.out.println("addCallAssignmentJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerRemoveStmtFromLoopTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-removeStmtFromLoopTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + System.out.println("--ORIGINAL METHOD"); + Unit gotoStmt = null; + for (Unit u : meth.getActiveBody().getUnits()) { + System.out.println(u); + if (u.toString().contains("goto [?= (branch)]")) + gotoStmt = u; + } + Assert.assertNotNull(gotoStmt); + + Unit ret = meth.getActiveBody().getUnits().getLast(); + UpdatableWrapper wrappedRet = icfg.wrapWeak(ret); + Set results = solver.ifdsResultsAt(wrappedRet); + + System.out.println("--------------\nORIGINAL RESULTS\n--------------"); + for (UpdatableReachingDefinition p : results) { + original.add(p.toString().replaceAll("(\\$r|l)\\d+", "")); + System.out.println(p); + } + System.out.println("ORIGINAL size: " + results.size()); + + wrappedRet = icfg.wrapWeak(gotoStmt); + results = solver.ifdsResultsAt(wrappedRet); + boolean found = false; + for (UpdatableReachingDefinition urd : results) + if (urd.getDefinitions().toString().contains("@parameter0")) { + found = true; + break; + } + Assert.assertTrue(found); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + Set results = solver.ifdsResultsAt(wrapped); + + System.out.println("--------------\nNEW RESULTS\n--------------"); + for (UpdatableReachingDefinition pr : results) + System.out.println(pr); + + for (String po : original) + { + boolean found = false; + for (UpdatableReachingDefinition pr : results) + if (po.equals(pr.toString().replaceAll("(\\$r|l)\\d+", ""))) + found = true; + + if (!found) { + System.out.println("Missing statement: " + po); + Assert.fail("Missing statement: " + po); + } + } + + Set newResults = new HashSet(results.size()); + for (UpdatableReachingDefinition pr : results) { + String newRes = pr.toString().replaceAll("(\\$r|l)\\d+", ""); + newResults.add(newRes); + + boolean found = false; + for (String po : original) + if (newRes.equals(po)) { + found = true; + break; + } + if (!found) { + System.out.println("New result: " + pr); + Assert.assertEquals("c", pr.getContents().getO1().toString()); + Assert.assertEquals("[c = null]", pr.getContents().getO2().toString()); + } + } + + Assert.assertEquals(original.size() + 1, newResults.size()); + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then removes a call from a loop and adds an + * assignment to a new variable instead + */ + @Test + public void removeStmtFromLoopJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeStmtFromLoopJU_Rerun..."); + performTestRerun(ITestHandlerRemoveStmtFromLoopTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtFromLoopJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then removes a call from a loop and adds an + * assignment to a new variable instead + */ + @Test + public void removeStmtFromLoopJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting removeStmtFromLoopJU_Propagate..."); + performTestDirect(ITestHandlerRemoveStmtFromLoopTest(), "org.junit.runner.JUnitCore"); + System.out.println("removeStmtFromLoopJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerRedefineReturnTest() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.10-redefineReturnTest.jar", "junit-4.10.jar") { + + Set original = new HashSet(); + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + for (UpdatableReachingDefinition p : results) + original.add(p); + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + Set results = solver.ifdsResultsAt(icfg.wrapWeak(ret)); + + Assert.assertEquals(original.size(), results.size()); + for (UpdatableReachingDefinition pr : results) { + boolean found = false; + for (UpdatableReachingDefinition po : original) + if (pr.getContents().toString().equals(po.getContents().toString())) { + found = true; + break; + } + if (!found) { + Assert.assertEquals("result", pr.getContents().getO1().toString()); + Assert.assertEquals("[result = null]", pr.getContents().getO2().toString()); + } + } + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis, then overwrites the return value of a called + * function and checks whether it is returned correctly + */ + @Test + public void redefineReturnJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting redefineReturnJU_Rerun..."); + performTestRerun(ITestHandlerRedefineReturnTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineReturnJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis, then overwrites the return value of a called + * function and checks whether it is returned correctly + */ + @Test + public void redefineReturnJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting redefineReturnJU_Propagate..."); + performTestDirect(ITestHandlerRedefineReturnTest(), "org.junit.runner.JUnitCore"); + System.out.println("redefineReturnJU_Propagate finished."); + } + } + + private ITestHandler ITestHandlerNewVersion() { + return new DynamicTestHandler("junit-4.10-original.jar", "junit-4.11-original.jar", "junit-4.10.jar") { + + Set original; + Set originalRM; + Set originalAL1; + Set originalALS; + Set originalAL; + Set originalRet; + + private Set getResultsAt + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + Unit unit) { + Set retList = new HashSet(); + UpdatableWrapper wrapped = icfg.wrapWeak(unit); + Set results = solver.ifdsResultsAt(wrapped); +System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + for (UpdatableReachingDefinition p : results) + retList.add(p); + + System.out.println("RESULTS:\n-------------------"); + for (UpdatableReachingDefinition pr : results) + System.out.println(pr); + return retList; + } + + private void compareResults + (Set original, + Set updated) { + System.out.println("NEW RESULTS:\n-------------------"); + for (UpdatableReachingDefinition pr : updated) + System.out.println(pr); + + Assert.assertEquals(original.size()/* + offset*/, updated.size()); + for (UpdatableReachingDefinition pr : original) { + boolean found = false; + for (UpdatableReachingDefinition po : updated) + if (pr.getContents().toString().replaceAll("((\\$r|l)\\d+)|(\\w+\\$)|args", "").equals + (po.getContents().toString().replaceAll("((\\$r|l)\\d+)|i\\$|(\\w+\\$)|args", ""))) { + found = true; + break; + } + Assert.assertTrue(found); + } + + } + + @Override + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver) { + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + original = getResultsAt(icfg, solver, ret); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + originalRM = getResultsAt(icfg, solver, ret); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getFirst(); + originalAL1 = getResultsAt(icfg, solver, ret); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getSuccOf + (meth.getActiveBody().getUnits().getFirst()); + originalALS = getResultsAt(icfg, solver, ret); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + originalAL = getResultsAt(icfg, solver, ret); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + originalRet = getResultsAt(icfg, solver, ret); + } + } + + @Override + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,UpdatableReachingDefinition,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase) { + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMainAndExit"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(original, results); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("runMain"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(originalRM, results); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getFirst(); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(originalAL1, results); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getSuccOf + (meth.getActiveBody().getUnits().getFirst()); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(originalALS, results); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("addListener"); + Unit ret = meth.getActiveBody().getUnits().getPredOf + (meth.getActiveBody().getUnits().getLast()); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(originalAL, results); + } + + { + SootMethod meth = Scene.v().getMainClass().getMethodByName("main"); + Unit ret = meth.getActiveBody().getUnits().getLast(); + UpdatableWrapper wrapped = icfg.wrapWeak(ret); + System.out.println("GET RESULTS AT " + wrapped + " (" + Integer.toHexString(System.identityHashCode(wrapped)) + ")"); + Set results = solver.ifdsResultsAt(wrapped); + compareResults(originalRet, results); + } + } + + @Override + public void initApplicationClasses() { + // TODO Auto-generated method stub + + } + }; + } + + /** + * Performs a simple analysis and then replaces the JUnit library with a + * newer version + */ + @Test + public void newVersionJU_Rerun() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting newVersionJU_Rerun..."); + performTestRerun(ITestHandlerNewVersion(), "org.junit.runner.JUnitCore"); + System.out.println("newVersionJU_Rerun finished."); + } + } + + /** + * Performs a simple analysis and then replaces the JUnit library with a + * newer version + */ + @Test + public void newVersionJU_Propagate() { + for (int i = 0; i < 10; i++) { + System.out.println("Starting newVersionJU_Propagate..."); + performTestDirect(ITestHandlerNewVersion(), "org.junit.runner.JUnitCore"); + System.out.println("newVersionJU_Propagate finished."); + } + } + +} \ No newline at end of file diff --git a/src/soot/jimple/interproc/ifds/test/ITestHandler.java b/src/soot/jimple/interproc/ifds/test/ITestHandler.java new file mode 100644 index 0000000..3f714ed --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/ITestHandler.java @@ -0,0 +1,60 @@ +package soot.jimple.interproc.ifds.test; + +import soot.SootMethod; +import soot.Unit; +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.solver.IFDSSolver; +import soot.jimple.interproc.incremental.UpdatableWrapper; + +/** + * Interface for injecting new code into the generic test case + */ +public interface ITestHandler> { + + /** + * Method that is called before Soot is run the first time. + */ + public void initialize(); + + /** + * Initializes the application classes if necessary. + */ + public void initApplicationClasses(); + + /** + * Method that is called when the basic information leakage analysis is complete. + * Implement this method to perform additional test tasks. + * @param solver The solver that has performed the generic analysis + */ + public void extendBasicTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,D,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver); + + /** + * Method that is called when the CFG shall be patched by the implementer + * @param phase The phase of the test being executed. This value starts with zero + * and is extended by one up to the number of phases minues one. + */ + public void patchGraph(int phase); + + /** + * Method that is called when the extended test after updating/rerunning shall be + * performed. + * @param solver The solver containing the results on the modified CFG. + * @param phase The phase of the test being executed. This value starts with zero + * and is extended by one up to the number of phases minues one. + */ + public void performExtendedTest + (InterproceduralCFG, UpdatableWrapper> icfg, + IFDSSolver,D,UpdatableWrapper, + InterproceduralCFG,UpdatableWrapper>> solver, + int phase); + + /** + * Gets the number of phases in this test + * @return The number of phases in this test + */ + public int getPhaseCount(); + +} diff --git a/src/soot/jimple/interproc/ifds/test/SingleJUnitTestRunner.java b/src/soot/jimple/interproc/ifds/test/SingleJUnitTestRunner.java new file mode 100644 index 0000000..d2f1ab0 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/test/SingleJUnitTestRunner.java @@ -0,0 +1,25 @@ +package soot.jimple.interproc.ifds.test; + +import org.junit.runner.JUnitCore; +import org.junit.runner.Request; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; + +public class SingleJUnitTestRunner { + public static void main(String... args) throws ClassNotFoundException { + String[] classAndMethod = args[0].split("#"); + Request request = Request.method(Class.forName(classAndMethod[0]), + classAndMethod[1]); + + Result result = new JUnitCore().run(request); + if (result.wasSuccessful()) + System.out.println("Test run was successful"); + else { + System.out.println("Test run was NOT successful: "); + for (Failure f : result.getFailures()) + System.out.println(f.getMessage() + " - Trace: " + f.getTrace() + " - Exception: " + + f.getException() + " - " + f.getException().getStackTrace()); + } + System.exit(result.wasSuccessful() ? 0 : 1); + } +} diff --git a/src/soot/jimple/interproc/ifds/utils/Utils.java b/src/soot/jimple/interproc/ifds/utils/Utils.java new file mode 100644 index 0000000..1521687 --- /dev/null +++ b/src/soot/jimple/interproc/ifds/utils/Utils.java @@ -0,0 +1,184 @@ +package soot.jimple.interproc.ifds.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Table; + +/** + * Generic utility class + */ +public class Utils { + + /** + * Adds the given value to a map from keys to lists of values. If there is already + * a list for the given key in the map, the value is added. Otherwise, a new list + * containing the value is created. + * @param edgeList The list map to which to add the new element + * @param key The key identifying the list to which to add the element + * @param value The value to be added to the list + * @return True if the value was actually inserted, false if not (i.e. the + * value has already been there before) + */ + public static boolean addElementToMapList(Map> edgeList, X key, Y value) { + List list = edgeList.get(key); + if (list != null) { + if (list.contains(value)) + return false; + else { + list.add(value); + return true; + } + } + else { + list = new ArrayList(); + list.add(value); + edgeList.put(key, list); + return true; + } + } + + /** + * Adds the given value to a map from keys to sets of values. If there is already + * a set for the given key in the map, the value is added. Otherwise, a new set + * containing the value is created. + * @param edgeList The set map to which to add the new element + * @param key The key identifying the set to which to add the element + * @param value The value to be added to the set + * @return True if the value was actually inserted, false if not (i.e. the + * value has already been there before) + */ + public static boolean addElementToMapSet(Map> edgeList, X key, Y value) { + Set list = edgeList.get(key); + if (list != null) { + if (list.contains(value)) + return false; + else { + list.add(value); + return true; + } + } + else { + list = new HashSet(15); + list.add(value); + edgeList.put(key, list); + return true; + } + } + + /** + * Removes the given value from a map from keys to lists of values. If the list + * becomes empty after removing the value, it is removed from the map as well. + * @param edgeList The list map from which to remove the element + * @param key The key identifying the list from which to remove the element + * @param value The value to be removed from the list + * @return True if the value was actually remove, false if not (i.e. the + * value was not in the list) + */ + public static boolean removeElementFromMapList(Map> edgeList, X key, Y value) { + List list = edgeList.get(key); + if (list == null) + return false; + if (!list.remove(value)) + return false; + if (list.isEmpty()) + edgeList.remove(key); + return true; + } + + /** + * Removes the given value from a map from keys to sets of values. If the set + * becomes empty after removing the value, it is removed from the map as well. + * @param edgeList The set map from which to remove the element + * @param key The key identifying the set from which to remove the element + * @param value The value to be removed from the set + * @return True if the value was actually remove, false if not (i.e. the + * value was not in the list) + */ + public static boolean removeElementFromMapSet(Map> edgeList, X key, Y value) { + Set list = edgeList.get(key); + if (list == null) + return false; + if (!list.remove(value)) + return false; + if (list.isEmpty()) + edgeList.remove(key); + return true; + } + + /** + * Removes an element from a map from keys to sets of values. If the given + * value appears as a key, the whole row is deleted. If it appears inside a + * list, it is removed from this list. If a list becomes empty during + * processing, the whole row is deleted. + * @param edgeList The set map from which to remove the element + * @param value The value to be removed + */ + public static void removeElementFromMapSet(Map> edgeList, X value) { + edgeList.remove(value); + List l = new ArrayList(edgeList.keySet()); + for (X x : l) + removeElementFromMapSet(edgeList, x, value); + } + + /** + * Removes all rows which have the given element as their key from the + * given table + * @param table The table from which to remove the rows + * @param element The key for which which to remove all rows from the table + */ + public static void removeElementFromTable(Table table, X element) { + List rm = new ArrayList(table.row(element).keySet()); + for (Y y : rm) + table.remove(element, y); + } + + public static void copyFile(String sourceFile, String destFile) throws IOException { + // Code adapted from http://www.javabeat.net/2007/10/copying-file-contents-using-filechannel/ + FileInputStream source = null; + FileOutputStream destination = null; + try { + File f = new File(destFile); + if (f.exists()) + f.delete(); + + source = new FileInputStream(sourceFile); + destination = new FileOutputStream(destFile); + + FileChannel sourceFileChannel = source.getChannel(); + FileChannel destinationFileChannel = destination.getChannel(); + + long size = sourceFileChannel.size(); + sourceFileChannel.transferTo(0, size, destinationFileChannel); + } + finally { + if (source != null) + source.close(); + if (destination != null) + destination.close(); + } + } + + public static void stringToTextFile(String fileName, String data) throws IOException { + FileWriter fw = null; + try { + fw = new FileWriter(fileName); + fw.write(data); + fw.flush(); + } + finally { + if (fw != null) + fw.close(); + } + } + +} diff --git a/src/soot/jimple/interproc/incremental/AbstractUpdatableInterproceduralCFG.java b/src/soot/jimple/interproc/incremental/AbstractUpdatableInterproceduralCFG.java new file mode 100644 index 0000000..42e5f12 --- /dev/null +++ b/src/soot/jimple/interproc/incremental/AbstractUpdatableInterproceduralCFG.java @@ -0,0 +1,179 @@ +package soot.jimple.interproc.incremental; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import soot.jimple.interproc.ifds.InterproceduralCFG; +import soot.jimple.interproc.ifds.utils.Utils; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.MapMaker; + +/** + * Abstract base class that provides listener registration and deregistration + * functionality for interprocedural program graphs. + * + * @param Nodes in the CFG, typically Unit or Block + * @param Method representation + */ +public abstract class AbstractUpdatableInterproceduralCFG implements InterproceduralCFG { + + public static final boolean BROADCAST_NOTIFICATIONS = true; + + private final LoadingCache> wrappedObjects; + private final Map> objectListeners; + private final Set globalListeners = new HashSet(); + + public AbstractUpdatableInterproceduralCFG() { + CacheBuilder cb = CacheBuilder.newBuilder().concurrencyLevel + (Runtime.getRuntime().availableProcessors()).initialCapacity(100000); //.weakKeys(); + wrappedObjects = cb.build(new CacheLoader>() { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public UpdatableWrapper load(Object key) throws Exception { + UpdatableWrapper wrapped = new DefaultUpdatableWrapper(key); + registerListener(wrapped, key); + return wrapped; + } + + }); + + objectListeners = new MapMaker().concurrencyLevel(Runtime.getRuntime().availableProcessors()).initialCapacity + (100000).makeMap(); + } + + @Override + public void registerListener(CFGChangeListener listener, Object reference) { + if (!BROADCAST_NOTIFICATIONS || listener != reference) + Utils.addElementToMapSet(objectListeners, reference, listener); + } + + @Override + public void registerListener(CFGChangeListener listener) { + synchronized (globalListeners) { + if (!globalListeners.contains(listener)) + globalListeners.add(listener); + } + } + + @Override + public void unregisterListener(CFGChangeListener listener, Object reference) { + if (!BROADCAST_NOTIFICATIONS || listener != reference) + Utils.removeElementFromMapSet(objectListeners, reference, listener); + } + + @Override + public void unregisterListener(CFGChangeListener listener) { + synchronized (globalListeners) { + globalListeners.remove(listener); + } + } + + /** + * Notifies all registered listeners that an object reference has changed. + * @param oldObject The old object that is replaced + * @param newObject The object that takes the place of the old one + */ + protected void notifyReferenceChanged(Object oldObject, Object newObject) { + // Avoid spurious notifications + if (oldObject == newObject) + return; + + Set invokedListeners = new HashSet(1000); + + // Get the wrapper for the old object. If we broadcast notifications, we + // directly inform this object. + try { + UpdatableWrapper wrapper = this.wrappedObjects.get(oldObject); + if (BROADCAST_NOTIFICATIONS && wrapper != null) { + wrapper.notifyReferenceChanged(oldObject, newObject); + invokedListeners.add(wrapper); + } + + // Notify all explicitly registered object listeners + Set objListeners = objectListeners.get(oldObject); + if (objListeners != null) { + for (CFGChangeListener listener : objListeners) { + if (listener != null && invokedListeners.add(listener)) + listener.notifyReferenceChanged(oldObject, newObject); + } + + // Make sure that we don't loose track of our listeners. Expired + // listeners for gc'ed objects will automatically be removed by + // the WeakHashMap. + objectListeners.put(newObject, objListeners); + } + + // Notify the global listeners that have not yet been notified as + // object listeners + for (CFGChangeListener listener : globalListeners) + if (!invokedListeners.contains(listener)) + listener.notifyReferenceChanged(oldObject, newObject); + + // We must also update our list of wrapped objects + this.wrappedObjects.put(newObject, wrapper); +// this.wrappedObjects.remove(oldObject); + } catch (ExecutionException e) { + System.err.println("Could not wrap object"); + e.printStackTrace(); + } + } + + /** + * Sets a safe point, i.e. creates a backup of all current wrapped object + * references so that they can still be accessed after an update. + */ + protected void setSafepoint() { + for (UpdatableWrapper uw : this.wrappedObjects.asMap().values()) + uw.setSafepoint(); + } + + @SuppressWarnings("unchecked") + @Override + public UpdatableWrapper wrapWeak(X obj) { + assert obj != null; + try { + return (UpdatableWrapper) this.wrappedObjects.get(obj); + } catch (ExecutionException e) { + System.err.println("Could not wrap object"); + e.printStackTrace(); + return null; + } + } + + @Override + public List> wrapWeak(List list) { + assert list != null; + List> resList = new ArrayList>(); + for (X x : list) + resList.add(wrapWeak(x)); + return resList; + } + + @Override + public Set> wrapWeak(Set set) { + assert set != null; + Set> resSet = new HashSet>(set.size()); + for (X x : set) + resSet.add(wrapWeak(x)); + return resSet; + } + + @Override + public void mergeWrappers(InterproceduralCFG otherCfg) { + if (!(otherCfg instanceof AbstractUpdatableInterproceduralCFG)) + throw new RuntimeException("Unexpected control flow graph type"); + + AbstractUpdatableInterproceduralCFG other = + (AbstractUpdatableInterproceduralCFG) otherCfg; + this.wrappedObjects.asMap().putAll(other.wrappedObjects.asMap()); + } + +} diff --git a/src/soot/jimple/interproc/incremental/CFGChangeListener.java b/src/soot/jimple/interproc/incremental/CFGChangeListener.java new file mode 100644 index 0000000..8576fd4 --- /dev/null +++ b/src/soot/jimple/interproc/incremental/CFGChangeListener.java @@ -0,0 +1,24 @@ +package soot.jimple.interproc.incremental; + +/** + * Listener interface for CFG change notifications. Classes can implement this + * interface and register at a change provider to be notified when CFG objects + * are replaced by semantically equivalent ones (i.e. the references are updated) + * @see {@link CFGChangeProvider}. + */ +public interface CFGChangeListener { + + /** + * Method that is called when an object reference is changed. + * Note that notifications may be distributed in an arbitrary order - do not + * rely on some notification arriving before or after another one. However, + * if is an update sequence for one object, e.g. a->b->c, you are assured to + * receive a->b before you receive b->c. + * Furthermore, note that notifications may be repeated. Do not assume + * uniqueness. + * @param oldObject The old object + * @param newObject The new object which shall take the place of the old one + */ + void notifyReferenceChanged(Object oldObject, Object newObject); + +} diff --git a/src/soot/jimple/interproc/incremental/CFGChangeProvider.java b/src/soot/jimple/interproc/incremental/CFGChangeProvider.java new file mode 100644 index 0000000..4bf10f0 --- /dev/null +++ b/src/soot/jimple/interproc/incremental/CFGChangeProvider.java @@ -0,0 +1,40 @@ +package soot.jimple.interproc.incremental; + +/** + * Provides object reference changs to the control-flow graph to registered + * listeners. The listeners are called whenever an object is replaced by a + * semantically equivalent one which is supposed to the place of the old one. + */ +public interface CFGChangeProvider { + + /** + * Registers a listener that will be called whenever the given object is + * replaced. Note that listeners are unordered - do not rely on receiving + * notifications before or after some other listener. + * @param listener The listener to be called upon reference update + * @param reference The object that shall be monitored for reference updated + */ + public void registerListener(CFGChangeListener listener, Object reference); + + /** + * Registers a listener that will be called whenever any object is replaced. + * Note that listeners are unordered - do not rely on receiving notifications + * before or after some other listener. + * @param listener The listener to be called upon reference update + */ + public void registerListener(CFGChangeListener listener); + + /** + * Unregisters a listener for a single object + * @param listener The listener to be removed + * @param reference The object for which the listener shall be removed + */ + public void unregisterListener(CFGChangeListener listener, Object reference); + + /** + * Unregisters a global listener for all objects + * @param listener The listener to be removed + */ + public void unregisterListener(CFGChangeListener listener); + +} diff --git a/src/soot/jimple/interproc/incremental/DefaultUpdatableWrapper.java b/src/soot/jimple/interproc/incremental/DefaultUpdatableWrapper.java new file mode 100644 index 0000000..55ddd9a --- /dev/null +++ b/src/soot/jimple/interproc/incremental/DefaultUpdatableWrapper.java @@ -0,0 +1,83 @@ +package soot.jimple.interproc.incremental; + +/** + * Wrapper class for decoupling arbitrary objects and making their references + * exchangeable. This class holds a strong reference to object being wrapped. + * + * @param The type of object to wrap. + */ +public class DefaultUpdatableWrapper implements UpdatableWrapper { + + private N contents; + private N previousContents; + private int updateCount = 0; + + /** + * Creates a new instance of the UpdatableWrapper class. + * @param n The object to be wrapped + */ + public DefaultUpdatableWrapper(N n) { + this.contents = n; + this.previousContents = null; + } + + @Override + public N getContents() { + return this.contents; + } + + @Override + public N getPreviousContents() { + return this.previousContents; + } + + @SuppressWarnings("unchecked") + @Override + public void notifyReferenceChanged(Object oldObject, Object newObject) { + if (oldObject != newObject && contents == oldObject) { + contents = (N) newObject; + updateCount++; + } + } + + @Override + public String toString() { + return contents == null ? "" : contents.toString(); + } + + /* + * The idea that two wrappers are equal if the respective wrapped objects + * are equal has the nasty consequence that the hash code depends on the + * object's state - if the object is used as a key in a hash map, the + * behavior of the map becomes undefined. + * + @Override + public boolean equals(Object another) { + if (super.equals(another)) + return true; + if (another == null) + return false; + if (!(another instanceof UpdatableWrapper)) + return false; + @SuppressWarnings("unchecked") + UpdatableWrapper other = (UpdatableWrapper) another; + return this.contents.equals(other.contents); + } + + @Override + public int hashCode() { + return contents == null ? 0 : contents.hashCode(); + } + */ + + @Override + public void setSafepoint() { + this.previousContents = this.contents; + } + + @Override + public boolean hasPreviousContents() { + return this.previousContents != null; + } + +} diff --git a/src/soot/jimple/interproc/incremental/SceneDiff.java b/src/soot/jimple/interproc/incremental/SceneDiff.java new file mode 100644 index 0000000..64a03cb --- /dev/null +++ b/src/soot/jimple/interproc/incremental/SceneDiff.java @@ -0,0 +1,1140 @@ +package soot.jimple.interproc.incremental; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import soot.MethodOrMethodContext; +import soot.Scene; +import soot.SootClass; +import soot.SootField; +import soot.SootMethod; +import soot.jimple.interproc.ifds.utils.Utils; +import soot.jimple.toolkits.callgraph.Edge; +import soot.jimple.toolkits.callgraph.EdgePredicate; +import soot.jimple.toolkits.callgraph.Filter; +import soot.jimple.toolkits.callgraph.ReachableMethods; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.common.collect.Table.Cell; + +/** + * Class that captures the differences in the scene during an incremental build. Also provides some methods to test for equality. + */ +public class SceneDiff { + + private final static boolean DIFF_ALL_CLASSES = false; + + /** + * Map between the names of all classes in the scene and the actual classes. It is updated with every build. Classes in here are said to be equal if their names are equal. + */ + private Map classNameToClass = new HashMap(); + private Table methodBodies = HashBasedTable.create(); + + /** + * Returns true if the SceneDiff was initialized, false otherwise. A SceneDiff is initialized iff a full build was started in the past or an incremental build has succeeded. + * + * @return true if the SceneDiff was initialized, false otherwise + */ + public boolean isInitialized() { + // TODO actually we should persist this data so that it is still available when we close and re-open a project + return classNameToClass != null; + } + + /** + * Does a full build on the SceneDiff. Since there is nothing to diff, the method only initializes the SceneDiff. + */ + public void fullBuild() { + classNameToClass.clear(); + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(Scene.v().getCallGraph(), eps.iterator(), new EdgeFilter()); + reachableMethods.update(); + + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + SootClass c = m.getDeclaringClass(); + assert c.isInScene() : "Class not in scene: " + c.getName(); + + if (m.hasActiveBody()) { + SootClass oldClass = classNameToClass.put(c.getName(), c); + if (oldClass != null && oldClass != c && !oldClass.equals(c)) + throw new RuntimeException("Class name conflict for " + c + " <> " + oldClass); + this.methodBodies.put(c, m, m.getActiveBody().toString().hashCode()); + } + } + } + + /** + * Gets all classes that are part of the saved snapshot + * @return All classes known to this SceneDiff object + */ + public Collection getClassesInDiff() { + return this.classNameToClass.values(); + } + + //retains only callers that are explicit call sites or Thread.start() + protected static class EdgeFilter extends Filter { + protected EdgeFilter() { + super(new EdgePredicate() { + public boolean want(Edge e) { + return e.kind().isExplicit() || e.kind().isThread(); + } + }); + } + } + + /** + * Does an incremental build on the SceneDiff. Returns the difference to the last program that was built. + * + * @return A ProgramDiffNode that contains the differences to the last program that was built + */ + public ProgramDiffNode incrementalBuild() { + List eps = new ArrayList(); + eps.addAll(Scene.v().getEntryPoints()); + ReachableMethods reachableMethods = new ReachableMethods(Scene.v().getCallGraph(), eps.iterator(), new EdgeFilter()); + reachableMethods.update(); + + HashMap newClassNameToClass = new HashMap(); + for(Iterator iter = reachableMethods.listener(); iter.hasNext(); ) { + SootMethod m = iter.next().method(); + SootClass c = m.getDeclaringClass(); + assert c.isInScene(); + if (m.hasActiveBody()) { + SootClass oldClass = newClassNameToClass.put(c.getName(), c); + if (oldClass != null && oldClass != c) + throw new RuntimeException("Class name inconsistency"); + } + } + + /* + HashMap newClassNameToClass = new HashMap(); + for (SootClass c : Scene.v().getClasses()) + newClassNameToClass.put(c.getName(), c); + */ + + Map> oldMethods = new HashMap>(methodBodies.size()); + for (Cell cell : methodBodies.cellSet()) + Utils.addElementToMapList(oldMethods, cell.getRowKey(), cell.getColumnKey()); + ProgramDiffNode programDiff = new ProgramDiffNode(this.classNameToClass, oldMethods); + + // check for added classes + for (SootClass newClass : newClassNameToClass.values()) + if (!classNameToClass.containsKey(newClass.getName())) + programDiff.addDiffNode(new ClassDiffNode(null, newClass, DiffType.ADDED)); + for (SootClass oldClass : classNameToClass.values()) { + // check for removed classes + if (!newClassNameToClass.containsKey(oldClass.getName())) + programDiff.addDiffNode(new ClassDiffNode(oldClass, null, DiffType.REMOVED)); + else { + // check changed classes + SootClass newClass = newClassNameToClass.get(oldClass.getName()); + /* only diff contents of "application classes" because other classes cannot change anyway. + * SootClass.isApplicationClass() returns true only if the class is in the scene. + * Only one of oldClass and newClass is in the scene (should be newClass, but oldClass.isInScene() == true), + * hence the "or" in the if clause. + */ + if (DIFF_ALL_CLASSES || newClass.isApplicationClass() || oldClass.isApplicationClass()) { + ClassDiffNode classDiff = diffClasses(oldClass, newClass, reachableMethods); + if (classDiff != null) + programDiff.addDiffNode(classDiff); + } + } + } + // update our cache + classNameToClass = newClassNameToClass; + return programDiff; + } + + /** + * Generates a ClassDiffNode that contains the difference between the two given classes. + * + * @param oldClass the old class + * @param newClass the new class + * @param reachableMethods Listener for the reachable methods in the new class + * @return a ClassDiffNode that contains the difference between the two given classes, or null if there is none + */ + private ClassDiffNode diffClasses(SootClass oldClass, SootClass newClass, ReachableMethods reachableMethods) { + ClassDiffNode classDiff = new ClassDiffNode(oldClass, newClass, DiffType.CHANGED); + diffFields(oldClass, newClass, classDiff); + diffMethods(oldClass, newClass, reachableMethods, classDiff); + diffSuperClass(oldClass, newClass, classDiff); + diffInterfaces(oldClass, newClass, classDiff); + if (classDiff.getFieldDiffs().isEmpty() + && classDiff.getMethodDiffs().isEmpty() + && classDiff.getSuperClassDiffs().isEmpty() + && classDiff.getInterfacesDiffs().isEmpty()) + return null; + return classDiff; + } + + /** + * Generates the differences between the methods in the two given classes (methods added, changed or removed). + * + * @param oldClass the old class + * @param newClass the new class + * @param reachableMethods Listener for the reachable methods in the new class + * @param classDiff the ClassDiffNode that should contain the differences + */ + private void diffMethods(SootClass oldClass, SootClass newClass, ReachableMethods reachableMethods, ClassDiffNode classDiff) { + // construct the wrapper maps used for tracking similar, but not necessarily equal methods + Map oldSootMethods = + new HashMap(); + Map newSootMethods = + new HashMap(); + + for (SootMethod oldMethod : this.methodBodies.row(oldClass).keySet()) { + SootMethodEqualsWrapper wrapper = new SootMethodEqualsWrapper(oldMethod); + oldSootMethods.put(wrapper, oldMethod); + } + for (SootMethod newMethod : newClass.getMethods()) + if (newMethod.hasActiveBody() && reachableMethods.contains(newMethod)) { + SootMethodEqualsWrapper wrapper = new SootMethodEqualsWrapper(newMethod); + newSootMethods.put(wrapper, newMethod); + } + + // check for addition and removal + for (SootMethodEqualsWrapper wrapper : newSootMethods.keySet()) { + SootMethod newMethod = wrapper.getMethod(); + SootMethod matchingOldMethod = oldSootMethods.get(wrapper); + if (matchingOldMethod == null && !newMethod.hasActiveBody()) + continue; + + boolean isNewMethod = (matchingOldMethod == null); + if (!isNewMethod) { + Integer oldBody = this.methodBodies.get(oldClass, matchingOldMethod); + if (newMethod.hasActiveBody() && oldBody == null) + isNewMethod = true; + else if (!equal(newMethod, oldBody, matchingOldMethod.getSubSignature())) { + classDiff.addMethodDiff(new MethodDiffNode(matchingOldMethod, newMethod, DiffType.CHANGED)); + + // Remove the old body and method reference + this.methodBodies.remove(oldClass, matchingOldMethod); + + if(newMethod.getName().contains("addListener")) + System.out.println("x"); + } + } + if (isNewMethod) + // did not find a matching old method, hence the method must have been added + classDiff.addMethodDiff(new MethodDiffNode(null, newMethod, DiffType.ADDED)); + + // Put the new body and class reference + if (newMethod.hasActiveBody()) + this.methodBodies.put(newClass, newMethod, newMethod.getActiveBody().toString().hashCode()); + } + // check for removal + for (SootMethod oldMethod : oldSootMethods.values()) { + if (!oldMethod.hasActiveBody()) + continue; + SootMethod newMethod = newSootMethods.get(new SootMethodEqualsWrapper(oldMethod)); + if (newMethod == null) { + // did not find a matching new method, hence the method must have been removed + classDiff.addMethodDiff(new MethodDiffNode(oldMethod, null, DiffType.REMOVED)); + + // Remove the old body and method reference + this.methodBodies.remove(oldClass, oldMethod); + } + } + } + + /** + * Generates the differences between the fields in the two given classes (fields added, changed or removed). + * + * @param oldClass the old class + * @param newClass the new class + * @param classDiff the ClassDiffNode that should contain the differences + */ + private void diffFields(SootClass oldClass, SootClass newClass, ClassDiffNode classDiff) { + // construct the wrapper maps used for tracking similar, but not necessarily equal fields + Map oldSootFields = new HashMap(); + Map newSootFields = new HashMap(); + for (SootField oldField : oldClass.getFields()) { + SootFieldEqualsWrapper wrapper = new SootFieldEqualsWrapper(oldField); + oldSootFields.put(wrapper, wrapper); + } + for (SootField newField : newClass.getFields()) { + SootFieldEqualsWrapper wrapper = new SootFieldEqualsWrapper(newField); + newSootFields.put(wrapper, wrapper); + } + // check for addition and change + for (SootField newField : newClass.getFields()) { + SootFieldEqualsWrapper wrapper = new SootFieldEqualsWrapper(newField); + SootFieldEqualsWrapper matchingOldFieldWrapper = oldSootFields.get(wrapper); + if (matchingOldFieldWrapper != null) { + SootField matchingOldField = matchingOldFieldWrapper.getField(); + if (!equal(matchingOldField, newField)) + classDiff.addFieldDiff(new FieldDiffNode(matchingOldField, newField, DiffType.CHANGED)); + } else + // did not find a matching old field, hence the field must have been added + classDiff.addFieldDiff(new FieldDiffNode(null, newField, DiffType.ADDED)); + } + // check for removal + for (SootField oldField : oldClass.getFields()) { + SootFieldEqualsWrapper wrapper = new SootFieldEqualsWrapper(oldField); + if (!newSootFields.containsKey(wrapper)) + // did not find a matching new field, hence the field must have been removed + classDiff.addFieldDiff(new FieldDiffNode(oldField, null, DiffType.REMOVED)); + } + } + + /** + * Generates the differences between the interfaces implemented by the two given classes (implemented interfaces added or removed and if the interfaces themselves changed). + * + * @param oldClass the old class + * @param newClass the new class + * @param classDiff the ClassDiffNode that should contain the differences + */ + private void diffInterfaces(SootClass oldClass, SootClass newClass, ClassDiffNode classDiff) { + // construct the wrapper maps used for tracking similar, but not necessarily equal interfaces + Map oldSootInterfaces = new HashMap(); + Map newSootInterfaces = new HashMap(); + for (SootClass oldInterface : oldClass.getInterfaces()) { + SootClassEqualsWrapper wrapper = new SootClassEqualsWrapper(oldInterface); + oldSootInterfaces.put(wrapper, wrapper); + } + for (SootClass newInterface : newClass.getInterfaces()) { + SootClassEqualsWrapper wrapper = new SootClassEqualsWrapper(newInterface); + newSootInterfaces.put(wrapper, wrapper); + } + // check for addition and change + for (SootClass newInterface : newClass.getInterfaces()) { + SootClassEqualsWrapper wrapper = new SootClassEqualsWrapper(newInterface); + SootClassEqualsWrapper matchingOldClassWrapper = oldSootInterfaces.get(wrapper); + if (matchingOldClassWrapper != null) { + SootClass matchingOldInterface = matchingOldClassWrapper.getClazz(); + if (!equal(matchingOldInterface, newInterface)) + classDiff.addInterfacesDiff(new DependencyDiffNode(matchingOldInterface, newInterface, DiffType.CHANGED)); + } else + // did not find a matching old interface, hence the interface must have been added + classDiff.addInterfacesDiff(new DependencyDiffNode(null, newInterface, DiffType.ADDED)); + } + // check for removal + for (SootClass oldInterface : oldClass.getInterfaces()) { + SootClassEqualsWrapper wrapper = new SootClassEqualsWrapper(oldInterface); + if (!newSootInterfaces.containsKey(wrapper)) + // did not find a matching new interface, hence the interface must have been removed + classDiff.addInterfacesDiff(new DependencyDiffNode(oldInterface, null, DiffType.REMOVED)); + } + } + + /** + * Generates the differences between the superclasses of the two given classes (superclass added or removed and if the superclass itself changed).
+ *
+ * NOTE: The implicit superclass java.lang.Object is treated like every other superclass. + * + * @param oldClass the old class + * @param newClass the new class + * @param classDiff the ClassDiffNode that should contain the differences + */ + private void diffSuperClass(SootClass oldClass, SootClass newClass, ClassDiffNode classDiff) { + if (!oldClass.hasSuperclass()) { + if (newClass.hasSuperclass()) { + SootClass newSuperClass = newClass.getSuperclass(); + classDiff.addSuperClassDiff(new DependencyDiffNode(null, newSuperClass, DiffType.ADDED)); + } + // if both classes do not have a super class, nothing changed + } else { + SootClass oldSuperClass = oldClass.getSuperclass(); + if (!newClass.hasSuperclass()) + // the old class had a super class, but the new one has not + classDiff.addSuperClassDiff(new DependencyDiffNode(oldSuperClass, null, DiffType.REMOVED)); + else { + // both classes have a super class + SootClass newSuperClass = newClass.getSuperclass(); + if (!equal(oldSuperClass, newSuperClass)) + // the super classes differ + classDiff.addSuperClassDiff(new DependencyDiffNode(oldSuperClass, newSuperClass, DiffType.CHANGED)); + // if the super classes are equal, nothing changed + } + } + } + + /** + * Tests the two given SootMethods for equality. Two SootMethods are considered + * equal iff their sub signatures and their active bodies (if any) match. + * + * @param m1 the first SootMethod to test + * @param body The hash of the second SootMethod's body + * @param subSignature The second SootMethod's sub-signature + * @return true if the methods are considered equal, false otherwise + */ + public boolean equal(SootMethod m1, Integer body, String subSiganture) { + if (!m1.getSubSignature().equals(subSiganture)) + return false; + if ((m1.hasActiveBody() && body == null) || (!m1.hasActiveBody() && body != null)) + return false; + if (!m1.hasActiveBody()) + return true; + return body.equals(m1.getActiveBody().toString().hashCode()); + } + + /** + * Tests the two given SootFields for equality. Two SootFields are considered equal iff their signatures match. + * + * @param f1 the first SootField to test + * @param f2 the second SootField to test + * @return true, if the fields are considered equal, false otherwise + */ + public boolean equal(SootField f1, SootField f2) { + return f1.getSignature().equals(f2.getSignature()); + } + + /** + * Tests the two given SootClasses for equality. Two SootClasses are considered equal iff their names, fields and methods match. + * + * @param oldClass the first SootClass to test + * @param newClass the second SootClass to test + * @return true, if the classes are considered equal, false otherwise + */ + public boolean equal(SootClass oldClass, SootClass newClass) { + if (!oldClass.getName().equals(newClass.getName())) + return false; + if (oldClass.getFieldCount() != newClass.getFieldCount() + || oldClass.getMethodCount() != newClass.getMethodCount()) + return false; + for (SootField f1 : oldClass.getFields()) { + SootField f2 = newClass.getField(f1.getSubSignature()); + if (f2 == null || !equal(f1, f2)) + return false; + } + for (SootMethod m1 : oldClass.getMethods()) { + SootMethod m2 = newClass.getMethod(m1.getSubSignature()); + if (m2 == null || !equal(m2, this.methodBodies.get(oldClass, m1), m1.getSubSignature())) + return false; + } + return true; + } + + /** + * A class that contains differences in a program. Essentially it is a set of {@link ClassDiffNode}s. + */ + public static class ProgramDiffNode implements Iterable { + + protected final Map classNameToClass = new HashMap(); + protected final Map> oldMethods; + + /** + * The set of ClassDiffNodes. + */ + protected Set diffClasses; + + /** + * Constructs an empty node. + * @param classNameToClass Mapping between class names and Soot classes. + * This object is copied, the ProgramDiffNode object does not keep a + * reference to the original. + * @param oldMethods Mapping between old classes and their respective + * methods. The object takes ownerships of this map which therefore + * must not be modified by the caller afterwards. + */ + public ProgramDiffNode(Map classNameToClass, + Map> oldMethods) { + diffClasses = new HashSet(); + this.classNameToClass.putAll(classNameToClass); + this.oldMethods = oldMethods; + } + + /** + * Adds the given class node to this program node. + * + * @param n the node to add + */ + public void addDiffNode(ClassDiffNode n) { + diffClasses.add(n); + } + + public Iterator iterator() { + return diffClasses.iterator(); + } + + @Override + public String toString() { + if (diffClasses.isEmpty()) + return "NO CHANGES"; + else { + StringBuffer buff = new StringBuffer("CHANGES: \n"); + for (ClassDiffNode classDiff : diffClasses) + buff.append(classDiff.toString()); + return buff.toString(); + } + } + + /** + * Gets whether this diff node is empty, i.e. the program has not been + * changed. + * @return True if there are no changes in this diff node, otherwise + * false. + */ + public boolean isEmpty() { + return diffClasses.isEmpty(); + } + + /** + * Gets the changes for the given Soot class. + * @param sc The class to check for changes. This can either be the new + * class or the old one. + * @return The changes made to the given Soot class or null if the class + * was not changed. + */ + public ClassDiffNode getClassChanges(SootClass sc) { + for (ClassDiffNode cd : this.diffClasses) + if (cd.getOldClass() == sc || cd.getNewClass() == sc) + return cd; + return null; + } + + /** + * Gets the old class from before the update that corresponds to the given + * new class after the update. + * @param sc The new class for which to get the corresponding old one. + * @return The old class that corresponds to the given new one if it has + * been found, otherwise null. + */ + public SootClass getOldClassFor(SootClass sc) { + return this.classNameToClass.get(sc.getName()); + } + + /** + * Gets the old method before the update that corresponds to the given method + * after the update. + * @param oldClass The old class in which to look for the old method + * @param newMethod The new method for which to get the corresponding old + * method + * @return The old method corresponding to the given new one if it has been + * found, otherwise null + */ + public SootMethod getOldMethodFor(SootClass oldClass, SootMethod newMethod) { + assert oldClass != null && newMethod != null; + Map oldSootMethods = + new HashMap(); + if (!this.oldMethods.containsKey(oldClass)) + throw new RuntimeException("Old class " + oldClass.getName() + " not found"); + for (SootMethod oldMethod : this.oldMethods.get(oldClass)) { + SootMethodEqualsWrapper wrapper = new SootMethodEqualsWrapper(oldMethod); + oldSootMethods.put(wrapper, oldMethod); + } + SootMethodEqualsWrapper wrapper = new SootMethodEqualsWrapper(newMethod); + return oldSootMethods.get(wrapper); + } + + /** + * Gets the old method before the update that corresponds to the given method + * after the update. + * @param newMethod The new method for which to get the corresponding old + * method + * @return The old method corresponding to the given new one if it has been + * found, otherwise null + */ + public SootMethod getOldMethodFor(SootMethod newMethod) { + SootClass oldClass = this.getOldClassFor(newMethod.getDeclaringClass()); + return this.getOldMethodFor(oldClass, newMethod); + } + } + + /** + * A class that contains differences between two classes. Essentially it is sets of {@link FieldDiffNode}s, {@link MethodDiffNode}s + * and two sets of {@link DependencyDiffNode}s for the super classes and implemented interfaces. + */ + public static class ClassDiffNode { + + /** + * The old class. + */ + protected SootClass oldClass; + + /** + * The new class. + */ + protected SootClass newClass; + + /** + * The type of difference between the two classes. + */ + protected DiffType diffType; + + /** + * The set of FieldDiffNodes. + */ + protected Set fieldDiffs; + + /** + * The set of MethodDiffNodes. + */ + protected Set methodDiffs; + + /** + * The set of DependencyDiffNodes for the super classes. + */ + protected Set superClassDiffs; + + /** + * The set of DependencyDiffNodes for the implemented interfaces. + */ + protected Set interfacesDiffs; + + /** + * Constructs a new node for the two given classes and the given type of difference between them. + * + * @param oldClass the old class + * @param newClass the new class + * @param diffType the type of difference + */ + public ClassDiffNode(SootClass oldClass, SootClass newClass, DiffType diffType) { + this.oldClass = oldClass; + this.newClass = newClass; + this.diffType = diffType; + this.fieldDiffs = new HashSet(); + this.methodDiffs = new HashSet(); + this.superClassDiffs = new HashSet(); + this.interfacesDiffs = new HashSet(); + } + + /** + * Adds the given field node to this class node. + * + * @param n the node to add + */ + public void addFieldDiff(FieldDiffNode n) { + fieldDiffs.add(n); + } + + /** + * Adds the given method node to this class node. + * + * @param n the node to add + */ + public void addMethodDiff(MethodDiffNode n) { + methodDiffs.add(n); + } + + /** + * Adds the given dependency node for the super classes to this class node. + * + * @param n the node to add + */ + public void addSuperClassDiff(DependencyDiffNode n) { + superClassDiffs.add(n); + } + + /** + * Adds the given dependency node for the implemented interfaces to this class node. + * + * @param n the node to add + */ + public void addInterfacesDiff(DependencyDiffNode n) { + interfacesDiffs.add(n); + } + + /** + * Returns the old class. + * + * @return the old class + */ + public SootClass getOldClass() { + return oldClass; + } + + /** + * Returns the new class. + * + * @return the new class + */ + public SootClass getNewClass() { + return newClass; + } + + /** + * Returns the type of difference between the two classes. + * + * @return the type of difference between the two classes + */ + public DiffType getDiffType() { + return diffType; + } + + /** + * Returns an unmodifiable set of the FieldDiffNodes. + * + * @return an unmodifiable set of the FieldDiffNodes + */ + public Set getFieldDiffs() { + return Collections.unmodifiableSet(fieldDiffs); // TODO why should this be unmodifiable? + } + + /** + * Returns the set of MethodDiffNodes. + * + * @return the set of MethoddiffNodes + */ + public Set getMethodDiffs() { + return methodDiffs; + } + + /** + * Gets the method diff for one specific method. + * @param sm The Soot method for which to get the diff information. This + * can either be the old method or the new one. + * @return The changes made to the given method if such changes have been + * found, otherwise null. + */ + public MethodDiffNode getMethodDiff(SootMethod sm) { + for (MethodDiffNode md : this.methodDiffs) + if (md.getOldMethod() == sm || md.getNewMethod() == sm) + return md; + return null; + } + + /** + * Returns the set of DependencyDiffNodes for the super classes. + * + * @return the set of DependencyDiffNodes for the super classes + */ + public Set getSuperClassDiffs() { + return superClassDiffs; + } + + /** + * Returns the set of DependencyDiffNodes for the implemented interfaces. + * + * @return the set of DependencyDiffNodes for the implemented interfaces + */ + public Set getInterfacesDiffs() { + return interfacesDiffs; + } + + @Override + public String toString() { + StringBuffer buff = new StringBuffer(diffType.toString()); + buff.append(" CLASS "); + buff.append(oldClass != null ? oldClass.getName() : newClass.getName()); + buff.append(": \n"); + for (FieldDiffNode fieldDiff : fieldDiffs) { + buff.append(fieldDiff.toString()); + buff.append("\n"); + } + for (MethodDiffNode methodDiff : methodDiffs) { + buff.append(methodDiff.toString()); + buff.append("\n"); + } + for (DependencyDiffNode superDiff : superClassDiffs) { + buff.append(superDiff.toString()); + buff.append("\n"); + } + for (DependencyDiffNode sigDiff : interfacesDiffs) { + buff.append(sigDiff.toString()); + buff.append("\n"); + } + return buff.toString(); + } + } + + /** + * A class that contains the differences between two fields. + */ + public static class FieldDiffNode { + + /** + * The old field. + */ + private final SootField oldField; + + /** + * The new field. + */ + private final SootField newField; + + /** + * The type of difference between the two fields. + */ + private final DiffType diffType; + + /** + * Constructs a new node for the two given fields and the given type of difference between them. + * + * @param oldField the old field + * @param newField the new field + * @param diffType the type of difference + */ + public FieldDiffNode(SootField oldField, SootField newField, DiffType diffType) { + this.oldField = oldField; + this.newField = newField; + this.diffType = diffType; + } + + /** + * Returns the old field. + * + * @return the old field + */ + public SootField getOldField() { + return oldField; + } + + /** + * Returns the new field. + * + * @return the new field + */ + public SootField getNewField() { + return newField; + } + + /** + * Returns the type of difference between the two fields. + * + * @return the type of difference between the two fields + */ + public DiffType getDiffType() { + return diffType; + } + + @Override + public String toString() { + StringBuffer buff = new StringBuffer(" " + diffType.toString()); + buff.append(" FIELD "); + buff.append(oldField != null ? oldField.getName() : newField.getName()); + return buff.toString(); + } + } + + /** + * A class that contains the difference between two methods. + */ + public static class MethodDiffNode { + + /** + * The old method. + */ + private final SootMethod oldMethod; + + /** + * The new method. + */ + private final SootMethod newMethod; + + /** + * The type of difference between the two methods. + */ + private final DiffType diffType; + + /** + * Constructs a new node for the two given methods and the given type of difference between them. + * + * @param oldMethod the old method + * @param newMethod the new method + * @param diffType the type of difference + */ + public MethodDiffNode(SootMethod oldMethod, SootMethod newMethod, DiffType diffType) { + this.oldMethod = oldMethod; + this.newMethod = newMethod; + this.diffType = diffType; + } + + /** + * Returns the old method. + * + * @return the old method + */ + public SootMethod getOldMethod() { + return oldMethod; + } + + /** + * Returns the new method. + * + * @return the new method + */ + public SootMethod getNewMethod() { + return newMethod; + } + + /** + * Returns the type of difference between the two methods. + * + * @return the type of difference between the two methods + */ + public DiffType getDiffType() { + return diffType; + } + + /** + * Gets the name of the method that has been changed + * @return The name of the changed method + */ + public String getMethodName() { + return this.oldMethod == null ? this.newMethod.getName() : this.oldMethod.getName(); + } + + @Override + public String toString() { + StringBuffer buff = new StringBuffer(" " + diffType.toString()); + buff.append(" METHOD "); + buff.append(oldMethod != null ? oldMethod.getSubSignature() : newMethod.getSubSignature()); + return buff.toString(); + } + } + + /** + * A class that contains the difference between two dependencies, that is super classes or implemented interfaces. + */ + public static class DependencyDiffNode { + + /** + * The old dependency. + */ + private final SootClass oldDependency; + + /** + * The new dependency. + */ + private final SootClass newDependency; + + /** + * The type of difference between the two dependencies. + */ + private final DiffType diffType; + + /** + * Constructs a new node for the two given dependencies and the given type of difference between them. + * + * @param oldDependency the old dependency + * @param newDependency the new dependency + * @param diffType the type of difference + */ + public DependencyDiffNode(SootClass oldDependency, SootClass newDependency, DiffType diffType) { + this.oldDependency = oldDependency; + this.newDependency = newDependency; + this.diffType = diffType; + } + + /** + * Returns the old dependency. + * + * @return the old dependency + */ + public SootClass getOldDependency() { + return oldDependency; + } + + /** + * Returns the new dependency. + * + * @return the new dependency + */ + public SootClass getNewDependency() { + return newDependency; + } + + /** + * Returns the type of difference between the two dependencies. + * + * @return the type of difference between the two dependencies + */ + public DiffType getDiffType() { + return diffType; + } + + @Override + public String toString() { + StringBuffer buff = new StringBuffer(" " + diffType.toString()); + buff.append(" DEPENDENCY "); + buff.append(oldDependency != null ? oldDependency.getName() : newDependency.getName()); + return buff.toString(); + } + } + + /** + * Wrapper for a SootField that is equal to another wrapper when both associated fields have the same name. + * This is useful when indexing on "similar" fields, where fields are similar when they have the same name. + */ + public static class SootFieldEqualsWrapper { + + /** + * The associated field. + */ + protected SootField f; + + /** + * Constructs a new wrapper for the given field. + * + * @param f the field + */ + public SootFieldEqualsWrapper(SootField f) { + this.f = f; + } + + /** + * Returns the associated field. + * + * @return the associated field + */ + public SootField getField() { + return f; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((f == null) ? 0 : f.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SootFieldEqualsWrapper other = (SootFieldEqualsWrapper) obj; + if (f == null) { + if (other.f != null) + return false; + } else if (!f.getName().equals(other.f.getName())) + return false; + return true; + } + + } + + /** + * Wrapper for a SootMethod that is equal to another wrapper when both associated methods have the same (sub-)signature. + * This is useful when indexing on "similar" methods, where fields are similar when they have the same signature. + */ + public static class SootMethodEqualsWrapper { + + /** + * The associated method. + */ + protected SootMethod m; + + /** + * Constructs a new wrapper for the given method. + * + * @param m the method + */ + public SootMethodEqualsWrapper(SootMethod m) { + this.m = m; + } + + /** + * Returns the associated method. + * + * @return the associated method + */ + public SootMethod getMethod() { + return m; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((m == null) ? 0 : m.getSubSignature().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj || super.equals(obj)) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SootMethodEqualsWrapper other = (SootMethodEqualsWrapper) obj; + if (m == null) + return other.m == null; + return m.getSubSignature().equals(other.m.getSubSignature()); + } + + } + + /** + * Wrapper for a SootClass that is equal to another wrapper when both associated classes have the same name. + * This is useful when indexing on "similar" classes, where classes are similar when they have the same name. + */ + public static class SootClassEqualsWrapper { + + /** + * The associated class. + */ + protected SootClass c; + + /** + * Constructs a new wrapper for the given class. + * + * @param c the class + */ + public SootClassEqualsWrapper(SootClass c) { + this.c = c; + } + + /** + * Returns the associated class. + * + * @return the associated class + */ + public SootClass getClazz() { + return c; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((c == null) ? 0 : c.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SootClassEqualsWrapper other = (SootClassEqualsWrapper) obj; + if (c == null) { + if (other.c != null) + return false; + } else if (!c.getName().equals(other.c.getName())) + return false; + return true; + } + + } + + /** + * A enumeration of different types of changes. + */ + public enum DiffType { + + /** + * If something was added. + */ + ADDED, + + /** + * If something was removed. + */ + REMOVED, + + /** + * If something has changed. + */ + CHANGED; + } + +} diff --git a/src/soot/jimple/interproc/incremental/UpdatableWrapper.java b/src/soot/jimple/interproc/incremental/UpdatableWrapper.java new file mode 100644 index 0000000..205c25c --- /dev/null +++ b/src/soot/jimple/interproc/incremental/UpdatableWrapper.java @@ -0,0 +1,39 @@ +package soot.jimple.interproc.incremental; + +/** + * Common interface for all sorts of wrappers which decouple arbitrary objects + * and make their references exchangeable. + * + * @param The type of object to wrap. + */ +public interface UpdatableWrapper extends CFGChangeListener { + + + /** + * Gets the object being wrapped. + * @return The object inside this wrapper + */ + public N getContents(); + + /** + * Gets the object that was wrapped at the last safe point, i.e. when + * setSafepoint() was last called. + * @return The previous contents of this wrapper. + */ + public N getPreviousContents(); + + /** + * Gets whether this wrapper contains the previous reference. This is + * equivalent to checking whether a safe point has been created. + * @return True if this wrapper stores a previous reference, otherwise + * false. + */ + boolean hasPreviousContents(); + + /** + * Creates a new safe point. This copies the current contents into the + * "previousContents" property for later access. + */ + public void setSafepoint(); + +} diff --git a/src/soot/jimple/interproc/incremental/WeakUpdatableWrapper.java b/src/soot/jimple/interproc/incremental/WeakUpdatableWrapper.java new file mode 100644 index 0000000..a2db8dc --- /dev/null +++ b/src/soot/jimple/interproc/incremental/WeakUpdatableWrapper.java @@ -0,0 +1,64 @@ +package soot.jimple.interproc.incremental; + +import java.lang.ref.WeakReference; + +/** + * Wrapper class for decoupling arbitrary objects and making their references + * exchangeable. This object holds a weak reference to the object being + * wrapped, allowing for garbage collection. + * + * @param The type of object to wrap. + */ +public class WeakUpdatableWrapper implements UpdatableWrapper { + + private WeakReference contents; + private WeakReference previousContents; + private int updateCount = 0; + + /** + * Creates a new instance of the UpdatableWrapper class. + * @param n The object to be wrapped + */ + public WeakUpdatableWrapper(N n) { + this.contents = new WeakReference(n); + this.previousContents = null; + } + + @Override + public N getContents() { + return this.contents.get(); + } + + @Override + public N getPreviousContents() { + if (this.previousContents == null) + throw new RuntimeException("No safepoint available on this wrapper. Call " + + "setSafepoint() first."); + return this.previousContents.get(); + } + + @SuppressWarnings("unchecked") + @Override + public void notifyReferenceChanged(Object oldObject, Object newObject) { + if (oldObject != newObject && contents.get() == oldObject) { + contents = new WeakReference((N) newObject); + updateCount++; + } + } + + @Override + public String toString() { + return contents.get() == null ? "" : contents.get().toString(); + } + + @Override + public void setSafepoint() { + this.previousContents = this.contents; + } + + @Override + public boolean hasPreviousContents() { + return this.previousContents != null; + } + +}