js-confuser 1.6.0 → 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 (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +215 -170
  3. package/dist/constants.js +6 -2
  4. package/dist/obfuscator.js +0 -6
  5. package/dist/options.js +4 -4
  6. package/dist/presets.js +6 -7
  7. package/dist/templates/crash.js +2 -2
  8. package/dist/templates/functionLength.js +16 -0
  9. package/dist/transforms/dispatcher.js +4 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +89 -58
  11. package/dist/transforms/flatten.js +224 -147
  12. package/dist/transforms/identifier/movedDeclarations.js +38 -85
  13. package/dist/transforms/identifier/renameVariables.js +94 -41
  14. package/dist/transforms/lock/lock.js +0 -37
  15. package/dist/transforms/minify.js +2 -2
  16. package/dist/transforms/rgf.js +139 -246
  17. package/dist/transforms/stack.js +42 -1
  18. package/dist/transforms/transform.js +1 -1
  19. package/dist/util/gen.js +2 -1
  20. package/dist/util/identifiers.js +37 -3
  21. package/dist/util/insert.js +24 -3
  22. package/docs/ControlFlowFlattening.md +595 -0
  23. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  24. package/{Integrity.md → docs/Integrity.md} +2 -2
  25. package/docs/RGF.md +419 -0
  26. package/package.json +1 -1
  27. package/src/constants.ts +3 -0
  28. package/src/obfuscator.ts +0 -4
  29. package/src/options.ts +9 -86
  30. package/src/presets.ts +6 -7
  31. package/src/templates/crash.ts +10 -10
  32. package/src/templates/functionLength.ts +14 -0
  33. package/src/transforms/dispatcher.ts +5 -1
  34. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +130 -129
  35. package/src/transforms/flatten.ts +357 -290
  36. package/src/transforms/identifier/movedDeclarations.ts +50 -96
  37. package/src/transforms/identifier/renameVariables.ts +120 -56
  38. package/src/transforms/lock/lock.ts +1 -42
  39. package/src/transforms/minify.ts +11 -2
  40. package/src/transforms/rgf.ts +214 -404
  41. package/src/transforms/stack.ts +62 -0
  42. package/src/transforms/transform.ts +6 -2
  43. package/src/util/gen.ts +7 -2
  44. package/src/util/identifiers.ts +43 -2
  45. package/src/util/insert.ts +26 -2
  46. package/test/code/ES6.src.js +24 -0
  47. package/test/transforms/flatten.test.ts +352 -88
  48. package/test/transforms/identifier/movedDeclarations.test.ts +37 -9
  49. package/test/transforms/identifier/renameVariables.test.ts +37 -0
  50. package/test/transforms/lock/lock.test.ts +1 -48
  51. package/test/transforms/minify.test.ts +19 -0
  52. package/test/transforms/rgf.test.ts +262 -353
  53. package/test/transforms/stack.test.ts +52 -0
  54. package/test/util/identifiers.test.ts +113 -1
  55. package/test/util/insert.test.ts +57 -3
  56. package/src/transforms/eval.ts +0 -89
  57. package/src/transforms/identifier/nameRecycling.ts +0 -280
  58. package/test/transforms/eval.test.ts +0 -131
  59. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
@@ -1,7 +1,7 @@
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
6
  Identifier,
7
7
  ReturnStatement,
@@ -9,7 +9,6 @@ import {
9
9
  VariableDeclarator,
10
10
  CallExpression,
11
11
  MemberExpression,
12
- ArrayExpression,
13
12
  ExpressionStatement,
14
13
  AssignmentExpression,
15
14
  Node,
@@ -19,70 +18,93 @@ import {
19
18
  ObjectExpression,
20
19
  Property,
21
20
  Literal,
22
- IfStatement,
23
- ThrowStatement,
24
- NewExpression,
25
21
  AwaitExpression,
22
+ FunctionDeclaration,
23
+ SpreadElement,
26
24
  UnaryExpression,
25
+ RestElement,
27
26
  } from "../util/gen";
28
27
  import { getIdentifierInfo } from "../util/identifiers";
29
- import { getBlockBody, getVarContext, prepend, clone } from "../util/insert";
30
- import { chance, shuffle } from "../util/random";
28
+ import {
29
+ getBlockBody,
30
+ prepend,
31
+ clone,
32
+ getDefiningContext,
33
+ computeFunctionLength,
34
+ } from "../util/insert";
35
+ import { shuffle } from "../util/random";
31
36
  import Transform from "./transform";
37
+ import { FunctionLengthTemplate } from "../templates/functionLength";
32
38
 
33
39
  /**
34
- * 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.
35
43
  *
36
- * 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:
37
45
  *
38
46
  * ```js
39
- * function topLevel(ref1, ref2, refN, param1, param2, paramN){
40
- * 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)
41
73
  * }
42
74
  * ```
43
75
  *
44
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
45
79
  */
46
80
  export default class Flatten extends Transform {
81
+ isDebug = false;
82
+
47
83
  definedNames: Map<Node, Set<string>>;
84
+
85
+ // Array of FunctionDeclaration nodes
48
86
  flattenedFns: Node[];
49
87
  gen: ReturnType<Transform["getGenerator"]>;
50
88
 
89
+ functionLengthName: string;
90
+
51
91
  constructor(o) {
52
92
  super(o, ObfuscateOrder.Flatten);
53
93
 
54
94
  this.definedNames = new Map();
55
95
  this.flattenedFns = [];
56
- this.gen = this.getGenerator();
96
+ this.gen = this.getGenerator("mangled");
97
+
98
+ if (this.isDebug) {
99
+ console.warn("Flatten debug mode");
100
+ }
57
101
  }
58
102
 
59
103
  apply(tree) {
60
- traverse(tree, (o, p) => {
61
- if (
62
- o.type == "Identifier" &&
63
- !reservedIdentifiers.has(o.name) &&
64
- !this.options.globalVariables.has(o.name)
65
- ) {
66
- var info = getIdentifierInfo(o, p);
67
- if (info.spec.isReferenced) {
68
- if (info.spec.isDefined) {
69
- var c = getVarContext(o, p);
70
- if (c) {
71
- if (!this.definedNames.has(c)) {
72
- this.definedNames.set(c, new Set([o.name]));
73
- } else {
74
- this.definedNames.get(c).add(o.name);
75
- }
76
- }
77
- }
78
- }
79
- }
80
- });
81
-
82
104
  super.apply(tree);
83
105
 
84
106
  if (this.flattenedFns.length) {
85
- prepend(tree, VariableDeclaration(this.flattenedFns));
107
+ prepend(tree, ...this.flattenedFns);
86
108
  }
87
109
  }
88
110
 
@@ -91,6 +113,7 @@ export default class Flatten extends Transform {
91
113
  (object.type == "FunctionDeclaration" ||
92
114
  object.type === "FunctionExpression") &&
93
115
  object.body.type == "BlockStatement" &&
116
+ !object.$requiresEval &&
94
117
  !object.generator &&
95
118
  !object.params.find((x) => x.type !== "Identifier")
96
119
  );
@@ -111,7 +134,7 @@ export default class Flatten extends Transform {
111
134
  if (
112
135
  parents[0].type === "Property" &&
113
136
  parents[0].value === object &&
114
- parents[0].kind !== "init"
137
+ (parents[0].kind !== "init" || parents[0].method)
115
138
  ) {
116
139
  return;
117
140
  }
@@ -131,36 +154,37 @@ export default class Flatten extends Transform {
131
154
  parents[0].id?.name;
132
155
 
133
156
  if (parents[0]?.type === "Property" && parents[0]?.key) {
134
- currentFnName =
135
- currentFnName ||
136
- String(parents[0]?.key?.name || parents[0]?.key?.value);
157
+ currentFnName = currentFnName || String(parents[0]?.key?.name);
137
158
  }
138
159
 
139
160
  if (!currentFnName) currentFnName = "unnamed";
140
161
 
141
- var defined = new Set<string>();
142
- var references = new Set<string>();
143
- var modified = new Set<string>();
162
+ var definedMap = new Map<Node, Set<string>>();
144
163
 
145
164
  var illegal = new Set<string>();
146
165
  var isIllegal = false;
147
166
 
148
- var definedAbove = new Set<string>(this.options.globalVariables);
149
-
150
- parents.forEach((x) => {
151
- var set = this.definedNames.get(x);
152
- if (set) {
153
- set.forEach((name) => definedAbove.add(name));
154
- }
155
- });
167
+ var identifierNodes: [
168
+ Node,
169
+ Node[],
170
+ ReturnType<typeof getIdentifierInfo>
171
+ ][] = [];
156
172
 
157
173
  walk(object, parents, (o, p) => {
158
- if (object.id && o === object.id) {
159
- 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";
160
183
  }
161
184
 
162
185
  if (
163
186
  o.type == "Identifier" &&
187
+ o !== object.id &&
164
188
  !this.options.globalVariables.has(o.name) &&
165
189
  !reservedIdentifiers.has(o.name)
166
190
  ) {
@@ -169,45 +193,36 @@ export default class Flatten extends Transform {
169
193
  return;
170
194
  }
171
195
 
172
- if (o.hidden) {
196
+ if (
197
+ info.spec.isExported ||
198
+ o.name.startsWith(noRenameVariablePrefix)
199
+ ) {
173
200
  illegal.add(o.name);
174
- } else if (info.spec.isDefined) {
175
- defined.add(o.name);
176
- } else if (info.spec.isModified) {
177
- modified.add(o.name);
178
- } else {
179
- references.add(o.name);
201
+
202
+ return;
180
203
  }
181
- }
182
204
 
183
- if (o.type == "TryStatement") {
184
- isIllegal = true;
185
- return "EXIT";
186
- }
205
+ if (info.spec.isDefined) {
206
+ var definingContext = getDefiningContext(o, p);
187
207
 
188
- if (o.type == "Identifier") {
189
- if (o.name == "arguments") {
190
- isIllegal = true;
191
- 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;
192
214
  }
193
- }
194
-
195
- if (o.type == "ThisExpression") {
196
- isIllegal = true;
197
- return "EXIT";
198
- }
199
215
 
200
- if (o.type == "Super") {
201
- isIllegal = true;
202
- return "EXIT";
203
- }
216
+ var isDefined = p.find(
217
+ (x) => definedMap.has(x) && definedMap.get(x).has(o.name)
218
+ );
204
219
 
205
- if (o.type == "MetaProperty") {
206
- isIllegal = true;
207
- return "EXIT";
220
+ if (!isDefined) {
221
+ identifierNodes.push([o, p, info]);
222
+ }
208
223
  }
209
224
 
210
- if (o.type == "VariableDeclaration" && o.kind !== "var") {
225
+ if (o.type == "TryStatement") {
211
226
  isIllegal = true;
212
227
  return "EXIT";
213
228
  }
@@ -220,75 +235,221 @@ export default class Flatten extends Transform {
220
235
  return;
221
236
  }
222
237
 
223
- defined.forEach((name) => {
224
- references.delete(name);
225
- modified.delete(name);
226
- });
238
+ var newFnName = this.getPlaceholder() + "_flat_" + currentFnName;
239
+ var flatObjectName = this.getPlaceholder() + "_flat_object";
227
240
 
228
- // 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
+ };
229
248
 
230
- 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);
231
257
 
232
- if (Array.from(input).find((x) => !definedAbove.has(x))) {
233
- return;
234
- }
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;
235
266
 
236
- var output = Array.from(modified);
267
+ ok(!info.spec.isDefined);
237
268
 
238
- var newName = this.getPlaceholder() + "_flat_" + currentFnName;
239
- var resultName = this.getPlaceholder();
240
- var propName = this.gen.generate();
269
+ var type = info.spec.isModified ? "setter" : "getter";
241
270
 
242
- var newOutputNames: { [originalName: string]: string } =
243
- Object.create(null);
244
- output.forEach((name) => {
245
- newOutputNames[name] = this.gen.generate();
246
- });
247
- var returnOutputName = this.gen.generate();
248
-
249
- getBlockBody(object.body).push(ReturnStatement());
250
- walk(object.body, [object, ...parents], (o, p) => {
251
- // Change return statements from
252
- // return (argument)
253
- // to
254
- // return [ [modifiedRefs], ]
255
- if (o.type == "ReturnStatement" && getVarContext(o, p) === object) {
256
- return () => {
257
- var returnObject = ObjectExpression(
258
- output.map((outputName) =>
259
- Property(
260
- Literal(newOutputNames[outputName]),
261
- Identifier(outputName),
262
- true
263
- )
264
- )
265
- );
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
+ }
266
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
+ }
311
+
312
+ // Typeof expression check
267
313
  if (
268
- o.argument &&
269
- !(
270
- o.argument.type == "Identifier" &&
271
- o.argument.name == "undefined"
272
- )
314
+ p[0].type === "UnaryExpression" &&
315
+ p[0].operator === "typeof" &&
316
+ p[0].argument === o
273
317
  ) {
274
- // FIX: The return argument must be executed first so it must use 'unshift'
275
- returnObject.properties.unshift(
276
- 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
+ )
277
351
  );
352
+ break;
278
353
  }
279
354
 
280
- o.argument = AssignmentExpression(
281
- "=",
282
- MemberExpression(
283
- Identifier(resultName),
284
- Identifier(propName),
285
- false
286
- ),
287
- returnObject
288
- );
289
- };
355
+ // calls flatObject.get_identifier_value()
356
+ this.replace(o, getFlatObjectMember(getterPropName));
357
+ break;
290
358
  }
291
- });
359
+ }
360
+
361
+ // Create the getter and setter functions
362
+ var flatObjectProperties: Node[] = [];
363
+
364
+ // Getter functions
365
+ for (var identifierName in getterPropNames) {
366
+ var getterPropName = getterPropNames[identifierName];
367
+
368
+ flatObjectProperties.push(
369
+ Property(
370
+ Literal(getterPropName),
371
+ FunctionExpression(
372
+ [],
373
+ [ReturnStatement(Identifier(identifierName))]
374
+ ),
375
+ true,
376
+ "get"
377
+ )
378
+ );
379
+ }
380
+
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
+ }
401
+
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
+ }
449
+
450
+ if (!this.isDebug) {
451
+ shuffle(flatObjectProperties);
452
+ }
292
453
 
293
454
  var newBody = getBlockBody(object.body);
294
455
 
@@ -297,179 +458,85 @@ export default class Flatten extends Transform {
297
458
  newBody.shift();
298
459
  }
299
460
 
300
- var newFunctionExpression = FunctionExpression(
301
- [
302
- ArrayPattern(input.map((name) => Identifier(name))),
303
- ArrayPattern(clone(object.params)),
304
- Identifier(resultName),
305
- ],
461
+ var newFunctionDeclaration = FunctionDeclaration(
462
+ newFnName,
463
+ [ArrayPattern(clone(object.params)), Identifier(flatObjectName)],
306
464
  newBody
307
465
  );
308
466
 
309
- newFunctionExpression.async = !!object.async;
310
- newFunctionExpression.generator = !!object.generator;
467
+ newFunctionDeclaration.async = !!object.async;
468
+ newFunctionDeclaration.generator = false;
311
469
 
312
- this.flattenedFns.push(
313
- VariableDeclarator(newName, newFunctionExpression)
314
- );
470
+ this.flattenedFns.push(newFunctionDeclaration);
315
471
 
316
- var newParamNames: string[] = object.params.map(() =>
317
- this.getPlaceholder()
318
- );
472
+ var argumentsName = this.getPlaceholder();
319
473
 
320
- // result.pop()
321
- var getOutputMemberExpression = (outputName) =>
322
- MemberExpression(
323
- MemberExpression(Identifier(resultName), Literal(propName), true),
324
- Literal(outputName),
325
- true
326
- );
327
-
328
- // newFn.call([...refs], ...arguments, resultObject)
329
- var callExpression = CallExpression(Identifier(newName), [
330
- ArrayExpression(input.map((name) => Identifier(name))),
331
- ArrayExpression(newParamNames.map((name) => Identifier(name))),
332
- Identifier(resultName),
474
+ // newFn.call([...arguments], flatObject)
475
+ var callExpression = CallExpression(Identifier(newFnName), [
476
+ Identifier(argumentsName),
477
+ Identifier(flatObjectName),
333
478
  ]);
334
479
 
335
480
  var newObjectBody: Node[] = [
336
- // var resultObject = {};
481
+ // var flatObject = { get(), set() };
337
482
  VariableDeclaration([
338
- VariableDeclarator(resultName, ObjectExpression([])),
483
+ VariableDeclarator(
484
+ flatObjectName,
485
+ ObjectExpression(flatObjectProperties)
486
+ ),
339
487
  ]),
340
488
 
341
- ExpressionStatement(
342
- newFunctionExpression.async
489
+ ReturnStatement(
490
+ newFunctionDeclaration.async
343
491
  ? AwaitExpression(callExpression)
344
492
  : callExpression
345
493
  ),
346
494
  ];
347
495
 
348
- var outputReversed = [...output].reverse();
349
-
350
- // realVar
351
- outputReversed.forEach((outputName) => {
352
- newObjectBody.push(
353
- ExpressionStatement(
354
- AssignmentExpression(
355
- "=",
356
- Identifier(outputName),
357
- getOutputMemberExpression(newOutputNames[outputName])
358
- )
359
- )
360
- );
361
- });
496
+ object.body = BlockStatement(newObjectBody);
362
497
 
363
- // DECOY STATEMENTS
364
- var decoyKey = this.gen.generate();
365
- var decoyNodes = [
366
- // if (result.random) throw result.prop.random
367
- IfStatement(
368
- MemberExpression(
369
- Identifier(resultName),
370
- Literal(this.gen.generate()),
371
- true
372
- ),
373
- [
374
- ThrowStatement(
375
- NewExpression(Identifier("Error"), [
376
- getOutputMemberExpression(this.gen.generate()),
377
- ])
378
- ),
379
- ]
380
- ),
381
- // if (result.random) return true;
382
- IfStatement(
383
- MemberExpression(
384
- Identifier(resultName),
385
- Literal(this.gen.generate()),
386
- true
387
- ),
388
- [ReturnStatement(Literal(true))]
389
- ),
390
- // if (result.random) return result;
391
- IfStatement(
392
- MemberExpression(
393
- Identifier(resultName),
394
- Literal(this.gen.generate()),
395
- true
396
- ),
397
- [ReturnStatement(Identifier(resultName))]
398
- ),
399
- // if (result.random) return result.random;
400
- IfStatement(
401
- MemberExpression(Identifier(resultName), Literal(decoyKey), true),
402
- [
403
- ReturnStatement(
404
- MemberExpression(Identifier(resultName), Literal(decoyKey), true)
405
- ),
406
- ]
407
- ),
408
- // if(result.random1) return result.random2;
409
- IfStatement(
410
- MemberExpression(
411
- Identifier(resultName),
412
- Literal(this.gen.generate()),
413
- true
414
- ),
415
- [
416
- ReturnStatement(
417
- MemberExpression(
418
- Identifier(resultName),
419
- Literal(this.gen.generate()),
420
- true
421
- )
422
- ),
423
- ]
424
- ),
425
- // if(result.random) return flatFn;
426
- IfStatement(
427
- MemberExpression(
428
- Identifier(resultName),
429
- Literal(this.gen.generate()),
430
- true
431
- ),
432
- [ReturnStatement(Identifier(newName))]
433
- ),
434
- // if(result.random) flatFn = undefined;
435
- IfStatement(
436
- MemberExpression(
437
- Identifier(resultName),
438
- Literal(this.gen.generate()),
439
- true
440
- ),
441
- [
442
- ExpressionStatement(
443
- AssignmentExpression(
444
- "=",
445
- Identifier(newName),
446
- Identifier("undefined")
447
- )
448
- ),
449
- ]
450
- ),
451
- // if(!result) return;
452
- IfStatement(UnaryExpression("!", Identifier(resultName)), [
453
- ReturnStatement(),
454
- ]),
455
- ].filter(() => chance(25));
456
-
457
- // if (result.output) return result.output.returnValue;
458
- // this is the real return statement, it is always added
459
- decoyNodes.push(
460
- IfStatement(
461
- MemberExpression(Identifier(resultName), Literal(propName), true),
462
- [ReturnStatement(getOutputMemberExpression(returnOutputName))]
463
- )
464
- );
498
+ // Preserve function.length property
499
+ var originalFunctionLength = computeFunctionLength(object.params);
465
500
 
466
- shuffle(decoyNodes);
501
+ object.params = [SpreadElement(Identifier(argumentsName))];
467
502
 
468
- newObjectBody.push(...decoyNodes);
503
+ if (originalFunctionLength !== 0) {
504
+ if (!this.functionLengthName) {
505
+ this.functionLengthName = this.getPlaceholder();
469
506
 
470
- object.body = BlockStatement(newObjectBody);
507
+ prepend(
508
+ parents[parents.length - 1] || object,
509
+ FunctionLengthTemplate.single({ name: this.functionLengthName })
510
+ );
511
+ }
471
512
 
472
- object.params = newParamNames.map((name) => Identifier(name));
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
+ }
473
540
  };
474
541
  }
475
542
  }