changes for handling of 'callbacks' in dynamic CGs

This commit is contained in:
Julian Dolby 2015-06-28 17:06:21 -04:00
parent 93a522eecd
commit a6a060ed25
12 changed files with 229 additions and 63 deletions

View File

@ -2,6 +2,6 @@
<classpath>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@ -106,7 +106,7 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disa
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.7
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0

View File

@ -0,0 +1,33 @@
package dynamicCG;
import java.util.HashSet;
import java.util.Set;
public class CallbacksMainClass {
private static CallbacksMainClass instance;
static {
callSomethingStatic();
}
public static void main(String[] args) {
Set<CallbacksMainClass> junk = new HashSet<CallbacksMainClass>();
junk.add(instance);
System.err.println(junk.iterator().next().toString());
}
private static void callSomethingStatic() {
instance = new CallbacksMainClass();
}
@Override
public String toString() {
return callSomething();
}
private String callSomething() {
return "string";
}
}

View File

@ -1,5 +1,3 @@
apple\/.*
com\/apple\/.*
java\/awt\/.*
javax\/swing\/.*
sun\/awt\/.*
@ -13,6 +11,8 @@ com\/ibm\/security\/.*
org\/apache\/xerces\/.*
dalvik\/.*
java\/io\/ObjectStreamClass*
apple\/.*
com\/apple\/.*
jdk\/.*
org\/omg\/.*
org\/w3c\/.*

View File

@ -32,13 +32,11 @@ import com.ibm.wala.util.CancelException;
public class DynamicCallGraphTest extends DynamicCallGraphTestBase {
private static String testJarLocation = getClasspathEntry("com.ibm.wala.core.testdata");
private static String testMain = "dynamicCG.MainClass";
private CallGraph staticCG(String exclusionsFile) throws IOException, ClassHierarchyException, IllegalArgumentException, CancelException {
private CallGraph staticCG(String mainClass, String exclusionsFile) throws IOException, ClassHierarchyException, IllegalArgumentException, CancelException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.WALA_TESTDATA, exclusionsFile != null? exclusionsFile: CallGraphTestUtil.REGRESSION_EXCLUSIONS);
ClassHierarchy cha = ClassHierarchy.make(scope);
Iterable<Entrypoint> entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, "LdynamicCG/MainClass");
Iterable<Entrypoint> entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, mainClass);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
return CallGraphTestUtil.buildZeroOneCFA(options, new AnalysisCache(), cha, scope, false);
}
@ -46,16 +44,24 @@ public class DynamicCallGraphTest extends DynamicCallGraphTestBase {
@Test
public void testGraph() throws IOException, ClassNotFoundException, InvalidClassFileException, FailureException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassHierarchyException, CancelException, InterruptedException {
instrument(testJarLocation);
run(testMain, null);
CallGraph staticCG = staticCG(null);
run("dynamicCG.MainClass", null);
CallGraph staticCG = staticCG("LdynamicCG/MainClass", null);
checkEdges(staticCG);
}
@Test
public void testCallbacks() throws IOException, ClassNotFoundException, InvalidClassFileException, FailureException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassHierarchyException, CancelException, InterruptedException {
instrument(testJarLocation);
run("dynamicCG.CallbacksMainClass", null);
CallGraph staticCG = staticCG("LdynamicCG/CallbacksMainClass", null);
checkEdges(staticCG);
}
@Test
public void testExclusions() throws IOException, ClassNotFoundException, InvalidClassFileException, FailureException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassHierarchyException, CancelException, InterruptedException {
instrument(testJarLocation);
run(testMain, "ShrikeTestExclusions.txt");
CallGraph staticCG = staticCG("ShrikeTestExclusions.txt");
run("dynamicCG.MainClass", "ShrikeTestExclusions.txt");
CallGraph staticCG = staticCG("LdynamicCG/MainClass", "ShrikeTestExclusions.txt");
checkEdges(staticCG);
}

View File

@ -61,9 +61,9 @@ public abstract class DynamicCallGraphTestBase extends WalaTestCase {
private boolean instrumentedJarBuilt = false;
private static String instrumentedJarLocation = System.getProperty("java.io.tmpdir") + File.separator + "test.jar";
private String instrumentedJarLocation = System.getProperty("java.io.tmpdir") + File.separator + "test.jar";
private static String cgLocation = System.getProperty("java.io.tmpdir") + File.separator + "cg.txt";
private String cgLocation = System.getProperty("java.io.tmpdir") + File.separator + "cg.txt";
protected void instrument(String testJarLocation) throws IOException, ClassNotFoundException, InvalidClassFileException, FailureException {
if (! instrumentedJarBuilt) {
@ -116,8 +116,14 @@ public abstract class DynamicCallGraphTestBase extends WalaTestCase {
childJvm.setFailonerror(true);
childJvm.setFork(true);
if (new File(cgLocation).exists()) {
new File(cgLocation).delete();
}
childJvm.init();
Process x = Runtime.getRuntime().exec(childJvm.getCommandLine().toString());
String commandLine = childJvm.getCommandLine().toString();
System.err.println(commandLine);
Process x = Runtime.getRuntime().exec(commandLine);
x.waitFor();
Assert.assertTrue("expected to create call graph", new File(cgLocation).exists());
@ -149,7 +155,7 @@ public abstract class DynamicCallGraphTestBase extends WalaTestCase {
Pair<CGNode,CGNode> x = Pair.make(caller, callee);
if (! edges.contains(x)) {
edges.add(x);
System.err.println("found expected edge" + caller + " --> " + callee);
System.err.println("found expected edge " + caller + " --> " + callee);
}
}
}
@ -189,6 +195,8 @@ public abstract class DynamicCallGraphTestBase extends WalaTestCase {
String callerClass = edge.nextToken();
if ("root".equals(callerClass)) {
caller = staticCG.getFakeRootNode();
} else if ("callbacks".equals(callerClass)) {
continue loop;
} else {
String callerMethod = edge.nextToken();
MethodReference callerRef = MethodReference.findOrCreate(TypeReference.findOrCreate(ClassLoaderReference.Application, "L" + callerClass), Selector.make(callerMethod));

View File

@ -51,7 +51,6 @@ import com.ibm.wala.util.functions.Function;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetUtil;
@SuppressWarnings("deprecation")
public class MethodHandles {
private static final IntSet self = IntSetUtil.make(new int[0]);

View File

@ -14,6 +14,7 @@ import java.lang.ref.SoftReference;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
@ -32,9 +33,9 @@ import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.ssa.DefUse;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.util.CancelException;
import com.ibm.wala.util.Predicate;
import com.ibm.wala.util.collections.ComposedIterator;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.Predicate;
import com.ibm.wala.util.collections.FilterIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
@ -116,7 +117,8 @@ public class CHACallGraph extends BasicCallGraph<CHAContextInterpreter> {
for(Entrypoint e : entrypoints) {
root.addTarget(e.makeSite(programCounter++), null);
}
closure(root, true);
newNodes.push(root);
closure();
isInitialized = true;
}
@ -206,24 +208,6 @@ public class CHACallGraph extends BasicCallGraph<CHAContextInterpreter> {
protected CGNode makeFakeWorldClinitNode() throws CancelException {
return new CHARootNode(new FakeWorldClinitMethod(cha, options, cache), Everywhere.EVERYWHERE);
}
private void closure(CGNode n, boolean fromRoot) throws CancelException {
for(Iterator<CallSiteReference> sites = n.iterateCallSites(); sites.hasNext(); ) {
Iterator<IMethod> methods = getPossibleTargets(sites.next());
while (methods.hasNext()) {
IMethod target = methods.next();
if (!target.isAbstract()) {
CGNode callee = getNode(target, Everywhere.EVERYWHERE);
if (callee == null) {
callee = findOrCreateNode(target, Everywhere.EVERYWHERE);
if (fromRoot) {
registerEntrypoint(callee);
}
}
}
}
}
}
private int clinitPC = 0;
@ -247,12 +231,35 @@ public class CHACallGraph extends BasicCallGraph<CHAContextInterpreter> {
return n;
}
private Stack<CGNode> newNodes = new Stack<CGNode>();
private void closure() throws CancelException {
while (! newNodes.isEmpty()) {
CGNode n = newNodes.pop();
for(Iterator<CallSiteReference> sites = n.iterateCallSites(); sites.hasNext(); ) {
Iterator<IMethod> methods = getPossibleTargets(sites.next());
while (methods.hasNext()) {
IMethod target = methods.next();
if (!target.isAbstract()) {
CGNode callee = getNode(target, Everywhere.EVERYWHERE);
if (callee == null) {
callee = findOrCreateNode(target, Everywhere.EVERYWHERE);
if (n == getFakeRootNode()) {
registerEntrypoint(callee);
}
}
}
}
}
}
}
private CGNode makeNewNode(IMethod method, Context C) throws CancelException {
CGNode n;
Key k = new Key(method, C);
n = new CHANode(method, C);
registerNode(k, n);
closure(n, false);
newNodes.push(n);
return n;
}

View File

@ -260,11 +260,8 @@ public abstract class PropagationCallGraphBuilder implements CallGraphBuilder {
/** BEGIN Custom change: throw exception on empty entry points. This is a severe issue that should not go undetected! */
if (entrypointCallSites.isEmpty()) {
throw new IllegalStateException("Could not create a entrypoint callsites."
+ " This happens when some parameters of the method can not be generated automatically "
+ "(e.g. when they refer to an interface or an abstract class).");
}
throw new IllegalStateException("Could not create a entrypoint callsites: " + Warnings.asString());
}
/** END Custom change: throw exception on empty entry points. This is a severe issue that should not go undetected! */
customInit();

View File

@ -146,17 +146,19 @@ public abstract class DroidBenchCGTest extends DalvikCallGraphTestBase {
skipTests.add("Button2.apk");
}
public static Collection<Object[]> generateData(final URI[] androidLibs, final File androidJavaJar, final String filter) {
String f = walaProperties.getProperty("droidbench.root");
if (f == null || !new File(f).exists()) {
f = "/tmp/DroidBench";
}
System.err.println("Use " + f + " as droid bench root");
assert new File(f).exists() : "Use " + f + " as droid bench root";
assert new File(f + "/apk/").exists() : "Use " + f + " as droid bench root";
String droidBenchRoot = f;
public static Collection<Object[]> generateData(final URI[] androidLibs, final File androidJavaJar, final String filter) {
String f = walaProperties.getProperty("droidbench.root");
if (f == null || !new File(f).exists()) {
f = "/tmp/DroidBench";
}
System.err.println("Use " + f + " as droid bench root");
assert new File(f).exists() : "Use " + f + " as droid bench root";
assert new File(f + "/apk/").exists() : "Use " + f + " as droid bench root";
return generateData(f, androidLibs, androidJavaJar, filter);
}
public static Collection<Object[]> generateData(String droidBenchRoot, final URI[] androidLibs, final File androidJavaJar, final String filter) {
final List<Object[]> files = new LinkedList<Object[]>();
FileUtil.recurseFiles(new VoidFunction<File>() {
@Override

File diff suppressed because one or more lines are too long

View File

@ -23,12 +23,42 @@ import com.ibm.wala.util.config.FileOfClasses;
import com.ibm.wala.util.config.SetOfClasses;
public class Runtime {
public interface Policy {
void callback(StackTraceElement[] stack, String klass, String method, Object receiver);
}
private static class DefaultCallbackPolicy implements Policy {
// if not found:
// look up the stack for expected caller
// if found:
// (callback case)
// record real target of expected caller (or not)
// policy-based edge for call to current method
// if not found:
// (async system edge)
// policy-based edge for call to current method
@Override
public void callback(StackTraceElement[] stack, String klass, String method, Object receiver) {
// stack frames: Runtime.execution(0), callee(1), caller(2)
String root = "<clinit>".equals(stack[1].getMethodName())? "clinit": "callbacks";
String line = root + "\t" + bashToDescriptor(klass) + "\t" + String.valueOf(method) + "\n";
synchronized (runtime) {
if (runtime.output != null) {
runtime.output.printf(line);
runtime.output.flush();
}
}
}
}
private static final Runtime runtime =
new Runtime(System.getProperty("dynamicCGFile"), System.getProperty("dynamicCGFilter"));
new Runtime(System.getProperty("dynamicCGFile"),
System.getProperty("dynamicCGFilter"),
System.getProperty("policyClass", "com.ibm.wala.shrike.cg.Runtime$DefaultPolicy"));
private PrintWriter output;
private SetOfClasses filter;
private boolean handleUninstrumentedCode = false;
private Policy handleCallback;
private ThreadLocal<Stack<String>> callStacks = new ThreadLocal<Stack<String>>() {
@ -41,7 +71,7 @@ public class Runtime {
};
private Runtime(String fileName, String filterFileName) {
private Runtime(String fileName, String filterFileName, String policyClassName) {
try {
filter = new FileOfClasses(new FileInputStream(filterFileName));
} catch (Exception e) {
@ -54,7 +84,11 @@ public class Runtime {
output = new PrintWriter(System.err);
}
handleUninstrumentedCode = Boolean.parseBoolean(System.getProperty("dynamicCGHandleMissing", "false"));
try {
handleCallback = (Policy) Class.forName(policyClassName).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
handleCallback = new DefaultCallbackPolicy();
}
java.lang.Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
@ -96,13 +130,17 @@ public class Runtime {
String caller = runtime.callStacks.get().peek();
checkValid: {
if (runtime.handleUninstrumentedCode) {
//
// check for expected caller
//
if (runtime.handleCallback != null) {
StackTraceElement[] stack = (new Throwable()).getStackTrace();
if (stack.length > 2) {
// frames: me(0), callee(1), caller(2)
// frames: Runtime.execution(0), callee(1), caller(2)
StackTraceElement callerFrame = stack[2];
if (! caller.contains(callerFrame.getMethodName()) ||
! caller.contains(bashToDescriptor(callerFrame.getClassName()))) {
runtime.handleCallback.callback(stack, klass, method, receiver);
break checkValid;
}
}