WALA/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/JavaScriptFunctionApplyCont...

200 lines
9.0 KiB
Java

/*******************************************************************************
* 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.ipa.callgraph;
import com.ibm.wala.cast.ipa.callgraph.AstContextInsensitiveSSAContextInterpreter;
import com.ibm.wala.cast.js.ipa.summaries.JavaScriptSummarizedFunction;
import com.ibm.wala.cast.js.ipa.summaries.JavaScriptSummary;
import com.ibm.wala.cast.js.ssa.JSInstructionFactory;
import com.ibm.wala.cast.js.types.JavaScriptTypes;
import com.ibm.wala.cast.loader.DynamicCallSiteReference;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.ContextItem;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ssa.ConstantValue;
import com.ibm.wala.ssa.DefUse;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeName;
/**
* TODO cache generated IRs
*
* @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/Apply">MDN Function.apply() docs</a>
*/
public class JavaScriptFunctionApplyContextInterpreter extends AstContextInsensitiveSSAContextInterpreter {
private static final TypeName APPLY_TYPE_NAME = TypeName.findOrCreate("Lprologue.js/Function_prototype_apply");
public JavaScriptFunctionApplyContextInterpreter(AnalysisOptions options, IAnalysisCacheView cache) {
super(options, cache);
}
@Override
public boolean understands(CGNode node) {
return node.getMethod().getDeclaringClass().getName().equals(APPLY_TYPE_NAME);
}
@Override
public IR getIR(CGNode node) {
assert understands(node);
@SuppressWarnings("unchecked")
ContextItem.Value<Boolean> isNonNullArray = (ContextItem.Value<Boolean>) node.getContext().get(JavaScriptFunctionApplyContextSelector.APPLY_NON_NULL_ARGS);
// isNonNullArray can be null if, e.g., due to recursion bounding we have no
// information on the arguments parameter
if (isNonNullArray == null || isNonNullArray.getValue()) {
return makeIRForArgList(node);
} else {
return makeIRForNoArgList(node);
}
}
private static IR makeIRForArgList(CGNode node) {
// we have: v1 is dummy apply method
// v2 is function to be invoked
// v3 is argument to be passed as 'this'
// v4 is array containing remaining arguments
// Ideally, we would take advantage of cases like constant arrays and
// precisely pass arguments in the appropriate slots. Unfortunately, in the
// pointer analysis fixed-point computation, it's possible that we will
// process the apply() call and then process some update to the arguments
// array, reflected only in its property values and object catalog. Perhaps
// eventually, we could create contexts based on the catalog of the object
// and then do a better job, but since the catalog is not passed directly as
// a parameter to apply(), this is not so easy.
// In the meantime, we do things imprecisely. We read an arbitrary
// enumerable property name of the argument list (via an
// EachElementGetInstruction), perform a dynamic read of that property, and
// then pass the resulting values in all argument positions (except 'this').
//
// NOTE: we don't know how many arguments the callee will take, whether it
// uses
// the arguments array, etc. For now, we use an unsound hack and pass the
// argument 10 times.
//
// NOTE: strictly speaking, using EachElementGet could be imprecise, as it
// should
// return properties inherited via the prototype chain. However, since this
// behavior
// is not modeled in WALA as of now, using the instruction is ok.
MethodReference ref = node.getMethod().getReference();
IClass declaringClass = node.getMethod().getDeclaringClass();
JSInstructionFactory insts = (JSInstructionFactory) declaringClass.getClassLoader().getInstructionFactory();
// nargs needs to match that of Function.apply(), even though no argsList
// argument was passed in this case
int nargs = 4;
JavaScriptSummary S = new JavaScriptSummary(ref, nargs);
int numParamsToPass = 10;
int[] paramsToPassToInvoked = new int[numParamsToPass + 1];
// pass the 'this' argument first
paramsToPassToInvoked[0] = 3;
// int curValNum = passArbitraryPropertyValAsParams(insts, nargs, S, paramsToPassToInvoked);
int curValNum = passActualPropertyValsAsParams(insts, nargs, S, paramsToPassToInvoked);
CallSiteReference cs = new DynamicCallSiteReference(JavaScriptTypes.CodeBody, S.getNextProgramCounter());
// function being invoked is in v2
int resultVal = curValNum++;
int excVal = curValNum++;
S.addStatement(insts.Invoke(S.getNumberOfStatements(), 2, resultVal, paramsToPassToInvoked, excVal, cs));
S.getNextProgramCounter();
S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), resultVal, false));
S.getNextProgramCounter();
JavaScriptSummarizedFunction t = new JavaScriptSummarizedFunction(ref, S, declaringClass);
return t.makeIR(node.getContext(), null);
}
@SuppressWarnings("unused")
private static int passArbitraryPropertyValAsParams(JSInstructionFactory insts, int nargs, JavaScriptSummary S, int[] paramsToPassToInvoked) {
// read an arbitrary property name via EachElementGet
int curValNum = nargs + 2;
int eachElementGetResult = curValNum++;
int nullPredVn = curValNum++;
S.addConstant(nullPredVn, new ConstantValue(null));
S.addStatement(insts.EachElementGetInstruction(S.getNumberOfStatements(), eachElementGetResult, 4, nullPredVn));
S.getNextProgramCounter();
// read value from the arbitrary property name
int propertyReadResult = curValNum++;
S.addStatement(insts.PropertyRead(S.getNumberOfStatements(), propertyReadResult, 4, eachElementGetResult));
S.getNextProgramCounter();
for (int i = 1; i < paramsToPassToInvoked.length; i++) {
paramsToPassToInvoked[i] = propertyReadResult;
}
return curValNum;
}
private static int passActualPropertyValsAsParams(JSInstructionFactory insts, int nargs, JavaScriptSummary S, int[] paramsToPassToInvoked) {
// read an arbitrary property name via EachElementGet
int nullVn = nargs + 2;
S.addConstant(nullVn, new ConstantValue(null));
int curValNum = nargs + 3;
for (int i = 1; i < paramsToPassToInvoked.length; i++) {
// create a String constant for i-1
final int constVN = curValNum++;
// the commented line is correct, but it doesn't work because
// of our broken handling of int constants as properties.
// TODO fix property handling, and then fix this
S.addConstant(constVN, new ConstantValue(Integer.toString(i-1)));
//S.addConstant(constVN, new ConstantValue(i-1));
int propertyReadResult = curValNum++;
// 4 is position of arguments array
S.addStatement(insts.PropertyWrite(S.getNumberOfStatements(), 4, constVN, nullVn));
S.getNextProgramCounter();
S.addStatement(insts.PropertyRead(S.getNumberOfStatements(), propertyReadResult, 4, constVN));
S.getNextProgramCounter();
paramsToPassToInvoked[i] = propertyReadResult;
}
return curValNum;
}
private static IR makeIRForNoArgList(CGNode node) {
// kind of a hack; re-use the summarized function infrastructure
MethodReference ref = node.getMethod().getReference();
IClass declaringClass = node.getMethod().getDeclaringClass();
JSInstructionFactory insts = (JSInstructionFactory) declaringClass.getClassLoader().getInstructionFactory();
// nargs needs to match that of Function.apply(), even though no argsList
// argument was passed in this case
int nargs = 4;
JavaScriptSummary S = new JavaScriptSummary(ref, nargs);
// generate invocation instruction for the real method being invoked
int resultVal = nargs + 2;
CallSiteReference cs = new DynamicCallSiteReference(JavaScriptTypes.CodeBody, S.getNextProgramCounter());
int[] params = new int[1];
params[0] = 3;
// function being invoked is in v2
S.addStatement(insts.Invoke(S.getNumberOfStatements(), 2, resultVal, params, resultVal + 1, cs));
S.getNextProgramCounter();
S.addStatement(insts.ReturnInstruction(S.getNumberOfStatements(), resultVal, false));
S.getNextProgramCounter();
JavaScriptSummarizedFunction t = new JavaScriptSummarizedFunction(ref, S, declaringClass);
return t.makeIR(node.getContext(), null);
}
@Override
public DefUse getDU(CGNode node) {
assert understands(node);
return new DefUse(getIR(node));
}
}