js-confuser-vm 0.1.1 → 0.1.2

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.
Files changed (58) hide show
  1. package/README.md +242 -89
  2. package/dist/compiler.js +583 -208
  3. package/dist/disassembler.js +58 -8
  4. package/dist/runtime.js +93 -74
  5. package/dist/template.js +81 -76
  6. package/dist/transforms/bytecode/concealConstants.js +2 -2
  7. package/dist/transforms/bytecode/controlFlowFlattening.js +143 -25
  8. package/dist/transforms/bytecode/dispatcher.js +3 -3
  9. package/dist/transforms/bytecode/resolveRegisters.js +19 -4
  10. package/dist/transforms/bytecode/selfModifying.js +88 -21
  11. package/dist/transforms/bytecode/specializedOpcodes.js +6 -3
  12. package/dist/transforms/bytecode/stringConcealing.js +253 -75
  13. package/dist/utils/ast-utils.js +61 -0
  14. package/dist/utils/op-utils.js +1 -0
  15. package/package.json +7 -1
  16. package/.gitmodules +0 -4
  17. package/.prettierignore +0 -1
  18. package/CHANGELOG.md +0 -358
  19. package/babel-plugin-inline-runtime.cjs +0 -34
  20. package/babel.config.json +0 -23
  21. package/bench.ts +0 -146
  22. package/disassemble.ts +0 -12
  23. package/index.ts +0 -43
  24. package/jest-strip-types.js +0 -10
  25. package/jest.config.js +0 -64
  26. package/output.disassembled.js +0 -41
  27. package/src/build-runtime.ts +0 -113
  28. package/src/compiler.ts +0 -2703
  29. package/src/disassembler.ts +0 -329
  30. package/src/index.ts +0 -24
  31. package/src/minify.ts +0 -21
  32. package/src/options.ts +0 -24
  33. package/src/runtime.ts +0 -956
  34. package/src/template.ts +0 -265
  35. package/src/transforms/bytecode/aliasedOpcodes.ts +0 -151
  36. package/src/transforms/bytecode/concealConstants.ts +0 -52
  37. package/src/transforms/bytecode/controlFlowFlattening.ts +0 -566
  38. package/src/transforms/bytecode/dispatcher.ts +0 -292
  39. package/src/transforms/bytecode/macroOpcodes.ts +0 -193
  40. package/src/transforms/bytecode/resolveConstants.ts +0 -126
  41. package/src/transforms/bytecode/resolveLabels.ts +0 -112
  42. package/src/transforms/bytecode/resolveRegisters.ts +0 -226
  43. package/src/transforms/bytecode/selfModifying.ts +0 -121
  44. package/src/transforms/bytecode/specializedOpcodes.ts +0 -164
  45. package/src/transforms/bytecode/stringConcealing.ts +0 -130
  46. package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
  47. package/src/transforms/runtime/classObfuscation.ts +0 -59
  48. package/src/transforms/runtime/macroOpcodes.ts +0 -138
  49. package/src/transforms/runtime/minify.ts +0 -1
  50. package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
  51. package/src/transforms/runtime/specializedOpcodes.ts +0 -161
  52. package/src/types.ts +0 -134
  53. package/src/utils/ast-utils.ts +0 -19
  54. package/src/utils/op-utils.ts +0 -46
  55. package/src/utils/pass-utils.ts +0 -126
  56. package/src/utils/profile-utils.ts +0 -3
  57. package/src/utils/random-utils.ts +0 -31
  58. package/tsconfig.json +0 -12
package/src/runtime.ts DELETED
@@ -1,956 +0,0 @@
1
- import { OP_ORIGINAL as OP } from "./compiler.ts";
2
- const BYTECODE = [];
3
- const MAIN_START_PC = 0;
4
- const MAIN_REG_COUNT = 0;
5
- const CONSTANTS = [];
6
- const ENCODE_BYTECODE = false;
7
- const TIMING_CHECKS = false;
8
- // The text above is not included in the compiled output - for type intellisense only
9
- // @START
10
-
11
- function base64ToBytes(s) {
12
- return typeof Buffer !== "undefined"
13
- ? Buffer.from(s, "base64")
14
- : Uint8Array.from(atob(s), function (c) {
15
- return c.charCodeAt(0);
16
- });
17
- }
18
-
19
- function decodeBytecode(s) {
20
- if (!ENCODE_BYTECODE) return s;
21
-
22
- var b = base64ToBytes(s);
23
- // Each slot is a u32 stored as 4 little-endian bytes.
24
- var r = new Uint32Array(b.length / 4);
25
- for (var i = 0; i < r.length; i++)
26
- r[i] =
27
- (b[i * 4] |
28
- (b[i * 4 + 1] << 8) |
29
- (b[i * 4 + 2] << 16) |
30
- (b[i * 4 + 3] << 24)) >>>
31
- 0;
32
- return r;
33
- }
34
-
35
- // Closure symbol
36
- // Used to tag shell functions so the VM can fast-path back to the
37
- // inner Closure instead of going through a sub-VM on internal calls.
38
- var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
39
-
40
- // Upvalue — Lua/CPython style.
41
- // While the outer frame is alive: reads/writes go to vm._regs[_absSlot].
42
- // After the outer frame returns (closed): reads/writes hit this._value.
43
- // _absSlot is the absolute index in VM._regs (frame._base + local slot).
44
- function Upvalue(regs, absSlot) {
45
- this._regs = regs; // shared reference to VM._regs flat array
46
- this._absSlot = absSlot; // absolute index; stable as long as frame is alive
47
- this._closed = false;
48
- this._value = undefined;
49
- }
50
- Upvalue.prototype._read = function () {
51
- return this._closed ? this._value : this._regs[this._absSlot];
52
- };
53
- Upvalue.prototype._write = function (v) {
54
- if (this._closed) this._value = v;
55
- else this._regs[this._absSlot] = v;
56
- };
57
- Upvalue.prototype._close = function () {
58
- this._value = this._regs[this._absSlot];
59
- this._closed = true;
60
- };
61
-
62
- // Closure & Frame
63
- function Closure(fn) {
64
- this.fn = fn;
65
- this.upvalues = [];
66
- this.prototype = {}; // <- default prototype object for `new`
67
- }
68
-
69
- // Frame — analogous to Lua CallInfo / CPython PyFrameObject.
70
- // Does NOT own a register array; registers live in VM._regs[_base .. _base+regCount).
71
- function Frame(closure, returnPc, parent, thisVal, retDstReg, base) {
72
- this.closure = closure;
73
- this._base = base; // absolute offset into VM._regs for this frame's r0
74
- this._pc = closure.fn.startPc;
75
- this._returnPc = returnPc;
76
- this._parent = parent;
77
- this.thisVal = thisVal !== undefined ? thisVal : undefined;
78
- this._retDstReg = retDstReg !== undefined ? retDstReg : 0;
79
- this._newObj = null;
80
- this._handlerStack = [];
81
- }
82
-
83
- // VM
84
- function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
85
- this.bytecode = bytecode;
86
- this.constants = constants;
87
- this.globals = globals;
88
- this._frameStack = [];
89
- this._openUpvalues = [];
90
-
91
- // ── Flat register file (Lua-style) ────────────────────────────────────────
92
- // All frames share a single array. Each Frame records its _base offset.
93
- // _regsTop is the next free slot (= base of the hypothetical next frame).
94
- // On CALL: newBase = _regsTop; _regsTop += fn.regCount
95
- // On RETURN: _regsTop = frame._base (pop the frame's register window)
96
- this._regs = new Array(mainRegCount).fill(undefined);
97
- this._regsTop = mainRegCount; // main frame occupies [0, mainRegCount)
98
-
99
- var mainFn = {
100
- paramCount: 0,
101
- regCount: mainRegCount,
102
- startPc: mainStartPc,
103
- };
104
- this._currentFrame = new Frame(
105
- new Closure(mainFn),
106
- null,
107
- null,
108
- undefined,
109
- 0,
110
- 0,
111
- );
112
- }
113
-
114
- // Consume the next slot from the flat bytecode stream and advance the PC.
115
- // Called by opcode handlers to read each of their operands in order.
116
- VM.prototype._operand = function () {
117
- return this.bytecode[this._currentFrame._pc++];
118
- };
119
-
120
- VM.prototype.captureUpvalue = function (frame, slot) {
121
- // Dedup by absolute slot — two closures capturing the same local share one Upvalue.
122
- var absSlot = frame._base + slot;
123
- for (var i = 0; i < this._openUpvalues.length; i++) {
124
- var uv = this._openUpvalues[i];
125
- if (!uv._closed && uv._absSlot === absSlot) return uv;
126
- }
127
- var uv = new Upvalue(this._regs, absSlot);
128
- this._openUpvalues.push(uv);
129
- return uv;
130
- };
131
-
132
- // Reads and decodes a constant from the pool.
133
- // idx — pool index (first operand of the constant pair emitted by resolveConstants).
134
- // key — conceal key (second operand). 0 means no concealment.
135
- //
136
- // For integers: stored value is (original ^ key); XOR again to recover.
137
- // For strings: stored value is a base64 string containing u16 LE byte pairs.
138
- // Mirrors decodeBytecode: base64 → bytes → u16 LE → XOR with
139
- // (key + i) & 0xFFFF to recover the original char codes.
140
- // idxIn, keyIn are passed in from specializedOpcodes when the operands are determined at compile time.
141
- VM.prototype._constant = function (idxIn, keyIn) {
142
- var idx = idxIn ?? this._operand();
143
- var key = keyIn ?? this._operand();
144
-
145
- var v = this.constants[idx];
146
- if (!key) return v;
147
- if (typeof v === "number") return v ^ key;
148
- // String: base64-decode to u16 LE byte pairs, then XOR each code with (key+i).
149
- var b = base64ToBytes(v);
150
- var out = "";
151
- for (var i = 0; i < b.length / 2; i++) {
152
- var code = b[i * 2] | (b[i * 2 + 1] << 8); // u16 LE
153
- out += String.fromCharCode(code ^ ((key + i) & 0xffff));
154
- }
155
- return out;
156
- };
157
-
158
- VM.prototype._closeUpvaluesFor = function (frame) {
159
- // Called on RETURN — close every upvalue whose absolute slot falls within
160
- // this frame's register window [_base, _base + regCount).
161
- var lo = frame._base;
162
- var hi = frame._base + frame.closure.fn.regCount;
163
- this._openUpvalues = this._openUpvalues.filter(function (uv) {
164
- if (!uv._closed && uv._absSlot >= lo && uv._absSlot < hi) {
165
- uv._close();
166
- return false;
167
- }
168
- return true;
169
- });
170
- };
171
-
172
- VM.prototype._ensureRegisterWindow = function (base, regCount) {
173
- var end = base + regCount;
174
- while (this._regs.length < end) this._regs.push(undefined);
175
- for (var i = base; i < end; i++) this._regs[i] = undefined;
176
- };
177
-
178
- VM.prototype.run = function () {
179
- var now = () => {
180
- return performance.now();
181
- };
182
-
183
- var lastTime = now();
184
-
185
- while (true) {
186
- var frame = this._currentFrame;
187
- var bc = this.bytecode;
188
- if (frame._pc >= bc.length) break;
189
-
190
- var pc = frame._pc++;
191
- var op = this.bytecode[pc];
192
- var opcode = this.bytecode[pc];
193
- // console.log(
194
- // "pc=" + pc,
195
- // "opcode=" + opcode,
196
- // Object.keys(OP).find((key) => OP[key] === opcode),
197
- // );
198
-
199
- // Debugging protection: Detects debugger by checking for >1s pauses which can only happen from debugger; or extremely slow sync tasks
200
- if (TIMING_CHECKS) {
201
- var currentTime = now();
202
- var isTamper = currentTime - lastTime > 1000;
203
- lastTime = currentTime;
204
- if (isTamper) {
205
- // Poison the bytecode
206
- for (var i = 0; i < this.bytecode.length; i++) this.bytecode[i] = 0;
207
- // Break the current state
208
- for (var i2 = frame._base; i2 < this._regsTop; i2++)
209
- this._regs[i2] = undefined;
210
- op = OP.JUMP;
211
- frame._pc = this.bytecode.length; // jump past end to halt
212
- }
213
- }
214
-
215
- try {
216
- var regs = this._regs;
217
- var base = frame._base;
218
-
219
- /* @SWITCH */
220
- switch (op) {
221
- case OP.LOAD_CONST: {
222
- var dst = this._operand();
223
- regs[base + dst] = this._constant();
224
- break;
225
- }
226
-
227
- case OP.LOAD_INT: {
228
- var dst = this._operand();
229
- regs[base + dst] = this._operand();
230
- break;
231
- }
232
-
233
- case OP.LOAD_GLOBAL: {
234
- var dst = this._operand();
235
- var globalName = this._constant();
236
-
237
- if (!(globalName in this.globals)) {
238
- throw new ReferenceError(`${globalName} is not defined`);
239
- }
240
-
241
- regs[base + dst] = this.globals[globalName];
242
- break;
243
- }
244
-
245
- case OP.LOAD_UPVALUE: {
246
- var dst = this._operand();
247
- regs[base + dst] = frame.closure.upvalues[this._operand()]._read();
248
- break;
249
- }
250
-
251
- case OP.LOAD_THIS: {
252
- var dst = this._operand();
253
- regs[base + dst] = frame.thisVal;
254
- break;
255
- }
256
-
257
- case OP.MOVE: {
258
- var dst = this._operand();
259
- regs[base + dst] = regs[base + this._operand()];
260
- break;
261
- }
262
-
263
- case OP.STORE_GLOBAL: {
264
- // globals[globalName] = regs[src]
265
- this.globals[this._constant()] = regs[base + this._operand()];
266
- break;
267
- }
268
-
269
- case OP.STORE_UPVALUE: {
270
- var uvIdx = this._operand();
271
- frame.closure.upvalues[uvIdx]._write(regs[base + this._operand()]);
272
- break;
273
- }
274
-
275
- case OP.GET_PROP: {
276
- // dst = regs[obj][regs[key]]
277
- var dst = this._operand();
278
- var obj = regs[base + this._operand()];
279
- var key = regs[base + this._operand()];
280
- regs[base + dst] = obj[key];
281
- break;
282
- }
283
-
284
- case OP.SET_PROP: {
285
- // regs[obj][regs[key]] = regs[val]
286
- var obj = regs[base + this._operand()];
287
- var key = regs[base + this._operand()];
288
- var val = regs[base + this._operand()];
289
- // Reflect.set performs [[Set]] without throwing on failure (non-strict mode behavior)
290
- Reflect.set(obj, key, val);
291
- break;
292
- }
293
-
294
- case OP.DELETE_PROP: {
295
- // regs[dst] = delete regs[obj][regs[key]]
296
- // The delete operator returns true if successful which is most cases
297
- var dst = this._operand();
298
- var obj = regs[base + this._operand()];
299
- var key = regs[base + this._operand()];
300
- regs[base + dst] = delete obj[key];
301
- break;
302
- }
303
-
304
- // Arithmetic (dst, src1, src2)
305
- case OP.ADD: {
306
- var dst = this._operand();
307
- var a = regs[base + this._operand()];
308
- regs[base + dst] = a + regs[base + this._operand()];
309
- break;
310
- }
311
- case OP.SUB: {
312
- var dst = this._operand();
313
- var a = regs[base + this._operand()];
314
- regs[base + dst] = a - regs[base + this._operand()];
315
- break;
316
- }
317
- case OP.MUL: {
318
- var dst = this._operand();
319
- var a = regs[base + this._operand()];
320
- regs[base + dst] = a * regs[base + this._operand()];
321
- break;
322
- }
323
- case OP.DIV: {
324
- var dst = this._operand();
325
- var a = regs[base + this._operand()];
326
- regs[base + dst] = a / regs[base + this._operand()];
327
- break;
328
- }
329
- case OP.MOD: {
330
- var dst = this._operand();
331
- var a = regs[base + this._operand()];
332
- regs[base + dst] = a % regs[base + this._operand()];
333
- break;
334
- }
335
- case OP.BAND: {
336
- var dst = this._operand();
337
- var a = regs[base + this._operand()];
338
- regs[base + dst] = a & regs[base + this._operand()];
339
- break;
340
- }
341
- case OP.BOR: {
342
- var dst = this._operand();
343
- var a = regs[base + this._operand()];
344
- regs[base + dst] = a | regs[base + this._operand()];
345
- break;
346
- }
347
- case OP.BXOR: {
348
- var dst = this._operand();
349
- var a = regs[base + this._operand()];
350
- regs[base + dst] = a ^ regs[base + this._operand()];
351
- break;
352
- }
353
- case OP.SHL: {
354
- var dst = this._operand();
355
- var a = regs[base + this._operand()];
356
- regs[base + dst] = a << regs[base + this._operand()];
357
- break;
358
- }
359
- case OP.SHR: {
360
- var dst = this._operand();
361
- var a = regs[base + this._operand()];
362
- regs[base + dst] = a >> regs[base + this._operand()];
363
- break;
364
- }
365
- case OP.USHR: {
366
- var dst = this._operand();
367
- var a = regs[base + this._operand()];
368
- regs[base + dst] = a >>> regs[base + this._operand()];
369
- break;
370
- }
371
-
372
- // Comparison (dst, src1, src2)
373
- case OP.LT: {
374
- var dst = this._operand();
375
- var a = regs[base + this._operand()];
376
- regs[base + dst] = a < regs[base + this._operand()];
377
- break;
378
- }
379
- case OP.GT: {
380
- var dst = this._operand();
381
- var a = regs[base + this._operand()];
382
- regs[base + dst] = a > regs[base + this._operand()];
383
- break;
384
- }
385
- case OP.LTE: {
386
- var dst = this._operand();
387
- var a = regs[base + this._operand()];
388
- regs[base + dst] = a <= regs[base + this._operand()];
389
- break;
390
- }
391
- case OP.GTE: {
392
- var dst = this._operand();
393
- var a = regs[base + this._operand()];
394
- regs[base + dst] = a >= regs[base + this._operand()];
395
- break;
396
- }
397
- case OP.EQ: {
398
- var dst = this._operand();
399
- var a = regs[base + this._operand()];
400
- regs[base + dst] = a === regs[base + this._operand()];
401
- break;
402
- }
403
- case OP.NEQ: {
404
- var dst = this._operand();
405
- var a = regs[base + this._operand()];
406
- regs[base + dst] = a !== regs[base + this._operand()];
407
- break;
408
- }
409
- case OP.LOOSE_EQ: {
410
- var dst = this._operand();
411
- var a = regs[base + this._operand()];
412
- regs[base + dst] = a == regs[base + this._operand()];
413
- break;
414
- }
415
- case OP.LOOSE_NEQ: {
416
- var dst = this._operand();
417
- var a = regs[base + this._operand()];
418
- regs[base + dst] = a != regs[base + this._operand()];
419
- break;
420
- }
421
- case OP.IN: {
422
- var dst = this._operand();
423
- var a = regs[base + this._operand()];
424
- regs[base + dst] = a in regs[base + this._operand()];
425
- break;
426
- }
427
- case OP.INSTANCEOF: {
428
- // regs[dst] = regs[obj] instanceof regs[ctor]
429
- var dst = this._operand();
430
- var obj = regs[base + this._operand()];
431
- var ctor = regs[base + this._operand()];
432
- if (typeof ctor === "function") {
433
- regs[base + dst] = obj instanceof ctor;
434
- } else {
435
- // TODO: Why is this needed?
436
- var proto = ctor.prototype;
437
- var target = Object.getPrototypeOf(obj);
438
- var result = false;
439
- while (target !== null) {
440
- if (target === proto) {
441
- result = true;
442
- break;
443
- }
444
- target = Object.getPrototypeOf(target);
445
- }
446
- regs[base + dst] = result;
447
- }
448
- break;
449
- }
450
-
451
- // Unary (dst, src)
452
- case OP.UNARY_NEG: {
453
- var dst = this._operand();
454
- regs[base + dst] = -regs[base + this._operand()];
455
- break;
456
- }
457
- case OP.UNARY_POS: {
458
- var dst = this._operand();
459
- regs[base + dst] = +regs[base + this._operand()];
460
- break;
461
- }
462
- case OP.UNARY_NOT: {
463
- var dst = this._operand();
464
- regs[base + dst] = !regs[base + this._operand()];
465
- break;
466
- }
467
- case OP.UNARY_BITNOT: {
468
- var dst = this._operand();
469
- regs[base + dst] = ~regs[base + this._operand()];
470
- break;
471
- }
472
- case OP.TYPEOF: {
473
- var dst = this._operand();
474
- regs[base + dst] = typeof regs[base + this._operand()];
475
- break;
476
- }
477
- case OP.VOID: {
478
- var dst = this._operand();
479
- this._operand(); // consumes argument (intended)
480
- regs[base + dst] = undefined;
481
- break;
482
- }
483
- case OP.TYPEOF_SAFE: {
484
- // regs[dst] = typeof window[name]
485
- // Never throws ReferenceError, instead returns undefined for undeclared variables
486
- var dst = this._operand();
487
- var name = this._constant();
488
- var val = Object.prototype.hasOwnProperty.call(this.globals, name)
489
- ? this.globals[name]
490
- : undefined;
491
- regs[base + dst] = typeof val;
492
- break;
493
- }
494
-
495
- // Control flow
496
- case OP.JUMP:
497
- frame._pc = this._operand();
498
- break;
499
-
500
- case OP.JUMP_IF_FALSE: {
501
- var src = this._operand();
502
- var target = this._operand();
503
- if (!regs[base + src]) frame._pc = target;
504
- break;
505
- }
506
-
507
- case OP.JUMP_IF_TRUE: {
508
- // || short-circuit: if truthy, jump over RHS.
509
- var src = this._operand();
510
- var target = this._operand();
511
- if (regs[base + src]) frame._pc = target;
512
- break;
513
- }
514
-
515
- // Calls
516
- case OP.CALL: {
517
- // dst, calleeReg, argc, [argReg...]
518
- var dst = this._operand();
519
- var callee = regs[base + this._operand()];
520
- var argc = this._operand();
521
- var args = new Array(argc);
522
- for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
523
-
524
- if (callee && callee[CLOSURE_SYM]) {
525
- var closure = callee[CLOSURE_SYM];
526
- var newBase = this._regsTop;
527
- this._ensureRegisterWindow(newBase, closure.fn.regCount);
528
- this._regsTop = newBase + closure.fn.regCount;
529
- var f = new Frame(
530
- closure,
531
- frame._pc,
532
- frame,
533
- this.globals,
534
- dst,
535
- newBase,
536
- );
537
- if (closure.fn.hasRest) {
538
- var restSlot = closure.fn.paramCount - 1;
539
- for (var i = 0; i < restSlot; i++)
540
- this._regs[newBase + i] = i < args.length ? args[i] : undefined;
541
- this._regs[newBase + restSlot] = args.slice(restSlot);
542
- } else {
543
- for (var i = 0; i < args.length && i < closure.fn.regCount; i++)
544
- this._regs[newBase + i] = args[i];
545
- }
546
- if (closure.fn.paramCount < closure.fn.regCount) {
547
- this._regs[newBase + closure.fn.paramCount] = args;
548
- }
549
- this._frameStack.push(this._currentFrame);
550
- this._currentFrame = f;
551
- } else {
552
- regs[base + dst] = callee.apply(null, args);
553
- }
554
- break;
555
- }
556
-
557
- case OP.CALL_METHOD: {
558
- // dst, receiverReg, calleeReg, argc, [argReg...]
559
- var dst = this._operand();
560
- var receiver = regs[base + this._operand()];
561
- var callee = regs[base + this._operand()];
562
- var argc = this._operand();
563
- var args = new Array(argc);
564
- for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
565
-
566
- if (callee && callee[CLOSURE_SYM]) {
567
- var closure = callee[CLOSURE_SYM];
568
- var newBase = this._regsTop;
569
- this._ensureRegisterWindow(newBase, closure.fn.regCount);
570
- this._regsTop = newBase + closure.fn.regCount;
571
- var f = new Frame(
572
- closure,
573
- frame._pc,
574
- frame,
575
- receiver,
576
- dst,
577
- newBase,
578
- );
579
- if (closure.fn.hasRest) {
580
- var restSlot = closure.fn.paramCount - 1;
581
- for (var i = 0; i < restSlot; i++)
582
- this._regs[newBase + i] = i < args.length ? args[i] : undefined;
583
- this._regs[newBase + restSlot] = args.slice(restSlot);
584
- } else {
585
- for (var i = 0; i < args.length && i < closure.fn.regCount; i++)
586
- this._regs[newBase + i] = args[i];
587
- }
588
- if (closure.fn.paramCount < closure.fn.regCount) {
589
- this._regs[newBase + closure.fn.paramCount] = args;
590
- }
591
- this._frameStack.push(this._currentFrame);
592
- this._currentFrame = f;
593
- } else {
594
- regs[base + dst] = callee.apply(receiver, args);
595
- }
596
- break;
597
- }
598
-
599
- case OP.NEW: {
600
- // dst, calleeReg, argc, [argReg...]
601
- var dst = this._operand();
602
- var callee = regs[base + this._operand()];
603
- var argc = this._operand();
604
- var args = new Array(argc);
605
- for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
606
-
607
- if (callee && callee[CLOSURE_SYM]) {
608
- var closure = callee[CLOSURE_SYM];
609
- var newObj = Object.create(closure.prototype || null);
610
- var newBase = this._regsTop;
611
- this._ensureRegisterWindow(newBase, closure.fn.regCount);
612
- this._regsTop = newBase + closure.fn.regCount;
613
- var f = new Frame(closure, frame._pc, frame, newObj, dst, newBase);
614
- if (closure.fn.hasRest) {
615
- var restSlot = closure.fn.paramCount - 1;
616
- for (var i = 0; i < restSlot; i++)
617
- this._regs[newBase + i] = i < args.length ? args[i] : undefined;
618
- this._regs[newBase + restSlot] = args.slice(restSlot);
619
- } else {
620
- for (var i = 0; i < args.length && i < closure.fn.regCount; i++)
621
- this._regs[newBase + i] = args[i];
622
- }
623
- if (closure.fn.paramCount < closure.fn.regCount) {
624
- this._regs[newBase + closure.fn.paramCount] = args;
625
- }
626
- f._newObj = newObj;
627
- this._frameStack.push(this._currentFrame);
628
- this._currentFrame = f;
629
- } else {
630
- // Reflect.construct is required - Object.create+apply does NOT set
631
- // internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
632
- regs[base + dst] = Reflect.construct(callee, args);
633
- }
634
- break;
635
- }
636
-
637
- case OP.RETURN: {
638
- var retVal = regs[base + this._operand()];
639
- this._closeUpvaluesFor(frame); // must happen before frame is abandoned
640
-
641
- // Zero out callee's register window to limit exposing runtime values
642
- var hi = frame._base + frame.closure.fn.regCount;
643
- for (var i = frame._base as number; i < hi; i++)
644
- this._regs[i] = undefined;
645
- this._regsTop = frame._base;
646
-
647
- if (this._frameStack.length === 0) return retVal; // main script returning
648
-
649
- // NewExpression: When invoking from the 'new' keyword, the newly constructed object is returned instead (if the original function doesn't return an object)
650
- if (frame._newObj !== null) {
651
- if (typeof retVal !== "object" || retVal === null)
652
- retVal = frame._newObj;
653
- }
654
-
655
- var parentFrame = this._frameStack.pop();
656
- this._regs[parentFrame._base + frame._retDstReg] = retVal;
657
- this._currentFrame = parentFrame;
658
- break;
659
- }
660
-
661
- case OP.THROW:
662
- throw regs[base + this._operand()];
663
-
664
- // Closures
665
- case OP.MAKE_CLOSURE: {
666
- // dst, startPc, paramCount, regCount, uvCount, hasRest, [isLocal, idx, ...]
667
- var dst = this._operand();
668
- var startPc = this._operand();
669
- var paramCount = this._operand();
670
- var regCount = this._operand();
671
- var uvCount = this._operand();
672
- var hasRest = this._operand(); // 1 if last param is a rest element
673
-
674
- var uvDescs = new Array(uvCount);
675
- for (var i = 0; i < uvCount; i++) {
676
- var isLocalRaw = this._operand();
677
- var uvIndex = this._operand();
678
- uvDescs[i] = { isLocal: isLocalRaw, _index: uvIndex };
679
- }
680
-
681
- var fn = {
682
- paramCount: paramCount,
683
- regCount: regCount,
684
- startPc: startPc,
685
- upvalueDescriptors: uvDescs,
686
- hasRest: hasRest,
687
- };
688
-
689
- var closure = new Closure(fn);
690
- for (var i = 0; i < uvDescs.length; i++) {
691
- var uvd = uvDescs[i];
692
- if (uvd.isLocal) {
693
- closure.upvalues.push(this.captureUpvalue(frame, uvd._index));
694
- } else {
695
- closure.upvalues.push(frame.closure.upvalues[uvd._index]);
696
- }
697
- }
698
-
699
- // Wrap in a native callable shell so host code (array methods,
700
- // test assertions, setTimeout, etc.) can invoke VM closures.
701
- // CLOSURE_SYM lets VM-internal CALL/NEW bypass the sub-VM entirely.
702
- var self = this;
703
- var shell = (function (c) {
704
- return function () {
705
- var args = Array.prototype.slice.call(arguments);
706
- var sub = new VM(
707
- self.bytecode,
708
- 0,
709
- c.fn.regCount,
710
- self.constants,
711
- self.globals,
712
- );
713
- var f = new Frame(
714
- c,
715
- null,
716
- null,
717
- this == null ? self.globals : this,
718
- 0,
719
- 0,
720
- );
721
- sub._currentFrame = f;
722
- if (c.fn.hasRest) {
723
- var restSlot = c.fn.paramCount - 1;
724
- for (var i = 0; i < restSlot; i++)
725
- sub._regs[i] = i < args.length ? args[i] : undefined;
726
- sub._regs[restSlot] = args.slice(restSlot);
727
- } else {
728
- for (var i = 0; i < args.length && i < c.fn.regCount; i++)
729
- sub._regs[i] = args[i];
730
- }
731
- if (c.fn.paramCount < c.fn.regCount) {
732
- sub._regs[c.fn.paramCount] = args;
733
- }
734
- return sub.run();
735
- };
736
- })(closure);
737
- shell[CLOSURE_SYM] = closure;
738
- shell.prototype = closure.prototype; // unified prototype for new/instanceof
739
- regs[base + dst] = shell;
740
- break;
741
- }
742
-
743
- // Collections
744
- case OP.BUILD_ARRAY: {
745
- // dst, count, [elemReg...]
746
- var dst = this._operand();
747
- var count = this._operand();
748
- var elems = new Array(count);
749
- for (var i = 0; i < count; i++)
750
- elems[i] = regs[base + this._operand()];
751
- regs[base + dst] = elems;
752
- break;
753
- }
754
-
755
- case OP.BUILD_OBJECT: {
756
- // dst, pairCount, [keyReg, valReg, ...]
757
- var dst = this._operand();
758
- var pairCount = this._operand();
759
- var o = {};
760
- for (var i = 0; i < pairCount; i++) {
761
- var key = regs[base + this._operand()];
762
- var val = regs[base + this._operand()];
763
- o[key] = val;
764
- }
765
- regs[base + dst] = o;
766
- break;
767
- }
768
-
769
- // Object methods (getters / setters)
770
- case OP.DEFINE_GETTER: {
771
- // obj, key, fn
772
- var obj = regs[base + this._operand()];
773
- var key = regs[base + this._operand()];
774
- var getterFn = regs[base + this._operand()];
775
- var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
776
- var getDesc: PropertyDescriptor = {
777
- get: getterFn,
778
- configurable: true,
779
- enumerable: true,
780
- };
781
- if (existingDesc && typeof existingDesc.set === "function") {
782
- getDesc.set = existingDesc.set;
783
- }
784
- Object.defineProperty(obj, key, getDesc);
785
- break;
786
- }
787
-
788
- case OP.DEFINE_SETTER: {
789
- // obj, key, fn
790
- var obj = regs[base + this._operand()];
791
- var key = regs[base + this._operand()];
792
- var setterFn = regs[base + this._operand()];
793
- var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
794
- var setDesc: PropertyDescriptor = {
795
- set: setterFn,
796
- configurable: true,
797
- enumerable: true,
798
- };
799
- if (existingDesc && typeof existingDesc.get === "function") {
800
- setDesc.get = existingDesc.get;
801
- }
802
- Object.defineProperty(obj, key, setDesc);
803
- break;
804
- }
805
-
806
- // ── For-in iteration ──────────────────────────────────────────────────
807
- case OP.FOR_IN_SETUP: {
808
- // dst, src — build iterator object from enumerable keys of regs[src]
809
- var dst = this._operand();
810
- var obj = regs[base + this._operand()];
811
- var keys = [];
812
- if (obj !== null && obj !== undefined) {
813
- var seen = Object.create(null);
814
- var cur = Object(obj); // box primitives
815
- while (cur !== null) {
816
- var ownNames = Object.getOwnPropertyNames(cur);
817
- for (var i = 0; i < ownNames.length; i++) {
818
- var k = ownNames[i];
819
- if (!(k in seen)) {
820
- seen[k] = true;
821
- var propDesc = Object.getOwnPropertyDescriptor(cur, k);
822
- if (propDesc && propDesc.enumerable) {
823
- keys.push(k);
824
- }
825
- }
826
- }
827
- cur = Object.getPrototypeOf(cur);
828
- }
829
- }
830
- regs[base + dst] = { _keys: keys, i: 0 };
831
- break;
832
- }
833
-
834
- case OP.FOR_IN_NEXT: {
835
- // dst, iterReg, exitTarget
836
- // Advances iterator; writes next key to dst, or jumps to exitTarget when done.
837
- var dst = this._operand();
838
- var iter = regs[base + this._operand()];
839
- var exitTarget = this._operand();
840
- if (iter.i >= iter._keys.length) {
841
- frame._pc = exitTarget;
842
- } else {
843
- regs[base + dst] = iter._keys[iter.i++];
844
- }
845
- break;
846
- }
847
-
848
- // ── Exception handling ────────────────────────────────────────────────
849
- case OP.TRY_SETUP: {
850
- // handlerPc, exceptionReg — push exception handler record onto current frame.
851
- frame._handlerStack.push({
852
- handlerPc: this._operand(),
853
- exceptionReg: this._operand(),
854
- frameStackDepth: this._frameStack.length,
855
- });
856
- break;
857
- }
858
-
859
- case OP.TRY_END: {
860
- // Normal exit from a try block — disarm the exception handler.
861
- frame._handlerStack.pop();
862
- break;
863
- }
864
-
865
- // Self-modifying bytecode
866
- case OP.PATCH: {
867
- // destPc, sliceStart, sliceEnd
868
- var destPc = this._operand();
869
- var sliceStart = this._operand();
870
- var sliceEnd = this._operand();
871
- for (var pi = sliceStart; pi < sliceEnd; pi++) {
872
- this.bytecode[destPc + (pi - sliceStart)] = this.bytecode[pi];
873
- }
874
- break;
875
- }
876
-
877
- case OP.JUMP_REG: {
878
- // Indirect jump: allows VM to jump based on runtime values.
879
- frame._pc = regs[base + this._operand()];
880
- break;
881
- }
882
-
883
- case OP.DEBUGGER: {
884
- debugger;
885
- break;
886
- }
887
-
888
- default:
889
- throw new Error(
890
- "Unknown opcode: " + op + " at pc " + (frame._pc - 1),
891
- );
892
- }
893
- } catch (err) {
894
- // Exception handler unwinding
895
- // Walk from the current frame upward until we find a frame that has an open exception handler (TRY_SETUP without a matching TRY_END).
896
- // For every frame we abandon along the way, close its captured upvalues.
897
- var handledFrame = null;
898
- var searchFrame = this._currentFrame;
899
- while (true) {
900
- if (searchFrame._handlerStack.length > 0) {
901
- handledFrame = searchFrame;
902
- break;
903
- }
904
- // No handler in this frame — abandon it and walk up.
905
- this._closeUpvaluesFor(searchFrame);
906
- this._regsTop = searchFrame._base;
907
- if (this._frameStack.length === 0) break;
908
- searchFrame = this._frameStack.pop();
909
- this._currentFrame = searchFrame;
910
- }
911
-
912
- if (!handledFrame) throw err; // if there's no handler, propagate back to host
913
-
914
- var h = handledFrame._handlerStack.pop();
915
- // Discard any call-frames that were pushed inside the try body.
916
- this._frameStack.length = h.frameStackDepth;
917
- // Write the caught exception directly into the designated register.
918
- this._regs[handledFrame._base + h.exceptionReg] = err;
919
- // Jump to the catch block.
920
- handledFrame._pc = h.handlerPc;
921
- this._regsTop = handledFrame._base + handledFrame.closure.fn.regCount;
922
- this._currentFrame = handledFrame;
923
- }
924
- }
925
- };
926
-
927
- /* @BOOT */ // <- This comment can't be removed!
928
- var globals: any = {}; // global object for globals
929
-
930
- // Always pull built-ins from globalThis so eval() scoping can't shadow them
931
- // with a local `window` variable (e.g. the test harness fake window).
932
- for (var k of Object.getOwnPropertyNames(globalThis)) {
933
- globals[k] = globalThis[k];
934
- }
935
- // If a window object is in scope (browser or test harness), capture it
936
- // explicitly so VM code can read/write window.TEST_OUTPUT etc.
937
- if (typeof window !== "undefined") {
938
- globals.window = window;
939
- for (var k of Object.getOwnPropertyNames(window)) {
940
- globals[k] = window[k];
941
- }
942
- }
943
-
944
- // Transfer common primitives
945
- globals.undefined = undefined;
946
- globals.Infinity = Infinity;
947
- globals.NaN = NaN;
948
-
949
- var vm = new VM(
950
- decodeBytecode(BYTECODE),
951
- MAIN_START_PC,
952
- MAIN_REG_COUNT,
953
- CONSTANTS,
954
- globals,
955
- );
956
- vm.run();