@dacely/toildefender 0.1.6 → 0.1.7

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.
@@ -8,19 +8,20 @@ var estest = require("../estest");
8
8
  var traverser = require("../traverser");
9
9
 
10
10
  var RUNTIME = `
11
- function veilmark$numericVmString(program, length, salt) {
11
+ function toildefender$numericVmString(program, length, salt) {
12
12
  var out = "";
13
13
  var i = 0;
14
+ var base = BigInt(65537);
14
15
  while (i < length) {
15
- var encoded = Number(program % BigInt(65537));
16
- program = program / BigInt(65537);
16
+ var encoded = Number(program % base);
17
+ program = program / base;
17
18
  out += String.fromCharCode(encoded ^ ((salt + i * 97) & 65535));
18
19
  i += 1;
19
20
  }
20
21
  return out;
21
22
  }
22
23
 
23
- function veilmark$numericVmPow(a, b) {
24
+ function toildefender$numericVmPow(a, b) {
24
25
  if (typeof a === "bigint" && typeof b === "bigint") {
25
26
  if (b < BigInt(0)) throw new RangeError("BigInt exponent must be positive");
26
27
  var out = BigInt(1);
@@ -36,7 +37,7 @@ function veilmark$numericVmPow(a, b) {
36
37
  return Math.pow(a, b);
37
38
  }
38
39
 
39
- function veilmark$numericVmDigit(program, baseBig, index, powers) {
40
+ function toildefender$numericVmDigit(program, baseBig, index, powers) {
40
41
  if (powers) {
41
42
  while (powers.length <= index) {
42
43
  powers[powers.length] = powers[powers.length - 1] * baseBig;
@@ -51,73 +52,81 @@ function veilmark$numericVmDigit(program, baseBig, index, powers) {
51
52
  return Number((program / pow) % baseBig);
52
53
  }
53
54
 
54
- function veilmark$hashMeshMix(current, value) {
55
+ function toildefender$hashMeshMix(current, value) {
55
56
  var h = (current ^ value) >>> 0;
56
57
  h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
57
58
  h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
58
59
  return (h ^ (h >>> 16)) >>> 0;
59
60
  }
60
61
 
61
- function veilmark$hashMeshValue(hash, value) {
62
- if (typeof value === "number") return veilmark$hashMeshMix(hash, value >>> 0);
62
+ function toildefender$hashMeshValue(hash, value) {
63
+ if (typeof value === "number") return toildefender$hashMeshMix(hash, value >>> 0);
63
64
  if (typeof value === "string") {
64
- hash = veilmark$hashMeshMix(hash, value.length >>> 0);
65
+ hash = toildefender$hashMeshMix(hash, value.length >>> 0);
65
66
  var j = 0;
66
67
  while (j < value.length) {
67
- hash = veilmark$hashMeshMix(hash, value.charCodeAt(j));
68
+ hash = toildefender$hashMeshMix(hash, value.charCodeAt(j));
68
69
  j += 1;
69
70
  }
70
71
  return hash;
71
72
  }
72
73
  if (value && typeof value.length === "number") {
73
- hash = veilmark$hashMeshMix(hash, value.length >>> 0);
74
+ hash = toildefender$hashMeshMix(hash, value.length >>> 0);
74
75
  var i = 0;
75
76
  while (i < value.length) {
76
- hash = veilmark$hashMeshValue(hash, value[i]);
77
+ hash = toildefender$hashMeshValue(hash, value[i]);
77
78
  i += 1;
78
79
  }
79
80
  return hash;
80
81
  }
81
- return veilmark$hashMeshMix(hash, 3735928559);
82
+ return toildefender$hashMeshMix(hash, 3735928559);
82
83
  }
83
84
 
84
- function veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops) {
85
+ function toildefender$hashMeshKey(mesh, base, tokenCount, seed, tag, ops) {
85
86
  var hash = 2166136261;
86
- hash = veilmark$hashMeshMix(hash, 1145713480);
87
- hash = veilmark$hashMeshMix(hash, 1296388936);
88
- hash = veilmark$hashMeshValue(hash, mesh);
89
- hash = veilmark$hashMeshMix(hash, base >>> 0);
90
- hash = veilmark$hashMeshMix(hash, tokenCount >>> 0);
91
- hash = veilmark$hashMeshMix(hash, seed >>> 0);
92
- hash = veilmark$hashMeshMix(hash, tag >>> 0);
93
- hash = veilmark$hashMeshValue(hash, ops);
87
+ hash = toildefender$hashMeshMix(hash, 1145713480);
88
+ hash = toildefender$hashMeshMix(hash, 1296388936);
89
+ hash = toildefender$hashMeshValue(hash, mesh);
90
+ hash = toildefender$hashMeshMix(hash, base >>> 0);
91
+ hash = toildefender$hashMeshMix(hash, tokenCount >>> 0);
92
+ hash = toildefender$hashMeshMix(hash, seed >>> 0);
93
+ hash = toildefender$hashMeshMix(hash, tag >>> 0);
94
+ hash = toildefender$hashMeshValue(hash, ops);
94
95
  return hash >>> 0;
95
96
  }
96
97
 
97
- function veilmark$hashMeshStream(key, index, base, salt) {
98
- var hash = veilmark$hashMeshMix(key >>> 0, 1398035796);
99
- hash = veilmark$hashMeshMix(hash, salt >>> 0);
100
- hash = veilmark$hashMeshMix(hash, index >>> 0);
101
- hash = veilmark$hashMeshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
98
+ function toildefender$hashMeshStream(key, index, base, salt) {
99
+ var hash = toildefender$hashMeshMix(key >>> 0, 1398035796);
100
+ hash = toildefender$hashMeshMix(hash, salt >>> 0);
101
+ hash = toildefender$hashMeshMix(hash, index >>> 0);
102
+ hash = toildefender$hashMeshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
102
103
  return hash % base;
103
104
  }
104
105
 
105
- function veilmark$hashMeshUnlock(program, base, baseBig, index, key, salt, powers) {
106
- var cipher = veilmark$numericVmDigit(program, baseBig, index, powers);
107
- return (cipher - veilmark$hashMeshStream(key, index, base, salt) + base) % base;
106
+ function toildefender$hashMeshUnlock(program, base, baseBig, index, key, salt, powers) {
107
+ var cipher = toildefender$numericVmDigit(program, baseBig, index, powers);
108
+ return (cipher - toildefender$hashMeshStream(key, index, base, salt) + base) % base;
108
109
  }
109
110
 
110
- function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants, argsLike, self, ops, mesh) {
111
+ function toildefender$numericVmRun(program, base, tokenCount, seed, tag, constants, argsLike, self, ops, mesh, refs, cache) {
111
112
  var baseBig = BigInt(base);
112
- var digitPowers = [BigInt(1)];
113
113
  var meshKey = 0;
114
114
  var meshSalt = 0;
115
115
  if (mesh) {
116
- meshKey = veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops);
116
+ meshKey = toildefender$hashMeshKey(mesh, base, tokenCount, seed, tag, ops);
117
117
  meshSalt = mesh[5] >>> 0;
118
118
  }
119
+ var encryptedCache = cache && cache[0] || null;
120
+ var stateCache = [seed >>> 0];
121
+ var plainCache = new Array(tokenCount);
122
+ var inverseCache = cache && cache[1] || null;
123
+ if (inverseCache === null) {
124
+ inverseCache = [];
125
+ if (cache) cache[1] = inverseCache;
126
+ }
119
127
 
120
128
  function inverse(value, modulo) {
129
+ if (inverseCache[value] !== undefined) return inverseCache[value];
121
130
  var t = 0, nt = 1;
122
131
  var r = modulo, nr = value % modulo;
123
132
  while (nr !== 0) {
@@ -129,7 +138,9 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
129
138
  r = nr;
130
139
  nr = or - q * nr;
131
140
  }
132
- return t < 0 ? t + modulo : t;
141
+ var out = t < 0 ? t + modulo : t;
142
+ inverseCache[value] = out;
143
+ return out;
133
144
  }
134
145
 
135
146
  function mix(current, encrypted, index) {
@@ -138,9 +149,23 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
138
149
  return (mixed ^ (mixed >>> 13)) >>> 0;
139
150
  }
140
151
 
152
+ function unpackEncrypted() {
153
+ if (encryptedCache !== null) return;
154
+ encryptedCache = new Array(tokenCount);
155
+ var work = program;
156
+ var index = 0;
157
+ while (index < tokenCount) {
158
+ var cipher = Number(work % baseBig);
159
+ work = work / baseBig;
160
+ encryptedCache[index] = mesh ? (cipher - toildefender$hashMeshStream(meshKey, index, base, meshSalt) + base) % base : cipher;
161
+ index += 1;
162
+ }
163
+ if (cache) cache[0] = encryptedCache;
164
+ }
165
+
141
166
  function encryptedAt(index) {
142
- if (mesh) return veilmark$hashMeshUnlock(program, base, baseBig, index, meshKey, meshSalt, digitPowers);
143
- return veilmark$numericVmDigit(program, baseBig, index, digitPowers);
167
+ unpackEncrypted();
168
+ return encryptedCache[index];
144
169
  }
145
170
 
146
171
  var i = 0;
@@ -148,37 +173,33 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
148
173
  while (i < tokenCount) {
149
174
  var encrypted = encryptedAt(i);
150
175
  seen = mix(seen, encrypted, i);
176
+ stateCache[i + 1] = seen;
151
177
  i += 1;
152
178
  }
153
179
 
154
180
  if ((seen >>> 0) !== (tag >>> 0)) throw new Error("invalid numeric vm program");
155
181
 
156
- var decodeIndex = 0;
157
- var decodeState = seed >>> 0;
158
-
159
182
  function stateBefore(index) {
160
- if (index < decodeIndex) {
161
- decodeIndex = 0;
162
- decodeState = seed >>> 0;
183
+ if (stateCache[index] !== undefined) return stateCache[index];
184
+ var cursor = index - 1;
185
+ while (cursor > 0 && stateCache[cursor] === undefined) cursor -= 1;
186
+ var current = stateCache[cursor] === undefined ? seed >>> 0 : stateCache[cursor];
187
+ while (cursor < index) {
188
+ current = mix(current, encryptedAt(cursor), cursor);
189
+ cursor += 1;
190
+ stateCache[cursor] = current;
163
191
  }
164
- while (decodeIndex < index) {
165
- var skipped = encryptedAt(decodeIndex);
166
- decodeState = mix(decodeState, skipped, decodeIndex);
167
- decodeIndex += 1;
168
- }
169
- return decodeState;
192
+ return current;
170
193
  }
171
194
 
172
195
  function decodeAt(index) {
196
+ if (plainCache[index] !== undefined) return plainCache[index];
173
197
  var state = stateBefore(index);
174
198
  var encrypted = encryptedAt(index);
175
199
  var mul = 1 + ((state >>> 5) % (base - 1));
176
200
  var add = state % base;
177
201
  var plain = (((encrypted - add + base) % base) * inverse(mul, base)) % base;
178
- if (index === decodeIndex) {
179
- decodeState = mix(state, encrypted, index);
180
- decodeIndex = index + 1;
181
- }
202
+ plainCache[index] = plain;
182
203
  return plain;
183
204
  }
184
205
 
@@ -219,6 +240,8 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
219
240
  cell[1] = cell[1]();
220
241
  cell[0] = 1;
221
242
  }
243
+ if (cell && cell[0] === 2 && typeof cell[1] === "function") return cell[1]();
244
+ if (cell && cell[0] === 3) return refs[cell[1]];
222
245
  return cell && cell[0] === 1 ? cell[1] : cell;
223
246
  }
224
247
 
@@ -274,7 +297,7 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
274
297
  if (op === ops[14]) { var mulB = pop(); var mulA = pop(); push(mulA * mulB); continue; }
275
298
  if (op === ops[15]) { var divB = pop(); var divA = pop(); push(divA / divB); continue; }
276
299
  if (op === ops[16]) { var modB = pop(); var modA = pop(); push(modA % modB); continue; }
277
- if (op === ops[17]) { var powB = pop(); var powA = pop(); push(veilmark$numericVmPow(powA, powB)); continue; }
300
+ if (op === ops[17]) { var powB = pop(); var powA = pop(); push(toildefender$numericVmPow(powA, powB)); continue; }
278
301
  if (op === ops[18]) { push(-pop()); continue; }
279
302
  if (op === ops[19]) { push(!pop()); continue; }
280
303
  if (op === ops[20]) { push(~pop()); continue; }
@@ -303,6 +326,8 @@ function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants,
303
326
  if (op === ops[43]) { var mc = readUnsigned(); var ma = popArgs(mc); var mk = pop(); var mo = pop(); push(mo[mk].apply(mo, ma)); continue; }
304
327
  if (op === ops[44]) { var cgpKey = readConstant(readUnsigned()); var cgpObj = pop(); push(cgpObj[cgpKey]); continue; }
305
328
  if (op === ops[45]) { storeLocal(readUnsigned(), pop()); continue; }
329
+ if (op === ops[46]) { var jn = readSigned(); var nv = pop(); if (nv === null || nv === undefined) ip += jn; continue; }
330
+ if (op === ops[47]) { push(Number(pop())); continue; }
306
331
  throw new Error("invalid virtual opcode");
307
332
  }
308
333
  }
@@ -315,7 +340,7 @@ var OP_NAMES = [
315
340
  "STRICT_EQ", "STRICT_NEQ", "LT", "LTE", "GT", "GTE", "JMP", "JMP_FALSE",
316
341
  "JMP_TRUE", "CALL_EXT", "CALL_LOCAL", "GET_PROP", "SET_PROP", "MAKE_ARRAY",
317
342
  "MAKE_OBJECT", "RETURN", "THROW", "PUSH_THIS", "PUSH_ARGUMENTS", "TYPEOF",
318
- "CALL_METHOD", "GET_CONST_PROP", "STORE_LOCAL_POP"
343
+ "CALL_METHOD", "GET_CONST_PROP", "STORE_LOCAL_POP", "JMP_NULLISH", "TO_NUMBER"
319
344
  ];
320
345
 
321
346
  var BASES = [257, 263, 269, 521, 1031, 4099, 65537];
@@ -330,8 +355,30 @@ function member(object, property) { return { type: "MemberExpression", object: o
330
355
  function arrayExpression(values) { return { type: "ArrayExpression", elements: values }; }
331
356
  function returnStatement(argument) { return { type: "ReturnStatement", argument: argument }; }
332
357
  function functionExpression(body) { return { type: "FunctionExpression", id: null, params: [], body: { type: "BlockStatement", body: body }, generator: false, expression: false, async: false }; }
358
+ function variableDeclaration(name, init) {
359
+ return {
360
+ type: "VariableDeclaration",
361
+ kind: "var",
362
+ declarations: [
363
+ {
364
+ type: "VariableDeclarator",
365
+ id: identifier(name),
366
+ init: init
367
+ }
368
+ ]
369
+ };
370
+ }
333
371
  function functionName(node) { return node.id && node.id.name ? node.id.name : ""; }
334
372
 
373
+ function markNumericVmInternal(ast) {
374
+ return traverser.traverse(ast, [], function (node) {
375
+ if (estest.isFunction(node)) {
376
+ node.toildefender$numericVmInternal = true;
377
+ }
378
+ return node;
379
+ });
380
+ }
381
+
335
382
  function hashSeed(seed) {
336
383
  return crypto.createHash("sha256").update(String(seed)).digest().readUInt32LE(0) || 1;
337
384
  }
@@ -490,6 +537,27 @@ function textDigest(value) {
490
537
  return hash >>> 0;
491
538
  }
492
539
 
540
+ function normalizeRatio(value) {
541
+ var ratio = Number(value);
542
+ if (!Number.isFinite(ratio)) return 1;
543
+ if (ratio < 0) return 0;
544
+ if (ratio > 1) return 1;
545
+ return ratio;
546
+ }
547
+
548
+ function normalizeMaxFunctions(value) {
549
+ if (value === undefined || value === null) return Infinity;
550
+ var max = Number(value);
551
+ if (!Number.isFinite(max)) return Infinity;
552
+ return Math.max(0, Math.floor(max));
553
+ }
554
+
555
+ function selectionScore(options, node, index) {
556
+ var name = functionName(node);
557
+ var bodySize = node && node.body && node.body.body ? node.body.body.length : 0;
558
+ return textDigest(String(options.seed) + ":" + index + ":" + name + ":" + bodySize) / 0x100000000;
559
+ }
560
+
493
561
  function constantDigest(constants) {
494
562
  var hash = textDigest("DJS-HMESH/constants/v1");
495
563
  for (var i = 0; i < constants.length; i += 1) {
@@ -597,18 +665,42 @@ function Compiler(fn, dialect, options) {
597
665
  this.instructions = [];
598
666
  this.labelId = 0;
599
667
  this.params = {};
600
- this.locals = {};
668
+ this.functionScope = Object.create(null);
669
+ this.scopeStack = [];
601
670
  this.localCount = 0;
602
671
  this.constants = [];
603
672
  this.constantKeys = {};
673
+ this.references = [];
674
+ this.referenceKeys = {};
604
675
  }
605
676
 
606
677
  Compiler.prototype.label = function () { return "L" + this.labelId++; };
607
678
  Compiler.prototype.mark = function (name) { this.instructions.push({ label: name }); };
608
679
  Compiler.prototype.emit = function (op) { this.instructions.push({ op: op, args: Array.prototype.slice.call(arguments, 1) }); };
609
- Compiler.prototype.localSlot = function (name) {
610
- if (!Object.prototype.hasOwnProperty.call(this.locals, name)) this.locals[name] = this.localCount++;
611
- return this.locals[name];
680
+ Compiler.prototype.pushScope = function () {
681
+ var scope = Object.create(null);
682
+ this.scopeStack.push(scope);
683
+ return scope;
684
+ };
685
+ Compiler.prototype.popScope = function () {
686
+ this.scopeStack.pop();
687
+ };
688
+ Compiler.prototype.currentScope = function () {
689
+ if (this.scopeStack.length === 0) return this.pushScope();
690
+ return this.scopeStack[this.scopeStack.length - 1];
691
+ };
692
+ Compiler.prototype.declareLocal = function (name, functionScoped) {
693
+ var scope = functionScoped ? this.functionScope : this.currentScope();
694
+ if (!Object.prototype.hasOwnProperty.call(scope, name)) scope[name] = this.localCount++;
695
+ return scope[name];
696
+ };
697
+ Compiler.prototype.resolveLocal = function (name) {
698
+ for (var i = this.scopeStack.length - 1; i >= 0; i -= 1) {
699
+ var scope = this.scopeStack[i];
700
+ if (Object.prototype.hasOwnProperty.call(scope, name)) return scope[name];
701
+ }
702
+ if (Object.prototype.hasOwnProperty.call(this.functionScope, name)) return this.functionScope[name];
703
+ return null;
612
704
  };
613
705
  Compiler.prototype.addConstant = function (kind, value) {
614
706
  var key = kind + ":" + String(value);
@@ -618,7 +710,15 @@ Compiler.prototype.addConstant = function (kind, value) {
618
710
  this.constants.push({ kind: kind, value: value });
619
711
  return index;
620
712
  };
621
- Compiler.prototype.collectLocals = function () {
713
+ Compiler.prototype.addReference = function (value) {
714
+ var key = String(value);
715
+ if (Object.prototype.hasOwnProperty.call(this.referenceKeys, key)) return this.referenceKeys[key];
716
+ var index = this.references.length;
717
+ this.referenceKeys[key] = index;
718
+ this.references.push(key);
719
+ return index;
720
+ };
721
+ Compiler.prototype.validateBindings = function () {
622
722
  var self = this;
623
723
  this.fn.params.forEach(function (param, index) {
624
724
  if (!isSimplePattern(param)) throw new Error("unsupported parameter pattern");
@@ -631,7 +731,6 @@ Compiler.prototype.collectLocals = function () {
631
731
  }
632
732
  if (node.type === "VariableDeclarator") {
633
733
  if (!isSimplePattern(node.id)) throw new Error("unsupported declaration pattern");
634
- self.localSlot(node.id.name);
635
734
  }
636
735
  return node;
637
736
  });
@@ -639,23 +738,27 @@ Compiler.prototype.collectLocals = function () {
639
738
  Compiler.prototype.compile = function () {
640
739
  if (!this.fn.body || this.fn.body.type !== "BlockStatement") throw new Error("unsupported function body");
641
740
  if (containsNestedFunction(this.fn.body)) throw new Error("nested functions are not virtualized");
642
- this.collectLocals();
643
- this.compileBlock(this.fn.body);
741
+ this.validateBindings();
742
+ this.pushScope();
743
+ this.compileBlock(this.fn.body, false);
744
+ this.popScope();
644
745
  this.emit("PUSH_UNDEFINED");
645
746
  this.emit("RETURN");
646
747
  return this.finish();
647
748
  };
648
- Compiler.prototype.compileBlock = function (block) {
749
+ Compiler.prototype.compileBlock = function (block, createScope) {
649
750
  var self = this;
751
+ if (createScope !== false) this.pushScope();
650
752
  block.body.forEach(function (stmt) { self.compileStatement(stmt); });
753
+ if (createScope !== false) this.popScope();
651
754
  };
652
755
  Compiler.prototype.compileStatement = function (stmt) {
653
756
  switch (stmt.type) {
654
- case "BlockStatement": this.compileBlock(stmt); return;
757
+ case "BlockStatement": this.compileBlock(stmt, true); return;
655
758
  case "VariableDeclaration":
656
759
  for (var i = 0; i < stmt.declarations.length; i += 1) {
657
760
  var decl = stmt.declarations[i];
658
- var slot = this.localSlot(decl.id.name);
761
+ var slot = this.declareLocal(decl.id.name, stmt.kind === "var");
659
762
  if (decl.init) this.compileExpression(decl.init); else this.emit("PUSH_UNDEFINED");
660
763
  this.emit("STORE_LOCAL", slot);
661
764
  }
@@ -726,9 +829,12 @@ Compiler.prototype.compileLiteral = function (expr) {
726
829
  Compiler.prototype.compileIdentifier = function (name) {
727
830
  if (name === "undefined") this.emit("PUSH_UNDEFINED");
728
831
  else if (name === "arguments") this.emit("PUSH_ARGUMENTS");
729
- else if (Object.prototype.hasOwnProperty.call(this.params, name)) this.emit("LOAD_ARG", this.params[name]);
730
- else if (Object.prototype.hasOwnProperty.call(this.locals, name)) this.emit("LOAD_LOCAL", this.locals[name]);
731
- else this.emit("PUSH_CONST", this.addConstant("reference", name));
832
+ else {
833
+ var slot = this.resolveLocal(name);
834
+ if (slot !== null) this.emit("LOAD_LOCAL", slot);
835
+ else if (Object.prototype.hasOwnProperty.call(this.params, name)) this.emit("LOAD_ARG", this.params[name]);
836
+ else this.emit("PUSH_CONST", this.addConstant("reference", name));
837
+ }
732
838
  };
733
839
  Compiler.prototype.compileArray = function (expr) {
734
840
  for (var i = 0; i < expr.elements.length; i += 1) {
@@ -760,7 +866,8 @@ Compiler.prototype.compileUnary = function (expr) {
760
866
  else if (expr.operator === "!") this.emit("NOT");
761
867
  else if (expr.operator === "~") this.emit("BIT_NOT");
762
868
  else if (expr.operator === "typeof") this.emit("TYPEOF");
763
- else if (expr.operator !== "+") throw new Error("unsupported unary operator " + expr.operator);
869
+ else if (expr.operator === "+") this.emit("TO_NUMBER");
870
+ else throw new Error("unsupported unary operator " + expr.operator);
764
871
  };
765
872
  Compiler.prototype.compileBinary = function (expr) {
766
873
  this.compileExpression(expr.left);
@@ -771,6 +878,19 @@ Compiler.prototype.compileBinary = function (expr) {
771
878
  };
772
879
  Compiler.prototype.compileLogical = function (expr) {
773
880
  var end = this.label();
881
+ if (expr.operator === "??") {
882
+ var right = this.label();
883
+ this.compileExpression(expr.left);
884
+ this.emit("DUP");
885
+ this.emit("JMP_NULLISH", right);
886
+ this.emit("JMP", end);
887
+ this.mark(right);
888
+ this.emit("POP");
889
+ this.compileExpression(expr.right);
890
+ this.mark(end);
891
+ return;
892
+ }
893
+ if (expr.operator !== "&&" && expr.operator !== "||") throw new Error("unsupported logical operator " + expr.operator);
774
894
  this.compileExpression(expr.left);
775
895
  this.emit("DUP");
776
896
  this.emit(expr.operator === "&&" ? "JMP_FALSE" : "JMP_TRUE", end);
@@ -780,7 +900,8 @@ Compiler.prototype.compileLogical = function (expr) {
780
900
  };
781
901
  Compiler.prototype.compileAssignment = function (expr) {
782
902
  if (expr.left.type === "Identifier") {
783
- if (!Object.prototype.hasOwnProperty.call(this.locals, expr.left.name)) throw new Error("unsupported assignment target " + expr.left.name);
903
+ var slot = this.resolveLocal(expr.left.name);
904
+ if (slot === null) throw new Error("unsupported assignment target " + expr.left.name);
784
905
  if (expr.operator === "=") this.compileExpression(expr.right);
785
906
  else {
786
907
  var map = { "+=": "ADD", "-=": "SUB", "*=": "MUL", "/=": "DIV", "%=": "MOD" };
@@ -790,7 +911,7 @@ Compiler.prototype.compileAssignment = function (expr) {
790
911
  this.emit(map[expr.operator]);
791
912
  }
792
913
  this.emit("DUP");
793
- this.emit("STORE_LOCAL", this.locals[expr.left.name]);
914
+ this.emit("STORE_LOCAL", slot);
794
915
  return;
795
916
  }
796
917
  if (expr.left.type === "MemberExpression" && expr.operator === "=") {
@@ -916,7 +1037,7 @@ Compiler.prototype.constantExpression = function (constant) {
916
1037
  if (constant.kind === "string" || constant.kind === "reference") {
917
1038
  var value = String(constant.value);
918
1039
  var salt = (next() & 65535) || 1;
919
- var decoded = call(identifier("veilmark$numericVmString"), [ bigintExpression(stringBlob(value, salt), next), literal(value.length), literal(salt) ]);
1040
+ var decoded = call(identifier("toildefender$numericVmString"), [ bigintExpression(stringBlob(value, salt), next), literal(value.length), literal(salt) ]);
920
1041
  if (constant.kind === "reference") {
921
1042
  return {
922
1043
  type: "ConditionalExpression",
@@ -931,10 +1052,33 @@ Compiler.prototype.constantExpression = function (constant) {
931
1052
  if (constant.kind === "undefined") return { type: "UnaryExpression", operator: "void", prefix: true, argument: literal(0) };
932
1053
  throw new Error("unsupported constant " + constant.kind);
933
1054
  };
1055
+ Compiler.prototype.referenceExpression = function (value) {
1056
+ var next = this.dialect.next;
1057
+ var name = String(value);
1058
+ var salt = (next() & 65535) || 1;
1059
+ var decoded = call(identifier("toildefender$numericVmString"), [ bigintExpression(stringBlob(name, salt), next), literal(name.length), literal(salt) ]);
1060
+ return {
1061
+ type: "ConditionalExpression",
1062
+ test: binary("===", unary("typeof", identifier(name)), literal("undefined")),
1063
+ consequent: member(identifier("globalThis"), decoded),
1064
+ alternate: identifier(name)
1065
+ };
1066
+ };
934
1067
  Compiler.prototype.constantCellExpression = function (constant) {
1068
+ if (constant.kind === "reference") {
1069
+ return arrayExpression([
1070
+ literal(3),
1071
+ literal(this.addReference(constant.value))
1072
+ ]);
1073
+ }
1074
+ if (constant.kind !== "string" && constant.kind !== "reference") {
1075
+ return this.constantExpression(constant);
1076
+ }
935
1077
  return arrayExpression([
936
- literal(0),
937
- functionExpression([ returnStatement(this.constantExpression(constant)) ])
1078
+ literal(constant.kind === "reference" ? 2 : 0),
1079
+ Object.assign(functionExpression([ returnStatement(this.constantExpression(constant)) ]), {
1080
+ toildefender$numericVmInternal: true
1081
+ })
938
1082
  ]);
939
1083
  };
940
1084
  Compiler.prototype.finish = function () {
@@ -947,6 +1091,7 @@ Compiler.prototype.finish = function () {
947
1091
  blob: packTokens(encrypted.encrypted, this.dialect.base),
948
1092
  constants: this.constants.map(this.constantCellExpression.bind(this)),
949
1093
  opValues: opValues.map(literal),
1094
+ references: this.references.map(this.referenceExpression.bind(this)),
950
1095
  seed: this.dialect.seed,
951
1096
  tag: encrypted.tag,
952
1097
  tokenCount: tokens.length
@@ -968,18 +1113,21 @@ function makeDialect(seedText) {
968
1113
  return { base: BASES[next() % BASES.length], next: next, opcodes: opcodes, seed: seed };
969
1114
  }
970
1115
 
971
- function vmCall(record, next) {
972
- return call(identifier("veilmark$numericVmRun"), [
1116
+ function vmCall(record, next, refs) {
1117
+ refs = refs || {};
1118
+ return call(identifier("toildefender$numericVmRun"), [
973
1119
  bigintExpression(record.blob, next),
974
1120
  literal(record.base),
975
1121
  literal(record.tokenCount),
976
1122
  literal(record.seed),
977
1123
  literal(record.tag),
978
- arrayExpression(record.constants),
1124
+ refs.constants || arrayExpression(record.constants),
979
1125
  identifier("arguments"),
980
1126
  { type: "ThisExpression" },
981
- arrayExpression(record.opValues),
982
- record.mesh ? meshExpression(record.mesh) : literal(null)
1127
+ refs.ops || arrayExpression(record.opValues),
1128
+ refs.mesh || (record.mesh ? meshExpression(record.mesh) : literal(null)),
1129
+ arrayExpression(record.references || []),
1130
+ refs.cache || arrayExpression([])
983
1131
  ]);
984
1132
  }
985
1133
 
@@ -987,8 +1135,10 @@ function resolveOptions(options) {
987
1135
  return Object.assign({
988
1136
  enabled: false,
989
1137
  maxFunctionSize: 120,
1138
+ maxFunctions: Infinity,
990
1139
  minFunctionSize: 1,
991
1140
  mode: "balanced",
1141
+ ratio: 1,
992
1142
  seed: "toildefender-numeric-vm",
993
1143
  hashMesh: {
994
1144
  bindToVmState: true,
@@ -1008,13 +1158,15 @@ module.exports = class NumericVm {
1008
1158
  constructor(logger, options) {
1009
1159
  this.logger = logger;
1010
1160
  this.options = resolveOptions(options);
1161
+ this.options.ratio = normalizeRatio(this.options.ratio);
1162
+ this.options.maxFunctions = normalizeMaxFunctions(this.options.maxFunctions);
1011
1163
  this.count = 0;
1012
1164
  }
1013
1165
 
1014
1166
  shouldTry(node) {
1015
1167
  if (!this.options.enabled || !estest.isFunction(node) || node.generator || node.async) return false;
1016
1168
  if (!node.body || node.body.type !== "BlockStatement") return false;
1017
- if (functionName(node).indexOf("veilmark$numericVm") === 0) return false;
1169
+ if (functionName(node).indexOf("toildefender$numericVm") === 0) return false;
1018
1170
  var bodySize = node.body.body.length;
1019
1171
  if (bodySize < this.options.minFunctionSize || bodySize > this.options.maxFunctionSize) return false;
1020
1172
  if (this.options.virtualize === "all-supported") return true;
@@ -1026,17 +1178,52 @@ module.exports = class NumericVm {
1026
1178
  assert.ok(estest.isNode(ast));
1027
1179
  if (!this.options.enabled) return ast;
1028
1180
 
1029
- var runtime = replaceStaticBigIntCalls(esprima.parse(RUNTIME));
1181
+ var runtime = markNumericVmInternal(replaceStaticBigIntCalls(esprima.parse(RUNTIME)));
1030
1182
  var self = this;
1031
1183
  var transformed = 0;
1184
+ var candidateIndex = 0;
1185
+ var dataDeclarations = [];
1186
+ var trace = typeof process !== "undefined" && process.env && process.env.TOILDEFENDER_NUMERIC_VM_TRACE === "1";
1032
1187
 
1033
1188
  ast = traverser.traverse(ast, [], function (node) {
1034
1189
  if (!self.shouldTry(node)) return node;
1190
+ var currentIndex = candidateIndex;
1191
+ candidateIndex += 1;
1192
+ if (transformed >= self.options.maxFunctions) return node;
1193
+ if (self.options.ratio <= 0 || selectionScore(self.options, node, currentIndex) >= self.options.ratio) return node;
1035
1194
  try {
1195
+ var originalBodySize = node.body && node.body.body ? node.body.body.length : 0;
1036
1196
  var dialect = makeDialect(self.options.seed + ":" + transformed + ":" + functionName(node));
1037
1197
  var record = new Compiler(node, dialect, self.options).compile();
1038
- node.body = { type: "BlockStatement", body: [ returnStatement(vmCall(record, dialect.next)) ] };
1198
+ var dataName = "toildefender$numericVmData$" + transformed;
1199
+ var opsName = "toildefender$numericVmOps$" + transformed;
1200
+ var meshName = "toildefender$numericVmMesh$" + transformed;
1201
+ var cacheName = "toildefender$numericVmCache$" + transformed;
1202
+ [
1203
+ variableDeclaration(dataName, arrayExpression(record.constants)),
1204
+ variableDeclaration(opsName, arrayExpression(record.opValues)),
1205
+ variableDeclaration(meshName, record.mesh ? meshExpression(record.mesh) : literal(null)),
1206
+ variableDeclaration(cacheName, arrayExpression([]))
1207
+ ].forEach(function (declaration) {
1208
+ declaration.toildefender$numericVmInternal = true;
1209
+ dataDeclarations.push(declaration);
1210
+ });
1211
+ node.body = { type: "BlockStatement", body: [ returnStatement(vmCall(record, dialect.next, {
1212
+ cache: identifier(cacheName),
1213
+ constants: identifier(dataName),
1214
+ mesh: identifier(meshName),
1215
+ ops: identifier(opsName)
1216
+ })) ] };
1039
1217
  transformed += 1;
1218
+ if (trace) {
1219
+ console.error(JSON.stringify({
1220
+ event: "numeric_vm_transformed",
1221
+ index: transformed,
1222
+ candidateIndex: currentIndex,
1223
+ name: functionName(node),
1224
+ bodySize: originalBodySize
1225
+ }));
1226
+ }
1040
1227
  } catch (error) {
1041
1228
  if (self.options.virtualize === "all-supported") self.logger.warn("numeric_vm skipped " + functionName(node) + ": " + error.message);
1042
1229
  }
@@ -1044,7 +1231,7 @@ module.exports = class NumericVm {
1044
1231
  });
1045
1232
 
1046
1233
  if (transformed > 0) {
1047
- runtime.body.reverse().forEach(function (node) { ast.body.unshift(node); });
1234
+ ast.body = runtime.body.concat(dataDeclarations).concat(ast.body);
1048
1235
  }
1049
1236
  this.count = transformed;
1050
1237
  return ast;