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
@@ -25,19 +25,23 @@ var _random = require("../../util/random");
25
25
 
26
26
  var _transform = _interopRequireDefault(require("../transform"));
27
27
 
28
- var _controlFlowObfuscation = _interopRequireDefault(require("./controlFlowObfuscation"));
29
-
30
28
  var _expressionObfuscation = _interopRequireDefault(require("./expressionObfuscation"));
31
29
 
32
- var _switchCaseObfuscation = _interopRequireDefault(require("./switchCaseObfuscation"));
33
-
34
30
  var _stringConcealing = require("../string/stringConcealing");
35
31
 
32
+ var _constants = require("../../constants");
33
+
34
+ var _compare = require("../../util/compare");
35
+
36
36
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
37
37
 
38
38
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
39
39
 
40
- var flattenStructures = new Set(["IfStatement", "ForStatement", "WhileStatement", "DoWhileStatement"]);
40
+ const flattenStructures = new Set(["IfStatement", "ForStatement", "WhileStatement", "DoWhileStatement"]);
41
+ /**
42
+ * A chunk represents a small segment of code
43
+ */
44
+
41
45
  /**
42
46
  * Breaks functions into DAGs (Directed Acyclic Graphs)
43
47
  *
@@ -51,178 +55,154 @@ var flattenStructures = new Set(["IfStatement", "ForStatement", "WhileStatement"
51
55
  * - 2. At the end of each case, the state variable is updated to the next block of code.
52
56
  * - 3. The while loop continues until the the state variable is the end state.
53
57
  */
54
-
55
58
  class ControlFlowFlattening extends _transform.default {
59
+ // in Debug mode, the output is much easier to read
60
+ // Flatten if-statements, for-loops, etc
61
+ // var control = { str1, num1 }
62
+ // 50 => state + X
63
+ // true => state == X
64
+ // console => (state == X ? console : _)
65
+ // Tries to outline entire chunks
66
+ // Tries to outline expressions found in chunks
67
+ // case s != 49 && s - 10:
68
+ // case 100: case 490: case 510: ...
69
+ // add fakes chunks of code
70
+ // predicate ? REAL : FAKE
71
+ // s=NEXT_STATE,flag=true,break
72
+ // Limit amount of mangling
73
+ // Amount of blocks changed by Control Flow Flattening
56
74
  constructor(o) {
57
75
  super(o, _order.ObfuscateOrder.ControlFlowFlattening);
58
76
 
59
77
  _defineProperty(this, "isDebug", false);
60
78
 
79
+ _defineProperty(this, "flattenControlStructures", true);
80
+
81
+ _defineProperty(this, "addToControlObject", true);
82
+
83
+ _defineProperty(this, "mangleNumberLiterals", true);
84
+
85
+ _defineProperty(this, "mangleBooleanLiterals", true);
86
+
87
+ _defineProperty(this, "mangleIdentifiers", true);
88
+
89
+ _defineProperty(this, "outlineStatements", true);
90
+
91
+ _defineProperty(this, "outlineExpressions", true);
92
+
93
+ _defineProperty(this, "addComplexTest", true);
94
+
95
+ _defineProperty(this, "addFakeTest", true);
96
+
97
+ _defineProperty(this, "addDeadCode", true);
98
+
99
+ _defineProperty(this, "addOpaquePredicates", true);
100
+
101
+ _defineProperty(this, "addFlaggedLabels", true);
102
+
103
+ _defineProperty(this, "mangledExpressionsMade", 0);
104
+
105
+ _defineProperty(this, "cffCount", 0);
106
+
61
107
  if (!this.isDebug) {
62
108
  this.before.push(new _expressionObfuscation.default(o));
63
- this.after.push(new _controlFlowObfuscation.default(o));
64
- this.after.push(new _switchCaseObfuscation.default(o));
65
109
  } else {
66
110
  console.warn("Debug mode enabled");
67
- } // this.after.push(new ChoiceFlowObfuscation(o));
68
-
111
+ }
69
112
  }
70
113
 
71
114
  match(object, parents) {
72
- return (0, _traverse.isBlock)(object) && (!parents[1] || !flattenStructures.has(parents[1].type)) && (!parents[2] || !flattenStructures.has(parents[2].type));
115
+ return (0, _traverse.isBlock)(object) && (!parents[0] || !flattenStructures.has(parents[0].type)) && (!parents[1] || !flattenStructures.has(parents[1].type));
73
116
  }
74
117
 
75
118
  transform(object, parents) {
76
119
  var _this = this;
77
120
 
78
- return () => {
79
- if (object.body.length < 3) {
80
- return;
81
- }
121
+ // Must be at least 3 statements or more
122
+ if (object.body.length < 3) {
123
+ return;
124
+ } // No 'let'/'const' allowed (These won't work in Switch cases!)
82
125
 
83
- if ((0, _identifiers.containsLexicallyBoundVariables)(object, parents)) {
84
- return;
85
- }
86
126
 
87
- if (!(0, _probability.ComputeProbabilityMap)(this.options.controlFlowFlattening, x => x)) {
88
- return;
89
- }
127
+ if ((0, _identifiers.containsLexicallyBoundVariables)(object, parents)) {
128
+ return;
129
+ } // Check user's threshold setting
90
130
 
91
- var body = (0, _insert.getBlockBody)(object.body);
92
131
 
93
- if (!body.length) {
94
- return;
95
- } // First step is to reorder the body
96
- // Fix 1. Bring hoisted functions up to be declared first
132
+ if (!(0, _probability.ComputeProbabilityMap)(this.options.controlFlowFlattening, x => x)) {
133
+ return;
134
+ }
97
135
 
136
+ var objectBody = (0, _insert.getBlockBody)(object.body);
98
137
 
99
- var functionDeclarations = new Set();
100
- var fnNames = new Set();
101
- var illegalFnNames = new Set();
102
- /**
103
- * The variable names
104
- *
105
- * index -> var name
106
- */
138
+ if (!objectBody.length) {
139
+ return;
140
+ } // Purely for naming purposes
107
141
 
108
- var stateVars = Array(this.isDebug ? 1 : (0, _random.getRandomInteger)(2, 5)).fill(0).map(() => this.getPlaceholder());
109
- body.forEach((stmt, i) => {
110
- if (stmt.type == "FunctionDeclaration") {
111
- functionDeclarations.add(stmt);
112
- var name = stmt.id && stmt.id.name;
113
- fnNames.add(name);
114
142
 
115
- if (stmt.body.type !== "BlockStatement") {
116
- illegalFnNames.add(name);
117
- } else {
118
- (0, _traverse.walk)(stmt, [body, object, ...parents], (o, p) => {
119
- if (o.type == "ThisExpression" || o.type == "SuperExpression" || o.type == "Identifier" && (o.name == "arguments" || o.name == "eval")) {
120
- illegalFnNames.add(name);
121
- return "EXIT";
122
- }
123
- });
124
- }
125
- }
126
- });
127
- (0, _traverse.walk)(object, parents, (o, p) => {
128
- if (o.type == "Identifier" && fnNames.has(o.name)) {
129
- var info = (0, _identifiers.getIdentifierInfo)(o, p);
143
+ var cffIndex = this.cffCount++; // The controlVar is an object containing:
144
+ // - Strings found in chunks
145
+ // - Numbers found in chunks
146
+ // - Helper functions to adjust the state
147
+ // - Outlined expressions changed into functions
130
148
 
131
- if (!info.spec.isReferenced) {
132
- return;
133
- }
149
+ var controlVar = this.getPlaceholder() + "_c".concat(cffIndex, "_CONTROL");
150
+ var controlProperties = [];
151
+ var controlConstantMap = new Map();
152
+ var controlGen = this.getGenerator("mangled");
153
+ var controlTestKey = controlGen.generate(); // This 'controlVar' can be accessed by child-nodes
134
154
 
135
- if (info.spec.isModified) {
136
- fnNames.delete(o.name);
137
- } else if (info.spec.isDefined) {
138
- if (info.isFunctionDeclaration) {
139
- if (!functionDeclarations.has(p[0])) {
140
- fnNames.delete(o.name);
141
- }
142
- } else {
143
- fnNames.delete(o.name);
144
- }
145
- }
146
-
147
- if (!info.spec.isDefined) {
148
- var b = (0, _traverse.getBlock)(o, p);
149
-
150
- if (b !== object || !p[0] || p[0].type !== "CallExpression") {
151
- illegalFnNames.add(o.name);
152
- } else {
153
- var isExtractable = false;
154
-
155
- if (p[1]) {
156
- if (p[1].type == "ExpressionStatement" && p[1].expression == p[0] && p[2] == object.body) {
157
- isExtractable = true;
158
- p[1].$callExpression = "ExpressionStatement";
159
- p[1].$fnName = o.name;
160
- } else if (p[1].type == "VariableDeclarator" && p[1].init == p[0] && p[2].length === 1 && p[4] == object.body) {
161
- isExtractable = true;
162
- p[3].$callExpression = "VariableDeclarator";
163
- p[3].$fnName = o.name;
164
- } else if (p[1].type == "AssignmentExpression" && p[1].operator == "=" && p[1].right === p[0] && p[2] && p[2].type == "ExpressionStatement" && p[3] == object.body) {
165
- isExtractable = true;
166
- p[2].$callExpression = "AssignmentExpression";
167
- p[2].$fnName = o.name;
168
- }
169
- }
155
+ object.$controlVar = controlVar;
156
+ object.$controlConstantMap = controlConstantMap;
157
+ object.$controlProperties = controlProperties;
158
+ object.$controlGen = controlGen;
159
+ return () => {
160
+ (0, _assert.ok)(Array.isArray(objectBody)); // The state variable names (and quantity)
170
161
 
171
- if (!isExtractable) {
172
- illegalFnNames.add(o.name);
173
- }
174
- }
175
- }
176
- }
177
- }); // redefined function,
162
+ var stateVars = Array(this.isDebug ? 1 : (0, _random.getRandomInteger)(2, 5)).fill(0).map((_, i) => this.getPlaceholder() + "_c".concat(cffIndex, "_S").concat(i)); // How often should chunks be split up?
163
+ // Percentage between 10% and 90% based on block size
178
164
 
179
- if (functionDeclarations.size !== fnNames.size) {
180
- return;
181
- }
165
+ var splitPercent = Math.max(10, 90 - objectBody.length * 5); // Find functions and import declarations
182
166
 
183
- illegalFnNames.forEach(illegal => {
184
- fnNames.delete(illegal);
185
- });
186
167
  var importDeclarations = [];
187
-
188
- for (var stmt of body) {
189
- if (stmt.type === "ImportDeclaration") {
190
- importDeclarations.push(stmt);
168
+ var functionDeclarationNames = new Set();
169
+ var functionDeclarationValues = new Map(); // Find all parent control-nodes
170
+
171
+ const allControlNodes = [object];
172
+ parents.filter(x => x.$controlVar).forEach(node => allControlNodes.push(node));
173
+
174
+ const addControlMapConstant = literalValue => {
175
+ var _selectedControlConst;
176
+
177
+ // Choose a random control node to add to
178
+ var controlNode = (0, _random.choice)(allControlNodes);
179
+ var selectedControlVar = controlNode.$controlVar;
180
+ var selectedControlConstantMap = controlNode.$controlConstantMap;
181
+ var selectedControlProperties = controlNode.$controlProperties;
182
+ var key = (_selectedControlConst = selectedControlConstantMap.get(literalValue)) === null || _selectedControlConst === void 0 ? void 0 : _selectedControlConst.key; // Not found, create
183
+
184
+ if (!key) {
185
+ key = controlNode.$controlGen.generate();
186
+ selectedControlConstantMap.set(literalValue, {
187
+ key: key
188
+ });
189
+ selectedControlProperties.push((0, _gen.Property)((0, _gen.Literal)(key), (0, _gen.Literal)(literalValue), false));
191
190
  }
192
- }
193
191
 
194
- var fraction = 0.9;
192
+ return getControlMember(key, selectedControlVar);
193
+ }; // Helper function to easily make control object accessors
195
194
 
196
- if (body.length > 20) {
197
- fraction /= Math.max(1.2, body.length - 18);
198
- }
199
195
 
200
- fraction = Math.min(0.1, fraction);
196
+ const getControlMember = function (key) {
197
+ let objectName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : controlVar;
198
+ return (0, _gen.MemberExpression)((0, _gen.Identifier)(objectName), (0, _gen.Literal)(key), true);
199
+ }; // This function recursively calls itself to flatten and split up code into 'chunks'
201
200
 
202
- if (isNaN(fraction) || !isFinite(fraction)) {
203
- fraction = 0.5;
204
- }
205
-
206
- var resultVar = this.getPlaceholder();
207
- var argVar = this.getPlaceholder();
208
- var testVar = this.getPlaceholder();
209
- var stringBankVar = this.getPlaceholder();
210
- var stringBank = Object.create(null);
211
- var stringBankByLabels = Object.create(null);
212
- let stringBankGen = this.getGenerator();
213
- var needsTestVar = false;
214
- var needsResultAndArgVar = false;
215
- var needsStringBankVar = false;
216
- var fnToLabel = Object.create(null);
217
- fnNames.forEach(fnName => {
218
- fnToLabel[fnName] = this.getPlaceholder();
219
- });
220
201
 
221
- const flattenBody = function (body) {
222
- let startingLabel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.getPlaceholder();
202
+ const flattenBody = (body, startingLabel) => {
223
203
  var chunks = [];
224
204
  var currentBody = [];
225
- var currentLabel = startingLabel;
205
+ var currentLabel = startingLabel; // This function ends the current chunk being created ('currentBody')
226
206
 
227
207
  const finishCurrentChunk = function (pointingLabel, newLabel) {
228
208
  let addGotoStatement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
@@ -245,94 +225,94 @@ class ControlFlowFlattening extends _transform.default {
245
225
  chunks.push({
246
226
  label: currentLabel,
247
227
  body: [...currentBody]
248
- });
249
- (0, _traverse.walk)(currentBody, [], (o, p) => {
250
- if (o.type == "Literal" && typeof o.value == "string" && !(0, _stringConcealing.isModuleSource)(o, p) && !o.regex && Math.random() / (Object.keys(stringBank).length / 2 + 1) > 0.5) {
251
- needsStringBankVar = true;
228
+ }); // Random chance of this chunk being flagged (First label cannot be flagged)
252
229
 
253
- if (!stringBankByLabels[currentLabel]) {
254
- stringBankByLabels[currentLabel] = new Set();
255
- }
230
+ if (!_this.isDebug && _this.addFlaggedLabels && currentLabel !== startLabel && (0, _random.chance)(25)) {
231
+ flaggedLabels[currentLabel] = {
232
+ flagKey: controlGen.generate(),
233
+ flagValue: (0, _random.choice)([true, false])
234
+ };
235
+ }
256
236
 
257
- stringBankByLabels[currentLabel].add(o.value);
237
+ (0, _traverse.walk)(currentBody, [], (o, p) => {
238
+ if (o.type === "Literal" && !_this.isDebug) {
239
+ // Add strings to the control object
240
+ if (_this.addToControlObject && typeof o.value === "string" && o.value.length >= 3 && o.value.length <= 100 && !(0, _stringConcealing.isModuleSource)(o, p) && !(0, _compare.isDirective)(o, p) && !o.regex && (0, _random.chance)(50 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
241
+ return () => {
242
+ _this.replaceIdentifierOrLiteral(o, addControlMapConstant(o.value), p);
243
+ };
244
+ } // Add numbers to the control object
258
245
 
259
- if (typeof stringBank[o.value] === "undefined") {
260
- stringBank[o.value] = stringBankGen.generate();
261
- }
262
246
 
263
- return () => {
264
- _this.replaceIdentifierOrLiteral(o, (0, _gen.MemberExpression)((0, _gen.Identifier)(stringBankVar), (0, _gen.Literal)(stringBank[o.value]), true), p);
265
- };
247
+ if (_this.addToControlObject && typeof o.value === "number" && Math.floor(o.value) === o.value && Math.abs(o.value) < 100000 && (0, _random.chance)(50 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
248
+ return () => {
249
+ _this.replaceIdentifierOrLiteral(o, addControlMapConstant(o.value), p);
250
+ };
251
+ }
266
252
  }
267
253
  });
268
254
  currentLabel = newLabel;
269
255
  currentBody = [];
270
256
  };
271
257
 
272
- body.forEach((stmt, i) => {
273
- if (functionDeclarations.has(stmt) || stmt.type === "ImportDeclaration") {
274
- return;
258
+ if (body !== objectBody) {
259
+ // This code is nested. Move function declarations up
260
+ var newBody = [];
261
+
262
+ for (var stmt of body) {
263
+ if (stmt.type === "FunctionDeclaration") {
264
+ newBody.unshift(stmt);
265
+ } else {
266
+ newBody.push(stmt);
267
+ }
275
268
  }
276
269
 
277
- if (stmt.$exit) {
278
- currentBody.push(stmt);
279
- currentBody.push((0, _gen.BreakStatement)(switchLabel));
280
- finishCurrentChunk(null, null, false);
270
+ body = newBody;
271
+ }
272
+
273
+ body.forEach((stmt, i) => {
274
+ if (stmt.type === "ImportDeclaration") {
275
+ // The 'importDeclarations' hold statements that are required to be left untouched at the top of the block
276
+ importDeclarations.push(stmt);
281
277
  return;
282
- }
278
+ } else if (stmt.type === "FunctionDeclaration") {
279
+ var functionName = stmt.id.name;
280
+ stmt.type = "FunctionExpression";
281
+ stmt.id = null;
282
+ functionDeclarationNames.add(functionName);
283
+
284
+ if (objectBody === body) {
285
+ functionDeclarationValues.set(functionName, stmt);
286
+ return;
287
+ } else {
288
+ currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(functionName), stmt)));
289
+ }
283
290
 
284
- if (stmt.$callExpression && fnToLabel[stmt.$fnName]) {
285
- var afterPath = _this.getPlaceholder();
286
-
287
- var args = [];
288
-
289
- switch (stmt.$callExpression) {
290
- // var a = fn();
291
- case "VariableDeclarator":
292
- args = stmt.declarations[0].init.arguments;
293
- stmt.declarations[0].init = (0, _gen.Identifier)(resultVar);
294
- break;
295
- // fn();
296
-
297
- case "ExpressionStatement":
298
- args = stmt.expression.arguments;
299
- stmt.expression = (0, _gen.Identifier)("undefined");
300
- break;
301
- // a = fn();
302
-
303
- case "AssignmentExpression":
304
- args = stmt.expression.right.arguments;
305
- stmt.expression.right = (0, _gen.Identifier)(resultVar);
306
- break;
291
+ return;
292
+ } else if (stmt.directive) {
293
+ if (objectBody === body) {
294
+ importDeclarations.push(stmt);
295
+ } else {
296
+ this.error(new Error("Unimplemented directive support."));
307
297
  }
308
298
 
309
- needsResultAndArgVar = true;
310
- currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(argVar), (0, _gen.ArrayExpression)([{
311
- type: "StateIdentifier",
312
- label: afterPath
313
- }, (0, _gen.ArrayExpression)(args)]))));
314
- finishCurrentChunk(fnToLabel[stmt.$fnName], afterPath);
299
+ return;
315
300
  }
316
301
 
317
302
  if (stmt.type == "GotoStatement" && i !== body.length - 1) {
318
303
  finishCurrentChunk(stmt.label);
319
304
  return;
320
- }
305
+ } // The Preparation transform adds labels to every Control-Flow node
321
306
 
322
- if (stmt.type == "LabeledStatement") {
307
+
308
+ if (this.flattenControlStructures && stmt.type == "LabeledStatement") {
323
309
  var lbl = stmt.label.name;
324
310
  var control = stmt.body;
325
311
  var isSwitchStatement = control.type === "SwitchStatement";
326
312
 
327
313
  if (isSwitchStatement || (control.type == "ForStatement" || control.type == "WhileStatement" || control.type == "DoWhileStatement") && control.body.type == "BlockStatement") {
328
314
  if (isSwitchStatement) {
329
- if (control.cases.length == 0 || // at least 1 case
330
- control.cases.find(x => !x.test || // cant be default case
331
- !x.consequent.length || // must have body
332
- x.consequent.findIndex(node => node.type == "BreakStatement") !== x.consequent.length - 1 || // break statement must be at the end
333
- x.consequent[x.consequent.length - 1].type !== // must end with break statement
334
- "BreakStatement" || !x.consequent[x.consequent.length - 1].label || // must be labeled and correct
335
- x.consequent[x.consequent.length - 1].label.name != lbl)) {
315
+ if (control.cases.length == 0) {
336
316
  currentBody.push(stmt);
337
317
  return;
338
318
  }
@@ -340,20 +320,20 @@ class ControlFlowFlattening extends _transform.default {
340
320
 
341
321
  var isLoop = !isSwitchStatement;
342
322
  var supportContinueStatement = isLoop;
323
+ var testPath = this.getPlaceholder();
324
+ var updatePath = this.getPlaceholder();
325
+ var bodyPath = this.getPlaceholder();
326
+ var afterPath = this.getPlaceholder();
327
+ var possible = true;
328
+ var toReplace = []; // Find all break; and continue; statements and change them into 'GotoStatement's
343
329
 
344
- var testPath = _this.getPlaceholder();
345
-
346
- var updatePath = _this.getPlaceholder();
347
-
348
- var bodyPath = _this.getPlaceholder();
349
-
350
- var afterPath = _this.getPlaceholder();
330
+ (0, _traverse.walk)(control.body || control.cases, [], (o, p) => {
331
+ if (o.type === "BreakStatement" || o.type === "ContinueStatement") {
332
+ var allowedLabels = new Set(p.filter(x => x.type === "LabeledStatement" && x.body.type === "SwitchStatement").map(x => x.label.name));
333
+ var isUnsupportedContinue = !supportContinueStatement && o.type === "ContinueStatement";
334
+ var isInvalidLabel = !o.label || o.label.name !== lbl && !allowedLabels.has(o.label.name); // This seems like the best solution:
351
335
 
352
- var possible = true;
353
- var toReplace = [];
354
- (0, _traverse.walk)(control.body, [], (o, p) => {
355
- if (o.type == "BreakStatement" || supportContinueStatement && o.type == "ContinueStatement") {
356
- if (!o.label || o.label.name !== lbl) {
336
+ if (isUnsupportedContinue || isInvalidLabel) {
357
337
  possible = false;
358
338
  return "EXIT";
359
339
  }
@@ -374,33 +354,57 @@ class ControlFlowFlattening extends _transform.default {
374
354
  return;
375
355
  }
376
356
 
377
- toReplace.forEach(v => _this.replace(v[0], v[1]));
357
+ toReplace.forEach(v => this.replace(v[0], v[1]));
378
358
 
379
359
  if (isSwitchStatement) {
380
- var switchVarName = _this.getPlaceholder();
360
+ var switchDiscriminantName = this.getPlaceholder() + "_switchD"; // Stores the value of the discriminant
381
361
 
382
- currentBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(switchVarName, control.discriminant)));
362
+ var switchTestName = this.getPlaceholder() + "_switchT"; // Set to true when a Switch case is matched
383
363
 
384
- var afterPath = _this.getPlaceholder();
364
+ currentBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(switchDiscriminantName, control.discriminant)));
365
+ currentBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(switchTestName, (0, _gen.Literal)(false)))); // case labels are:
366
+ // `${caseLabelPrefix}_test_${index}`
367
+ // `${caseLabelPrefix}_entry_${index}`
385
368
 
386
- finishCurrentChunk();
369
+ var caseLabelPrefix = this.getPlaceholder();
370
+ var defaultCaseIndex = control.cases.findIndex(x => x.test === null);
387
371
  control.cases.forEach((switchCase, i) => {
388
- var entryPath = _this.getPlaceholder();
389
-
390
- currentBody.push((0, _gen.IfStatement)((0, _gen.BinaryExpression)("===", (0, _gen.Identifier)(switchVarName), switchCase.test), [{
372
+ var testPath = caseLabelPrefix + "_test_" + i;
373
+ var entryPath = caseLabelPrefix + "_entry_" + i;
374
+ var nextEntryPath = i === control.cases.length - 1 // Last path goes to afterPath
375
+ ? afterPath // Else go to next entry path (fall-through behavior)
376
+ : caseLabelPrefix + "_entry_" + (i + 1);
377
+ var nextTestPath = i === control.cases.length - 1 ? afterPath : caseLabelPrefix + "_test_" + (i + 1);
378
+ finishCurrentChunk(testPath, testPath, i == 0);
379
+
380
+ if (switchCase.test) {
381
+ // Check the case condition and goto statement
382
+ currentBody.push((0, _gen.IfStatement)((0, _gen.BinaryExpression)("===", (0, _gen.Identifier)(switchDiscriminantName), switchCase.test), [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(switchTestName), (0, _gen.Literal)(true))), {
383
+ type: "GotoStatement",
384
+ label: entryPath
385
+ }]));
386
+ } else {// Default case: No test needed.
387
+ } // If default case, on last test, if no case was matched, goto default case
388
+
389
+
390
+ if (i === control.cases.length - 1 && defaultCaseIndex !== -1) {
391
+ currentBody.push((0, _gen.IfStatement)((0, _gen.UnaryExpression)("!", (0, _gen.Identifier)(switchTestName)), [{
392
+ type: "GotoStatement",
393
+ label: caseLabelPrefix + "_entry_" + defaultCaseIndex
394
+ }]));
395
+ } // Jump to next test
396
+
397
+
398
+ currentBody.push({
391
399
  type: "GotoStatement",
392
- label: entryPath
393
- }]));
394
- chunks.push(...flattenBody([...switchCase.consequent.slice(0, switchCase.consequent.length - 1), {
400
+ label: nextTestPath
401
+ });
402
+ chunks.push(...flattenBody([...switchCase.consequent, {
395
403
  type: "GotoStatement",
396
- label: afterPath
404
+ label: nextEntryPath
397
405
  }], entryPath));
398
-
399
- if (i === control.cases.length - 1) {} else {
400
- finishCurrentChunk();
401
- }
402
406
  });
403
- finishCurrentChunk(afterPath, afterPath);
407
+ finishCurrentChunk(afterPath, afterPath, false);
404
408
  return;
405
409
  } else if (isLoop) {
406
410
  var isPostTest = control.type == "DoWhileStatement"; // add initializing section to current chunk
@@ -415,18 +419,15 @@ class ControlFlowFlattening extends _transform.default {
415
419
 
416
420
 
417
421
  finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
418
- currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(testVar), control.test || (0, _gen.Literal)(true))));
419
- needsTestVar = true;
422
+ currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlTestKey), control.test || (0, _gen.Literal)(true))));
420
423
  finishCurrentChunk();
421
- currentBody.push((0, _gen.IfStatement)((0, _gen.Identifier)(testVar), [{
424
+ currentBody.push((0, _gen.IfStatement)(getControlMember(controlTestKey), [{
422
425
  type: "GotoStatement",
423
426
  label: bodyPath
424
427
  }])); // create new label called `bodyPath` and have test body point to afterPath (goto afterPath)
425
428
 
426
429
  finishCurrentChunk(afterPath, bodyPath);
427
-
428
- var innerBothPath = _this.getPlaceholder();
429
-
430
+ var innerBothPath = this.getPlaceholder();
430
431
  chunks.push(...flattenBody([...control.body.body, {
431
432
  type: "GotoStatement",
432
433
  label: updatePath
@@ -443,21 +444,16 @@ class ControlFlowFlattening extends _transform.default {
443
444
  }
444
445
  }
445
446
 
446
- if (stmt.type == "IfStatement" && stmt.consequent.type == "BlockStatement" && (!stmt.alternate || stmt.alternate.type == "BlockStatement")) {
447
+ if (this.flattenControlStructures && stmt.type == "IfStatement" && stmt.consequent.type == "BlockStatement" && (!stmt.alternate || stmt.alternate.type == "BlockStatement")) {
447
448
  finishCurrentChunk();
448
- currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(testVar), stmt.test)));
449
- needsTestVar = true;
449
+ currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlTestKey), stmt.test)));
450
450
  finishCurrentChunk();
451
451
  var hasAlternate = !!stmt.alternate;
452
452
  (0, _assert.ok)(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
453
-
454
- var yesPath = _this.getPlaceholder();
455
-
456
- var noPath = _this.getPlaceholder();
457
-
458
- var afterPath = _this.getPlaceholder();
459
-
460
- currentBody.push((0, _gen.IfStatement)((0, _gen.Identifier)(testVar), [{
453
+ var yesPath = this.getPlaceholder();
454
+ var noPath = this.getPlaceholder();
455
+ var afterPath = this.getPlaceholder();
456
+ currentBody.push((0, _gen.IfStatement)(getControlMember(controlTestKey), [{
461
457
  type: "GotoStatement",
462
458
  label: yesPath
463
459
  }]));
@@ -479,7 +475,7 @@ class ControlFlowFlattening extends _transform.default {
479
475
  return;
480
476
  }
481
477
 
482
- if (!currentBody.length || Math.random() < fraction) {
478
+ if (!currentBody.length || !(0, _random.chance)(splitPercent)) {
483
479
  currentBody.push(stmt);
484
480
  } else {
485
481
  // Start new chunk
@@ -491,43 +487,35 @@ class ControlFlowFlattening extends _transform.default {
491
487
  chunks[chunks.length - 1].body.pop();
492
488
  return chunks;
493
489
  };
494
-
495
- var chunks = [];
496
490
  /**
497
- * label: switch(a+b+c){...break label...}
491
+ * Executable code segments are broken down into `chunks` typically 1-3 statements each
492
+ *
493
+ * Chunked Code has a special `GotoStatement` node that get processed later on
494
+ * This allows more complex control structures like `IfStatement`s and `ForStatement`s to be converted into basic
495
+ * conditional jumps and flattened in the switch body
496
+ *
497
+ * IfStatement would be converted like this:
498
+ *
499
+ * MAIN:
500
+ * if ( TEST ) {
501
+ * GOTO consequent_label;
502
+ * } else? {
503
+ * GOTO alternate_label;
504
+ * }
505
+ * GOTO NEXT_CHUNK;
498
506
  */
499
507
 
500
- var switchLabel = this.getPlaceholder();
501
- functionDeclarations.forEach(node => {
502
- if (node.id && fnNames.has(node.id.name)) {
503
- var exitStateName = this.getPlaceholder();
504
- var argumentsName = this.getPlaceholder();
505
- needsResultAndArgVar = true;
506
- node.body.body.push((0, _gen.ReturnStatement)());
507
- (0, _traverse.walk)(node.body, [], (o, p) => {
508
- if (o.type == "ReturnStatement") {
509
- if (!(0, _insert.getFunction)(o, p)) {
510
- return () => {
511
- var exitExpr = (0, _gen.SequenceExpression)([(0, _gen.AssignmentExpression)("=", (0, _gen.ArrayPattern)(stateVars.map(_gen.Identifier)), (0, _gen.Identifier)(exitStateName)), (0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(resultVar), o.argument || (0, _gen.Identifier)("undefined"))]);
512
- this.replace(o, (0, _gen.ReturnStatement)(exitExpr));
513
- };
514
- }
515
- }
516
- });
517
- var declarations = [(0, _gen.VariableDeclarator)((0, _gen.ArrayPattern)([(0, _gen.Identifier)(exitStateName), (0, _gen.Identifier)(argumentsName)]), (0, _gen.Identifier)(argVar))];
518
508
 
519
- if (node.params.length) {
520
- declarations.push((0, _gen.VariableDeclarator)((0, _gen.ArrayPattern)(node.params), (0, _gen.Identifier)(argumentsName)));
521
- }
509
+ const chunks = []; // Flagged labels have addition code protecting the control state
522
510
 
523
- var innerName = this.getPlaceholder();
524
- chunks.push(...flattenBody([(0, _gen.FunctionDeclaration)(innerName, [], [(0, _gen.VariableDeclaration)(declarations), ...node.body.body]), this.objectAssign((0, _gen.ExpressionStatement)((0, _gen.CallExpression)((0, _gen.Identifier)(innerName), [])), {
525
- $exit: true
526
- })], fnToLabel[node.id.name]));
527
- }
528
- });
529
- var startLabel = this.getPlaceholder();
530
- chunks.push(...flattenBody(body, startLabel));
511
+ const flaggedLabels = Object.create(null);
512
+ /**
513
+ * label: switch(a+b+c){...break label...}
514
+ */
515
+
516
+ const switchLabel = this.getPlaceholder();
517
+ const startLabel = this.getPlaceholder();
518
+ chunks.push(...flattenBody(objectBody, startLabel));
531
519
  chunks[chunks.length - 1].body.push({
532
520
  type: "GotoStatement",
533
521
  label: "END_LABEL"
@@ -536,9 +524,63 @@ class ControlFlowFlattening extends _transform.default {
536
524
  label: "END_LABEL",
537
525
  body: []
538
526
  });
527
+ const endLabel = chunks[Object.keys(chunks).length - 1].label;
528
+
529
+ if (!this.isDebug && this.addDeadCode) {
530
+ // DEAD CODE 1/3: Add fake chunks that are never reached
531
+ var fakeChunkCount = (0, _random.getRandomInteger)(1, 5);
532
+
533
+ for (var i = 0; i < fakeChunkCount; i++) {
534
+ // These chunks just jump somewhere random, they are never executed
535
+ // so it could contain any code
536
+ var fakeChunkBody = [// This a fake assignment expression
537
+ (0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Literal)((0, _random.getRandomInteger)(-150, 150)))), {
538
+ type: "GotoStatement",
539
+ label: (0, _random.choice)(chunks).label
540
+ }];
541
+ chunks.push({
542
+ label: this.getPlaceholder(),
543
+ body: fakeChunkBody,
544
+ impossible: true
545
+ });
546
+ } // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators
547
+
548
+
549
+ chunks.forEach(chunk => {
550
+ if ((0, _random.chance)(25)) {
551
+ var randomLabel = (0, _random.choice)(chunks).label; // The `false` literal will be mangled
552
+
553
+ chunk.body.unshift((0, _gen.IfStatement)((0, _gen.Literal)(false), [{
554
+ type: "GotoStatement",
555
+ label: randomLabel,
556
+ impossible: true
557
+ }]));
558
+ }
559
+ }); // DEAD CODE 3/3: Clone chunks but these chunks are never ran
560
+
561
+ var cloneChunkCount = (0, _random.getRandomInteger)(1, 5);
562
+
563
+ for (var i = 0; i < cloneChunkCount; i++) {
564
+ var randomChunk = (0, _random.choice)(chunks);
565
+ var clonedChunk = {
566
+ body: (0, _insert.clone)(randomChunk.body),
567
+ label: this.getPlaceholder(),
568
+ impossible: true
569
+ }; // Don't double define functions
570
+
571
+ var hasDeclaration = clonedChunk.body.find(stmt => {
572
+ return stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration";
573
+ });
574
+
575
+ if (!hasDeclaration) {
576
+ chunks.unshift(clonedChunk);
577
+ }
578
+ }
579
+ } // Generate a unique 'state' number for each chunk
580
+
581
+
539
582
  var caseSelection = new Set();
540
583
  var uniqueStatesNeeded = chunks.length;
541
- var endLabel = chunks[Object.keys(chunks).length - 1].label;
542
584
 
543
585
  do {
544
586
  var newState = (0, _random.getRandomInteger)(1, chunks.length * 15);
@@ -567,9 +609,11 @@ class ControlFlowFlattening extends _transform.default {
567
609
  */
568
610
 
569
611
  var labelToStates = Object.create(null);
612
+ var lastLabel;
570
613
  Object.values(chunks).forEach((chunk, i) => {
571
614
  var state = caseStates[i];
572
- var stateValues = Array(stateVars.length).fill(0).map(() => (0, _random.getRandomInteger)(-250, 250));
615
+ var stateValues = Array(stateVars.length).fill(0).map((_, i) => lastLabel && (0, _random.chance)(95) // Try to make state changes not as drastic (If last label, re-use some of it's values)
616
+ ? labelToStates[lastLabel][i] : (0, _random.getRandomInteger)(-500, 500));
573
617
 
574
618
  const getCurrentState = () => {
575
619
  return stateValues.reduce((a, b) => b + a, 0);
@@ -578,53 +622,368 @@ class ControlFlowFlattening extends _transform.default {
578
622
  var correctIndex = (0, _random.getRandomInteger)(0, stateValues.length);
579
623
  stateValues[correctIndex] = state - (getCurrentState() - stateValues[correctIndex]);
580
624
  labelToStates[chunk.label] = stateValues;
581
- }); // console.log(labelToStates);
582
-
625
+ lastLabel = chunk.label;
626
+ });
583
627
  var initStateValues = [...labelToStates[startLabel]];
584
- var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0);
628
+ var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0); // Creates a predicate based on the state-variables and control-object properties
629
+
630
+ const createPredicate = stateValues => {
631
+ this.mangledExpressionsMade++;
632
+ var index = (0, _random.getRandomInteger)(0, stateVars.length);
633
+ var compareValue = (0, _random.choice)([stateValues[index], (0, _random.getRandomInteger)(-100, 100)]); // 'state equality' test
634
+
635
+ var test = (0, _gen.BinaryExpression)("==", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(compareValue, stateValues));
636
+ var testValue = stateValues[index] === compareValue; // 'control' equality test
637
+
638
+ if (controlConstantMap.size && (0, _random.chance)(50)) {
639
+ var _controlConstantMap$g;
640
+
641
+ // The controlMap maps LITERAL-values to STRING property names
642
+ var actualValue = (0, _random.choice)(Array.from(controlConstantMap.keys()));
643
+ var controlKey = (_controlConstantMap$g = controlConstantMap.get(actualValue)) === null || _controlConstantMap$g === void 0 ? void 0 : _controlConstantMap$g.key;
644
+ var controlCompareValue = (0, _random.choice)([actualValue, stateValues[index], (0, _random.getRandomInteger)(-100, 100), controlGen.generate()]); // 'control equality' test
645
+
646
+ test = (0, _gen.BinaryExpression)("==", getControlMember(controlKey), (0, _gen.Literal)(controlCompareValue));
647
+ testValue = actualValue == controlCompareValue; // 'control typeof' test
585
648
 
586
- const numberLiteral = (num, depth, stateValues) => {
587
- (0, _assert.ok)(Array.isArray(stateValues));
649
+ if ((0, _random.chance)(10)) {
650
+ var compareTypeofValue = (0, _random.choice)(["number", "string", "object", "function", "undefined"]);
651
+ test = (0, _gen.BinaryExpression)("==", (0, _gen.UnaryExpression)("typeof", getControlMember(controlKey)), (0, _gen.Literal)(compareTypeofValue));
652
+ testValue = typeof actualValue === compareTypeofValue;
653
+ } // 'control hasOwnProperty' test
654
+
655
+
656
+ if ((0, _random.chance)(10)) {
657
+ var hasOwnProperty = (0, _random.choice)([controlKey, controlGen.generate()]);
658
+ test = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(controlVar), (0, _gen.Literal)("hasOwnProperty"), true), [(0, _gen.Literal)(hasOwnProperty)]);
659
+ testValue = hasOwnProperty === controlKey;
660
+ }
661
+ }
662
+
663
+ return {
664
+ test,
665
+ testValue
666
+ };
667
+ }; // A "state-less" number literal is a Number Literal that is mangled in with the Control properties.
668
+ // Example: X = CONTROL.Y + Z. These can be used anywhere because control properties are constant (unlike state variables)
669
+
670
+
671
+ const createStatelessNumberLiteral = function (num) {
672
+ var _selectedControlConst2;
673
+
674
+ let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
675
+
676
+ if (!controlConstantMap.size || depth > 4 || (0, _random.chance)(75 + depth * 5 + _this.mangledExpressionsMade / 25)) {
677
+ // Add to control constant map?
678
+ if ((0, _random.chance)(25 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
679
+ return addControlMapConstant(num);
680
+ }
588
681
 
589
- if (depth > 10 || Math.random() > 0.8 / (depth * 4)) {
590
682
  return (0, _gen.Literal)(num);
591
683
  }
592
684
 
685
+ _this.mangledExpressionsMade++;
686
+
687
+ if (controlConstantMap.has(num)) {
688
+ var _controlConstantMap$g2;
689
+
690
+ return getControlMember((_controlConstantMap$g2 = controlConstantMap.get(num)) === null || _controlConstantMap$g2 === void 0 ? void 0 : _controlConstantMap$g2.key);
691
+ }
692
+
693
+ var allControlNodes = [object];
694
+ parents.filter(x => x.$controlVar && x.$controlConstantMap.size > 0).forEach(node => allControlNodes.push(node));
695
+ var controlNode = (0, _random.choice)(allControlNodes);
696
+ var selectedControlConstantMap = controlNode.$controlConstantMap;
697
+ var selectedControlVar = controlNode.$controlVar;
698
+ var actualValue = (0, _random.choice)(Array.from(selectedControlConstantMap.keys()));
699
+ var controlKey = (_selectedControlConst2 = selectedControlConstantMap.get(actualValue)) === null || _selectedControlConst2 === void 0 ? void 0 : _selectedControlConst2.key;
700
+
701
+ if (typeof actualValue === "number") {
702
+ var difference = actualValue - num;
703
+ return (0, _gen.BinaryExpression)("-", getControlMember(controlKey, selectedControlVar), createStatelessNumberLiteral(difference, depth + 1));
704
+ } else if (typeof actualValue === "string") {
705
+ // 'control string length' test
706
+ var compareValue = (0, _random.choice)([actualValue.length, (0, _random.getRandomInteger)(0, 50)]);
707
+ var test = (0, _gen.BinaryExpression)("==", (0, _gen.MemberExpression)(getControlMember(controlKey, selectedControlVar), (0, _gen.Literal)("length"), true), createStatelessNumberLiteral(compareValue, depth + 1));
708
+ var testValue = actualValue.length == compareValue;
709
+ var consequent = createStatelessNumberLiteral(num, depth + 1);
710
+ var alternate = (0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100));
711
+ return (0, _gen.ConditionalExpression)(test, testValue ? consequent : alternate, !testValue ? consequent : alternate);
712
+ } else {
713
+ throw new Error("Unknown: " + typeof actualValue);
714
+ }
715
+ }; // A "state-bound" number literal is a Number Literal that is mangled in with the current state variables
716
+ // Example: X = STATE + Y. This can only be used when the state-values are guaranteed to be known.
717
+
718
+
719
+ const createStateBoundNumberLiteral = function (num, stateValues) {
720
+ let depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
721
+ (0, _assert.ok)(Array.isArray(stateValues)); // Base case: After 4 depth, OR random chance
722
+
723
+ if (depth > 4 || (0, _random.chance)(75 + depth * 5 + _this.mangledExpressionsMade / 25)) {
724
+ // Add this number to the control object?
725
+ // Add to control constant map?
726
+ if ((0, _random.chance)(25 - controlConstantMap.size)) {
727
+ return addControlMapConstant(num);
728
+ }
729
+
730
+ return (0, _gen.Literal)(num);
731
+ }
732
+
733
+ _this.mangledExpressionsMade++;
734
+
735
+ if ((0, _random.chance)(10)) {
736
+ return createStatelessNumberLiteral(num, depth + 1);
737
+ } // Terminated predicate
738
+
739
+
740
+ if ((0, _random.chance)(50)) {
741
+ var {
742
+ test,
743
+ testValue
744
+ } = createPredicate(stateValues);
745
+ var alternateNode = (0, _random.choice)([(0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100)), (0, _gen.Literal)(controlGen.generate()), getControlMember(controlGen.generate())]);
746
+ return (0, _gen.ConditionalExpression)(test, testValue ? (0, _gen.Literal)(num) : alternateNode, !testValue ? (0, _gen.Literal)(num) : alternateNode);
747
+ } // Recursive predicate
748
+
749
+
593
750
  var opposing = (0, _random.getRandomInteger)(0, stateVars.length);
594
751
 
595
- if (Math.random() > 0.5) {
596
- var x = (0, _random.getRandomInteger)(-250, 250);
597
- var operator = (0, _random.choice)(["<", ">"]);
598
- var answer = operator == "<" ? x < stateValues[opposing] : x > stateValues[opposing];
599
- var correct = numberLiteral(num, depth + 1, stateValues);
600
- var incorrect = numberLiteral((0, _random.getRandomInteger)(-250, 250), depth + 1, stateValues);
601
- return (0, _gen.ConditionalExpression)((0, _gen.BinaryExpression)(operator, numberLiteral(x, depth + 1, stateValues), (0, _gen.Identifier)(stateVars[opposing])), answer ? correct : incorrect, answer ? incorrect : correct);
752
+ if ((0, _random.chance)(10)) {
753
+ // state > compare ? real : fake
754
+ var compareValue = (0, _random.choice)([stateValues[opposing], (0, _random.getRandomInteger)(-150, 150)]);
755
+ var operator = (0, _random.choice)(["<", ">", "==", "!="]);
756
+ var answer = {
757
+ ">": compareValue > stateValues[opposing],
758
+ "<": compareValue < stateValues[opposing],
759
+ "==": compareValue === stateValues[opposing],
760
+ "!=": compareValue !== stateValues[opposing]
761
+ }[operator];
762
+ var correct = createStateBoundNumberLiteral(num, stateValues, depth + 1);
763
+ var incorrect = createStateBoundNumberLiteral((0, _random.getRandomInteger)(-150, 150), stateValues, depth + 1);
764
+ return (0, _gen.ConditionalExpression)((0, _gen.BinaryExpression)(operator, createStateBoundNumberLiteral(compareValue, stateValues, depth + 1), (0, _gen.Identifier)(stateVars[opposing])), answer ? correct : incorrect, answer ? incorrect : correct);
765
+ } // state + 10 = <REAL>
766
+
767
+
768
+ var difference = num - stateValues[opposing];
769
+
770
+ if (difference === 0) {
771
+ return (0, _gen.Identifier)(stateVars[opposing]);
772
+ }
773
+
774
+ return (0, _gen.BinaryExpression)("+", (0, _gen.Identifier)(stateVars[opposing]), createStateBoundNumberLiteral(difference, stateValues, depth + 1));
775
+ };
776
+
777
+ var outlinesCreated = 0;
778
+
779
+ const isExpression = (object, parents) => {
780
+ var fnIndex = parents.findIndex(x => (0, _insert.isFunction)(x));
781
+
782
+ if (fnIndex != -1) {
783
+ // This does NOT mutate
784
+ parents = parents.slice(0, fnIndex);
785
+ }
786
+
787
+ var assignmentIndex = parents.findIndex(x => x.type === "AssignmentExpression"); // Left-hand assignment validation
788
+
789
+ if (assignmentIndex != -1) {
790
+ if (parents[assignmentIndex].left === (parents[assignmentIndex - 1] || object)) {
791
+ return false;
792
+ }
793
+ } // For in/of left validation
794
+
795
+
796
+ var forInOfIndex = parents.findIndex(x => x.type === "ForInStatement" || x.type === "ForOfStatement");
797
+
798
+ if (forInOfIndex != -1) {
799
+ if (parents[forInOfIndex].left === (parents[forInOfIndex - 1] || object)) {
800
+ return false;
801
+ }
802
+ } // Bound call-expression validation
803
+
804
+
805
+ var callExpressionIndex = parents.findIndex(x => x.type === "CallExpression");
806
+
807
+ if (callExpressionIndex != -1) {
808
+ if (parents[callExpressionIndex].callee == (parents[callExpressionIndex - 1] || object)) {
809
+ var callee = parents[callExpressionIndex].callee; // Detected bound call expression. Not supported.
810
+
811
+ if (callee.type === "MemberExpression") {
812
+ return false;
813
+ }
814
+ }
815
+ } // Update-expression validation:
816
+
817
+
818
+ var updateExpressionIndex = parents.findIndex(x => x.type === "UpdateExpression");
819
+ if (updateExpressionIndex !== -1) return false;
820
+ return true;
821
+ }; // This function checks if the expression or statements is possible to be outlined
822
+
823
+
824
+ const canOutline = (object, parents) => {
825
+ var isIllegal = false;
826
+ var breakStatements = [];
827
+ var returnStatements = [];
828
+
829
+ if (!Array.isArray(object) && !isExpression(object, parents)) {
830
+ return {
831
+ isIllegal: true,
832
+ breakStatements: [],
833
+ returnStatements: []
834
+ };
835
+ }
836
+
837
+ (0, _traverse.walk)(object, parents, (o, p) => {
838
+ if (o.type === "ThisExpression" || o.type === "MetaProperty" || o.type === "Super") {
839
+ isIllegal = true;
840
+ return "EXIT";
841
+ }
842
+
843
+ if (o.type === "BreakStatement") {
844
+ // This can be safely outlined
845
+ if (o.label && o.label.name === switchLabel) {
846
+ breakStatements.push([o, p]);
847
+ } else {
848
+ isIllegal = true;
849
+ return "EXIT";
850
+ }
851
+ }
852
+
853
+ if ((o.type === "ContinueStatement" || o.type === "AwaitExpression" || o.type === "YieldExpression" || o.type === "ReturnStatement" || o.type === "VariableDeclaration" || o.type === "FunctionDeclaration" || o.type === "ClassDeclaration") && !p.find(x => (0, _insert.isVarContext)(x))) {
854
+ // This can be safely outlined
855
+ if (o.type === "ReturnStatement") {
856
+ returnStatements.push([o, p]);
857
+ } else {
858
+ isIllegal = true;
859
+ return "EXIT";
860
+ }
861
+ }
862
+
863
+ if (o.type === "Identifier") {
864
+ if (o.name === "arguments") {
865
+ isIllegal = true;
866
+ return "EXIT";
867
+ }
868
+ }
869
+ });
870
+ return {
871
+ isIllegal,
872
+ breakStatements,
873
+ returnStatements
874
+ };
875
+ };
876
+
877
+ const createOutlineFunction = (body, stateValues, label) => {
878
+ var key = controlGen.generate();
879
+ var functionExpression = (0, _gen.FunctionExpression)([], body);
880
+
881
+ if (!this.options.es5 && (0, _random.chance)(50)) {
882
+ functionExpression.type = "ArrowFunctionExpression";
602
883
  }
603
884
 
604
- return (0, _gen.BinaryExpression)("+", (0, _gen.Identifier)(stateVars[opposing]), numberLiteral(num - stateValues[opposing], depth + 1, stateValues));
885
+ controlProperties.push((0, _gen.Property)((0, _gen.Literal)(key), functionExpression, false)); // Add dead code to function
886
+
887
+ if (!this.isDebug && (0, _random.chance)(25)) {
888
+ var {
889
+ test,
890
+ testValue
891
+ } = createPredicate(stateValues);
892
+ var deadCodeVar = this.getPlaceholder();
893
+ functionExpression.params.push((0, _gen.AssignmentPattern)((0, _gen.Identifier)(deadCodeVar), test));
894
+ var alternate = [(0, _gen.ReturnStatement)((0, _random.choice)([(0, _gen.BinaryExpression)("==", (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100))), (0, _gen.Literal)(controlGen.generate()), (0, _gen.Identifier)("arguments"), (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Identifier)(controlVar), (0, _gen.CallExpression)(getControlMember(controlGen.generate()), [])]))];
895
+ functionExpression.body.body.unshift((0, _gen.IfStatement)(testValue ? (0, _gen.UnaryExpression)("!", (0, _gen.Identifier)(deadCodeVar)) : (0, _gen.Identifier)(deadCodeVar), alternate));
896
+ }
897
+
898
+ outlinesCreated++;
899
+ return key;
605
900
  };
606
901
 
607
- const createTransitionExpression = (index, add, mutatingStateValues) => {
902
+ const attemptOutlineStatements = (statements, parentBlock, stateValues, label) => {
903
+ if (this.isDebug || !this.outlineStatements || (0, _random.chance)(75 + outlinesCreated - this.mangledExpressionsMade / 25)) {
904
+ return;
905
+ }
906
+
907
+ var index = parentBlock.indexOf(statements[0]);
908
+ if (index === -1) return;
909
+ var outlineInfo = canOutline(statements, parentBlock);
910
+ if (outlineInfo.isIllegal) return;
911
+ var breakFlag = controlGen.generate();
912
+ outlineInfo.breakStatements.forEach(_ref => {
913
+ let [breakStatement, p] = _ref;
914
+ this.replace(breakStatement, (0, _gen.ReturnStatement)((0, _gen.Literal)(breakFlag)));
915
+ });
916
+ var returnFlag = controlGen.generate();
917
+ outlineInfo.returnStatements.forEach(_ref2 => {
918
+ let [returnStatement, p] = _ref2;
919
+ var argument = returnStatement.argument || (0, _gen.Identifier)("undefined");
920
+ this.replace(returnStatement, (0, _gen.ReturnStatement)((0, _gen.ObjectExpression)([(0, _gen.Property)((0, _gen.Literal)(returnFlag), argument, false)])));
921
+ }); // Outline these statements!
922
+
923
+ var key = createOutlineFunction((0, _insert.clone)(statements), stateValues, label);
924
+ var callExpression = (0, _gen.CallExpression)(getControlMember(key), []);
925
+ var newStatements = [];
926
+
927
+ if (outlineInfo.breakStatements.length === 0 && outlineInfo.returnStatements.length === 0) {
928
+ newStatements.push((0, _gen.ExpressionStatement)(callExpression));
929
+ } else if (outlineInfo.returnStatements.length === 0) {
930
+ newStatements.push((0, _gen.IfStatement)((0, _gen.BinaryExpression)("==", callExpression, (0, _gen.Literal)(breakFlag)), [(0, _gen.BreakStatement)(switchLabel)]));
931
+ } else {
932
+ var tempVar = this.getPlaceholder();
933
+ newStatements.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(tempVar, callExpression)));
934
+
935
+ const t = str => (0, _template.default)(str).single().expression;
936
+
937
+ newStatements.push((0, _gen.IfStatement)(t("".concat(tempVar, " === \"").concat(breakFlag, "\"")), [(0, _gen.BreakStatement)(switchLabel)], [(0, _gen.IfStatement)(t("typeof ".concat(tempVar, " == \"object\"")), [(0, _gen.ReturnStatement)(t("".concat(tempVar, "[\"").concat(returnFlag, "\"]")))])]));
938
+ } // Remove the original statements from the block and replace it with the call expression
939
+
940
+
941
+ parentBlock.splice(index, statements.length, ...newStatements);
942
+ };
943
+
944
+ const attemptOutlineExpression = (expression, expressionParents, stateValues, label) => {
945
+ if (this.isDebug || !this.outlineExpressions || (0, _random.chance)(75 + outlinesCreated - this.mangledExpressionsMade / 25)) {
946
+ return;
947
+ }
948
+
949
+ var outlineInfo = canOutline(expression, expressionParents);
950
+ if (outlineInfo.isIllegal || outlineInfo.breakStatements.length || outlineInfo.returnStatements.length) return; // Outline this expression!
951
+
952
+ var key = createOutlineFunction([(0, _gen.ReturnStatement)((0, _insert.clone)(expression))], stateValues, label);
953
+ var callExpression = (0, _gen.CallExpression)(getControlMember(key), []);
954
+ this.replaceIdentifierOrLiteral(expression, callExpression, expressionParents);
955
+ };
956
+
957
+ const createTransitionExpression = (index, add, mutatingStateValues, label) => {
958
+ var beforeStateValues = [...mutatingStateValues];
608
959
  var newValue = mutatingStateValues[index] + add;
609
960
  var expr = null;
610
961
 
611
962
  if (this.isDebug) {
963
+ // state = NEW_STATE
612
964
  expr = (0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(stateVars[index]), (0, _gen.Literal)(newValue));
613
- } else if (Math.random() > 0.5) {
614
- expr = (0, _gen.AssignmentExpression)("+=", (0, _gen.Identifier)(stateVars[index]), numberLiteral(add, 0, mutatingStateValues));
965
+ } else if ((0, _random.chance)(90)) {
966
+ // state += (NEW_STATE - CURRENT_STATE)
967
+ expr = (0, _gen.AssignmentExpression)("+=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(add, mutatingStateValues));
615
968
  } else {
969
+ // state *= 2
970
+ // state -= DIFFERENCE
616
971
  var double = mutatingStateValues[index] * 2;
617
972
  var diff = double - newValue;
618
- var first = (0, _gen.AssignmentExpression)("*=", (0, _gen.Identifier)(stateVars[index]), numberLiteral(2, 0, mutatingStateValues));
973
+ var first = (0, _gen.AssignmentExpression)("*=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(2, mutatingStateValues));
619
974
  mutatingStateValues[index] = double;
620
- expr = (0, _gen.SequenceExpression)([first, (0, _gen.AssignmentExpression)("-=", (0, _gen.Identifier)(stateVars[index]), numberLiteral(diff, 0, mutatingStateValues))]);
975
+ expr = (0, _gen.SequenceExpression)([first, (0, _gen.AssignmentExpression)("-=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(diff, mutatingStateValues))]);
976
+ }
977
+
978
+ mutatingStateValues[index] = newValue; // These are lower quality outlines vs. the entire transition outline
979
+
980
+ if ((0, _random.chance)(50)) {
981
+ attemptOutlineExpression(expr, [], [...beforeStateValues], label);
621
982
  }
622
983
 
623
- mutatingStateValues[index] = newValue;
624
984
  return expr;
625
985
  };
626
986
 
627
- var order = Object.create(null);
628
987
  var cases = [];
629
988
  chunks.forEach((chunk, i) => {
630
989
  // skip last case, its empty and never ran
@@ -634,18 +993,111 @@ class ControlFlowFlattening extends _transform.default {
634
993
 
635
994
  (0, _assert.ok)(labelToStates[chunk.label]);
636
995
  var state = caseStates[i];
637
- var made = 1;
638
- var breaksInsertion = [];
639
996
  var staticStateValues = [...labelToStates[chunk.label]];
640
997
  var potentialBranches = new Set();
641
- chunk.body.forEach((stmt, stmtIndex) => {
642
- var addBreak = false;
998
+ [...chunk.body].forEach(stmt => {
643
999
  (0, _traverse.walk)(stmt, [], (o, p) => {
644
- if (!this.isDebug && o.type == "Literal" && typeof o.value === "number" && Math.floor(o.value) === o.value && Math.abs(o.value) < 100000 && Math.random() < 4 / made && !p.find(x => (0, _insert.isVarContext)(x))) {
645
- made++;
1000
+ // This mangles certain literals with the state variables
1001
+ // Ex: A number literal (50) changed to a expression (stateVar + 40), when stateVar = 10
1002
+ if (!this.isDebug && o.type === "Literal" && !p.find(x => (0, _insert.isVarContext)(x))) {
1003
+ if (typeof o.value === "number" && Math.floor(o.value) === o.value && // Only whole numbers
1004
+ Math.abs(o.value) < 100000 && // Hard-coded limit
1005
+ this.mangleNumberLiterals && (0, _random.chance)(50 - this.mangledExpressionsMade / 100)) {
1006
+ // 50 -> state1 - 10, when state1 = 60. The result is still 50
1007
+ return () => {
1008
+ this.replaceIdentifierOrLiteral(o, createStateBoundNumberLiteral(o.value, staticStateValues), p);
1009
+ };
1010
+ }
1011
+
1012
+ if (typeof o.value === "boolean" && this.mangleBooleanLiterals && (0, _random.chance)(50 - this.mangledExpressionsMade / 100)) {
1013
+ // true -> state1 == 10, when state1 = 10. The result is still true
1014
+ // Choose a random state var to compare again
1015
+ var index = (0, _random.getRandomInteger)(0, stateVars.length);
1016
+ var compareValue = staticStateValues[index]; // When false, always choose a different number, so the expression always equals false
1017
+
1018
+ while (!o.value && compareValue === staticStateValues[index]) {
1019
+ compareValue = (0, _random.getRandomInteger)(-150, 150);
1020
+ }
1021
+
1022
+ var mangledExpression = (0, _gen.BinaryExpression)("==", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(compareValue, staticStateValues));
1023
+ return () => {
1024
+ this.replaceIdentifierOrLiteral(o, mangledExpression, p);
1025
+ attemptOutlineExpression(o, p, staticStateValues, chunk.label);
1026
+ };
1027
+ }
1028
+ } // Mangle certain referenced identifiers
1029
+ // console.log("hi") -> (x ? console : window).log("hi"), when is x true. The result is the same
1030
+
1031
+
1032
+ if (!this.isDebug && o.type === "Identifier" && this.mangleIdentifiers && (0, _random.chance)(50 - this.mangledExpressionsMade / 100) && !p.find(x => (0, _insert.isVarContext)(x))) {
1033
+ // ONLY referenced identifiers (like actual variable names) can be changed
1034
+ var info = (0, _identifiers.getIdentifierInfo)(o, p);
1035
+
1036
+ if (!info.spec.isReferenced || info.spec.isDefined || info.spec.isModified || info.spec.isExported) {
1037
+ return;
1038
+ } // TYPEOF expression check
1039
+
1040
+
1041
+ if (p[0] && p[0].type === "UnaryExpression" && p[0].operator === "typeof" && p[0].argument === o) {
1042
+ return;
1043
+ } // Update expression check
1044
+
1045
+
1046
+ if (p[0] && p[0].type === "UpdateExpression") {
1047
+ return;
1048
+ } // FOR-in/of initializer check
1049
+
1050
+
1051
+ if ((0, _insert.isForInitialize)(o, p) === "left-hand") {
1052
+ return;
1053
+ }
1054
+
1055
+ var {
1056
+ test,
1057
+ testValue
1058
+ } = createPredicate(staticStateValues); // test && real
1059
+
1060
+ var mangledExpression = (0, _gen.LogicalExpression)(testValue ? "&&" : "||", test, (0, _gen.Identifier)(o.name)); // control.fake = real
1061
+
1062
+ if ((0, _random.chance)(50)) {
1063
+ mangledExpression = (0, _gen.AssignmentExpression)("=", getControlMember(controlGen.generate()), (0, _gen.Identifier)(o.name));
1064
+ } // test ? real : fake
1065
+
1066
+
1067
+ if ((0, _random.chance)(50)) {
1068
+ var alternateName = (0, _random.choice)([controlVar, ...stateVars, ...this.options.globalVariables, ..._constants.reservedIdentifiers]); // Don't use 'arguments'
1069
+
1070
+ if (alternateName === "arguments") alternateName = "undefined";
1071
+ mangledExpression = (0, _gen.ConditionalExpression)(test, (0, _gen.Identifier)(testValue ? o.name : alternateName), (0, _gen.Identifier)(!testValue ? o.name : alternateName));
1072
+ }
1073
+
1074
+ return () => {
1075
+ this.replaceIdentifierOrLiteral(o, mangledExpression, p);
1076
+ };
1077
+ } // Function outlining: bring out certain expressions
1078
+
1079
+
1080
+ if (!this.isDebug && o.type && ["BinaryExpression", "LogicalExpression", "CallExpression", "AssignmentExpression", "MemberExpression", "ObjectExpression", "ConditionalExpression"].includes(o.type) && !(0, _random.chance)(p.length * 5) && // The further down the tree the lower quality of expression
1081
+ !p.find(x => (0, _insert.isContext)(x) || x.$outlining)) {
1082
+ o.$outlining = true;
646
1083
  return () => {
647
- this.replaceIdentifierOrLiteral(o, numberLiteral(o.value, 0, staticStateValues), p);
1084
+ attemptOutlineExpression(o, p, staticStateValues, chunk.label);
648
1085
  };
1086
+ } // Opaque predicates: If Statements, Conditional Statements, Switch Case test
1087
+
1088
+
1089
+ if (!this.isDebug && this.addOpaquePredicates && p[0] && (0, _random.chance)(50 - outlinesCreated - this.mangledExpressionsMade / 100)) {
1090
+ var isTestExpression = p[0].type == "IfStatement" && p[0].test === o || p[0].type === "ConditionalExpression" && p[0].test === o || p[0].type === "SwitchCase" && p[0].test === o;
1091
+
1092
+ if (isTestExpression && !p.find(x => (0, _insert.isContext)(x))) {
1093
+ return () => {
1094
+ var {
1095
+ test,
1096
+ testValue
1097
+ } = createPredicate(staticStateValues);
1098
+ this.replace(o, (0, _gen.LogicalExpression)(testValue ? "&&" : "||", test, (0, _insert.clone)(o)));
1099
+ };
1100
+ }
649
1101
  }
650
1102
 
651
1103
  if (o.type == "StateIdentifier") {
@@ -657,104 +1109,265 @@ class ControlFlowFlattening extends _transform.default {
657
1109
 
658
1110
  if (o.type == "GotoStatement") {
659
1111
  return () => {
660
- var blockIndex = p.findIndex(node => (0, _traverse.isBlock)(node));
1112
+ var blockIndex = p.findIndex(node => (0, _traverse.isBlock)(node) || node.type === "SwitchCase");
661
1113
 
662
1114
  if (blockIndex === -1) {
663
- addBreak = true;
1115
+ var index = chunk.body.indexOf(stmt);
1116
+ (0, _assert.ok)(index != -1); // Top level: Insert break statement in the chunk body
1117
+ // This is OKAY because this forEach uses a cloned version of the body `[...chunk.body]`
1118
+
1119
+ chunk.body.splice(index + 1, 0, (0, _gen.BreakStatement)(switchLabel));
664
1120
  } else {
665
- var child = p[blockIndex - 2] || o;
666
- var childIndex = p[blockIndex].body.indexOf(child);
667
- p[blockIndex].body.splice(childIndex + 1, 0, (0, _gen.BreakStatement)(switchLabel));
1121
+ var block = p[blockIndex];
1122
+
1123
+ if (block.type === "SwitchCase") {
1124
+ // Handle switch case break placement (Important!)
1125
+ block.consequent.splice(block.consequent.indexOf(p[blockIndex - 2] || o) + 1, 0, (0, _gen.BreakStatement)(switchLabel));
1126
+ } else {
1127
+ // Standard block placement
1128
+ var child = p[blockIndex - 2] || o;
1129
+ var childIndex = block.body.indexOf(child);
1130
+ block.body.splice(childIndex + 1, 0, (0, _gen.BreakStatement)(switchLabel));
1131
+ }
1132
+ }
1133
+
1134
+ if (!o.impossible) {
1135
+ potentialBranches.add(o.label);
668
1136
  }
669
1137
 
670
- potentialBranches.add(o.label);
671
1138
  var mutatingStateValues = [...labelToStates[chunk.label]];
672
1139
  var nextStateValues = labelToStates[o.label];
673
1140
  (0, _assert.ok)(nextStateValues, o.label);
674
- this.replace(o, (0, _gen.ExpressionStatement)((0, _gen.SequenceExpression)(mutatingStateValues.map((_v, stateValueIndex) => {
675
- var diff = nextStateValues[stateValueIndex] - mutatingStateValues[stateValueIndex];
676
- return createTransitionExpression(stateValueIndex, diff, mutatingStateValues);
677
- }))));
1141
+ var transitionExpressions = [];
1142
+
1143
+ for (var stateValueIndex = 0; stateValueIndex < stateVars.length; stateValueIndex++) {
1144
+ var diff = nextStateValues[stateValueIndex] - mutatingStateValues[stateValueIndex]; // Only add if state value changed
1145
+ // If pointing to itself then always add to ensure SequenceExpression isn't empty
1146
+
1147
+ if (diff !== 0 || o.label === chunk.label) {
1148
+ transitionExpressions.push(createTransitionExpression(stateValueIndex, diff, mutatingStateValues, chunk.label));
1149
+ }
1150
+ }
1151
+
1152
+ (0, _assert.ok)(transitionExpressions.length !== 0);
1153
+ var sequenceExpression = (0, _gen.SequenceExpression)(transitionExpressions); // Check if flagged and additional code here
1154
+
1155
+ if (typeof flaggedLabels[o.label] === "object") {
1156
+ var {
1157
+ flagKey,
1158
+ flagValue
1159
+ } = flaggedLabels[o.label];
1160
+ sequenceExpression.expressions.push((0, _gen.AssignmentExpression)("=", getControlMember(flagKey), (0, _gen.Literal)(flagValue)));
1161
+ }
1162
+
1163
+ attemptOutlineExpression(sequenceExpression, [], staticStateValues, chunk.label);
1164
+ this.replace(o, (0, _gen.ExpressionStatement)(sequenceExpression));
678
1165
  };
679
1166
  }
680
1167
  });
681
-
682
- if (addBreak) {
683
- breaksInsertion.push(stmtIndex);
684
- }
685
- });
686
- breaksInsertion.sort();
687
- breaksInsertion.reverse();
688
- breaksInsertion.forEach(index => {
689
- chunk.body.splice(index + 1, 0, (0, _gen.BreakStatement)(switchLabel));
690
1168
  });
1169
+ attemptOutlineStatements(chunk.body, chunk.body, staticStateValues, chunk.label);
691
1170
 
692
- for (var branch of Array.from(potentialBranches)) {
693
- var strings = stringBankByLabels[branch];
694
-
695
- if (strings) {
696
- chunk.body.unshift((0, _gen.ExpressionStatement)((0, _gen.SequenceExpression)(Array.from(strings).map(strValue => {
697
- return (0, _gen.AssignmentExpression)("=", (0, _gen.MemberExpression)((0, _gen.Identifier)(stringBankVar), (0, _gen.Literal)(stringBank[strValue]), true), (0, _gen.Literal)(strValue));
698
- }))));
699
- }
700
- } // var c = Identifier("undefined");
701
- // this.addComment(c, stateValues.join(", "));
702
- // transitionStatements.push(c);
703
-
1171
+ if (!chunk.impossible) {// FUTURE OBFUSCATION IDEA: Update controlObject based on 'potentialBranches' code
1172
+ // This idea would require a lot of work but would make some seriously effective obfuscation
1173
+ // for protecting the data. In 'inactive' states the data could be overwritten to fake values
1174
+ // And in the 'active' state the data would brought back just in time. This would require the controlObject
1175
+ // state to be known in all chunks
1176
+ }
704
1177
 
705
1178
  var caseObject = {
706
1179
  body: chunk.body,
707
1180
  state: state,
708
- order: i
1181
+ label: chunk.label
709
1182
  };
710
- order[i] = caseObject;
711
1183
  cases.push(caseObject);
712
1184
  });
713
1185
 
1186
+ if (!this.isDebug && this.addDeadCode) {
1187
+ // Add fake control object updates
1188
+ chunks.forEach(chunk => {
1189
+ if ((0, _random.chance)(10)) {
1190
+ // These deadCode variants can NOT break the state/control variables
1191
+ // They are executed!
1192
+ var deadCodeChoices = [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlGen.generate()), (0, _gen.Literal)(controlGen.generate()))), (0, _gen.ExpressionStatement)((0, _gen.UnaryExpression)("delete", getControlMember(controlGen.generate())))]; // These deadCode variants can make breaking changes
1193
+ // because they are never ran
1194
+
1195
+ if (chunk.impossible) {
1196
+ var randomControlKey = (0, _random.choice)(controlProperties.map(prop => {
1197
+ var _prop$key;
1198
+
1199
+ return (_prop$key = prop.key) === null || _prop$key === void 0 ? void 0 : _prop$key.value;
1200
+ }).filter(x => x && typeof x === "string")) || controlGen.generate();
1201
+ deadCodeChoices = deadCodeChoices.concat([(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(controlVar), (0, _gen.Literal)(false))), (0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(controlVar), (0, _gen.Identifier)("undefined"))), (0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(randomControlKey), (0, _gen.Identifier)("undefined"))), (0, _gen.ExpressionStatement)((0, _gen.UnaryExpression)("delete", getControlMember(randomControlKey)))]);
1202
+ }
1203
+
1204
+ chunk.body.unshift((0, _random.choice)(deadCodeChoices));
1205
+ }
1206
+ });
1207
+ }
1208
+
714
1209
  if (!this.isDebug) {
715
1210
  (0, _random.shuffle)(cases);
1211
+ (0, _random.shuffle)(controlProperties);
716
1212
  }
717
1213
 
718
1214
  var discriminant = (0, _template.default)("".concat(stateVars.join("+"))).single().expression;
719
- body.length = 0;
1215
+ objectBody.length = 0; // Perverse position of import declarations
720
1216
 
721
1217
  for (var importDeclaration of importDeclarations) {
722
- body.push(importDeclaration);
723
- }
1218
+ objectBody.push(importDeclaration);
1219
+ } // As well as functions are brought up
724
1220
 
725
- if (functionDeclarations.size) {
726
- functionDeclarations.forEach(x => {
727
- if (!x.id || illegalFnNames.has(x.id.name)) {
728
- body.unshift((0, _insert.clone)(x));
729
- }
730
- });
1221
+
1222
+ for (var functionName of functionDeclarationNames) {
1223
+ objectBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(functionName, functionDeclarationValues.get(functionName))));
731
1224
  }
732
1225
 
733
- var switchStatement = (0, _gen.SwitchStatement)(discriminant, cases.map((x, i) => {
734
- var statements = [];
735
- statements.push(...x.body);
736
- var test = (0, _gen.Literal)(x.state);
737
- return (0, _gen.SwitchCase)(test, statements);
738
- }));
739
- var declarations = [];
1226
+ var defaultCaseIndex = (0, _random.getRandomInteger)(0, cases.length);
1227
+ var switchCases = [];
1228
+ cases.forEach((caseObject, i) => {
1229
+ var _caseObject$body$0$la;
740
1230
 
741
- if (needsTestVar) {
742
- declarations.push((0, _gen.VariableDeclarator)(testVar));
743
- }
1231
+ // Empty case OR single break statement is skipped
1232
+ if (caseObject.body.length === 0 || caseObject.body.length === 1 && caseObject.body[0].type === "BreakStatement" && ((_caseObject$body$0$la = caseObject.body[0].label) === null || _caseObject$body$0$la === void 0 ? void 0 : _caseObject$body$0$la.name) === switchLabel) return;
1233
+ var test = (0, _gen.Literal)(caseObject.state);
1234
+ var isEligibleForOutlining = false; // Check if Control Map has this value
744
1235
 
745
- if (needsResultAndArgVar) {
746
- declarations.push((0, _gen.VariableDeclarator)(resultVar));
747
- declarations.push((0, _gen.VariableDeclarator)(argVar));
748
- }
1236
+ if (!this.isDebug && controlConstantMap.has(caseObject.state)) {
1237
+ var _controlConstantMap$g3;
749
1238
 
750
- if (needsStringBankVar) {
751
- declarations.push((0, _gen.VariableDeclarator)(stringBankVar, (0, _gen.ObjectExpression)(stringBankByLabels[startLabel] ? Array.from(stringBankByLabels[startLabel]).map(strValue => (0, _gen.Property)((0, _gen.Literal)(stringBank[strValue]), (0, _gen.Literal)(strValue), false)) : [])));
752
- }
1239
+ test = getControlMember((_controlConstantMap$g3 = controlConstantMap.get(caseObject.state)) === null || _controlConstantMap$g3 === void 0 ? void 0 : _controlConstantMap$g3.key);
1240
+ } // Create complex test expressions for each switch case
1241
+
1242
+
1243
+ if (!this.isDebug && this.addComplexTest && (0, _random.chance)(25)) {
1244
+ isEligibleForOutlining = true; // case STATE+X:
1245
+
1246
+ var stateVarIndex = (0, _random.getRandomInteger)(0, stateVars.length);
1247
+ var stateValues = labelToStates[caseObject.label];
1248
+ var difference = stateValues[stateVarIndex] - caseObject.state;
1249
+ var conditionNodes = [];
1250
+ var alreadyConditionedItems = new Set(); // This code finds clash conditions and adds them to 'conditionNodes' array
1251
+
1252
+ Object.keys(labelToStates).forEach(label => {
1253
+ if (label !== caseObject.label) {
1254
+ var labelStates = labelToStates[label];
1255
+ var totalState = labelStates.reduce((a, b) => a + b, 0);
1256
+
1257
+ if (totalState === labelStates[stateVarIndex] - difference) {
1258
+ var differentIndex = labelStates.findIndex((v, i) => v !== stateValues[i]);
1259
+
1260
+ if (differentIndex !== -1) {
1261
+ var expressionAsString = stateVars[differentIndex] + "!=" + labelStates[differentIndex];
1262
+
1263
+ if (!alreadyConditionedItems.has(expressionAsString)) {
1264
+ alreadyConditionedItems.add(expressionAsString);
1265
+ conditionNodes.push((0, _gen.BinaryExpression)("!=", (0, _gen.Identifier)(stateVars[differentIndex]), (0, _gen.Literal)(labelStates[differentIndex])));
1266
+ }
1267
+ } else {
1268
+ conditionNodes.push((0, _gen.BinaryExpression)("!=", (0, _insert.clone)(discriminant), (0, _gen.Literal)(totalState)));
1269
+ }
1270
+ }
1271
+ }
1272
+ }); // case STATE!=Y && STATE+X
1273
+
1274
+ test = (0, _gen.BinaryExpression)("-", (0, _gen.Identifier)(stateVars[stateVarIndex]), (0, _gen.Literal)(difference)); // Use the 'conditionNodes' to not cause state clashing issues
1275
+
1276
+ conditionNodes.forEach(conditionNode => {
1277
+ test = (0, _gen.LogicalExpression)("&&", conditionNode, test);
1278
+ });
1279
+ } // A 'flagged' label has addition 'flagKey' that gets switched before jumped to
1280
+
1281
+
1282
+ if (flaggedLabels[caseObject.label]) {
1283
+ isEligibleForOutlining = true;
1284
+ var {
1285
+ flagKey,
1286
+ flagValue
1287
+ } = flaggedLabels[caseObject.label];
1288
+ var alternateNum;
1289
+
1290
+ do {
1291
+ alternateNum = (0, _random.getRandomInteger)(-1000, 1000 + chunks.length);
1292
+ } while (caseSelection.has(alternateNum));
1293
+
1294
+ var alternate = (0, _gen.Literal)(alternateNum); // case FLAG ? <REAL> : <FAKE>:
1295
+
1296
+ test = (0, _gen.ConditionalExpression)(getControlMember(flagKey), flagValue ? test : alternate, !flagValue ? test : alternate);
1297
+ } // Outline this switch case test
1298
+
1299
+
1300
+ if (!this.isDebug && this.outlineExpressions && isEligibleForOutlining && (0, _random.chance)(75 - outlinesCreated - this.mangledExpressionsMade / 25)) {
1301
+ this.mangledExpressionsMade++; // Selected a random parent node (or this node) to insert this function in
1302
+
1303
+ var selectedControlNode = (0, _random.choice)(allControlNodes);
1304
+ var selectedControlProperties = selectedControlNode.$controlProperties;
1305
+ var selectedControlVar = selectedControlNode.$controlVar;
1306
+ var selectedControlGen = selectedControlNode.$controlGen;
1307
+ var fnKey = selectedControlGen.generate(); // Pass in the:
1308
+ // - controlVar for 'flagged labels' code check
1309
+ // - stateVars for 'complex test expressions'
1310
+ // (Check which identifiers are actually needed)
1311
+
1312
+ var argumentList = [],
1313
+ watchingFor = new Set([controlVar, ...stateVars]);
1314
+ (0, _traverse.walk)(test, [], (o, p) => {
1315
+ if (o.type === "Identifier" && watchingFor.has(o.name)) {
1316
+ watchingFor.delete(o.name);
1317
+ argumentList.push((0, _gen.Identifier)(o.name));
1318
+ }
1319
+ });
1320
+ selectedControlProperties.push((0, _gen.Property)((0, _gen.Literal)(fnKey), (0, _gen.FunctionExpression)(argumentList, [(0, _gen.ReturnStatement)(test)]), true)); // case control.a(control, s1, s2):
1321
+
1322
+ test = (0, _gen.CallExpression)(getControlMember(fnKey, selectedControlVar), (0, _insert.clone)(argumentList));
1323
+ } // One random case gets to be default
1324
+
1325
+
1326
+ if (!this.isDebug && i === defaultCaseIndex) test = null;
1327
+ var testArray = [test];
1328
+
1329
+ if (!this.isDebug && this.addFakeTest && (0, _random.chance)(50)) {
1330
+ // Add fake test
1331
+ // case <FAKE>:
1332
+ // case <REAL>:
1333
+ // case <FAKE>:
1334
+ var fakeTestCount = (0, _random.getRandomInteger)(1, 4);
1335
+
1336
+ for (var i = 0; i < fakeTestCount; i++) {
1337
+ // Create a fake test number that doesn't interfere with the actual states
1338
+ var fakeTestNum;
1339
+
1340
+ do {
1341
+ fakeTestNum = (0, _random.getRandomInteger)(1, 1000 + caseSelection.size);
1342
+ } while (caseSelection.has(fakeTestNum)); // Add this fake test
1343
+
1344
+
1345
+ testArray.push((0, _gen.Literal)(fakeTestNum));
1346
+ }
1347
+
1348
+ (0, _random.shuffle)(testArray);
1349
+ }
1350
+
1351
+ testArray.forEach((test, i) => {
1352
+ var body = i === testArray.length - 1 ? caseObject.body : [];
1353
+ switchCases.push((0, _gen.SwitchCase)(test, body));
1354
+ });
1355
+ }); // switch(state) { case ... }
1356
+
1357
+ var switchStatement = (0, _gen.SwitchStatement)(discriminant, switchCases);
1358
+ var declarations = []; // var state = START_STATE
753
1359
 
754
1360
  declarations.push(...stateVars.map((stateVar, i) => {
755
1361
  return (0, _gen.VariableDeclarator)(stateVar, (0, _gen.Literal)(initStateValues[i]));
756
- }));
757
- body.push((0, _gen.VariableDeclaration)(declarations), (0, _gen.WhileStatement)((0, _gen.BinaryExpression)("!=", (0, _insert.clone)(discriminant), (0, _gen.Literal)(endState)), [(0, _gen.LabeledStatement)(switchLabel, switchStatement)])); // mark this object for switch case obfuscation
1362
+ })); // var control = { strings, numbers, outlined functions, etc... }
1363
+
1364
+ var objectExpression = (0, _gen.ObjectExpression)(controlProperties);
1365
+ declarations.push((0, _gen.VariableDeclarator)(controlVar, objectExpression));
1366
+ objectBody.push( // Use individual variable declarations instead so Stack can apply
1367
+ ...declarations.map(declaration => (0, _gen.VariableDeclaration)(declaration, "var"))); // while (state != END_STATE) {...}
1368
+
1369
+ var whileTest = (0, _gen.BinaryExpression)("!=", (0, _insert.clone)(discriminant), (0, _gen.Literal)(endState));
1370
+ objectBody.push((0, _gen.WhileStatement)(whileTest, [(0, _gen.LabeledStatement)(switchLabel, switchStatement)])); // mark this object for switch case obfuscation
758
1371
 
759
1372
  switchStatement.$controlFlowFlattening = true;
760
1373
  };