js-confuser-vm 0.1.0 → 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.
- package/README.md +281 -147
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +714 -265
- package/dist/disassembler.js +367 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +160 -119
- package/dist/template.js +163 -42
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/concealConstants.js +2 -2
- package/dist/transforms/bytecode/controlFlowFlattening.js +569 -0
- package/dist/transforms/bytecode/dispatcher.js +15 -111
- package/dist/transforms/bytecode/macroOpcodes.js +2 -2
- package/{src/transforms/bytecode/resolveContants.ts → dist/transforms/bytecode/resolveConstants.js} +30 -56
- package/dist/transforms/bytecode/resolveRegisters.js +23 -4
- package/dist/transforms/bytecode/selfModifying.js +88 -21
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +23 -12
- package/dist/transforms/bytecode/stringConcealing.js +288 -0
- package/dist/transforms/runtime/classObfuscation.js +43 -0
- package/dist/transforms/runtime/handlerTable.js +91 -0
- package/dist/transforms/runtime/semanticOpcodes.js +35 -0
- package/dist/transforms/runtime/specializedOpcodes.js +11 -5
- package/dist/types.js +1 -1
- package/dist/utils/ast-utils.js +75 -0
- package/dist/utils/op-utils.js +1 -2
- package/dist/utils/pass-utils.js +100 -0
- package/dist/utils/profile-utils.js +3 -0
- package/package.json +8 -1
- package/.gitmodules +0 -4
- package/.prettierignore +0 -1
- package/CHANGELOG.md +0 -335
- package/babel-plugin-inline-runtime.cjs +0 -34
- package/babel.config.json +0 -23
- package/index.ts +0 -38
- package/jest-strip-types.js +0 -10
- package/jest.config.js +0 -52
- package/src/build-runtime.ts +0 -78
- package/src/compiler.ts +0 -2593
- package/src/index.ts +0 -14
- package/src/minify.ts +0 -21
- package/src/options.ts +0 -18
- package/src/runtime.ts +0 -923
- package/src/template.ts +0 -141
- package/src/transforms/bytecode/aliasedOpcodes.ts +0 -148
- package/src/transforms/bytecode/concealConstants.ts +0 -52
- package/src/transforms/bytecode/dispatcher.ts +0 -398
- package/src/transforms/bytecode/macroOpcodes.ts +0 -193
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/bytecode/resolveLabels.ts +0 -112
- package/src/transforms/bytecode/resolveRegisters.ts +0 -221
- package/src/transforms/bytecode/selfModifying.ts +0 -121
- package/src/transforms/bytecode/specializedOpcodes.ts +0 -153
- package/src/transforms/runtime/aliasedOpcodes.ts +0 -191
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/macroOpcodes.ts +0 -138
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- package/src/transforms/runtime/minify.ts +0 -1
- package/src/transforms/runtime/shuffleOpcodes.ts +0 -24
- package/src/transforms/runtime/specializedOpcodes.ts +0 -156
- package/src/types.ts +0 -93
- package/src/utils/op-utils.ts +0 -48
- package/src/utils/random-utils.ts +0 -31
- package/tsconfig.json +0 -12
package/dist/runtime.js
CHANGED
|
@@ -5,29 +5,34 @@ const MAIN_REG_COUNT = 0;
|
|
|
5
5
|
const CONSTANTS = [];
|
|
6
6
|
const ENCODE_BYTECODE = false;
|
|
7
7
|
const TIMING_CHECKS = false;
|
|
8
|
+
const SENTINELS = {
|
|
9
|
+
CALL_SPREAD: 0
|
|
10
|
+
};
|
|
8
11
|
// The text above is not included in the compiled output - for type intellisense only
|
|
9
12
|
// @START
|
|
10
13
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
var b = typeof Buffer !== "undefined" ? Buffer.from(s, "base64") : Uint8Array.from(atob(s), function (c) {
|
|
14
|
+
function base64ToBytes(s) {
|
|
15
|
+
return typeof Buffer !== "undefined" ? Buffer.from(s, "base64") : Uint8Array.from(atob(s), function (c) {
|
|
14
16
|
return c.charCodeAt(0);
|
|
15
17
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
}
|
|
19
|
+
function decodeBytecode(s) {
|
|
20
|
+
if (!ENCODE_BYTECODE) return s;
|
|
21
|
+
var b = base64ToBytes(s);
|
|
22
|
+
// Each slot is a u32 stored as 4 little-endian bytes.
|
|
23
|
+
var r = new Uint32Array(b.length / 4);
|
|
24
|
+
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) >>> 0;
|
|
19
25
|
return r;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
// Closure
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
var
|
|
28
|
+
// Closure map
|
|
29
|
+
// Maps shell functions -> inner Closure so the VM can fast-path instead of going through a sub-VM on internal calls.
|
|
30
|
+
// A WeakMap is used over a Symbol to prevent leaking information to debuggers
|
|
31
|
+
var CLOSURE_MAP = new WeakMap();
|
|
26
32
|
|
|
27
|
-
// Upvalue
|
|
33
|
+
// Upvalue (Lua style)
|
|
28
34
|
// While the outer frame is alive: reads/writes go to vm._regs[_absSlot].
|
|
29
35
|
// After the outer frame returns (closed): reads/writes hit this._value.
|
|
30
|
-
// _absSlot is the absolute index in VM._regs (frame._base + local slot).
|
|
31
36
|
function Upvalue(regs, absSlot) {
|
|
32
37
|
this._regs = regs; // shared reference to VM._regs flat array
|
|
33
38
|
this._absSlot = absSlot; // absolute index; stable as long as frame is alive
|
|
@@ -52,7 +57,7 @@ function Closure(fn) {
|
|
|
52
57
|
this.prototype = {}; // <- default prototype object for `new`
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
// Frame
|
|
60
|
+
// Frame (Lua CallInfo / CPython PyFrameObject)
|
|
56
61
|
// Does NOT own a register array; registers live in VM._regs[_base .. _base+regCount).
|
|
57
62
|
function Frame(closure, returnPc, parent, thisVal, retDstReg, base) {
|
|
58
63
|
this.closure = closure;
|
|
@@ -74,8 +79,8 @@ function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
|
74
79
|
this._frameStack = [];
|
|
75
80
|
this._openUpvalues = [];
|
|
76
81
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
82
|
+
// Flat register array (Lua-style)
|
|
83
|
+
// Each Frame records its _base offset.
|
|
79
84
|
// _regsTop is the next free slot (= base of the hypothetical next frame).
|
|
80
85
|
// On CALL: newBase = _regsTop; _regsTop += fn.regCount
|
|
81
86
|
// On RETURN: _regsTop = frame._base (pop the frame's register window)
|
|
@@ -88,7 +93,6 @@ function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
|
88
93
|
startPc: mainStartPc
|
|
89
94
|
};
|
|
90
95
|
this._currentFrame = new Frame(new Closure(mainFn), null, null, undefined, 0, 0);
|
|
91
|
-
this._internals = {};
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
// Consume the next slot from the flat bytecode stream and advance the PC.
|
|
@@ -109,8 +113,8 @@ VM.prototype.captureUpvalue = function (frame, slot) {
|
|
|
109
113
|
};
|
|
110
114
|
|
|
111
115
|
// Reads and decodes a constant from the pool.
|
|
112
|
-
// idx
|
|
113
|
-
// key
|
|
116
|
+
// idx: pool index (first operand of the constant pair emitted by resolveConstants).
|
|
117
|
+
// key: conceal key (second operand). 0 means no concealment.
|
|
114
118
|
//
|
|
115
119
|
// For integers: stored value is (original ^ key); XOR again to recover.
|
|
116
120
|
// For strings: stored value is a base64 string containing u16 LE byte pairs.
|
|
@@ -124,9 +128,7 @@ VM.prototype._constant = function (idxIn, keyIn) {
|
|
|
124
128
|
if (!key) return v;
|
|
125
129
|
if (typeof v === "number") return v ^ key;
|
|
126
130
|
// String: base64-decode to u16 LE byte pairs, then XOR each code with (key+i).
|
|
127
|
-
var b =
|
|
128
|
-
return c.charCodeAt(0);
|
|
129
|
-
});
|
|
131
|
+
var b = base64ToBytes(v);
|
|
130
132
|
var out = "";
|
|
131
133
|
for (var i = 0; i < b.length / 2; i++) {
|
|
132
134
|
var code = b[i * 2] | b[i * 2 + 1] << 8; // u16 LE
|
|
@@ -164,11 +166,7 @@ VM.prototype.run = function () {
|
|
|
164
166
|
var pc = frame._pc++;
|
|
165
167
|
var op = this.bytecode[pc];
|
|
166
168
|
var opcode = this.bytecode[pc];
|
|
167
|
-
// console.log(
|
|
168
|
-
// "pc=" + pc,
|
|
169
|
-
// "opcode=" + opcode,
|
|
170
|
-
// Object.keys(OP).find((key) => OP[key] === opcode),
|
|
171
|
-
// );
|
|
169
|
+
// console.log(`[run] pc=${pc}, opcode=${opcode}, name=${Object.keys(OP).find((key) => OP[key] === opcode)}`);
|
|
172
170
|
|
|
173
171
|
// Debugging protection: Detects debugger by checking for >1s pauses which can only happen from debugger; or extremely slow sync tasks
|
|
174
172
|
if (TIMING_CHECKS) {
|
|
@@ -187,6 +185,7 @@ VM.prototype.run = function () {
|
|
|
187
185
|
try {
|
|
188
186
|
var regs = this._regs;
|
|
189
187
|
var base = frame._base;
|
|
188
|
+
|
|
190
189
|
/* @SWITCH */
|
|
191
190
|
switch (op) {
|
|
192
191
|
case OP.LOAD_CONST:
|
|
@@ -231,8 +230,7 @@ VM.prototype.run = function () {
|
|
|
231
230
|
}
|
|
232
231
|
case OP.STORE_GLOBAL:
|
|
233
232
|
{
|
|
234
|
-
//
|
|
235
|
-
// transform can rewrite this._constant() consistently.
|
|
233
|
+
// globals[globalName] = regs[src]
|
|
236
234
|
this.globals[this._constant()] = regs[base + this._operand()];
|
|
237
235
|
break;
|
|
238
236
|
}
|
|
@@ -257,13 +255,14 @@ VM.prototype.run = function () {
|
|
|
257
255
|
var obj = regs[base + this._operand()];
|
|
258
256
|
var key = regs[base + this._operand()];
|
|
259
257
|
var val = regs[base + this._operand()];
|
|
260
|
-
// Reflect.set performs [[Set]] without throwing on failure
|
|
261
|
-
// correctly simulating sloppy-mode assignment from a strict-mode host.
|
|
258
|
+
// Reflect.set performs [[Set]] without throwing on failure (non-strict mode behavior)
|
|
262
259
|
Reflect.set(obj, key, val);
|
|
263
260
|
break;
|
|
264
261
|
}
|
|
265
262
|
case OP.DELETE_PROP:
|
|
266
263
|
{
|
|
264
|
+
// regs[dst] = delete regs[obj][regs[key]]
|
|
265
|
+
// The delete operator returns true if successful which is most cases
|
|
267
266
|
var dst = this._operand();
|
|
268
267
|
var obj = regs[base + this._operand()];
|
|
269
268
|
var key = regs[base + this._operand()];
|
|
@@ -271,7 +270,7 @@ VM.prototype.run = function () {
|
|
|
271
270
|
break;
|
|
272
271
|
}
|
|
273
272
|
|
|
274
|
-
//
|
|
273
|
+
// Arithmetic (dst, src1, src2)
|
|
275
274
|
case OP.ADD:
|
|
276
275
|
{
|
|
277
276
|
var dst = this._operand();
|
|
@@ -307,6 +306,14 @@ VM.prototype.run = function () {
|
|
|
307
306
|
regs[base + dst] = a % regs[base + this._operand()];
|
|
308
307
|
break;
|
|
309
308
|
}
|
|
309
|
+
case OP.EXP:
|
|
310
|
+
{
|
|
311
|
+
// Math.pow instead of `**`
|
|
312
|
+
var dst = this._operand();
|
|
313
|
+
var a = regs[base + this._operand()];
|
|
314
|
+
regs[base + dst] = Math.pow(a, regs[base + this._operand()]);
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
310
317
|
case OP.BAND:
|
|
311
318
|
{
|
|
312
319
|
var dst = this._operand();
|
|
@@ -350,7 +357,7 @@ VM.prototype.run = function () {
|
|
|
350
357
|
break;
|
|
351
358
|
}
|
|
352
359
|
|
|
353
|
-
//
|
|
360
|
+
// Comparison (dst, src1, src2)
|
|
354
361
|
case OP.LT:
|
|
355
362
|
{
|
|
356
363
|
var dst = this._operand();
|
|
@@ -416,29 +423,15 @@ VM.prototype.run = function () {
|
|
|
416
423
|
}
|
|
417
424
|
case OP.INSTANCEOF:
|
|
418
425
|
{
|
|
426
|
+
// regs[dst] = regs[obj] instanceof regs[ctor]
|
|
427
|
+
// Since VM closures are wrapped in native function shells (MAKE_CLOSURE), the native operator works
|
|
419
428
|
var dst = this._operand();
|
|
420
429
|
var obj = regs[base + this._operand()];
|
|
421
|
-
|
|
422
|
-
if (typeof ctor === "function") {
|
|
423
|
-
regs[base + dst] = obj instanceof ctor;
|
|
424
|
-
} else {
|
|
425
|
-
// VM Closure - walk prototype chain for identity with ctor.prototype.
|
|
426
|
-
var proto = ctor.prototype;
|
|
427
|
-
var target = Object.getPrototypeOf(obj);
|
|
428
|
-
var result = false;
|
|
429
|
-
while (target !== null) {
|
|
430
|
-
if (target === proto) {
|
|
431
|
-
result = true;
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
target = Object.getPrototypeOf(target);
|
|
435
|
-
}
|
|
436
|
-
regs[base + dst] = result;
|
|
437
|
-
}
|
|
430
|
+
regs[base + dst] = obj instanceof regs[base + this._operand()];
|
|
438
431
|
break;
|
|
439
432
|
}
|
|
440
433
|
|
|
441
|
-
//
|
|
434
|
+
// Unary (dst, src)
|
|
442
435
|
case OP.UNARY_NEG:
|
|
443
436
|
{
|
|
444
437
|
var dst = this._operand();
|
|
@@ -472,13 +465,14 @@ VM.prototype.run = function () {
|
|
|
472
465
|
case OP.VOID:
|
|
473
466
|
{
|
|
474
467
|
var dst = this._operand();
|
|
475
|
-
this._operand(); //
|
|
468
|
+
this._operand(); // consumes argument (intended)
|
|
476
469
|
regs[base + dst] = undefined;
|
|
477
470
|
break;
|
|
478
471
|
}
|
|
479
472
|
case OP.TYPEOF_SAFE:
|
|
480
473
|
{
|
|
481
|
-
// dst
|
|
474
|
+
// regs[dst] = typeof window[name]
|
|
475
|
+
// Never throws ReferenceError, instead returns undefined for undeclared variables
|
|
482
476
|
var dst = this._operand();
|
|
483
477
|
var name = this._constant();
|
|
484
478
|
var val = Object.prototype.hasOwnProperty.call(this.globals, name) ? this.globals[name] : undefined;
|
|
@@ -486,7 +480,7 @@ VM.prototype.run = function () {
|
|
|
486
480
|
break;
|
|
487
481
|
}
|
|
488
482
|
|
|
489
|
-
//
|
|
483
|
+
// Control flow
|
|
490
484
|
case OP.JUMP:
|
|
491
485
|
frame._pc = this._operand();
|
|
492
486
|
break;
|
|
@@ -506,23 +500,32 @@ VM.prototype.run = function () {
|
|
|
506
500
|
break;
|
|
507
501
|
}
|
|
508
502
|
|
|
509
|
-
//
|
|
503
|
+
// Calls
|
|
510
504
|
case OP.CALL:
|
|
511
505
|
{
|
|
512
|
-
// dst, calleeReg, argc, [argReg...]
|
|
506
|
+
// dst, calleeReg, argc, [argReg...] (argc=-1 means next operand is spread-args array reg)
|
|
513
507
|
var dst = this._operand();
|
|
514
508
|
var callee = regs[base + this._operand()];
|
|
515
509
|
var argc = this._operand();
|
|
516
|
-
var args
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
510
|
+
var args;
|
|
511
|
+
if (argc === SENTINELS.CALL_SPREAD) {
|
|
512
|
+
args = regs[base + this._operand()];
|
|
513
|
+
} else {
|
|
514
|
+
args = new Array(argc);
|
|
515
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
516
|
+
}
|
|
517
|
+
var closure = callee && CLOSURE_MAP.get(callee);
|
|
518
|
+
if (closure) {
|
|
520
519
|
var newBase = this._regsTop;
|
|
521
520
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
522
521
|
this._regsTop = newBase + closure.fn.regCount;
|
|
523
522
|
var f = new Frame(closure, frame._pc, frame, this.globals, dst, newBase);
|
|
524
|
-
|
|
525
|
-
|
|
523
|
+
if (closure.fn.hasRest) {
|
|
524
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
525
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
526
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
527
|
+
} else {
|
|
528
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
526
529
|
}
|
|
527
530
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
528
531
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -536,21 +539,30 @@ VM.prototype.run = function () {
|
|
|
536
539
|
}
|
|
537
540
|
case OP.CALL_METHOD:
|
|
538
541
|
{
|
|
539
|
-
// dst, receiverReg, calleeReg, argc, [argReg...]
|
|
542
|
+
// dst, receiverReg, calleeReg, argc, [argReg...] (argc=SENTINELS.CALL_SPREAD means spread-args array reg)
|
|
540
543
|
var dst = this._operand();
|
|
541
544
|
var receiver = regs[base + this._operand()];
|
|
542
545
|
var callee = regs[base + this._operand()];
|
|
543
546
|
var argc = this._operand();
|
|
544
|
-
var args
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
547
|
+
var args;
|
|
548
|
+
if (argc === SENTINELS.CALL_SPREAD) {
|
|
549
|
+
args = regs[base + this._operand()];
|
|
550
|
+
} else {
|
|
551
|
+
args = new Array(argc);
|
|
552
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
553
|
+
}
|
|
554
|
+
var closure = callee && CLOSURE_MAP.get(callee);
|
|
555
|
+
if (closure) {
|
|
548
556
|
var newBase = this._regsTop;
|
|
549
557
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
550
558
|
this._regsTop = newBase + closure.fn.regCount;
|
|
551
559
|
var f = new Frame(closure, frame._pc, frame, receiver, dst, newBase);
|
|
552
|
-
|
|
553
|
-
|
|
560
|
+
if (closure.fn.hasRest) {
|
|
561
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
562
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
563
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
564
|
+
} else {
|
|
565
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
554
566
|
}
|
|
555
567
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
556
568
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -564,21 +576,30 @@ VM.prototype.run = function () {
|
|
|
564
576
|
}
|
|
565
577
|
case OP.NEW:
|
|
566
578
|
{
|
|
567
|
-
// dst, calleeReg, argc, [argReg...]
|
|
579
|
+
// dst, calleeReg, argc, [argReg...] (argc=SENTINELS.CALL_SPREAD means spread-args array reg)
|
|
568
580
|
var dst = this._operand();
|
|
569
581
|
var callee = regs[base + this._operand()];
|
|
570
582
|
var argc = this._operand();
|
|
571
|
-
var args
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
583
|
+
var args;
|
|
584
|
+
if (argc === SENTINELS.CALL_SPREAD) {
|
|
585
|
+
args = regs[base + this._operand()];
|
|
586
|
+
} else {
|
|
587
|
+
args = new Array(argc);
|
|
588
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
589
|
+
}
|
|
590
|
+
var closure = callee && CLOSURE_MAP.get(callee);
|
|
591
|
+
if (closure) {
|
|
575
592
|
var newObj = Object.create(closure.prototype || null);
|
|
576
593
|
var newBase = this._regsTop;
|
|
577
594
|
this._ensureRegisterWindow(newBase, closure.fn.regCount);
|
|
578
595
|
this._regsTop = newBase + closure.fn.regCount;
|
|
579
596
|
var f = new Frame(closure, frame._pc, frame, newObj, dst, newBase);
|
|
580
|
-
|
|
581
|
-
|
|
597
|
+
if (closure.fn.hasRest) {
|
|
598
|
+
var restSlot = closure.fn.paramCount - 1;
|
|
599
|
+
for (var i = 0; i < restSlot; i++) this._regs[newBase + i] = i < args.length ? args[i] : undefined;
|
|
600
|
+
this._regs[newBase + restSlot] = args.slice(restSlot);
|
|
601
|
+
} else {
|
|
602
|
+
for (var i = 0; i < args.length && i < closure.fn.regCount; i++) this._regs[newBase + i] = args[i];
|
|
582
603
|
}
|
|
583
604
|
if (closure.fn.paramCount < closure.fn.regCount) {
|
|
584
605
|
this._regs[newBase + closure.fn.paramCount] = args;
|
|
@@ -597,10 +618,14 @@ VM.prototype.run = function () {
|
|
|
597
618
|
{
|
|
598
619
|
var retVal = regs[base + this._operand()];
|
|
599
620
|
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
621
|
+
|
|
622
|
+
// Zero out callee's register window to limit exposing runtime values
|
|
623
|
+
var hi = frame._base + frame.closure.fn.regCount;
|
|
624
|
+
for (var i = frame._base; i < hi; i++) this._regs[i] = undefined;
|
|
600
625
|
this._regsTop = frame._base;
|
|
601
626
|
if (this._frameStack.length === 0) return retVal; // main script returning
|
|
602
627
|
|
|
603
|
-
//
|
|
628
|
+
// NewExpression: When invoking from the 'new' keyword, the newly constructed object is returned instead (if the original function doesn't return an object)
|
|
604
629
|
if (frame._newObj !== null) {
|
|
605
630
|
if (typeof retVal !== "object" || retVal === null) retVal = frame._newObj;
|
|
606
631
|
}
|
|
@@ -612,15 +637,17 @@ VM.prototype.run = function () {
|
|
|
612
637
|
case OP.THROW:
|
|
613
638
|
throw regs[base + this._operand()];
|
|
614
639
|
|
|
615
|
-
//
|
|
640
|
+
// Closures
|
|
616
641
|
case OP.MAKE_CLOSURE:
|
|
617
642
|
{
|
|
618
|
-
// dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, ...]
|
|
643
|
+
// dst, startPc, paramCount, regCount, uvCount, hasRest, [isLocal, idx, ...]
|
|
619
644
|
var dst = this._operand();
|
|
620
645
|
var startPc = this._operand();
|
|
621
646
|
var paramCount = this._operand();
|
|
622
647
|
var regCount = this._operand();
|
|
623
648
|
var uvCount = this._operand();
|
|
649
|
+
var hasRest = this._operand(); // 1 if last param is a rest element
|
|
650
|
+
|
|
624
651
|
var uvDescs = new Array(uvCount);
|
|
625
652
|
for (var i = 0; i < uvCount; i++) {
|
|
626
653
|
var isLocalRaw = this._operand();
|
|
@@ -634,7 +661,8 @@ VM.prototype.run = function () {
|
|
|
634
661
|
paramCount: paramCount,
|
|
635
662
|
regCount: regCount,
|
|
636
663
|
startPc: startPc,
|
|
637
|
-
upvalueDescriptors: uvDescs
|
|
664
|
+
upvalueDescriptors: uvDescs,
|
|
665
|
+
hasRest: hasRest
|
|
638
666
|
};
|
|
639
667
|
var closure = new Closure(fn);
|
|
640
668
|
for (var i = 0; i < uvDescs.length; i++) {
|
|
@@ -648,7 +676,7 @@ VM.prototype.run = function () {
|
|
|
648
676
|
|
|
649
677
|
// Wrap in a native callable shell so host code (array methods,
|
|
650
678
|
// test assertions, setTimeout, etc.) can invoke VM closures.
|
|
651
|
-
//
|
|
679
|
+
// CLOSURE_MAP lets VM-internal CALL/NEW bypass the sub-VM entirely.
|
|
652
680
|
var self = this;
|
|
653
681
|
var shell = function (c) {
|
|
654
682
|
return function () {
|
|
@@ -656,8 +684,12 @@ VM.prototype.run = function () {
|
|
|
656
684
|
var sub = new VM(self.bytecode, 0, c.fn.regCount, self.constants, self.globals);
|
|
657
685
|
var f = new Frame(c, null, null, this == null ? self.globals : this, 0, 0);
|
|
658
686
|
sub._currentFrame = f;
|
|
659
|
-
|
|
660
|
-
|
|
687
|
+
if (c.fn.hasRest) {
|
|
688
|
+
var restSlot = c.fn.paramCount - 1;
|
|
689
|
+
for (var i = 0; i < restSlot; i++) sub._regs[i] = i < args.length ? args[i] : undefined;
|
|
690
|
+
sub._regs[restSlot] = args.slice(restSlot);
|
|
691
|
+
} else {
|
|
692
|
+
for (var i = 0; i < args.length && i < c.fn.regCount; i++) sub._regs[i] = args[i];
|
|
661
693
|
}
|
|
662
694
|
if (c.fn.paramCount < c.fn.regCount) {
|
|
663
695
|
sub._regs[c.fn.paramCount] = args;
|
|
@@ -665,13 +697,13 @@ VM.prototype.run = function () {
|
|
|
665
697
|
return sub.run();
|
|
666
698
|
};
|
|
667
699
|
}(closure);
|
|
668
|
-
shell
|
|
700
|
+
CLOSURE_MAP.set(shell, closure);
|
|
669
701
|
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
670
702
|
regs[base + dst] = shell;
|
|
671
703
|
break;
|
|
672
704
|
}
|
|
673
705
|
|
|
674
|
-
//
|
|
706
|
+
// Collections
|
|
675
707
|
case OP.BUILD_ARRAY:
|
|
676
708
|
{
|
|
677
709
|
// dst, count, [elemReg...]
|
|
@@ -697,7 +729,7 @@ VM.prototype.run = function () {
|
|
|
697
729
|
break;
|
|
698
730
|
}
|
|
699
731
|
|
|
700
|
-
//
|
|
732
|
+
// Object methods (getters / setters)
|
|
701
733
|
case OP.DEFINE_GETTER:
|
|
702
734
|
{
|
|
703
735
|
// obj, key, fn
|
|
@@ -794,12 +826,28 @@ VM.prototype.run = function () {
|
|
|
794
826
|
}
|
|
795
827
|
case OP.TRY_END:
|
|
796
828
|
{
|
|
797
|
-
// Normal exit from a try block — disarm the
|
|
829
|
+
// Normal exit from a try block — disarm the top handler record
|
|
830
|
+
// (works for both catch and finally regions; they share the stack).
|
|
798
831
|
frame._handlerStack.pop();
|
|
799
832
|
break;
|
|
800
833
|
}
|
|
834
|
+
case OP.FINALLY_SETUP:
|
|
835
|
+
{
|
|
836
|
+
// finallyPc, contReg, payloadReg, throwPad
|
|
837
|
+
// Arm a finalizer for the current region. Unlike a catch record this
|
|
838
|
+
// carries no exceptionReg; instead the continuation register (contReg)
|
|
839
|
+
// receives the resume PC and payloadReg carries the in-flight value.
|
|
840
|
+
frame._handlerStack.push({
|
|
841
|
+
finallyPc: this._operand(),
|
|
842
|
+
contReg: this._operand(),
|
|
843
|
+
payloadReg: this._operand(),
|
|
844
|
+
throwPad: this._operand(),
|
|
845
|
+
frameStackDepth: this._frameStack.length
|
|
846
|
+
});
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
801
849
|
|
|
802
|
-
//
|
|
850
|
+
// Self-modifying bytecode
|
|
803
851
|
case OP.PATCH:
|
|
804
852
|
{
|
|
805
853
|
// destPc, sliceStart, sliceEnd
|
|
@@ -813,10 +861,7 @@ VM.prototype.run = function () {
|
|
|
813
861
|
}
|
|
814
862
|
case OP.JUMP_REG:
|
|
815
863
|
{
|
|
816
|
-
// Indirect jump:
|
|
817
|
-
// bytecode immediate. Used by the jumpDispatcher pass so that static
|
|
818
|
-
// analysis cannot determine the jump destination without tracking the
|
|
819
|
-
// register value (which contains an encoded PC resolved at runtime).
|
|
864
|
+
// Indirect jump: allows VM to jump based on runtime values.
|
|
820
865
|
frame._pc = regs[base + this._operand()];
|
|
821
866
|
break;
|
|
822
867
|
}
|
|
@@ -829,9 +874,8 @@ VM.prototype.run = function () {
|
|
|
829
874
|
throw new Error("Unknown opcode: " + op + " at pc " + (frame._pc - 1));
|
|
830
875
|
}
|
|
831
876
|
} catch (err) {
|
|
832
|
-
// Exception handler unwinding
|
|
833
|
-
// Walk from the current frame upward until we find a frame that has an open
|
|
834
|
-
// exception handler (TRY_SETUP without a matching TRY_END).
|
|
877
|
+
// Exception handler unwinding
|
|
878
|
+
// Walk from the current frame upward until we find a frame that has an open exception handler (TRY_SETUP without a matching TRY_END).
|
|
835
879
|
// For every frame we abandon along the way, close its captured upvalues.
|
|
836
880
|
var handledFrame = null;
|
|
837
881
|
var searchFrame = this._currentFrame;
|
|
@@ -847,41 +891,38 @@ VM.prototype.run = function () {
|
|
|
847
891
|
searchFrame = this._frameStack.pop();
|
|
848
892
|
this._currentFrame = searchFrame;
|
|
849
893
|
}
|
|
850
|
-
if (!handledFrame) throw err; // no handler
|
|
894
|
+
if (!handledFrame) throw err; // if there's no handler, propagate back to host
|
|
851
895
|
|
|
852
896
|
var h = handledFrame._handlerStack.pop();
|
|
853
|
-
// Discard any call-frames that were pushed inside the
|
|
897
|
+
// Discard any call-frames that were pushed inside the protected region.
|
|
854
898
|
this._frameStack.length = h.frameStackDepth;
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
899
|
+
var hBase = handledFrame._base;
|
|
900
|
+
if (h.exceptionReg !== undefined) {
|
|
901
|
+
// catch region — deliver the exception to the catch binding and run it.
|
|
902
|
+
this._regs[hBase + h.exceptionReg] = err;
|
|
903
|
+
handledFrame._pc = h.handlerPc;
|
|
904
|
+
} else {
|
|
905
|
+
// finally region: run the finalizer with the exception pending, then
|
|
906
|
+
// resume at its throw pad (which re-raises and continues unwinding).
|
|
907
|
+
this._regs[hBase + h.contReg] = h.throwPad;
|
|
908
|
+
this._regs[hBase + h.payloadReg] = err;
|
|
909
|
+
handledFrame._pc = h.finallyPc;
|
|
910
|
+
}
|
|
911
|
+
this._regsTop = hBase + handledFrame.closure.fn.regCount;
|
|
860
912
|
this._currentFrame = handledFrame;
|
|
861
913
|
}
|
|
862
914
|
}
|
|
863
915
|
};
|
|
864
916
|
|
|
865
|
-
//
|
|
866
|
-
var globals =
|
|
867
|
-
|
|
868
|
-
// Always pull built-ins from globalThis so eval() scoping can't shadow them
|
|
869
|
-
// with a local `window` variable (e.g. the test harness fake window).
|
|
870
|
-
for (var k of Object.getOwnPropertyNames(globalThis)) {
|
|
871
|
-
globals[k] = globalThis[k];
|
|
872
|
-
}
|
|
873
|
-
// If a window object is in scope (browser or test harness), capture it
|
|
874
|
-
// explicitly so VM code can read/write window.TEST_OUTPUT etc.
|
|
917
|
+
/* @BOOT */ // <- This comment can't be removed!
|
|
918
|
+
var globals = globalThis;
|
|
875
919
|
if (typeof window !== "undefined") {
|
|
876
920
|
globals.window = window;
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
921
|
+
globals.document = typeof document !== "undefined" ? document : undefined;
|
|
922
|
+
}
|
|
923
|
+
if (typeof module !== "undefined") {
|
|
924
|
+
globals.module = module;
|
|
925
|
+
globals.exports = typeof exports !== "undefined" ? exports : undefined;
|
|
880
926
|
}
|
|
881
|
-
|
|
882
|
-
// Transfer common primitives
|
|
883
|
-
globals.undefined = undefined;
|
|
884
|
-
globals.Infinity = Infinity;
|
|
885
|
-
globals.NaN = NaN;
|
|
886
927
|
var vm = new VM(decodeBytecode(BYTECODE), MAIN_START_PC, MAIN_REG_COUNT, CONSTANTS, globals);
|
|
887
928
|
vm.run();
|