js-confuser-vm 0.0.9 → 0.1.1
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/.gitmodules +4 -0
- package/CHANGELOG.md +125 -2
- package/README.md +128 -53
- package/bench.ts +146 -0
- package/disassemble.ts +12 -0
- package/dist/build-runtime.js +41 -15
- package/dist/compiler.js +328 -181
- package/dist/disassembler.js +317 -0
- package/dist/index.js +7 -2
- package/dist/runtime.js +255 -176
- package/dist/template.js +258 -0
- package/dist/transforms/bytecode/aliasedOpcodes.js +4 -1
- package/dist/transforms/bytecode/controlFlowFlattening.js +451 -0
- package/dist/transforms/bytecode/dispatcher.js +266 -0
- package/dist/transforms/bytecode/macroOpcodes.js +3 -3
- package/dist/transforms/bytecode/resolveConstants.js +100 -0
- package/dist/transforms/bytecode/resolveLabels.js +21 -18
- package/dist/transforms/bytecode/resolveRegisters.js +216 -0
- package/dist/transforms/bytecode/semanticOpcodes.js +162 -0
- package/dist/transforms/bytecode/specializedOpcodes.js +22 -12
- package/dist/transforms/bytecode/stringConcealing.js +110 -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 +42 -1
- package/dist/utils/ast-utils.js +14 -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/index.ts +22 -16
- package/jest.config.js +19 -2
- package/output.disassembled.js +41 -0
- package/package.json +2 -1
- package/src/build-runtime.ts +113 -78
- package/src/compiler.ts +2703 -2482
- package/src/disassembler.ts +329 -0
- package/src/index.ts +12 -2
- package/src/options.ts +8 -1
- package/src/runtime.ts +294 -180
- package/src/template.ts +265 -0
- package/src/transforms/bytecode/aliasedOpcodes.ts +5 -2
- package/src/transforms/bytecode/controlFlowFlattening.ts +566 -0
- package/src/transforms/bytecode/dispatcher.ts +292 -0
- package/src/transforms/bytecode/macroOpcodes.ts +4 -4
- package/src/transforms/bytecode/resolveLabels.ts +31 -27
- package/src/transforms/bytecode/resolveRegisters.ts +226 -0
- package/src/transforms/bytecode/specializedOpcodes.ts +27 -20
- package/src/transforms/bytecode/stringConcealing.ts +130 -0
- package/src/transforms/runtime/classObfuscation.ts +59 -0
- package/src/transforms/runtime/specializedOpcodes.ts +14 -9
- package/src/types.ts +106 -5
- package/src/utils/ast-utils.ts +19 -0
- package/src/utils/op-utils.ts +2 -2
- package/src/utils/pass-utils.ts +126 -0
- package/src/utils/profile-utils.ts +3 -0
- package/tsconfig.json +1 -1
- package/dist/transforms/utils/op-utils.js +0 -25
- package/dist/transforms/utils/random-utils.js +0 -27
- package/dist/utilts.js +0 -3
- package/src/transforms/bytecode/microOpcodes.ts +0 -291
- package/src/transforms/runtime/internalVariables.ts +0 -270
- package/src/transforms/runtime/microOpcodes.ts +0 -93
- /package/src/transforms/bytecode/{resolveContants.ts → resolveConstants.ts} +0 -0
package/src/runtime.ts
CHANGED
|
@@ -8,18 +8,27 @@ const TIMING_CHECKS = false;
|
|
|
8
8
|
// The text above is not included in the compiled output - for type intellisense only
|
|
9
9
|
// @START
|
|
10
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
|
+
|
|
11
19
|
function decodeBytecode(s) {
|
|
12
20
|
if (!ENCODE_BYTECODE) return s;
|
|
13
21
|
|
|
14
|
-
var b =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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;
|
|
23
32
|
return r;
|
|
24
33
|
}
|
|
25
34
|
|
|
@@ -28,24 +37,25 @@ function decodeBytecode(s) {
|
|
|
28
37
|
// inner Closure instead of going through a sub-VM on internal calls.
|
|
29
38
|
var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
|
|
30
39
|
|
|
31
|
-
// Upvalue
|
|
32
|
-
// While the outer frame is alive: reads/writes go to
|
|
40
|
+
// Upvalue — Lua/CPython style.
|
|
41
|
+
// While the outer frame is alive: reads/writes go to vm._regs[_absSlot].
|
|
33
42
|
// After the outer frame returns (closed): reads/writes hit this._value.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
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
|
|
37
47
|
this._closed = false;
|
|
38
48
|
this._value = undefined;
|
|
39
49
|
}
|
|
40
50
|
Upvalue.prototype._read = function () {
|
|
41
|
-
return this._closed ? this._value : this.
|
|
51
|
+
return this._closed ? this._value : this._regs[this._absSlot];
|
|
42
52
|
};
|
|
43
53
|
Upvalue.prototype._write = function (v) {
|
|
44
54
|
if (this._closed) this._value = v;
|
|
45
|
-
else this.
|
|
55
|
+
else this._regs[this._absSlot] = v;
|
|
46
56
|
};
|
|
47
57
|
Upvalue.prototype._close = function () {
|
|
48
|
-
this._value = this.
|
|
58
|
+
this._value = this._regs[this._absSlot];
|
|
49
59
|
this._closed = true;
|
|
50
60
|
};
|
|
51
61
|
|
|
@@ -56,16 +66,18 @@ function Closure(fn) {
|
|
|
56
66
|
this.prototype = {}; // <- default prototype object for `new`
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
|
|
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) {
|
|
60
72
|
this.closure = closure;
|
|
61
|
-
this.
|
|
62
|
-
this._pc = closure.fn.startPc;
|
|
63
|
-
this._returnPc = returnPc;
|
|
73
|
+
this._base = base; // absolute offset into VM._regs for this frame's r0
|
|
74
|
+
this._pc = closure.fn.startPc;
|
|
75
|
+
this._returnPc = returnPc;
|
|
64
76
|
this._parent = parent;
|
|
65
77
|
this.thisVal = thisVal !== undefined ? thisVal : undefined;
|
|
66
|
-
this._retDstReg = retDstReg !== undefined ? retDstReg : 0;
|
|
67
|
-
this._newObj = null;
|
|
68
|
-
this._handlerStack = [];
|
|
78
|
+
this._retDstReg = retDstReg !== undefined ? retDstReg : 0;
|
|
79
|
+
this._newObj = null;
|
|
80
|
+
this._handlerStack = [];
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
// VM
|
|
@@ -74,15 +86,29 @@ function VM(bytecode, mainStartPc, mainRegCount, constants, globals) {
|
|
|
74
86
|
this.constants = constants;
|
|
75
87
|
this.globals = globals;
|
|
76
88
|
this._frameStack = [];
|
|
77
|
-
this._openUpvalues = [];
|
|
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)
|
|
78
98
|
|
|
79
99
|
var mainFn = {
|
|
80
100
|
paramCount: 0,
|
|
81
101
|
regCount: mainRegCount,
|
|
82
|
-
startPc: mainStartPc,
|
|
102
|
+
startPc: mainStartPc,
|
|
83
103
|
};
|
|
84
|
-
this._currentFrame = new Frame(
|
|
85
|
-
|
|
104
|
+
this._currentFrame = new Frame(
|
|
105
|
+
new Closure(mainFn),
|
|
106
|
+
null,
|
|
107
|
+
null,
|
|
108
|
+
undefined,
|
|
109
|
+
0,
|
|
110
|
+
0,
|
|
111
|
+
);
|
|
86
112
|
}
|
|
87
113
|
|
|
88
114
|
// Consume the next slot from the flat bytecode stream and advance the PC.
|
|
@@ -92,13 +118,13 @@ VM.prototype._operand = function () {
|
|
|
92
118
|
};
|
|
93
119
|
|
|
94
120
|
VM.prototype.captureUpvalue = function (frame, slot) {
|
|
95
|
-
//
|
|
96
|
-
|
|
121
|
+
// Dedup by absolute slot — two closures capturing the same local share one Upvalue.
|
|
122
|
+
var absSlot = frame._base + slot;
|
|
97
123
|
for (var i = 0; i < this._openUpvalues.length; i++) {
|
|
98
124
|
var uv = this._openUpvalues[i];
|
|
99
|
-
if (uv.
|
|
125
|
+
if (!uv._closed && uv._absSlot === absSlot) return uv;
|
|
100
126
|
}
|
|
101
|
-
var uv = new Upvalue(
|
|
127
|
+
var uv = new Upvalue(this._regs, absSlot);
|
|
102
128
|
this._openUpvalues.push(uv);
|
|
103
129
|
return uv;
|
|
104
130
|
};
|
|
@@ -120,12 +146,7 @@ VM.prototype._constant = function (idxIn, keyIn) {
|
|
|
120
146
|
if (!key) return v;
|
|
121
147
|
if (typeof v === "number") return v ^ key;
|
|
122
148
|
// String: base64-decode to u16 LE byte pairs, then XOR each code with (key+i).
|
|
123
|
-
var b =
|
|
124
|
-
typeof Buffer !== "undefined"
|
|
125
|
-
? Buffer.from(v, "base64")
|
|
126
|
-
: Uint8Array.from(atob(v), function (c) {
|
|
127
|
-
return c.charCodeAt(0);
|
|
128
|
-
});
|
|
149
|
+
var b = base64ToBytes(v);
|
|
129
150
|
var out = "";
|
|
130
151
|
for (var i = 0; i < b.length / 2; i++) {
|
|
131
152
|
var code = b[i * 2] | (b[i * 2 + 1] << 8); // u16 LE
|
|
@@ -135,10 +156,12 @@ VM.prototype._constant = function (idxIn, keyIn) {
|
|
|
135
156
|
};
|
|
136
157
|
|
|
137
158
|
VM.prototype._closeUpvaluesFor = function (frame) {
|
|
138
|
-
// Called on RETURN
|
|
139
|
-
//
|
|
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;
|
|
140
163
|
this._openUpvalues = this._openUpvalues.filter(function (uv) {
|
|
141
|
-
if (uv.
|
|
164
|
+
if (!uv._closed && uv._absSlot >= lo && uv._absSlot < hi) {
|
|
142
165
|
uv._close();
|
|
143
166
|
return false;
|
|
144
167
|
}
|
|
@@ -146,6 +169,12 @@ VM.prototype._closeUpvaluesFor = function (frame) {
|
|
|
146
169
|
});
|
|
147
170
|
};
|
|
148
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
|
+
|
|
149
178
|
VM.prototype.run = function () {
|
|
150
179
|
var now = () => {
|
|
151
180
|
return performance.now();
|
|
@@ -176,24 +205,28 @@ VM.prototype.run = function () {
|
|
|
176
205
|
// Poison the bytecode
|
|
177
206
|
for (var i = 0; i < this.bytecode.length; i++) this.bytecode[i] = 0;
|
|
178
207
|
// Break the current state
|
|
179
|
-
frame.
|
|
208
|
+
for (var i2 = frame._base; i2 < this._regsTop; i2++)
|
|
209
|
+
this._regs[i2] = undefined;
|
|
180
210
|
op = OP.JUMP;
|
|
181
211
|
frame._pc = this.bytecode.length; // jump past end to halt
|
|
182
212
|
}
|
|
183
213
|
}
|
|
184
214
|
|
|
185
215
|
try {
|
|
216
|
+
var regs = this._regs;
|
|
217
|
+
var base = frame._base;
|
|
218
|
+
|
|
186
219
|
/* @SWITCH */
|
|
187
220
|
switch (op) {
|
|
188
221
|
case OP.LOAD_CONST: {
|
|
189
222
|
var dst = this._operand();
|
|
190
|
-
|
|
223
|
+
regs[base + dst] = this._constant();
|
|
191
224
|
break;
|
|
192
225
|
}
|
|
193
226
|
|
|
194
227
|
case OP.LOAD_INT: {
|
|
195
228
|
var dst = this._operand();
|
|
196
|
-
|
|
229
|
+
regs[base + dst] = this._operand();
|
|
197
230
|
break;
|
|
198
231
|
}
|
|
199
232
|
|
|
@@ -205,200 +238,201 @@ VM.prototype.run = function () {
|
|
|
205
238
|
throw new ReferenceError(`${globalName} is not defined`);
|
|
206
239
|
}
|
|
207
240
|
|
|
208
|
-
|
|
241
|
+
regs[base + dst] = this.globals[globalName];
|
|
209
242
|
break;
|
|
210
243
|
}
|
|
211
244
|
|
|
212
245
|
case OP.LOAD_UPVALUE: {
|
|
213
246
|
var dst = this._operand();
|
|
214
|
-
|
|
247
|
+
regs[base + dst] = frame.closure.upvalues[this._operand()]._read();
|
|
215
248
|
break;
|
|
216
249
|
}
|
|
217
250
|
|
|
218
251
|
case OP.LOAD_THIS: {
|
|
219
252
|
var dst = this._operand();
|
|
220
|
-
|
|
253
|
+
regs[base + dst] = frame.thisVal;
|
|
221
254
|
break;
|
|
222
255
|
}
|
|
223
256
|
|
|
224
257
|
case OP.MOVE: {
|
|
225
258
|
var dst = this._operand();
|
|
226
|
-
|
|
259
|
+
regs[base + dst] = regs[base + this._operand()];
|
|
227
260
|
break;
|
|
228
261
|
}
|
|
229
262
|
|
|
230
263
|
case OP.STORE_GLOBAL: {
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
this.globals[this._constant()] = frame.regs[this._operand()];
|
|
264
|
+
// globals[globalName] = regs[src]
|
|
265
|
+
this.globals[this._constant()] = regs[base + this._operand()];
|
|
234
266
|
break;
|
|
235
267
|
}
|
|
236
268
|
|
|
237
269
|
case OP.STORE_UPVALUE: {
|
|
238
270
|
var uvIdx = this._operand();
|
|
239
|
-
frame.closure.upvalues[uvIdx]._write(
|
|
271
|
+
frame.closure.upvalues[uvIdx]._write(regs[base + this._operand()]);
|
|
240
272
|
break;
|
|
241
273
|
}
|
|
242
274
|
|
|
243
275
|
case OP.GET_PROP: {
|
|
244
276
|
// dst = regs[obj][regs[key]]
|
|
245
277
|
var dst = this._operand();
|
|
246
|
-
var obj =
|
|
247
|
-
var key =
|
|
248
|
-
|
|
278
|
+
var obj = regs[base + this._operand()];
|
|
279
|
+
var key = regs[base + this._operand()];
|
|
280
|
+
regs[base + dst] = obj[key];
|
|
249
281
|
break;
|
|
250
282
|
}
|
|
251
283
|
|
|
252
284
|
case OP.SET_PROP: {
|
|
253
285
|
// regs[obj][regs[key]] = regs[val]
|
|
254
|
-
var obj =
|
|
255
|
-
var key =
|
|
256
|
-
var val =
|
|
257
|
-
// Reflect.set performs [[Set]] without throwing on failure
|
|
258
|
-
// correctly simulating sloppy-mode assignment from a strict-mode host.
|
|
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)
|
|
259
290
|
Reflect.set(obj, key, val);
|
|
260
291
|
break;
|
|
261
292
|
}
|
|
262
293
|
|
|
263
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
|
|
264
297
|
var dst = this._operand();
|
|
265
|
-
var obj =
|
|
266
|
-
var key =
|
|
267
|
-
|
|
298
|
+
var obj = regs[base + this._operand()];
|
|
299
|
+
var key = regs[base + this._operand()];
|
|
300
|
+
regs[base + dst] = delete obj[key];
|
|
268
301
|
break;
|
|
269
302
|
}
|
|
270
303
|
|
|
271
|
-
//
|
|
304
|
+
// Arithmetic (dst, src1, src2)
|
|
272
305
|
case OP.ADD: {
|
|
273
306
|
var dst = this._operand();
|
|
274
|
-
var a =
|
|
275
|
-
|
|
307
|
+
var a = regs[base + this._operand()];
|
|
308
|
+
regs[base + dst] = a + regs[base + this._operand()];
|
|
276
309
|
break;
|
|
277
310
|
}
|
|
278
311
|
case OP.SUB: {
|
|
279
312
|
var dst = this._operand();
|
|
280
|
-
var a =
|
|
281
|
-
|
|
313
|
+
var a = regs[base + this._operand()];
|
|
314
|
+
regs[base + dst] = a - regs[base + this._operand()];
|
|
282
315
|
break;
|
|
283
316
|
}
|
|
284
317
|
case OP.MUL: {
|
|
285
318
|
var dst = this._operand();
|
|
286
|
-
var a =
|
|
287
|
-
|
|
319
|
+
var a = regs[base + this._operand()];
|
|
320
|
+
regs[base + dst] = a * regs[base + this._operand()];
|
|
288
321
|
break;
|
|
289
322
|
}
|
|
290
323
|
case OP.DIV: {
|
|
291
324
|
var dst = this._operand();
|
|
292
|
-
var a =
|
|
293
|
-
|
|
325
|
+
var a = regs[base + this._operand()];
|
|
326
|
+
regs[base + dst] = a / regs[base + this._operand()];
|
|
294
327
|
break;
|
|
295
328
|
}
|
|
296
329
|
case OP.MOD: {
|
|
297
330
|
var dst = this._operand();
|
|
298
|
-
var a =
|
|
299
|
-
|
|
331
|
+
var a = regs[base + this._operand()];
|
|
332
|
+
regs[base + dst] = a % regs[base + this._operand()];
|
|
300
333
|
break;
|
|
301
334
|
}
|
|
302
335
|
case OP.BAND: {
|
|
303
336
|
var dst = this._operand();
|
|
304
|
-
var a =
|
|
305
|
-
|
|
337
|
+
var a = regs[base + this._operand()];
|
|
338
|
+
regs[base + dst] = a & regs[base + this._operand()];
|
|
306
339
|
break;
|
|
307
340
|
}
|
|
308
341
|
case OP.BOR: {
|
|
309
342
|
var dst = this._operand();
|
|
310
|
-
var a =
|
|
311
|
-
|
|
343
|
+
var a = regs[base + this._operand()];
|
|
344
|
+
regs[base + dst] = a | regs[base + this._operand()];
|
|
312
345
|
break;
|
|
313
346
|
}
|
|
314
347
|
case OP.BXOR: {
|
|
315
348
|
var dst = this._operand();
|
|
316
|
-
var a =
|
|
317
|
-
|
|
349
|
+
var a = regs[base + this._operand()];
|
|
350
|
+
regs[base + dst] = a ^ regs[base + this._operand()];
|
|
318
351
|
break;
|
|
319
352
|
}
|
|
320
353
|
case OP.SHL: {
|
|
321
354
|
var dst = this._operand();
|
|
322
|
-
var a =
|
|
323
|
-
|
|
355
|
+
var a = regs[base + this._operand()];
|
|
356
|
+
regs[base + dst] = a << regs[base + this._operand()];
|
|
324
357
|
break;
|
|
325
358
|
}
|
|
326
359
|
case OP.SHR: {
|
|
327
360
|
var dst = this._operand();
|
|
328
|
-
var a =
|
|
329
|
-
|
|
361
|
+
var a = regs[base + this._operand()];
|
|
362
|
+
regs[base + dst] = a >> regs[base + this._operand()];
|
|
330
363
|
break;
|
|
331
364
|
}
|
|
332
365
|
case OP.USHR: {
|
|
333
366
|
var dst = this._operand();
|
|
334
|
-
var a =
|
|
335
|
-
|
|
367
|
+
var a = regs[base + this._operand()];
|
|
368
|
+
regs[base + dst] = a >>> regs[base + this._operand()];
|
|
336
369
|
break;
|
|
337
370
|
}
|
|
338
371
|
|
|
339
|
-
//
|
|
372
|
+
// Comparison (dst, src1, src2)
|
|
340
373
|
case OP.LT: {
|
|
341
374
|
var dst = this._operand();
|
|
342
|
-
var a =
|
|
343
|
-
|
|
375
|
+
var a = regs[base + this._operand()];
|
|
376
|
+
regs[base + dst] = a < regs[base + this._operand()];
|
|
344
377
|
break;
|
|
345
378
|
}
|
|
346
379
|
case OP.GT: {
|
|
347
380
|
var dst = this._operand();
|
|
348
|
-
var a =
|
|
349
|
-
|
|
381
|
+
var a = regs[base + this._operand()];
|
|
382
|
+
regs[base + dst] = a > regs[base + this._operand()];
|
|
350
383
|
break;
|
|
351
384
|
}
|
|
352
385
|
case OP.LTE: {
|
|
353
386
|
var dst = this._operand();
|
|
354
|
-
var a =
|
|
355
|
-
|
|
387
|
+
var a = regs[base + this._operand()];
|
|
388
|
+
regs[base + dst] = a <= regs[base + this._operand()];
|
|
356
389
|
break;
|
|
357
390
|
}
|
|
358
391
|
case OP.GTE: {
|
|
359
392
|
var dst = this._operand();
|
|
360
|
-
var a =
|
|
361
|
-
|
|
393
|
+
var a = regs[base + this._operand()];
|
|
394
|
+
regs[base + dst] = a >= regs[base + this._operand()];
|
|
362
395
|
break;
|
|
363
396
|
}
|
|
364
397
|
case OP.EQ: {
|
|
365
398
|
var dst = this._operand();
|
|
366
|
-
var a =
|
|
367
|
-
|
|
399
|
+
var a = regs[base + this._operand()];
|
|
400
|
+
regs[base + dst] = a === regs[base + this._operand()];
|
|
368
401
|
break;
|
|
369
402
|
}
|
|
370
403
|
case OP.NEQ: {
|
|
371
404
|
var dst = this._operand();
|
|
372
|
-
var a =
|
|
373
|
-
|
|
405
|
+
var a = regs[base + this._operand()];
|
|
406
|
+
regs[base + dst] = a !== regs[base + this._operand()];
|
|
374
407
|
break;
|
|
375
408
|
}
|
|
376
409
|
case OP.LOOSE_EQ: {
|
|
377
410
|
var dst = this._operand();
|
|
378
|
-
var a =
|
|
379
|
-
|
|
411
|
+
var a = regs[base + this._operand()];
|
|
412
|
+
regs[base + dst] = a == regs[base + this._operand()];
|
|
380
413
|
break;
|
|
381
414
|
}
|
|
382
415
|
case OP.LOOSE_NEQ: {
|
|
383
416
|
var dst = this._operand();
|
|
384
|
-
var a =
|
|
385
|
-
|
|
417
|
+
var a = regs[base + this._operand()];
|
|
418
|
+
regs[base + dst] = a != regs[base + this._operand()];
|
|
386
419
|
break;
|
|
387
420
|
}
|
|
388
421
|
case OP.IN: {
|
|
389
422
|
var dst = this._operand();
|
|
390
|
-
var a =
|
|
391
|
-
|
|
423
|
+
var a = regs[base + this._operand()];
|
|
424
|
+
regs[base + dst] = a in regs[base + this._operand()];
|
|
392
425
|
break;
|
|
393
426
|
}
|
|
394
427
|
case OP.INSTANCEOF: {
|
|
428
|
+
// regs[dst] = regs[obj] instanceof regs[ctor]
|
|
395
429
|
var dst = this._operand();
|
|
396
|
-
var obj =
|
|
397
|
-
var ctor =
|
|
430
|
+
var obj = regs[base + this._operand()];
|
|
431
|
+
var ctor = regs[base + this._operand()];
|
|
398
432
|
if (typeof ctor === "function") {
|
|
399
|
-
|
|
433
|
+
regs[base + dst] = obj instanceof ctor;
|
|
400
434
|
} else {
|
|
401
|
-
//
|
|
435
|
+
// TODO: Why is this needed?
|
|
402
436
|
var proto = ctor.prototype;
|
|
403
437
|
var target = Object.getPrototypeOf(obj);
|
|
404
438
|
var result = false;
|
|
@@ -409,55 +443,56 @@ VM.prototype.run = function () {
|
|
|
409
443
|
}
|
|
410
444
|
target = Object.getPrototypeOf(target);
|
|
411
445
|
}
|
|
412
|
-
|
|
446
|
+
regs[base + dst] = result;
|
|
413
447
|
}
|
|
414
448
|
break;
|
|
415
449
|
}
|
|
416
450
|
|
|
417
|
-
//
|
|
451
|
+
// Unary (dst, src)
|
|
418
452
|
case OP.UNARY_NEG: {
|
|
419
453
|
var dst = this._operand();
|
|
420
|
-
|
|
454
|
+
regs[base + dst] = -regs[base + this._operand()];
|
|
421
455
|
break;
|
|
422
456
|
}
|
|
423
457
|
case OP.UNARY_POS: {
|
|
424
458
|
var dst = this._operand();
|
|
425
|
-
|
|
459
|
+
regs[base + dst] = +regs[base + this._operand()];
|
|
426
460
|
break;
|
|
427
461
|
}
|
|
428
462
|
case OP.UNARY_NOT: {
|
|
429
463
|
var dst = this._operand();
|
|
430
|
-
|
|
464
|
+
regs[base + dst] = !regs[base + this._operand()];
|
|
431
465
|
break;
|
|
432
466
|
}
|
|
433
467
|
case OP.UNARY_BITNOT: {
|
|
434
468
|
var dst = this._operand();
|
|
435
|
-
|
|
469
|
+
regs[base + dst] = ~regs[base + this._operand()];
|
|
436
470
|
break;
|
|
437
471
|
}
|
|
438
472
|
case OP.TYPEOF: {
|
|
439
473
|
var dst = this._operand();
|
|
440
|
-
|
|
474
|
+
regs[base + dst] = typeof regs[base + this._operand()];
|
|
441
475
|
break;
|
|
442
476
|
}
|
|
443
477
|
case OP.VOID: {
|
|
444
478
|
var dst = this._operand();
|
|
445
|
-
this._operand(); //
|
|
446
|
-
|
|
479
|
+
this._operand(); // consumes argument (intended)
|
|
480
|
+
regs[base + dst] = undefined;
|
|
447
481
|
break;
|
|
448
482
|
}
|
|
449
483
|
case OP.TYPEOF_SAFE: {
|
|
450
|
-
// dst
|
|
484
|
+
// regs[dst] = typeof window[name]
|
|
485
|
+
// Never throws ReferenceError, instead returns undefined for undeclared variables
|
|
451
486
|
var dst = this._operand();
|
|
452
487
|
var name = this._constant();
|
|
453
488
|
var val = Object.prototype.hasOwnProperty.call(this.globals, name)
|
|
454
489
|
? this.globals[name]
|
|
455
490
|
: undefined;
|
|
456
|
-
|
|
491
|
+
regs[base + dst] = typeof val;
|
|
457
492
|
break;
|
|
458
493
|
}
|
|
459
494
|
|
|
460
|
-
//
|
|
495
|
+
// Control flow
|
|
461
496
|
case OP.JUMP:
|
|
462
497
|
frame._pc = this._operand();
|
|
463
498
|
break;
|
|
@@ -465,7 +500,7 @@ VM.prototype.run = function () {
|
|
|
465
500
|
case OP.JUMP_IF_FALSE: {
|
|
466
501
|
var src = this._operand();
|
|
467
502
|
var target = this._operand();
|
|
468
|
-
if (!
|
|
503
|
+
if (!regs[base + src]) frame._pc = target;
|
|
469
504
|
break;
|
|
470
505
|
}
|
|
471
506
|
|
|
@@ -473,28 +508,48 @@ VM.prototype.run = function () {
|
|
|
473
508
|
// || short-circuit: if truthy, jump over RHS.
|
|
474
509
|
var src = this._operand();
|
|
475
510
|
var target = this._operand();
|
|
476
|
-
if (
|
|
511
|
+
if (regs[base + src]) frame._pc = target;
|
|
477
512
|
break;
|
|
478
513
|
}
|
|
479
514
|
|
|
480
|
-
//
|
|
515
|
+
// Calls
|
|
481
516
|
case OP.CALL: {
|
|
482
517
|
// dst, calleeReg, argc, [argReg...]
|
|
483
518
|
var dst = this._operand();
|
|
484
|
-
var callee =
|
|
519
|
+
var callee = regs[base + this._operand()];
|
|
485
520
|
var argc = this._operand();
|
|
486
521
|
var args = new Array(argc);
|
|
487
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
522
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
488
523
|
|
|
489
524
|
if (callee && callee[CLOSURE_SYM]) {
|
|
490
|
-
var
|
|
491
|
-
var
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
}
|
|
494
549
|
this._frameStack.push(this._currentFrame);
|
|
495
550
|
this._currentFrame = f;
|
|
496
551
|
} else {
|
|
497
|
-
|
|
552
|
+
regs[base + dst] = callee.apply(null, args);
|
|
498
553
|
}
|
|
499
554
|
break;
|
|
500
555
|
}
|
|
@@ -502,21 +557,41 @@ VM.prototype.run = function () {
|
|
|
502
557
|
case OP.CALL_METHOD: {
|
|
503
558
|
// dst, receiverReg, calleeReg, argc, [argReg...]
|
|
504
559
|
var dst = this._operand();
|
|
505
|
-
var receiver =
|
|
506
|
-
var callee =
|
|
560
|
+
var receiver = regs[base + this._operand()];
|
|
561
|
+
var callee = regs[base + this._operand()];
|
|
507
562
|
var argc = this._operand();
|
|
508
563
|
var args = new Array(argc);
|
|
509
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
564
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
510
565
|
|
|
511
566
|
if (callee && callee[CLOSURE_SYM]) {
|
|
512
|
-
var
|
|
513
|
-
var
|
|
514
|
-
|
|
515
|
-
|
|
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
|
+
}
|
|
516
591
|
this._frameStack.push(this._currentFrame);
|
|
517
592
|
this._currentFrame = f;
|
|
518
593
|
} else {
|
|
519
|
-
|
|
594
|
+
regs[base + dst] = callee.apply(receiver, args);
|
|
520
595
|
}
|
|
521
596
|
break;
|
|
522
597
|
}
|
|
@@ -524,57 +599,77 @@ VM.prototype.run = function () {
|
|
|
524
599
|
case OP.NEW: {
|
|
525
600
|
// dst, calleeReg, argc, [argReg...]
|
|
526
601
|
var dst = this._operand();
|
|
527
|
-
var callee =
|
|
602
|
+
var callee = regs[base + this._operand()];
|
|
528
603
|
var argc = this._operand();
|
|
529
604
|
var args = new Array(argc);
|
|
530
|
-
for (var i = 0; i < argc; i++) args[i] =
|
|
605
|
+
for (var i = 0; i < argc; i++) args[i] = regs[base + this._operand()];
|
|
531
606
|
|
|
532
607
|
if (callee && callee[CLOSURE_SYM]) {
|
|
533
|
-
var
|
|
534
|
-
var newObj = Object.create(
|
|
535
|
-
var
|
|
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
|
+
}
|
|
536
626
|
f._newObj = newObj;
|
|
537
|
-
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
538
|
-
f.regs[c.fn.paramCount] = args;
|
|
539
627
|
this._frameStack.push(this._currentFrame);
|
|
540
628
|
this._currentFrame = f;
|
|
541
629
|
} else {
|
|
542
630
|
// Reflect.construct is required - Object.create+apply does NOT set
|
|
543
631
|
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
544
|
-
|
|
632
|
+
regs[base + dst] = Reflect.construct(callee, args);
|
|
545
633
|
}
|
|
546
634
|
break;
|
|
547
635
|
}
|
|
548
636
|
|
|
549
637
|
case OP.RETURN: {
|
|
550
|
-
var retVal =
|
|
638
|
+
var retVal = regs[base + this._operand()];
|
|
551
639
|
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
552
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
|
+
|
|
553
647
|
if (this._frameStack.length === 0) return retVal; // main script returning
|
|
554
648
|
|
|
555
|
-
//
|
|
649
|
+
// NewExpression: When invoking from the 'new' keyword, the newly constructed object is returned instead (if the original function doesn't return an object)
|
|
556
650
|
if (frame._newObj !== null) {
|
|
557
651
|
if (typeof retVal !== "object" || retVal === null)
|
|
558
652
|
retVal = frame._newObj;
|
|
559
653
|
}
|
|
560
654
|
|
|
561
655
|
var parentFrame = this._frameStack.pop();
|
|
562
|
-
parentFrame.
|
|
656
|
+
this._regs[parentFrame._base + frame._retDstReg] = retVal;
|
|
563
657
|
this._currentFrame = parentFrame;
|
|
564
658
|
break;
|
|
565
659
|
}
|
|
566
660
|
|
|
567
661
|
case OP.THROW:
|
|
568
|
-
throw
|
|
662
|
+
throw regs[base + this._operand()];
|
|
569
663
|
|
|
570
|
-
//
|
|
664
|
+
// Closures
|
|
571
665
|
case OP.MAKE_CLOSURE: {
|
|
572
|
-
// dst, startPc, paramCount, regCount, uvCount, [isLocal, idx, ...]
|
|
666
|
+
// dst, startPc, paramCount, regCount, uvCount, hasRest, [isLocal, idx, ...]
|
|
573
667
|
var dst = this._operand();
|
|
574
668
|
var startPc = this._operand();
|
|
575
669
|
var paramCount = this._operand();
|
|
576
670
|
var regCount = this._operand();
|
|
577
671
|
var uvCount = this._operand();
|
|
672
|
+
var hasRest = this._operand(); // 1 if last param is a rest element
|
|
578
673
|
|
|
579
674
|
var uvDescs = new Array(uvCount);
|
|
580
675
|
for (var i = 0; i < uvCount; i++) {
|
|
@@ -588,6 +683,7 @@ VM.prototype.run = function () {
|
|
|
588
683
|
regCount: regCount,
|
|
589
684
|
startPc: startPc,
|
|
590
685
|
upvalueDescriptors: uvDescs,
|
|
686
|
+
hasRest: hasRest,
|
|
591
687
|
};
|
|
592
688
|
|
|
593
689
|
var closure = new Closure(fn);
|
|
@@ -620,28 +716,39 @@ VM.prototype.run = function () {
|
|
|
620
716
|
null,
|
|
621
717
|
this == null ? self.globals : this,
|
|
622
718
|
0,
|
|
719
|
+
0,
|
|
623
720
|
);
|
|
624
|
-
for (var i = 0; i < args.length; i++) f.regs[i] = args[i];
|
|
625
|
-
f.regs[c.fn.paramCount] = args;
|
|
626
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
|
+
}
|
|
627
734
|
return sub.run();
|
|
628
735
|
};
|
|
629
736
|
})(closure);
|
|
630
737
|
shell[CLOSURE_SYM] = closure;
|
|
631
738
|
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
632
|
-
|
|
739
|
+
regs[base + dst] = shell;
|
|
633
740
|
break;
|
|
634
741
|
}
|
|
635
742
|
|
|
636
|
-
//
|
|
743
|
+
// Collections
|
|
637
744
|
case OP.BUILD_ARRAY: {
|
|
638
745
|
// dst, count, [elemReg...]
|
|
639
746
|
var dst = this._operand();
|
|
640
747
|
var count = this._operand();
|
|
641
748
|
var elems = new Array(count);
|
|
642
749
|
for (var i = 0; i < count; i++)
|
|
643
|
-
elems[i] =
|
|
644
|
-
|
|
750
|
+
elems[i] = regs[base + this._operand()];
|
|
751
|
+
regs[base + dst] = elems;
|
|
645
752
|
break;
|
|
646
753
|
}
|
|
647
754
|
|
|
@@ -651,20 +758,20 @@ VM.prototype.run = function () {
|
|
|
651
758
|
var pairCount = this._operand();
|
|
652
759
|
var o = {};
|
|
653
760
|
for (var i = 0; i < pairCount; i++) {
|
|
654
|
-
var key =
|
|
655
|
-
var val =
|
|
761
|
+
var key = regs[base + this._operand()];
|
|
762
|
+
var val = regs[base + this._operand()];
|
|
656
763
|
o[key] = val;
|
|
657
764
|
}
|
|
658
|
-
|
|
765
|
+
regs[base + dst] = o;
|
|
659
766
|
break;
|
|
660
767
|
}
|
|
661
768
|
|
|
662
|
-
//
|
|
769
|
+
// Object methods (getters / setters)
|
|
663
770
|
case OP.DEFINE_GETTER: {
|
|
664
771
|
// obj, key, fn
|
|
665
|
-
var obj =
|
|
666
|
-
var key =
|
|
667
|
-
var getterFn =
|
|
772
|
+
var obj = regs[base + this._operand()];
|
|
773
|
+
var key = regs[base + this._operand()];
|
|
774
|
+
var getterFn = regs[base + this._operand()];
|
|
668
775
|
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
669
776
|
var getDesc: PropertyDescriptor = {
|
|
670
777
|
get: getterFn,
|
|
@@ -680,9 +787,9 @@ VM.prototype.run = function () {
|
|
|
680
787
|
|
|
681
788
|
case OP.DEFINE_SETTER: {
|
|
682
789
|
// obj, key, fn
|
|
683
|
-
var obj =
|
|
684
|
-
var key =
|
|
685
|
-
var setterFn =
|
|
790
|
+
var obj = regs[base + this._operand()];
|
|
791
|
+
var key = regs[base + this._operand()];
|
|
792
|
+
var setterFn = regs[base + this._operand()];
|
|
686
793
|
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
687
794
|
var setDesc: PropertyDescriptor = {
|
|
688
795
|
set: setterFn,
|
|
@@ -700,7 +807,7 @@ VM.prototype.run = function () {
|
|
|
700
807
|
case OP.FOR_IN_SETUP: {
|
|
701
808
|
// dst, src — build iterator object from enumerable keys of regs[src]
|
|
702
809
|
var dst = this._operand();
|
|
703
|
-
var obj =
|
|
810
|
+
var obj = regs[base + this._operand()];
|
|
704
811
|
var keys = [];
|
|
705
812
|
if (obj !== null && obj !== undefined) {
|
|
706
813
|
var seen = Object.create(null);
|
|
@@ -720,7 +827,7 @@ VM.prototype.run = function () {
|
|
|
720
827
|
cur = Object.getPrototypeOf(cur);
|
|
721
828
|
}
|
|
722
829
|
}
|
|
723
|
-
|
|
830
|
+
regs[base + dst] = { _keys: keys, i: 0 };
|
|
724
831
|
break;
|
|
725
832
|
}
|
|
726
833
|
|
|
@@ -728,12 +835,12 @@ VM.prototype.run = function () {
|
|
|
728
835
|
// dst, iterReg, exitTarget
|
|
729
836
|
// Advances iterator; writes next key to dst, or jumps to exitTarget when done.
|
|
730
837
|
var dst = this._operand();
|
|
731
|
-
var iter =
|
|
838
|
+
var iter = regs[base + this._operand()];
|
|
732
839
|
var exitTarget = this._operand();
|
|
733
840
|
if (iter.i >= iter._keys.length) {
|
|
734
841
|
frame._pc = exitTarget;
|
|
735
842
|
} else {
|
|
736
|
-
|
|
843
|
+
regs[base + dst] = iter._keys[iter.i++];
|
|
737
844
|
}
|
|
738
845
|
break;
|
|
739
846
|
}
|
|
@@ -755,7 +862,7 @@ VM.prototype.run = function () {
|
|
|
755
862
|
break;
|
|
756
863
|
}
|
|
757
864
|
|
|
758
|
-
//
|
|
865
|
+
// Self-modifying bytecode
|
|
759
866
|
case OP.PATCH: {
|
|
760
867
|
// destPc, sliceStart, sliceEnd
|
|
761
868
|
var destPc = this._operand();
|
|
@@ -767,6 +874,12 @@ VM.prototype.run = function () {
|
|
|
767
874
|
break;
|
|
768
875
|
}
|
|
769
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
|
+
|
|
770
883
|
case OP.DEBUGGER: {
|
|
771
884
|
debugger;
|
|
772
885
|
break;
|
|
@@ -778,9 +891,8 @@ VM.prototype.run = function () {
|
|
|
778
891
|
);
|
|
779
892
|
}
|
|
780
893
|
} catch (err) {
|
|
781
|
-
// Exception handler unwinding
|
|
782
|
-
// Walk from the current frame upward until we find a frame that has an open
|
|
783
|
-
// exception handler (TRY_SETUP without a matching TRY_END).
|
|
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).
|
|
784
896
|
// For every frame we abandon along the way, close its captured upvalues.
|
|
785
897
|
var handledFrame = null;
|
|
786
898
|
var searchFrame = this._currentFrame;
|
|
@@ -791,26 +903,28 @@ VM.prototype.run = function () {
|
|
|
791
903
|
}
|
|
792
904
|
// No handler in this frame — abandon it and walk up.
|
|
793
905
|
this._closeUpvaluesFor(searchFrame);
|
|
906
|
+
this._regsTop = searchFrame._base;
|
|
794
907
|
if (this._frameStack.length === 0) break;
|
|
795
908
|
searchFrame = this._frameStack.pop();
|
|
796
909
|
this._currentFrame = searchFrame;
|
|
797
910
|
}
|
|
798
911
|
|
|
799
|
-
if (!handledFrame) throw err; // no handler
|
|
912
|
+
if (!handledFrame) throw err; // if there's no handler, propagate back to host
|
|
800
913
|
|
|
801
914
|
var h = handledFrame._handlerStack.pop();
|
|
802
915
|
// Discard any call-frames that were pushed inside the try body.
|
|
803
916
|
this._frameStack.length = h.frameStackDepth;
|
|
804
917
|
// Write the caught exception directly into the designated register.
|
|
805
|
-
handledFrame.
|
|
918
|
+
this._regs[handledFrame._base + h.exceptionReg] = err;
|
|
806
919
|
// Jump to the catch block.
|
|
807
920
|
handledFrame._pc = h.handlerPc;
|
|
921
|
+
this._regsTop = handledFrame._base + handledFrame.closure.fn.regCount;
|
|
808
922
|
this._currentFrame = handledFrame;
|
|
809
923
|
}
|
|
810
924
|
}
|
|
811
925
|
};
|
|
812
926
|
|
|
813
|
-
//
|
|
927
|
+
/* @BOOT */ // <- This comment can't be removed!
|
|
814
928
|
var globals: any = {}; // global object for globals
|
|
815
929
|
|
|
816
930
|
// Always pull built-ins from globalThis so eval() scoping can't shadow them
|