com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.FlowGraphBuilder Maven / Gradle / Ivy
Show all versions of com.ibm.wala.cast.js Show documentation
/*
* 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.flowgraph;
import com.ibm.wala.cast.ir.ssa.AstGlobalRead;
import com.ibm.wala.cast.ir.ssa.AstGlobalWrite;
import com.ibm.wala.cast.ir.ssa.AstLexicalAccess.Access;
import com.ibm.wala.cast.ir.ssa.AstLexicalRead;
import com.ibm.wala.cast.ir.ssa.AstLexicalWrite;
import com.ibm.wala.cast.ir.ssa.AstPropertyRead;
import com.ibm.wala.cast.ir.ssa.AstPropertyWrite;
import com.ibm.wala.cast.js.callgraph.fieldbased.JSMethodInstructionVisitor;
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.CreationSiteVertex;
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.Vertex;
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.VertexFactory;
import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
import com.ibm.wala.cast.js.ipa.callgraph.JSSSAPropagationCallGraphBuilder;
import com.ibm.wala.cast.js.ssa.JavaScriptInvoke;
import com.ibm.wala.cast.js.ssa.PrototypeLookup;
import com.ibm.wala.cast.js.ssa.SetPrototype;
import com.ibm.wala.cast.js.types.JavaScriptMethods;
import com.ibm.wala.cast.js.types.JavaScriptTypes;
import com.ibm.wala.cast.js.util.Util;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.loader.AstMethod.LexicalInformation;
import com.ibm.wala.cast.types.AstMethodReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAGetCaughtExceptionInstruction;
import com.ibm.wala.ssa.SSAGetInstruction;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSANewInstruction;
import com.ibm.wala.ssa.SSAPhiInstruction;
import com.ibm.wala.ssa.SSAPutInstruction;
import com.ibm.wala.ssa.SSAReturnInstruction;
import com.ibm.wala.ssa.SSAThrowInstruction;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.Iterator2Iterable;
import com.ibm.wala.util.intset.EmptyIntSet;
import com.ibm.wala.util.intset.IntSet;
/**
* Class for building intra-procedural flow graphs for a given class hierarchy.
*
* @author mschaefer
*/
public class FlowGraphBuilder {
private final IClassHierarchy cha;
private final IAnalysisCacheView cache;
private final boolean supportFullPointerAnalysis;
public FlowGraphBuilder(
IClassHierarchy cha, IAnalysisCacheView cache, boolean supportPointerAnalysis) {
this.cha = cha;
this.cache = cache;
this.supportFullPointerAnalysis = supportPointerAnalysis;
}
/**
* This is the main entry point of the flow graph builder.
*
* It creates a new, empty flow graph, adds nodes for a small number of special primitive
* functions such as {@code Object} and {@code Function} and sets up flow edges to make them flow
* into the corresponding global variables. Then it iterates over all functions in the class
* hierarchy and all their IR instructions, and adds the flow edges induced by these instructions.
*
* @return the completed flow graph
*/
public FlowGraph buildFlowGraph() {
FlowGraph flowgraph = new FlowGraph();
addPrimitives(flowgraph);
visitProgram(flowgraph);
return flowgraph;
}
public void visitProgram(FlowGraph flowgraph) {
for (IClass klass : cha) {
for (IMethod method : klass.getDeclaredMethods()) {
if (method.getDescriptor().equals(AstMethodReference.fnDesc)) {
visitFunction(flowgraph, method);
}
}
}
}
public void visitFunction(FlowGraph flowgraph, IMethod method) {
{
IR ir = cache.getIR(method);
FlowGraphSSAVisitor visitor = new FlowGraphSSAVisitor(ir, flowgraph);
// first visit normal instructions
SSAInstruction[] normalInstructions = ir.getInstructions();
for (int i = 0; i < normalInstructions.length; ++i)
if (normalInstructions[i] != null) {
visitor.instructionIndex = i;
normalInstructions[i].visit(visitor);
}
// now visit phis and catches
visitor.instructionIndex = -1;
for (SSAInstruction inst : Iterator2Iterable.make(ir.iteratePhis())) inst.visit(visitor);
for (SSAInstruction inst : Iterator2Iterable.make(ir.iterateCatchInstructions()))
inst.visit(visitor);
}
}
// primitive functions that are treated specially
private static final String[] primitiveFunctions = {
"Object", "Function", "Array", "StringObject", "NumberObject", "BooleanObject", "RegExp"
};
/**
* Add flows from the special primitive functions to the corresponding global variables.
*
* @param flowgraph the flow graph under construction
*/
private void addPrimitives(FlowGraph flowgraph) {
VertexFactory factory = flowgraph.getVertexFactory();
for (String pf : primitiveFunctions) {
TypeReference typeref = TypeReference.findOrCreate(JavaScriptTypes.jsLoader, 'L' + pf);
IClass klass = cha.lookupClass(typeref);
String prop = pf.endsWith("Object") ? pf.substring(0, pf.length() - 6) : pf;
flowgraph.addEdge(factory.makeFuncVertex(klass), factory.makePropVertex(prop));
}
}
/**
* Visitor class that does the heavy lifting (such as it is) of flow graph construction, adding
* flow graph edges for every instruction in the method IR.
*
*
The only slightly tricky thing are assignments to exposed variables inside their defining
* function. In the IR, they initially appear as normal SSA variable assignments, without any
* indication of their lexical nature. The normal call graph construction logic does something
* convoluted to fix this up later when an actual lexical access is encountered.
*
*
We use a much simpler approach. Whenever we see an assignment vi = e
* , we ask the enclosing function whether vi
is an exposed variable. If
* it is, we determine its source-level names
* x1, x2, ..., xn
, and then add edges corresponding to
* lexical writes of vi
into all the xj
.
*
* @author mschaefer
*/
private class FlowGraphSSAVisitor extends JSMethodInstructionVisitor {
// index of the instruction currently visited; -1 if the instruction isn't a normal instruction
public int instructionIndex = -1;
// flow graph being built
private final FlowGraph flowgraph;
// vertex factory to use for constructing new vertices
private final VertexFactory factory;
// lexical information about the current function
private final LexicalInformation lexicalInfo;
// the set of SSA variables in the current function that are accessed by nested functions
private final IntSet exposedVars;
// the IR of the current function
private final IR ir;
// the function vertex corresponding to the current function
private final FuncVertex func;
public FlowGraphSSAVisitor(IR ir, FlowGraph flowgraph) {
super(ir.getMethod(), ir.getSymbolTable(), cache.getDefUse(ir));
this.ir = ir;
this.flowgraph = flowgraph;
this.factory = flowgraph.getVertexFactory();
this.func = factory.makeFuncVertex(ir.getMethod().getDeclaringClass());
if (method instanceof AstMethod) {
this.lexicalInfo = ((AstMethod) method).lexicalInfo();
this.exposedVars = lexicalInfo.getAllExposedUses();
} else {
this.lexicalInfo = null;
this.exposedVars = EmptyIntSet.instance;
}
}
// add extra flow from v_def to every lexical variable it may correspond to at source-level
private void handleLexicalDef(int def) {
assert def != -1;
if (instructionIndex != -1 && exposedVars.contains(def)) {
VarVertex v = factory.makeVarVertex(func, def);
for (String localName : ir.getLocalNames(instructionIndex, def))
flowgraph.addEdge(
v, factory.makeLexicalAccessVertex(lexicalInfo.getScopingName(), localName));
}
}
@Override
public void visitPhi(SSAPhiInstruction phi) {
int n = phi.getNumberOfUses();
VarVertex w = factory.makeVarVertex(func, phi.getDef());
for (int i = 0; i < n; ++i) {
VarVertex v = factory.makeVarVertex(func, phi.getUse(i));
flowgraph.addEdge(v, w);
}
}
@Override
public void visitPrototypeLookup(PrototypeLookup proto) {
// treat it simply as an assignment
flowgraph.addEdge(
factory.makeVarVertex(func, proto.getUse(0)),
factory.makeVarVertex(func, proto.getDef()));
handleLexicalDef(proto.getDef());
}
private void visitPut(int val, String propName) {
Vertex v = factory.makeVarVertex(func, val), w = factory.makePropVertex(propName);
flowgraph.addEdge(v, w);
}
@Override
public void visitPut(SSAPutInstruction put) {
visitPut(put.getVal(), put.getDeclaredField().getName().toString());
}
@Override
public void visitSetPrototype(SetPrototype instruction) {
visitPut(instruction.getUse(1), "prototype");
}
@Override
public void visitAstGlobalWrite(AstGlobalWrite instruction) {
String propName = instruction.getDeclaredField().getName().toString();
// hack to account for global variables
assert propName.startsWith("global ");
propName = propName.substring("global ".length());
visitPut(instruction.getVal(), propName);
}
@Override
public void visitPropertyWrite(AstPropertyWrite pw) {
int p = pw.getMemberRef();
if (symtab.isConstant(p)) {
String pn = JSCallGraphUtil.simulateToStringForPropertyNames(symtab.getConstantValue(p));
Vertex v = factory.makeVarVertex(func, pw.getValue()), w = factory.makePropVertex(pn);
flowgraph.addEdge(v, w);
}
}
@Override
public void visitAstLexicalWrite(AstLexicalWrite lw) {
for (Access acc : lw.getAccesses()) {
Vertex v = factory.makeVarVertex(func, acc.valueNumber),
w = factory.makeLexicalAccessVertex(acc.variableDefiner, acc.variableName);
flowgraph.addEdge(v, w);
}
}
@Override
public void visitGet(SSAGetInstruction get) {
String propName = get.getDeclaredField().getName().toString();
if (propName.startsWith("global ")) propName = propName.substring("global ".length());
Vertex v = factory.makePropVertex(propName), w = factory.makeVarVertex(func, get.getDef());
flowgraph.addEdge(v, w);
handleLexicalDef(get.getDef());
}
@Override
public void visitAstGlobalRead(AstGlobalRead instruction) {
if (supportFullPointerAnalysis
&& instruction
.getGlobalName()
.endsWith(JSSSAPropagationCallGraphBuilder.GLOBAL_OBJ_VAR_NAME)) {
Vertex lval = factory.makeVarVertex(func, instruction.getDef());
flowgraph.addEdge(factory.global(), lval);
} else {
visitGet(instruction);
}
}
@Override
public void visitPropertyRead(AstPropertyRead pr) {
int p = pr.getMemberRef();
if (symtab.isConstant(p)) {
String pn = JSCallGraphUtil.simulateToStringForPropertyNames(symtab.getConstantValue(p));
Vertex v = factory.makePropVertex(pn), w = factory.makeVarVertex(func, pr.getDef());
flowgraph.addEdge(v, w);
}
IntSet argVns = Util.getArgumentsArrayVns(ir, du);
if (argVns.contains(pr.getObjectRef())) {
Vertex v = factory.makeArgVertex(func), w = factory.makeVarVertex(func, pr.getDef());
flowgraph.addEdge(v, w);
}
handleLexicalDef(pr.getDef());
}
@Override
public void visitAstLexicalRead(AstLexicalRead lr) {
for (Access acc : lr.getAccesses()) {
Vertex v = factory.makeLexicalAccessVertex(acc.variableDefiner, acc.variableName),
w = factory.makeVarVertex(func, acc.valueNumber);
flowgraph.addEdge(v, w);
handleLexicalDef(acc.valueNumber);
}
}
@Override
public void visitReturn(SSAReturnInstruction ret) {
if (ret.getResult() != -1) { // non-void return
Vertex v = factory.makeVarVertex(func, ret.getResult()), w = factory.makeRetVertex(func);
flowgraph.addEdge(v, w);
}
}
@Override
public void visitThrow(SSAThrowInstruction thr) {
Vertex v = factory.makeVarVertex(func, thr.getException()), w = factory.makeUnknownVertex();
flowgraph.addEdge(v, w);
}
@Override
public void visitGetCaughtException(SSAGetCaughtExceptionInstruction katch) {
Vertex v = factory.makeUnknownVertex(), w = factory.makeVarVertex(func, katch.getDef());
flowgraph.addEdge(v, w);
}
@Override
public void visitJavaScriptInvoke(JavaScriptInvoke invk) {
flowgraph.addEdge(
factory.makeUnknownVertex(), factory.makeVarVertex(func, invk.getException()));
// check whether this invoke corresponds to a function expression/declaration
// flow callee variable into callee vertex
if (invk.getDeclaredTarget().equals(JavaScriptMethods.ctorReference)) {
flowgraph.addEdge(
factory.makeVarVertex(func, invk.getFunction()), factory.makeCallVertex(func, invk));
if (isFunctionConstructorInvoke(invk)) {
// second parameter is function name
String fn_name = symtab.getStringValue(invk.getUse(1));
// find the function being defined here
IClass klass =
cha.lookupClass(TypeReference.findOrCreate(JavaScriptTypes.jsLoader, fn_name));
if (klass == null) {
System.err.println(
"cannot find "
+ fn_name
+ " at "
+ ((AstMethod) ir.getMethod())
.getSourcePosition(
ir.getCallInstructionIndices(invk.getCallSite()).intIterator().next()));
return;
}
IMethod fn = klass.getMethod(AstMethodReference.fnSelector);
FuncVertex fnVertex = factory.makeFuncVertex(klass);
// function flows into its own v1 variable
flowgraph.addEdge(fnVertex, factory.makeVarVertex(fnVertex, 1));
// flow parameters into local variables
for (int i = 1; i < fn.getNumberOfParameters(); ++i)
flowgraph.addEdge(
factory.makeParamVertex(fnVertex, i), factory.makeVarVertex(fnVertex, i + 1));
// flow function into result variable
flowgraph.addEdge(fnVertex, factory.makeVarVertex(func, invk.getDef()));
} else if (supportFullPointerAnalysis) {
CreationSiteVertex cs =
factory.makeCreationSiteVertex(method, invk.iIndex(), JavaScriptTypes.Object);
// flow creation site into result of new call
flowgraph.addEdge(cs, factory.makeVarVertex(func, invk.getDef()));
// also passed as 'this' to constructor
if (invk.getNumberOfPositionalParameters() > 1) {
flowgraph.addEdge(cs, factory.makeVarVertex(func, invk.getUse(0)));
}
}
} else {
// check whether it is a method call
if (invk.getDeclaredTarget().equals(JavaScriptMethods.dispatchReference)) {
// we only handle method calls with constant names
if (symtab.isConstant(invk.getFunction())) {
String pn =
JSCallGraphUtil.simulateToStringForPropertyNames(
symtab.getConstantValue(invk.getFunction()));
// flow callee property into callee vertex
flowgraph.addEdge(factory.makePropVertex(pn), factory.makeCallVertex(func, invk));
}
} else {
// this case is simpler: just flow callee variable into callee vertex
flowgraph.addEdge(
factory.makeVarVertex(func, invk.getFunction()), factory.makeCallVertex(func, invk));
}
}
handleLexicalDef(invk.getDef());
}
@Override
public void visitNew(SSANewInstruction invk) {
if (supportFullPointerAnalysis) {
// special case for supporting full pointer analysis
// some core objects in the prologue (and the arguments array objects) get created with
// 'new'
CreationSiteVertex cs =
factory.makeCreationSiteVertex(method, invk.iIndex(), invk.getConcreteType());
// flow creation site into result of new call
flowgraph.addEdge(cs, factory.makeVarVertex(func, invk.getDef()));
}
}
}
}