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
167 lines • 6.33 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = __importDefault(require("chai"));
const shift_parser_1 = require("shift-parser");
const src_1 = require("../../src");
const util_1 = require("../util");
describe('Functions', () => {
it('should declare functions', () => {
util_1.assertResult(util_1.compare('function a(){return 2}; a();'));
});
it('should hoist functions for purposes of member access/assignment', () => {
util_1.assertResult(util_1.compare("a.foo = 'bar'; function a(){}; a.foo;"));
});
it('should use global context if called with undefined context', () => {
util_1.assertResult(util_1.compare(`'();'.replace('()', function(){})`));
});
it('declarations in block scope should be defined across all scope Variable references', () => {
// shift-scope defines multiple variables for block scopes, references should be the same all around.
util_1.assertResult(util_1.compare(`let a = '';{function fn() {return 'decl'}a+=fn()}a+=fn();a;`));
util_1.assertResult(util_1.compare(`let inner;{function fn() {}inner=fn}let outer = fn; outer === inner`));
});
it('should call methods on primitive literals', () => {
util_1.assertResult(util_1.compare("'a'.replace('a','b');"));
});
it('should call methods on primitive return values', () => {
util_1.assertResult(util_1.compare("fn = () => 'a';fn().replace('a','b');"));
});
it('should assign arrow expressions', () => {
util_1.assertResult(util_1.compare('let a = (a) => {return a}; a(4)'));
});
it('arrow expressions should retain `this` binding', () => {
util_1.assertResult(util_1.compare('let a = { a: () => {return this.b}, b: 44 }; const b = a.a; b();'));
});
it('should evaluate shorthand arrow expressions', () => {
util_1.assertResult(util_1.compare('let a = _ => _ + 10; a(2);'));
});
it('should call functions with arguments that have defaults', () => {
util_1.assertResult(util_1.compare('function fn(a = 22){return a + 10}; fn() + fn(33);'));
});
it('should call functions with arguments', () => {
util_1.assertResult(util_1.compare('function a(a,b){return a+b}; a(2,5) === 7;'));
});
it('should allow reference to arguments special variable', () => {
util_1.assertResult(util_1.compare('function a(b){return arguments[0] + 10}; a(33);'));
});
it('should support .call()', () => {
util_1.assertResult(util_1.compare(`
var context = {
expected: "hello",
};
function dyno(prop) {
return this[prop];
}
dyno.call(context, "expected");
`));
});
it('should support prototype modifications', () => {
util_1.assertResult(util_1.compare(`
function d() {}
d.prototype.run = function () {return "expected"};
d.prototype.run();
`));
});
it('should support .apply()', () => {
util_1.assertResult(util_1.compare(`
var context = {
expected: "hello",
};
function dyno(prop) {
return this[prop];
}
dyno.apply(context, ["expected"]);
`));
});
it('should access appropriate context', () => {
util_1.assertResult(util_1.compare(`
var c = {
expected: "hello",
test: function(actual) {
return actual === c.expected;
}
};
c.test("hello") === true;
`));
util_1.assertResult(util_1.compare(`
var c = {
expected: "hello",
test: function(actual) {
return actual === c.expected;
}
};
var b = {
expected: "on b"
};
b.test = c.test;
b.test('on b') === true;
`));
});
it('should hoist functions', () => {
const src = 'a.b = 2; function a(){}';
const ast = shift_parser_1.parseScript(src);
const interpreter = new src_1.Interpreter();
interpreter.load(ast);
interpreter.run();
const fnDecl = ast.statements.find((st) => st.type === 'FunctionDeclaration');
const fn = () => {
const value = interpreter.getRuntimeValue(fnDecl.name);
chai_1.default.expect(value.b).to.equal(2);
};
chai_1.default.expect(fn).to.not.throw();
});
it('should store and execute function expressions', () => {
util_1.assertResult(util_1.compare('let a = function(){return 2}; a();'));
});
it('should return from sub statements', () => {
util_1.assertResult(util_1.compare("function a() { if (true) return 'in branch'; return 'should not get here'}; a();"));
});
it('should return from sub blocks', () => {
util_1.assertResult(util_1.compare(`
function _isSameValue(a, b) {
if (a === b) {
return true;
}
return false;
};
_isSameValue("1","1");
`));
});
});
describe('Getters/Setters', () => {
it('should define getters', () => {
util_1.assertResult(util_1.compare('let a = { get b() {return 2} }; a.b;'));
});
it('should define setters', () => {
util_1.assertResult(util_1.compare('let holder = { set property(argument) {this._secretProp = argument} }; holder.property = 22; false /* dummy expression to catch promise-based setter regression */; holder._secretProp'));
});
it('should register setters properly on host objects', () => {
const tree = shift_parser_1.parseScript(`holder = { set property(argument) {this._secretProp = argument} };`);
//@ts-ignore
const objectExpression = tree.statements[0].expression;
const interpreter = new src_1.Interpreter();
interpreter.load(tree);
const obj = interpreter.evaluate(objectExpression);
obj.property = 22;
chai_1.default.expect(obj._secretProp).to.equal(22);
});
it('should define both', () => {
const src = `
let a = {
_b: 0,
set b(c) {
this._b = c + 10;
},
get b() {
return this._b;
},
};
a.b = 22;
a.b;
`;
util_1.assertResult(util_1.compare(src));
});
});
//# sourceMappingURL=functions.test.js.map