UNPKG

shift-interpreter

Version:

Shift-interpreter is an experimental JavaScript meta-interpreter useful for reverse engineering and analysis. One notable difference from other projects is that shift-interpreter retains state over an entire script but can be fed expressions and statement

742 lines 29.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeHandler = void 0; const debug_1 = __importDefault(require("debug")); const errors_1 = require("./errors"); const operators_1 = require("./operators"); const util_1 = require("./util"); const debug = debug_1.default('shift:interpreter:node-handler'); class NodeHandler { constructor(interpreter) { this.interpreter = interpreter; } ReturnStatement(stmt) { const value = this.interpreter.evaluate(stmt.expression); this.interpreter.isReturning(true); return value; } ExpressionStatement(stmt) { return this.interpreter.evaluate(stmt.expression); } VariableDeclarationStatement(stmt) { return this.interpreter.declareVariables(stmt.declaration); } VariableDeclarator(declarator) { const value = this.interpreter.evaluate(declarator.init); return this.interpreter.bindVariable(declarator.binding, value); } FunctionDeclaration(decl) { const fn = this.interpreter.createFunction(decl); const variables = this.interpreter.lookupTable.variableMap.get(decl.name); variables.forEach((variable) => this.interpreter.variableMap.set(variable, fn)); } BlockStatement(stmt) { return this.interpreter.evaluate(stmt.block); } ClassDeclaration(decl) { const staticMethods = []; const methods = []; let constructor = null; if (decl.elements.length > 0) { for (let el of decl.elements) { if (el.method.type === 'Method') { const intermediateFunction = this.interpreter.createFunction(el.method); if (el.isStatic) { staticMethods.push([intermediateFunction.name, intermediateFunction]); } else { if (intermediateFunction.name === 'constructor') constructor = intermediateFunction; else methods.push([intermediateFunction.name, intermediateFunction]); } } else { this.interpreter.skipOrThrow(`ClassElement type ${el.method.type}`); } } } let Class = class { }; if (decl.super) { const xtends = this.interpreter.evaluate(decl.super); Class = ((SuperClass = xtends) => { if (constructor === null) { class InterpreterClassWithExtendsA extends SuperClass { constructor(...args) { super(...args); } } return InterpreterClassWithExtendsA; } else { class InterpreterClassWithExtendsB extends SuperClass { constructor(...args) { super(...args); constructor(args, this); } } return InterpreterClassWithExtendsB; } })(); } else { Class = (() => { if (constructor === null) { class InterpreterClassA { constructor() { } } return InterpreterClassA; } else { class InterpreterClassB { constructor(...args) { constructor(args, this); } } return InterpreterClassB; } })(); } methods.forEach(([name, intermediateFunction]) => { Class.prototype[name] = intermediateFunction; }); staticMethods.forEach(([name, intermediateFunction]) => { Class[name] = intermediateFunction; }); const variables = this.interpreter.lookupTable.variableMap.get(decl.name); variables.forEach((variable) => this.interpreter.variableMap.set(variable, Class)); return Class; } IfStatement(stmt) { const test = this.interpreter.evaluate(stmt.test); if (test) return this.interpreter.evaluate(stmt.consequent); else if (stmt.alternate) return this.interpreter.evaluate(stmt.alternate); } ConditionalExpression(stmt) { const test = this.interpreter.evaluate(stmt.test); if (test) return this.interpreter.evaluate(stmt.consequent); else if (stmt.alternate) return this.interpreter.evaluate(stmt.alternate); } ThrowStatement(stmt) { const error = this.interpreter.evaluate(stmt.expression); throw error; } TryCatchStatement(stmt) { let returnValue = undefined; try { returnValue = this.interpreter.evaluate(stmt.body); } catch (e) { this.interpreter.bindVariable(stmt.catchClause.binding, e); try { returnValue = this.interpreter.evaluate(stmt.catchClause.body); } catch (e) { throw e; } } return returnValue; } TryFinallyStatement(stmt) { let returnValue = undefined; if (stmt.catchClause) { try { returnValue = this.interpreter.evaluate(stmt.body); } catch (e) { this.interpreter.bindVariable(stmt.catchClause.binding, e); try { returnValue = this.interpreter.evaluate(stmt.catchClause.body); } catch (e) { throw e; } } finally { returnValue = this.interpreter.evaluate(stmt.finalizer); } } else { try { returnValue = this.interpreter.evaluate(stmt.body); } finally { returnValue = this.interpreter.evaluate(stmt.finalizer); } } return returnValue; } Block(block) { let value; const _debug = debug.extend('Block'); this.interpreter.hoistFunctions(block); this.interpreter.hoistVars(block); const statements = block.statements.filter(stmt => stmt.type !== 'FunctionDeclaration'); for (let i = 0; i < statements.length; i++) { const statement = statements[i]; _debug(`Evaluating next ${statement.type} in ${block.type}`); value = this.interpreter.evaluate(statement); _debug(`${block.type} statement ${statement.type} completed`); } _debug(`completed ${block.type}, returning with: ${value}`); return value; } FunctionBody(body) { let value; const _debug = debug.extend(body.type); this.interpreter.hoistFunctions(body); this.interpreter.hoistVars(body); const statements = body.statements.filter(stmt => stmt.type !== 'FunctionDeclaration'); for (let i = 0; i < statements.length; i++) { const statement = statements[i]; _debug(`Evaluating ${statement.type} in ${body.type}`); value = this.interpreter.evaluate(statement); _debug(`${body.type} statement ${statement.type} completed`); if (this.interpreter.isReturning()) { break; } } _debug(`completed ${body.type}, returning with: ${value}`); return value; } Script(body) { let value; const _debug = debug.extend(body.type); this.interpreter.hoistFunctions(body); this.interpreter.hoistVars(body); const statements = body.statements.filter(stmt => stmt.type !== 'FunctionDeclaration'); for (let i = 0; i < statements.length; i++) { const statement = statements[i]; _debug(`Evaluating ${statement.type} in ${body.type}`); value = this.interpreter.evaluate(statement); _debug(`${body.type} statement ${statement.type} completed`); } _debug(`completed ${body.type}, returning with: ${value}`); return value; } loopBlock(stmt) { const _debug = debug.extend(stmt.type); let statements = null; if (stmt.body.type === 'BlockStatement') { this.interpreter.hoistFunctions(stmt.body.block); this.interpreter.hoistVars(stmt.body.block); statements = stmt.body.block.statements.filter(stmt => stmt.type !== 'FunctionDeclaration'); } else { statements = [stmt.body]; } for (let i = 0; i < statements.length; i++) { const statement = statements[i]; _debug(`Evaluating ${statement.type} in ${stmt.type}`); this.interpreter.evaluate(statement); _debug(`${stmt.type} statement ${statement.type} completed`); if (this.interpreter.isBreaking()) { break; } if (this.interpreter.isContinuing()) { break; } } } ForOfStatement(stmt) { const iterationExpression = this.interpreter.evaluate(stmt.right); function* nextValue() { yield* iterationExpression; } let iterator = nextValue(); let result = null; while ((result = iterator.next())) { if (result.done) break; const { value } = result; switch (stmt.left.type) { case 'VariableDeclaration': { this.interpreter.declareVariables(stmt.left); const binding = stmt.left.declarators[0].binding; if (binding.type === 'BindingIdentifier') this.interpreter.updateVariableValue(binding, value); else this.interpreter.skipOrThrow(stmt.type + '.left->' + binding.type); break; } default: this.interpreter.skipOrThrow(stmt.type + '.left->' + stmt.left.type); } this.loopBlock(stmt); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } } } ForInStatement(stmt) { const iterationExpression = this.interpreter.evaluate(stmt.right); switch (stmt.left.type) { case 'VariableDeclaration': { this.interpreter.declareVariables(stmt.left); const binding = stmt.left.declarators[0].binding; for (let a in iterationExpression) { if (binding.type === 'BindingIdentifier') this.interpreter.updateVariableValue(binding, a); else this.interpreter.skipOrThrow(stmt.type + '.left->' + binding.type); this.loopBlock(stmt); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } } break; } case 'AssignmentTargetIdentifier': { for (let a in iterationExpression) { this.interpreter.updateVariableValue(stmt.left, a); this.loopBlock(stmt); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } } break; } default: this.interpreter.skipOrThrow(stmt.type + '.left->' + stmt.left.type); } } ForStatement(stmt) { if (stmt.init) { if (stmt.init.type === 'VariableDeclaration') this.interpreter.declareVariables(stmt.init); else this.interpreter.evaluate(stmt.init); } while (this.interpreter.evaluate(stmt.test)) { this.loopBlock(stmt); if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } if (stmt.update) this.interpreter.evaluate(stmt.update); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } } } WhileStatement(stmt) { while (this.interpreter.evaluate(stmt.test)) { this.loopBlock(stmt); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } } } DoWhileStatement(stmt) { do { this.loopBlock(stmt); if (this.interpreter.isContinuing()) { this.interpreter.isContinuing(false); continue; } if (this.interpreter.isBreaking()) { this.interpreter.isBreaking(false); break; } } while (this.interpreter.evaluate(stmt.test)); } ThisExpression(expr) { return this.interpreter.getCurrentContext(); } NewExpression(expr) { const newTarget = this.interpreter.evaluate(expr.callee); const args = []; for (let arg of expr.arguments) { if (arg.type === 'SpreadElement') { const value = this.interpreter.evaluate(arg.expression); args.push(...value); } else { args.push(this.interpreter.evaluate(arg)); } } let result = new newTarget(...args); return result; } ArrayExpression(expr) { const elements = []; for (let el of expr.elements) { if (el === null) { elements.push(null); } else if (el.type === 'SpreadElement') { const iterable = this.interpreter.evaluate(el.expression); elements.push(...Array.from(iterable)); } else { elements.push(this.interpreter.evaluate(el)); } } return elements; } ObjectExpression(expr) { const _debug = debug.extend('ObjectExpression'); const obj = {}; const batchOperations = new Map(); function getPropertyDescriptors(name) { if (batchOperations.has(name)) return batchOperations.get(name); const operations = new Map(); batchOperations.set(name, operations); return operations; } for (let prop of expr.properties) { switch (prop.type) { case 'DataProperty': { const name = prop.name.type === 'StaticPropertyName' ? prop.name.value : this.interpreter.evaluate(prop.name.expression); obj[name] = this.interpreter.evaluate(prop.expression); break; } case 'Method': { const name = prop.name.type === 'StaticPropertyName' ? prop.name.value : this.interpreter.evaluate(prop.name.expression); obj[name] = this.interpreter.createFunction(prop); break; } case 'ShorthandProperty': { const name = prop.name.name; obj[name] = this.interpreter.getRuntimeValue(prop.name); break; } case 'Getter': { const name = prop.name.type === 'StaticPropertyName' ? prop.name.value : this.interpreter.evaluate(prop.name.expression); const operations = getPropertyDescriptors(name); operations.set('get', this.interpreter.createFunction(prop)); break; } case 'Setter': { const name = prop.name.type === 'StaticPropertyName' ? prop.name.value : this.interpreter.evaluate(prop.name.expression); const operations = getPropertyDescriptors(name); operations.set('set', this.interpreter.createFunction(prop)); break; } default: this.interpreter.skipOrThrow(`${expr.type}[${prop.type}]`); } } Array.from(batchOperations.entries()).forEach(([prop, ops]) => { _debug(`setting object property ${prop} (setter:${ops.has('set')}, getter:${ops.has('get')})`); const descriptor = { get: ops.get('get'), set: ops.get('set'), configurable: true, }; Object.defineProperty(obj, prop, descriptor); }); return obj; } StaticMemberExpression(expr) { if (expr.object.type === 'Super') return this.interpreter.skipOrThrow(expr.object.type); const object = this.interpreter.evaluate(expr.object); let result = object[expr.property]; return result; } ComputedMemberExpression(expr) { if (expr.object.type === 'Super') return this.interpreter.skipOrThrow(expr.object.type); const object = this.interpreter.evaluate(expr.object); const property = this.interpreter.evaluate(expr.expression); let result = object[property]; return result; } CallExpression(expr) { const _debug = debug.extend('CallExpression'); if (expr.callee.type === 'Super') return this.interpreter.skipOrThrow(expr.callee.type); const args = []; for (let arg of expr.arguments) { if (arg.type === 'SpreadElement') { const value = this.interpreter.evaluate(arg.expression); args.push(...value); } else { args.push(this.interpreter.evaluate(arg)); } } let context = this.interpreter.getCurrentContext(); let fn = null; if (expr.callee.type === 'StaticMemberExpression') { context = this.interpreter.evaluate(expr.callee.object); fn = context[expr.callee.property]; } else if (expr.callee.type === 'ComputedMemberExpression') { context = this.interpreter.evaluate(expr.callee.object); const computedProperty = this.interpreter.evaluate(expr.callee.expression); fn = context[computedProperty]; } else { fn = this.interpreter.evaluate(expr.callee); } if (typeof fn === 'function') { let returnValue; _debug(`calling function ${fn.name}`); returnValue = fn.apply(context, args); _debug(`function completed ${fn.name}`); return returnValue; } else { new TypeError(`${fn} is not a function (${this.interpreter.codegen(expr)})`); } } AssignmentExpression(expr) { const _debug = debug.extend('AssignmentExpression'); switch (expr.binding.type) { case 'AssignmentTargetIdentifier': _debug(`assigning ${expr.binding.name} new value`); return this.interpreter.updateVariableValue(expr.binding, this.interpreter.evaluate(expr.expression)); case 'ComputedMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.binding.object); const property = this.interpreter.evaluate(expr.binding.expression); _debug(`evaluating expression ${expr.expression.type} to assign to ${util_1.toString(property)}`); const value = this.interpreter.evaluate(expr.expression); _debug(`assigning object property "${util_1.toString(property)}" new value`); let result = (object[property] = value); return result; } case 'StaticMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.binding.object); const property = expr.binding.property; _debug(`evaluating expression ${expr.expression.type} to assign to ${property}`); const value = this.interpreter.evaluate(expr.expression); _debug(`assigning object property "${property}" new value`); const descriptor = Object.getOwnPropertyDescriptor(object, property); let result = null; result = object[property] = value; return result; } case 'ArrayAssignmentTarget': case 'ObjectAssignmentTarget': default: return this.interpreter.skipOrThrow(expr.binding.type); } } UpdateExpression(expr) { switch (expr.operand.type) { case 'AssignmentTargetIdentifier': { const currentValue = this.interpreter.getRuntimeValue(expr.operand); const nextValue = expr.operator === '++' ? currentValue + 1 : currentValue - 1; this.interpreter.updateVariableValue(expr.operand, nextValue); return expr.isPrefix ? nextValue : currentValue; } case 'ComputedMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.operand.object); const property = this.interpreter.evaluate(expr.operand.expression); const currentValue = object[property]; const nextValue = expr.operator === '++' ? currentValue + 1 : currentValue - 1; object[property] = nextValue; return expr.isPrefix ? nextValue : currentValue; } case 'StaticMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.operand.object); const property = expr.operand.property; const currentValue = object[property]; const nextValue = expr.operator === '++' ? currentValue + 1 : currentValue - 1; object[property] = nextValue; return expr.isPrefix ? nextValue : currentValue; } default: return; } } CompoundAssignmentExpression(expr) { const operation = operators_1.compoundAssignmentOperatorMap.get(expr.operator); switch (expr.binding.type) { case 'AssignmentTargetIdentifier': { const currentValue = this.interpreter.getRuntimeValue(expr.binding); const newValue = this.interpreter.evaluate(expr.expression); return this.interpreter.updateVariableValue(expr.binding, operation(currentValue, newValue)); } case 'ComputedMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.binding.object); const property = this.interpreter.evaluate(expr.binding.expression); const currentValue = object[property]; const newValue = this.interpreter.evaluate(expr.expression); const result = (object[property] = operation(currentValue, newValue)); return result; } case 'StaticMemberAssignmentTarget': { const object = this.interpreter.evaluate(expr.binding.object); const property = expr.binding.property; const currentValue = object[property]; const newValue = this.interpreter.evaluate(expr.expression); const result = (object[property] = operation(currentValue, newValue)); return result; } default: return; } } LiteralRegExpExpression(expr) { const flags = [ expr.global ? 'g' : '', expr.ignoreCase ? 'i' : '', expr.dotAll ? 's' : '', expr.multiLine ? 'm' : '', expr.sticky ? 'y' : '', expr.unicode ? 'u' : '', ].filter(_ => !!_); return new RegExp(expr.pattern, ...flags); } TemplateExpression(expr) { const parts = []; for (let el of expr.elements) { if (el.type === 'TemplateElement') { parts.push(el.rawValue); } else { parts.push(this.interpreter.evaluate(el)); } } return parts.join(''); } ArrowExpression(expr) { const interpreter = this.interpreter; const currentContext = interpreter.getCurrentContext(); return function () { const arrowFn = (...args) => { interpreter.pushContext(this); for (let i = 0; i < expr.params.items.length; i++) { let param = expr.params.items[i]; interpreter.bindVariable(param, args[i]); } let returnValue = undefined; if (expr.body.type === 'FunctionBody') { const blockResult = interpreter.evaluate(expr.body); returnValue = blockResult; } else { returnValue = interpreter.evaluate(expr.body); } interpreter.popContext(); return returnValue; }; Object.assign(arrowFn); return arrowFn; }.bind(currentContext)(); } FunctionExpression(expr) { return this.interpreter.createFunction(expr); } IdentifierExpression(expr) { return this.interpreter.getRuntimeValue(expr); } LiteralNumericExpression(expr) { return expr.value; } LiteralStringExpression(expr) { return expr.value; } LiteralBooleanExpression(expr) { return expr.value; } LiteralInfinityExpression(expr) { return 1 / 0; } LiteralNullExpression(expr) { return null; } BinaryExpression(expr) { const operation = operators_1.binaryOperatorMap.get(expr.operator); const left = this.interpreter.evaluate(expr.left); const deferredRight = () => { return this.interpreter.evaluate(expr.right); }; return operation(left, deferredRight); } UnaryExpression(expr) { const operation = operators_1.unaryOperatorMap.get(expr.operator); if (!operation) return this.interpreter.skipOrThrow(`${expr.type} : ${expr.operator}`); try { const operand = this.interpreter.evaluate(expr.operand); return operation(operand); } catch (e) { if (e instanceof ReferenceError && expr.operator === 'typeof' && expr.operand.type === 'IdentifierExpression') { return 'undefined'; } throw e; } } BreakStatement(...args) { this.interpreter.isBreaking(true); } ContinueStatement(...args) { this.interpreter.isContinuing(true); } DebuggerStatement(...args) { debugger; } EmptyStatement(...args) { } // TODO support these nodes WithStatement(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } SwitchStatementWithDefault(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } SwitchStatement(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } LabeledStatement(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } ForAwaitStatement(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } NewTargetExpression(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } AwaitExpression(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } Super(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } ClassExpression(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } YieldExpression(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } YieldGeneratorExpression(...args) { throw new errors_1.InterpreterRuntimeError(`Unsupported node ${arguments[0].type}`); } } exports.NodeHandler = NodeHandler; //# sourceMappingURL=node-handler.js.map