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
JavaScript
"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