/******************************************************************************* * Copyright (c) 2013 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.Set; import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.FlowGraph; 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.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.summaries.JavaScriptConstructorFunctions; import com.ibm.wala.cast.js.ssa.JavaScriptInvoke; import com.ibm.wala.cast.js.types.JavaScriptMethods; import com.ibm.wala.cast.types.AstMethodReference; import com.ibm.wala.ipa.callgraph.AnalysisCache; import com.ibm.wala.ipa.callgraph.AnalysisOptions; import com.ibm.wala.ipa.callgraph.MethodTargetSelector; import com.ibm.wala.ipa.cha.IClassHierarchy; 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; /** * Optimistic call graph builder that propagates inter-procedural data flow iteratively as * call edges are discovered. Slower, but potentially more sound than {@link PessimisticCallGraphBuilder}. * * @author mschaefer * */ public class OptimisticCallgraphBuilder extends FieldBasedCallGraphBuilder { /** The maximum number of iterations to perform. */ public int ITERATION_CUTOFF = Integer.MAX_VALUE; private final boolean handleCallApply; public OptimisticCallgraphBuilder(IClassHierarchy cha, AnalysisOptions options, AnalysisCache cache, boolean supportFullPointerAnalysis) { super(cha, options, cache, supportFullPointerAnalysis); handleCallApply = options instanceof JSAnalysisOptions && ((JSAnalysisOptions)options).handleCallApply(); } @Override public FlowGraph buildFlowGraph(IProgressMonitor monitor) throws CancelException { FlowGraph flowgraph = flowGraphFactory(); // keep track of which call edges we already know about Set<Pair<CallVertex, FuncVertex>> knownEdges = HashSetFactory.make(); // flag for fixpoint iteration boolean changed = true; int iter = 0; while(iter++ < ITERATION_CUTOFF && changed) { MonitorUtil.throwExceptionIfCanceled(monitor); changed = false; // extract all call edges from the flow graph Set<Pair<CallVertex, FuncVertex>> newEdges = this.extractCallGraphEdges(flowgraph, monitor); for(Pair<CallVertex, FuncVertex> edge : newEdges) { MonitorUtil.throwExceptionIfCanceled(monitor); // set changed to true if this is a new edge boolean newEdge = knownEdges.add(edge); changed = changed || newEdge; if(newEdge) { // handle it addEdge(flowgraph, edge.fst, edge.snd, monitor); // special handling of invocations of Function.prototype.call // TODO: since we've just added some edges to the flow graph, its transitive closure will be // recomputed here, which is slow and unnecessary if(handleCallApply && (edge.snd.getFullName().equals("Lprologue.js/Function_prototype_call") || edge.snd.getFullName().equals("Lprologue.js/Function_prototype_apply"))) { addReflectiveCallEdge(flowgraph, edge.fst, monitor); } } } } return flowgraph; } // add flow corresponding to a new call edge private void addEdge(FlowGraph flowgraph, CallVertex c, FuncVertex callee, IProgressMonitor monitor) throws CancelException { VertexFactory factory = flowgraph.getVertexFactory(); JavaScriptInvoke invk = c.getInstruction(); FuncVertex caller = c.getCaller(); int offset = 0; if (invk.getDeclaredTarget().getSelector().equals(JavaScriptMethods.ctorReference.getSelector())) { offset = 1; } for(int i=0;i<invk.getNumberOfParameters();++i) { // only flow receiver into 'this' if invk is, in fact, a method call flowgraph.addEdge(factory.makeVarVertex(caller, invk.getUse(i)), factory.makeArgVertex(callee)); //if(i != 1 || !invk.getDeclaredTarget().getSelector().equals(AstMethodReference.fnSelector)) flowgraph.addEdge(factory.makeVarVertex(caller, invk.getUse(i)), factory.makeParamVertex(callee, i+offset)); } // flow from return vertex to result vertex flowgraph.addEdge(factory.makeRetVertex(callee), factory.makeVarVertex(caller, invk.getDef())); } // add data flow corresponding to a reflective invocation via Function.prototype.call // NB: for f.call(...), f will _not_ appear as a call target, but the appropriate argument and return data flow will be set up private void addReflectiveCallEdge(FlowGraph flowgraph, CallVertex c, IProgressMonitor monitor) throws CancelException { VertexFactory factory = flowgraph.getVertexFactory(); FuncVertex caller = c.getCaller(); JavaScriptInvoke invk = c.getInstruction(); VarVertex receiverVertex = factory.makeVarVertex(caller, invk.getUse(1)); OrdinalSet<FuncVertex> realCallees = flowgraph.getReachingSet(receiverVertex, monitor); System.err.println("callees " + realCallees + " for " + caller); for(FuncVertex realCallee: realCallees) { // flow from arguments to parameters for(int i=2;i<invk.getNumberOfParameters();++i) flowgraph.addEdge(factory.makeVarVertex(caller, invk.getUse(i)), factory.makeParamVertex(realCallee, i-1)); // flow from return vertex to result vertex flowgraph.addEdge(factory.makeRetVertex(realCallee), factory.makeVarVertex(caller, invk.getDef())); } } }