@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.
- package/README.md +1 -1
- package/esutils.js +12 -8
- package/obfuscator.js +101 -12
- package/package.json +1 -1
- package/processors/deadCode.js +32 -0
- package/processors/flattener.js +73 -8
- package/processors/identifiers.js +26 -6
- package/processors/literals.js +82 -2
- package/processors/methods.js +47 -34
- package/processors/modules.js +2 -2
- package/processors/normalizer.js +435 -69
- package/processors/numericVm.js +270 -83
- package/processors/postprocessing.js +4 -4
- package/processors/scopes.js +366 -50
- package/processors/uglifier.js +22 -2
- package/processors/variables.js +51 -8
package/processors/numericVm.js
CHANGED
|
@@ -8,19 +8,20 @@ var estest = require("../estest");
|
|
|
8
8
|
var traverser = require("../traverser");
|
|
9
9
|
|
|
10
10
|
var RUNTIME = `
|
|
11
|
-
function
|
|
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 %
|
|
16
|
-
program = program /
|
|
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
|
|
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
|
|
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
|
|
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
|
|
62
|
-
if (typeof value === "number") return
|
|
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 =
|
|
65
|
+
hash = toildefender$hashMeshMix(hash, value.length >>> 0);
|
|
65
66
|
var j = 0;
|
|
66
67
|
while (j < value.length) {
|
|
67
|
-
hash =
|
|
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 =
|
|
74
|
+
hash = toildefender$hashMeshMix(hash, value.length >>> 0);
|
|
74
75
|
var i = 0;
|
|
75
76
|
while (i < value.length) {
|
|
76
|
-
hash =
|
|
77
|
+
hash = toildefender$hashMeshValue(hash, value[i]);
|
|
77
78
|
i += 1;
|
|
78
79
|
}
|
|
79
80
|
return hash;
|
|
80
81
|
}
|
|
81
|
-
return
|
|
82
|
+
return toildefender$hashMeshMix(hash, 3735928559);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function
|
|
85
|
+
function toildefender$hashMeshKey(mesh, base, tokenCount, seed, tag, ops) {
|
|
85
86
|
var hash = 2166136261;
|
|
86
|
-
hash =
|
|
87
|
-
hash =
|
|
88
|
-
hash =
|
|
89
|
-
hash =
|
|
90
|
-
hash =
|
|
91
|
-
hash =
|
|
92
|
-
hash =
|
|
93
|
-
hash =
|
|
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
|
|
98
|
-
var hash =
|
|
99
|
-
hash =
|
|
100
|
-
hash =
|
|
101
|
-
hash =
|
|
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
|
|
106
|
-
var cipher =
|
|
107
|
-
return (cipher -
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
return
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
610
|
-
|
|
611
|
-
|
|
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.
|
|
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.
|
|
643
|
-
this.
|
|
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.
|
|
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
|
|
730
|
-
|
|
731
|
-
|
|
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
|
|
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
|
-
|
|
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",
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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.
|
|
1234
|
+
ast.body = runtime.body.concat(dataDeclarations).concat(ast.body);
|
|
1048
1235
|
}
|
|
1049
1236
|
this.count = transformed;
|
|
1050
1237
|
return ast;
|