js-confuser 1.5.9 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.github/workflows/node.js.yml +2 -2
  2. package/CHANGELOG.md +55 -0
  3. package/README.md +346 -165
  4. package/dist/constants.js +6 -2
  5. package/dist/index.js +9 -21
  6. package/dist/obfuscator.js +19 -31
  7. package/dist/options.js +5 -5
  8. package/dist/order.js +1 -3
  9. package/dist/presets.js +6 -7
  10. package/dist/probability.js +2 -4
  11. package/dist/templates/bufferToString.js +13 -0
  12. package/dist/templates/crash.js +3 -3
  13. package/dist/templates/es5.js +18 -0
  14. package/dist/templates/functionLength.js +16 -0
  15. package/dist/transforms/calculator.js +77 -21
  16. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +980 -367
  17. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +4 -1
  18. package/dist/transforms/controlFlowFlattening/switchCaseObfuscation.js +25 -26
  19. package/dist/transforms/deadCode.js +33 -25
  20. package/dist/transforms/dispatcher.js +8 -4
  21. package/dist/transforms/es5/antiDestructuring.js +2 -0
  22. package/dist/transforms/es5/es5.js +31 -34
  23. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +92 -58
  24. package/dist/transforms/finalizer.js +82 -0
  25. package/dist/transforms/flatten.js +229 -148
  26. package/dist/transforms/identifier/globalAnalysis.js +88 -0
  27. package/dist/transforms/identifier/globalConcealing.js +10 -83
  28. package/dist/transforms/identifier/movedDeclarations.js +35 -88
  29. package/dist/transforms/identifier/renameVariables.js +124 -59
  30. package/dist/transforms/identifier/variableAnalysis.js +58 -62
  31. package/dist/transforms/lock/lock.js +0 -37
  32. package/dist/transforms/minify.js +60 -57
  33. package/dist/transforms/opaquePredicates.js +1 -1
  34. package/dist/transforms/preparation/preparation.js +2 -2
  35. package/dist/transforms/preparation.js +231 -0
  36. package/dist/transforms/renameLabels.js +1 -1
  37. package/dist/transforms/rgf.js +139 -247
  38. package/dist/transforms/stack.js +128 -26
  39. package/dist/transforms/string/encoding.js +150 -179
  40. package/dist/transforms/string/stringCompression.js +14 -15
  41. package/dist/transforms/string/stringConcealing.js +25 -8
  42. package/dist/transforms/string/stringEncoding.js +13 -24
  43. package/dist/transforms/transform.js +12 -19
  44. package/dist/traverse.js +24 -10
  45. package/dist/util/gen.js +17 -1
  46. package/dist/util/identifiers.js +37 -3
  47. package/dist/util/insert.js +35 -4
  48. package/dist/util/random.js +15 -0
  49. package/docs/ControlFlowFlattening.md +595 -0
  50. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  51. package/{Integrity.md → docs/Integrity.md} +2 -2
  52. package/docs/RGF.md +419 -0
  53. package/package.json +5 -5
  54. package/src/constants.ts +3 -0
  55. package/src/index.ts +2 -2
  56. package/src/obfuscator.ts +19 -31
  57. package/src/options.ts +14 -103
  58. package/src/order.ts +1 -5
  59. package/src/presets.ts +6 -7
  60. package/src/probability.ts +2 -3
  61. package/src/templates/bufferToString.ts +68 -0
  62. package/src/templates/crash.ts +15 -19
  63. package/src/templates/es5.ts +131 -0
  64. package/src/templates/functionLength.ts +14 -0
  65. package/src/transforms/calculator.ts +122 -59
  66. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +1583 -571
  67. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +4 -1
  68. package/src/transforms/deadCode.ts +383 -26
  69. package/src/transforms/dispatcher.ts +9 -4
  70. package/src/transforms/es5/antiDestructuring.ts +2 -0
  71. package/src/transforms/es5/es5.ts +32 -77
  72. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +133 -129
  73. package/src/transforms/{hexadecimalNumbers.ts → finalizer.ts} +29 -13
  74. package/src/transforms/flatten.ts +357 -300
  75. package/src/transforms/identifier/globalAnalysis.ts +85 -0
  76. package/src/transforms/identifier/globalConcealing.ts +14 -103
  77. package/src/transforms/identifier/movedDeclarations.ts +49 -102
  78. package/src/transforms/identifier/renameVariables.ts +149 -78
  79. package/src/transforms/identifier/variableAnalysis.ts +66 -73
  80. package/src/transforms/lock/lock.ts +1 -42
  81. package/src/transforms/minify.ts +91 -75
  82. package/src/transforms/opaquePredicates.ts +2 -2
  83. package/src/transforms/preparation.ts +238 -0
  84. package/src/transforms/renameLabels.ts +2 -2
  85. package/src/transforms/rgf.ts +213 -405
  86. package/src/transforms/stack.ts +156 -36
  87. package/src/transforms/string/encoding.ts +115 -212
  88. package/src/transforms/string/stringCompression.ts +27 -18
  89. package/src/transforms/string/stringConcealing.ts +39 -9
  90. package/src/transforms/string/stringEncoding.ts +18 -18
  91. package/src/transforms/transform.ts +21 -23
  92. package/src/traverse.ts +23 -4
  93. package/src/types.ts +2 -1
  94. package/src/util/gen.ts +28 -3
  95. package/src/util/identifiers.ts +43 -2
  96. package/src/util/insert.ts +38 -3
  97. package/src/util/random.ts +13 -0
  98. package/test/code/Cash.test.ts +1 -1
  99. package/test/code/Dynamic.test.ts +12 -10
  100. package/test/code/ES6.src.js +146 -0
  101. package/test/code/ES6.test.ts +28 -2
  102. package/test/index.test.ts +2 -1
  103. package/test/probability.test.ts +44 -0
  104. package/test/templates/template.test.ts +1 -1
  105. package/test/transforms/antiTooling.test.ts +22 -0
  106. package/test/transforms/calculator.test.ts +40 -0
  107. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +702 -160
  108. package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +173 -0
  109. package/test/transforms/deadCode.test.ts +66 -15
  110. package/test/transforms/dispatcher.test.ts +20 -1
  111. package/test/transforms/es5/antiDestructuring.test.ts +16 -0
  112. package/test/transforms/flatten.test.ts +399 -86
  113. package/test/transforms/identifier/movedDeclarations.test.ts +63 -8
  114. package/test/transforms/identifier/renameVariables.test.ts +119 -0
  115. package/test/transforms/lock/antiDebug.test.ts +2 -2
  116. package/test/transforms/lock/lock.test.ts +1 -48
  117. package/test/transforms/minify.test.ts +104 -0
  118. package/test/transforms/preparation.test.ts +157 -0
  119. package/test/transforms/rgf.test.ts +261 -381
  120. package/test/transforms/stack.test.ts +143 -21
  121. package/test/transforms/string/stringCompression.test.ts +39 -0
  122. package/test/transforms/string/stringConcealing.test.ts +82 -0
  123. package/test/transforms/string/stringEncoding.test.ts +53 -2
  124. package/test/transforms/transform.test.ts +66 -0
  125. package/test/traverse.test.ts +139 -0
  126. package/test/util/identifiers.test.ts +113 -1
  127. package/test/util/insert.test.ts +57 -3
  128. package/src/transforms/controlFlowFlattening/choiceFlowObfuscation.ts +0 -87
  129. package/src/transforms/controlFlowFlattening/controlFlowObfuscation.ts +0 -203
  130. package/src/transforms/controlFlowFlattening/switchCaseObfuscation.ts +0 -130
  131. package/src/transforms/eval.ts +0 -89
  132. package/src/transforms/hideInitializingCode.ts +0 -432
  133. package/src/transforms/identifier/nameRecycling.ts +0 -280
  134. package/src/transforms/label.ts +0 -64
  135. package/src/transforms/preparation/nameConflicts.ts +0 -102
  136. package/src/transforms/preparation/preparation.ts +0 -176
  137. package/test/transforms/controlFlowFlattening/controlFlowObfuscation.test.ts +0 -101
  138. package/test/transforms/controlFlowFlattening/switchCaseObfuscation.test.ts +0 -120
  139. package/test/transforms/eval.test.ts +0 -131
  140. package/test/transforms/hideInitializingCode.test.ts +0 -336
  141. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
  142. package/test/transforms/preparation/nameConflicts.test.ts +0 -52
  143. package/test/transforms/preparation/preparation.test.ts +0 -62
@@ -1,17 +1,14 @@
1
1
  import { ok } from "assert";
2
- import { reservedIdentifiers } from "../constants";
2
+ import { noRenameVariablePrefix, reservedIdentifiers } from "../constants";
3
3
  import { ObfuscateOrder } from "../order";
4
- import traverse, { walk } from "../traverse";
4
+ import { walk } from "../traverse";
5
5
  import {
6
- FunctionDeclaration,
7
6
  Identifier,
8
7
  ReturnStatement,
9
8
  VariableDeclaration,
10
9
  VariableDeclarator,
11
10
  CallExpression,
12
11
  MemberExpression,
13
- ThisExpression,
14
- ArrayExpression,
15
12
  ExpressionStatement,
16
13
  AssignmentExpression,
17
14
  Node,
@@ -20,78 +17,94 @@ import {
20
17
  FunctionExpression,
21
18
  ObjectExpression,
22
19
  Property,
23
- SpreadElement,
24
20
  Literal,
25
- IfStatement,
26
- ThrowStatement,
27
- NewExpression,
28
21
  AwaitExpression,
22
+ FunctionDeclaration,
23
+ SpreadElement,
29
24
  UnaryExpression,
25
+ RestElement,
30
26
  } from "../util/gen";
31
27
  import { getIdentifierInfo } from "../util/identifiers";
32
28
  import {
33
29
  getBlockBody,
34
- getVarContext,
35
- isFunction,
36
30
  prepend,
37
31
  clone,
32
+ getDefiningContext,
33
+ computeFunctionLength,
38
34
  } from "../util/insert";
39
35
  import { shuffle } from "../util/random";
40
36
  import Transform from "./transform";
37
+ import { FunctionLengthTemplate } from "../templates/functionLength";
41
38
 
42
39
  /**
43
- * Brings every function to the global level.
40
+ * Flatten takes functions and isolates them from their original scope, and brings it to the top level of the program.
41
+ *
42
+ * An additional `flatObject` parameter is passed in, giving access to the original scoped variables.
44
43
  *
45
- * Functions take parameters, input, have a return value and return modified changes to the scoped variables.
44
+ * The `flatObject` uses `get` and `set` properties to allow easy an AST transformation:
46
45
  *
47
46
  * ```js
48
- * function topLevel(ref1, ref2, refN, param1, param2, paramN){
49
- * return [ref1, ref2, refN, returnValue];
47
+ * // Input
48
+ * function myFunction(myParam){
49
+ * modified = true;
50
+ * if(reference) {
51
+ *
52
+ * }
53
+ * ...
54
+ * console.log(myParam);
55
+ * }
56
+ *
57
+ * // Output
58
+ * function myFunction_flat([myParam], flatObject){
59
+ * flatObject["set_modified"] = true;
60
+ * if(flatObject["get_reference"]) {
61
+ *
62
+ * }
63
+ * ...
64
+ * console.log(myParam)
65
+ * }
66
+ *
67
+ * function myFunction(){
68
+ * var flatObject = {
69
+ * set set_modified(v) { modified = v }
70
+ * get get_reference() { return reference }
71
+ * }
72
+ * return myFunction_flat([...arguments], flatObject)
50
73
  * }
51
74
  * ```
52
75
  *
53
76
  * Flatten is used to make functions eligible for the RGF transformation.
77
+ *
78
+ * - `myFunction_flat` is now eligible because it does not rely on outside scoped variables
54
79
  */
55
80
  export default class Flatten extends Transform {
81
+ isDebug = false;
82
+
56
83
  definedNames: Map<Node, Set<string>>;
84
+
85
+ // Array of FunctionDeclaration nodes
57
86
  flattenedFns: Node[];
58
87
  gen: ReturnType<Transform["getGenerator"]>;
59
88
 
89
+ functionLengthName: string;
90
+
60
91
  constructor(o) {
61
92
  super(o, ObfuscateOrder.Flatten);
62
93
 
63
94
  this.definedNames = new Map();
64
95
  this.flattenedFns = [];
65
- this.gen = this.getGenerator();
96
+ this.gen = this.getGenerator("mangled");
97
+
98
+ if (this.isDebug) {
99
+ console.warn("Flatten debug mode");
100
+ }
66
101
  }
67
102
 
68
103
  apply(tree) {
69
- traverse(tree, (o, p) => {
70
- if (
71
- o.type == "Identifier" &&
72
- !reservedIdentifiers.has(o.name) &&
73
- !this.options.globalVariables.has(o.name)
74
- ) {
75
- var info = getIdentifierInfo(o, p);
76
- if (info.spec.isReferenced) {
77
- if (info.spec.isDefined) {
78
- var c = getVarContext(o, p);
79
- if (c) {
80
- if (!this.definedNames.has(c)) {
81
- this.definedNames.set(c, new Set([o.name]));
82
- } else {
83
- this.definedNames.get(c).add(o.name);
84
- }
85
- }
86
- }
87
- }
88
- }
89
- });
90
-
91
104
  super.apply(tree);
92
105
 
93
106
  if (this.flattenedFns.length) {
94
- prepend(tree, VariableDeclaration(this.flattenedFns));
107
+ prepend(tree, ...this.flattenedFns);
95
108
  }
96
109
  }
97
110
 
@@ -100,6 +113,7 @@ export default class Flatten extends Transform {
100
113
  (object.type == "FunctionDeclaration" ||
101
114
  object.type === "FunctionExpression") &&
102
115
  object.body.type == "BlockStatement" &&
116
+ !object.$requiresEval &&
103
117
  !object.generator &&
104
118
  !object.params.find((x) => x.type !== "Identifier")
105
119
  );
@@ -120,7 +134,7 @@ export default class Flatten extends Transform {
120
134
  if (
121
135
  parents[0].type === "Property" &&
122
136
  parents[0].value === object &&
123
- parents[0].kind !== "init"
137
+ (parents[0].kind !== "init" || parents[0].method)
124
138
  ) {
125
139
  return;
126
140
  }
@@ -140,36 +154,37 @@ export default class Flatten extends Transform {
140
154
  parents[0].id?.name;
141
155
 
142
156
  if (parents[0]?.type === "Property" && parents[0]?.key) {
143
- currentFnName =
144
- currentFnName ||
145
- String(parents[0]?.key?.name || parents[0]?.key?.value);
157
+ currentFnName = currentFnName || String(parents[0]?.key?.name);
146
158
  }
147
159
 
148
160
  if (!currentFnName) currentFnName = "unnamed";
149
161
 
150
- var defined = new Set<string>();
151
- var references = new Set<string>();
152
- var modified = new Set<string>();
162
+ var definedMap = new Map<Node, Set<string>>();
153
163
 
154
164
  var illegal = new Set<string>();
155
165
  var isIllegal = false;
156
166
 
157
- var definedAbove = new Set<string>(this.options.globalVariables);
158
-
159
- parents.forEach((x) => {
160
- var set = this.definedNames.get(x);
161
- if (set) {
162
- set.forEach((name) => definedAbove.add(name));
163
- }
164
- });
167
+ var identifierNodes: [
168
+ Node,
169
+ Node[],
170
+ ReturnType<typeof getIdentifierInfo>
171
+ ][] = [];
165
172
 
166
173
  walk(object, parents, (o, p) => {
167
- if (object.id && o === object.id) {
168
- return;
174
+ if (
175
+ (o.type === "Identifier" && o.name === "arguments") ||
176
+ (o.type === "UnaryExpression" && o.operator === "delete") ||
177
+ o.type == "ThisExpression" ||
178
+ o.type == "Super" ||
179
+ o.type == "MetaProperty"
180
+ ) {
181
+ isIllegal = true;
182
+ return "EXIT";
169
183
  }
170
184
 
171
185
  if (
172
186
  o.type == "Identifier" &&
187
+ o !== object.id &&
173
188
  !this.options.globalVariables.has(o.name) &&
174
189
  !reservedIdentifiers.has(o.name)
175
190
  ) {
@@ -178,45 +193,36 @@ export default class Flatten extends Transform {
178
193
  return;
179
194
  }
180
195
 
181
- if (o.hidden) {
196
+ if (
197
+ info.spec.isExported ||
198
+ o.name.startsWith(noRenameVariablePrefix)
199
+ ) {
182
200
  illegal.add(o.name);
183
- } else if (info.spec.isDefined) {
184
- defined.add(o.name);
185
- } else if (info.spec.isModified) {
186
- modified.add(o.name);
187
- } else {
188
- references.add(o.name);
201
+
202
+ return;
189
203
  }
190
- }
191
204
 
192
- if (o.type == "TryStatement") {
193
- isIllegal = true;
194
- return "EXIT";
195
- }
205
+ if (info.spec.isDefined) {
206
+ var definingContext = getDefiningContext(o, p);
196
207
 
197
- if (o.type == "Identifier") {
198
- if (o.name == "arguments") {
199
- isIllegal = true;
200
- return "EXIT";
208
+ if (!definedMap.has(definingContext)) {
209
+ definedMap.set(definingContext, new Set([o.name]));
210
+ } else {
211
+ definedMap.get(definingContext).add(o.name);
212
+ }
213
+ return;
201
214
  }
202
- }
203
215
 
204
- if (o.type == "ThisExpression") {
205
- isIllegal = true;
206
- return "EXIT";
207
- }
216
+ var isDefined = p.find(
217
+ (x) => definedMap.has(x) && definedMap.get(x).has(o.name)
218
+ );
208
219
 
209
- if (o.type == "Super") {
210
- isIllegal = true;
211
- return "EXIT";
212
- }
213
-
214
- if (o.type == "MetaProperty") {
215
- isIllegal = true;
216
- return "EXIT";
220
+ if (!isDefined) {
221
+ identifierNodes.push([o, p, info]);
222
+ }
217
223
  }
218
224
 
219
- if (o.type == "VariableDeclaration" && o.kind !== "var") {
225
+ if (o.type == "TryStatement") {
220
226
  isIllegal = true;
221
227
  return "EXIT";
222
228
  }
@@ -229,257 +235,308 @@ export default class Flatten extends Transform {
229
235
  return;
230
236
  }
231
237
 
232
- defined.forEach((name) => {
233
- references.delete(name);
234
- modified.delete(name);
235
- });
238
+ var newFnName = this.getPlaceholder() + "_flat_" + currentFnName;
239
+ var flatObjectName = this.getPlaceholder() + "_flat_object";
236
240
 
237
- // console.log(object.id.name, illegal, references);
241
+ const getFlatObjectMember = (propertyName: string) => {
242
+ return MemberExpression(
243
+ Identifier(flatObjectName),
244
+ Literal(propertyName),
245
+ true
246
+ );
247
+ };
238
248
 
239
- var input = Array.from(new Set([...modified, ...references]));
249
+ var getterPropNames: { [identifierName: string]: string } =
250
+ Object.create(null);
251
+ var setterPropNames: { [identifierName: string]: string } =
252
+ Object.create(null);
253
+ var typeofPropNames: { [identifierName: string]: string } =
254
+ Object.create(null);
255
+ var callPropNames: { [identifierName: string]: string } =
256
+ Object.create(null);
240
257
 
241
- if (Array.from(input).find((x) => !definedAbove.has(x))) {
242
- return;
243
- }
258
+ for (var [o, p, info] of identifierNodes) {
259
+ var identifierName: string = o.name;
260
+ if (
261
+ p.find(
262
+ (x) => definedMap.has(x) && definedMap.get(x).has(identifierName)
263
+ )
264
+ )
265
+ continue;
244
266
 
245
- var output = Array.from(modified);
267
+ ok(!info.spec.isDefined);
246
268
 
247
- var newName = this.getPlaceholder() + "_flat_" + currentFnName;
248
- var resultName = this.getPlaceholder();
249
- var propName = this.gen.generate();
269
+ var type = info.spec.isModified ? "setter" : "getter";
250
270
 
251
- var newOutputNames: { [originalName: string]: string } =
252
- Object.create(null);
253
- output.forEach((name) => {
254
- newOutputNames[name] = this.gen.generate();
255
- });
256
- var returnOutputName = this.gen.generate();
257
-
258
- getBlockBody(object.body).push(ReturnStatement());
259
- walk(object.body, [object, ...parents], (o, p) => {
260
- return () => {
261
- // Change return statements from
262
- // return (argument)
263
- // to
264
- // return [ [modifiedRefs], ]
265
- if (o.type == "ReturnStatement" && getVarContext(o, p) === object) {
266
- var returnObject = ObjectExpression(
267
- output.map((outputName) =>
268
- Property(
269
- Literal(newOutputNames[outputName]),
270
- Identifier(outputName),
271
- true
272
- )
273
- )
274
- );
271
+ switch (type) {
272
+ case "setter":
273
+ var setterPropName = setterPropNames[identifierName];
274
+ if (typeof setterPropName === "undefined") {
275
+ // No getter function made yet, make it (Try to re-use getter name if available)
276
+ setterPropName =
277
+ getterPropNames[identifierName] ||
278
+ (this.isDebug ? "set_" + identifierName : this.gen.generate());
279
+ setterPropNames[identifierName] = setterPropName;
280
+ }
281
+
282
+ // If an update expression, ensure a getter function is also available. Ex: a++
283
+ if (p[0].type === "UpdateExpression") {
284
+ getterPropNames[identifierName] = setterPropName;
285
+ } else {
286
+ // If assignment on member expression, ensure a getter function is also available: Ex. myObject.property = ...
287
+ var assignmentIndex = p.findIndex(
288
+ (x) => x.type === "AssignmentExpression"
289
+ );
290
+ if (
291
+ assignmentIndex !== -1 &&
292
+ p[assignmentIndex].left.type !== "Identifier"
293
+ ) {
294
+ getterPropNames[identifierName] = setterPropName;
295
+ }
296
+ }
297
+
298
+ // calls flatObject.set_identifier_value(newValue)
299
+ this.replace(o, getFlatObjectMember(setterPropName));
300
+ break;
301
+
302
+ case "getter":
303
+ var getterPropName = getterPropNames[identifierName];
304
+ if (typeof getterPropName === "undefined") {
305
+ // No getter function made yet, make it (Try to re-use setter name if available)
306
+ getterPropName =
307
+ setterPropNames[identifierName] ||
308
+ (this.isDebug ? "get_" + identifierName : this.gen.generate());
309
+ getterPropNames[identifierName] = getterPropName;
310
+ }
275
311
 
312
+ // Typeof expression check
276
313
  if (
277
- o.argument &&
278
- !(
279
- o.argument.type == "Identifier" &&
280
- o.argument.name == "undefined"
281
- )
314
+ p[0].type === "UnaryExpression" &&
315
+ p[0].operator === "typeof" &&
316
+ p[0].argument === o
282
317
  ) {
283
- returnObject.properties.push(
284
- Property(Literal(returnOutputName), clone(o.argument), true)
318
+ var typeofPropName = typeofPropNames[identifierName];
319
+ if (typeof typeofPropName === "undefined") {
320
+ // No typeof getter function made yet, make it (Don't re-use getter/setter names)
321
+ typeofPropName = this.isDebug
322
+ ? "get_typeof_" + identifierName
323
+ : this.gen.generate();
324
+ typeofPropNames[identifierName] = typeofPropName;
325
+ }
326
+
327
+ // Replace the entire unary expression not just the identifier node
328
+ // calls flatObject.get_typeof_identifier()
329
+ this.replace(p[0], getFlatObjectMember(typeofPropName));
330
+ break;
331
+ }
332
+
333
+ // Bound call-expression check
334
+ if (p[0].type === "CallExpression" && p[0].callee === o) {
335
+ var callPropName = callPropNames[identifierName];
336
+ if (typeof callPropName === "undefined") {
337
+ callPropName = this.isDebug
338
+ ? "call_" + identifierName
339
+ : this.gen.generate();
340
+ callPropNames[identifierName] = callPropName;
341
+ }
342
+
343
+ // Replace the entire call expression not just the identifier node
344
+ // calls flatObject.call_identifier(...arguments)
345
+ this.replace(
346
+ p[0],
347
+ CallExpression(
348
+ getFlatObjectMember(callPropName),
349
+ p[0].arguments
350
+ )
285
351
  );
352
+ break;
286
353
  }
287
354
 
288
- o.argument = AssignmentExpression(
289
- "=",
290
- MemberExpression(
291
- Identifier(resultName),
292
- Identifier(propName),
293
- false
294
- ),
295
- returnObject
296
- );
297
- }
298
- };
299
- });
355
+ // calls flatObject.get_identifier_value()
356
+ this.replace(o, getFlatObjectMember(getterPropName));
357
+ break;
358
+ }
359
+ }
300
360
 
301
- var newBody = getBlockBody(object.body);
361
+ // Create the getter and setter functions
362
+ var flatObjectProperties: Node[] = [];
302
363
 
303
- var newFunctionExpression = FunctionExpression(
304
- [
305
- ArrayPattern(input.map(Identifier)),
306
- ArrayPattern(clone(object.params)),
307
- Identifier(resultName),
308
- ],
309
- newBody
310
- );
364
+ // Getter functions
365
+ for (var identifierName in getterPropNames) {
366
+ var getterPropName = getterPropNames[identifierName];
311
367
 
312
- newFunctionExpression.async = !!object.async;
313
- newFunctionExpression.generator = !!object.generator;
368
+ flatObjectProperties.push(
369
+ Property(
370
+ Literal(getterPropName),
371
+ FunctionExpression(
372
+ [],
373
+ [ReturnStatement(Identifier(identifierName))]
374
+ ),
375
+ true,
376
+ "get"
377
+ )
378
+ );
379
+ }
314
380
 
315
- var property = Property(
316
- Identifier(newName),
317
- newFunctionExpression,
318
- false
319
- );
320
- property.kind = "set";
381
+ // Get typeof functions
382
+ for (var identifierName in typeofPropNames) {
383
+ var typeofPropName = typeofPropNames[identifierName];
384
+
385
+ flatObjectProperties.push(
386
+ Property(
387
+ Literal(typeofPropName),
388
+ FunctionExpression(
389
+ [],
390
+ [
391
+ ReturnStatement(
392
+ UnaryExpression("typeof", Identifier(identifierName))
393
+ ),
394
+ ]
395
+ ),
396
+ true,
397
+ "get"
398
+ )
399
+ );
400
+ }
321
401
 
322
- this.flattenedFns.push(
323
- VariableDeclarator(newName, newFunctionExpression)
324
- );
402
+ // Call functions
403
+ for (var identifierName in callPropNames) {
404
+ var callPropName = callPropNames[identifierName];
405
+ var argumentsName = this.getPlaceholder();
406
+ flatObjectProperties.push(
407
+ Property(
408
+ Literal(callPropName),
409
+ FunctionExpression(
410
+ [RestElement(Identifier(argumentsName))],
411
+ [
412
+ ReturnStatement(
413
+ CallExpression(Identifier(identifierName), [
414
+ SpreadElement(Identifier(argumentsName)),
415
+ ])
416
+ ),
417
+ ]
418
+ ),
419
+ true
420
+ )
421
+ );
422
+ }
423
+
424
+ // Setter functions
425
+ for (var identifierName in setterPropNames) {
426
+ var setterPropName = setterPropNames[identifierName];
427
+ var newValueParameterName = this.getPlaceholder();
428
+
429
+ flatObjectProperties.push(
430
+ Property(
431
+ Literal(setterPropName),
432
+ FunctionExpression(
433
+ [Identifier(newValueParameterName)],
434
+ [
435
+ ExpressionStatement(
436
+ AssignmentExpression(
437
+ "=",
438
+ Identifier(identifierName),
439
+ Identifier(newValueParameterName)
440
+ )
441
+ ),
442
+ ]
443
+ ),
444
+ true,
445
+ "set"
446
+ )
447
+ );
448
+ }
325
449
 
326
- var newParamNodes = object.params.map(() =>
327
- Identifier(this.getPlaceholder())
450
+ if (!this.isDebug) {
451
+ shuffle(flatObjectProperties);
452
+ }
453
+
454
+ var newBody = getBlockBody(object.body);
455
+
456
+ // Remove 'use strict' directive
457
+ if (newBody.length > 0 && newBody[0].directive) {
458
+ newBody.shift();
459
+ }
460
+
461
+ var newFunctionDeclaration = FunctionDeclaration(
462
+ newFnName,
463
+ [ArrayPattern(clone(object.params)), Identifier(flatObjectName)],
464
+ newBody
328
465
  );
329
466
 
330
- // result.pop()
331
- var getOutputMemberExpression = (outputName) =>
332
- MemberExpression(
333
- MemberExpression(Identifier(resultName), Literal(propName), true),
334
- Literal(outputName),
335
- true
336
- );
467
+ newFunctionDeclaration.async = !!object.async;
468
+ newFunctionDeclaration.generator = false;
469
+
470
+ this.flattenedFns.push(newFunctionDeclaration);
337
471
 
338
- // newFn.call([...refs], ...arguments, resultObject)
339
- var callExpression = CallExpression(Identifier(newName), [
340
- ArrayExpression(input.map(Identifier)),
341
- ArrayExpression([...newParamNodes]),
342
- Identifier(resultName),
472
+ var argumentsName = this.getPlaceholder();
473
+
474
+ // newFn.call([...arguments], flatObject)
475
+ var callExpression = CallExpression(Identifier(newFnName), [
476
+ Identifier(argumentsName),
477
+ Identifier(flatObjectName),
343
478
  ]);
344
479
 
345
480
  var newObjectBody: Node[] = [
346
- // var resultObject = {};
481
+ // var flatObject = { get(), set() };
347
482
  VariableDeclaration([
348
- VariableDeclarator(resultName, ObjectExpression([])),
483
+ VariableDeclarator(
484
+ flatObjectName,
485
+ ObjectExpression(flatObjectProperties)
486
+ ),
349
487
  ]),
350
488
 
351
- ExpressionStatement(
352
- newFunctionExpression.async
489
+ ReturnStatement(
490
+ newFunctionDeclaration.async
353
491
  ? AwaitExpression(callExpression)
354
492
  : callExpression
355
493
  ),
356
494
  ];
357
495
 
358
- var outputReversed = [...output].reverse();
359
-
360
- // realVar
361
- outputReversed.forEach((outputName) => {
362
- newObjectBody.push(
363
- ExpressionStatement(
364
- AssignmentExpression(
365
- "=",
366
- Identifier(outputName),
367
- getOutputMemberExpression(newOutputNames[outputName])
368
- )
369
- )
370
- );
371
- });
496
+ object.body = BlockStatement(newObjectBody);
372
497
 
373
- // DECOY STATEMENTS
374
- var decoyKey = this.gen.generate();
375
- var decoyNodes = [
376
- // if (result.random) throw result.prop.random
377
- IfStatement(
378
- MemberExpression(
379
- Identifier(resultName),
380
- Literal(this.gen.generate()),
381
- true
382
- ),
383
- [
384
- ThrowStatement(
385
- NewExpression(Identifier("Error"), [
386
- getOutputMemberExpression(this.gen.generate()),
387
- ])
388
- ),
389
- ]
390
- ),
391
- // if (result.random) return true;
392
- IfStatement(
393
- MemberExpression(
394
- Identifier(resultName),
395
- Literal(this.gen.generate()),
396
- true
397
- ),
398
- [ReturnStatement(Literal(true))]
399
- ),
400
- // if (result.random) return result;
401
- IfStatement(
402
- MemberExpression(
403
- Identifier(resultName),
404
- Literal(this.gen.generate()),
405
- true
406
- ),
407
- [ReturnStatement(Identifier(resultName))]
408
- ),
409
- // if (result.random) return result.random;
410
- IfStatement(
411
- MemberExpression(Identifier(resultName), Literal(decoyKey), true),
412
- [
413
- ReturnStatement(
414
- MemberExpression(Identifier(resultName), Literal(decoyKey), true)
415
- ),
416
- ]
417
- ),
418
- // if(result.random1) return result.random2;
419
- IfStatement(
420
- MemberExpression(
421
- Identifier(resultName),
422
- Literal(this.gen.generate()),
423
- true
424
- ),
425
- [
426
- ReturnStatement(
427
- MemberExpression(
428
- Identifier(resultName),
429
- Literal(this.gen.generate()),
430
- true
431
- )
432
- ),
433
- ]
434
- ),
435
- // if(result.random) return flatFn;
436
- IfStatement(
437
- MemberExpression(
438
- Identifier(resultName),
439
- Literal(this.gen.generate()),
440
- true
441
- ),
442
- [ReturnStatement(Identifier(newName))]
443
- ),
444
- // if(result.random) flatFn = undefined;
445
- IfStatement(
446
- MemberExpression(
447
- Identifier(resultName),
448
- Literal(this.gen.generate()),
449
- true
450
- ),
451
- [
452
- ExpressionStatement(
453
- AssignmentExpression(
454
- "=",
455
- Identifier(newName),
456
- Identifier("undefined")
457
- )
458
- ),
459
- ]
460
- ),
461
- // if(!result) return;
462
- IfStatement(UnaryExpression("!", Identifier(resultName)), [
463
- ReturnStatement(),
464
- ]),
465
- ].filter(() => Math.random() > 0.25);
466
-
467
- // if (result.output) return result.output.returnValue;
468
- // this is the real return statement, it is always added
469
- decoyNodes.push(
470
- IfStatement(
471
- MemberExpression(Identifier(resultName), Literal(propName), true),
472
- [ReturnStatement(getOutputMemberExpression(returnOutputName))]
473
- )
474
- );
498
+ // Preserve function.length property
499
+ var originalFunctionLength = computeFunctionLength(object.params);
475
500
 
476
- shuffle(decoyNodes);
501
+ object.params = [SpreadElement(Identifier(argumentsName))];
477
502
 
478
- newObjectBody.push(...decoyNodes);
503
+ if (originalFunctionLength !== 0) {
504
+ if (!this.functionLengthName) {
505
+ this.functionLengthName = this.getPlaceholder();
479
506
 
480
- object.body = BlockStatement(newObjectBody);
507
+ prepend(
508
+ parents[parents.length - 1] || object,
509
+ FunctionLengthTemplate.single({ name: this.functionLengthName })
510
+ );
511
+ }
481
512
 
482
- object.params = newParamNodes;
513
+ if (object.type === "FunctionDeclaration") {
514
+ var body = parents[0];
515
+ if (Array.isArray(body)) {
516
+ var index = body.indexOf(object);
517
+
518
+ body.splice(
519
+ index + 1,
520
+ 0,
521
+ ExpressionStatement(
522
+ CallExpression(Identifier(this.functionLengthName), [
523
+ Identifier(object.id.name),
524
+ Literal(originalFunctionLength),
525
+ ])
526
+ )
527
+ );
528
+ }
529
+ } else {
530
+ ok(object.type === "FunctionExpression");
531
+ this.replace(
532
+ object,
533
+ CallExpression(Identifier(this.functionLengthName), [
534
+ { ...object },
535
+ Literal(originalFunctionLength),
536
+ ])
537
+ );
538
+ }
539
+ }
483
540
  };
484
541
  }
485
542
  }