package dk.brics.jspointers.test.instrument; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import dk.brics.jsparser.AstUtil; import dk.brics.jsparser.Literals; import dk.brics.jsparser.analysis.AnalysisAdapter; import dk.brics.jsparser.analysis.DepthFirstAdapter; import dk.brics.jsparser.node.AArrayLiteralExp; import dk.brics.jsparser.node.ABody; import dk.brics.jsparser.node.AConstExp; import dk.brics.jsparser.node.ADynamicPropertyExp; import dk.brics.jsparser.node.AFunctionDeclStmt; import dk.brics.jsparser.node.AFunctionExp; import dk.brics.jsparser.node.AInvokeExp; import dk.brics.jsparser.node.ANewExp; import dk.brics.jsparser.node.AObjectLiteralExp; import dk.brics.jsparser.node.AParenthesisExp; import dk.brics.jsparser.node.APropertyExp; import dk.brics.jsparser.node.ARegexpExp; import dk.brics.jsparser.node.IFunction; import dk.brics.jsparser.node.IInvocationNode; import dk.brics.jsparser.node.PExp; import dk.brics.jsparser.node.Start; import dk.brics.jspointers.test.NodeFactory; import dk.brics.jspointers.test.PrettyPrinter; import dk.brics.jsutil.MultiMap; import dk.brics.jsutil.Pair; public class Instrumenter { private MultiMap<IFunction, Integer> func2callees = new MultiMap<IFunction, Integer>(); private Map<AInvokeExp, Integer> invoke2callid = new HashMap<AInvokeExp, Integer>(); private Map<AllocSite,Integer> allocsite2id = new HashMap<AllocSite, Integer>(); private InstrumentData data; private ArrayList<AbstractObj> objectGraph = new ArrayList<AbstractObj>(); public static class AbstractObj { MultiMap<String,Integer> pointsTo = new MultiMap<String, Integer>(); Set<Integer> proto = new HashSet<Integer>(); } public Instrumenter(InstrumentData data) { this.data = data; } public void instrument() { buildIDs(); buildCallGraph(); buildObjectGraph(); doInstrumentation(); } private void buildObjectGraph() { allAllocSites = new ArrayList<AllocSite>(); for (Start start : data.getAst()) { start.apply(new DepthFirstAdapter() { @Override public void inANewExp(ANewExp node) { allAllocSites.add(new NewExpAllocSite(node)); } @Override public void inAObjectLiteralExp(AObjectLiteralExp node) { allAllocSites.add(new ObjLiteralAllocSite(node)); } @Override public void inAArrayLiteralExp(AArrayLiteralExp node) { allAllocSites.add(new ArrayLiteralAllocSite(node)); } @Override public void inARegexpExp(ARegexpExp node) { allAllocSites.add(new RegExpAllocSite(node)); } @Override public void inAFunctionDeclStmt(AFunctionDeclStmt node) { allAllocSites.add(new FunctionAllocSite(node)); allAllocSites.add(new FunctionProtoAllocSite(node)); allAllocSites.add(new ArgumentsArrayAllocSite(node)); } @Override public void inAFunctionExp(AFunctionExp node) { allAllocSites.add(new FunctionAllocSite(node)); allAllocSites.add(new FunctionProtoAllocSite(node)); allAllocSites.add(new ArgumentsArrayAllocSite(node)); } }); } int nextAllocSiteId = 0; for (AllocSite site : allAllocSites) { allocsite2id.put(site, nextAllocSiteId++); } for (AllocSite site : allAllocSites) { final AbstractObj ab = new AbstractObj(); MultiMap<String, AllocSite> pointsTo = data.getPointsTo(site); for (String prty : pointsTo.keySet()) { for (AllocSite dst : pointsTo.getView(prty)) { ab.pointsTo.add(prty, allocsite2id.get(dst)); } } for (AllocSite proto : data.getPrototypeOf(site)) { assert allocsite2id.get(proto) != null; ab.proto.add(allocsite2id.get(proto)); } // compensate for non-standard properties of V8 site.apply(new AbstractAllocSiteVisitor() { @Override public void caseFunction(FunctionAllocSite site) { ab.pointsTo.add("arguments", allocsite2id.get(new ArgumentsArrayAllocSite(site.getExp()))); } @Override public void caseArguments(ArgumentsArrayAllocSite site) { ab.pointsTo.add("callee", -1); } }); objectGraph.add(ab); } } int nextCallId = 1; private List<AllocSite> allAllocSites; private void buildIDs() { for (Start start : data.getAst()) { if (data.isNative(start)) continue; start.apply(new DepthFirstAdapter() { @Override public void inAInvokeExp(AInvokeExp node) { invoke2callid.put(node, nextCallId++); } // @Override // public void inANewExp(ANewExp node) { // exp2allocid.put(node, nextAllocId++); // } // @Override // public void inAObjectLiteralExp(AObjectLiteralExp node) { // exp2allocid.put(node, nextAllocId++); // } // @Override // public void inARegexpExp(ARegexpExp node) { // exp2allocid.put(node, nextAllocId++); // } // @Override // public void inAArrayLiteralExp(AArrayLiteralExp node) { // exp2allocid.put(node, nextAllocId++); // } // @Override // public void inAFunctionExp(AFunctionExp node) { // handleFunction(node); // } // @Override // public void inAFunctionDeclStmt(AFunctionDeclStmt node) { // handleFunction(node); // } // void handleFunction(IFunction node) { // func2allocid.put(node, nextAllocId++); // funcproto2allocid.put(node, nextAllocId++); // } }); } } private void buildCallGraph() { for (Start start : data.getAst()) { if (data.isNative(start)) continue; start.apply(new DepthFirstAdapter() { @Override public void inAInvokeExp(AInvokeExp invoke) { int id = invoke2callid.get(invoke); for (IFunction func : data.getTargets(invoke)) { if (data.isNative(func)) continue; func2callees.add(func, id); } } }); } } private int getAllocSiteId(AllocSite site) { // if (allocsite2id.containsKey(site)) { // return allocsite2id.get(site); // } else { // int id = nextAllocSiteId++; // allocsite2id.put(site, id); // return id; // } return allocsite2id.get(site); } private void doInstrumentation() { for (Start start : data.getAst()) { if (data.isNative(start)) continue; AstUtil.fillTokens(start); start.apply(new DepthFirstAdapter() { @Override public void outAFunctionExp(AFunctionExp node) { // function(..) {..} // => (function(..) {..}).$init(id, protoId, [callers, ...], info) AArrayLiteralExp callers = makeCallerArray(node); String name = node.getName() == null ? "<anon>" : node.getName().getText(); int id = getAllocSiteId(new FunctionAllocSite(node)); int protoId = getAllocSiteId(new FunctionProtoAllocSite(node)); AInvokeExp exp = NodeFactory.createInvokeExp( NodeFactory.createPropertyExp(NodeFactory.createParenExp(AstUtil.clone(node)), "$init"), Arrays.asList( NodeFactory.createLiteral(id), NodeFactory.createLiteral(protoId), callers, NodeFactory.createStringLiteral(name + ":" + node.getFunction().getLine()))); AstUtil.replaceNode(node, exp); } @Override public void outAFunctionDeclStmt(AFunctionDeclStmt node) { // { // ... // function Foo(..) {..} // ... // } // => { // Foo.$init(id, protoId, [callers, ...], info); // ... // function Foo(..) {..} // ... // } ABody body = node.getAncestor(ABody.class); AArrayLiteralExp callers = makeCallerArray(node); int id = getAllocSiteId(new FunctionAllocSite(node)); int protoId = getAllocSiteId(new FunctionProtoAllocSite(node)); AInvokeExp exp = NodeFactory.createInvokeExp( NodeFactory.createPropertyExp(NodeFactory.createNameExp(Literals.getName(node)), "$init"), Arrays.asList( NodeFactory.createLiteral(id), NodeFactory.createLiteral(protoId), callers, NodeFactory.createStringLiteral(node.getName().getText() + ":" + node.getName().getLine()))); PrettyPrinter.insertStmtIntoBlock(body.getBlock(), 0, NodeFactory.createExpStmt(exp)); } private AArrayLiteralExp makeCallerArray(IFunction func) { List<PExp> args = new LinkedList<PExp>(); for (int callId : func2callees.getView(func)) { args.add(NodeFactory.createLiteral(callId)); } return NodeFactory.createArrayLiteralExp(args); } @Override public void outAInvokeExp(final AInvokeExp invoke) { final int id = invoke2callid.get(invoke); PExp rcv = unparen(invoke.getFunctionExp()); rcv.apply(new AnalysisAdapter() { @Override public void caseAPropertyExp(APropertyExp node) { methodInvoke(AstUtil.clone(node.getBase()), NodeFactory.createStringLiteral(Literals.getName(node))); } @Override public void caseADynamicPropertyExp(ADynamicPropertyExp node) { methodInvoke(AstUtil.clone(node.getBase()), AstUtil.clone(node.getPropertyExp())); } @Override public void defaultPExp(PExp node) { functionInvoke(AstUtil.clone(node)); } void methodInvoke(PExp base, PExp name) { // base[name](arg1, arg2, ...) // => base.$invoke_method(name, <callID>, [arg1, arg2, ...]) APropertyExp newFuncExp = NodeFactory.createPropertyExp(base, "$invoke_method"); AConstExp callIdExp = NodeFactory.createLiteral(id); AArrayLiteralExp arrayExp = makeArgumentsArray(invoke); AstUtil.replaceNode(invoke, NodeFactory.createInvokeExp(newFuncExp, Arrays.asList(name, callIdExp, arrayExp))); } void functionInvoke(PExp exp) { // foo(arg1, arg2, ...) // => $invoke_function(foo, <callID>, [arg1, arg2, ...]) PExp newFuncExp = NodeFactory.createNameExp("$invoke_function"); AConstExp callIdExp = NodeFactory.createLiteral(id); AArrayLiteralExp arrayExp = makeArgumentsArray(invoke); AstUtil.replaceNode(invoke, NodeFactory.createInvokeExp(newFuncExp, Arrays.asList(exp, callIdExp, arrayExp))); } }); } private AArrayLiteralExp makeArgumentsArray(final IInvocationNode invoke) { List<PExp> arrayArgs = new ArrayList<PExp>(); for (PExp arg : invoke.getArguments()) { arrayArgs.add(AstUtil.clone(arg)); } AArrayLiteralExp arrayExp = NodeFactory.createArrayLiteralExp(arrayArgs); return arrayExp; } private PExp unparen(PExp exp) { while (exp instanceof AParenthesisExp) { exp = ((AParenthesisExp)exp).getExp(); } return exp; } @Override public void outANewExp(ANewExp node) { // new Foo(arg1, ...) // => $construct(Foo, id, [arg1, ...]) int id = getAllocSiteId(new NewExpAllocSite(node)); AArrayLiteralExp arrayExp = makeArgumentsArray(node); AInvokeExp invoke = NodeFactory.createInvokeExp( NodeFactory.createNameExp("$construct"), Arrays.asList( AstUtil.clone(node.getFunctionExp()), NodeFactory.createLiteral(id), arrayExp)); AstUtil.replaceNode(node, invoke); } private void simpleAllocator(PExp node, int id) { AInvokeExp invoke = NodeFactory.createInvokeExp( NodeFactory.createPropertyExp(AstUtil.clone(node), "$init"), Arrays.asList(NodeFactory.createLiteral(id))); AstUtil.replaceNode(node, invoke); } @Override public void outAObjectLiteralExp(AObjectLiteralExp node) { // { prty1: val1, ... } // => { prty1: val1, ... }.$init(id) int id = getAllocSiteId(new ObjLiteralAllocSite(node)); simpleAllocator(node, id); } public void outAArrayLiteralExp(AArrayLiteralExp node) { // [ x1, ... ] // [ x1, ... ].$init(id) int id = getAllocSiteId(new ArrayLiteralAllocSite(node)); simpleAllocator(node, id); } @Override public void outARegexpExp(ARegexpExp node) { // /foo.../ // => /foo.../.$init(id) int id = getAllocSiteId(new RegExpAllocSite(node)); simpleAllocator(node, id); } @Override public void outABody(ABody node) { IFunction function = node.getAncestor(IFunction.class); if (function == null) { addGraphDefinition(node); } else { // insert: arguments.$_obj_id = id int id = getAllocSiteId(new ArgumentsArrayAllocSite(function)); PExp exp = NodeFactory.createAssignExp( NodeFactory.createPropertyExp(NodeFactory.createNameExp("arguments"), "$_obj_id"), NodeFactory.createLiteral(id)); PrettyPrinter.insertStmtIntoBlock(node.getBlock(), 0, NodeFactory.createExpStmt(exp)); int maxDepth = 2; // insert: $check_object_graph([this,arg1,...],[[id1,..],[id,..]...],"line X",["this","arg1",...],maxDepth) List<PExp> objs = new LinkedList<PExp>(); List<PExp> idss = new LinkedList<PExp>(); List<PExp> descriptions = new LinkedList<PExp>(); // first add 'this' objs.add(NodeFactory.createThisExp()); idss.add(makeObjIdArray(data.getThisAllocationSites(function))); descriptions.add(NodeFactory.createStringLiteral("this")); // add parameters for (int i=0; i<function.getParameters().size(); i++) { String name = Literals.parseIdentifier(function.getParameters().get(i).getText()); objs.add(NodeFactory.createNameExp(name)); idss.add(makeObjIdArray(data.getArgumentAllocationSites(function, i))); descriptions.add(NodeFactory.createStringLiteral(name)); } String infoStr = "line " + function.getFunction().getLine(); AInvokeExp invoke = NodeFactory.createInvokeExp( NodeFactory.createNameExp("$check_object_graph"), Arrays.asList( NodeFactory.createArrayLiteralExp(objs), NodeFactory.createArrayLiteralExp(idss), NodeFactory.createStringLiteral(infoStr), NodeFactory.createArrayLiteralExp(descriptions), NodeFactory.createLiteral(maxDepth) )); PrettyPrinter.insertStmtIntoBlock(node.getBlock(), 1, NodeFactory.createExpStmt(invoke)); } } private AArrayLiteralExp makeObjIdArray(Collection<? extends AllocSite> sites) { List<Integer> num = new LinkedList<Integer>(); for (AllocSite site : sites) { num.add(allocsite2id.get(site)); } return numberArray(num); } // @Override // public void caseAExpStmt(AExpStmt node) { // // compute source string before instrumenting children // String infoStr = "line " + AstUtil.getFirstAndLastToken(node).first.getLine(); // String expStr; // if (node.getExp() instanceof AAssignExp) { // AAssignExp as = (AAssignExp) node.getExp(); // expStr = AstUtil.toSourceString(as.getLeft()); // } else { // expStr = AstUtil.toSourceString(node.getExp()); // } // Set<AllocSite> resultAllocationSites = data.getResultAllocationSites(node.getExp()); // // node.getExp().apply(this); // recurse // // // exp // // $check_object_graph(exp, [id1, ...], "linenr", "...") // List<PExp> ids = new LinkedList<PExp>(); // for (AllocSite site : resultAllocationSites) { // ids.add(NodeFactory.createLiteral(getAllocSiteId(site))); // } // if (expStr.length() > 55) { // expStr = expStr.substring(0, 52) + "..."; // } // AInvokeExp invoke = NodeFactory.createInvokeExp( // NodeFactory.createNameExp("$check_object_graph"), // Arrays.<PExp>asList( // AstUtil.clone(node.getExp()), // NodeFactory.createArrayLiteralExp(ids), // NodeFactory.createStringLiteral(infoStr), // NodeFactory.createStringLiteral(expStr))); // AstUtil.replaceNode(node.getExp(), invoke); // } private AArrayLiteralExp numberArray(Collection<Integer> nums) { List<PExp> exps= new LinkedList<PExp>(); for (int num : nums) { exps.add(NodeFactory.createLiteral(num)); } return NodeFactory.createArrayLiteralExp(exps); } private void addGraphDefinition(ABody node) { // $graph = [ // {"$_proto":[proto1,...], "prty1":[prty1,...], ...}, // .... // ]; List<PExp> abstractObjs = new LinkedList<PExp>(); for (AbstractObj ab : objectGraph) { List<Pair<String,PExp>> prtys = new LinkedList<Pair<String,PExp>>(); prtys.add(new Pair<String,PExp>("$_proto", numberArray(ab.proto))); for (String prty : ab.pointsTo.keySet()) { prtys.add(new Pair<String,PExp>(prty, numberArray(ab.pointsTo.getView(prty)))); } abstractObjs.add(NodeFactory.createObjectLiteral(prtys)); } AArrayLiteralExp array = NodeFactory.createArrayLiteralExp(abstractObjs); PExp assign = NodeFactory.createAssignExp( NodeFactory.createNameExp("$graph"), array); PrettyPrinter.insertStmtIntoBlock(node.getBlock(), 0, NodeFactory.createExpStmt(assign)); } }); } } }