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