/****************************************************************************** * Copyright (c) 2002 - 2012 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.callgraph.fieldbased; import java.util.Iterator; import java.util.Set; import com.ibm.wala.cast.ipa.callgraph.AstContextInsensitiveSSAContextInterpreter; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.FlowGraph; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.FlowGraphBuilder; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.CallVertex; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.FuncVertex; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.ObjectVertex; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.VarVertex; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.VertexFactory; import com.ibm.wala.cast.js.ipa.callgraph.JSAnalysisOptions; import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraph; import com.ibm.wala.cast.js.ipa.callgraph.JavaScriptConstructTargetSelector; import com.ibm.wala.cast.js.ipa.callgraph.JavaScriptFunctionApplyContextInterpreter; import com.ibm.wala.cast.js.ipa.callgraph.JavaScriptFunctionApplyTargetSelector; import com.ibm.wala.cast.js.ipa.callgraph.JavaScriptFunctionDotCallTargetSelector; import com.ibm.wala.cast.js.ipa.summaries.JavaScriptConstructorFunctions; import com.ibm.wala.cast.js.ipa.summaries.JavaScriptConstructorFunctions.JavaScriptConstructor; import com.ibm.wala.cast.js.types.JavaScriptMethods; import com.ibm.wala.cast.types.AstMethodReference; import com.ibm.wala.classLoader.CallSiteReference; import com.ibm.wala.classLoader.IClass; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.ipa.callgraph.AnalysisCache; import com.ibm.wala.ipa.callgraph.AnalysisOptions; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.Context; import com.ibm.wala.ipa.callgraph.Entrypoint; import com.ibm.wala.ipa.callgraph.MethodTargetSelector; import com.ibm.wala.ipa.callgraph.impl.AbstractRootMethod; import com.ibm.wala.ipa.callgraph.impl.ContextInsensitiveSelector; import com.ibm.wala.ipa.callgraph.impl.Everywhere; import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis; import com.ibm.wala.ipa.callgraph.propagation.cfa.DefaultSSAInterpreter; import com.ibm.wala.ipa.callgraph.propagation.cfa.DelegatingSSAContextInterpreter; import com.ibm.wala.ipa.callgraph.propagation.cfa.nCFAContextSelector; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; import com.ibm.wala.util.CancelException; import com.ibm.wala.util.MonitorUtil; import com.ibm.wala.util.MonitorUtil.IProgressMonitor; import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.collections.Pair; import com.ibm.wala.util.intset.OrdinalSet; /** * Abstract call graph builder class for building a call graph from a field-based flow graph. * The algorithm for building the flow graph is left unspecified, and is implemented differently * by subclasses. * * @author mschaefer * */ public abstract class FieldBasedCallGraphBuilder { // class hierarchy of the program to be analysed protected final IClassHierarchy cha; // standard call graph machinery protected final AnalysisOptions options; protected final AnalysisCache cache; protected final JavaScriptConstructorFunctions constructors; protected final MethodTargetSelector targetSelector; protected final boolean supportFullPointerAnalysis; private static final boolean LOG_TIMINGS = true; public FieldBasedCallGraphBuilder(IClassHierarchy cha, AnalysisOptions options, AnalysisCache cache, boolean supportFullPointerAnalysis) { this.cha = cha; this.options = options; this.cache = cache; this.constructors = new JavaScriptConstructorFunctions(cha); this.targetSelector = setupMethodTargetSelector(cha, constructors, options); this.supportFullPointerAnalysis = supportFullPointerAnalysis; } private MethodTargetSelector setupMethodTargetSelector(IClassHierarchy cha, JavaScriptConstructorFunctions constructors2, AnalysisOptions options) { MethodTargetSelector result = new JavaScriptConstructTargetSelector(constructors2, options.getMethodTargetSelector()); if (options instanceof JSAnalysisOptions && ((JSAnalysisOptions)options).handleCallApply()) { result = new JavaScriptFunctionApplyTargetSelector(new JavaScriptFunctionDotCallTargetSelector(result)); } return result; } protected FlowGraph flowGraphFactory() { FlowGraphBuilder builder = new FlowGraphBuilder(cha, cache, supportFullPointerAnalysis); return builder.buildFlowGraph(); } /** * Build a flow graph for the program to be analysed. */ public abstract FlowGraph buildFlowGraph(IProgressMonitor monitor) throws CancelException; /** * Main entry point: builds a flow graph, then extracts a call graph and returns it. */ public Pair<JSCallGraph,PointerAnalysis<ObjectVertex>> buildCallGraph(Iterable<Entrypoint> eps, IProgressMonitor monitor) throws CancelException { long fgBegin, fgEnd, cgBegin, cgEnd; if(LOG_TIMINGS) fgBegin = System.currentTimeMillis(); MonitorUtil.beginTask(monitor, "flow graph", 1); FlowGraph flowGraph = buildFlowGraph(monitor); MonitorUtil.done(monitor); if(LOG_TIMINGS) { fgEnd = System.currentTimeMillis(); System.out.println("flow graph construction took " + (fgEnd-fgBegin)/1000.0 + " seconds"); cgBegin = System.currentTimeMillis(); } MonitorUtil.beginTask(monitor, "extract call graph", 1); JSCallGraph cg = extract(flowGraph, eps, monitor); MonitorUtil.done(monitor); if(LOG_TIMINGS) { cgEnd = System.currentTimeMillis(); System.out.println("call graph extraction took " + (cgEnd-cgBegin)/1000.0 + " seconds"); } return Pair.make(cg,flowGraph.getPointerAnalysis(cg, cache, monitor)); } /** * Extract a call graph from a given flow graph. */ @SuppressWarnings("deprecation") protected JSCallGraph extract(FlowGraph flowgraph, Iterable<Entrypoint> eps, IProgressMonitor monitor) throws CancelException { // set up call graph final JSCallGraph cg = new JSCallGraph(cha, options, cache); cg.init(); // setup context interpreters DelegatingSSAContextInterpreter interpreter = new DelegatingSSAContextInterpreter(new AstContextInsensitiveSSAContextInterpreter(options, cache), new DefaultSSAInterpreter(options, cache)); if (options instanceof JSAnalysisOptions && ((JSAnalysisOptions)options).handleCallApply()) { interpreter = new DelegatingSSAContextInterpreter(new JavaScriptFunctionApplyContextInterpreter(options, cache), interpreter); } cg.setInterpreter(interpreter); // set up call edges from fake root to all script nodes AbstractRootMethod fakeRootMethod = (AbstractRootMethod)cg.getFakeRootNode().getMethod(); CGNode fakeRootNode = cg.findOrCreateNode(fakeRootMethod, Everywhere.EVERYWHERE); for(Iterator<Entrypoint> iter = eps.iterator(); iter.hasNext();) { Entrypoint ep = iter.next(); CGNode nd = cg.findOrCreateNode(ep.getMethod(), Everywhere.EVERYWHERE); SSAAbstractInvokeInstruction invk = ep.addCall(fakeRootMethod); fakeRootNode.addTarget(invk.getCallSite(), nd); } // register the fake root as the "true" entrypoint cg.registerEntrypoint(fakeRootNode); // now add genuine call edges Set<Pair<CallVertex, FuncVertex>> edges = extractCallGraphEdges(flowgraph, monitor); for (Pair<CallVertex, FuncVertex> edge : edges) { CallVertex callVertex = edge.fst; FuncVertex targetVertex = edge.snd; IClass kaller = callVertex.getCaller().getConcreteType(); CGNode caller = cg.findOrCreateNode(kaller.getMethod(AstMethodReference.fnSelector), Everywhere.EVERYWHERE); CallSiteReference site = callVertex.getSite(); IMethod target = targetSelector.getCalleeTarget(caller, site, targetVertex.getConcreteType()); boolean isFunctionPrototypeCall = target != null && target.getName().toString().startsWith(JavaScriptFunctionDotCallTargetSelector.SYNTHETIC_CALL_METHOD_PREFIX); boolean isFunctionPrototypeApply = target != null && target.getName().toString().startsWith(JavaScriptFunctionApplyTargetSelector.SYNTHETIC_APPLY_METHOD_PREFIX); if (isFunctionPrototypeCall || isFunctionPrototypeApply) { handleFunctionCallOrApplyInvocation(flowgraph, monitor, cg, callVertex, caller, site, target); } else { addEdgeToJSCallGraph(cg, site, target, caller); if (target instanceof JavaScriptConstructor) { IMethod fun = ((JavaScriptConstructor)target).constructedType().getMethod(AstMethodReference.fnSelector); CGNode ctorCaller = cg.findOrCreateNode(target, Everywhere.EVERYWHERE); CallSiteReference ref = null; Iterator<CallSiteReference> sites = ctorCaller.iterateCallSites(); while(sites.hasNext()) { CallSiteReference r = sites.next(); if (r.getDeclaredTarget().getSelector().equals(AstMethodReference.fnSelector)) { ref = r; break; } } if (ref != null) { addEdgeToJSCallGraph(cg, ref, fun, ctorCaller); } } } } return cg; } private boolean handleFunctionCallOrApplyInvocation(FlowGraph flowgraph, IProgressMonitor monitor, final JSCallGraph cg, CallVertex callVertex, CGNode caller, CallSiteReference site, IMethod target) throws CancelException { // use to get 1-level of call string for Function.prototype.call, to // preserve the precision of the field-based call graph final nCFAContextSelector functionPrototypeCallSelector = new nCFAContextSelector(1, new ContextInsensitiveSelector()); Context calleeContext = functionPrototypeCallSelector.getCalleeTarget(caller, site, target, null); boolean ret = addCGEdgeWithContext(cg, site, target, caller, calleeContext); CGNode functionPrototypeCallNode = cg.findOrCreateNode(target, calleeContext); // need to create nodes for reflective targets of call, and then add them // as callees of the synthetic method OrdinalSet<FuncVertex> reflectiveTargets = getReflectiveTargets(flowgraph, callVertex, monitor); System.err.println("adding callees " + reflectiveTargets + " for " + caller); // there should only be one call site in the synthetic method CallSiteReference reflectiveCallSite = functionPrototypeCallNode.getIR().iterateCallSites().next(); for (FuncVertex f : reflectiveTargets) { IMethod reflectiveTgtMethod = targetSelector.getCalleeTarget(functionPrototypeCallNode, reflectiveCallSite, f.getConcreteType()); ret |= addEdgeToJSCallGraph(cg, reflectiveCallSite, reflectiveTgtMethod, functionPrototypeCallNode); } return ret; } private boolean addEdgeToJSCallGraph(final JSCallGraph cg, CallSiteReference site, IMethod target, CGNode caller) throws CancelException { return addCGEdgeWithContext(cg, site, target, caller, Everywhere.EVERYWHERE); } Set<IClass> constructedTypes = HashSetFactory.make(); Everywhere targetContext = Everywhere.EVERYWHERE; @SuppressWarnings("deprecation") private boolean addCGEdgeWithContext(final JSCallGraph cg, CallSiteReference site, IMethod target, CGNode caller, Context targetContext) throws CancelException { boolean ret = false; if(target != null) { CGNode callee = cg.findOrCreateNode(target, targetContext); // add nodes first, to be on the safe side cg.addNode(caller); cg.addNode(callee); // add callee as successor of caller ret = !cg.getPossibleTargets(caller, site).contains(callee); if (ret) { cg.addEdge(caller, callee); caller.addTarget(site, callee); } } return ret; } /** * Given a callVertex that can invoke Function.prototype.call, get the * FuncVertex nodes for the reflectively-invoked methods * @throws CancelException */ private OrdinalSet<FuncVertex> getReflectiveTargets(FlowGraph flowGraph, CallVertex callVertex, IProgressMonitor monitor) throws CancelException { SSAAbstractInvokeInstruction invoke = callVertex.getInstruction(); VarVertex functionParam = flowGraph.getVertexFactory().makeVarVertex(callVertex.getCaller(), invoke.getUse(1)); return flowGraph.getReachingSet(functionParam, monitor); } private OrdinalSet<FuncVertex> getConstructorTargets(FlowGraph flowGraph, CallVertex callVertex, IProgressMonitor monitor) throws CancelException { SSAAbstractInvokeInstruction invoke = callVertex.getInstruction(); assert invoke.getDeclaredTarget().getName().equals(JavaScriptMethods.ctorAtom); VarVertex objectParam = flowGraph.getVertexFactory().makeVarVertex(callVertex.getCaller(), invoke.getUse(0)); return flowGraph.getReachingSet(objectParam, monitor); } /** * Extract call edges from the flow graph into high-level representation. */ protected Set<Pair<CallVertex, FuncVertex>> extractCallGraphEdges(FlowGraph flowgraph, IProgressMonitor monitor) throws CancelException { VertexFactory factory = flowgraph.getVertexFactory(); final Set<Pair<CallVertex, FuncVertex>> result = HashSetFactory.make(); // find all pairs <call, func> such that call is reachable from func in the flow graph for(final CallVertex callVertex : factory.getCallVertices()) { for(FuncVertex funcVertex : flowgraph.getReachingSet(callVertex, monitor)) { result.add(Pair.make(callVertex, funcVertex)); } } return result; } }