simple-javascript-obf 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.
Files changed (43) hide show
  1. package/.github/workflows/node.js.yml +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +82 -0
  5. package/bin/js-obf +228 -0
  6. package/bin/obf.sh +257 -0
  7. package/package.json +26 -0
  8. package/src/index.js +106 -0
  9. package/src/options.js +128 -0
  10. package/src/pipeline.js +56 -0
  11. package/src/plugins/antiHook.js +123 -0
  12. package/src/plugins/controlFlowFlatten.js +203 -0
  13. package/src/plugins/deadCode.js +82 -0
  14. package/src/plugins/encodeMembers.js +44 -0
  15. package/src/plugins/entry.js +31 -0
  16. package/src/plugins/rename.js +100 -0
  17. package/src/plugins/stringEncode.js +494 -0
  18. package/src/plugins/vm/ast-utils.js +58 -0
  19. package/src/plugins/vm/compiler.js +113 -0
  20. package/src/plugins/vm/constants.js +72 -0
  21. package/src/plugins/vm/emit.js +916 -0
  22. package/src/plugins/vm/encoding.js +252 -0
  23. package/src/plugins/vm/index.js +366 -0
  24. package/src/plugins/vm/mapping.js +24 -0
  25. package/src/plugins/vm/normalize.js +692 -0
  26. package/src/plugins/vm/runtime.js +1145 -0
  27. package/src/plugins/vm.js +1 -0
  28. package/src/utils/names.js +55 -0
  29. package/src/utils/reserved.js +57 -0
  30. package/src/utils/rng.js +55 -0
  31. package/src/utils/stream.js +97 -0
  32. package/src/utils/string.js +13 -0
  33. package/test/bench-runner.js +78 -0
  34. package/test/benchmark-source.js +35 -0
  35. package/test/benchmark-vm.js +160 -0
  36. package/test/dist/bench.obf.js +1 -0
  37. package/test/dist/bench.original.js +35 -0
  38. package/test/dist/bench.vm.js +1 -0
  39. package/test/dist/sample-input.obf.js +1 -0
  40. package/test/dist/sample-input.vm.js +1 -0
  41. package/test/generate-obf.js +38 -0
  42. package/test/obf-smoke.js +129 -0
  43. package/test/sample-input.js +23 -0
@@ -0,0 +1,1145 @@
1
+ const parser = require("@babel/parser");
2
+
3
+ const { BIN_OPS, UNARY_OPS, OPCODES } = require("./constants");
4
+
5
+ function buildVmRuntime(names, opcodeInfo, opcodeMask, miniVmInfo, miniVmPrograms) {
6
+ const {
7
+ execName,
8
+ execAsyncName,
9
+ opsName,
10
+ opsLookupName,
11
+ globalsName,
12
+ makeFuncName,
13
+ maskName,
14
+ opcodeB64Name,
15
+ miniVmName,
16
+ bytecodeDecodeName,
17
+ bytecodeCacheName,
18
+ bytecodeB64Name,
19
+ bytecodeRc4Name,
20
+ constDecodeName,
21
+ constCacheName,
22
+ constB64Name,
23
+ constRc4Name,
24
+ constUtf8Name,
25
+ } = names;
26
+ const miniVmOps = JSON.stringify(miniVmInfo.ops);
27
+ const miniVmMask = JSON.stringify(miniVmInfo.mask);
28
+ const miniVmProgramOpcode = JSON.stringify(miniVmPrograms.opcode);
29
+ const miniVmProgramBytecode = JSON.stringify(miniVmPrograms.bytecode);
30
+ const miniVmProgramConsts = JSON.stringify(miniVmPrograms.consts);
31
+ const opcodeHelpers = `
32
+ const ${opcodeB64Name} = (() => {
33
+ const __vmOps = ${miniVmOps};
34
+ const __vmMask = ${miniVmMask};
35
+ let __alpha = "";
36
+ let __xor = 0;
37
+ let __rot = 0;
38
+ let __cipherLen = 0;
39
+ let __opsLen = 0;
40
+ let __key = null;
41
+ let __p0 = null;
42
+ let __p1 = null;
43
+ let __map = null;
44
+ const rotl = (v, s) => ((v << s) | (v >>> (32 - s))) >>> 0;
45
+ const rotr = (v, s) => ((v >>> s) | (v << (32 - s))) >>> 0;
46
+ const joinKey = (a, b, c) => {
47
+ const total = (a ? a.length : 0) + (b ? b.length : 0) + (c ? c.length : 0);
48
+ const out = new Uint8Array(total);
49
+ let offset = 0;
50
+ if (a && a.length) {
51
+ out.set(a, offset);
52
+ offset += a.length;
53
+ }
54
+ if (b && b.length) {
55
+ out.set(b, offset);
56
+ offset += b.length;
57
+ }
58
+ if (c && c.length) {
59
+ out.set(c, offset);
60
+ }
61
+ return out;
62
+ };
63
+ const makeStream = (keyBytes) => {
64
+ const key = Uint8Array.from(keyBytes || []);
65
+ const len = key.length || 1;
66
+ const pick = (idx) => key[idx % len] & 255;
67
+ const variant = pick(0) & 1;
68
+ const r0 = (pick(1) % 7) + 3;
69
+ const r1 = (pick(2) % 7) + 5;
70
+ const r2 = (pick(3) % 7) + 3;
71
+ const r3 = (pick(4) % 7) + 5;
72
+ let x =
73
+ ((pick(5) << 24) | (pick(6) << 16) | (pick(7) << 8) | pick(8)) >>> 0;
74
+ let y =
75
+ ((pick(9) << 24) | (pick(10) << 16) | (pick(11) << 8) | pick(12)) >>> 0;
76
+ x ^= (len << 24) >>> 0;
77
+ y ^= (len << 16) >>> 0;
78
+ for (let i = 0; i < len; i += 1) {
79
+ const v = (pick(i) + i * 17) & 255;
80
+ const fold = ((v << (i & 7)) | (v >>> (8 - (i & 7)))) & 255;
81
+ x = (x + fold) >>> 0;
82
+ x = rotl(x, r0);
83
+ y = (y + (x ^ (v << r2))) >>> 0;
84
+ y = rotr(y, r1);
85
+ }
86
+ let idx = 0;
87
+ return (byte) => {
88
+ const k = key[idx % len];
89
+ let out;
90
+ if (variant === 0) {
91
+ x = (x + (y ^ (k + idx + 1))) >>> 0;
92
+ x = rotl(x, r0);
93
+ y = (y + (k ^ (x & 255))) >>> 0;
94
+ y = rotr(y, r1);
95
+ out = (x ^ y ^ (x >>> 7)) & 255;
96
+ } else {
97
+ y = (y + k + (x & 255)) >>> 0;
98
+ y = rotl(y, r2);
99
+ x = (x + (y ^ (k << 8))) >>> 0;
100
+ x = rotr(x, r3);
101
+ out = (x + y + (x >>> 11)) & 255;
102
+ }
103
+ idx += 1;
104
+ return byte ^ out;
105
+ };
106
+ };
107
+ const stream = (data, order, onByte) => {
108
+ const parts = new Array(order.length);
109
+ for (let i = 0; i < order.length; i += 1) {
110
+ parts[order[i]] = data[i];
111
+ }
112
+ let total = 0;
113
+ let acc = 0;
114
+ let accLen = 0;
115
+ for (let p = 0; p < parts.length; p += 1) {
116
+ const segment = parts[p];
117
+ if (!segment) continue;
118
+ for (let i = 0; i < segment.length; i += 1) {
119
+ const mapped = __map[segment.charCodeAt(i)];
120
+ if (mapped === 65535) continue;
121
+ const val = ((mapped ^ __xor) - __rot) & 63;
122
+ acc = (acc << 6) | val;
123
+ accLen += 6;
124
+ if (accLen >= 24) {
125
+ accLen -= 24;
126
+ const triple = acc >>> accLen;
127
+ acc &= (1 << accLen) - 1;
128
+ const b0 = (triple >>> 16) & 255;
129
+ const b1 = (triple >>> 8) & 255;
130
+ const b2 = triple & 255;
131
+ if (total < __cipherLen) {
132
+ onByte(b0);
133
+ total += 1;
134
+ }
135
+ if (total < __cipherLen) {
136
+ onByte(b1);
137
+ total += 1;
138
+ }
139
+ if (total < __cipherLen) {
140
+ onByte(b2);
141
+ total += 1;
142
+ }
143
+ if (total >= __cipherLen) {
144
+ return;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ };
150
+ const open = (data, order) => {
151
+ const key = joinKey(__key, __p0, __p1);
152
+ const next = makeStream(key);
153
+ const out = new Array(__opsLen);
154
+ let outIdx = 0;
155
+ stream(data, order, (byte) => {
156
+ if (outIdx >= __opsLen) return;
157
+ out[outIdx++] = next(byte);
158
+ });
159
+ return out;
160
+ };
161
+ const rebuild = (masked, mask, orderEnc, orderMask) => {
162
+ const len = masked.length;
163
+ const out = new Uint8Array(len);
164
+ for (let i = 0; i < len; i += 1) {
165
+ const pos = orderEnc[i] ^ orderMask;
166
+ out[pos] = masked[i] ^ mask[i];
167
+ }
168
+ return out;
169
+ };
170
+ const ${miniVmName} = (program, pool) => {
171
+ const opPush = __vmOps[0] ^ __vmMask;
172
+ const opRebuild = __vmOps[1] ^ __vmMask;
173
+ const opToStr = __vmOps[2] ^ __vmMask;
174
+ const opStore = __vmOps[3] ^ __vmMask;
175
+ const stack = [];
176
+ const store = Object.create(null);
177
+ let ip = 0;
178
+ while (ip < program.length) {
179
+ const op = program[ip++] ^ __vmMask;
180
+ if (op === opPush) {
181
+ stack.push(pool[program[ip++]]);
182
+ } else if (op === opRebuild) {
183
+ const orderMask = stack.pop();
184
+ const orderEnc = stack.pop();
185
+ const mask = stack.pop();
186
+ const masked = stack.pop();
187
+ stack.push(rebuild(masked, mask, orderEnc, orderMask));
188
+ } else if (op === opToStr) {
189
+ const arr = stack.pop();
190
+ let text = "";
191
+ for (let i = 0; i < arr.length; i += 1) {
192
+ text += String.fromCharCode(arr[i]);
193
+ }
194
+ stack.push(text);
195
+ } else if (op === opStore) {
196
+ store[program[ip++]] = stack.pop();
197
+ }
198
+ }
199
+ return store;
200
+ };
201
+ const seed = (out) => {
202
+ const params = out[1];
203
+ __alpha = out[0];
204
+ __xor = params[0];
205
+ __rot = params[1];
206
+ __cipherLen =
207
+ (params[2] |
208
+ (params[3] << 8) |
209
+ (params[4] << 16) |
210
+ (params[5] << 24)) >>> 0;
211
+ __opsLen = params.length >= 8 ? params[6] | (params[7] << 8) : 0;
212
+ __key = out[2];
213
+ __p0 = out[3];
214
+ __p1 = out[4];
215
+ const map = new Uint16Array(256);
216
+ for (let i = 0; i < map.length; i += 1) {
217
+ map[i] = 65535;
218
+ }
219
+ for (let i = 0; i < __alpha.length; i += 1) {
220
+ map[__alpha.charCodeAt(i)] = i;
221
+ }
222
+ __map = map;
223
+ };
224
+ const len = () => __cipherLen;
225
+ return {
226
+ open,
227
+ rebuild,
228
+ ${miniVmName},
229
+ seed,
230
+ stream,
231
+ len,
232
+ makeStream,
233
+ joinKey,
234
+ };
235
+ })();
236
+ `;
237
+ const bytecodeHelpers = bytecodeDecodeName
238
+ ? `
239
+ const ${bytecodeRc4Name} = (key) => ${opcodeB64Name}.makeStream(key);
240
+ function ${bytecodeDecodeName}(parts, order, pool) {
241
+ const program = ${miniVmProgramBytecode};
242
+ const out = ${opcodeB64Name}.${miniVmName}(program, pool);
243
+ ${opcodeB64Name}.seed(out);
244
+ const key = out[2];
245
+ const next = ${bytecodeRc4Name}(key);
246
+ const total = ${opcodeB64Name}.len();
247
+ const words = new Int32Array(total >>> 2);
248
+ let acc = 0;
249
+ let accLen = 0;
250
+ let wordIdx = 0;
251
+ ${opcodeB64Name}.stream(parts, order, (byte) => {
252
+ const plain = next(byte);
253
+ acc |= plain << (accLen * 8);
254
+ accLen += 1;
255
+ if (accLen === 4) {
256
+ words[wordIdx++] = acc | 0;
257
+ acc = 0;
258
+ accLen = 0;
259
+ }
260
+ });
261
+ return words;
262
+ }
263
+ `
264
+ : "";
265
+ const constHelpers = constDecodeName
266
+ ? `
267
+ const ${constRc4Name} = (key) => ${opcodeB64Name}.makeStream(key);
268
+ const ${constUtf8Name} = (bytes) => {
269
+ if (typeof TextDecoder !== "undefined") {
270
+ return new TextDecoder("utf-8").decode(bytes);
271
+ }
272
+ if (typeof Buffer !== "undefined") {
273
+ return Buffer.from(bytes).toString("utf8");
274
+ }
275
+ let out = "";
276
+ for (let i = 0; i < bytes.length; i += 1) {
277
+ out += String.fromCharCode(bytes[i]);
278
+ }
279
+ try {
280
+ return decodeURIComponent(escape(out));
281
+ } catch (err) {
282
+ return out;
283
+ }
284
+ };
285
+ function ${constDecodeName}(parts, order, pool) {
286
+ const program = ${miniVmProgramConsts};
287
+ const out = ${opcodeB64Name}.${miniVmName}(program, pool);
288
+ ${opcodeB64Name}.seed(out);
289
+ const key = out[2];
290
+ const mask = out[3];
291
+ const next = ${constRc4Name}(key);
292
+ const total = ${opcodeB64Name}.len();
293
+ const bytes = new Uint8Array(total);
294
+ let idx = 0;
295
+ let maskIdx = 0;
296
+ ${opcodeB64Name}.stream(parts, order, (byte) => {
297
+ const masked = byte ^ mask[maskIdx++ % mask.length];
298
+ bytes[idx++] = next(masked);
299
+ });
300
+ const text = ${constUtf8Name}(bytes);
301
+ const entries = JSON.parse(text);
302
+ const outVals = new Array(entries.length);
303
+ for (let i = 0; i < entries.length; i += 1) {
304
+ const entry = entries[i];
305
+ const tag = entry[0];
306
+ const value = entry[1];
307
+ if (tag === "u") outVals[i] = undefined;
308
+ else if (tag === "l") outVals[i] = null;
309
+ else if (tag === "b") outVals[i] = Boolean(value);
310
+ else if (tag === "n") outVals[i] = Number(value);
311
+ else if (tag === "s") outVals[i] = value;
312
+ else outVals[i] = value;
313
+ }
314
+ return outVals;
315
+ }
316
+ `
317
+ : "";
318
+ const opcodeParts = JSON.stringify(opcodeInfo.parts);
319
+ const opcodeOrder = JSON.stringify(opcodeInfo.order);
320
+ const opcodeAlphabetMasked = JSON.stringify(opcodeInfo.alphabetMasked);
321
+ const opcodeAlphabetMask = JSON.stringify(opcodeInfo.alphabetMask);
322
+ const opcodeAlphabetOrder = JSON.stringify(opcodeInfo.alphabetOrder);
323
+ const opcodeAlphabetOrderMask = JSON.stringify(opcodeInfo.alphabetOrderMask);
324
+ const opcodeParamMasked = JSON.stringify(opcodeInfo.paramMasked);
325
+ const opcodeParamMask = JSON.stringify(opcodeInfo.paramMask);
326
+ const opcodeParamOrder = JSON.stringify(opcodeInfo.paramOrder);
327
+ const opcodeParamOrderMask = JSON.stringify(opcodeInfo.paramOrderMask);
328
+ const opcodeKeyMasked = JSON.stringify(opcodeInfo.keyMasked);
329
+ const opcodeKeyMask = JSON.stringify(opcodeInfo.keyMask);
330
+ const opcodeKeyOrder = JSON.stringify(opcodeInfo.keyOrder);
331
+ const opcodeKeyOrderMask = JSON.stringify(opcodeInfo.keyOrderMask);
332
+ const opcodeIvMasked = JSON.stringify(opcodeInfo.ivMasked);
333
+ const opcodeIvMask = JSON.stringify(opcodeInfo.ivMask);
334
+ const opcodeIvOrder = JSON.stringify(opcodeInfo.ivOrder);
335
+ const opcodeIvOrderMask = JSON.stringify(opcodeInfo.ivOrderMask);
336
+ const opcodeTagMasked = JSON.stringify(opcodeInfo.tagMasked);
337
+ const opcodeTagMask = JSON.stringify(opcodeInfo.tagMask);
338
+ const opcodeTagOrder = JSON.stringify(opcodeInfo.tagOrder);
339
+ const opcodeTagOrderMask = JSON.stringify(opcodeInfo.tagOrderMask);
340
+ const code = `
341
+ ${opcodeHelpers}
342
+ ${bytecodeHelpers}
343
+ ${constHelpers}
344
+ const ${opsName} = (() => {
345
+ const data = ${opcodeParts};
346
+ const order = ${opcodeOrder};
347
+ const pool = [
348
+ ${opcodeAlphabetMasked},
349
+ ${opcodeAlphabetMask},
350
+ ${opcodeAlphabetOrder},
351
+ ${opcodeAlphabetOrderMask},
352
+ ${opcodeParamMasked},
353
+ ${opcodeParamMask},
354
+ ${opcodeParamOrder},
355
+ ${opcodeParamOrderMask},
356
+ ${opcodeKeyMasked},
357
+ ${opcodeKeyMask},
358
+ ${opcodeKeyOrder},
359
+ ${opcodeKeyOrderMask},
360
+ ${opcodeIvMasked},
361
+ ${opcodeIvMask},
362
+ ${opcodeIvOrder},
363
+ ${opcodeIvOrderMask},
364
+ ${opcodeTagMasked},
365
+ ${opcodeTagMask},
366
+ ${opcodeTagOrder},
367
+ ${opcodeTagOrderMask},
368
+ ];
369
+ const program = ${miniVmProgramOpcode};
370
+ const out = ${opcodeB64Name}.${miniVmName}(program, pool);
371
+ ${opcodeB64Name}.seed(out);
372
+ const raw = ${opcodeB64Name}.open(data, order);
373
+ const a0 = [];
374
+ const a1 = [];
375
+ const a2 = [];
376
+ const a3 = [];
377
+ for (let i = 0; i < raw.length; i += 1) {
378
+ const bucket = i & 3;
379
+ if (bucket === 0) {
380
+ a0.push(raw[i]);
381
+ } else if (bucket === 1) {
382
+ a1.push(raw[i]);
383
+ } else if (bucket === 2) {
384
+ a2.push(raw[i]);
385
+ } else {
386
+ a3.push(raw[i]);
387
+ }
388
+ }
389
+ return [a0, a1, a2, a3];
390
+ })();
391
+ const ${opsLookupName} = (() => {
392
+ const table = new Uint8Array(256);
393
+ for (let i = 0; i < 256; i += 1) {
394
+ const bucket = i & 3;
395
+ const slot = (i ^ bucket) >>> 2;
396
+ table[i] = ${opsName}[bucket][slot];
397
+ }
398
+ return table;
399
+ })();
400
+ const ${maskName} = ${opcodeMask};
401
+ function ${makeFuncName}(src, env) {
402
+ return Function("env", "with (env) { return (" + src + ") }")(env);
403
+ }
404
+ function ${execName}(code, consts, env, thisArg) {
405
+ const stack = [];
406
+ const tryStack = [];
407
+ let sp = 0;
408
+ const opTable = ${opsLookupName};
409
+ const mask = ${maskName};
410
+ const ${globalsName} = typeof globalThis !== "undefined"
411
+ ? globalThis
412
+ : typeof window !== "undefined"
413
+ ? window
414
+ : typeof global !== "undefined"
415
+ ? global
416
+ : {};
417
+ let ip = 0;
418
+ let currentError = null;
419
+ let pendingThrow = null;
420
+ let fakeAcc = 0;
421
+ let returnValue;
422
+ let done = false;
423
+
424
+ function handleError(err) {
425
+ while (tryStack.length) {
426
+ const handler = tryStack[tryStack.length - 1];
427
+ if (handler.inFinally) {
428
+ tryStack.pop();
429
+ continue;
430
+ }
431
+ if (handler.inCatch) {
432
+ if (handler.finallyIp !== null) {
433
+ handler.inCatch = false;
434
+ pendingThrow = err;
435
+ currentError = null;
436
+ sp = handler.stackSize;
437
+ stack.length = sp;
438
+ ip = handler.finallyIp;
439
+ return true;
440
+ }
441
+ tryStack.pop();
442
+ continue;
443
+ }
444
+ if (handler.catchIp !== null) {
445
+ currentError = err;
446
+ pendingThrow = null;
447
+ sp = handler.stackSize;
448
+ stack.length = sp;
449
+ ip = handler.catchIp;
450
+ return true;
451
+ }
452
+ if (handler.finallyIp !== null) {
453
+ pendingThrow = err;
454
+ sp = handler.stackSize;
455
+ stack.length = sp;
456
+ ip = handler.finallyIp;
457
+ return true;
458
+ }
459
+ tryStack.pop();
460
+ }
461
+ return false;
462
+ }
463
+
464
+ while (ip < code.length) {
465
+ const op = opTable[(code[ip++] ^ mask) & 255];
466
+ try {
467
+ switch (op) {
468
+ case ${OPCODES.PUSH_CONST}:
469
+ stack[sp++] = consts[code[ip++]];
470
+ break;
471
+ case ${OPCODES.GET_VAR}:
472
+ stack[sp++] = env[consts[code[ip++]]];
473
+ break;
474
+ case ${OPCODES.SET_VAR}: {
475
+ const name = consts[code[ip++]];
476
+ const value = stack[--sp];
477
+ env[name] = value;
478
+ stack[sp++] = value;
479
+ break;
480
+ }
481
+ case ${OPCODES.GET_GLOBAL}:
482
+ stack[sp++] = ${globalsName}[consts[code[ip++]]];
483
+ break;
484
+ case ${OPCODES.SET_GLOBAL}: {
485
+ const name = consts[code[ip++]];
486
+ const value = stack[--sp];
487
+ ${globalsName}[name] = value;
488
+ stack[sp++] = value;
489
+ break;
490
+ }
491
+ case ${OPCODES.BIN_OP}: {
492
+ const opId = code[ip++];
493
+ const b = stack[--sp];
494
+ const a = stack[--sp];
495
+ switch (opId) {
496
+ ${BIN_OPS.map(
497
+ (opItem, idx) => `case ${idx}: stack[sp++] = a ${opItem} b; break;`
498
+ ).join("\n ")}
499
+ default:
500
+ throw new Error("Unknown BIN op");
501
+ }
502
+ break;
503
+ }
504
+ case ${OPCODES.UNARY_OP}: {
505
+ const opId = code[ip++];
506
+ const a = stack[--sp];
507
+ switch (opId) {
508
+ ${UNARY_OPS.map((opItem, idx) => {
509
+ if (opItem === "typeof") {
510
+ return `case ${idx}: stack[sp++] = typeof a; break;`;
511
+ }
512
+ if (opItem === "void") {
513
+ return `case ${idx}: stack[sp++] = void a; break;`;
514
+ }
515
+ return `case ${idx}: stack[sp++] = ${opItem}a; break;`;
516
+ }).join("\n ")}
517
+ default:
518
+ throw new Error("Unknown UNARY op");
519
+ }
520
+ break;
521
+ }
522
+ case ${OPCODES.CALL}: {
523
+ const argc = code[ip++];
524
+ if (argc === 0) {
525
+ const fn = stack[--sp];
526
+ stack[sp++] = fn.call(null);
527
+ break;
528
+ }
529
+ if (argc === 1) {
530
+ const arg0 = stack[--sp];
531
+ const fn = stack[--sp];
532
+ stack[sp++] = fn.call(null, arg0);
533
+ break;
534
+ }
535
+ if (argc === 2) {
536
+ const arg1 = stack[--sp];
537
+ const arg0 = stack[--sp];
538
+ const fn = stack[--sp];
539
+ stack[sp++] = fn.call(null, arg0, arg1);
540
+ break;
541
+ }
542
+ if (argc === 3) {
543
+ const arg2 = stack[--sp];
544
+ const arg1 = stack[--sp];
545
+ const arg0 = stack[--sp];
546
+ const fn = stack[--sp];
547
+ stack[sp++] = fn.call(null, arg0, arg1, arg2);
548
+ break;
549
+ }
550
+ const args = new Array(argc);
551
+ for (let i = argc - 1; i >= 0; i -= 1) {
552
+ args[i] = stack[--sp];
553
+ }
554
+ const fn = stack[--sp];
555
+ stack[sp++] = fn.apply(null, args);
556
+ break;
557
+ }
558
+ case ${OPCODES.CALL_METHOD}: {
559
+ const argc = code[ip++];
560
+ if (argc === 0) {
561
+ const prop = stack[--sp];
562
+ const obj = stack[--sp];
563
+ const fn = obj[prop];
564
+ stack[sp++] = fn.call(obj);
565
+ break;
566
+ }
567
+ if (argc === 1) {
568
+ const arg0 = stack[--sp];
569
+ const prop = stack[--sp];
570
+ const obj = stack[--sp];
571
+ const fn = obj[prop];
572
+ stack[sp++] = fn.call(obj, arg0);
573
+ break;
574
+ }
575
+ if (argc === 2) {
576
+ const arg1 = stack[--sp];
577
+ const arg0 = stack[--sp];
578
+ const prop = stack[--sp];
579
+ const obj = stack[--sp];
580
+ const fn = obj[prop];
581
+ stack[sp++] = fn.call(obj, arg0, arg1);
582
+ break;
583
+ }
584
+ if (argc === 3) {
585
+ const arg2 = stack[--sp];
586
+ const arg1 = stack[--sp];
587
+ const arg0 = stack[--sp];
588
+ const prop = stack[--sp];
589
+ const obj = stack[--sp];
590
+ const fn = obj[prop];
591
+ stack[sp++] = fn.call(obj, arg0, arg1, arg2);
592
+ break;
593
+ }
594
+ const args = new Array(argc);
595
+ for (let i = argc - 1; i >= 0; i -= 1) {
596
+ args[i] = stack[--sp];
597
+ }
598
+ const prop = stack[--sp];
599
+ const obj = stack[--sp];
600
+ const fn = obj[prop];
601
+ stack[sp++] = fn.apply(obj, args);
602
+ break;
603
+ }
604
+ case ${OPCODES.NEW}: {
605
+ const argc = code[ip++];
606
+ if (argc === 0) {
607
+ const ctor = stack[--sp];
608
+ stack[sp++] = new ctor();
609
+ break;
610
+ }
611
+ if (argc === 1) {
612
+ const arg0 = stack[--sp];
613
+ const ctor = stack[--sp];
614
+ stack[sp++] = new ctor(arg0);
615
+ break;
616
+ }
617
+ if (argc === 2) {
618
+ const arg1 = stack[--sp];
619
+ const arg0 = stack[--sp];
620
+ const ctor = stack[--sp];
621
+ stack[sp++] = new ctor(arg0, arg1);
622
+ break;
623
+ }
624
+ if (argc === 3) {
625
+ const arg2 = stack[--sp];
626
+ const arg1 = stack[--sp];
627
+ const arg0 = stack[--sp];
628
+ const ctor = stack[--sp];
629
+ stack[sp++] = new ctor(arg0, arg1, arg2);
630
+ break;
631
+ }
632
+ const args = new Array(argc);
633
+ for (let i = argc - 1; i >= 0; i -= 1) {
634
+ args[i] = stack[--sp];
635
+ }
636
+ const ctor = stack[--sp];
637
+ stack[sp++] = Reflect.construct(ctor, args);
638
+ break;
639
+ }
640
+ case ${OPCODES.GET_PROP}: {
641
+ const prop = stack[--sp];
642
+ const obj = stack[--sp];
643
+ stack[sp++] = obj[prop];
644
+ break;
645
+ }
646
+ case ${OPCODES.SET_PROP}: {
647
+ const value = stack[--sp];
648
+ const prop = stack[--sp];
649
+ const obj = stack[--sp];
650
+ obj[prop] = value;
651
+ stack[sp++] = value;
652
+ break;
653
+ }
654
+ case ${OPCODES.PUSH_THIS}:
655
+ stack[sp++] = thisArg;
656
+ break;
657
+ case ${OPCODES.POP}:
658
+ sp -= 1;
659
+ break;
660
+ case ${OPCODES.DUP}:
661
+ stack[sp] = stack[sp - 1];
662
+ sp += 1;
663
+ break;
664
+ case ${OPCODES.JMP}:
665
+ ip = code[ip];
666
+ break;
667
+ case ${OPCODES.JMP_IF_FALSE}:
668
+ case ${OPCODES.JMP_IF_TRUE}: {
669
+ const target = code[ip++];
670
+ const value = stack[--sp];
671
+ const isTrue = op === ${OPCODES.JMP_IF_TRUE};
672
+ if (isTrue ? value : !value) {
673
+ ip = target;
674
+ }
675
+ break;
676
+ }
677
+ case ${OPCODES.TRY}: {
678
+ const catchIp = code[ip++];
679
+ const finallyIp = code[ip++];
680
+ const endIp = code[ip++];
681
+ tryStack.push({
682
+ catchIp: catchIp >= 0 ? catchIp : null,
683
+ finallyIp: finallyIp >= 0 ? finallyIp : null,
684
+ endIp,
685
+ stackSize: sp,
686
+ inCatch: false,
687
+ inFinally: false,
688
+ });
689
+ break;
690
+ }
691
+ case ${OPCODES.END_TRY}:
692
+ tryStack.pop();
693
+ pendingThrow = null;
694
+ currentError = null;
695
+ break;
696
+ case ${OPCODES.ENTER_CATCH}: {
697
+ const handler = tryStack[tryStack.length - 1];
698
+ if (handler) {
699
+ handler.inCatch = true;
700
+ }
701
+ break;
702
+ }
703
+ case ${OPCODES.ENTER_FINALLY}: {
704
+ const handler = tryStack[tryStack.length - 1];
705
+ if (handler) {
706
+ handler.inFinally = true;
707
+ }
708
+ break;
709
+ }
710
+ case ${OPCODES.PUSH_ERROR}:
711
+ stack[sp++] = currentError;
712
+ break;
713
+ case ${OPCODES.MAKE_ARR}:
714
+ stack[sp++] = [];
715
+ break;
716
+ case ${OPCODES.MAKE_OBJ}:
717
+ stack[sp++] = {};
718
+ break;
719
+ case ${OPCODES.MAKE_FUNC}: {
720
+ const src = consts[code[ip++]];
721
+ stack[sp++] = ${makeFuncName}(src, env);
722
+ break;
723
+ }
724
+ case ${OPCODES.AWAIT}:
725
+ throw new Error("Await in sync VM");
726
+ case ${OPCODES.RETURN}:
727
+ returnValue = stack[--sp];
728
+ done = true;
729
+ break;
730
+ case ${OPCODES.THROW}:
731
+ throw stack[--sp];
732
+ case ${OPCODES.RETHROW}:
733
+ if (pendingThrow !== null) {
734
+ const err = pendingThrow;
735
+ pendingThrow = null;
736
+ throw err;
737
+ }
738
+ break;
739
+ case ${OPCODES.FAKE_ADD}:
740
+ fakeAcc = (fakeAcc + sp + ip) | 0;
741
+ break;
742
+ case ${OPCODES.FAKE_POP_PUSH}:
743
+ if (sp) {
744
+ const idx = sp - 1;
745
+ stack[idx] = stack[idx];
746
+ }
747
+ break;
748
+ case ${OPCODES.FAKE_JMP}: {
749
+ const target = code[ip++];
750
+ if (false) {
751
+ ip = target;
752
+ }
753
+ break;
754
+ }
755
+ default:
756
+ throw new Error("Unknown opcode");
757
+ }
758
+ if (done) {
759
+ return returnValue;
760
+ }
761
+ } catch (err) {
762
+ if (!handleError(err)) {
763
+ throw err;
764
+ }
765
+ }
766
+ }
767
+ return undefined;
768
+ }
769
+ `;
770
+ const asyncCode = `
771
+ async function ${execAsyncName}(code, consts, env, thisArg) {
772
+ const stack = [];
773
+ const tryStack = [];
774
+ let sp = 0;
775
+ const opTable = ${opsLookupName};
776
+ const mask = ${maskName};
777
+ const ${globalsName} = typeof globalThis !== "undefined"
778
+ ? globalThis
779
+ : typeof window !== "undefined"
780
+ ? window
781
+ : typeof global !== "undefined"
782
+ ? global
783
+ : {};
784
+ let ip = 0;
785
+ let currentError = null;
786
+ let pendingThrow = null;
787
+ let fakeAcc = 0;
788
+ let returnValue;
789
+ let done = false;
790
+
791
+ function handleError(err) {
792
+ while (tryStack.length) {
793
+ const handler = tryStack[tryStack.length - 1];
794
+ if (handler.inFinally) {
795
+ tryStack.pop();
796
+ continue;
797
+ }
798
+ if (handler.inCatch) {
799
+ if (handler.finallyIp !== null) {
800
+ handler.inCatch = false;
801
+ pendingThrow = err;
802
+ currentError = null;
803
+ sp = handler.stackSize;
804
+ stack.length = sp;
805
+ ip = handler.finallyIp;
806
+ return true;
807
+ }
808
+ tryStack.pop();
809
+ continue;
810
+ }
811
+ if (handler.catchIp !== null) {
812
+ currentError = err;
813
+ pendingThrow = null;
814
+ sp = handler.stackSize;
815
+ stack.length = sp;
816
+ ip = handler.catchIp;
817
+ return true;
818
+ }
819
+ if (handler.finallyIp !== null) {
820
+ pendingThrow = err;
821
+ sp = handler.stackSize;
822
+ stack.length = sp;
823
+ ip = handler.finallyIp;
824
+ return true;
825
+ }
826
+ tryStack.pop();
827
+ }
828
+ return false;
829
+ }
830
+
831
+ while (ip < code.length) {
832
+ const op = opTable[(code[ip++] ^ mask) & 255];
833
+ try {
834
+ switch (op) {
835
+ case ${OPCODES.PUSH_CONST}:
836
+ stack[sp++] = consts[code[ip++]];
837
+ break;
838
+ case ${OPCODES.GET_VAR}:
839
+ stack[sp++] = env[consts[code[ip++]]];
840
+ break;
841
+ case ${OPCODES.SET_VAR}: {
842
+ const name = consts[code[ip++]];
843
+ const value = stack[--sp];
844
+ env[name] = value;
845
+ stack[sp++] = value;
846
+ break;
847
+ }
848
+ case ${OPCODES.GET_GLOBAL}:
849
+ stack[sp++] = ${globalsName}[consts[code[ip++]]];
850
+ break;
851
+ case ${OPCODES.SET_GLOBAL}: {
852
+ const name = consts[code[ip++]];
853
+ const value = stack[--sp];
854
+ ${globalsName}[name] = value;
855
+ stack[sp++] = value;
856
+ break;
857
+ }
858
+ case ${OPCODES.BIN_OP}: {
859
+ const opId = code[ip++];
860
+ const b = stack[--sp];
861
+ const a = stack[--sp];
862
+ switch (opId) {
863
+ ${BIN_OPS.map(
864
+ (opItem, idx) => `case ${idx}: stack[sp++] = a ${opItem} b; break;`
865
+ ).join("\n ")}
866
+ default:
867
+ throw new Error("Unknown BIN op");
868
+ }
869
+ break;
870
+ }
871
+ case ${OPCODES.UNARY_OP}: {
872
+ const opId = code[ip++];
873
+ const a = stack[--sp];
874
+ switch (opId) {
875
+ ${UNARY_OPS.map((opItem, idx) => {
876
+ if (opItem === "typeof") {
877
+ return `case ${idx}: stack[sp++] = typeof a; break;`;
878
+ }
879
+ if (opItem === "void") {
880
+ return `case ${idx}: stack[sp++] = void a; break;`;
881
+ }
882
+ return `case ${idx}: stack[sp++] = ${opItem}a; break;`;
883
+ }).join("\n ")}
884
+ default:
885
+ throw new Error("Unknown UNARY op");
886
+ }
887
+ break;
888
+ }
889
+ case ${OPCODES.CALL}: {
890
+ const argc = code[ip++];
891
+ if (argc === 0) {
892
+ const fn = stack[--sp];
893
+ stack[sp++] = fn.call(null);
894
+ break;
895
+ }
896
+ if (argc === 1) {
897
+ const arg0 = stack[--sp];
898
+ const fn = stack[--sp];
899
+ stack[sp++] = fn.call(null, arg0);
900
+ break;
901
+ }
902
+ if (argc === 2) {
903
+ const arg1 = stack[--sp];
904
+ const arg0 = stack[--sp];
905
+ const fn = stack[--sp];
906
+ stack[sp++] = fn.call(null, arg0, arg1);
907
+ break;
908
+ }
909
+ if (argc === 3) {
910
+ const arg2 = stack[--sp];
911
+ const arg1 = stack[--sp];
912
+ const arg0 = stack[--sp];
913
+ const fn = stack[--sp];
914
+ stack[sp++] = fn.call(null, arg0, arg1, arg2);
915
+ break;
916
+ }
917
+ const args = new Array(argc);
918
+ for (let i = argc - 1; i >= 0; i -= 1) {
919
+ args[i] = stack[--sp];
920
+ }
921
+ const fn = stack[--sp];
922
+ stack[sp++] = fn.apply(null, args);
923
+ break;
924
+ }
925
+ case ${OPCODES.CALL_METHOD}: {
926
+ const argc = code[ip++];
927
+ if (argc === 0) {
928
+ const prop = stack[--sp];
929
+ const obj = stack[--sp];
930
+ const fn = obj[prop];
931
+ stack[sp++] = fn.call(obj);
932
+ break;
933
+ }
934
+ if (argc === 1) {
935
+ const arg0 = stack[--sp];
936
+ const prop = stack[--sp];
937
+ const obj = stack[--sp];
938
+ const fn = obj[prop];
939
+ stack[sp++] = fn.call(obj, arg0);
940
+ break;
941
+ }
942
+ if (argc === 2) {
943
+ const arg1 = stack[--sp];
944
+ const arg0 = stack[--sp];
945
+ const prop = stack[--sp];
946
+ const obj = stack[--sp];
947
+ const fn = obj[prop];
948
+ stack[sp++] = fn.call(obj, arg0, arg1);
949
+ break;
950
+ }
951
+ if (argc === 3) {
952
+ const arg2 = stack[--sp];
953
+ const arg1 = stack[--sp];
954
+ const arg0 = stack[--sp];
955
+ const prop = stack[--sp];
956
+ const obj = stack[--sp];
957
+ const fn = obj[prop];
958
+ stack[sp++] = fn.call(obj, arg0, arg1, arg2);
959
+ break;
960
+ }
961
+ const args = new Array(argc);
962
+ for (let i = argc - 1; i >= 0; i -= 1) {
963
+ args[i] = stack[--sp];
964
+ }
965
+ const prop = stack[--sp];
966
+ const obj = stack[--sp];
967
+ const fn = obj[prop];
968
+ stack[sp++] = fn.apply(obj, args);
969
+ break;
970
+ }
971
+ case ${OPCODES.NEW}: {
972
+ const argc = code[ip++];
973
+ if (argc === 0) {
974
+ const ctor = stack[--sp];
975
+ stack[sp++] = new ctor();
976
+ break;
977
+ }
978
+ if (argc === 1) {
979
+ const arg0 = stack[--sp];
980
+ const ctor = stack[--sp];
981
+ stack[sp++] = new ctor(arg0);
982
+ break;
983
+ }
984
+ if (argc === 2) {
985
+ const arg1 = stack[--sp];
986
+ const arg0 = stack[--sp];
987
+ const ctor = stack[--sp];
988
+ stack[sp++] = new ctor(arg0, arg1);
989
+ break;
990
+ }
991
+ if (argc === 3) {
992
+ const arg2 = stack[--sp];
993
+ const arg1 = stack[--sp];
994
+ const arg0 = stack[--sp];
995
+ const ctor = stack[--sp];
996
+ stack[sp++] = new ctor(arg0, arg1, arg2);
997
+ break;
998
+ }
999
+ const args = new Array(argc);
1000
+ for (let i = argc - 1; i >= 0; i -= 1) {
1001
+ args[i] = stack[--sp];
1002
+ }
1003
+ const ctor = stack[--sp];
1004
+ stack[sp++] = Reflect.construct(ctor, args);
1005
+ break;
1006
+ }
1007
+ case ${OPCODES.GET_PROP}: {
1008
+ const prop = stack[--sp];
1009
+ const obj = stack[--sp];
1010
+ stack[sp++] = obj[prop];
1011
+ break;
1012
+ }
1013
+ case ${OPCODES.SET_PROP}: {
1014
+ const value = stack[--sp];
1015
+ const prop = stack[--sp];
1016
+ const obj = stack[--sp];
1017
+ obj[prop] = value;
1018
+ stack[sp++] = value;
1019
+ break;
1020
+ }
1021
+ case ${OPCODES.PUSH_THIS}:
1022
+ stack[sp++] = thisArg;
1023
+ break;
1024
+ case ${OPCODES.POP}:
1025
+ sp -= 1;
1026
+ break;
1027
+ case ${OPCODES.DUP}:
1028
+ stack[sp] = stack[sp - 1];
1029
+ sp += 1;
1030
+ break;
1031
+ case ${OPCODES.JMP}:
1032
+ ip = code[ip];
1033
+ break;
1034
+ case ${OPCODES.JMP_IF_FALSE}:
1035
+ case ${OPCODES.JMP_IF_TRUE}: {
1036
+ const target = code[ip++];
1037
+ const value = stack[--sp];
1038
+ const isTrue = op === ${OPCODES.JMP_IF_TRUE};
1039
+ if (isTrue ? value : !value) {
1040
+ ip = target;
1041
+ }
1042
+ break;
1043
+ }
1044
+ case ${OPCODES.TRY}: {
1045
+ const catchIp = code[ip++];
1046
+ const finallyIp = code[ip++];
1047
+ const endIp = code[ip++];
1048
+ tryStack.push({
1049
+ catchIp: catchIp >= 0 ? catchIp : null,
1050
+ finallyIp: finallyIp >= 0 ? finallyIp : null,
1051
+ endIp,
1052
+ stackSize: sp,
1053
+ inCatch: false,
1054
+ inFinally: false,
1055
+ });
1056
+ break;
1057
+ }
1058
+ case ${OPCODES.END_TRY}:
1059
+ tryStack.pop();
1060
+ pendingThrow = null;
1061
+ currentError = null;
1062
+ break;
1063
+ case ${OPCODES.ENTER_CATCH}: {
1064
+ const handler = tryStack[tryStack.length - 1];
1065
+ if (handler) {
1066
+ handler.inCatch = true;
1067
+ }
1068
+ break;
1069
+ }
1070
+ case ${OPCODES.ENTER_FINALLY}: {
1071
+ const handler = tryStack[tryStack.length - 1];
1072
+ if (handler) {
1073
+ handler.inFinally = true;
1074
+ }
1075
+ break;
1076
+ }
1077
+ case ${OPCODES.PUSH_ERROR}:
1078
+ stack[sp++] = currentError;
1079
+ break;
1080
+ case ${OPCODES.MAKE_ARR}:
1081
+ stack[sp++] = [];
1082
+ break;
1083
+ case ${OPCODES.MAKE_OBJ}:
1084
+ stack[sp++] = {};
1085
+ break;
1086
+ case ${OPCODES.MAKE_FUNC}: {
1087
+ const src = consts[code[ip++]];
1088
+ stack[sp++] = ${makeFuncName}(src, env);
1089
+ break;
1090
+ }
1091
+ case ${OPCODES.AWAIT}: {
1092
+ const awaited = stack[--sp];
1093
+ stack[sp++] = await awaited;
1094
+ break;
1095
+ }
1096
+ case ${OPCODES.RETURN}:
1097
+ returnValue = stack[--sp];
1098
+ done = true;
1099
+ break;
1100
+ case ${OPCODES.THROW}:
1101
+ throw stack[--sp];
1102
+ case ${OPCODES.RETHROW}:
1103
+ if (pendingThrow !== null) {
1104
+ const err = pendingThrow;
1105
+ pendingThrow = null;
1106
+ throw err;
1107
+ }
1108
+ break;
1109
+ case ${OPCODES.FAKE_ADD}:
1110
+ fakeAcc = (fakeAcc + sp + ip) | 0;
1111
+ break;
1112
+ case ${OPCODES.FAKE_POP_PUSH}:
1113
+ if (sp) {
1114
+ const idx = sp - 1;
1115
+ stack[idx] = stack[idx];
1116
+ }
1117
+ break;
1118
+ case ${OPCODES.FAKE_JMP}: {
1119
+ const target = code[ip++];
1120
+ if (false) {
1121
+ ip = target;
1122
+ }
1123
+ break;
1124
+ }
1125
+ default:
1126
+ throw new Error("Unknown opcode");
1127
+ }
1128
+ if (done) {
1129
+ return returnValue;
1130
+ }
1131
+ } catch (err) {
1132
+ if (!handleError(err)) {
1133
+ throw err;
1134
+ }
1135
+ }
1136
+ }
1137
+ return undefined;
1138
+ }
1139
+ `;
1140
+ return parser
1141
+ .parse(`${code}\n${asyncCode}`, { sourceType: "script" })
1142
+ .program.body;
1143
+ }
1144
+
1145
+ module.exports = { buildVmRuntime };