398 lines
17 KiB
Java
398 lines
17 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2007 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.cast.js.test;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URL;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
|
|
import org.junit.Assert;
|
|
import org.junit.Test;
|
|
|
|
import com.ibm.wala.analysis.pointers.HeapGraph;
|
|
import com.ibm.wala.cast.ipa.callgraph.GlobalObjectKey;
|
|
import com.ibm.wala.cast.ir.ssa.AstGlobalWrite;
|
|
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.GlobalVertex;
|
|
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.ObjectVertex;
|
|
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.PrototypeFieldVertex;
|
|
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.PrototypeFieldVertex.PrototypeField;
|
|
import com.ibm.wala.cast.js.html.JSSourceExtractor;
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSCFABuilder;
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraph;
|
|
import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
|
|
import com.ibm.wala.cast.js.ipa.callgraph.TransitivePrototypeKey;
|
|
import com.ibm.wala.cast.js.ipa.summaries.JavaScriptConstructorFunctions.JavaScriptConstructor;
|
|
import com.ibm.wala.cast.js.ssa.JavaScriptInvoke;
|
|
import com.ibm.wala.cast.js.ssa.JavaScriptPropertyWrite;
|
|
import com.ibm.wala.cast.js.test.FieldBasedCGUtil.BuilderType;
|
|
import com.ibm.wala.cast.js.translator.JavaScriptTranslatorFactory;
|
|
import com.ibm.wala.cast.js.types.JavaScriptTypes;
|
|
import com.ibm.wala.cast.loader.AstDynamicField;
|
|
import com.ibm.wala.cast.types.AstMethodReference;
|
|
import com.ibm.wala.classLoader.CallSiteReference;
|
|
import com.ibm.wala.classLoader.NewSiteReference;
|
|
import com.ibm.wala.ipa.callgraph.CGNode;
|
|
import com.ibm.wala.ipa.callgraph.CallGraph;
|
|
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
|
|
import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
|
|
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
|
|
import com.ibm.wala.ssa.IR;
|
|
import com.ibm.wala.ssa.SSAInstruction;
|
|
import com.ibm.wala.ssa.SymbolTable;
|
|
import com.ibm.wala.types.MethodReference;
|
|
import com.ibm.wala.util.CancelException;
|
|
import com.ibm.wala.util.NullProgressMonitor;
|
|
import com.ibm.wala.util.Predicate;
|
|
import com.ibm.wala.util.WalaException;
|
|
import com.ibm.wala.util.collections.EmptyIterator;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.collections.MapIterator;
|
|
import com.ibm.wala.util.collections.Pair;
|
|
import com.ibm.wala.util.functions.Function;
|
|
import com.ibm.wala.util.intset.OrdinalSet;
|
|
import com.ibm.wala.util.strings.Atom;
|
|
|
|
public abstract class TestPointerAnalyses {
|
|
|
|
private final class CheckPointers extends Predicate<Pair<Set<Pair<CGNode, NewSiteReference>>, Set<Pair<CGNode, NewSiteReference>>>> {
|
|
private Set<Pair<String,Integer>> map(Set<Pair<CGNode, NewSiteReference>> sites) {
|
|
Set<Pair<String,Integer>> result = HashSetFactory.make();
|
|
for(Pair<CGNode,NewSiteReference> s : sites) {
|
|
result.add(Pair.make(s.fst.getMethod().toString(), s.snd.getProgramCounter()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean test(Pair<Set<Pair<CGNode, NewSiteReference>>, Set<Pair<CGNode, NewSiteReference>>> t) {
|
|
if (t.snd.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
Set<Pair<String, Integer>> x = HashSetFactory.make(map(t.fst));
|
|
x.retainAll(map(t.snd));
|
|
return ! x.isEmpty();
|
|
}
|
|
}
|
|
|
|
private final JavaScriptTranslatorFactory factory;
|
|
|
|
protected TestPointerAnalyses(JavaScriptTranslatorFactory factory) {
|
|
this.factory = factory;
|
|
JSCallGraphUtil.setTranslatorFactory(factory);
|
|
}
|
|
|
|
private Pair<CGNode, NewSiteReference> map(CallGraph CG, Pair<CGNode, NewSiteReference> ptr) {
|
|
CGNode n = ptr.fst;
|
|
|
|
if (! (n.getMethod() instanceof JavaScriptConstructor)) {
|
|
return ptr;
|
|
}
|
|
|
|
Iterator<CGNode> preds = CG.getPredNodes(n);
|
|
|
|
if (! preds.hasNext()) {
|
|
return ptr;
|
|
}
|
|
|
|
CGNode caller = preds.next();
|
|
assert !preds.hasNext() : n;
|
|
|
|
Iterator<CallSiteReference> sites = CG.getPossibleSites(caller, n);
|
|
CallSiteReference site = sites.next();
|
|
assert ! sites.hasNext();
|
|
|
|
return Pair.make(caller, new NewSiteReference(site.getProgramCounter(), ptr.snd.getDeclaredType()));
|
|
}
|
|
|
|
private Set<Pair<CGNode, NewSiteReference>> map(CallGraph CG, Set<Pair<CGNode, NewSiteReference>> ptrs) {
|
|
Set<Pair<CGNode, NewSiteReference>> result = HashSetFactory.make();
|
|
for(Pair<CGNode, NewSiteReference> ptr : ptrs) {
|
|
result.add(map(CG, ptr));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private Set<Pair<CGNode, NewSiteReference>> ptrs(Set<CGNode> functions, int local, CallGraph CG, PointerAnalysis<? extends InstanceKey> pa) {
|
|
Set<Pair<CGNode, NewSiteReference>> result = HashSetFactory.make();
|
|
|
|
for(CGNode n : functions) {
|
|
PointerKey l = pa.getHeapModel().getPointerKeyForLocal(n, local);
|
|
if (l != null) {
|
|
OrdinalSet<? extends InstanceKey> pointers = pa.getPointsToSet(l);
|
|
if (pointers != null) {
|
|
for(InstanceKey k : pointers) {
|
|
for(Iterator<Pair<CGNode, NewSiteReference>> css = k.getCreationSites(CG); css.hasNext(); ) {
|
|
result.add(css.next());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private boolean isGlobal(Set<CGNode> functions, int local, PointerAnalysis<? extends InstanceKey> pa) {
|
|
for(CGNode n : functions) {
|
|
PointerKey l = pa.getHeapModel().getPointerKeyForLocal(n, local);
|
|
if (l != null) {
|
|
OrdinalSet<? extends InstanceKey> pointers = pa.getPointsToSet(l);
|
|
if (pointers != null) {
|
|
for(InstanceKey k : pointers) {
|
|
if (k instanceof GlobalObjectKey || k instanceof GlobalVertex) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void testPage(URL page, Predicate<MethodReference> filter, Predicate<Pair<Set<Pair<CGNode, NewSiteReference>>, Set<Pair<CGNode, NewSiteReference>>>> test) throws IOException, WalaException, CancelException {
|
|
boolean save = JSSourceExtractor.USE_TEMP_NAME;
|
|
try {
|
|
JSSourceExtractor.USE_TEMP_NAME = false;
|
|
|
|
FieldBasedCGUtil fb = new FieldBasedCGUtil(factory);
|
|
Pair<JSCallGraph, PointerAnalysis<ObjectVertex>> fbResult = fb.buildCG(page, BuilderType.OPTIMISTIC, true);
|
|
|
|
JSCFABuilder propagationBuilder = JSCallGraphBuilderUtil.makeHTMLCGBuilder(page);
|
|
CallGraph propCG = propagationBuilder.makeCallGraph(propagationBuilder.getOptions());
|
|
PointerAnalysis<InstanceKey> propPA = propagationBuilder.getPointerAnalysis();
|
|
|
|
test(filter, test, fbResult.fst, fbResult.snd, propCG, propPA);
|
|
|
|
} finally {
|
|
JSSourceExtractor.USE_TEMP_NAME = save;
|
|
}
|
|
}
|
|
|
|
private void testTestScript(String dir, String name, Predicate<MethodReference> filter, Predicate<Pair<Set<Pair<CGNode, NewSiteReference>>, Set<Pair<CGNode, NewSiteReference>>>> test) throws IOException, WalaException, CancelException {
|
|
boolean save = JSSourceExtractor.USE_TEMP_NAME;
|
|
try {
|
|
JSSourceExtractor.USE_TEMP_NAME = false;
|
|
|
|
FieldBasedCGUtil fb = new FieldBasedCGUtil(factory);
|
|
Pair<JSCallGraph, PointerAnalysis<ObjectVertex>> fbResult = fb.buildTestCG(dir, name, BuilderType.OPTIMISTIC, new NullProgressMonitor(), true);
|
|
|
|
JSCFABuilder propagationBuilder = JSCallGraphBuilderUtil.makeScriptCGBuilder(dir, name);
|
|
CallGraph propCG = propagationBuilder.makeCallGraph(propagationBuilder.getOptions());
|
|
PointerAnalysis<InstanceKey> propPA = propagationBuilder.getPointerAnalysis();
|
|
|
|
test(filter, test, fbResult.fst, fbResult.snd, propCG, propPA);
|
|
|
|
} finally {
|
|
JSSourceExtractor.USE_TEMP_NAME = save;
|
|
}
|
|
}
|
|
|
|
protected void test(Predicate<MethodReference> filter,
|
|
Predicate<Pair<Set<Pair<CGNode, NewSiteReference>>, Set<Pair<CGNode, NewSiteReference>>>> test, CallGraph fbCG,
|
|
PointerAnalysis<ObjectVertex> fbPA, CallGraph propCG, PointerAnalysis<InstanceKey> propPA) {
|
|
HeapGraph<ObjectVertex> hg = fbPA.getHeapGraph();
|
|
|
|
Set<MethodReference> functionsToCompare = HashSetFactory.make();
|
|
for(CGNode n : fbCG) {
|
|
MethodReference ref = n.getMethod().getReference();
|
|
if (filter.test(ref) && !propCG.getNodes(ref).isEmpty()) {
|
|
functionsToCompare.add(ref);
|
|
}
|
|
}
|
|
|
|
System.err.println(fbCG);
|
|
|
|
for(MethodReference function : functionsToCompare) {
|
|
System.err.println("testing " + function);
|
|
|
|
Set<CGNode> fbNodes = fbCG.getNodes(function);
|
|
Set<CGNode> propNodes = propCG.getNodes(function);
|
|
|
|
CGNode node = fbNodes.iterator().next();
|
|
IR ir = node.getIR();
|
|
System.err.println(ir);
|
|
|
|
int maxVn = -1;
|
|
for(CallGraph cg : new CallGraph[]{fbCG, propCG}) {
|
|
for(CGNode n : cg) {
|
|
IR nir = n.getIR();
|
|
if (nir != null && nir.getSymbolTable().getMaxValueNumber() > maxVn) {
|
|
maxVn = nir.getSymbolTable().getMaxValueNumber();
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 1; i <= maxVn; i++) {
|
|
Set<Pair<CGNode, NewSiteReference>> fbPtrs = ptrs(fbNodes, i, fbCG, fbPA);
|
|
Set<Pair<CGNode, NewSiteReference>> propPtrs = map(propCG, ptrs(propNodes, i, propCG, propPA));
|
|
|
|
Assert.assertTrue("analysis should agree on global object for " + i + " of " + ir, isGlobal(fbNodes, i, fbPA) == isGlobal(propNodes, i, propPA));
|
|
|
|
if (!fbPtrs.isEmpty() || !propPtrs.isEmpty()) {
|
|
System.err.println("checking local " + i + " of " + function + ": " + fbPtrs + " vs " + propPtrs);
|
|
}
|
|
|
|
Assert.assertTrue(fbPtrs + " should intersect " + propPtrs + " for " + i + " of " + ir, test.test(Pair.make(fbPtrs, propPtrs)));
|
|
}
|
|
|
|
SymbolTable symtab = ir.getSymbolTable();
|
|
for(SSAInstruction inst : ir.getInstructions()) {
|
|
if (inst instanceof JavaScriptPropertyWrite) {
|
|
int property = ((JavaScriptPropertyWrite) inst).getMemberRef();
|
|
if (symtab.isConstant(property)) {
|
|
String p = JSCallGraphUtil.simulateToStringForPropertyNames(symtab.getConstantValue(property));
|
|
|
|
int obj = ((JavaScriptPropertyWrite) inst).getObjectRef();
|
|
PointerKey objKey = fbPA.getHeapModel().getPointerKeyForLocal(node, obj);
|
|
OrdinalSet<ObjectVertex> objPtrs = fbPA.getPointsToSet(objKey);
|
|
for(ObjectVertex o : objPtrs) {
|
|
PointerKey propKey = fbPA.getHeapModel().getPointerKeyForInstanceField(o, new AstDynamicField(false, o.getConcreteType(), Atom.findOrCreateUnicodeAtom(p), JavaScriptTypes.Root));
|
|
Assert.assertTrue("object " + o + " should have field " + propKey, hg.hasEdge(o, propKey));
|
|
|
|
int val = ((JavaScriptPropertyWrite) inst).getValue();
|
|
PointerKey valKey = fbPA.getHeapModel().getPointerKeyForLocal(node, val);
|
|
OrdinalSet<ObjectVertex> valPtrs = fbPA.getPointsToSet(valKey);
|
|
for(ObjectVertex v : valPtrs) {
|
|
Assert.assertTrue("field " + propKey + " should point to object " + valKey + "(" + v + ")", hg.hasEdge(propKey, v));
|
|
}
|
|
}
|
|
|
|
System.err.println("heap graph models instruction " + inst);
|
|
}
|
|
} else if (inst instanceof AstGlobalWrite) {
|
|
String propName = ((AstGlobalWrite) inst).getGlobalName();
|
|
propName = propName.substring("global ".length());
|
|
PointerKey propKey = fbPA.getHeapModel().getPointerKeyForInstanceField(null, new AstDynamicField(false, null, Atom.findOrCreateUnicodeAtom(propName), JavaScriptTypes.Root));
|
|
Assert.assertTrue("global " + propName + " should exist", hg.hasEdge(GlobalVertex.instance(), propKey));
|
|
|
|
System.err.println("heap graph models instruction " + inst);
|
|
} else if (inst instanceof JavaScriptInvoke) {
|
|
int vn = ((JavaScriptInvoke) inst).getReceiver();
|
|
|
|
Set<Pair<CGNode, NewSiteReference>> fbPrototypes = getFbPrototypes(fbPA, hg, fbCG, node, vn);
|
|
Set<Pair<CGNode, NewSiteReference>> propPrototypes = getPropPrototypes(propPA, propCG, node, vn);
|
|
Assert.assertTrue("should have prototype overlap for " + fbPrototypes + " and " + propPrototypes + " at " + inst,
|
|
(fbPrototypes.isEmpty() && propPrototypes.isEmpty()) || !Collections.disjoint(fbPrototypes, propPrototypes));
|
|
}
|
|
}
|
|
}
|
|
|
|
for(InstanceKey k : fbPA.getInstanceKeys()) {
|
|
k.getCreationSites(fbCG);
|
|
for(String f : new String[]{ "__proto__", "prototype" }) {
|
|
boolean dump = false;
|
|
PointerKey pointerKeyForInstanceField = fbPA.getHeapModel().getPointerKeyForInstanceField(k, new AstDynamicField(false, k.getConcreteType(), Atom.findOrCreateUnicodeAtom(f), JavaScriptTypes.Root));
|
|
if (! hg.containsNode(pointerKeyForInstanceField)) {
|
|
dump = true;
|
|
System.err.println("no " + f + " for " + k + "(" + k.getConcreteType() + ")");
|
|
} else if (! hg.getSuccNodes(pointerKeyForInstanceField).hasNext()){
|
|
dump = true;
|
|
System.err.println("empty " + f + " for " + k + "(" + k.getConcreteType() + ")");
|
|
}
|
|
if (dump) {
|
|
for(Iterator<Pair<CGNode, NewSiteReference>> css = k.getCreationSites(fbCG); css.hasNext(); ) {
|
|
System.err.println(css.next());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static <T extends InstanceKey> Set<Pair<CGNode, NewSiteReference>> getPrototypeSites(PointerAnalysis<T> fbPA,
|
|
CallGraph CG,
|
|
Function<T,Iterator<T>> proto,
|
|
CGNode node,
|
|
int vn) {
|
|
Set<Pair<CGNode, NewSiteReference>> fbProtos = HashSetFactory.make();
|
|
PointerKey fbKey = fbPA.getHeapModel().getPointerKeyForLocal(node, vn);
|
|
OrdinalSet<T> fbPointsTo = fbPA.getPointsToSet(fbKey);
|
|
for(T o : fbPointsTo) {
|
|
for(Iterator<T> ps = proto.apply(o); ps.hasNext(); ) {
|
|
for(Iterator<Pair<CGNode, NewSiteReference>> css = ps.next().getCreationSites(CG); css.hasNext(); ) {
|
|
fbProtos.add(css.next());
|
|
}
|
|
}
|
|
}
|
|
return fbProtos;
|
|
}
|
|
|
|
private static Set<Pair<CGNode, NewSiteReference>> getFbPrototypes(PointerAnalysis<ObjectVertex> fbPA,
|
|
final HeapGraph<ObjectVertex> hg,
|
|
CallGraph CG,
|
|
CGNode node,
|
|
int vn) {
|
|
return getPrototypeSites(fbPA, CG, new Function<ObjectVertex,Iterator<ObjectVertex>>() {
|
|
@Override
|
|
public Iterator<ObjectVertex> apply(ObjectVertex o) {
|
|
PrototypeFieldVertex proto = new PrototypeFieldVertex(PrototypeField.__proto__, o);
|
|
if (hg.containsNode(proto)) {
|
|
return
|
|
new MapIterator<Object,ObjectVertex>(hg.getSuccNodes(proto),
|
|
new Function<Object,ObjectVertex>() {
|
|
@Override
|
|
public ObjectVertex apply(Object object) {
|
|
return (ObjectVertex)object;
|
|
}
|
|
});
|
|
} else {
|
|
return EmptyIterator.instance();
|
|
}
|
|
}
|
|
}, node, vn);
|
|
}
|
|
|
|
private static Set<Pair<CGNode, NewSiteReference>> getPropPrototypes(final PointerAnalysis<InstanceKey> fbPA,
|
|
CallGraph CG,
|
|
CGNode node,
|
|
int vn) {
|
|
return getPrototypeSites(fbPA, CG, new Function<InstanceKey,Iterator<InstanceKey>>() {
|
|
@Override
|
|
public Iterator<InstanceKey> apply(InstanceKey o) {
|
|
return fbPA.getPointsToSet(new TransitivePrototypeKey(o)).iterator();
|
|
}
|
|
}, node, vn);
|
|
}
|
|
|
|
private void testPageUserCodeEquivalent(URL page) throws IOException, WalaException, CancelException {
|
|
final String name = page.getFile().substring(page.getFile().lastIndexOf('/')+1, page.getFile().lastIndexOf('.'));
|
|
testPage(page, nameFilter(name), new CheckPointers());
|
|
}
|
|
|
|
protected Predicate<MethodReference> nameFilter(final String name) {
|
|
return new Predicate<MethodReference>() {
|
|
@Override
|
|
public boolean test(MethodReference t) {
|
|
System.err.println(t + " " + name);
|
|
return t.getSelector().equals(AstMethodReference.fnSelector) &&
|
|
t.getDeclaringClass().getName().toString().startsWith("L" + name);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Test
|
|
public void testWindowOnload() throws IOException, WalaException, CancelException {
|
|
testPageUserCodeEquivalent(getClass().getClassLoader().getResource("pages/windowonload.html"));
|
|
}
|
|
|
|
@Test
|
|
public void testObjects() throws IOException, WalaException, CancelException {
|
|
testTestScript("tests", "objects.js", nameFilter("tests/objects.js"), new CheckPointers());
|
|
}
|
|
|
|
@Test
|
|
public void testInherit() throws IOException, WalaException, CancelException {
|
|
testTestScript("tests", "inherit.js", nameFilter("tests/inherit.js"), new CheckPointers());
|
|
}
|
|
} |