js-confuser 2.0.0-alpha.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +43 -43
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/workflows/node.js.yml +28 -28
  4. package/.prettierrc +4 -4
  5. package/CHANGELOG.md +1015 -989
  6. package/CODE_OF_CONDUCT.md +131 -131
  7. package/CONTRIBUTING.md +52 -52
  8. package/LICENSE +21 -21
  9. package/Migration.md +72 -71
  10. package/README.md +86 -78
  11. package/dist/constants.js +43 -43
  12. package/dist/index.js +14 -23
  13. package/dist/obfuscator.js +31 -25
  14. package/dist/order.js +4 -4
  15. package/dist/presets.js +31 -31
  16. package/dist/templates/integrityTemplate.js +4 -4
  17. package/dist/templates/template.js +1 -2
  18. package/dist/transforms/astScrambler.js +1 -2
  19. package/dist/transforms/calculator.js +1 -2
  20. package/dist/transforms/controlFlowFlattening.js +93 -63
  21. package/dist/transforms/deadCode.js +1 -2
  22. package/dist/transforms/dispatcher.js +4 -5
  23. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
  24. package/dist/transforms/extraction/objectExtraction.js +1 -2
  25. package/dist/transforms/finalizer.js +1 -2
  26. package/dist/transforms/flatten.js +1 -2
  27. package/dist/transforms/identifier/globalConcealing.js +15 -2
  28. package/dist/transforms/identifier/movedDeclarations.js +8 -7
  29. package/dist/transforms/identifier/renameVariables.js +7 -7
  30. package/dist/transforms/lock/integrity.js +11 -10
  31. package/dist/transforms/lock/lock.js +2 -3
  32. package/dist/transforms/minify.js +11 -29
  33. package/dist/transforms/opaquePredicates.js +1 -2
  34. package/dist/transforms/pack.js +5 -2
  35. package/dist/transforms/plugin.js +18 -19
  36. package/dist/transforms/preparation.js +16 -16
  37. package/dist/transforms/renameLabels.js +1 -2
  38. package/dist/transforms/rgf.js +8 -9
  39. package/dist/transforms/shuffle.js +1 -2
  40. package/dist/transforms/string/encoding.js +1 -2
  41. package/dist/transforms/string/stringCompression.js +3 -4
  42. package/dist/transforms/string/stringConcealing.js +8 -3
  43. package/dist/transforms/string/stringEncoding.js +1 -2
  44. package/dist/transforms/variableMasking.js +1 -2
  45. package/dist/utils/NameGen.js +2 -2
  46. package/dist/utils/PredicateGen.js +1 -2
  47. package/dist/utils/ast-utils.js +87 -88
  48. package/dist/utils/function-utils.js +8 -8
  49. package/dist/utils/node.js +5 -6
  50. package/dist/utils/object-utils.js +4 -4
  51. package/dist/utils/random-utils.js +20 -20
  52. package/dist/utils/static-utils.js +1 -2
  53. package/dist/validateOptions.js +4 -7
  54. package/index.d.ts +17 -17
  55. package/package.json +61 -59
  56. package/src/constants.ts +168 -168
  57. package/src/index.ts +118 -118
  58. package/src/obfuscationResult.ts +49 -49
  59. package/src/obfuscator.ts +501 -497
  60. package/src/options.ts +407 -407
  61. package/src/order.ts +54 -54
  62. package/src/presets.ts +125 -125
  63. package/src/templates/bufferToStringTemplate.ts +57 -57
  64. package/src/templates/deadCodeTemplates.ts +1185 -1185
  65. package/src/templates/getGlobalTemplate.ts +76 -76
  66. package/src/templates/integrityTemplate.ts +64 -64
  67. package/src/templates/setFunctionLengthTemplate.ts +11 -11
  68. package/src/templates/stringCompressionTemplate.ts +20 -20
  69. package/src/templates/tamperProtectionTemplates.ts +120 -120
  70. package/src/templates/template.ts +224 -224
  71. package/src/transforms/astScrambler.ts +99 -99
  72. package/src/transforms/calculator.ts +99 -99
  73. package/src/transforms/controlFlowFlattening.ts +1716 -1664
  74. package/src/transforms/deadCode.ts +82 -82
  75. package/src/transforms/dispatcher.ts +450 -450
  76. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
  77. package/src/transforms/extraction/objectExtraction.ts +186 -186
  78. package/src/transforms/finalizer.ts +74 -74
  79. package/src/transforms/flatten.ts +421 -420
  80. package/src/transforms/identifier/globalConcealing.ts +315 -295
  81. package/src/transforms/identifier/movedDeclarations.ts +252 -251
  82. package/src/transforms/identifier/renameVariables.ts +328 -321
  83. package/src/transforms/lock/integrity.ts +117 -114
  84. package/src/transforms/lock/lock.ts +418 -425
  85. package/src/transforms/minify.ts +615 -629
  86. package/src/transforms/opaquePredicates.ts +100 -100
  87. package/src/transforms/pack.ts +239 -231
  88. package/src/transforms/plugin.ts +173 -173
  89. package/src/transforms/preparation.ts +349 -347
  90. package/src/transforms/renameLabels.ts +175 -175
  91. package/src/transforms/rgf.ts +322 -322
  92. package/src/transforms/shuffle.ts +82 -82
  93. package/src/transforms/string/encoding.ts +144 -144
  94. package/src/transforms/string/stringCompression.ts +128 -128
  95. package/src/transforms/string/stringConcealing.ts +312 -298
  96. package/src/transforms/string/stringEncoding.ts +80 -80
  97. package/src/transforms/string/stringSplitting.ts +77 -77
  98. package/src/transforms/variableMasking.ts +257 -257
  99. package/src/utils/IntGen.ts +33 -33
  100. package/src/utils/NameGen.ts +116 -116
  101. package/src/utils/PredicateGen.ts +61 -61
  102. package/src/utils/ast-utils.ts +663 -663
  103. package/src/utils/function-utils.ts +50 -50
  104. package/src/utils/gen-utils.ts +48 -48
  105. package/src/utils/node.ts +78 -78
  106. package/src/utils/object-utils.ts +21 -21
  107. package/src/utils/random-utils.ts +93 -93
  108. package/src/utils/static-utils.ts +66 -66
  109. package/src/validateOptions.ts +256 -259
  110. package/tsconfig.json +13 -14
  111. package/dist/probability.js +0 -1
  112. package/dist/transforms/functionOutlining.js +0 -230
  113. package/dist/utils/ControlObject.js +0 -125
@@ -1,450 +1,450 @@
1
- import { PluginArg, PluginObject } from "./plugin";
2
- import { NodePath } from "@babel/traverse";
3
- import * as t from "@babel/types";
4
- import Template from "../templates/template";
5
- import { ok } from "assert";
6
- import { chance, getRandomString } from "../utils/random-utils";
7
- import { Order } from "../order";
8
- import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
9
- import {
10
- computeFunctionLength,
11
- isVariableFunctionIdentifier,
12
- } from "../utils/function-utils";
13
- import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate";
14
- import { numericLiteral } from "../utils/node";
15
- import {
16
- isStrictMode,
17
- isVariableIdentifier,
18
- prependProgram,
19
- } from "../utils/ast-utils";
20
-
21
- export default ({ Plugin }: PluginArg): PluginObject => {
22
- const me = Plugin(Order.Dispatcher, {
23
- changeData: {
24
- functions: 0,
25
- },
26
- });
27
- let dispatcherCounter = 0;
28
-
29
- const setFunctionLength = me.getPlaceholder("d_fnLength");
30
-
31
- // in Debug mode, function names are preserved
32
- const isDebug = false;
33
-
34
- return {
35
- visitor: {
36
- "Program|Function": {
37
- exit(_path) {
38
- const blockPath = _path as NodePath<t.Program | t.Function>;
39
-
40
- if (blockPath.isProgram()) {
41
- blockPath.scope.crawl();
42
-
43
- // Don't insert function length code when disabled
44
- // Instead insert empty function as the identifier is still referenced
45
- var insertNode = t.functionDeclaration(
46
- t.identifier(setFunctionLength),
47
- [],
48
- t.blockStatement([])
49
- );
50
-
51
- if (me.options.preserveFunctionLength) {
52
- // Insert function length code
53
- insertNode = SetFunctionLengthTemplate.single({
54
- fnName: setFunctionLength,
55
- });
56
- }
57
-
58
- me.skip(prependProgram(_path, insertNode));
59
- }
60
-
61
- if ((blockPath.node as NodeSymbol)[UNSAFE]) return;
62
-
63
- // For testing
64
- // if (!blockPath.isProgram()) return;
65
-
66
- var blockStatement: NodePath<t.Block> = blockPath.isProgram()
67
- ? blockPath
68
- : (blockPath.get("body") as NodePath<t.BlockStatement>);
69
-
70
- // Track functions and illegal ones
71
- // A function is illegal if:
72
- // - the function is async or generator
73
- // - the function is redefined
74
- // - the function uses 'this', 'eval', or 'arguments'
75
- var functionPaths = new Map<
76
- string,
77
- NodePath<t.FunctionDeclaration>
78
- >();
79
- var illegalNames = new Set<string>();
80
-
81
- // Scan for function declarations
82
- blockPath.traverse({
83
- // Check for reassigned / redefined functions
84
- BindingIdentifier: {
85
- exit(path: NodePath<t.Identifier>) {
86
- if (!isVariableIdentifier(path)) return;
87
-
88
- const name = path.node.name;
89
- if (!path.parentPath?.isFunctionDeclaration()) {
90
- illegalNames.add(name);
91
- }
92
- },
93
- },
94
- // Find functions eligible for dispatching
95
- FunctionDeclaration: {
96
- exit(path: NodePath<t.FunctionDeclaration>) {
97
- const name = path.node.id.name;
98
- // If the function is not named, we can't dispatch it
99
- if (!name) {
100
- return;
101
- }
102
-
103
- // Do not apply to async or generator functions
104
- if (path.node.async || path.node.generator) {
105
- return;
106
- }
107
-
108
- // Do not apply to functions in nested scopes
109
- if (path.parentPath !== blockStatement || me.isSkipped(path)) {
110
- illegalNames.add(name);
111
- return;
112
- }
113
-
114
- if (isStrictMode(path)) {
115
- illegalNames.add(name);
116
- return;
117
- }
118
-
119
- // Do not apply to unsafe functions, redefined functions, or internal obfuscator functions
120
- if (
121
- (path.node as NodeSymbol)[UNSAFE] ||
122
- functionPaths.has(name) ||
123
- me.obfuscator.isInternalVariable(name)
124
- ) {
125
- illegalNames.add(name);
126
- return;
127
- }
128
-
129
- var hasAssignmentPattern = false;
130
-
131
- for (var param of path.get("params")) {
132
- if (param.isAssignmentPattern()) {
133
- hasAssignmentPattern = true;
134
- break;
135
- }
136
- param.traverse({
137
- AssignmentPattern(innerPath) {
138
- var fn = innerPath.getFunctionParent();
139
- if (fn === path) {
140
- hasAssignmentPattern = true;
141
- innerPath.stop();
142
- } else {
143
- innerPath.skip();
144
- }
145
- },
146
- });
147
-
148
- if (hasAssignmentPattern) break;
149
- }
150
-
151
- // Functions with default parameters are not fully supported
152
- // (Could be a Function Expression referencing outer scope)
153
- if (hasAssignmentPattern) {
154
- illegalNames.add(name);
155
- return;
156
- }
157
-
158
- functionPaths.set(name, path);
159
- },
160
- },
161
- });
162
-
163
- for (let name of illegalNames) {
164
- functionPaths.delete(name);
165
- }
166
-
167
- for (var name of functionPaths.keys()) {
168
- if (!me.computeProbabilityMap(me.options.dispatcher, name)) {
169
- functionPaths.delete(name);
170
- }
171
- }
172
-
173
- // No functions here to change
174
- if (functionPaths.size === 0) {
175
- return;
176
- }
177
-
178
- me.changeData.functions += functionPaths.size;
179
-
180
- const dispatcherName =
181
- me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++;
182
- const payloadName = me.getPlaceholder() + "_payload";
183
- const cacheName = me.getPlaceholder() + "_cache";
184
- const newNameMapping = new Map<string, string>();
185
-
186
- const keys = {
187
- placeholderNoMeaning: isDebug ? "noMeaning" : getRandomString(10),
188
- clearPayload: isDebug ? "clearPayload" : getRandomString(10),
189
- nonCall: isDebug ? "nonCall" : getRandomString(10),
190
- returnAsObject: isDebug ? "returnAsObject" : getRandomString(10),
191
- returnAsObjectProperty: isDebug
192
- ? "returnAsObjectProperty"
193
- : getRandomString(10),
194
- };
195
-
196
- for (var name of functionPaths.keys()) {
197
- newNameMapping.set(
198
- name,
199
- isDebug ? "_" + name : getRandomString(6) /** "_" + name */
200
- );
201
- }
202
-
203
- // Find identifiers calling/referencing the functions
204
- blockPath.traverse({
205
- ReferencedIdentifier: {
206
- exit(path: NodePath<t.Identifier | t.JSXIdentifier>) {
207
- if (path.isJSX()) return;
208
- if (isVariableFunctionIdentifier(path)) return;
209
- const name = path.node.name;
210
-
211
- var fnPath = functionPaths.get(name);
212
- if (!fnPath) return;
213
-
214
- var newName = newNameMapping.get(name);
215
-
216
- // Do not replace if not referencing the actual function
217
- if (path.scope.getBinding(name).path !== fnPath) {
218
- return;
219
- }
220
-
221
- const createDispatcherCall = (name, flagArg?) => {
222
- var dispatcherArgs = [t.stringLiteral(name)];
223
- if (flagArg) {
224
- dispatcherArgs.push(t.stringLiteral(flagArg));
225
- }
226
-
227
- var asObject = chance(50);
228
-
229
- if (asObject) {
230
- if (dispatcherArgs.length < 2) {
231
- dispatcherArgs.push(
232
- t.stringLiteral(keys.placeholderNoMeaning)
233
- );
234
- }
235
- dispatcherArgs.push(t.stringLiteral(keys.returnAsObject));
236
- }
237
-
238
- var callExpression: t.CallExpression | t.NewExpression =
239
- t.callExpression(
240
- t.identifier(dispatcherName),
241
- dispatcherArgs
242
- );
243
-
244
- if (!asObject) {
245
- return callExpression;
246
- }
247
-
248
- if (chance(50)) {
249
- (callExpression as t.Node).type = "NewExpression";
250
- }
251
-
252
- return t.memberExpression(
253
- callExpression,
254
- t.stringLiteral(keys.returnAsObjectProperty),
255
- true
256
- );
257
- };
258
-
259
- // Replace the identifier with a call to the function
260
- const parentPath = path.parentPath;
261
- if (path.key === "callee" && parentPath?.isCallExpression()) {
262
- var expressions: t.Expression[] = [];
263
- var callArguments = parentPath.node.arguments;
264
-
265
- if (callArguments.length === 0) {
266
- expressions.push(
267
- // Call the function
268
- createDispatcherCall(newName, keys.clearPayload)
269
- );
270
- } else {
271
- expressions.push(
272
- // Prepare the payload arguments
273
- t.assignmentExpression(
274
- "=",
275
- t.identifier(payloadName),
276
- t.arrayExpression(callArguments as t.Expression[])
277
- ),
278
-
279
- // Call the function
280
- createDispatcherCall(newName)
281
- );
282
- }
283
-
284
- const output =
285
- expressions.length === 1
286
- ? expressions[0]
287
- : t.sequenceExpression(expressions);
288
-
289
- if (!parentPath.container) return;
290
- parentPath.replaceWith(output);
291
- } else {
292
- if (!path.container) return;
293
- // Replace non-invocation references with a 'cached' version of the function
294
- path.replaceWith(createDispatcherCall(newName, keys.nonCall));
295
- }
296
- },
297
- },
298
- });
299
-
300
- const fnLengthProperties = [];
301
-
302
- // Create the dispatcher function
303
- const objectExpression = t.objectExpression(
304
- Array.from(newNameMapping).map(([name, newName]) => {
305
- const originalPath = functionPaths.get(name);
306
- const originalFn = originalPath.node;
307
-
308
- if (me.options.preserveFunctionLength) {
309
- const fnLength = computeFunctionLength(originalPath);
310
-
311
- if (fnLength >= 1) {
312
- // 0 is already the default
313
- fnLengthProperties.push(
314
- t.objectProperty(
315
- t.stringLiteral(newName),
316
- numericLiteral(fnLength)
317
- )
318
- );
319
- }
320
- }
321
-
322
- const newBody = [...originalFn.body.body];
323
- ok(Array.isArray(newBody));
324
-
325
- // Unpack parameters
326
- if (originalFn.params.length > 0) {
327
- newBody.unshift(
328
- t.variableDeclaration("var", [
329
- t.variableDeclarator(
330
- t.arrayPattern([...originalFn.params]),
331
- t.identifier(payloadName)
332
- ),
333
- ])
334
- );
335
- }
336
-
337
- // Add debug label
338
- if (isDebug) {
339
- newBody.unshift(
340
- t.expressionStatement(
341
- t.stringLiteral(`Dispatcher: ${name} -> ${newName}`)
342
- )
343
- );
344
- }
345
-
346
- const functionExpression = t.functionExpression(
347
- null,
348
- [],
349
- t.blockStatement(newBody)
350
- );
351
-
352
- for (var symbol of Object.getOwnPropertySymbols(originalFn)) {
353
- (functionExpression as any)[symbol] = (originalFn as any)[
354
- symbol
355
- ];
356
- }
357
-
358
- (functionExpression as NodeSymbol)[PREDICTABLE] = true;
359
-
360
- return t.objectProperty(
361
- t.stringLiteral(newName),
362
-
363
- functionExpression
364
- );
365
- })
366
- );
367
-
368
- const fnLengths = t.objectExpression(fnLengthProperties);
369
-
370
- const dispatcher = new Template(`
371
- function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) {
372
- var output;
373
- var fns = {objectExpression};
374
-
375
- if(flagArg === "${keys.clearPayload}") {
376
- ${payloadName} = [];
377
- }
378
- if(flagArg === "${keys.nonCall}") {
379
- function createFunction(){
380
- var fn = function(...args){
381
- ${payloadName} = args;
382
- return fns[name].apply(this);
383
- }
384
-
385
- var fnLength = fnLengths[name];
386
- if(fnLength) {
387
- ${setFunctionLength}(fn, fnLength);
388
- }
389
-
390
- return fn;
391
- }
392
- output = ${cacheName}[name] || (${cacheName}[name] = createFunction());
393
- } else {
394
- output = fns[name]();
395
- }
396
-
397
- if(returnTypeArg === "${keys.returnAsObject}") {
398
- return { "${keys.returnAsObjectProperty}": output };
399
- } else {
400
- return output;
401
- }
402
- }
403
- `).single({
404
- objectExpression,
405
- fnLengthsObjectExpression: fnLengths,
406
- });
407
-
408
- (dispatcher as NodeSymbol)[PREDICTABLE] = true;
409
-
410
- /**
411
- * Prepends the node into the block. (And registers the declaration)
412
- * @param node
413
- */
414
- function prepend(node: t.Statement) {
415
- var newPath = blockStatement.unshiftContainer<any, any, any>(
416
- "body",
417
- node
418
- )[0];
419
- blockStatement.scope.registerDeclaration(newPath);
420
- }
421
-
422
- // Insert the dispatcher function
423
- prepend(dispatcher);
424
-
425
- // Insert the payload variable
426
- prepend(
427
- t.variableDeclaration("var", [
428
- t.variableDeclarator(t.identifier(payloadName)),
429
- ])
430
- );
431
-
432
- // Insert the cache variable
433
- prepend(
434
- t.variableDeclaration("var", [
435
- t.variableDeclarator(
436
- t.identifier(cacheName),
437
- new Template(`Object["create"](null)`).expression()
438
- ),
439
- ])
440
- );
441
-
442
- // Remove original functions
443
- for (let path of functionPaths.values()) {
444
- path.remove();
445
- }
446
- },
447
- },
448
- },
449
- };
450
- };
1
+ import { PluginArg, PluginObject } from "./plugin";
2
+ import { NodePath } from "@babel/traverse";
3
+ import * as t from "@babel/types";
4
+ import Template from "../templates/template";
5
+ import { ok } from "assert";
6
+ import { chance, getRandomString } from "../utils/random-utils";
7
+ import { Order } from "../order";
8
+ import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
9
+ import {
10
+ computeFunctionLength,
11
+ isVariableFunctionIdentifier,
12
+ } from "../utils/function-utils";
13
+ import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate";
14
+ import { numericLiteral } from "../utils/node";
15
+ import {
16
+ isStrictMode,
17
+ isVariableIdentifier,
18
+ prependProgram,
19
+ } from "../utils/ast-utils";
20
+
21
+ export default ({ Plugin }: PluginArg): PluginObject => {
22
+ const me = Plugin(Order.Dispatcher, {
23
+ changeData: {
24
+ functions: 0,
25
+ },
26
+ });
27
+ let dispatcherCounter = 0;
28
+
29
+ const setFunctionLength = me.getPlaceholder("d_fnLength");
30
+
31
+ // in Debug mode, function names are preserved
32
+ const isDebug = false;
33
+
34
+ return {
35
+ visitor: {
36
+ "Program|Function": {
37
+ exit(_path) {
38
+ const blockPath = _path as NodePath<t.Program | t.Function>;
39
+
40
+ if (blockPath.isProgram()) {
41
+ blockPath.scope.crawl();
42
+
43
+ // Don't insert function length code when disabled
44
+ // Instead insert empty function as the identifier is still referenced
45
+ var insertNode = t.functionDeclaration(
46
+ t.identifier(setFunctionLength),
47
+ [],
48
+ t.blockStatement([])
49
+ );
50
+
51
+ if (me.options.preserveFunctionLength) {
52
+ // Insert function length code
53
+ insertNode = SetFunctionLengthTemplate.single({
54
+ fnName: setFunctionLength,
55
+ });
56
+ }
57
+
58
+ me.skip(prependProgram(_path, insertNode));
59
+ }
60
+
61
+ if ((blockPath.node as NodeSymbol)[UNSAFE]) return;
62
+
63
+ // For testing
64
+ // if (!blockPath.isProgram()) return;
65
+
66
+ var blockStatement: NodePath<t.Block> = blockPath.isProgram()
67
+ ? blockPath
68
+ : (blockPath.get("body") as NodePath<t.BlockStatement>);
69
+
70
+ // Track functions and illegal ones
71
+ // A function is illegal if:
72
+ // - the function is async or generator
73
+ // - the function is redefined
74
+ // - the function uses 'this', 'eval', or 'arguments'
75
+ var functionPaths = new Map<
76
+ string,
77
+ NodePath<t.FunctionDeclaration>
78
+ >();
79
+ var illegalNames = new Set<string>();
80
+
81
+ // Scan for function declarations
82
+ blockPath.traverse({
83
+ // Check for reassigned / redefined functions
84
+ BindingIdentifier: {
85
+ exit(path: NodePath<t.Identifier>) {
86
+ if (!isVariableIdentifier(path)) return;
87
+
88
+ const name = path.node.name;
89
+ if (!path.parentPath?.isFunctionDeclaration()) {
90
+ illegalNames.add(name);
91
+ }
92
+ },
93
+ },
94
+ // Find functions eligible for dispatching
95
+ FunctionDeclaration: {
96
+ exit(path: NodePath<t.FunctionDeclaration>) {
97
+ const name = path.node.id.name;
98
+ // If the function is not named, we can't dispatch it
99
+ if (!name) {
100
+ return;
101
+ }
102
+
103
+ // Do not apply to async or generator functions
104
+ if (path.node.async || path.node.generator) {
105
+ return;
106
+ }
107
+
108
+ // Do not apply to functions in nested scopes
109
+ if (path.parentPath !== blockStatement || me.isSkipped(path)) {
110
+ illegalNames.add(name);
111
+ return;
112
+ }
113
+
114
+ if (isStrictMode(path)) {
115
+ illegalNames.add(name);
116
+ return;
117
+ }
118
+
119
+ // Do not apply to unsafe functions, redefined functions, or internal obfuscator functions
120
+ if (
121
+ (path.node as NodeSymbol)[UNSAFE] ||
122
+ functionPaths.has(name) ||
123
+ me.obfuscator.isInternalVariable(name)
124
+ ) {
125
+ illegalNames.add(name);
126
+ return;
127
+ }
128
+
129
+ var hasAssignmentPattern = false;
130
+
131
+ for (var param of path.get("params")) {
132
+ if (param.isAssignmentPattern()) {
133
+ hasAssignmentPattern = true;
134
+ break;
135
+ }
136
+ param.traverse({
137
+ AssignmentPattern(innerPath) {
138
+ var fn = innerPath.getFunctionParent();
139
+ if (fn === path) {
140
+ hasAssignmentPattern = true;
141
+ innerPath.stop();
142
+ } else {
143
+ innerPath.skip();
144
+ }
145
+ },
146
+ });
147
+
148
+ if (hasAssignmentPattern) break;
149
+ }
150
+
151
+ // Functions with default parameters are not fully supported
152
+ // (Could be a Function Expression referencing outer scope)
153
+ if (hasAssignmentPattern) {
154
+ illegalNames.add(name);
155
+ return;
156
+ }
157
+
158
+ functionPaths.set(name, path);
159
+ },
160
+ },
161
+ });
162
+
163
+ for (let name of illegalNames) {
164
+ functionPaths.delete(name);
165
+ }
166
+
167
+ for (var name of functionPaths.keys()) {
168
+ if (!me.computeProbabilityMap(me.options.dispatcher, name)) {
169
+ functionPaths.delete(name);
170
+ }
171
+ }
172
+
173
+ // No functions here to change
174
+ if (functionPaths.size === 0) {
175
+ return;
176
+ }
177
+
178
+ me.changeData.functions += functionPaths.size;
179
+
180
+ const dispatcherName =
181
+ me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++;
182
+ const payloadName = me.getPlaceholder() + "_payload";
183
+ const cacheName = me.getPlaceholder() + "_cache";
184
+ const newNameMapping = new Map<string, string>();
185
+
186
+ const keys = {
187
+ placeholderNoMeaning: isDebug ? "noMeaning" : getRandomString(10),
188
+ clearPayload: isDebug ? "clearPayload" : getRandomString(10),
189
+ nonCall: isDebug ? "nonCall" : getRandomString(10),
190
+ returnAsObject: isDebug ? "returnAsObject" : getRandomString(10),
191
+ returnAsObjectProperty: isDebug
192
+ ? "returnAsObjectProperty"
193
+ : getRandomString(10),
194
+ };
195
+
196
+ for (var name of functionPaths.keys()) {
197
+ newNameMapping.set(
198
+ name,
199
+ isDebug ? "_" + name : getRandomString(6) /** "_" + name */
200
+ );
201
+ }
202
+
203
+ // Find identifiers calling/referencing the functions
204
+ blockPath.traverse({
205
+ ReferencedIdentifier: {
206
+ exit(path: NodePath<t.Identifier | t.JSXIdentifier>) {
207
+ if (path.isJSX()) return;
208
+ if (isVariableFunctionIdentifier(path)) return;
209
+ const name = path.node.name;
210
+
211
+ var fnPath = functionPaths.get(name);
212
+ if (!fnPath) return;
213
+
214
+ var newName = newNameMapping.get(name);
215
+
216
+ // Do not replace if not referencing the actual function
217
+ if (path.scope.getBinding(name).path !== fnPath) {
218
+ return;
219
+ }
220
+
221
+ const createDispatcherCall = (name, flagArg?) => {
222
+ var dispatcherArgs = [t.stringLiteral(name)];
223
+ if (flagArg) {
224
+ dispatcherArgs.push(t.stringLiteral(flagArg));
225
+ }
226
+
227
+ var asObject = chance(50);
228
+
229
+ if (asObject) {
230
+ if (dispatcherArgs.length < 2) {
231
+ dispatcherArgs.push(
232
+ t.stringLiteral(keys.placeholderNoMeaning)
233
+ );
234
+ }
235
+ dispatcherArgs.push(t.stringLiteral(keys.returnAsObject));
236
+ }
237
+
238
+ var callExpression: t.CallExpression | t.NewExpression =
239
+ t.callExpression(
240
+ t.identifier(dispatcherName),
241
+ dispatcherArgs
242
+ );
243
+
244
+ if (!asObject) {
245
+ return callExpression;
246
+ }
247
+
248
+ if (chance(50)) {
249
+ (callExpression as t.Node).type = "NewExpression";
250
+ }
251
+
252
+ return t.memberExpression(
253
+ callExpression,
254
+ t.stringLiteral(keys.returnAsObjectProperty),
255
+ true
256
+ );
257
+ };
258
+
259
+ // Replace the identifier with a call to the function
260
+ const parentPath = path.parentPath;
261
+ if (path.key === "callee" && parentPath?.isCallExpression()) {
262
+ var expressions: t.Expression[] = [];
263
+ var callArguments = parentPath.node.arguments;
264
+
265
+ if (callArguments.length === 0) {
266
+ expressions.push(
267
+ // Call the function
268
+ createDispatcherCall(newName, keys.clearPayload)
269
+ );
270
+ } else {
271
+ expressions.push(
272
+ // Prepare the payload arguments
273
+ t.assignmentExpression(
274
+ "=",
275
+ t.identifier(payloadName),
276
+ t.arrayExpression(callArguments as t.Expression[])
277
+ ),
278
+
279
+ // Call the function
280
+ createDispatcherCall(newName)
281
+ );
282
+ }
283
+
284
+ const output =
285
+ expressions.length === 1
286
+ ? expressions[0]
287
+ : t.sequenceExpression(expressions);
288
+
289
+ if (!parentPath.container) return;
290
+ parentPath.replaceWith(output);
291
+ } else {
292
+ if (!path.container) return;
293
+ // Replace non-invocation references with a 'cached' version of the function
294
+ path.replaceWith(createDispatcherCall(newName, keys.nonCall));
295
+ }
296
+ },
297
+ },
298
+ });
299
+
300
+ const fnLengthProperties = [];
301
+
302
+ // Create the dispatcher function
303
+ const objectExpression = t.objectExpression(
304
+ Array.from(newNameMapping).map(([name, newName]) => {
305
+ const originalPath = functionPaths.get(name);
306
+ const originalFn = originalPath.node;
307
+
308
+ if (me.options.preserveFunctionLength) {
309
+ const fnLength = computeFunctionLength(originalPath);
310
+
311
+ if (fnLength >= 1) {
312
+ // 0 is already the default
313
+ fnLengthProperties.push(
314
+ t.objectProperty(
315
+ t.stringLiteral(newName),
316
+ numericLiteral(fnLength)
317
+ )
318
+ );
319
+ }
320
+ }
321
+
322
+ const newBody = [...originalFn.body.body];
323
+ ok(Array.isArray(newBody));
324
+
325
+ // Unpack parameters
326
+ if (originalFn.params.length > 0) {
327
+ newBody.unshift(
328
+ t.variableDeclaration("var", [
329
+ t.variableDeclarator(
330
+ t.arrayPattern([...originalFn.params]),
331
+ t.identifier(payloadName)
332
+ ),
333
+ ])
334
+ );
335
+ }
336
+
337
+ // Add debug label
338
+ if (isDebug) {
339
+ newBody.unshift(
340
+ t.expressionStatement(
341
+ t.stringLiteral(`Dispatcher: ${name} -> ${newName}`)
342
+ )
343
+ );
344
+ }
345
+
346
+ const functionExpression = t.functionExpression(
347
+ null,
348
+ [],
349
+ t.blockStatement(newBody)
350
+ );
351
+
352
+ for (var symbol of Object.getOwnPropertySymbols(originalFn)) {
353
+ (functionExpression as any)[symbol] = (originalFn as any)[
354
+ symbol
355
+ ];
356
+ }
357
+
358
+ (functionExpression as NodeSymbol)[PREDICTABLE] = true;
359
+
360
+ return t.objectProperty(
361
+ t.stringLiteral(newName),
362
+
363
+ functionExpression
364
+ );
365
+ })
366
+ );
367
+
368
+ const fnLengths = t.objectExpression(fnLengthProperties);
369
+
370
+ const dispatcher = new Template(`
371
+ function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) {
372
+ var output;
373
+ var fns = {objectExpression};
374
+
375
+ if(flagArg === "${keys.clearPayload}") {
376
+ ${payloadName} = [];
377
+ }
378
+ if(flagArg === "${keys.nonCall}") {
379
+ function createFunction(){
380
+ var fn = function(...args){
381
+ ${payloadName} = args;
382
+ return fns[name].apply(this);
383
+ }
384
+
385
+ var fnLength = fnLengths[name];
386
+ if(fnLength) {
387
+ ${setFunctionLength}(fn, fnLength);
388
+ }
389
+
390
+ return fn;
391
+ }
392
+ output = ${cacheName}[name] || (${cacheName}[name] = createFunction());
393
+ } else {
394
+ output = fns[name]();
395
+ }
396
+
397
+ if(returnTypeArg === "${keys.returnAsObject}") {
398
+ return { "${keys.returnAsObjectProperty}": output };
399
+ } else {
400
+ return output;
401
+ }
402
+ }
403
+ `).single({
404
+ objectExpression,
405
+ fnLengthsObjectExpression: fnLengths,
406
+ });
407
+
408
+ (dispatcher as NodeSymbol)[PREDICTABLE] = true;
409
+
410
+ /**
411
+ * Prepends the node into the block. (And registers the declaration)
412
+ * @param node
413
+ */
414
+ function prepend(node: t.Statement) {
415
+ var newPath = blockStatement.unshiftContainer<any, any, any>(
416
+ "body",
417
+ node
418
+ )[0];
419
+ blockStatement.scope.registerDeclaration(newPath);
420
+ }
421
+
422
+ // Insert the dispatcher function
423
+ prepend(dispatcher);
424
+
425
+ // Insert the payload variable
426
+ prepend(
427
+ t.variableDeclaration("var", [
428
+ t.variableDeclarator(t.identifier(payloadName)),
429
+ ])
430
+ );
431
+
432
+ // Insert the cache variable
433
+ prepend(
434
+ t.variableDeclaration("var", [
435
+ t.variableDeclarator(
436
+ t.identifier(cacheName),
437
+ new Template(`Object["create"](null)`).expression()
438
+ ),
439
+ ])
440
+ );
441
+
442
+ // Remove original functions
443
+ for (let path of functionPaths.values()) {
444
+ path.remove();
445
+ }
446
+ },
447
+ },
448
+ },
449
+ };
450
+ };