WALA/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestPointerAnalyses.java

385 lines
16 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 java.util.function.Function;
import java.util.function.Predicate;
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.ir.ssa.AstPropertyWrite;
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.DefaultSourceExtractor;
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.WalaException;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Iterator2Iterable;
import com.ibm.wala.util.collections.MapIterator;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.intset.OrdinalSet;
import com.ibm.wala.util.strings.Atom;
public abstract class TestPointerAnalyses {
private final class CheckPointers implements 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 static 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 static 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 static 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(Pair<CGNode, NewSiteReference> cs : Iterator2Iterable.make(k.getCreationSites(CG))) {
result.add(cs);
}
}
}
}
}
return result;
}
private static 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 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, DefaultSourceExtractor.factory);
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 = ((AstPropertyWrite) inst).getMemberRef();
if (symtab.isConstant(property)) {
String p = JSCallGraphUtil.simulateToStringForPropertyNames(symtab.getConstantValue(property));
int obj = ((AstPropertyWrite) 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 = ((AstPropertyWrite) 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(Pair<CGNode, NewSiteReference> cs : Iterator2Iterable.make(k.getCreationSites(fbCG))) {
System.err.println(cs);
}
}
}
}
}
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(T p : Iterator2Iterable.make(proto.apply(o))) {
for(Pair<CGNode, NewSiteReference> cs : Iterator2Iterable.make(p.getCreationSites(CG))) {
fbProtos.add(cs);
}
}
}
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, o -> {
PrototypeFieldVertex proto = new PrototypeFieldVertex(PrototypeField.__proto__, o);
if (hg.containsNode(proto)) {
return
new MapIterator<>(hg.getSuccNodes(proto),
ObjectVertex.class::cast);
} 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, o -> fbPA.getPointsToSet(new TransitivePrototypeKey(o)).iterator(), node, vn);
}
private void testPageUserCodeEquivalent(URL page) throws 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 t -> {
System.err.println(t + " " + name);
return t.getSelector().equals(AstMethodReference.fnSelector) &&
t.getDeclaringClass().getName().toString().startsWith("L" + name);
};
}
@Test
public void testWindowOnload() throws 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());
}
}