WALA/com.ibm.wala.core.tests/com.ibm.wala.core.tests/src/com/ibm/wala/core/tests/callGraph/CallGraphTest.java

568 lines
21 KiB
Java

/*******************************************************************************
* Copyright (c) 2006 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.core.tests.callGraph;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import com.ibm.wala.cfg.CFGCache;
import com.ibm.wala.cfg.IBasicBlock;
import com.ibm.wala.core.tests.util.TestConstants;
import com.ibm.wala.core.tests.util.WalaTestCase;
import com.ibm.wala.ecore.java.ECallSite;
import com.ibm.wala.ecore.java.EJavaMethod;
import com.ibm.wala.ecore.java.impl.JavaPackageImpl;
import com.ibm.wala.emf.wrappers.ECallGraphWrapper;
import com.ibm.wala.emf.wrappers.EMFBridge;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.AnalysisScope;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.callgraph.CallGraphStats;
import com.ibm.wala.ipa.callgraph.Entrypoints;
import com.ibm.wala.ipa.callgraph.impl.AllApplicationEntrypoints;
import com.ibm.wala.ipa.callgraph.impl.Util;
import com.ibm.wala.ipa.cfg.BasicBlockInContext;
import com.ibm.wala.ipa.cfg.InterproceduralCFG;
import com.ibm.wala.ipa.cha.ClassHierarchy;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.Trace;
import com.ibm.wala.util.graph.Graph;
import com.ibm.wala.util.graph.GraphIntegrity;
import com.ibm.wala.util.graph.GraphIntegrity.UnsoundGraphException;
import com.ibm.wala.util.graph.traverse.DFS;
import com.ibm.wala.util.warnings.CallGraphWarnings;
import com.ibm.wala.util.warnings.WarningSet;
/**
*
* Tests for Call Graph construction
*
* @author sfink
*/
public class CallGraphTest extends WalaTestCase {
static {
JavaPackageImpl.init();
}
private static final String[] IGNORE_STRINGS = { "finalize", "java.lang.ThreadLocal", "java.lang.ref.Reference.get()" };
public static void main(String[] args) {
justThisTest(CallGraphTest.class);
}
/**
* Constructor for SpecJTest.
*
* @param arg0
*/
public CallGraphTest(String arg0) {
super(arg0);
}
/*
* public void testOpenccg() { AnalysisScope scope =
* CallGraphTestUtil.makeJ2SEAnalysisScope(Config.OPENCCG); WarningSet
* warnings = new WarningSet(); ClassHierarchy cha =
* ClassHierarchy.buildClassHierarchy(scope, warnings); Entrypoints
* entrypoints =
* com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha,
* Config.OPENCCG_CROSS_VALIDATE_REALIZER); AnalysisOptions options =
* CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
*
* Trace.println("OPENCCG Cross Validate Realizer set up warnings:\n");
* Trace.print(warnings.toString());
*
* doCallGraphs(options, cha, scope, null, useShortProfile(), false); }
*
* public void testKaba() { AnalysisScope scope =
* CallGraphTestUtil.makeJ2SEAnalysisScope(Config.KABA); WarningSet warnings =
* new WarningSet(); ClassHierarchy cha =
* ClassHierarchy.buildClassHierarchy(scope, warnings); Entrypoints
* entrypoints =
* com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha,
* Config.KABA_MAIN); AnalysisOptions options =
* CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
*
* Trace.println("kaba verifier set up warnings:\n");
* Trace.print(warnings.toString());
*
* doCallGraphs(options, cha, scope, null, useShortProfile(), false); }
*
* public void testAntlr() { AnalysisScope scope =
* CallGraphTestUtil.makeJ2SEAnalysisScope(Config.ANTLR); WarningSet warnings =
* new WarningSet(); ClassHierarchy cha =
* ClassHierarchy.buildClassHierarchy(scope, warnings); Entrypoints
* entrypoints =
* com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha,
* Config.ANTLR_MAIN); AnalysisOptions options =
* CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
*
* Trace.println("ANTLR verifier set up warnings:\n");
* Trace.print(warnings.toString());
*
* doCallGraphs(options, cha, scope, null, useShortProfile(), false); }
*/
public void testBcelVerifier() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.BCEL);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = com.ibm.wala.ipa.callgraph.impl.Util
.makeMainEntrypoints(scope, cha, TestConstants.BCEL_VERIFIER_MAIN);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("bcel verifier set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, useShortProfile(), false);
}
public void testJava_cup() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.JAVA_CUP);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, TestConstants.JAVA_CUP_MAIN);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("java_cup set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, useShortProfile(), false);
}
public void testJLex() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.JLEX);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, TestConstants.JLEX_MAIN);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("JLex set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, useShortProfile(), false);
}
public void testCornerCases() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.WALA_TESTDATA);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = new AllApplicationEntrypoints(scope, cha);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("testCornerCases set up warnings:\n");
Trace.print(warnings.toString());
warnings = doCallGraphs(options, cha, scope, null, useShortProfile(), false);
// we expect a warning or two about class Abstract1, which has no concrete
// subclasses
String ws = warnings.toString();
assertTrue("failed to report a warning about Abstract1", ws.indexOf("cornerCases/Abstract1") > -1);
// we do not expect a warning about class Abstract2, which has a concrete
// subclasses
assertTrue("reported a warning about Abstract2", ws.indexOf("cornerCases/Abstract2") == -1);
}
//
// public void testSPECjvm98() {
// AnalysisScope scope = CGTUtils.makeJ2SEAnalysisScope(Config.SPECJVM);
//
// // temporary hack because 1.5 libraries still cause grief
// if (scope.isJava15Libraries()) {
// scope = CGTUtils.makeJ2EEAnalysisScope(Config.SPECJVM);
// }
//
// WarningSet warnings = new WarningSet();
// ClassHierarchy cha = ClassHierarchy.buildClassHierarchy(scope, warnings);
// Entrypoints entrypoints =
// com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha,
// Config.SPECJVM_MAIN);
// AnalysisOptions options = CGTUtils.makeAnalysisOptions(scope, entrypoints);
//
// Trace.println("SPECjvm98 set up warnings:\n");
// Trace.print(warnings.toString());
//
// doCallGraphs(options, cha, scope, Config.SPECJVM_DCG, false, false);
// }
//
public void testHello() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.HELLO);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, TestConstants.HELLO_MAIN);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("hello set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, false, false);
}
public void testRecursion() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.WALA_TESTDATA);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = com.ibm.wala.ipa.callgraph.impl.Util.makeMainEntrypoints(scope, cha, TestConstants.RECURSE_MAIN);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("testRecursion set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, useShortProfile(), false);
}
public void testHelloAllEntrypoints() throws ClassHierarchyException {
AnalysisScope scope = CallGraphTestUtil.makeJ2SEAnalysisScope(TestConstants.HELLO);
WarningSet warnings = new WarningSet();
ClassHierarchy cha = ClassHierarchy.make(scope, warnings);
Entrypoints entrypoints = new AllApplicationEntrypoints(scope, cha);
AnalysisOptions options = CallGraphTestUtil.makeAnalysisOptions(scope, entrypoints);
Trace.println("hello all entrypoints set up warnings:\n");
Trace.print(warnings.toString());
doCallGraphs(options, cha, scope, null, useShortProfile(), false);
}
//
// public void testSPECjvm98AllEntrypoints() {
// AnalysisScope scope = CGTUtils.makeJ2SEAnalysisScope(Config.SPECJVM);
// WarningSet warnings = new WarningSet();
// ClassHierarchy cha = ClassHierarchy.buildClassHierarchy(scope, warnings);
// Entrypoints entrypoints = new AllApplicationEntrypoints(scope, cha);
// AnalysisOptions options = CGTUtils.makeAnalysisOptions(scope, entrypoints);
//
// Trace.println("SPECjvm98 set up warnings:\n");
// Trace.print(warnings.toString());
//
// doCallGraphs(options, cha, scope, null, useShortProfile(), true);
// }
/**
* TODO: refactor this to avoid excessive code bloat.
*/
public static WarningSet doCallGraphs(AnalysisOptions options, ClassHierarchy cha, AnalysisScope scope, String dcgFile,
boolean stopAfterZeroCFA, boolean stopAfterZeroContainerCFA) {
// ///////////////
// // RTA /////
// ///////////////
WarningSet warnings = new WarningSet();
CallGraph cg = CallGraphTestUtil.buildRTA(options, cha, scope, warnings);
try {
GraphIntegrity.check(cg);
} catch (UnsoundGraphException e1) {
e1.printStackTrace();
assertTrue(e1.getMessage(), false);
}
Set<MethodReference> rtaMethods = CallGraphStats.collectMethods(cg);
Trace.println("RTA methods reached: " + rtaMethods.size());
Trace.println(CallGraphStats.getStats(cg));
Trace.println("RTA warnings:\n");
warnings.addAll(CallGraphWarnings.getWarnings(cg));
Trace.print(warnings.toString(cg));
// ///////////////
// // 0-CFA /////
// ///////////////
warnings = new WarningSet();
cg = CallGraphTestUtil.buildZeroCFA(options, cha, scope, warnings);
// FIXME: annoying special cases caused by clone2assign mean using
// the rta graph for proper graph subset checking does not work.
// (note that all the other such checks do use proper graph subset)
Graph<MethodReference> squashZero = checkCallGraph(warnings, cg, null, rtaMethods, "0-CFA");
// test Pretransitive 0-CFA
// not currently supported
// warnings = new WarningSet();
// options.setUsePreTransitiveSolver(true);
// CallGraph cgP = CallGraphTestUtil.buildZeroCFA(options, cha, scope,
// warnings);
// options.setUsePreTransitiveSolver(false);
// Graph squashPT = checkCallGraph(warnings, cgP, squashZero, null, "Pre-T
// 1");
// checkCallGraph(warnings, cg, squashPT, null, "Pre-T 2");
if (stopAfterZeroCFA) {
return warnings;
}
// ///////////////
// // 0-1-CFA ///
// ///////////////
warnings = new WarningSet();
cg = CallGraphTestUtil.buildZeroOneCFA(options, cha, scope, warnings);
Graph<MethodReference> squashZeroOne = checkCallGraph(warnings, cg, squashZero, null, "0-1-CFA");
// ///////////////////////////////////////////////////
// // 0-CFA augmented to disambiguate containers ///
// ///////////////////////////////////////////////////
warnings = new WarningSet();
cg = CallGraphTestUtil.buildZeroContainerCFA(options, cha, scope, warnings);
Graph<MethodReference> squashZeroContainer = checkCallGraph(warnings, cg, squashZero, null, "0-Container-CFA");
if (stopAfterZeroContainerCFA)
return warnings;
// ///////////////////////////////////////////////////
// // 0-1-CFA augmented to disambiguate containers ///
// ///////////////////////////////////////////////////
warnings = new WarningSet();
cg = CallGraphTestUtil.buildZeroOneContainerCFA(options, cha, scope, warnings);
checkCallGraph(warnings, cg, squashZeroContainer, null, "0-1-Container-CFA");
checkCallGraph(warnings, cg, squashZeroOne, null, "0-1-Container-CFA");
if (dcgFile != null) {
checkAgainstDCG(cg, dcgFile);
}
// test ICFG
checkICFG(cg, options.getCFGCache());
return warnings;
// /////////////
// // 1-CFA ///
// /////////////
// warnings = new WarningSet();
// cg = buildOneCFA();
}
/**
* Check properties of the InterproceduralCFG
*
* @param cg
*/
private static void checkICFG(CallGraph cg, CFGCache cfgCache) {
InterproceduralCFG icfg = new InterproceduralCFG(cg, cfgCache, new WarningSet());
try {
GraphIntegrity.check(icfg);
} catch (UnsoundGraphException e) {
e.printStackTrace();
assertTrue(false);
}
// perform a little icfg exercise
int count = 0;
for (Iterator<? extends BasicBlockInContext> it = icfg.iterateNodes(); it.hasNext();) {
IBasicBlock bb = (IBasicBlock) it.next();
if (icfg.hasCall((BasicBlockInContext) bb)) {
count++;
}
}
}
/**
* Check that cg is a superset of the dynamic call graph encoded in the
* dcgFile
*
* @param cg
* @param dcgFile
*/
private static void checkAgainstDCG(CallGraph cg, String dcgFile) {
Set<CGNode> synthLeaves = getSyntheticLeaves(cg);
com.ibm.wala.emf.wrappers.ECallGraphWrapper subG = com.ibm.wala.emf.wrappers.ECallGraphWrapper.load(dcgFile,
CallGraphTest.class.getClassLoader());
com.ibm.wala.emf.wrappers.ECallGraphWrapper superG = EMFBridge.makeCallGraph(cg);
prune(subG, synthLeaves);
prune(superG, synthLeaves);
checkGraphSubset(superG, subG);
}
/**
* @param superG
* @param subG
*/
public static void checkGraphSubset(ECallGraphWrapper superG, ECallGraphWrapper subG) {
Set<EObject> nodeDiff = Util.setify(subG.iterateNodes());
nodeDiff.removeAll(Util.setify(superG.iterateNodes()));
Set<EObject> toRemove = HashSetFactory.make();
for (Iterator<EObject> it = nodeDiff.iterator(); it.hasNext();) {
EObject o = it.next();
if (o instanceof ECallSite) {
toRemove.add(o);
}
}
// a bogus hack: ignore some stuff in the dcg that we haven't
// cleaned out; TODO: figure out what's happening and delete this
outer: for (Iterator<EObject> it = nodeDiff.iterator(); it.hasNext();) {
EObject o = it.next();
for (int i = 0; i < IGNORE_STRINGS.length; i++) {
if (o.toString().indexOf(IGNORE_STRINGS[i]) > -1) {
toRemove.add(o);
continue outer;
}
}
}
nodeDiff.removeAll(toRemove);
if (!nodeDiff.isEmpty()) {
Trace.println("supergraph: ");
Trace.println(superG.toString());
Trace.println("subgraph: ");
Trace.println(subG.toString());
Trace.println("nodeDiff: ");
for (Iterator<EObject> it = nodeDiff.iterator(); it.hasNext();) {
Trace.println(it.next().toString());
}
Assertions.productionAssertion(nodeDiff.isEmpty(), "bad superset, see tracefile\n");
}
}
/**
* <ul>
* <li>remove all methods from G that correspond to synthetic methods
* <li>remove all nodes from G that are no longer reachable from the fake
* root.
* <ul>
*
* @param G
* an EMF format call graph
* @param synthetic
* a set of synthetic methods
*/
private static void prune(ECallGraphWrapper G, Set<CGNode> synthetic) {
// compute synthetic nodes
Set<EObject> toRemove = HashSetFactory.make();
for (Iterator<CGNode> it = synthetic.iterator(); it.hasNext();) {
CGNode n = it.next();
EJavaMethod node = EMFBridge.makeJavaMethod(n.getMethod().getReference());
if (node != null) {
toRemove.add(node);
}
}
removeNodes(G, toRemove);
// compute nodes reachable from the fake root
EJavaMethod fakeRoot = EMFBridge.makeFakeRootMethod();
Assertions._assert(fakeRoot != null);
Collection<EObject> c = DFS.getReachableNodes(G, Collections.singleton(fakeRoot));
// remove other nodes
toRemove = HashSetFactory.make();
for (Iterator<? extends EObject> it = G.iterateNodes(); it.hasNext();) {
EObject n = it.next();
if (!c.contains(n)) {
toRemove.add(n);
}
}
removeNodes(G, toRemove);
// remove call site nodes with no targets (these won't appear in the dcg)
toRemove = HashSetFactory.make();
for (Iterator<? extends EObject> it = G.iterateNodes(); it.hasNext();) {
EObject n = it.next();
if (n instanceof ECallSite) {
if (G.getSuccNodeCount(n) == 0) {
toRemove.add(n);
}
}
}
removeNodes(G, toRemove);
}
/**
* @param G
* @param toRemove
*/
private static void removeNodes(ECallGraphWrapper G, Set<EObject> toRemove) {
// remove all these nodes
for (Iterator<EObject> it = toRemove.iterator(); it.hasNext();) {
EObject n = it.next();
if (G.containsNode(n)) {
G.removeNodeAndEdges(n);
}
}
}
/**
* @param cg
* @return Set <CGNode>in cg that are synthetic and have no call sites
*/
private static Set<CGNode> getSyntheticLeaves(CallGraph cg) {
HashSet<CGNode> result = HashSetFactory.make();
for (Iterator<? extends CGNode> it = cg.iterateNodes(); it.hasNext();) {
CGNode node = (CGNode) it.next();
if (!node.equals(cg.getFakeRootNode())) {
if (node.getMethod().isSynthetic()) {
if (!node.iterateSites().hasNext()) {
result.add(node);
}
}
}
}
return result;
}
/**
* Check consistency of a callgraph, and check that this call graph is a
* subset of a super-graph
*
* @param warnings
* object to track warnings
* @param cg
* @param superCG
* @param superMethods
* @param thisAlgorithm
* @return a squashed version of cg
*/
private static Graph<MethodReference> checkCallGraph(WarningSet warnings, CallGraph cg, Graph<MethodReference> superCG,
Set<MethodReference> superMethods, String thisAlgorithm) {
try {
GraphIntegrity.check(cg);
} catch (UnsoundGraphException e1) {
assertTrue(e1.getMessage(), false);
}
Set<MethodReference> callGraphMethods = CallGraphStats.collectMethods(cg);
Trace.println(thisAlgorithm + " methods reached: " + callGraphMethods.size());
Trace.println(CallGraphStats.getStats(cg));
Trace.println(thisAlgorithm + " warnings:\n");
warnings.addAll(CallGraphWarnings.getWarnings(cg));
Trace.print(warnings.toString(cg));
Graph<MethodReference> thisCG = com.ibm.wala.ipa.callgraph.impl.Util.squashCallGraph(thisAlgorithm, cg);
if (superCG != null) {
com.ibm.wala.ipa.callgraph.impl.Util.checkGraphSubset(superCG, thisCG);
} else {
if (!superMethods.containsAll(callGraphMethods)) {
Set<MethodReference> temp = HashSetFactory.make();
temp.addAll(callGraphMethods);
temp.removeAll(superMethods);
Trace.printCollection("Violations", temp);
Assertions.UNREACHABLE();
}
}
return thisCG;
}
}