eval5
Version:
A JavaScript interpreter written in JavaScript
1,365 lines • 59.6 kB
JavaScript
import { parse } from "acorn";
import { Messages, InterruptThrowError, InterruptThrowReferenceError, InterruptThrowSyntaxError, } from "./messages";
const version = "%VERSION%";
function defineFunctionName(func, name) {
Object.defineProperty(func, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
const Break = Symbol("Break");
const Continue = Symbol("Continue");
const DefaultCase = Symbol("DefaultCase");
const EmptyStatementReturn = Symbol("EmptyStatementReturn");
const WithScopeName = Symbol("WithScopeName");
const SuperScopeName = Symbol("SuperScopeName");
const RootScopeName = Symbol("RootScopeName");
const GlobalScopeName = Symbol("GlobalScopeName");
function isFunction(func) {
return typeof func === "function";
}
class InternalInterpreterReflection {
interpreter;
constructor(interpreter) {
this.interpreter = interpreter;
}
generator() {
const interpreter = this.interpreter;
function getCurrentScope() {
return this.getCurrentScope();
}
function getGlobalScope() {
return this.getGlobalScope();
}
function getCurrentContext() {
return this.getCurrentContext();
}
return {
getOptions: interpreter.getOptions.bind(interpreter),
getCurrentScope: getCurrentScope.bind(interpreter),
getGlobalScope: getGlobalScope.bind(interpreter),
getCurrentContext: getCurrentContext.bind(interpreter),
getExecStartTime: interpreter.getExecStartTime.bind(interpreter),
};
}
}
function internalEval(reflection, code, useGlobalScope = true) {
if (!(reflection instanceof InternalInterpreterReflection)) {
throw new Error("Illegal call");
}
if (typeof code !== "string")
return code;
if (!code)
return void 0;
const instance = reflection.generator();
const opts = instance.getOptions();
const options = {
timeout: opts.timeout,
_initEnv: function () {
// set caller context
if (!useGlobalScope) {
this.setCurrentContext(instance.getCurrentContext());
}
// share timeout
this.execStartTime = instance.getExecStartTime();
this.execEndTime = this.execStartTime;
},
};
const currentScope = useGlobalScope ? instance.getGlobalScope() : instance.getCurrentScope();
const interpreter = new Interpreter(currentScope, options);
return interpreter.evaluate(code);
}
Object.defineProperty(internalEval, "__IS_EVAL_FUNC", {
value: true,
writable: false,
enumerable: false,
configurable: false,
});
function internalFunction(reflection, ...params) {
if (!(reflection instanceof InternalInterpreterReflection)) {
throw new Error("Illegal call");
}
const instance = reflection.generator();
const code = params.pop();
const interpreter = new Interpreter(instance.getGlobalScope(), instance.getOptions());
const wrapCode = `
(function anonymous(${params.join(",")}){
${code}
});
`;
return interpreter.evaluate(wrapCode);
}
Object.defineProperty(internalFunction, "__IS_FUNCTION_FUNC", {
value: true,
writable: false,
enumerable: false,
configurable: false,
});
class Return {
value;
constructor(value) {
this.value = value;
}
}
class BreakLabel {
value;
constructor(value) {
this.value = value;
}
}
class ContinueLabel {
value;
constructor(value) {
this.value = value;
}
}
/**
* scope chain
*
* superScope
* ↓
* rootScope
* ↓
* globalScope
* ↓
* functionScope
*
*/
class Scope {
name;
parent;
data;
labelStack;
constructor(data, parent = null, name) {
this.name = name;
this.parent = parent;
this.data = data;
this.labelStack = [];
}
}
function noop() { }
function createScope(parent = null, name) {
return new Scope(Object.create(null), parent, name);
}
function createRootContext(data) {
return Object.create(data);
}
const BuildInObjects = {
NaN,
Infinity,
undefined,
// null,
Object,
Array,
String,
Boolean,
Number,
Date,
RegExp,
Error,
URIError,
TypeError,
RangeError,
SyntaxError,
ReferenceError,
Math,
parseInt,
parseFloat,
isNaN,
isFinite,
decodeURI,
decodeURIComponent,
encodeURI,
encodeURIComponent,
escape,
unescape,
eval: internalEval,
Function: internalFunction,
};
// ES5 Object
if (typeof JSON !== "undefined") {
BuildInObjects.JSON = JSON;
}
//ES6 Object
if (typeof Promise !== "undefined") {
BuildInObjects.Promise = Promise;
}
if (typeof Set !== "undefined") {
BuildInObjects.Set = Set;
}
if (typeof Map !== "undefined") {
BuildInObjects.Map = Map;
}
if (typeof Symbol !== "undefined") {
BuildInObjects.Symbol = Symbol;
}
if (typeof Proxy !== "undefined") {
BuildInObjects.Proxy = Proxy;
}
if (typeof WeakMap !== "undefined") {
BuildInObjects.WeakMap = WeakMap;
}
if (typeof WeakSet !== "undefined") {
BuildInObjects.WeakSet = WeakSet;
}
if (typeof Reflect !== "undefined") {
BuildInObjects.Reflect = Reflect;
}
export class Interpreter {
static version = version;
static eval = internalEval;
static Function = internalFunction;
static ecmaVersion = 5;
// alert.call(globalContextInFunction, 1);
// fix: alert.call({}, 1); // Illegal invocation
// function func(){
// this;// Interpreter.globalContextInFunction
// }
// func()
static globalContextInFunction = void 0;
static global = Object.create(null);
// last expression value
value;
context;
globalContext;
source;
sourceList = [];
currentScope;
globalScope;
currentContext;
options;
callStack;
collectDeclVars = Object.create(null);
collectDeclFuncs = Object.create(null);
isVarDeclMode = false;
lastExecNode = null;
isRunning = false;
execStartTime;
execEndTime;
constructor(context = Interpreter.global, options = {}) {
this.options = {
ecmaVersion: options.ecmaVersion || Interpreter.ecmaVersion,
timeout: options.timeout || 0,
rootContext: options.rootContext,
globalContextInFunction: options.globalContextInFunction === undefined
? Interpreter.globalContextInFunction
: options.globalContextInFunction,
_initEnv: options._initEnv,
};
this.context = context || Object.create(null);
this.callStack = [];
this.initEnvironment(this.context);
}
initEnvironment(ctx) {
let scope;
//init global scope
if (ctx instanceof Scope) {
scope = ctx;
}
else {
let rootScope = null;
const superScope = this.createSuperScope(ctx);
if (this.options.rootContext) {
rootScope = new Scope(createRootContext(this.options.rootContext), superScope, RootScopeName);
}
scope = new Scope(ctx, rootScope || superScope, GlobalScopeName);
}
this.globalScope = scope;
this.currentScope = this.globalScope;
//init global context to this
this.globalContext = scope.data;
this.currentContext = scope.data;
// collect var/function declare
this.collectDeclVars = Object.create(null);
this.collectDeclFuncs = Object.create(null);
this.execStartTime = Date.now();
this.execEndTime = this.execStartTime;
const _initEnv = this.options._initEnv;
if (_initEnv) {
_initEnv.call(this);
}
}
getExecStartTime() {
return this.execStartTime;
}
getExecutionTime() {
return this.execEndTime - this.execStartTime;
}
setExecTimeout(timeout = 0) {
this.options.timeout = timeout;
}
getOptions() {
return this.options;
}
getGlobalScope() {
return this.globalScope;
}
getCurrentScope() {
return this.currentScope;
}
getCurrentContext() {
return this.currentContext;
}
isInterruptThrow(err) {
return (err instanceof InterruptThrowError ||
err instanceof InterruptThrowReferenceError ||
err instanceof InterruptThrowSyntaxError);
}
createSuperScope(ctx) {
let data = {
...BuildInObjects,
};
const buildInObjectKeys = Object.keys(data);
buildInObjectKeys.forEach(key => {
if (key in ctx) {
delete data[key];
}
});
return new Scope(data, null, SuperScopeName);
}
setCurrentContext(ctx) {
this.currentContext = ctx;
}
setCurrentScope(scope) {
this.currentScope = scope;
}
evaluate(code = "") {
let node;
if (!code)
return;
node = parse(code, {
ranges: true,
locations: true,
ecmaVersion: this.options.ecmaVersion || Interpreter.ecmaVersion,
});
return this.evaluateNode(node, code);
}
appendCode(code) {
return this.evaluate(code);
}
evaluateNode(node, source = "") {
this.value = undefined;
this.source = source;
this.sourceList.push(source);
this.isRunning = true;
//reset timeout
this.execStartTime = Date.now();
this.execEndTime = this.execStartTime;
// reset
this.collectDeclVars = Object.create(null);
this.collectDeclFuncs = Object.create(null);
const currentScope = this.getCurrentScope();
const currentContext = this.getCurrentContext();
const labelStack = currentScope.labelStack.concat([]);
const callStack = this.callStack.concat([]);
const reset = () => {
this.setCurrentScope(currentScope); //reset scope
this.setCurrentContext(currentContext); //reset context
currentScope.labelStack = labelStack; //reset label stack
this.callStack = callStack; //reset call stack
};
// start run
try {
const bodyClosure = this.createClosure(node);
// add declares to data
this.addDeclarationsToScope(this.collectDeclVars, this.collectDeclFuncs, this.getCurrentScope());
bodyClosure();
}
catch (e) {
throw e;
}
finally {
reset();
this.execEndTime = Date.now();
}
this.isRunning = false;
return this.getValue();
}
createErrorMessage(msg, value, node) {
let message = msg[1].replace("%0", String(value));
if (node !== null) {
message += this.getNodePosition(node || this.lastExecNode);
}
return message;
}
createError(message, error) {
return new error(message);
}
createThrowError(message, error) {
return this.createError(message, error);
}
createInternalThrowError(msg, value, node) {
return this.createError(this.createErrorMessage(msg, value, node), msg[2]);
}
checkTimeout() {
if (!this.isRunning)
return false;
const timeout = this.options.timeout || 0;
const now = Date.now();
if (now - this.execStartTime > timeout) {
return true;
}
return false;
}
getNodePosition(node) {
if (node) {
const errorCode = ""; //this.source.slice(node.start, node.end);
return node.loc ? ` [${node.loc.start.line}:${node.loc.start.column}]${errorCode}` : "";
}
return "";
}
createClosure(node) {
let closure;
switch (node.type) {
case "BinaryExpression":
closure = this.binaryExpressionHandler(node);
break;
case "LogicalExpression":
closure = this.logicalExpressionHandler(node);
break;
case "UnaryExpression":
closure = this.unaryExpressionHandler(node);
break;
case "UpdateExpression":
closure = this.updateExpressionHandler(node);
break;
case "ObjectExpression":
closure = this.objectExpressionHandler(node);
break;
case "ArrayExpression":
closure = this.arrayExpressionHandler(node);
break;
case "CallExpression":
closure = this.callExpressionHandler(node);
break;
case "NewExpression":
closure = this.newExpressionHandler(node);
break;
case "MemberExpression":
closure = this.memberExpressionHandler(node);
break;
case "ThisExpression":
closure = this.thisExpressionHandler(node);
break;
case "SequenceExpression":
closure = this.sequenceExpressionHandler(node);
break;
case "Literal":
closure = this.literalHandler(node);
break;
case "Identifier":
closure = this.identifierHandler(node);
break;
case "AssignmentExpression":
closure = this.assignmentExpressionHandler(node);
break;
case "FunctionDeclaration":
closure = this.functionDeclarationHandler(node);
break;
case "VariableDeclaration":
closure = this.variableDeclarationHandler(node);
break;
case "BlockStatement":
case "Program":
closure = this.programHandler(node);
break;
case "ExpressionStatement":
closure = this.expressionStatementHandler(node);
break;
case "EmptyStatement":
closure = this.emptyStatementHandler(node);
break;
case "ReturnStatement":
closure = this.returnStatementHandler(node);
break;
case "FunctionExpression":
closure = this.functionExpressionHandler(node);
break;
case "IfStatement":
closure = this.ifStatementHandler(node);
break;
case "ConditionalExpression":
closure = this.conditionalExpressionHandler(node);
break;
case "ForStatement":
closure = this.forStatementHandler(node);
break;
case "WhileStatement":
closure = this.whileStatementHandler(node);
break;
case "DoWhileStatement":
closure = this.doWhileStatementHandler(node);
break;
case "ForInStatement":
closure = this.forInStatementHandler(node);
break;
case "WithStatement":
closure = this.withStatementHandler(node);
break;
case "ThrowStatement":
closure = this.throwStatementHandler(node);
break;
case "TryStatement":
closure = this.tryStatementHandler(node);
break;
case "ContinueStatement":
closure = this.continueStatementHandler(node);
break;
case "BreakStatement":
closure = this.breakStatementHandler(node);
break;
case "SwitchStatement":
closure = this.switchStatementHandler(node);
break;
case "LabeledStatement":
closure = this.labeledStatementHandler(node);
break;
case "DebuggerStatement":
closure = this.debuggerStatementHandler(node);
break;
default:
throw this.createInternalThrowError(Messages.NodeTypeSyntaxError, node.type, node);
}
return (...args) => {
const timeout = this.options.timeout;
if (timeout && timeout > 0 && this.checkTimeout()) {
throw this.createInternalThrowError(Messages.ExecutionTimeOutError, timeout, null);
}
this.lastExecNode = node;
return closure(...args);
};
}
// a==b a/b
binaryExpressionHandler(node) {
const leftExpression = this.createClosure(node.left);
const rightExpression = this.createClosure(node.right);
return () => {
const leftValue = leftExpression();
const rightValue = rightExpression();
switch (node.operator) {
case "==":
return leftValue == rightValue;
case "!=":
return leftValue != rightValue;
case "===":
return leftValue === rightValue;
case "!==":
return leftValue !== rightValue;
case "<":
return leftValue < rightValue;
case "<=":
return leftValue <= rightValue;
case ">":
return leftValue > rightValue;
case ">=":
return leftValue >= rightValue;
case "<<":
return leftValue << rightValue;
case ">>":
return leftValue >> rightValue;
case ">>>":
return leftValue >>> rightValue;
case "+":
return leftValue + rightValue;
case "-":
return leftValue - rightValue;
case "*":
return leftValue * rightValue;
case "**":
return Math.pow(leftValue, rightValue);
case "/":
return leftValue / rightValue;
case "%":
return leftValue % rightValue;
case "|":
return leftValue | rightValue;
case "^":
return leftValue ^ rightValue;
case "&":
return leftValue & rightValue;
case "in":
return leftValue in rightValue;
case "instanceof":
return leftValue instanceof rightValue;
default:
throw this.createInternalThrowError(Messages.BinaryOperatorSyntaxError, node.operator, node);
}
};
}
// a && b
logicalExpressionHandler(node) {
const leftExpression = this.createClosure(node.left);
const rightExpression = this.createClosure(node.right);
return () => {
switch (node.operator) {
case "||":
return leftExpression() || rightExpression();
case "&&":
return leftExpression() && rightExpression();
default:
throw this.createInternalThrowError(Messages.LogicalOperatorSyntaxError, node.operator, node);
}
};
}
// protected isRootScope(node: ESTree.Expression | ESTree.Pattern): boolean {
// if (node.type === "Identifier") {
// const scope = this.getScopeFromName(node.name, this.getCurrentScope());
// return scope.name === "rootScope";
// }
// return false;
// }
// typeof a !a()
unaryExpressionHandler(node) {
switch (node.operator) {
case "delete":
const objectGetter = this.createObjectGetter(node.argument);
const nameGetter = this.createNameGetter(node.argument);
return () => {
// not allowed to delete root scope property
// rootContext has move to prototype chai, so no judgment required
// if (this.isRootScope(node.argument)) {
// return false;
// }
let obj = objectGetter();
const name = nameGetter();
return delete obj[name];
};
default:
let expression;
// for typeof undefined var
// typeof adf9ad
if (node.operator === "typeof" && node.argument.type === "Identifier") {
const objectGetter = this.createObjectGetter(node.argument);
const nameGetter = this.createNameGetter(node.argument);
expression = () => objectGetter()[nameGetter()];
}
else {
expression = this.createClosure(node.argument);
}
return () => {
const value = expression();
switch (node.operator) {
case "-":
return -value;
case "+":
return +value;
case "!":
return !value;
case "~":
return ~value;
case "void":
return void value;
case "typeof":
return typeof value;
default:
throw this.createInternalThrowError(Messages.UnaryOperatorSyntaxError, node.operator, node);
}
};
}
}
// ++a --a
updateExpressionHandler(node) {
const objectGetter = this.createObjectGetter(node.argument);
const nameGetter = this.createNameGetter(node.argument);
return () => {
const obj = objectGetter();
const name = nameGetter();
this.assertVariable(obj, name, node);
switch (node.operator) {
case "++":
return node.prefix ? ++obj[name] : obj[name]++;
case "--":
return node.prefix ? --obj[name] : obj[name]--;
default:
throw this.createInternalThrowError(Messages.UpdateOperatorSyntaxError, node.operator, node);
}
};
}
// var o = {a: 1, b: 's', get name(){}, set name(){} ...}
objectExpressionHandler(node) {
const items = [];
function getKey(keyNode) {
if (keyNode.type === "Identifier") {
// var o = {a:1}
return keyNode.name;
}
else if (keyNode.type === "Literal") {
// var o = {"a":1}
return keyNode.value;
}
else {
return this.throwError(Messages.ObjectStructureSyntaxError, keyNode.type, keyNode);
}
}
// collect value, getter, and/or setter.
const properties = Object.create(null);
node.properties.forEach(property => {
const kind = property.kind;
const key = getKey(property.key);
if (!properties[key] || kind === "init") {
properties[key] = {};
}
properties[key][kind] = this.createClosure(property.value);
items.push({
key,
property,
});
});
return () => {
const result = {};
const len = items.length;
for (let i = 0; i < len; i++) {
const item = items[i];
const key = item.key;
const kinds = properties[key];
const value = kinds.init ? kinds.init() : undefined;
const getter = kinds.get ? kinds.get() : function () { };
const setter = kinds.set ? kinds.set() : function (a) { };
if ("set" in kinds || "get" in kinds) {
const descriptor = {
configurable: true,
enumerable: true,
get: getter,
set: setter,
};
Object.defineProperty(result, key, descriptor);
}
else {
const property = item.property;
const kind = property.kind;
// set function.name
// var d = { test(){} }
// var d = { test: function(){} }
if (property.key.type === "Identifier" &&
property.value.type === "FunctionExpression" &&
kind === "init" &&
!property.value.id) {
defineFunctionName(value, property.key.name);
}
result[key] = value;
}
}
return result;
};
}
// [1,2,3]
arrayExpressionHandler(node) {
//fix: [,,,1,2]
const items = node.elements.map(element => element ? this.createClosure(element) : element);
return () => {
const len = items.length;
const result = Array(len);
for (let i = 0; i < len; i++) {
const item = items[i];
if (item) {
result[i] = item();
}
}
return result;
};
}
safeObjectGet(obj, key, node) {
return obj[key];
}
createCallFunctionGetter(node) {
switch (node.type) {
case "MemberExpression":
const objectGetter = this.createClosure(node.object);
const keyGetter = this.createMemberKeyGetter(node);
const source = this.source;
return () => {
const obj = objectGetter();
const key = keyGetter();
const func = this.safeObjectGet(obj, key, node);
if (!func || !isFunction(func)) {
const name = source.slice(node.start, node.end);
throw this.createInternalThrowError(Messages.FunctionUndefinedReferenceError, name, node);
}
// obj.eval = eval
// obj.eval(...)
if (func.__IS_EVAL_FUNC) {
return (code) => {
return func(new InternalInterpreterReflection(this), code, true);
};
}
// obj.func = Function
// obj.func(...)
if (func.__IS_FUNCTION_FUNC) {
return (...args) => {
return func(new InternalInterpreterReflection(this), ...args);
};
}
// method call
// eg:obj.say(...)
// eg: obj.say.call(...)
// eg: obj.say.apply(...)
// ======================
// obj.func(...)
// func = func.bind(obj)
// tips:
// func(...) -> func.bind(obj)(...)
// func.call(...) -> obj.func.call.bind(obj.func)(...)
// func.apply(...) -> obj.func.apply.bind(obj.func)(...)
// ...others
return func.bind(obj);
};
default:
// test() or (0,test)() or a[1]() ...
const closure = this.createClosure(node);
return () => {
let name = "";
if (node.type === "Identifier") {
name = node.name;
}
// const name: string = (<ESTree.Identifier>node).name;
const func = closure();
if (!func || !isFunction(func)) {
throw this.createInternalThrowError(Messages.FunctionUndefinedReferenceError, name, node);
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
// var eval = eval;
// function test(){
// eval(...); //note: use local scope in eval5,but in Browser is use global scope
// }
if (node.type === "Identifier" && func.__IS_EVAL_FUNC && name === "eval") {
return (code) => {
const scope = this.getScopeFromName(name, this.getCurrentScope());
const useGlobalScope = scope.name === SuperScopeName ||
// !scope.parent || // super scope
scope.name === GlobalScopeName ||
// this.globalScope === scope ||
scope.name === RootScopeName;
// use local scope if calling eval in super scope
return func(new InternalInterpreterReflection(this), code, !useGlobalScope);
};
}
// use global scope
// var g_eval = eval;
// g_eval("a+1");
//(0,eval)(...) ...eval alias
if (func.__IS_EVAL_FUNC) {
return (code) => {
return func(new InternalInterpreterReflection(this), code, true);
};
}
// Function('a', 'b', 'return a+b')
if (func.__IS_FUNCTION_FUNC) {
return (...args) => {
return func(new InternalInterpreterReflection(this), ...args);
};
}
let ctx = this.options.globalContextInFunction;
// with(obj) {
// test() // test.call(obj, ...)
// }
if (node.type === "Identifier") {
const scope = this.getIdentifierScope(node);
if (scope.name === WithScopeName) {
ctx = scope.data;
}
}
// function call
// this = undefined
// tips:
// test(...) === test.call(undefined, ...)
// fix: alert.call({}, ...) Illegal invocation
return func.bind(ctx);
};
}
}
// func()
callExpressionHandler(node) {
const funcGetter = this.createCallFunctionGetter(node.callee);
const argsGetter = node.arguments.map(arg => this.createClosure(arg));
return () => {
return funcGetter()(...argsGetter.map(arg => arg()));
};
}
// var f = function() {...}
functionExpressionHandler(node) {
const self = this;
const source = this.source;
const oldDeclVars = this.collectDeclVars;
const oldDeclFuncs = this.collectDeclFuncs;
this.collectDeclVars = Object.create(null);
this.collectDeclFuncs = Object.create(null);
const name = node.id ? node.id.name : ""; /**anonymous*/
const paramLength = node.params.length;
const paramsGetter = node.params.map(param => this.createParamNameGetter(param));
// set scope
const bodyClosure = this.createClosure(node.body);
const declVars = this.collectDeclVars;
const declFuncs = this.collectDeclFuncs;
this.collectDeclVars = oldDeclVars;
this.collectDeclFuncs = oldDeclFuncs;
return () => {
// bind current scope
const runtimeScope = self.getCurrentScope();
const func = function (...args) {
self.callStack.push(`${name}`);
const prevScope = self.getCurrentScope();
const currentScope = createScope(runtimeScope, `FunctionScope(${name})`);
self.setCurrentScope(currentScope);
self.addDeclarationsToScope(declVars, declFuncs, currentScope);
// var t = function(){ typeof t } // function
// t = function(){ typeof t } // function
// z = function tx(){ typeof tx } // function
// but
// d = { say: function(){ typeof say } } // undefined
if (name) {
currentScope.data[name] = func;
}
// init arguments var
currentScope.data["arguments"] = arguments;
paramsGetter.forEach((getter, i) => {
currentScope.data[getter()] = args[i];
});
// init this
const prevContext = self.getCurrentContext();
//for ThisExpression
self.setCurrentContext(this);
const result = bodyClosure();
//reset
self.setCurrentContext(prevContext);
self.setCurrentScope(prevScope);
self.callStack.pop();
if (result instanceof Return) {
return result.value;
}
};
defineFunctionName(func, name);
Object.defineProperty(func, "length", {
value: paramLength,
writable: false,
enumerable: false,
configurable: true,
});
Object.defineProperty(func, "toString", {
value: () => {
return source.slice(node.start, node.end);
},
writable: true,
configurable: true,
enumerable: false,
});
Object.defineProperty(func, "valueOf", {
value: () => {
return source.slice(node.start, node.end);
},
writable: true,
configurable: true,
enumerable: false,
});
return func;
};
}
// new Ctrl()
newExpressionHandler(node) {
const source = this.source;
const expression = this.createClosure(node.callee);
const args = node.arguments.map(arg => this.createClosure(arg));
return () => {
const construct = expression();
if (!isFunction(construct) || construct.__IS_EVAL_FUNC) {
const callee = node.callee;
const name = source.slice(callee.start, callee.end);
throw this.createInternalThrowError(Messages.IsNotConstructor, name, node);
}
// new Function(...)
if (construct.__IS_FUNCTION_FUNC) {
return construct(new InternalInterpreterReflection(this), ...args.map(arg => arg()));
}
return new construct(...args.map(arg => arg()));
};
}
// a.b a['b']
memberExpressionHandler(node) {
const objectGetter = this.createClosure(node.object);
const keyGetter = this.createMemberKeyGetter(node);
return () => {
const obj = objectGetter();
let key = keyGetter();
return obj[key];
};
}
//this
thisExpressionHandler(node) {
return () => this.getCurrentContext();
}
// var1,var2,...
sequenceExpressionHandler(node) {
const expressions = node.expressions.map(item => this.createClosure(item));
return () => {
let result;
const len = expressions.length;
for (let i = 0; i < len; i++) {
const expression = expressions[i];
result = expression();
}
return result;
};
}
// 1 'name'
literalHandler(node) {
return () => {
if (node.regex) {
return new RegExp(node.regex.pattern, node.regex.flags);
}
return node.value;
};
}
// var1 ...
identifierHandler(node) {
return () => {
const currentScope = this.getCurrentScope();
const data = this.getScopeDataFromName(node.name, currentScope);
this.assertVariable(data, node.name, node);
return data[node.name];
};
}
getIdentifierScope(node) {
const currentScope = this.getCurrentScope();
const scope = this.getScopeFromName(node.name, currentScope);
return scope;
}
// a=1 a+=2
assignmentExpressionHandler(node) {
// var s = function(){}
// s.name === s
if (node.left.type === "Identifier" &&
node.right.type === "FunctionExpression" &&
!node.right.id) {
node.right.id = {
type: "Identifier",
name: node.left.name,
};
}
const dataGetter = this.createObjectGetter(node.left);
const nameGetter = this.createNameGetter(node.left);
const rightValueGetter = this.createClosure(node.right);
return () => {
const data = dataGetter();
const name = nameGetter();
const rightValue = rightValueGetter();
if (node.operator !== "=") {
// if a is undefined
// a += 1
this.assertVariable(data, name, node);
}
switch (node.operator) {
case "=":
return (data[name] = rightValue);
case "+=":
return (data[name] += rightValue);
case "-=":
return (data[name] -= rightValue);
case "*=":
return (data[name] *= rightValue);
case "**=":
return (data[name] = Math.pow(data[name], rightValue));
case "/=":
return (data[name] /= rightValue);
case "%=":
return (data[name] %= rightValue);
case "<<=":
return (data[name] <<= rightValue);
case ">>=":
return (data[name] >>= rightValue);
case ">>>=":
return (data[name] >>>= rightValue);
case "&=":
return (data[name] &= rightValue);
case "^=":
return (data[name] ^= rightValue);
case "|=":
return (data[name] |= rightValue);
default:
throw this.createInternalThrowError(Messages.AssignmentExpressionSyntaxError, node.type, node);
}
};
}
// function test(){}
functionDeclarationHandler(node) {
if (node.id) {
const functionClosure = this.functionExpressionHandler(node);
Object.defineProperty(functionClosure, "isFunctionDeclareClosure", {
value: true,
writable: false,
configurable: false,
enumerable: false,
});
this.funcDeclaration(node.id.name, functionClosure);
}
return () => {
return EmptyStatementReturn;
};
}
getVariableName(node) {
if (node.type === "Identifier") {
return node.name;
}
else {
throw this.createInternalThrowError(Messages.VariableTypeSyntaxError, node.type, node);
}
}
// var i;
// var i=1;
variableDeclarationHandler(node) {
let assignmentsClosure;
const assignments = [];
for (let i = 0; i < node.declarations.length; i++) {
const decl = node.declarations[i];
this.varDeclaration(this.getVariableName(decl.id));
if (decl.init) {
assignments.push({
type: "AssignmentExpression",
operator: "=",
left: decl.id,
right: decl.init,
});
}
}
if (assignments.length) {
assignmentsClosure = this.createClosure({
type: "BlockStatement",
body: assignments,
});
}
return () => {
if (assignmentsClosure) {
const oldValue = this.isVarDeclMode;
this.isVarDeclMode = true;
assignmentsClosure();
this.isVarDeclMode = oldValue;
}
return EmptyStatementReturn;
};
}
assertVariable(data, name, node) {
if (data === this.globalScope.data && !(name in data)) {
throw this.createInternalThrowError(Messages.VariableUndefinedReferenceError, name, node);
}
}
// {...}
programHandler(node) {
// const currentScope = this.getCurrentScope();
const stmtClosures = node.body.map((stmt) => {
// if (stmt.type === "EmptyStatement") return null;
return this.createClosure(stmt);
});
return () => {
let result = EmptyStatementReturn;
for (let i = 0; i < stmtClosures.length; i++) {
const stmtClosure = stmtClosures[i];
// save last value
const ret = this.setValue(stmtClosure());
// if (!stmtClosure) continue;
// EmptyStatement
if (ret === EmptyStatementReturn)
continue;
result = ret;
// BlockStatement: break label; continue label; for(){ break ... }
// ReturnStatement: return xx;
if (result instanceof Return ||
result instanceof BreakLabel ||
result instanceof ContinueLabel ||
result === Break ||
result === Continue) {
break;
}
}
// save last value
return result;
};
}
// all expression: a+1 a&&b a() a.b ...
expressionStatementHandler(node) {
return this.createClosure(node.expression);
}
emptyStatementHandler(node) {
return () => EmptyStatementReturn;
}
// return xx;
returnStatementHandler(node) {
const argumentClosure = node.argument ? this.createClosure(node.argument) : noop;
return () => new Return(argumentClosure());
}
// if else
ifStatementHandler(node) {
const testClosure = this.createClosure(node.test);
const consequentClosure = this.createClosure(node.consequent);
const alternateClosure = node.alternate
? this.createClosure(node.alternate)
: /*!important*/ () => EmptyStatementReturn;
return () => {
return testClosure() ? consequentClosure() : alternateClosure();
};
}
// test() ? true : false
conditionalExpressionHandler(node) {
return this.ifStatementHandler(node);
}
// for(var i = 0; i < 10; i++) {...}
forStatementHandler(node) {
let initClosure = noop;
let testClosure = node.test ? this.createClosure(node.test) : () => true;
let updateClosure = noop;
const bodyClosure = this.createClosure(node.body);
if (node.type === "ForStatement") {
initClosure = node.init ? this.createClosure(node.init) : initClosure;
updateClosure = node.update ? this.createClosure(node.update) : noop;
}
return pNode => {
let labelName;
let result = EmptyStatementReturn;
let shouldInitExec = node.type === "DoWhileStatement";
if (pNode && pNode.type === "LabeledStatement") {
labelName = pNode.label.name;
}
for (initClosure(); shouldInitExec || testClosure(); updateClosure()) {
shouldInitExec = false;
// save last value
const ret = this.setValue(bodyClosure());
// notice: never return Break or Continue!
if (ret === EmptyStatementReturn || ret === Continue)
continue;
if (ret === Break) {
break;
}
result = ret;
// stop continue label
if (result instanceof ContinueLabel && result.value === labelName) {
result = EmptyStatementReturn;
continue;
}
if (result instanceof Return ||
result instanceof BreakLabel ||
result instanceof ContinueLabel) {
break;
}
}
return result;
};
}
// while(1) {...}
whileStatementHandler(node) {
return this.forStatementHandler(node);
}
doWhileStatementHandler(node) {
return this.forStatementHandler(node);
}
forInStatementHandler(node) {
// for( k in obj) or for(o.k in obj) ...
let left = node.left;
const rightClosure = this.createClosure(node.right);
const bodyClosure = this.createClosure(node.body);
// for(var k in obj) {...}
if (node.left.type === "VariableDeclaration") {
// init var k
this.createClosure(node.left)();
// reset left
// for( k in obj)
left = node.left.declarations[0].id;
}
return pNode => {
let labelName;
let result = EmptyStatementReturn;
let x;
if (pNode && pNode.type === "LabeledStatement") {
labelName = pNode.label.name;
}
const data = rightClosure();
for (x in data) {
// assign left to scope
// k = x
// o.k = x
this.assignmentExpressionHandler({
type: "AssignmentExpression",
operator: "=",
left: left,
right: {
type: "Literal",
value: x,
},
})();
// save last value
const ret = this.setValue(bodyClosure());
// notice: never return Break or Continue!
if (ret === EmptyStatementReturn || ret === Continue)
continue;
if (ret === Break) {
break;
}
result = ret;
// stop continue label
if (result instanceof ContinueLabel && result.value === labelName) {
result = EmptyStatementReturn;
continue;
}
if (result instanceof Return ||
result instanceof BreakLabel ||
result instanceof ContinueLabel) {
break;
}
}
return result;
};
}
withStatementHandler(node) {
const objectClosure = this.createClosure(node.object);
const bodyClosure = this.createClosure(node.body);
return () => {
const data = objectClosure();
const currentScope = this.getCurrentScope();
const newScope = new Scope(data, currentScope, WithScopeName);
// const data = objectClosure();
// copy all properties
// for (let k in data) {
// newScope.data[k] = data[k];
// }
this.setCurrentScope(newScope);
// save last value
const result = this.setValue(bodyClosure());
this.setCurrentScope(currentScope);
return result;
};
}
throwStatementHandler(node) {
const argumentClosure = this.createClosure(node.argument);
return () => {
this.setValue(undefined);
throw argumentClosure();
};
}
// try{...}catch(e){...}finally{}
tryStatementHandler(node) {
const blockClosure = this.createClosure(node.block);
const handlerClosure = node.handler ? this.catchClauseHandler(node.handler) : null;
const finalizerClosure = node.finalizer ? this.createClosure(node.finalizer) : null;
return () => {
const currentScope = this.getCurrentScope();
const currentContext = this.getCurrentContext();
const labelStack = currentScope.labelStack.concat([]);
const callStack = this.callStack.concat([]);
let result = EmptyStatementReturn;
let finalReturn;
let throwError;
const reset = () => {
this.setCurrentScope(currentScope); //reset scope
this.setCurrentContext(currentContext); //reset context
currentScope.labelStack = labelStack; //reset label stack
this.callStack = callStack; //reset call stack
};
/**
* try{...}catch(e){...}finally{...} execution sequence:
* try stmt
* try throw
* catch stmt (if)
* finally stmt
*
* finally throw or finally return
* catch throw or catch return
*