UNPKG

eval5

Version:

A JavaScript interpreter written in JavaScript

1,365 lines 59.6 kB
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 *