js-confuser-vm 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/LICENSE +21 -21
- package/README.MD +370 -190
- package/babel-plugin-inline-runtime.cjs +34 -0
- package/babel.config.json +23 -24
- package/index.ts +34 -28
- package/jest-strip-types.js +10 -10
- package/jest.config.js +35 -18
- package/package.json +50 -48
- package/src/build-runtime.ts +57 -0
- package/src/compiler.ts +2069 -1677
- package/src/index.ts +14 -13
- package/src/minify.ts +21 -21
- package/src/options.ts +14 -10
- package/src/runtime.ts +771 -645
- package/src/transforms/bytecode/macroOpcodes.ts +177 -0
- package/src/transforms/bytecode/resolveContants.ts +62 -0
- package/src/transforms/bytecode/resolveLabels.ts +107 -0
- package/src/transforms/bytecode/selfModifying.ts +121 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +118 -0
- package/src/transforms/runtime/macroOpcodes.ts +111 -0
- package/src/transforms/runtime/minify.ts +1 -0
- package/src/transforms/runtime/shuffleOpcodes.ts +24 -0
- package/src/transforms/runtime/specializedOpcodes.ts +146 -0
- package/src/transforms/utils/op-utils.ts +26 -0
- package/src/{random.ts → transforms/utils/random-utils.ts} +31 -31
- package/src/types.ts +33 -0
- package/src/utilts.ts +3 -3
- package/tsconfig.json +12 -12
- package/dist/compiler.js +0 -1505
- package/dist/index.js +0 -9
- package/dist/minify.js +0 -18
- package/dist/minify_empty_externs.js +0 -4
- package/dist/options.js +0 -1
- package/dist/random.js +0 -27
- package/dist/runtime.js +0 -620
- package/dist/runtimeObf.js +0 -36
- package/dist/utilts.js +0 -3
- package/src/runtimeObf.ts +0 -48
package/dist/index.js
DELETED
package/dist/minify.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import ClosureCompiler from "google-closure-compiler";
|
|
2
|
-
export function minify(sourceCode) {
|
|
3
|
-
return new Promise((resolve, reject) => {
|
|
4
|
-
const compiler = new ClosureCompiler({
|
|
5
|
-
compilation_level: "ADVANCED",
|
|
6
|
-
warning_level: "QUIET"
|
|
7
|
-
});
|
|
8
|
-
const compilerProcess = compiler.run((exitCode, stdOut, stdErr) => {
|
|
9
|
-
if (exitCode !== 0) {
|
|
10
|
-
reject(new Error(stdErr || `Closure Compiler exited with code ${exitCode}`));
|
|
11
|
-
} else {
|
|
12
|
-
resolve(stdOut);
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
compilerProcess.stdin.write(sourceCode);
|
|
16
|
-
compilerProcess.stdin.end();
|
|
17
|
-
});
|
|
18
|
-
}
|
package/dist/options.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/random.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { ok } from "assert";
|
|
2
|
-
export function getPlaceholder() {
|
|
3
|
-
return Math.random().toString(36).substring(2, 15);
|
|
4
|
-
}
|
|
5
|
-
export function choice(elements) {
|
|
6
|
-
ok(elements.length > 0, "choice() called on empty sequence");
|
|
7
|
-
return elements[Math.floor(Math.random() * elements.length)];
|
|
8
|
-
}
|
|
9
|
-
export function getRandom() {
|
|
10
|
-
return Math.random();
|
|
11
|
-
}
|
|
12
|
-
export function getRandomInt(min, max) {
|
|
13
|
-
ok(min <= max, "min must be <= max");
|
|
14
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Shuffles an array in-place using the Fisher-Yates algorithm.
|
|
19
|
-
* @param array - The array to shuffle (mutated)
|
|
20
|
-
*/
|
|
21
|
-
export function shuffle(array) {
|
|
22
|
-
for (let i = array.length - 1; i > 0; i--) {
|
|
23
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
24
|
-
[array[i], array[j]] = [array[j], array[i]];
|
|
25
|
-
}
|
|
26
|
-
return array;
|
|
27
|
-
}
|
package/dist/runtime.js
DELETED
|
@@ -1,620 +0,0 @@
|
|
|
1
|
-
import { OP_ORIGINAL as OP } from "./compiler.js";
|
|
2
|
-
const BYTECODE = [];
|
|
3
|
-
const MAIN_START_PC = 0;
|
|
4
|
-
const CONSTANTS = [];
|
|
5
|
-
const ENCODE_BYTECODE = false;
|
|
6
|
-
const TIMING_CHECKS = false;
|
|
7
|
-
// The text above is not included in the compiled output - for type intellisense only
|
|
8
|
-
// @START
|
|
9
|
-
|
|
10
|
-
function decodeBytecode(s) {
|
|
11
|
-
if (!ENCODE_BYTECODE) return s;
|
|
12
|
-
var b = typeof Buffer !== "undefined" ? Buffer.from(s, "base64") : Uint8Array.from(atob(s), function (c) {
|
|
13
|
-
return c.charCodeAt(0);
|
|
14
|
-
});
|
|
15
|
-
var r = new Int32Array(b.length / 4);
|
|
16
|
-
for (var i = 0; i < r.length; i++) r[i] = b[i * 4] | b[i * 4 + 1] << 8 | b[i * 4 + 2] << 16 | b[i * 4 + 3] << 24;
|
|
17
|
-
return r;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Closure symbol
|
|
21
|
-
// Used to tag shell functions so the VM can fast-path back to the
|
|
22
|
-
// inner Closure instead of going through a sub-VM on internal calls.
|
|
23
|
-
var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
|
|
24
|
-
|
|
25
|
-
// Upvalue
|
|
26
|
-
// While the outer frame is alive: reads/writes go to frame.locals[slot].
|
|
27
|
-
// After the outer frame returns (closed): reads/writes hit this.value.
|
|
28
|
-
function Upvalue(frame, slot) {
|
|
29
|
-
this.frame = frame;
|
|
30
|
-
this.slot = slot;
|
|
31
|
-
this._closed = false;
|
|
32
|
-
this._value = undefined;
|
|
33
|
-
}
|
|
34
|
-
Upvalue.prototype.read = function () {
|
|
35
|
-
return this._closed ? this._value : this.frame.locals[this.slot];
|
|
36
|
-
};
|
|
37
|
-
Upvalue.prototype.write = function (v) {
|
|
38
|
-
if (this._closed) this._value = v;else this.frame.locals[this.slot] = v;
|
|
39
|
-
};
|
|
40
|
-
Upvalue.prototype.close = function () {
|
|
41
|
-
this._value = this.frame.locals[this.slot];
|
|
42
|
-
this._closed = true;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Closure & Frame
|
|
46
|
-
function Closure(fn) {
|
|
47
|
-
this.fn = fn;
|
|
48
|
-
this.upvalues = [];
|
|
49
|
-
this.prototype = {}; // <- default prototype object for \`new\`
|
|
50
|
-
}
|
|
51
|
-
function Frame(closure, returnPc, parent, thisVal) {
|
|
52
|
-
this.closure = closure;
|
|
53
|
-
this.locals = new Array(closure.fn.localCount).fill(undefined);
|
|
54
|
-
this.pc = closure.fn.startPc; // <- initialize from fn descriptor
|
|
55
|
-
this.returnPc = returnPc; // pc to resume in parent frame after RETURN
|
|
56
|
-
this.parent = parent;
|
|
57
|
-
this.thisVal = thisVal !== undefined ? thisVal : undefined;
|
|
58
|
-
this._newObj = null; // <- set by NEW so RETURN can see it
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// VM
|
|
62
|
-
function VM(bytecode, mainStartPc, constants, globals) {
|
|
63
|
-
this.bytecode = bytecode;
|
|
64
|
-
this.constants = constants;
|
|
65
|
-
this.globals = globals;
|
|
66
|
-
this._stack = [];
|
|
67
|
-
this.frameStack = [];
|
|
68
|
-
this.openUpvalues = []; // all currently open Upvalue objects across all frames
|
|
69
|
-
|
|
70
|
-
var mainFn = {
|
|
71
|
-
paramCount: 0,
|
|
72
|
-
localCount: 0,
|
|
73
|
-
startPc: mainStartPc // <- where main begins
|
|
74
|
-
};
|
|
75
|
-
this.currentFrame = new Frame(new Closure(mainFn), null, null);
|
|
76
|
-
}
|
|
77
|
-
VM.prototype._push = function (v) {
|
|
78
|
-
this._stack.push(v);
|
|
79
|
-
};
|
|
80
|
-
VM.prototype._pop = function () {
|
|
81
|
-
return this._stack.pop();
|
|
82
|
-
};
|
|
83
|
-
VM.prototype.peek = function () {
|
|
84
|
-
return this._stack[this._stack.length - 1];
|
|
85
|
-
};
|
|
86
|
-
VM.prototype.captureUpvalue = function (frame, slot) {
|
|
87
|
-
// Reuse existing open upvalue for this frame+slot if one exists.
|
|
88
|
-
// This is what makes two closures share the same mutable cell.
|
|
89
|
-
for (var i = 0; i < this.openUpvalues.length; i++) {
|
|
90
|
-
var uv = this.openUpvalues[i];
|
|
91
|
-
if (uv.frame === frame && uv.slot === slot) return uv;
|
|
92
|
-
}
|
|
93
|
-
var uv = new Upvalue(frame, slot);
|
|
94
|
-
this.openUpvalues.push(uv);
|
|
95
|
-
return uv;
|
|
96
|
-
};
|
|
97
|
-
VM.prototype.closeUpvaluesFor = function (frame) {
|
|
98
|
-
// Called on RETURN - close every upvalue that was pointing into this frame.
|
|
99
|
-
// After this, closures that captured from the frame read from upvalue.value.
|
|
100
|
-
this.openUpvalues = this.openUpvalues.filter(function (uv) {
|
|
101
|
-
if (uv.frame === frame) {
|
|
102
|
-
uv.close();
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return true;
|
|
106
|
-
});
|
|
107
|
-
};
|
|
108
|
-
VM.prototype.run = function () {
|
|
109
|
-
var now = () => {
|
|
110
|
-
return performance.now();
|
|
111
|
-
};
|
|
112
|
-
var t = now();
|
|
113
|
-
while (true) {
|
|
114
|
-
var frame = this.currentFrame;
|
|
115
|
-
var bc = this.bytecode;
|
|
116
|
-
if (frame.pc >= bc.length) break;
|
|
117
|
-
var op, operand;
|
|
118
|
-
var word = bc[frame.pc++];
|
|
119
|
-
if (ENCODE_BYTECODE) {
|
|
120
|
-
op = word & 0xff;
|
|
121
|
-
operand = word >>> 8;
|
|
122
|
-
} else {
|
|
123
|
-
op = word[0];
|
|
124
|
-
operand = word[1];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// console.log(frame.pc - 1, op, operand);
|
|
128
|
-
|
|
129
|
-
// Debugging protection
|
|
130
|
-
if (TIMING_CHECKS) {
|
|
131
|
-
var t2 = now();
|
|
132
|
-
var isTamper = t2 - t > 1000;
|
|
133
|
-
t = t2;
|
|
134
|
-
if (isTamper) {
|
|
135
|
-
op = OP.POP;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/* @SWITCH */
|
|
140
|
-
switch (op) {
|
|
141
|
-
case OP.LOAD_CONST:
|
|
142
|
-
this._push(this.constants[operand]);
|
|
143
|
-
break;
|
|
144
|
-
case OP.LOAD_LOCAL:
|
|
145
|
-
this._push(frame.locals[operand]);
|
|
146
|
-
break;
|
|
147
|
-
case OP.STORE_LOCAL:
|
|
148
|
-
frame.locals[operand] = this._pop();
|
|
149
|
-
break;
|
|
150
|
-
case OP.LOAD_GLOBAL:
|
|
151
|
-
this._push(this.globals[this.constants[operand]]);
|
|
152
|
-
break;
|
|
153
|
-
case OP.STORE_GLOBAL:
|
|
154
|
-
this.globals[this.constants[operand]] = this._pop();
|
|
155
|
-
break;
|
|
156
|
-
case OP.GET_PROP:
|
|
157
|
-
{
|
|
158
|
-
// Stack: [..., obj, key] -> [..., obj, obj[key]]
|
|
159
|
-
// obj is PEEKED (not popped) - CALL_METHOD needs it as receiver
|
|
160
|
-
var key = this._pop();
|
|
161
|
-
var obj = this.peek();
|
|
162
|
-
this._push(obj[key]);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
case OP.ADD:
|
|
166
|
-
{
|
|
167
|
-
var b = this._pop();
|
|
168
|
-
this._push(this._pop() + b);
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
case OP.SUB:
|
|
172
|
-
{
|
|
173
|
-
var b = this._pop();
|
|
174
|
-
this._push(this._pop() - b);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
case OP.MUL:
|
|
178
|
-
{
|
|
179
|
-
var b = this._pop();
|
|
180
|
-
this._push(this._pop() * b);
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
case OP.DIV:
|
|
184
|
-
{
|
|
185
|
-
var b = this._pop();
|
|
186
|
-
this._push(this._pop() / b);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
case OP.MOD:
|
|
190
|
-
{
|
|
191
|
-
var b = this._pop();
|
|
192
|
-
this._push(this._pop() % b);
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
195
|
-
case OP.BAND:
|
|
196
|
-
{
|
|
197
|
-
var b = this._pop();
|
|
198
|
-
this._push(this._pop() & b);
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
case OP.BOR:
|
|
202
|
-
{
|
|
203
|
-
var b = this._pop();
|
|
204
|
-
this._push(this._pop() | b);
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
case OP.BXOR:
|
|
208
|
-
{
|
|
209
|
-
var b = this._pop();
|
|
210
|
-
this._push(this._pop() ^ b);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case OP.SHL:
|
|
214
|
-
{
|
|
215
|
-
var b = this._pop();
|
|
216
|
-
this._push(this._pop() << b);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case OP.SHR:
|
|
220
|
-
{
|
|
221
|
-
var b = this._pop();
|
|
222
|
-
this._push(this._pop() >> b);
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
case OP.USHR:
|
|
226
|
-
{
|
|
227
|
-
var b = this._pop();
|
|
228
|
-
this._push(this._pop() >>> b);
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
case OP.LT:
|
|
232
|
-
{
|
|
233
|
-
var b = this._pop();
|
|
234
|
-
this._push(this._pop() < b);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
case OP.GT:
|
|
238
|
-
{
|
|
239
|
-
var b = this._pop();
|
|
240
|
-
this._push(this._pop() > b);
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
case OP.EQ:
|
|
244
|
-
{
|
|
245
|
-
var b = this._pop();
|
|
246
|
-
this._push(this._pop() === b);
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
case OP.LTE:
|
|
250
|
-
{
|
|
251
|
-
var b = this._pop();
|
|
252
|
-
this._push(this._pop() <= b);
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
case OP.GTE:
|
|
256
|
-
{
|
|
257
|
-
var b = this._pop();
|
|
258
|
-
this._push(this._pop() >= b);
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
case OP.NEQ:
|
|
262
|
-
{
|
|
263
|
-
var b = this._pop();
|
|
264
|
-
this._push(this._pop() !== b);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
case OP.LOOSE_EQ:
|
|
268
|
-
{
|
|
269
|
-
var b = this._pop();
|
|
270
|
-
this._push(this._pop() == b);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
case OP.LOOSE_NEQ:
|
|
274
|
-
{
|
|
275
|
-
var b = this._pop();
|
|
276
|
-
this._push(this._pop() != b);
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
case OP.IN:
|
|
280
|
-
{
|
|
281
|
-
var b = this._pop();
|
|
282
|
-
this._push(this._pop() in b);
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
case OP.INSTANCEOF:
|
|
286
|
-
{
|
|
287
|
-
var ctor = this._pop();
|
|
288
|
-
var obj = this._pop();
|
|
289
|
-
if (typeof ctor === "function") {
|
|
290
|
-
// Native constructor (e.g. Array, Date) - native instanceof is fine
|
|
291
|
-
this._push(obj instanceof ctor);
|
|
292
|
-
} else {
|
|
293
|
-
// VM Closure - ctor.prototype was set by MAKE_CLOSURE / user assignment.
|
|
294
|
-
// Walk obj's prototype chain looking for identity with ctor.prototype.
|
|
295
|
-
var proto = ctor.prototype; // the .prototype property on the Closure
|
|
296
|
-
var target = Object.getPrototypeOf(obj);
|
|
297
|
-
var result = false;
|
|
298
|
-
while (target !== null) {
|
|
299
|
-
if (target === proto) {
|
|
300
|
-
result = true;
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
target = Object.getPrototypeOf(target);
|
|
304
|
-
}
|
|
305
|
-
this._push(result);
|
|
306
|
-
}
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
case OP.UNARY_NEG:
|
|
310
|
-
this._push(-this._pop());
|
|
311
|
-
break;
|
|
312
|
-
case OP.UNARY_POS:
|
|
313
|
-
this._push(this._pop());
|
|
314
|
-
break;
|
|
315
|
-
case OP.UNARY_NOT:
|
|
316
|
-
this._push(!this._pop());
|
|
317
|
-
break;
|
|
318
|
-
case OP.UNARY_BITNOT:
|
|
319
|
-
this._push(~this._pop());
|
|
320
|
-
break;
|
|
321
|
-
case OP.TYPEOF:
|
|
322
|
-
this._push(typeof this._pop());
|
|
323
|
-
break;
|
|
324
|
-
case OP.VOID:
|
|
325
|
-
this._pop();
|
|
326
|
-
this._push(undefined);
|
|
327
|
-
break;
|
|
328
|
-
case OP.TYPEOF_SAFE:
|
|
329
|
-
{
|
|
330
|
-
// operand is a const index holding the variable name string.
|
|
331
|
-
// Mimics JS semantics: typeof undeclaredVar === "undefined" (no throw).
|
|
332
|
-
var name = this._pop(); // LOAD_CONST pushed the name - consume it
|
|
333
|
-
var val = Object.prototype.hasOwnProperty.call(this.globals, name) ? this.globals[name] : undefined;
|
|
334
|
-
this._push(typeof val);
|
|
335
|
-
break;
|
|
336
|
-
}
|
|
337
|
-
case OP.JUMP:
|
|
338
|
-
frame.pc = operand;
|
|
339
|
-
break;
|
|
340
|
-
case OP.JUMP_IF_FALSE:
|
|
341
|
-
if (!this._pop()) frame.pc = operand;
|
|
342
|
-
break;
|
|
343
|
-
case OP.JUMP_IF_TRUE_OR_POP:
|
|
344
|
-
// || semantics: if truthy, we're done - leave value, jump over RHS.
|
|
345
|
-
// If falsy, discard it and fall through to evaluate RHS.
|
|
346
|
-
if (this.peek()) {
|
|
347
|
-
frame.pc = operand;
|
|
348
|
-
} else {
|
|
349
|
-
this._pop();
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
case OP.JUMP_IF_FALSE_OR_POP:
|
|
353
|
-
// && semantics: if falsy, we're done - leave value, jump over RHS.
|
|
354
|
-
// If truthy, discard it and fall through to evaluate RHS.
|
|
355
|
-
if (!this.peek()) {
|
|
356
|
-
frame.pc = operand;
|
|
357
|
-
} else {
|
|
358
|
-
this._pop();
|
|
359
|
-
}
|
|
360
|
-
break;
|
|
361
|
-
case OP.MAKE_CLOSURE:
|
|
362
|
-
{
|
|
363
|
-
var fn = this.constants[operand];
|
|
364
|
-
var closure = new Closure(fn);
|
|
365
|
-
for (var i = 0; i < fn.upvalueDescriptors.length; i++) {
|
|
366
|
-
var desc = fn.upvalueDescriptors[i];
|
|
367
|
-
if (desc.isLocal) {
|
|
368
|
-
// Capture directly from current frame's local slot
|
|
369
|
-
closure.upvalues.push(this.captureUpvalue(frame, desc._index));
|
|
370
|
-
} else {
|
|
371
|
-
// Relay - take upvalue from the enclosing closure's list
|
|
372
|
-
closure.upvalues.push(frame.closure.upvalues[desc._index]);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// Wrap in a native callable shell so host code (array methods,
|
|
376
|
-
// test assertions, setTimeout, etc.) can invoke VM closures.
|
|
377
|
-
// CLOSURE_SYM lets VM-internal CALL/NEW bypass the sub-VM entirely.
|
|
378
|
-
var self = this;
|
|
379
|
-
var shell = function (c) {
|
|
380
|
-
return function () {
|
|
381
|
-
var args = Array.prototype.slice.call(arguments);
|
|
382
|
-
var sub = new VM(self.bytecode, 0, self.constants, self.globals);
|
|
383
|
-
// Sloppy-mode: null/undefined thisArg → global object
|
|
384
|
-
var f = new Frame(c, null, null, this == null ? self.globals : this);
|
|
385
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
386
|
-
f.locals[c.fn.paramCount] = args;
|
|
387
|
-
sub.currentFrame = f;
|
|
388
|
-
return sub.run();
|
|
389
|
-
};
|
|
390
|
-
}(closure);
|
|
391
|
-
shell[CLOSURE_SYM] = closure;
|
|
392
|
-
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
393
|
-
this._push(shell);
|
|
394
|
-
break;
|
|
395
|
-
}
|
|
396
|
-
case OP.LOAD_UPVALUE:
|
|
397
|
-
this._push(frame.closure.upvalues[operand].read());
|
|
398
|
-
break;
|
|
399
|
-
case OP.STORE_UPVALUE:
|
|
400
|
-
frame.closure.upvalues[operand].write(this._pop());
|
|
401
|
-
break;
|
|
402
|
-
case OP.BUILD_ARRAY:
|
|
403
|
-
{
|
|
404
|
-
// Pop \`operand\` values off the stack in reverse, assemble array.
|
|
405
|
-
var elems = this._stack.splice(this._stack.length - operand);
|
|
406
|
-
this._push(elems);
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
case OP.BUILD_OBJECT:
|
|
410
|
-
{
|
|
411
|
-
// Stack has: key0, val0, key1, val1 ... keyN, valN (pushed left->right)
|
|
412
|
-
// Pop all pairs and build the object.
|
|
413
|
-
var pairs = this._stack.splice(this._stack.length - operand * 2);
|
|
414
|
-
var o = {};
|
|
415
|
-
for (var i = 0; i < pairs.length; i += 2) {
|
|
416
|
-
o[pairs[i]] = pairs[i + 1]; // key at even index, val at odd
|
|
417
|
-
}
|
|
418
|
-
this._push(o);
|
|
419
|
-
break;
|
|
420
|
-
}
|
|
421
|
-
case OP.SET_PROP:
|
|
422
|
-
{
|
|
423
|
-
// Stack: [..., obj, key, val]
|
|
424
|
-
// Leaves val on stack - assignment is an expression in JS.
|
|
425
|
-
var val = this._pop();
|
|
426
|
-
var key = this._pop();
|
|
427
|
-
var obj = this._pop();
|
|
428
|
-
// Reflect.set performs [[Set]] without throwing on failure,
|
|
429
|
-
// correctly simulating sloppy-mode assignment from a strict-mode host
|
|
430
|
-
// (output.js is an ES module). This also properly invokes inherited
|
|
431
|
-
// or prototype-chain setter functions.
|
|
432
|
-
Reflect.set(obj, key, val);
|
|
433
|
-
this._push(val); // assignment expression evaluates to the assigned value
|
|
434
|
-
break;
|
|
435
|
-
}
|
|
436
|
-
case OP.GET_PROP_COMPUTED:
|
|
437
|
-
{
|
|
438
|
-
// Stack: [..., obj, key] - key is a runtime value (nums[i])
|
|
439
|
-
// Mirrors GET_PROP but pops the key that was pushed dynamically.
|
|
440
|
-
var key = this._pop();
|
|
441
|
-
var obj = this._pop();
|
|
442
|
-
this._push(obj[key]);
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
case OP.DELETE_PROP:
|
|
446
|
-
{
|
|
447
|
-
var key = this._pop();
|
|
448
|
-
var obj = this._pop();
|
|
449
|
-
this._push(delete obj[key]);
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
case OP.CALL:
|
|
453
|
-
{
|
|
454
|
-
var args = this._stack.splice(this._stack.length - operand);
|
|
455
|
-
var callee = this._pop();
|
|
456
|
-
if (callee && callee[CLOSURE_SYM]) {
|
|
457
|
-
// VM closure - run directly in this VM, no sub-VM overhead
|
|
458
|
-
var c = callee[CLOSURE_SYM];
|
|
459
|
-
// Sloppy-mode: plain function call → global object as this
|
|
460
|
-
var f = new Frame(c, frame.pc, frame, this.globals);
|
|
461
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
462
|
-
f.locals[c.fn.paramCount] = args;
|
|
463
|
-
this.frameStack.push(this.currentFrame);
|
|
464
|
-
this.currentFrame = f;
|
|
465
|
-
} else {
|
|
466
|
-
// Native function
|
|
467
|
-
this._push(callee.apply(null, args));
|
|
468
|
-
}
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
case OP.CALL_METHOD:
|
|
472
|
-
{
|
|
473
|
-
var args = this._stack.splice(this._stack.length - operand);
|
|
474
|
-
var callee = this._pop();
|
|
475
|
-
var receiver = this._pop(); // left on stack by GET_PROP
|
|
476
|
-
if (callee && callee[CLOSURE_SYM]) {
|
|
477
|
-
// VM closure - run directly in this VM with receiver as this
|
|
478
|
-
var c = callee[CLOSURE_SYM];
|
|
479
|
-
var f = new Frame(c, frame.pc, frame, receiver);
|
|
480
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
481
|
-
f.locals[c.fn.paramCount] = args;
|
|
482
|
-
this.frameStack.push(this.currentFrame);
|
|
483
|
-
this.currentFrame = f;
|
|
484
|
-
} else {
|
|
485
|
-
// Native method
|
|
486
|
-
this._push(callee.apply(receiver, args));
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
case OP.LOAD_THIS:
|
|
491
|
-
this._push(frame.thisVal);
|
|
492
|
-
break;
|
|
493
|
-
case OP.NEW:
|
|
494
|
-
{
|
|
495
|
-
var args = this._stack.splice(this._stack.length - operand);
|
|
496
|
-
var callee = this._pop();
|
|
497
|
-
if (callee && callee[CLOSURE_SYM]) {
|
|
498
|
-
// VM closure constructor - prototype is unified via shell.prototype = closure.prototype
|
|
499
|
-
var c = callee[CLOSURE_SYM];
|
|
500
|
-
var newObj = Object.create(c.prototype || null);
|
|
501
|
-
var f = new Frame(c, frame.pc, frame, newObj);
|
|
502
|
-
f._newObj = newObj;
|
|
503
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
504
|
-
f.locals[c.fn.paramCount] = args;
|
|
505
|
-
this.frameStack.push(this.currentFrame);
|
|
506
|
-
this.currentFrame = f;
|
|
507
|
-
} else {
|
|
508
|
-
// Native constructor (e.g. new Error(), new Date()).
|
|
509
|
-
// Reflect.construct is required - Object.create+apply does NOT set
|
|
510
|
-
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
511
|
-
this._push(Reflect.construct(callee, args));
|
|
512
|
-
}
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
case OP.RETURN:
|
|
516
|
-
{
|
|
517
|
-
var retVal = this._pop();
|
|
518
|
-
this.closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
519
|
-
if (this.frameStack.length === 0) return retVal;
|
|
520
|
-
|
|
521
|
-
// new-call rule: primitive return -> discard, use the constructed object instead
|
|
522
|
-
if (frame._newObj !== null) {
|
|
523
|
-
if (typeof retVal !== "object" || retVal === null) retVal = frame._newObj;
|
|
524
|
-
}
|
|
525
|
-
this.currentFrame = this.frameStack.pop();
|
|
526
|
-
this._push(retVal);
|
|
527
|
-
break;
|
|
528
|
-
}
|
|
529
|
-
case OP.POP:
|
|
530
|
-
this._pop();
|
|
531
|
-
break;
|
|
532
|
-
case OP.DUP:
|
|
533
|
-
this._push(this.peek());
|
|
534
|
-
break;
|
|
535
|
-
case OP.THROW:
|
|
536
|
-
throw this._pop();
|
|
537
|
-
case OP.FOR_IN_SETUP:
|
|
538
|
-
{
|
|
539
|
-
// Pop the object; build an ordered list of all enumerable own+inherited
|
|
540
|
-
// string keys by walking the prototype chain manually.
|
|
541
|
-
// Uses getOwnPropertyNames (includes non-enumerable) + descriptor check,
|
|
542
|
-
// so we never rely on Object.keys() and we handle inheritance correctly.
|
|
543
|
-
var obj = this._pop();
|
|
544
|
-
var keys = [];
|
|
545
|
-
if (obj !== null && obj !== undefined) {
|
|
546
|
-
var seen = Object.create(null);
|
|
547
|
-
var cur = Object(obj); // box primitives
|
|
548
|
-
while (cur !== null) {
|
|
549
|
-
var ownNames = Object.getOwnPropertyNames(cur);
|
|
550
|
-
for (var i = 0; i < ownNames.length; i++) {
|
|
551
|
-
var k = ownNames[i];
|
|
552
|
-
if (!(k in seen)) {
|
|
553
|
-
seen[k] = true;
|
|
554
|
-
var propDesc = Object.getOwnPropertyDescriptor(cur, k);
|
|
555
|
-
if (propDesc && propDesc.enumerable) {
|
|
556
|
-
keys.push(k);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
cur = Object.getPrototypeOf(cur);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
this._push({
|
|
564
|
-
keys: keys,
|
|
565
|
-
i: 0
|
|
566
|
-
});
|
|
567
|
-
break;
|
|
568
|
-
}
|
|
569
|
-
case OP.FOR_IN_NEXT:
|
|
570
|
-
{
|
|
571
|
-
// operand = jump target for the done case.
|
|
572
|
-
// Pop the iterator; if exhausted jump to exit, otherwise push next key.
|
|
573
|
-
var iter = this._pop();
|
|
574
|
-
if (iter.i >= iter.keys.length) {
|
|
575
|
-
frame.pc = operand;
|
|
576
|
-
} else {
|
|
577
|
-
this._push(iter.keys[iter.i++]);
|
|
578
|
-
}
|
|
579
|
-
break;
|
|
580
|
-
}
|
|
581
|
-
case OP.PATCH:
|
|
582
|
-
{
|
|
583
|
-
// Pop destination PC, then write constants[operand] (packed word array)
|
|
584
|
-
// directly into this.bytecode starting at that PC.
|
|
585
|
-
var destPc = this._pop();
|
|
586
|
-
var words = this.constants[operand];
|
|
587
|
-
if (ENCODE_BYTECODE) {
|
|
588
|
-
words = decodeBytecode(words);
|
|
589
|
-
}
|
|
590
|
-
for (var i = 0; i < words.length; i++) {
|
|
591
|
-
this.bytecode[destPc + i] = words[i];
|
|
592
|
-
}
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
default:
|
|
596
|
-
throw new Error("Unknown opcode: " + op + " at pc " + (frame.pc - 1));
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
// Boot
|
|
602
|
-
var globals = {}; // global object for globals
|
|
603
|
-
|
|
604
|
-
// Always pull built-ins from globalThis so eval() scoping can't shadow them
|
|
605
|
-
// with a local `window` variable (e.g. the test harness fake window).
|
|
606
|
-
for (var k of Object.getOwnPropertyNames(globalThis)) {
|
|
607
|
-
globals[k] = globalThis[k];
|
|
608
|
-
}
|
|
609
|
-
// If a window object is in scope (browser or test harness), capture it
|
|
610
|
-
// explicitly so VM code can read/write window.TEST_OUTPUT etc.
|
|
611
|
-
if (typeof window !== "undefined") {
|
|
612
|
-
globals["window"] = window;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Transfer common primitives
|
|
616
|
-
globals.undefined = undefined;
|
|
617
|
-
globals.Infinity = Infinity;
|
|
618
|
-
globals.NaN = NaN;
|
|
619
|
-
var vm = new VM(decodeBytecode(BYTECODE), MAIN_START_PC, CONSTANTS, globals);
|
|
620
|
-
vm.run();
|