jsguardian 1.2.0

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.
@@ -0,0 +1,892 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HANDLER_MAP = exports.OPCODE_NAMES = void 0;
4
+ exports.generateVmNameMap = generateVmNameMap;
5
+ exports.applyVmNameMap = applyVmNameMap;
6
+ exports.generateVm = generateVm;
7
+ // ── OPCODE NAMES ─────────────────────────────────────────────────────────────
8
+ exports.OPCODE_NAMES = [
9
+ "MOV", "MOVR", "ADD", "SUB", "MUL", "DIV", "MOD",
10
+ "NOR", "XOR", "AND", "OR", "NOT", "SHL", "SHR",
11
+ "LOAD", "STORE", "CMP", "INC", "DEC", "JMP", "JZ",
12
+ "JNZ", "JLT", "JGT", "PUSH", "POP", "CALLI", "CALLE", "RET",
13
+ "OUT", "HALT", "STR", "GGLO", "GPRP", "SPRP", "METH",
14
+ "TNUM", "FNUM", "CADR", "NEW", "IOF", "CREGI", "CREGE", "TYP",
15
+ "BOOL", "FUNC", "SAR", "EVAL",
16
+ ];
17
+ exports.HANDLER_MAP = {
18
+ mov: "MOV", movr: "MOVR", add: "ADD", sub: "SUB", mul: "MUL", div: "DIV", mod: "MOD",
19
+ nor: "NOR", xor: "XOR", and: "AND", or: "OR", not: "NOT", shl: "SHL", shr: "SHR",
20
+ load: "LOAD", store: "STORE", cmp: "CMP", inc: "INC", dec: "DEC",
21
+ jmp: "JMP", jz: "JZ", jnz: "JNZ", jlt: "JLT", jgt: "JGT",
22
+ push: "PUSH", pop: "POP", calli: "CALLI", calle: "CALLE", ret: "RET",
23
+ out: "OUT", halt: "HALT", str: "STR", getGlobal: "GGLO",
24
+ getProp: "GPRP", setProp: "SPRP", callMethod: "METH",
25
+ toNum: "TNUM", fromNum: "FNUM", callAddr: "CADR", newObject: "NEW",
26
+ instanceOf: "IOF", callRegInternal: "CREGI", callRegExternal: "CREGE",
27
+ typeOf: "TYP", toBool: "BOOL", newFunc: "FUNC", shiftRightArith: "SAR", _eval: "EVAL",
28
+ };
29
+ // ── HANDLER SOURCES (template) ────────────────────────────────────────────────
30
+ // Each handler is a string with @FETCH_START/@FETCH_END markers.
31
+ // The generator shuffles arg-fetch lines within each block.
32
+ const HANDLER_SRCS = {
33
+ mov: `function mov() {
34
+ //@FETCH_START
35
+ var r = readByte();
36
+ var v = readInt32();
37
+ //@FETCH_END
38
+ ctx.reg[r] = v;
39
+ }`,
40
+ movr: `function movr() {
41
+ //@FETCH_START
42
+ var d = readByte();
43
+ var s = readByte();
44
+ //@FETCH_END
45
+ ctx.reg[d] = ctx.reg[s];
46
+ }`,
47
+ add: `function add() {
48
+ //@FETCH_START
49
+ var d = readByte();
50
+ var s = readByte();
51
+ //@FETCH_END
52
+ ctx.reg[d] = ctx.reg[d] + ctx.reg[s];
53
+ }`,
54
+ sub: `function sub() {
55
+ //@FETCH_START
56
+ var d = readByte();
57
+ var s = readByte();
58
+ //@FETCH_END
59
+ ctx.reg[d] = ctx.reg[d] - ctx.reg[s];
60
+ }`,
61
+ mul: `function mul() {
62
+ //@FETCH_START
63
+ var d = readByte();
64
+ var s = readByte();
65
+ //@FETCH_END
66
+ ctx.reg[d] = ctx.reg[d] * ctx.reg[s];
67
+ }`,
68
+ div: `function div() {
69
+ //@FETCH_START
70
+ var d = readByte();
71
+ var s = readByte();
72
+ //@FETCH_END
73
+ ctx.reg[d] = ctx.reg[d] / ctx.reg[s];
74
+ }`,
75
+ mod: `function mod() {
76
+ //@FETCH_START
77
+ var d = readByte();
78
+ var s = readByte();
79
+ //@FETCH_END
80
+ ctx.reg[d] = ctx.reg[d] % ctx.reg[s];
81
+ }`,
82
+ nor: `function nor() {
83
+ //@FETCH_START
84
+ var d = readByte();
85
+ var s = readByte();
86
+ //@FETCH_END
87
+ ctx.reg[d] = ~(ctx.reg[d] | ctx.reg[s]);
88
+ }`,
89
+ xor: `function xor() {
90
+ //@FETCH_START
91
+ var d = readByte();
92
+ var s = readByte();
93
+ //@FETCH_END
94
+ ctx.reg[d] = ctx.reg[d] ^ ctx.reg[s];
95
+ }`,
96
+ and: `function and() {
97
+ //@FETCH_START
98
+ var d = readByte();
99
+ var s = readByte();
100
+ //@FETCH_END
101
+ ctx.reg[d] = ctx.reg[d] & ctx.reg[s];
102
+ }`,
103
+ or: `function or() {
104
+ //@FETCH_START
105
+ var d = readByte();
106
+ var s = readByte();
107
+ //@FETCH_END
108
+ ctx.reg[d] = ctx.reg[d] | ctx.reg[s];
109
+ }`,
110
+ not: `function not() {
111
+ //@FETCH_START
112
+ var r = readByte();
113
+ //@FETCH_END
114
+ ctx.reg[r] = ~ctx.reg[r];
115
+ }`,
116
+ shl: `function shl() {
117
+ //@FETCH_START
118
+ var d = readByte();
119
+ var s = readByte();
120
+ //@FETCH_END
121
+ ctx.reg[d] = ctx.reg[d] << ctx.reg[s];
122
+ }`,
123
+ shr: `function shr() {
124
+ //@FETCH_START
125
+ var d = readByte();
126
+ var s = readByte();
127
+ //@FETCH_END
128
+ ctx.reg[d] = ctx.reg[d] >>> ctx.reg[s];
129
+ }`,
130
+ shiftRightArith: `function shiftRightArith() {
131
+ //@FETCH_START
132
+ var d = readByte();
133
+ var s = readByte();
134
+ //@FETCH_END
135
+ ctx.reg[d] = ctx.reg[d] >> ctx.reg[s];
136
+ }`,
137
+ load: `function load() {
138
+ //@FETCH_START
139
+ var d = readByte();
140
+ var a = readByte();
141
+ //@FETCH_END
142
+ if (ctx.reg[a] < 0 || ctx.reg[a] >= 65536) crash(ctx);
143
+ ctx.reg[d] = ctx.heap[ctx.reg[a] & 0xFFFF];
144
+ }`,
145
+ store: `function store() {
146
+ //@FETCH_START
147
+ var a = readByte();
148
+ var s = readByte();
149
+ //@FETCH_END
150
+ if (ctx.reg[a] < 0 || ctx.reg[a] >= 65536) crash(ctx);
151
+ ctx.heap[ctx.reg[a] & 0xFFFF] = ctx.reg[s];
152
+ }`,
153
+ cmp: `function cmp() {
154
+ //@FETCH_START
155
+ var a = readByte();
156
+ var b = readByte();
157
+ //@FETCH_END
158
+ var v1 = ctx.reg[a]; var v2 = ctx.reg[b];
159
+ if (v1 === v2) ctx.reg[255] = 0;
160
+ else if (v1 < v2) ctx.reg[255] = -1;
161
+ else ctx.reg[255] = 1;
162
+ }`,
163
+ inc: `function inc() {
164
+ //@FETCH_START
165
+ var r = readByte();
166
+ //@FETCH_END
167
+ ctx.reg[r]++;
168
+ }`,
169
+ dec: `function dec() {
170
+ //@FETCH_START
171
+ var r = readByte();
172
+ //@FETCH_END
173
+ ctx.reg[r]--;
174
+ }`,
175
+ jmp: `function jmp() {
176
+ //@FETCH_START
177
+ var k = readInt32();
178
+ var t = readInt32();
179
+ //@FETCH_END
180
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
181
+ ctx.xk = k>>>0; ctx.ip = t;
182
+ }`,
183
+ jz: `function jz() {
184
+ //@FETCH_START
185
+ var r = readByte();
186
+ var k = readInt32();
187
+ var t = readInt32();
188
+ //@FETCH_END
189
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
190
+ if (!ctx.reg[r]) { ctx.ip = t; ctx.xk = k>>>0; }
191
+ }`,
192
+ jnz: `function jnz() {
193
+ //@FETCH_START
194
+ var r = readByte();
195
+ var k = readInt32();
196
+ var t = readInt32();
197
+ //@FETCH_END
198
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
199
+ if (ctx.reg[r]) { ctx.ip = t; ctx.xk = k>>>0; }
200
+ }`,
201
+ jlt: `function jlt() {
202
+ //@FETCH_START
203
+ var k = readInt32();
204
+ var t = readInt32();
205
+ //@FETCH_END
206
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
207
+ if (ctx.reg[255] < 0) { ctx.ip = t; ctx.xk = k>>>0; }
208
+ }`,
209
+ jgt: `function jgt() {
210
+ //@FETCH_START
211
+ var k = readInt32();
212
+ var t = readInt32();
213
+ //@FETCH_END
214
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
215
+ if (ctx.reg[255] > 0) { ctx.ip = t; ctx.xk = k>>>0; }
216
+ }`,
217
+ // Note: jz/jnz/jlt/jgt/jmp now use INT for k (32-bit LCG checkpoint).
218
+ push: `function push() {
219
+ //@FETCH_START
220
+ var r = readByte();
221
+ //@FETCH_END
222
+ if (ctx.stack.length >= MAX_STACK) crash(ctx);
223
+ ctx.stack.push(ctx.reg[r]);
224
+ }`,
225
+ pop: `function pop() {
226
+ //@FETCH_START
227
+ var r = readByte();
228
+ //@FETCH_END
229
+ var v = ctx.stack.pop(); ctx.reg[r] = (v !== undefined) ? v : 0;
230
+ }`,
231
+ calli: `function calli() {
232
+ //@FETCH_START
233
+ var o = readByte();
234
+ var c = readByte();
235
+ //@FETCH_END
236
+ var fn = ctx.reg[o]; var args = new Array(c);
237
+ for (var i = c - 1; i >= 0; i--) args[i] = ctx.stack.pop();
238
+ var addr = (fn >> 8) & 0xFFFFFF; var key = fn & 0xFF;
239
+ for (var i = 0; i < args.length; i++) ctx.stack.push(args[i]);
240
+ ctx.stack.push(null);
241
+ if (ctx.frames.length >= MAX_FRAMES) crash(ctx);
242
+ ctx.frames.push(ctx.ip, ctx.xk); ctx.ip = addr; ctx.xk = key;
243
+ }`,
244
+ calle: `function calle() {
245
+ //@FETCH_START
246
+ var o = readByte();
247
+ var c = readByte();
248
+ //@FETCH_END
249
+ var fn = ctx.reg[o]; var args = new Array(c);
250
+ for (var i = c - 1; i >= 0; i--) args[i] = ctx.stack.pop();
251
+ ctx.reg[0] = (typeof fn === 'function') ? fn.apply(null, args) : undefined;
252
+ }`,
253
+ ret: `function ret() {
254
+ var len = ctx.frames.length;
255
+ if (len >= 2) { ctx.xk = ctx.frames[len-1]; ctx.ip = ctx.frames[len-2]; ctx.frames.length = len-2; }
256
+ else { ctx.ip = ctx.mem.length + 1; }
257
+ }`,
258
+ out: `function out() {
259
+ //@FETCH_START
260
+ var r = readByte();
261
+ //@FETCH_END
262
+ console.log(ctx.reg[r]);
263
+ }`,
264
+ halt: `function halt() { ctx.ip = ctx.mem.length + 1; }`,
265
+ str: `function str() {
266
+ //@FETCH_START
267
+ var d = readByte();
268
+ //@FETCH_END
269
+ ctx.reg[d] = readStr();
270
+ }`,
271
+ getGlobal: `function getGlobal() {
272
+ //@FETCH_START
273
+ var d = readByte();
274
+ var n = readByte();
275
+ //@FETCH_END
276
+ ctx.reg[d] = ctx.env[ctx.reg[n]];
277
+ }`,
278
+ getProp: `function getProp() {
279
+ //@FETCH_START
280
+ var d = readByte();
281
+ var o = readByte();
282
+ var p = readByte();
283
+ //@FETCH_END
284
+ var obj = ctx.reg[o]; var key = ctx.reg[p];
285
+ ctx.reg[d] = (obj == null) ? undefined : obj[key];
286
+ }`,
287
+ setProp: `function setProp() {
288
+ //@FETCH_START
289
+ var o = readByte();
290
+ var p = readByte();
291
+ var v = readByte();
292
+ //@FETCH_END
293
+ var obj = ctx.reg[o]; if (obj != null) obj[ctx.reg[p]] = ctx.reg[v];
294
+ }`,
295
+ callMethod: `function callMethod() {
296
+ //@FETCH_START
297
+ var d = readByte();
298
+ var o = readByte();
299
+ var m = readByte();
300
+ var c = readByte();
301
+ //@FETCH_END
302
+ var obj = ctx.reg[o]; var name = ctx.reg[m];
303
+ var args = new Array(c); for (var i = c-1; i>=0; i--) args[i] = ctx.stack.pop();
304
+ var mfn = obj != null ? obj[name] : undefined;
305
+ ctx.reg[d] = (typeof mfn === 'function') ? mfn.apply(obj, args) : undefined;
306
+ }`,
307
+ toNum: `function toNum() {
308
+ //@FETCH_START
309
+ var d = readByte();
310
+ var s = readByte();
311
+ //@FETCH_END
312
+ ctx.reg[d] = Number(ctx.reg[s]);
313
+ }`,
314
+ fromNum: `function fromNum() {
315
+ //@FETCH_START
316
+ var d = readByte();
317
+ var s = readByte();
318
+ //@FETCH_END
319
+ ctx.reg[d] = ctx.reg[s];
320
+ }`,
321
+ callAddr: `function callAddr() {
322
+ //@FETCH_START
323
+ var k = readInt32();
324
+ var t = readInt32();
325
+ //@FETCH_END
326
+ if (ctx.frames.length >= MAX_FRAMES) crash(ctx);
327
+ if (t < 0 || t >= ctx.mem.length) crash(ctx);
328
+ ctx.frames.push(ctx.ip, ctx.xk); ctx.ip = t; ctx.xk = k>>>0;
329
+ }`,
330
+ newObject: `function newObject() {
331
+ //@FETCH_START
332
+ var d = readByte();
333
+ var c = readByte();
334
+ var n = readByte();
335
+ //@FETCH_END
336
+ var C = ctx.reg[c]; var args = new Array(n);
337
+ for (var i = n-1; i>=0; i--) args[i] = ctx.stack.pop();
338
+ ctx.reg[d] = new (Function.prototype.bind.apply(C, [null].concat(args)));
339
+ }`,
340
+ instanceOf: `function instanceOf() {
341
+ //@FETCH_START
342
+ var d = readByte();
343
+ var o = readByte();
344
+ var c = readByte();
345
+ //@FETCH_END
346
+ ctx.reg[d] = (ctx.reg[o] instanceof ctx.reg[c]) ? 1 : 0;
347
+ }`,
348
+ callRegInternal: `function callRegInternal() {
349
+ //@FETCH_START
350
+ var f = readByte();
351
+ var n = readByte();
352
+ //@FETCH_END
353
+ var fn = ctx.reg[f]; var args = new Array(n);
354
+ for (var i = n-1; i>=0; i--) args[i] = ctx.stack.pop();
355
+ var addr = (fn >> 8) & 0xFFFFFF; var key = fn & 0xFF;
356
+ for (var i = 0; i < args.length; i++) ctx.stack.push(args[i]);
357
+ ctx.stack.push(undefined);
358
+ if (ctx.frames.length >= MAX_FRAMES) crash(ctx);
359
+ ctx.frames.push(ctx.ip, ctx.xk); ctx.ip = addr; ctx.xk = key;
360
+ }`,
361
+ callRegExternal: `function callRegExternal() {
362
+ //@FETCH_START
363
+ var d = readByte();
364
+ var f = readByte();
365
+ var n = readByte();
366
+ //@FETCH_END
367
+ var fn = ctx.reg[f]; var args = new Array(n);
368
+ for (var i = n-1; i>=0; i--) args[i] = ctx.stack.pop();
369
+ ctx.reg[d] = (typeof fn === 'function') ? fn.apply(undefined, args) : undefined;
370
+ }`,
371
+ typeOf: `function typeOf() {
372
+ //@FETCH_START
373
+ var d = readByte();
374
+ var s = readByte();
375
+ //@FETCH_END
376
+ ctx.reg[d] = typeof ctx.reg[s];
377
+ }`,
378
+ toBool: `function toBool() {
379
+ //@FETCH_START
380
+ var d = readByte();
381
+ var v = readByte();
382
+ //@FETCH_END
383
+ ctx.reg[d] = v !== 0;
384
+ }`,
385
+ newFunc: `function newFunc() {
386
+ //@FETCH_START
387
+ var d = readByte();
388
+ var a = readInt32();
389
+ var k = readByte();
390
+ var n = readByte();
391
+ //@FETCH_END
392
+ ctx.reg[d] = ((a & 0xFFFFFF) << 8) | (k & 0xFF);
393
+ }`,
394
+ _eval: `function _eval() {
395
+ //@FETCH_START
396
+ var s = readStr();
397
+ //@FETCH_END
398
+ eval(s);
399
+ }`,
400
+ };
401
+ // ── SAFE-TO-INLINE handlers (last fetch var can be inlined) ──────────────────
402
+ const SAFE_INLINE = new Set([
403
+ "mov", "movr", "add", "sub", "mul", "div", "mod", "nor", "xor", "and", "or", "shl", "shr", "sar", "inc", "dec", "push", "pop", "str",
404
+ ]);
405
+ // ── HELPERS ───────────────────────────────────────────────────────────────────
406
+ function fisherYates(arr, rng) {
407
+ const out = arr.slice();
408
+ for (let i = out.length - 1; i > 0; i--) {
409
+ const j = Math.floor(rng.next() * (i + 1));
410
+ const tmp = out[i];
411
+ out[i] = out[j];
412
+ out[j] = tmp;
413
+ }
414
+ return out;
415
+ }
416
+ function uniqueOpcodes(n, rng) {
417
+ const buf = [];
418
+ for (let i = 0; i < 256; i++)
419
+ buf.push(i);
420
+ return fisherYates(buf, rng).slice(0, n);
421
+ }
422
+ function randOddInt32(rng) {
423
+ return ((Math.floor(rng.next() * 0x7fffffff) * 2 + 1) >>> 0);
424
+ }
425
+ function mathScramble(val, rng) {
426
+ if (rng.next() < 0.5) {
427
+ const add = Math.floor(rng.next() * 500000);
428
+ return `(${val - add} + ${add})`;
429
+ }
430
+ else {
431
+ const sub = Math.floor(rng.next() * 500000);
432
+ return `(${val + sub} - ${sub})`;
433
+ }
434
+ }
435
+ function argName(line) {
436
+ const m = line.match(/var\s+(\w+)\s*=/);
437
+ return m ? m[1] : null;
438
+ }
439
+ function argType(line) {
440
+ if (line.includes("readInt32()"))
441
+ return "INT";
442
+ if (line.includes("readByte()"))
443
+ return "BYTE";
444
+ if (line.includes("readStr()"))
445
+ return "STRING";
446
+ return null;
447
+ }
448
+ // Shuffle the @FETCH_START...@FETCH_END block inside a handler source string.
449
+ // Returns { shuffled source, argLayout }
450
+ function shuffleFetchBlock(handlerName, src, rng) {
451
+ const lines = src.split("\n");
452
+ const out = [];
453
+ let inBlock = false;
454
+ let block = [];
455
+ const layout = [];
456
+ for (const line of lines) {
457
+ if (line.includes("@FETCH_START")) {
458
+ inBlock = true;
459
+ out.push(line);
460
+ block = [];
461
+ }
462
+ else if (line.includes("@FETCH_END")) {
463
+ inBlock = false;
464
+ const shuffled = fisherYates(block, rng);
465
+ for (const sl of shuffled) {
466
+ const n = argName(sl), ty = argType(sl);
467
+ if (n && ty)
468
+ layout.push({ name: n, type: ty });
469
+ }
470
+ for (const sl of shuffled)
471
+ out.push(sl);
472
+ out.push(line);
473
+ }
474
+ else if (inBlock) {
475
+ block.push(line);
476
+ }
477
+ else {
478
+ out.push(line);
479
+ }
480
+ }
481
+ return { code: out.join("\n"), layout };
482
+ }
483
+ // Optionally inline the last fetched var into its usage site (80% probability).
484
+ function removeMarkers(handlerName, src, rng) {
485
+ const lines = src.split("\n");
486
+ let preFetch = [], fetchLines = [], postFetch = [];
487
+ let state = 0;
488
+ for (const line of lines) {
489
+ const cl = line.replace(/\r$/, "");
490
+ if (cl.includes("@FETCH_START")) {
491
+ state = 1;
492
+ }
493
+ else if (cl.includes("@FETCH_END")) {
494
+ state = 2;
495
+ }
496
+ else if (state === 0)
497
+ preFetch.push(cl);
498
+ else if (state === 1)
499
+ fetchLines.push(cl);
500
+ else
501
+ postFetch.push(cl);
502
+ }
503
+ if (SAFE_INLINE.has(handlerName) && fetchLines.length > 0 && rng.next() < 0.8) {
504
+ const lastFetch = fetchLines[fetchLines.length - 1];
505
+ const m = lastFetch.match(/var\s+(\w+)\s*=\s*(read\w+\(\));/);
506
+ if (m) {
507
+ const [, varName, fetchCall] = m;
508
+ const regex = new RegExp(`\\b${varName}\\b`, "g");
509
+ const postStr = postFetch.join("\n");
510
+ const matches = postStr.match(regex);
511
+ if (matches && matches.length === 1) {
512
+ postFetch = postStr.replace(regex, fetchCall).split("\n");
513
+ fetchLines.pop();
514
+ }
515
+ }
516
+ }
517
+ return [...preFetch, ...fetchLines, ...postFetch].join("\n");
518
+ }
519
+ // Rename ctx property names throughout a source string.
520
+ function renameCtxProps(src, propMap) {
521
+ const ctxProps = ["reg", "mem", "heap", "stack", "frames", "env", "ip", "xk"];
522
+ for (const p of ctxProps) {
523
+ const repl = propMap[p];
524
+ src = src.replace(new RegExp(`\\.${p}\\b`, "g"), `.${repl}`);
525
+ src = src.replace(new RegExp(`'${p}'`, "g"), `'${repl}'`);
526
+ src = src.replace(new RegExp(`"${p}"`, "g"), `"${repl}"`);
527
+ src = src.replace(new RegExp(`\\b${p}:`, "g"), `${repl}:`);
528
+ }
529
+ return src;
530
+ }
531
+ // Apply AST-level polymorphism (textual substitutions).
532
+ function applyASTPolymorphism(src, rng) {
533
+ if (rng.next() < 0.5)
534
+ src = src.replace(/\(raw \^ ctx\.xk\)/g, "(ctx.xk ^ raw)");
535
+ else
536
+ src = src.replace(/\(raw \^ ctx\.xk\)/g, "(~(~raw ^ ctx.xk))");
537
+ if (rng.next() < 0.5)
538
+ src = src.replace(/\(m\[p\] \^ k\)/g, "(k ^ m[p])");
539
+ else
540
+ src = src.replace(/\(m\[p\] \^ k\)/g, "(~(~k ^ m[p]))");
541
+ if (rng.next() < 0.5)
542
+ src = src.replace(/if \(ctx\.ip >= ctx\.mem\.length\)/g, "if (ctx.mem.length <= ctx.ip)");
543
+ if (rng.next() < 0.5)
544
+ src = src.replace(/if \(p \+ 4 > m\.length\)/g, "if (m.length < p + 4)");
545
+ if (rng.next() < 0.5)
546
+ src = src.replace(/while \(ctx\.ip < l\)/g, "for (; ctx.ip < l ;)");
547
+ if (rng.next() < 0.5)
548
+ src = src.replace(/if \(\!h\)/g, "if (h === undefined)");
549
+ if (rng.next() < 0.5)
550
+ src = src.replace(/if \(\+\+c >= max\)/g, "c++; if (c >= max)");
551
+ return src;
552
+ }
553
+ // Generate randomised error array.
554
+ function generateErrorArray(rng) {
555
+ const msgs = [
556
+ "Memory Exhausted " + rng.next().toString(36).slice(2, 6),
557
+ "Stack Bounds Exceeded " + rng.next().toString(36).slice(2, 6),
558
+ "Illegal Instruction " + rng.next().toString(36).slice(2, 6),
559
+ "Access Violation " + rng.next().toString(36).slice(2, 6),
560
+ ];
561
+ const arrs = msgs.map(m => "[" + m.split("").map(c => "0x" + c.charCodeAt(0).toString(16)).join(", ") + "]");
562
+ if (rng.next() < 0.5) {
563
+ return `var _err = [\n ${arrs.join(",\n ")}\n];`;
564
+ }
565
+ else {
566
+ return `var _err = [];\n` + arrs.map(a => `_err.push(${a});`).join("\n");
567
+ }
568
+ }
569
+ // Generate random ctx property names.
570
+ function generateCtxPropMap(rng) {
571
+ const ctxProps = ["reg", "mem", "heap", "stack", "frames", "env", "ip", "xk"];
572
+ const used = new Set();
573
+ const map = {};
574
+ for (const p of ctxProps) {
575
+ let hex;
576
+ do {
577
+ hex = "_" + Math.floor(rng.next() * 0xffffff).toString(16);
578
+ } while (used.has(hex));
579
+ used.add(hex);
580
+ map[p] = hex;
581
+ }
582
+ return map;
583
+ }
584
+ // Generate ctx initialisation code (random form).
585
+ function generateCtxInit(key, propMap, rng) {
586
+ let keyStr;
587
+ const r = rng.next();
588
+ if (r < 0.33) {
589
+ const add = Math.floor(rng.next() * 100000);
590
+ keyStr = `(${key - add} + ${add})`;
591
+ }
592
+ else if (r < 0.66) {
593
+ const sub = Math.floor(rng.next() * 100000);
594
+ keyStr = `(${key + sub} - ${sub})`;
595
+ }
596
+ else {
597
+ keyStr = String(key);
598
+ }
599
+ const pm = propMap;
600
+ const props = [
601
+ [pm.reg, "new Array(256).fill(0)"],
602
+ [pm.mem, "null"],
603
+ [pm.ip, "0"],
604
+ [pm.xk, keyStr],
605
+ [pm.heap, "new Array(65536).fill(0)"],
606
+ [pm.stack, "[]"],
607
+ [pm.frames, "[]"],
608
+ [pm.env, "(typeof globalThis !== 'undefined') ? globalThis : (typeof window !== 'undefined' ? window : global)"],
609
+ ];
610
+ const shuffled = fisherYates(props, rng);
611
+ let initCode;
612
+ if (rng.next() < 0.5) {
613
+ const lines = shuffled.map(([k, v]) => `${k}: ${v}`);
614
+ initCode = `var ctx = {\n ${lines.join(",\n ")}\n};`;
615
+ }
616
+ else {
617
+ const lines = shuffled.map(([k, v]) => `ctx.${k} = ${v};`);
618
+ initCode = `var ctx = {};\n${lines.join("\n")}`;
619
+ }
620
+ return { initCode, keyStr };
621
+ }
622
+ function generateConsts(mul, inc, rng) {
623
+ const stack = 100000 + Math.floor(rng.next() * 50000);
624
+ const frames = 10000 + Math.floor(rng.next() * 5000);
625
+ const lines = [
626
+ `var MAX_STACK = ${mathScramble(stack, rng)};`,
627
+ `var MAX_FRAMES = ${mathScramble(frames, rng)};`,
628
+ `var LCG_MUL = ${mathScramble(mul, rng)};`,
629
+ `var LCG_INC = ${mathScramble(inc, rng)};`,
630
+ ];
631
+ return fisherYates(lines, rng).join("\n");
632
+ }
633
+ /** Generate a per-build map of all internal VM identifiers → random hex names. */
634
+ function generateVmNameMap(rng) {
635
+ const used = new Set();
636
+ function fresh() {
637
+ let name;
638
+ do {
639
+ // Format: _ + 4-6 hex chars → looks like any other obfuscated var
640
+ const len = 4 + Math.floor(rng.next() * 3);
641
+ name = "_" + Math.floor(rng.next() * 0xffffff).toString(16).padStart(len, "0");
642
+ } while (used.has(name));
643
+ used.add(name);
644
+ return name;
645
+ }
646
+ return {
647
+ ctx: fresh(), ops: fresh(), t1: fresh(),
648
+ t2: fresh(), SK: fresh(), runVM: fresh(),
649
+ _init: fresh(), readByte: fresh(), readInt32: fresh(),
650
+ readStr: fresh(), crash: fresh(), _s: fresh(),
651
+ _err: fresh(), _h1: fresh(), _hash: fresh(),
652
+ _ss: fresh(), _ver: fresh(),
653
+ MAX_STACK: fresh(), MAX_FRAMES: fresh(), LCG_MUL: fresh(), LCG_INC: fresh(),
654
+ kv_run: fresh(), kv_args: fresh(), kv_ret: fresh(),
655
+ kv_g: fresh(), kv_fn: fresh(),
656
+ };
657
+ }
658
+ /**
659
+ * Apply the name map to a VM source string.
660
+ * Replacements are done longest-first to avoid partial matches
661
+ * (e.g. readInt32 must be replaced before readByte/readStr).
662
+ */
663
+ function applyVmNameMap(src, nm) {
664
+ // Build list of [original, replacement] sorted by length descending
665
+ const pairs = [
666
+ ["readInt32", nm.readInt32],
667
+ ["readByte", nm.readByte],
668
+ ["readStr", nm.readStr],
669
+ ["MAX_STACK", nm.MAX_STACK],
670
+ ["MAX_FRAMES", nm.MAX_FRAMES],
671
+ ["LCG_MUL", nm.LCG_MUL],
672
+ ["LCG_INC", nm.LCG_INC],
673
+ ["runVM", nm.runVM],
674
+ ["_init", nm._init],
675
+ ["crash", nm.crash],
676
+ ["_hash", nm._hash],
677
+ ["_ss", nm._ss],
678
+ ["_ver", nm._ver],
679
+ ["_err", nm._err],
680
+ ["_h1", nm._h1],
681
+ ["_s", nm._s],
682
+ // split-table names (longer first to avoid partial matches)
683
+ ["ops", nm.ops],
684
+ ["ctx", nm.ctx],
685
+ ];
686
+ for (const [orig, repl] of pairs) {
687
+ src = src.replace(new RegExp(`\\b${orig}\\b`, "g"), repl);
688
+ }
689
+ // t1/t2/SK are injected via template substitution, not regex rename
690
+ return src;
691
+ }
692
+ // ── CORE VM TEMPLATE ─────────────────────────────────────────────────────────
693
+ // All identifiers here use the canonical names from VmNameMap keys.
694
+ // applyVmNameMap() renames them to per-build random identifiers after generation.
695
+ const CORE_TEMPLATE = `
696
+ // @INJECT_ERR_START
697
+ var _err = [[0x4f,0x6f,0x6d]];
698
+ // @INJECT_ERR_END
699
+
700
+ function _s(b){var r='';for(var i=0;i<b.length;i++)r+=String.fromCharCode(b[i]);return r;}
701
+
702
+ function crash(ctx){
703
+ if(ctx){ctx.mem=null;if(ctx.reg)ctx.reg.fill(0);if(ctx.stack)ctx.stack.length=0;if(ctx.heap)ctx.heap.fill(0);}
704
+ throw new Error(_s(_err[(Math.random()*_err.length)|0]));
705
+ }
706
+
707
+ var _h1=0;
708
+ function _hash(m){if(!m)return 0;var c=0;for(var i=0;i<m.length;i++)c=((c<<5)-c+m[i])|0;return c;}
709
+ function _ss(m,arr){_h1=_hash(m);}
710
+ function _ver(ctx,arr){if(_hash(ctx.mem)!==_h1)crash(ctx);}
711
+
712
+ // @INJECT_CTX_START
713
+ var INIT_KEY=0;
714
+ var ctx={reg:new Array(256).fill(0),mem:null,ip:0,xk:INIT_KEY,heap:new Array(65536).fill(0),stack:[],frames:[],env:(typeof globalThis!=='undefined')?globalThis:(typeof window!=='undefined'?window:global)};
715
+ // @INJECT_CTX_END
716
+
717
+ // @INJECT_CONSTS_START
718
+ var MAX_STACK=100000;var MAX_FRAMES=10000;var LCG_MUL=1664525;var LCG_INC=1013904223;
719
+ // @INJECT_CONSTS_END
720
+
721
+ function readByte(){
722
+ if(ctx.ip>=ctx.mem.length)crash(ctx);
723
+ var raw=ctx.mem[ctx.ip++];
724
+ var dec=(raw^(ctx.xk>>>24))&0xFF;
725
+ ctx.xk=((Math.imul(ctx.xk,LCG_MUL)+LCG_INC)|0)>>>0;
726
+ return dec;
727
+ }
728
+
729
+ function readInt32(){
730
+ var m=ctx.mem,p=ctx.ip,k=ctx.xk;
731
+ if(p+4>m.length)crash(ctx);
732
+ var b1=(m[p]^(k>>>24))&0xFF;k=((Math.imul(k,LCG_MUL)+LCG_INC)|0)>>>0;
733
+ var b2=(m[p+1]^(k>>>24))&0xFF;k=((Math.imul(k,LCG_MUL)+LCG_INC)|0)>>>0;
734
+ var b3=(m[p+2]^(k>>>24))&0xFF;k=((Math.imul(k,LCG_MUL)+LCG_INC)|0)>>>0;
735
+ var b4=(m[p+3]^(k>>>24))&0xFF;k=((Math.imul(k,LCG_MUL)+LCG_INC)|0)>>>0;
736
+ ctx.ip=p+4;ctx.xk=k;
737
+ return (b1|(b2<<8)|(b3<<16)|(b4<<24));
738
+ }
739
+
740
+ function readStr(){
741
+ var len=readInt32();var c=[];
742
+ for(var i=0;i<len;i++)c.push(String.fromCharCode(readByte()));
743
+ return c.join('');
744
+ }
745
+
746
+ // @INJECT_SPLIT_DECL
747
+ var ops=new Array(256);
748
+ for(var i=0;i<256;i++){ops[i]=function(){crash(ctx);};}
749
+
750
+ function _init(b64){
751
+ var raw=atob(b64);
752
+ ctx.mem=new Uint8Array(raw.length);
753
+ for(var i=0;i<raw.length;i++)ctx.mem[i]=raw.charCodeAt(i);
754
+ ctx.ip=0;
755
+ // @INJECT_INIT_XK
756
+ ctx.reg.fill(0);ctx.heap.fill(0);ctx.stack=[];ctx.frames=[];
757
+ // @INJECT_SPLIT_MERGE
758
+ _ss(ctx.mem,ops);
759
+ }
760
+
761
+ function runVM(b64){
762
+ _init(b64);
763
+ var l=ctx.mem.length;
764
+ while(ctx.ip<l){
765
+ var op=readByte();var h=ops[op];
766
+ if(!h)crash(ctx);h();
767
+ }
768
+ _ver(ctx,ops);
769
+ }
770
+ `;
771
+ /**
772
+ * Generate a fresh polymorphic KrakVm runtime + config.
773
+ * Every call produces a structurally distinct VM with different:
774
+ * - opcode byte assignments
775
+ * - arg-fetch ordering per handler
776
+ * - ctx property names
777
+ * - LCG parameters
778
+ * - AST expression forms
779
+ */
780
+ function generateVm(rng) {
781
+ const initialKey = rng.int32() >>> 0; // full 32-bit initial key
782
+ const lcgMul = randOddInt32(rng);
783
+ const lcgInc = randOddInt32(rng);
784
+ // Per-build identifier name map (hides readByte, runVM, ctx, ops, etc.)
785
+ const nameMap = generateVmNameMap(rng);
786
+ // Assign opcode byte values
787
+ const opcodeVals = uniqueOpcodes(exports.OPCODE_NAMES.length, rng);
788
+ const opcodes = {};
789
+ exports.OPCODE_NAMES.forEach((name, i) => { opcodes[name] = opcodeVals[i]; });
790
+ // Build prop rename map (ctx.reg, ctx.mem, etc.)
791
+ const propMap = generateCtxPropMap(rng);
792
+ // Process each handler: shuffle fetches, collect layouts
793
+ const handlerBlocks = {};
794
+ for (const [hn, src] of Object.entries(HANDLER_SRCS)) {
795
+ const { code, layout } = shuffleFetchBlock(hn, src, rng);
796
+ handlerBlocks[hn] = { code, layout };
797
+ }
798
+ // Build argLayouts (keyed by opcode name)
799
+ const argLayouts = {};
800
+ for (const [hn, { layout }] of Object.entries(handlerBlocks)) {
801
+ const opName = exports.HANDLER_MAP[hn];
802
+ if (opName)
803
+ argLayouts[opName] = layout;
804
+ }
805
+ // Per-build split key: each opcode index is XORed with this before being
806
+ // stored in t1 or t2 so the slot numbers are not the real opcode values.
807
+ const splitKey = (rng.int32() >>> 0) & 0xFF;
808
+ // t1Name/t2Name/SKName will be substituted literally after nameMap apply.
809
+ const t1Name = nameMap.t1;
810
+ const t2Name = nameMap.t2;
811
+ const skName = nameMap.SK;
812
+ const opsName = nameMap.ops;
813
+ // Separate dynamic (40%) vs static handler assignments
814
+ const dynamicOps = {};
815
+ const staticOpsArr = [];
816
+ for (const [hn, { code }] of Object.entries(handlerBlocks)) {
817
+ const opName = exports.HANDLER_MAP[hn];
818
+ if (!opName)
819
+ continue;
820
+ const opcodeVal = opcodes[opName];
821
+ // Apply per-handler transformations
822
+ let processed = removeMarkers(hn, code, rng);
823
+ processed = applyASTPolymorphism(processed, rng);
824
+ processed = renameCtxProps(processed, propMap);
825
+ // Each handler goes into t1 or t2 (50/50 per-build random split).
826
+ // The slot index is opcodeVal ^ splitKey — not the raw opcode byte.
827
+ const tbl = rng.next() < 0.5 ? t1Name : t2Name;
828
+ const slot = opcodeVal ^ splitKey;
829
+ const assignment = `${tbl}[${slot}] = ${processed}`;
830
+ if (opName !== "EVAL" && opName !== "JMP" && rng.next() < 0.4) {
831
+ dynamicOps[opName] = { opcodeVal, src: `${tbl}[${slot}] = ${processed};` };
832
+ }
833
+ else {
834
+ staticOpsArr.push(assignment);
835
+ }
836
+ }
837
+ // Shuffle static assignments
838
+ const shuffledStatics = fisherYates(staticOpsArr, rng);
839
+ // ── Split-table declarations and merge code ──────────────────────────────
840
+ // t1 and t2 are null-initialised (not crash-filled) so null is a reliable
841
+ // sentinel — exactly one of t1[slot]/t2[slot] holds the real handler.
842
+ // ops[] is crash-filled separately.
843
+ const splitDecl = [
844
+ `var ${skName} = ${mathScramble(splitKey, rng)};`,
845
+ `var ${t1Name} = new Array(${mathScramble(256, rng)}).fill(null);`,
846
+ `var ${t2Name} = new Array(${mathScramble(256, rng)}).fill(null);`,
847
+ ].join("\n");
848
+ // Merge: ops is already crash-filled from its own init loop.
849
+ // For each slot i, exactly one of t1[i] or t2[i] is non-null (the real handler).
850
+ // Copy whichever is non-null into ops[i ^ SK].
851
+ const mergeCodeClean = [
852
+ `for(var _mi=0;_mi<256;_mi++){`,
853
+ ` if(${t1Name}[_mi]!==null)${opsName}[_mi^${skName}]=${t1Name}[_mi];`,
854
+ ` else if(${t2Name}[_mi]!==null)${opsName}[_mi^${skName}]=${t2Name}[_mi];`,
855
+ `}`,
856
+ ].join("\n");
857
+ // Build core template with all substitutions
858
+ const { initCode, keyStr } = generateCtxInit(initialKey, propMap, rng);
859
+ let core = CORE_TEMPLATE;
860
+ core = core.replace(/\/\/ @INJECT_ERR_START[\s\S]*?\/\/ @INJECT_ERR_END/, generateErrorArray(rng));
861
+ core = core.replace(/\/\/ @INJECT_CONSTS_START[\s\S]*?\/\/ @INJECT_CONSTS_END/, generateConsts(lcgMul, lcgInc, rng));
862
+ core = core.replace(/\/\/ @INJECT_CTX_START[\s\S]*?\/\/ @INJECT_CTX_END/, initCode);
863
+ core = core.replace(/\/\/ @INJECT_INIT_XK/g, `${nameMap.ctx}.${propMap.xk} = ${keyStr};`);
864
+ core = core.replace(/\/\/ @INJECT_SPLIT_DECL/g, splitDecl);
865
+ core = core.replace(/\/\/ @INJECT_SPLIT_MERGE/g, mergeCodeClean);
866
+ core = applyASTPolymorphism(core, rng);
867
+ core = renameCtxProps(core, propMap);
868
+ // Scramble the heap literal size.
869
+ core = core.replace(/\bnew Array\(65536\)/g, `new Array(${mathScramble(65536, rng)})`);
870
+ // Note: new Array(256) in split decl is already mathScramble'd above.
871
+ core = core.replace(/\/\/ @INJECT_INIT_XK/g, `${nameMap.ctx}.${propMap.xk} = ${keyStr};`);
872
+ // Assemble full runtime; then apply nameMap over everything at once.
873
+ // This renames: readByte, readInt32, readStr, runVM, _init, ops, ctx,
874
+ // crash, _s, _err, _h1, _hash, _ss, _ver, MAX_STACK, MAX_FRAMES,
875
+ // LCG_MUL, LCG_INC — all in one pass, longest-first.
876
+ // t1/t2/SK are already in the template as literal names — nameMap renames ops/ctx etc.
877
+ let runtimeSrc = core + "\n" + shuffledStatics.join("\n") + "\n";
878
+ runtimeSrc = applyVmNameMap(runtimeSrc, nameMap);
879
+ // Apply nameMap to dynamicOps sources too
880
+ for (const key of Object.keys(dynamicOps)) {
881
+ dynamicOps[key].src = applyVmNameMap(dynamicOps[key].src, nameMap);
882
+ }
883
+ const config = {
884
+ initialKey,
885
+ lcgMul,
886
+ lcgInc,
887
+ opcodes: opcodes,
888
+ argLayouts,
889
+ dynamicOps,
890
+ };
891
+ return { runtimeSrc, config, nameMap };
892
+ }