js-confuser-vm 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.MD +197 -0
- package/babel-plugin-inline-runtime.cjs +34 -0
- package/babel.config.json +23 -0
- package/dist/compiler.js +1771 -0
- package/dist/index.js +10 -0
- package/dist/minify.js +18 -0
- package/dist/options.js +1 -0
- package/dist/random.js +27 -0
- package/dist/runtime.js +755 -0
- package/dist/runtimeObf.js +56 -0
- package/dist/transforms/controlFlowFlattening.js +22 -0
- package/dist/transforms/resolveContants.js +33 -0
- package/dist/transforms/resolveLabels.js +59 -0
- package/dist/transforms/selfModifying.js +107 -0
- package/dist/types.js +13 -0
- package/dist/utilts.js +3 -0
- package/index.ts +17 -12
- package/jest.config.js +26 -5
- package/package.json +13 -6
- package/src/compiler.ts +1122 -673
- package/src/index.ts +14 -0
- package/src/minify.ts +21 -0
- package/src/options.ts +12 -0
- package/src/random.ts +31 -0
- package/src/runtime.ts +609 -461
- package/src/runtimeObf.ts +62 -0
- package/src/transforms/controlFlowFlattening.ts +30 -0
- package/src/transforms/resolveContants.ts +42 -0
- package/src/transforms/resolveLabels.ts +83 -0
- package/src/transforms/selfModifying.ts +124 -0
- package/src/types.ts +24 -0
- package/src/utilts.ts +3 -0
- package/.claude/settings.local.json +0 -8
- package/ReadME.MD +0 -164
- package/input.js +0 -15
- package/minify.js +0 -17
- package/minify_empty_externs.js +0 -4
- package/obfuscate.js +0 -12
- package/src/index.js +0 -5
- package/src/random.js +0 -3
package/src/runtime.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { OP } from "./compiler.ts";
|
|
1
|
+
import { OP_ORIGINAL as OP } from "./compiler.ts";
|
|
2
2
|
const BYTECODE = [];
|
|
3
3
|
const MAIN_START_PC = 0;
|
|
4
4
|
const CONSTANTS = [];
|
|
5
|
-
const
|
|
5
|
+
const ENCODE_BYTECODE = false;
|
|
6
|
+
const TIMING_CHECKS = false;
|
|
6
7
|
// The text above is not included in the compiled output - for type intellisense only
|
|
7
8
|
// @START
|
|
8
9
|
|
|
9
10
|
function decodeBytecode(s) {
|
|
10
|
-
if (!
|
|
11
|
+
if (!ENCODE_BYTECODE) return s;
|
|
11
12
|
|
|
12
13
|
var b =
|
|
13
14
|
typeof Buffer !== "undefined"
|
|
@@ -25,64 +26,65 @@ function decodeBytecode(s) {
|
|
|
25
26
|
return r;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
+
// Closure symbol
|
|
29
30
|
// Used to tag shell functions so the VM can fast-path back to the
|
|
30
31
|
// inner Closure instead of going through a sub-VM on internal calls.
|
|
31
32
|
var CLOSURE_SYM = Symbol(); // Nameless for obfuscation
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// Upvalue
|
|
34
35
|
// While the outer frame is alive: reads/writes go to frame.locals[slot].
|
|
35
36
|
// After the outer frame returns (closed): reads/writes hit this.value.
|
|
36
37
|
function Upvalue(frame, slot) {
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
38
|
+
this._frame = frame;
|
|
39
|
+
this._slot = slot;
|
|
39
40
|
this._closed = false;
|
|
40
41
|
this._value = undefined;
|
|
41
42
|
}
|
|
42
|
-
Upvalue.prototype.
|
|
43
|
-
return this._closed ? this._value : this.
|
|
43
|
+
Upvalue.prototype._read = function () {
|
|
44
|
+
return this._closed ? this._value : this._frame.locals[this._slot];
|
|
44
45
|
};
|
|
45
|
-
Upvalue.prototype.
|
|
46
|
+
Upvalue.prototype._write = function (v) {
|
|
46
47
|
if (this._closed) this._value = v;
|
|
47
|
-
else this.
|
|
48
|
+
else this._frame.locals[this._slot] = v;
|
|
48
49
|
};
|
|
49
|
-
Upvalue.prototype.
|
|
50
|
-
this._value = this.
|
|
50
|
+
Upvalue.prototype._close = function () {
|
|
51
|
+
this._value = this._frame.locals[this._slot];
|
|
51
52
|
this._closed = true;
|
|
52
53
|
};
|
|
53
54
|
|
|
54
|
-
//
|
|
55
|
+
// Closure & Frame
|
|
55
56
|
function Closure(fn) {
|
|
56
57
|
this.fn = fn;
|
|
57
58
|
this.upvalues = [];
|
|
58
|
-
this.prototype = {}; //
|
|
59
|
+
this.prototype = {}; // <- default prototype object for \`new\`
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
function Frame(closure, returnPc, parent, thisVal?) {
|
|
62
63
|
this.closure = closure;
|
|
63
64
|
this.locals = new Array(closure.fn.localCount).fill(undefined);
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
65
|
+
this._pc = closure.fn.startPc; // <- initialize from fn descriptor
|
|
66
|
+
this._returnPc = returnPc; // pc to resume in parent frame after RETURN
|
|
67
|
+
this._parent = parent;
|
|
67
68
|
this.thisVal = thisVal !== undefined ? thisVal : undefined;
|
|
68
|
-
this._newObj = null; //
|
|
69
|
+
this._newObj = null; // <- set by NEW so RETURN can see it
|
|
70
|
+
this._handlerStack = []; // <- exception handlers pushed by TRY_SETUP
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
//
|
|
73
|
+
// VM
|
|
72
74
|
function VM(bytecode, mainStartPc, constants, globals) {
|
|
73
75
|
this.bytecode = bytecode;
|
|
74
76
|
this.constants = constants;
|
|
75
77
|
this.globals = globals;
|
|
76
78
|
this._stack = [];
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
79
|
+
this._frameStack = [];
|
|
80
|
+
this._openUpvalues = []; // all currently open Upvalue objects across all frames
|
|
79
81
|
|
|
80
82
|
var mainFn = {
|
|
81
83
|
paramCount: 0,
|
|
82
84
|
localCount: 0,
|
|
83
|
-
startPc: mainStartPc, //
|
|
85
|
+
startPc: mainStartPc, // <- where main begins
|
|
84
86
|
};
|
|
85
|
-
this.
|
|
87
|
+
this._currentFrame = new Frame(new Closure(mainFn), null, null);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
VM.prototype._push = function (v) {
|
|
@@ -95,24 +97,36 @@ VM.prototype.peek = function () {
|
|
|
95
97
|
return this._stack[this._stack.length - 1];
|
|
96
98
|
};
|
|
97
99
|
|
|
100
|
+
// Read one instruction word from this.bytecode at `pc`, unwrapping the
|
|
101
|
+
// encoding so callers always get a plain { op, operand } pair regardless
|
|
102
|
+
// of whether ENCODE_BYTECODE is active.
|
|
103
|
+
VM.prototype.readWord = function (pc) {
|
|
104
|
+
var word = this.bytecode[pc];
|
|
105
|
+
if (ENCODE_BYTECODE) {
|
|
106
|
+
return { op: word & 0xff, operand: word >>> 8 };
|
|
107
|
+
} else {
|
|
108
|
+
return { op: word[0], operand: word[1] };
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
98
112
|
VM.prototype.captureUpvalue = function (frame, slot) {
|
|
99
113
|
// Reuse existing open upvalue for this frame+slot if one exists.
|
|
100
114
|
// This is what makes two closures share the same mutable cell.
|
|
101
|
-
for (var i = 0; i < this.
|
|
102
|
-
var uv = this.
|
|
103
|
-
if (uv.
|
|
115
|
+
for (var i = 0; i < this._openUpvalues.length; i++) {
|
|
116
|
+
var uv = this._openUpvalues[i];
|
|
117
|
+
if (uv._frame === frame && uv._slot === slot) return uv;
|
|
104
118
|
}
|
|
105
119
|
var uv = new Upvalue(frame, slot);
|
|
106
|
-
this.
|
|
120
|
+
this._openUpvalues.push(uv);
|
|
107
121
|
return uv;
|
|
108
122
|
};
|
|
109
123
|
|
|
110
|
-
VM.prototype.
|
|
111
|
-
// Called on RETURN
|
|
124
|
+
VM.prototype._closeUpvaluesFor = function (frame) {
|
|
125
|
+
// Called on RETURN - close every upvalue that was pointing into this frame.
|
|
112
126
|
// After this, closures that captured from the frame read from upvalue.value.
|
|
113
|
-
this.
|
|
114
|
-
if (uv.
|
|
115
|
-
uv.
|
|
127
|
+
this._openUpvalues = this._openUpvalues.filter(function (uv) {
|
|
128
|
+
if (uv._frame === frame) {
|
|
129
|
+
uv._close();
|
|
116
130
|
return false;
|
|
117
131
|
}
|
|
118
132
|
return true;
|
|
@@ -127,488 +141,622 @@ VM.prototype.run = function () {
|
|
|
127
141
|
var t = now();
|
|
128
142
|
|
|
129
143
|
while (true) {
|
|
130
|
-
var frame = this.
|
|
144
|
+
var frame = this._currentFrame;
|
|
131
145
|
var bc = this.bytecode;
|
|
132
|
-
if (frame.
|
|
146
|
+
if (frame._pc >= bc.length) break;
|
|
133
147
|
|
|
134
148
|
var op, operand;
|
|
135
|
-
var word =
|
|
136
|
-
|
|
137
|
-
if (PACK) {
|
|
138
|
-
op = word & 0xff;
|
|
139
|
-
operand = word >>> 8;
|
|
140
|
-
} else {
|
|
141
|
-
op = word[0];
|
|
142
|
-
operand = word[1];
|
|
143
|
-
}
|
|
149
|
+
var word = this.readWord(frame._pc++);
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
op = word.op;
|
|
152
|
+
operand = word.operand;
|
|
153
|
+
|
|
154
|
+
// console.log(frame._pc - 1, op, operand);
|
|
146
155
|
|
|
147
156
|
// Debugging protection
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
if (TIMING_CHECKS) {
|
|
158
|
+
var t2 = now();
|
|
159
|
+
var isTamper = t2 - t > 1000;
|
|
160
|
+
t = t2;
|
|
161
|
+
if (isTamper) {
|
|
162
|
+
op = OP.POP;
|
|
163
|
+
}
|
|
153
164
|
}
|
|
154
165
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
try {
|
|
167
|
+
/* @SWITCH */
|
|
168
|
+
switch (op) {
|
|
169
|
+
case OP.LOAD_CONST:
|
|
170
|
+
this._push(this.constants[operand]);
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
case OP.LOAD_INT:
|
|
174
|
+
this._push(operand);
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case OP.LOAD_LOCAL:
|
|
178
|
+
this._push(frame.locals[operand]);
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
case OP.STORE_LOCAL:
|
|
182
|
+
frame.locals[operand] = this._pop();
|
|
183
|
+
break;
|
|
184
|
+
|
|
185
|
+
case OP.LOAD_GLOBAL:
|
|
186
|
+
this._push(this.globals[this.constants[operand]]);
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
case OP.STORE_GLOBAL:
|
|
190
|
+
this.globals[this.constants[operand]] = this._pop();
|
|
191
|
+
break;
|
|
192
|
+
|
|
193
|
+
case OP.GET_PROP: {
|
|
194
|
+
// Stack: [..., obj, key] -> [..., obj, obj[key]]
|
|
195
|
+
// obj is PEEKED (not popped) - CALL_METHOD needs it as receiver
|
|
196
|
+
var key = this._pop();
|
|
197
|
+
var obj = this.peek();
|
|
198
|
+
this._push(obj[key]);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
185
201
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
202
|
+
case OP.ADD: {
|
|
203
|
+
var b = this._pop();
|
|
204
|
+
this._push(this._pop() + b);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case OP.SUB: {
|
|
208
|
+
var b = this._pop();
|
|
209
|
+
this._push(this._pop() - b);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case OP.MUL: {
|
|
213
|
+
var b = this._pop();
|
|
214
|
+
this._push(this._pop() * b);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case OP.DIV: {
|
|
218
|
+
var b = this._pop();
|
|
219
|
+
this._push(this._pop() / b);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case OP.MOD: {
|
|
223
|
+
var b = this._pop();
|
|
224
|
+
this._push(this._pop() % b);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case OP.BAND: {
|
|
228
|
+
var b = this._pop();
|
|
229
|
+
this._push(this._pop() & b);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case OP.BOR: {
|
|
233
|
+
var b = this._pop();
|
|
234
|
+
this._push(this._pop() | b);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case OP.BXOR: {
|
|
238
|
+
var b = this._pop();
|
|
239
|
+
this._push(this._pop() ^ b);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case OP.SHL: {
|
|
243
|
+
var b = this._pop();
|
|
244
|
+
this._push(this._pop() << b);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case OP.SHR: {
|
|
248
|
+
var b = this._pop();
|
|
249
|
+
this._push(this._pop() >> b);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case OP.USHR: {
|
|
253
|
+
var b = this._pop();
|
|
254
|
+
this._push(this._pop() >>> b);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
241
257
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
case OP.LT: {
|
|
259
|
+
var b = this._pop();
|
|
260
|
+
this._push(this._pop() < b);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case OP.GT: {
|
|
264
|
+
var b = this._pop();
|
|
265
|
+
this._push(this._pop() > b);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case OP.EQ: {
|
|
269
|
+
var b = this._pop();
|
|
270
|
+
this._push(this._pop() === b);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
257
273
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
274
|
+
case OP.LTE: {
|
|
275
|
+
var b = this._pop();
|
|
276
|
+
this._push(this._pop() <= b);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
case OP.GTE: {
|
|
280
|
+
var b = this._pop();
|
|
281
|
+
this._push(this._pop() >= b);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case OP.NEQ: {
|
|
285
|
+
var b = this._pop();
|
|
286
|
+
this._push(this._pop() !== b);
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case OP.LOOSE_EQ: {
|
|
290
|
+
var b = this._pop();
|
|
291
|
+
this._push(this._pop() == b);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case OP.LOOSE_NEQ: {
|
|
295
|
+
var b = this._pop();
|
|
296
|
+
this._push(this._pop() != b);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
283
299
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
300
|
+
case OP.IN: {
|
|
301
|
+
var b = this._pop();
|
|
302
|
+
this._push(this._pop() in b);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
289
305
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
case OP.INSTANCEOF: {
|
|
307
|
+
var ctor = this._pop();
|
|
308
|
+
var obj = this._pop();
|
|
309
|
+
if (typeof ctor === "function") {
|
|
310
|
+
// Native constructor (e.g. Array, Date) - native instanceof is fine
|
|
311
|
+
this._push(obj instanceof ctor);
|
|
312
|
+
} else {
|
|
313
|
+
// VM Closure - ctor.prototype was set by MAKE_CLOSURE / user assignment.
|
|
314
|
+
// Walk obj's prototype chain looking for identity with ctor.prototype.
|
|
315
|
+
var proto = ctor.prototype; // the .prototype property on the Closure
|
|
316
|
+
var target = Object.getPrototypeOf(obj);
|
|
317
|
+
var result = false;
|
|
318
|
+
while (target !== null) {
|
|
319
|
+
if (target === proto) {
|
|
320
|
+
result = true;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
target = Object.getPrototypeOf(target);
|
|
306
324
|
}
|
|
307
|
-
|
|
325
|
+
this._push(result);
|
|
308
326
|
}
|
|
309
|
-
|
|
327
|
+
break;
|
|
310
328
|
}
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
329
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
330
|
+
case OP.UNARY_NEG:
|
|
331
|
+
this._push(-this._pop());
|
|
332
|
+
break;
|
|
333
|
+
case OP.UNARY_POS:
|
|
334
|
+
this._push(this._pop());
|
|
335
|
+
break;
|
|
336
|
+
case OP.UNARY_NOT:
|
|
337
|
+
this._push(!this._pop());
|
|
338
|
+
break;
|
|
339
|
+
case OP.UNARY_BITNOT:
|
|
340
|
+
this._push(~this._pop());
|
|
341
|
+
break;
|
|
342
|
+
case OP.TYPEOF:
|
|
343
|
+
this._push(typeof this._pop());
|
|
344
|
+
break;
|
|
345
|
+
case OP.VOID:
|
|
346
|
+
this._pop();
|
|
347
|
+
this._push(undefined);
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case OP.TYPEOF_SAFE: {
|
|
351
|
+
// operand is a const index holding the variable name string.
|
|
352
|
+
// Mimics JS semantics: typeof undeclaredVar === "undefined" (no throw).
|
|
353
|
+
var name = this._pop(); // LOAD_CONST pushed the name - consume it
|
|
354
|
+
var val = Object.prototype.hasOwnProperty.call(this.globals, name)
|
|
355
|
+
? this.globals[name]
|
|
356
|
+
: undefined;
|
|
357
|
+
this._push(typeof val);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
344
360
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
361
|
+
case OP.JUMP:
|
|
362
|
+
frame._pc = operand;
|
|
363
|
+
break;
|
|
348
364
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
365
|
+
case OP.JUMP_IF_FALSE:
|
|
366
|
+
if (!this._pop()) frame._pc = operand;
|
|
367
|
+
break;
|
|
352
368
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
369
|
+
case OP.JUMP_IF_TRUE_OR_POP:
|
|
370
|
+
// || semantics: if truthy, we're done - leave value, jump over RHS.
|
|
371
|
+
// If falsy, discard it and fall through to evaluate RHS.
|
|
372
|
+
if (this.peek()) {
|
|
373
|
+
frame._pc = operand;
|
|
374
|
+
} else {
|
|
375
|
+
this._pop();
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
378
|
+
|
|
379
|
+
case OP.JUMP_IF_FALSE_OR_POP:
|
|
380
|
+
// && semantics: if falsy, we're done - leave value, jump over RHS.
|
|
381
|
+
// If truthy, discard it and fall through to evaluate RHS.
|
|
382
|
+
if (!this.peek()) {
|
|
383
|
+
frame._pc = operand;
|
|
384
|
+
} else {
|
|
385
|
+
this._pop();
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case OP.MAKE_CLOSURE: {
|
|
390
|
+
// operand = startPc: absolute index of the function body's first instruction.
|
|
391
|
+
// Metadata is read from the value stack (pushed by _emitClosureMetadata).
|
|
392
|
+
// Stack layout when we arrive here (top is rightmost):
|
|
393
|
+
// [isLocal_0, idx_0, ..., isLocal_N-1, idx_N-1, uvCount, localCount, paramCount]
|
|
394
|
+
var startPc = operand;
|
|
395
|
+
var paramCount = this._pop();
|
|
396
|
+
var localCount = this._pop();
|
|
397
|
+
var uvCount = this._pop();
|
|
398
|
+
|
|
399
|
+
// Upvalues were pushed in order 0..N-1 so we pop them in reverse.
|
|
400
|
+
var uvDescs = new Array(uvCount);
|
|
401
|
+
for (var i = uvCount - 1; i >= 0; i--) {
|
|
402
|
+
var uvIndex = this._pop();
|
|
403
|
+
var isLocalRaw = this._pop();
|
|
404
|
+
uvDescs[i] = { isLocal: isLocalRaw, _index: uvIndex };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
var fn = {
|
|
408
|
+
paramCount: paramCount,
|
|
409
|
+
localCount: localCount,
|
|
410
|
+
startPc: startPc,
|
|
411
|
+
upvalueDescriptors: uvDescs,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
var closure = new Closure(fn);
|
|
415
|
+
for (var i = 0; i < uvDescs.length; i++) {
|
|
416
|
+
var uvd = uvDescs[i];
|
|
417
|
+
if (uvd.isLocal) {
|
|
418
|
+
// Capture directly from current frame's local slot
|
|
419
|
+
closure.upvalues.push(this.captureUpvalue(frame, uvd._index));
|
|
420
|
+
} else {
|
|
421
|
+
// Relay - take upvalue from the enclosing closure's list
|
|
422
|
+
closure.upvalues.push(frame.closure.upvalues[uvd._index]);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Wrap in a native callable shell so host code (array methods,
|
|
426
|
+
// test assertions, setTimeout, etc.) can invoke VM closures.
|
|
427
|
+
// CLOSURE_SYM lets VM-internal CALL/NEW bypass the sub-VM entirely.
|
|
428
|
+
var self = this;
|
|
429
|
+
var shell = (function (c) {
|
|
430
|
+
return function () {
|
|
431
|
+
var args = Array.prototype.slice.call(arguments);
|
|
432
|
+
var sub = new VM(self.bytecode, 0, self.constants, self.globals);
|
|
433
|
+
// Sloppy-mode: null/undefined thisArg → global object
|
|
434
|
+
var f = new Frame(
|
|
435
|
+
c,
|
|
436
|
+
null,
|
|
437
|
+
null,
|
|
438
|
+
this == null ? self.globals : this,
|
|
439
|
+
);
|
|
440
|
+
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
441
|
+
f.locals[c.fn.paramCount] = args;
|
|
442
|
+
sub._currentFrame = f;
|
|
443
|
+
return sub.run();
|
|
444
|
+
};
|
|
445
|
+
})(closure);
|
|
446
|
+
shell[CLOSURE_SYM] = closure;
|
|
447
|
+
shell.prototype = closure.prototype; // unified prototype for new/instanceof
|
|
448
|
+
this._push(shell);
|
|
449
|
+
break;
|
|
360
450
|
}
|
|
361
|
-
break;
|
|
362
451
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
452
|
+
case OP.DATA:
|
|
453
|
+
// Should never appear in compiled output (reserved opcode slot).
|
|
454
|
+
throw new Error("DATA opcode executed at pc " + (frame._pc - 1));
|
|
455
|
+
|
|
456
|
+
case OP.LOAD_UPVALUE:
|
|
457
|
+
this._push(frame.closure.upvalues[operand]._read());
|
|
458
|
+
break;
|
|
459
|
+
|
|
460
|
+
case OP.STORE_UPVALUE:
|
|
461
|
+
frame.closure.upvalues[operand]._write(this._pop());
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
case OP.BUILD_ARRAY: {
|
|
465
|
+
// Pop \`operand\` values off the stack in reverse, assemble array.
|
|
466
|
+
var elems = this._stack.splice(this._stack.length - operand);
|
|
467
|
+
this._push(elems);
|
|
468
|
+
break;
|
|
370
469
|
}
|
|
371
|
-
break;
|
|
372
470
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
var
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
|
|
471
|
+
case OP.BUILD_OBJECT: {
|
|
472
|
+
// Stack has: key0, val0, key1, val1 ... keyN, valN (pushed left->right)
|
|
473
|
+
// Pop all pairs and build the object.
|
|
474
|
+
var pairs = this._stack.splice(this._stack.length - operand * 2);
|
|
475
|
+
var o = {};
|
|
476
|
+
for (var i = 0; i < pairs.length; i += 2) {
|
|
477
|
+
o[pairs[i]] = pairs[i + 1]; // key at even index, val at odd
|
|
478
|
+
}
|
|
479
|
+
this._push(o);
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case OP.SET_PROP: {
|
|
483
|
+
// Stack: [..., obj, key, val]
|
|
484
|
+
// Leaves val on stack - assignment is an expression in JS.
|
|
485
|
+
var val = this._pop();
|
|
486
|
+
var key = this._pop();
|
|
487
|
+
var obj = this._pop();
|
|
488
|
+
// Reflect.set performs [[Set]] without throwing on failure,
|
|
489
|
+
// correctly simulating sloppy-mode assignment from a strict-mode host
|
|
490
|
+
// (output.js is an ES module). This also properly invokes inherited
|
|
491
|
+
// or prototype-chain setter functions.
|
|
492
|
+
Reflect.set(obj, key, val);
|
|
493
|
+
this._push(val); // assignment expression evaluates to the assigned value
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case OP.GET_PROP_COMPUTED: {
|
|
497
|
+
// Stack: [..., obj, key] - key is a runtime value (nums[i])
|
|
498
|
+
// Mirrors GET_PROP but pops the key that was pushed dynamically.
|
|
499
|
+
var key = this._pop();
|
|
500
|
+
var obj = this._pop();
|
|
501
|
+
this._push(obj[key]);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
case OP.DELETE_PROP: {
|
|
505
|
+
var key = this._pop();
|
|
506
|
+
var obj = this._pop();
|
|
507
|
+
this._push(delete obj[key]);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
case OP.CALL: {
|
|
512
|
+
var args = this._stack.splice(this._stack.length - operand);
|
|
513
|
+
var callee = this._pop();
|
|
514
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
515
|
+
// VM closure - run directly in this VM, no sub-VM overhead
|
|
516
|
+
var c = callee[CLOSURE_SYM];
|
|
517
|
+
// Sloppy-mode: plain function call → global object as this
|
|
518
|
+
var f = new Frame(c, frame._pc, frame, this.globals);
|
|
519
|
+
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
520
|
+
f.locals[c.fn.paramCount] = args;
|
|
521
|
+
this._frameStack.push(this._currentFrame);
|
|
522
|
+
this._currentFrame = f;
|
|
381
523
|
} else {
|
|
382
|
-
//
|
|
383
|
-
|
|
524
|
+
// Native function
|
|
525
|
+
this._push(callee.apply(null, args));
|
|
384
526
|
}
|
|
527
|
+
break;
|
|
385
528
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
var
|
|
394
|
-
var f = new Frame(c,
|
|
529
|
+
|
|
530
|
+
case OP.CALL_METHOD: {
|
|
531
|
+
var args = this._stack.splice(this._stack.length - operand);
|
|
532
|
+
var callee = this._pop();
|
|
533
|
+
var receiver = this._pop(); // left on stack by GET_PROP
|
|
534
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
535
|
+
// VM closure - run directly in this VM with receiver as this
|
|
536
|
+
var c = callee[CLOSURE_SYM];
|
|
537
|
+
var f = new Frame(c, frame._pc, frame, receiver);
|
|
395
538
|
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
396
539
|
f.locals[c.fn.paramCount] = args;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
540
|
+
this._frameStack.push(this._currentFrame);
|
|
541
|
+
this._currentFrame = f;
|
|
542
|
+
} else {
|
|
543
|
+
// Native method
|
|
544
|
+
this._push(callee.apply(receiver, args));
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
406
548
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
549
|
+
case OP.LOAD_THIS:
|
|
550
|
+
this._push(frame.thisVal);
|
|
551
|
+
break;
|
|
552
|
+
|
|
553
|
+
case OP.NEW: {
|
|
554
|
+
var args = this._stack.splice(this._stack.length - operand);
|
|
555
|
+
var callee = this._pop();
|
|
556
|
+
if (callee && callee[CLOSURE_SYM]) {
|
|
557
|
+
// VM closure constructor - prototype is unified via shell.prototype = closure.prototype
|
|
558
|
+
var c = callee[CLOSURE_SYM];
|
|
559
|
+
var newObj = Object.create(c.prototype || null);
|
|
560
|
+
var f = new Frame(c, frame._pc, frame, newObj);
|
|
561
|
+
f._newObj = newObj;
|
|
562
|
+
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
563
|
+
f.locals[c.fn.paramCount] = args;
|
|
564
|
+
this._frameStack.push(this._currentFrame);
|
|
565
|
+
this._currentFrame = f;
|
|
566
|
+
} else {
|
|
567
|
+
// Native constructor (e.g. new Error(), new Date()).
|
|
568
|
+
// Reflect.construct is required - Object.create+apply does NOT set
|
|
569
|
+
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
570
|
+
this._push(Reflect.construct(callee, args));
|
|
571
|
+
}
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
410
574
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
575
|
+
case OP.RETURN: {
|
|
576
|
+
var retVal = this._pop();
|
|
577
|
+
this._closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
578
|
+
if (this._frameStack.length === 0) return retVal;
|
|
414
579
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
580
|
+
// new-call rule: primitive return -> discard, use the constructed object instead
|
|
581
|
+
if (frame._newObj !== null) {
|
|
582
|
+
if (typeof retVal !== "object" || retVal === null)
|
|
583
|
+
retVal = frame._newObj;
|
|
584
|
+
}
|
|
421
585
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
var pairs = this._stack.splice(this._stack.length - operand * 2);
|
|
426
|
-
var o = {};
|
|
427
|
-
for (var i = 0; i < pairs.length; i += 2) {
|
|
428
|
-
o[pairs[i]] = pairs[i + 1]; // key at even index, val at odd
|
|
586
|
+
this._currentFrame = this._frameStack.pop();
|
|
587
|
+
this._push(retVal);
|
|
588
|
+
break;
|
|
429
589
|
}
|
|
430
|
-
this._push(o);
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
case OP.SET_PROP: {
|
|
434
|
-
// Stack: [..., obj, key, val]
|
|
435
|
-
// Leaves val on stack — assignment is an expression in JS.
|
|
436
|
-
var val = this._pop();
|
|
437
|
-
var key = this._pop();
|
|
438
|
-
var obj = this._pop();
|
|
439
|
-
obj[key] = val;
|
|
440
|
-
this._push(val); // assignment expression evaluates to the assigned value
|
|
441
|
-
break;
|
|
442
|
-
}
|
|
443
|
-
case OP.GET_PROP_COMPUTED: {
|
|
444
|
-
// Stack: [..., obj, key] — key is a runtime value (nums[i])
|
|
445
|
-
// Mirrors GET_PROP but pops the key that was pushed dynamically.
|
|
446
|
-
var key = this._pop();
|
|
447
|
-
var obj = this._pop();
|
|
448
|
-
this._push(obj[key]);
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
case OP.DELETE_PROP: {
|
|
452
|
-
var key = this._pop();
|
|
453
|
-
var obj = this._pop();
|
|
454
|
-
this._push(delete obj[key]);
|
|
455
|
-
break;
|
|
456
|
-
}
|
|
457
590
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
this.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
591
|
+
case OP.POP:
|
|
592
|
+
this._pop();
|
|
593
|
+
break;
|
|
594
|
+
|
|
595
|
+
case OP.DUP:
|
|
596
|
+
this._push(this.peek());
|
|
597
|
+
break;
|
|
598
|
+
|
|
599
|
+
case OP.THROW:
|
|
600
|
+
throw this._pop();
|
|
601
|
+
|
|
602
|
+
case OP.FOR_IN_SETUP: {
|
|
603
|
+
// Pop the object; build an ordered list of all enumerable own+inherited
|
|
604
|
+
// string keys by walking the prototype chain manually.
|
|
605
|
+
// Uses getOwnPropertyNames (includes non-enumerable) + descriptor check,
|
|
606
|
+
// so we never rely on Object.keys() and we handle inheritance correctly.
|
|
607
|
+
var obj = this._pop();
|
|
608
|
+
var keys = [];
|
|
609
|
+
if (obj !== null && obj !== undefined) {
|
|
610
|
+
var seen = Object.create(null);
|
|
611
|
+
var cur = Object(obj); // box primitives
|
|
612
|
+
while (cur !== null) {
|
|
613
|
+
var ownNames = Object.getOwnPropertyNames(cur);
|
|
614
|
+
for (var i = 0; i < ownNames.length; i++) {
|
|
615
|
+
var k = ownNames[i];
|
|
616
|
+
if (!(k in seen)) {
|
|
617
|
+
seen[k] = true;
|
|
618
|
+
var propDesc = Object.getOwnPropertyDescriptor(cur, k);
|
|
619
|
+
if (propDesc && propDesc.enumerable) {
|
|
620
|
+
keys.push(k);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
cur = Object.getPrototypeOf(cur);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
this._push({ _keys: keys, i: 0 });
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
475
630
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
this.currentFrame = f;
|
|
488
|
-
} else {
|
|
489
|
-
// Native method
|
|
490
|
-
this._push(callee.apply(receiver, args));
|
|
491
|
-
}
|
|
492
|
-
break;
|
|
493
|
-
}
|
|
631
|
+
case OP.FOR_IN_NEXT: {
|
|
632
|
+
// operand = jump target for the done case.
|
|
633
|
+
// Pop the iterator; if exhausted jump to exit, otherwise push next key.
|
|
634
|
+
var iter = this._pop();
|
|
635
|
+
if (iter.i >= iter._keys.length) {
|
|
636
|
+
frame._pc = operand;
|
|
637
|
+
} else {
|
|
638
|
+
this._push(iter._keys[iter.i++]);
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
494
642
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
// VM closure constructor — prototype is unified via shell.prototype = closure.prototype
|
|
504
|
-
var c = callee[CLOSURE_SYM];
|
|
505
|
-
var newObj = Object.create(c.prototype || null);
|
|
506
|
-
var f = new Frame(c, frame.pc, frame, newObj);
|
|
507
|
-
f._newObj = newObj;
|
|
508
|
-
for (var i = 0; i < args.length; i++) f.locals[i] = args[i];
|
|
509
|
-
f.locals[c.fn.paramCount] = args;
|
|
510
|
-
this.frameStack.push(this.currentFrame);
|
|
511
|
-
this.currentFrame = f;
|
|
512
|
-
} else {
|
|
513
|
-
// Native constructor (e.g. new Error(), new Date()).
|
|
514
|
-
// Reflect.construct is required — Object.create+apply does NOT set
|
|
515
|
-
// internal slots ([[NumberData]], [[StringData]], etc.) for built-ins.
|
|
516
|
-
this._push(Reflect.construct(callee, args));
|
|
517
|
-
}
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
643
|
+
case OP.PATCH: {
|
|
644
|
+
// Writes at operand the bytecode[arg1:arg2]
|
|
645
|
+
var destPc = operand;
|
|
646
|
+
var instructions = this.bytecode.slice(this._pop(), this._pop());
|
|
647
|
+
|
|
648
|
+
for (var i = 0; i < instructions.length; i++) {
|
|
649
|
+
this.bytecode[destPc + i] = instructions[i];
|
|
650
|
+
}
|
|
520
651
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
this.closeUpvaluesFor(frame); // must happen before frame is abandoned
|
|
524
|
-
if (this.frameStack.length === 0) return retVal;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
525
654
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
655
|
+
case OP.TRY_SETUP: {
|
|
656
|
+
// Push an exception handler record onto the current frame.
|
|
657
|
+
// Saves: catch PC (operand), current stack depth, current frame-stack depth.
|
|
658
|
+
// If an exception is thrown before TRY_END fires, the VM jumps here.
|
|
659
|
+
frame._handlerStack.push({
|
|
660
|
+
handlerPc: operand,
|
|
661
|
+
stackDepth: this._stack.length,
|
|
662
|
+
frameStackDepth: this._frameStack.length,
|
|
663
|
+
});
|
|
664
|
+
break;
|
|
530
665
|
}
|
|
531
666
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
667
|
+
case OP.TRY_END: {
|
|
668
|
+
// Normal exit from a try block — disarm the exception handler.
|
|
669
|
+
frame._handlerStack.pop();
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
536
672
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
// so we never rely on Object.keys() and we handle inheritance correctly.
|
|
553
|
-
var obj = this._pop();
|
|
554
|
-
var keys = [];
|
|
555
|
-
if (obj !== null && obj !== undefined) {
|
|
556
|
-
var seen = Object.create(null);
|
|
557
|
-
var cur = Object(obj); // box primitives
|
|
558
|
-
while (cur !== null) {
|
|
559
|
-
var ownNames = Object.getOwnPropertyNames(cur);
|
|
560
|
-
for (var i = 0; i < ownNames.length; i++) {
|
|
561
|
-
var k = ownNames[i];
|
|
562
|
-
if (!(k in seen)) {
|
|
563
|
-
seen[k] = true;
|
|
564
|
-
var propDesc = Object.getOwnPropertyDescriptor(cur, k);
|
|
565
|
-
if (propDesc && propDesc.enumerable) {
|
|
566
|
-
keys.push(k);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
cur = Object.getPrototypeOf(cur);
|
|
673
|
+
case OP.DEFINE_GETTER: {
|
|
674
|
+
// Stack: [..., obj, key, getterFn]
|
|
675
|
+
// Pops all three; defines an enumerable, configurable getter on obj.
|
|
676
|
+
// If a setter was already defined for this key, it is preserved.
|
|
677
|
+
var getterFn = this._pop();
|
|
678
|
+
var key = this._pop();
|
|
679
|
+
var obj = this._pop();
|
|
680
|
+
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
681
|
+
var getDesc: PropertyDescriptor = {
|
|
682
|
+
get: getterFn,
|
|
683
|
+
configurable: true,
|
|
684
|
+
enumerable: true,
|
|
685
|
+
};
|
|
686
|
+
if (existingDesc && typeof existingDesc.set === "function") {
|
|
687
|
+
getDesc.set = existingDesc.set;
|
|
571
688
|
}
|
|
689
|
+
Object.defineProperty(obj, key, getDesc);
|
|
690
|
+
break;
|
|
572
691
|
}
|
|
573
|
-
this._push({ keys: keys, i: 0 });
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
692
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
693
|
+
case OP.DEFINE_SETTER: {
|
|
694
|
+
// Stack: [..., obj, key, setterFn]
|
|
695
|
+
// Pops all three; defines an enumerable, configurable setter on obj.
|
|
696
|
+
// If a getter was already defined for this key, it is preserved.
|
|
697
|
+
var setterFn = this._pop();
|
|
698
|
+
var key = this._pop();
|
|
699
|
+
var obj = this._pop();
|
|
700
|
+
var existingDesc = Object.getOwnPropertyDescriptor(obj, key);
|
|
701
|
+
var setDesc: PropertyDescriptor = {
|
|
702
|
+
set: setterFn,
|
|
703
|
+
configurable: true,
|
|
704
|
+
enumerable: true,
|
|
705
|
+
};
|
|
706
|
+
if (existingDesc && typeof existingDesc.get === "function") {
|
|
707
|
+
setDesc.get = existingDesc.get;
|
|
708
|
+
}
|
|
709
|
+
Object.defineProperty(obj, key, setDesc);
|
|
710
|
+
break;
|
|
585
711
|
}
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
712
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
var destPc = this._pop();
|
|
593
|
-
var words = this.constants[operand];
|
|
594
|
-
|
|
595
|
-
if (PACK) {
|
|
596
|
-
words = decodeBytecode(words);
|
|
713
|
+
case OP.DEBUGGER: {
|
|
714
|
+
debugger;
|
|
715
|
+
break;
|
|
597
716
|
}
|
|
598
717
|
|
|
599
|
-
|
|
600
|
-
|
|
718
|
+
default:
|
|
719
|
+
throw new Error(
|
|
720
|
+
"Unknown opcode: " + op + " at pc " + (frame._pc - 1),
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
} catch (err) {
|
|
724
|
+
// Exception handler unwinding (CPython-style frame walk, Lua-style upvalue close).
|
|
725
|
+
// Walk from the current frame upward until we find a frame that has an open
|
|
726
|
+
// exception handler (TRY_SETUP without a matching TRY_END).
|
|
727
|
+
// For every frame we abandon along the way, close its captured upvalues.
|
|
728
|
+
var handledFrame = null;
|
|
729
|
+
var searchFrame = this._currentFrame;
|
|
730
|
+
while (true) {
|
|
731
|
+
if (searchFrame._handlerStack.length > 0) {
|
|
732
|
+
handledFrame = searchFrame;
|
|
733
|
+
break;
|
|
601
734
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
735
|
+
// No handler in this frame — abandon it and walk up.
|
|
736
|
+
this._closeUpvaluesFor(searchFrame);
|
|
737
|
+
if (this._frameStack.length === 0) break;
|
|
738
|
+
searchFrame = this._frameStack.pop();
|
|
739
|
+
this._currentFrame = searchFrame;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (!handledFrame) throw err; // no handler anywhere — propagate to host
|
|
743
|
+
|
|
744
|
+
var h = handledFrame._handlerStack.pop();
|
|
745
|
+
// Restore the VM value stack to the depth recorded at TRY_SETUP time,
|
|
746
|
+
// then push the caught exception so the catch binding can store it.
|
|
747
|
+
this._stack.length = h.stackDepth;
|
|
748
|
+
this._push(err);
|
|
749
|
+
// Discard any call-frames that were pushed inside the try body
|
|
750
|
+
// (functions called from within the try block that are still live).
|
|
751
|
+
this._frameStack.length = h.frameStackDepth;
|
|
752
|
+
// Jump to the catch block.
|
|
753
|
+
handledFrame._pc = h.handlerPc;
|
|
754
|
+
this._currentFrame = handledFrame;
|
|
607
755
|
}
|
|
608
756
|
}
|
|
609
757
|
};
|
|
610
758
|
|
|
611
|
-
//
|
|
759
|
+
// Boot
|
|
612
760
|
var globals: any = {}; // global object for globals
|
|
613
761
|
|
|
614
762
|
// Always pull built-ins from globalThis so eval() scoping can't shadow them
|