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.
- package/.github/workflows/node.js.yml +2 -2
- package/CHANGELOG.md +55 -0
- package/README.md +346 -165
- package/dist/constants.js +6 -2
- package/dist/index.js +9 -21
- package/dist/obfuscator.js +19 -31
- package/dist/options.js +5 -5
- package/dist/order.js +1 -3
- package/dist/presets.js +6 -7
- package/dist/probability.js +2 -4
- package/dist/templates/bufferToString.js +13 -0
- package/dist/templates/crash.js +3 -3
- package/dist/templates/es5.js +18 -0
- package/dist/templates/functionLength.js +16 -0
- package/dist/transforms/calculator.js +77 -21
- package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +980 -367
- package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +4 -1
- package/dist/transforms/controlFlowFlattening/switchCaseObfuscation.js +25 -26
- package/dist/transforms/deadCode.js +33 -25
- package/dist/transforms/dispatcher.js +8 -4
- package/dist/transforms/es5/antiDestructuring.js +2 -0
- package/dist/transforms/es5/es5.js +31 -34
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +92 -58
- package/dist/transforms/finalizer.js +82 -0
- package/dist/transforms/flatten.js +229 -148
- package/dist/transforms/identifier/globalAnalysis.js +88 -0
- package/dist/transforms/identifier/globalConcealing.js +10 -83
- package/dist/transforms/identifier/movedDeclarations.js +35 -88
- package/dist/transforms/identifier/renameVariables.js +124 -59
- package/dist/transforms/identifier/variableAnalysis.js +58 -62
- package/dist/transforms/lock/lock.js +0 -37
- package/dist/transforms/minify.js +60 -57
- package/dist/transforms/opaquePredicates.js +1 -1
- package/dist/transforms/preparation/preparation.js +2 -2
- package/dist/transforms/preparation.js +231 -0
- package/dist/transforms/renameLabels.js +1 -1
- package/dist/transforms/rgf.js +139 -247
- package/dist/transforms/stack.js +128 -26
- package/dist/transforms/string/encoding.js +150 -179
- package/dist/transforms/string/stringCompression.js +14 -15
- package/dist/transforms/string/stringConcealing.js +25 -8
- package/dist/transforms/string/stringEncoding.js +13 -24
- package/dist/transforms/transform.js +12 -19
- package/dist/traverse.js +24 -10
- package/dist/util/gen.js +17 -1
- package/dist/util/identifiers.js +37 -3
- package/dist/util/insert.js +35 -4
- package/dist/util/random.js +15 -0
- package/docs/ControlFlowFlattening.md +595 -0
- package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
- package/{Integrity.md → docs/Integrity.md} +2 -2
- package/docs/RGF.md +419 -0
- package/package.json +5 -5
- package/src/constants.ts +3 -0
- package/src/index.ts +2 -2
- package/src/obfuscator.ts +19 -31
- package/src/options.ts +14 -103
- package/src/order.ts +1 -5
- package/src/presets.ts +6 -7
- package/src/probability.ts +2 -3
- package/src/templates/bufferToString.ts +68 -0
- package/src/templates/crash.ts +15 -19
- package/src/templates/es5.ts +131 -0
- package/src/templates/functionLength.ts +14 -0
- package/src/transforms/calculator.ts +122 -59
- package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +1583 -571
- package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +4 -1
- package/src/transforms/deadCode.ts +383 -26
- package/src/transforms/dispatcher.ts +9 -4
- package/src/transforms/es5/antiDestructuring.ts +2 -0
- package/src/transforms/es5/es5.ts +32 -77
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +133 -129
- package/src/transforms/{hexadecimalNumbers.ts → finalizer.ts} +29 -13
- package/src/transforms/flatten.ts +357 -300
- package/src/transforms/identifier/globalAnalysis.ts +85 -0
- package/src/transforms/identifier/globalConcealing.ts +14 -103
- package/src/transforms/identifier/movedDeclarations.ts +49 -102
- package/src/transforms/identifier/renameVariables.ts +149 -78
- package/src/transforms/identifier/variableAnalysis.ts +66 -73
- package/src/transforms/lock/lock.ts +1 -42
- package/src/transforms/minify.ts +91 -75
- package/src/transforms/opaquePredicates.ts +2 -2
- package/src/transforms/preparation.ts +238 -0
- package/src/transforms/renameLabels.ts +2 -2
- package/src/transforms/rgf.ts +213 -405
- package/src/transforms/stack.ts +156 -36
- package/src/transforms/string/encoding.ts +115 -212
- package/src/transforms/string/stringCompression.ts +27 -18
- package/src/transforms/string/stringConcealing.ts +39 -9
- package/src/transforms/string/stringEncoding.ts +18 -18
- package/src/transforms/transform.ts +21 -23
- package/src/traverse.ts +23 -4
- package/src/types.ts +2 -1
- package/src/util/gen.ts +28 -3
- package/src/util/identifiers.ts +43 -2
- package/src/util/insert.ts +38 -3
- package/src/util/random.ts +13 -0
- package/test/code/Cash.test.ts +1 -1
- package/test/code/Dynamic.test.ts +12 -10
- package/test/code/ES6.src.js +146 -0
- package/test/code/ES6.test.ts +28 -2
- package/test/index.test.ts +2 -1
- package/test/probability.test.ts +44 -0
- package/test/templates/template.test.ts +1 -1
- package/test/transforms/antiTooling.test.ts +22 -0
- package/test/transforms/calculator.test.ts +40 -0
- package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +702 -160
- package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +173 -0
- package/test/transforms/deadCode.test.ts +66 -15
- package/test/transforms/dispatcher.test.ts +20 -1
- package/test/transforms/es5/antiDestructuring.test.ts +16 -0
- package/test/transforms/flatten.test.ts +399 -86
- package/test/transforms/identifier/movedDeclarations.test.ts +63 -8
- package/test/transforms/identifier/renameVariables.test.ts +119 -0
- package/test/transforms/lock/antiDebug.test.ts +2 -2
- package/test/transforms/lock/lock.test.ts +1 -48
- package/test/transforms/minify.test.ts +104 -0
- package/test/transforms/preparation.test.ts +157 -0
- package/test/transforms/rgf.test.ts +261 -381
- package/test/transforms/stack.test.ts +143 -21
- package/test/transforms/string/stringCompression.test.ts +39 -0
- package/test/transforms/string/stringConcealing.test.ts +82 -0
- package/test/transforms/string/stringEncoding.test.ts +53 -2
- package/test/transforms/transform.test.ts +66 -0
- package/test/traverse.test.ts +139 -0
- package/test/util/identifiers.test.ts +113 -1
- package/test/util/insert.test.ts +57 -3
- package/src/transforms/controlFlowFlattening/choiceFlowObfuscation.ts +0 -87
- package/src/transforms/controlFlowFlattening/controlFlowObfuscation.ts +0 -203
- package/src/transforms/controlFlowFlattening/switchCaseObfuscation.ts +0 -130
- package/src/transforms/eval.ts +0 -89
- package/src/transforms/hideInitializingCode.ts +0 -432
- package/src/transforms/identifier/nameRecycling.ts +0 -280
- package/src/transforms/label.ts +0 -64
- package/src/transforms/preparation/nameConflicts.ts +0 -102
- package/src/transforms/preparation/preparation.ts +0 -176
- package/test/transforms/controlFlowFlattening/controlFlowObfuscation.test.ts +0 -101
- package/test/transforms/controlFlowFlattening/switchCaseObfuscation.test.ts +0 -120
- package/test/transforms/eval.test.ts +0 -131
- package/test/transforms/hideInitializingCode.test.ts +0 -336
- package/test/transforms/identifier/nameRecycling.test.ts +0 -205
- package/test/transforms/preparation/nameConflicts.test.ts +0 -52
- package/test/transforms/preparation/preparation.test.ts +0 -62
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { ok } from "assert";
|
|
2
|
-
import { compileJsSync } from "../../compiler";
|
|
3
2
|
import { ObfuscateOrder } from "../../order";
|
|
4
3
|
import { ComputeProbabilityMap } from "../../probability";
|
|
5
4
|
import Template from "../../templates/template";
|
|
6
|
-
import {
|
|
5
|
+
import { isBlock, walk } from "../../traverse";
|
|
7
6
|
import {
|
|
8
7
|
ArrayExpression,
|
|
9
|
-
ArrayPattern,
|
|
10
8
|
AssignmentExpression,
|
|
9
|
+
AssignmentPattern,
|
|
11
10
|
BinaryExpression,
|
|
12
11
|
BreakStatement,
|
|
13
12
|
CallExpression,
|
|
14
13
|
ConditionalExpression,
|
|
15
14
|
ExpressionStatement,
|
|
16
|
-
|
|
15
|
+
FunctionExpression,
|
|
17
16
|
Identifier,
|
|
18
17
|
IfStatement,
|
|
19
18
|
LabeledStatement,
|
|
20
19
|
Literal,
|
|
20
|
+
Location,
|
|
21
|
+
LogicalExpression,
|
|
21
22
|
MemberExpression,
|
|
22
23
|
Node,
|
|
23
24
|
ObjectExpression,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
SequenceExpression,
|
|
27
28
|
SwitchCase,
|
|
28
29
|
SwitchStatement,
|
|
30
|
+
UnaryExpression,
|
|
29
31
|
VariableDeclaration,
|
|
30
32
|
VariableDeclarator,
|
|
31
33
|
WhileStatement,
|
|
@@ -37,25 +39,35 @@ import {
|
|
|
37
39
|
import {
|
|
38
40
|
clone,
|
|
39
41
|
getBlockBody,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
isContext,
|
|
43
|
+
isForInitialize,
|
|
44
|
+
isFunction,
|
|
42
45
|
isVarContext,
|
|
43
46
|
} from "../../util/insert";
|
|
44
|
-
import { choice, getRandomInteger, shuffle } from "../../util/random";
|
|
47
|
+
import { chance, choice, getRandomInteger, shuffle } from "../../util/random";
|
|
45
48
|
import Transform from "../transform";
|
|
46
|
-
import ChoiceFlowObfuscation from "./choiceFlowObfuscation";
|
|
47
|
-
import ControlFlowObfuscation from "./controlFlowObfuscation";
|
|
48
49
|
import ExpressionObfuscation from "./expressionObfuscation";
|
|
49
|
-
import SwitchCaseObfuscation from "./switchCaseObfuscation";
|
|
50
50
|
import { isModuleSource } from "../string/stringConcealing";
|
|
51
|
+
import { reservedIdentifiers } from "../../constants";
|
|
52
|
+
import { isDirective } from "../../util/compare";
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
const flattenStructures = new Set([
|
|
53
55
|
"IfStatement",
|
|
54
56
|
"ForStatement",
|
|
55
57
|
"WhileStatement",
|
|
56
58
|
"DoWhileStatement",
|
|
57
59
|
]);
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A chunk represents a small segment of code
|
|
63
|
+
*/
|
|
64
|
+
interface Chunk {
|
|
65
|
+
label: string;
|
|
66
|
+
body: Node[];
|
|
67
|
+
|
|
68
|
+
impossible?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
/**
|
|
60
72
|
* Breaks functions into DAGs (Directed Acyclic Graphs)
|
|
61
73
|
*
|
|
@@ -70,204 +82,140 @@ var flattenStructures = new Set([
|
|
|
70
82
|
* - 3. The while loop continues until the the state variable is the end state.
|
|
71
83
|
*/
|
|
72
84
|
export default class ControlFlowFlattening extends Transform {
|
|
85
|
+
// in Debug mode, the output is much easier to read
|
|
73
86
|
isDebug = false;
|
|
87
|
+
flattenControlStructures = true; // Flatten if-statements, for-loops, etc
|
|
88
|
+
addToControlObject = true; // var control = { str1, num1 }
|
|
89
|
+
mangleNumberLiterals = true; // 50 => state + X
|
|
90
|
+
mangleBooleanLiterals = true; // true => state == X
|
|
91
|
+
mangleIdentifiers = true; // console => (state == X ? console : _)
|
|
92
|
+
outlineStatements = true; // Tries to outline entire chunks
|
|
93
|
+
outlineExpressions = true; // Tries to outline expressions found in chunks
|
|
94
|
+
addComplexTest = true; // case s != 49 && s - 10:
|
|
95
|
+
addFakeTest = true; // case 100: case 490: case 510: ...
|
|
96
|
+
addDeadCode = true; // add fakes chunks of code
|
|
97
|
+
addOpaquePredicates = true; // predicate ? REAL : FAKE
|
|
98
|
+
addFlaggedLabels = true; // s=NEXT_STATE,flag=true,break
|
|
99
|
+
|
|
100
|
+
// Limit amount of mangling
|
|
101
|
+
mangledExpressionsMade = 0;
|
|
102
|
+
|
|
103
|
+
// Amount of blocks changed by Control Flow Flattening
|
|
104
|
+
cffCount = 0;
|
|
74
105
|
|
|
75
106
|
constructor(o) {
|
|
76
107
|
super(o, ObfuscateOrder.ControlFlowFlattening);
|
|
77
108
|
|
|
78
109
|
if (!this.isDebug) {
|
|
79
110
|
this.before.push(new ExpressionObfuscation(o));
|
|
80
|
-
|
|
81
|
-
this.after.push(new ControlFlowObfuscation(o));
|
|
82
|
-
this.after.push(new SwitchCaseObfuscation(o));
|
|
83
111
|
} else {
|
|
84
112
|
console.warn("Debug mode enabled");
|
|
85
113
|
}
|
|
86
|
-
|
|
87
|
-
// this.after.push(new ChoiceFlowObfuscation(o));
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
match(object, parents) {
|
|
91
117
|
return (
|
|
92
118
|
isBlock(object) &&
|
|
93
|
-
(!parents[
|
|
94
|
-
(!parents[
|
|
119
|
+
(!parents[0] || !flattenStructures.has(parents[0].type)) &&
|
|
120
|
+
(!parents[1] || !flattenStructures.has(parents[1].type))
|
|
95
121
|
);
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
transform(object, parents) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
125
|
+
// Must be at least 3 statements or more
|
|
126
|
+
if (object.body.length < 3) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// No 'let'/'const' allowed (These won't work in Switch cases!)
|
|
130
|
+
if (containsLexicallyBoundVariables(object, parents)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Check user's threshold setting
|
|
134
|
+
if (!ComputeProbabilityMap(this.options.controlFlowFlattening, (x) => x)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
106
137
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
138
|
+
var objectBody = getBlockBody(object.body);
|
|
139
|
+
if (!objectBody.length) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
112
142
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
// Purely for naming purposes
|
|
144
|
+
var cffIndex = this.cffCount++;
|
|
145
|
+
|
|
146
|
+
// The controlVar is an object containing:
|
|
147
|
+
// - Strings found in chunks
|
|
148
|
+
// - Numbers found in chunks
|
|
149
|
+
// - Helper functions to adjust the state
|
|
150
|
+
// - Outlined expressions changed into functions
|
|
151
|
+
var controlVar = this.getPlaceholder() + `_c${cffIndex}_CONTROL`;
|
|
152
|
+
var controlProperties: Node[] = [];
|
|
153
|
+
var controlConstantMap = new Map<string | number, { key: string }>();
|
|
154
|
+
var controlGen = this.getGenerator("mangled");
|
|
155
|
+
var controlTestKey = controlGen.generate();
|
|
156
|
+
|
|
157
|
+
// This 'controlVar' can be accessed by child-nodes
|
|
158
|
+
object.$controlVar = controlVar;
|
|
159
|
+
object.$controlConstantMap = controlConstantMap;
|
|
160
|
+
object.$controlProperties = controlProperties;
|
|
161
|
+
object.$controlGen = controlGen;
|
|
119
162
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
var illegalFnNames = new Set<string>();
|
|
163
|
+
return () => {
|
|
164
|
+
ok(Array.isArray(objectBody));
|
|
123
165
|
|
|
124
|
-
|
|
125
|
-
* The variable names
|
|
126
|
-
*
|
|
127
|
-
* index -> var name
|
|
128
|
-
*/
|
|
166
|
+
// The state variable names (and quantity)
|
|
129
167
|
var stateVars = Array(this.isDebug ? 1 : getRandomInteger(2, 5))
|
|
130
168
|
.fill(0)
|
|
131
|
-
.map(() => this.getPlaceholder());
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
} else if (info.spec.isDefined) {
|
|
166
|
-
if (info.isFunctionDeclaration) {
|
|
167
|
-
if (!functionDeclarations.has(p[0])) {
|
|
168
|
-
fnNames.delete(o.name);
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
fnNames.delete(o.name);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (!info.spec.isDefined) {
|
|
176
|
-
var b = getBlock(o, p);
|
|
177
|
-
if (b !== object || !p[0] || p[0].type !== "CallExpression") {
|
|
178
|
-
illegalFnNames.add(o.name);
|
|
179
|
-
} else {
|
|
180
|
-
var isExtractable = false;
|
|
181
|
-
if (p[1]) {
|
|
182
|
-
if (
|
|
183
|
-
p[1].type == "ExpressionStatement" &&
|
|
184
|
-
p[1].expression == p[0] &&
|
|
185
|
-
p[2] == object.body
|
|
186
|
-
) {
|
|
187
|
-
isExtractable = true;
|
|
188
|
-
p[1].$callExpression = "ExpressionStatement";
|
|
189
|
-
p[1].$fnName = o.name;
|
|
190
|
-
} else if (
|
|
191
|
-
p[1].type == "VariableDeclarator" &&
|
|
192
|
-
p[1].init == p[0] &&
|
|
193
|
-
p[2].length === 1 &&
|
|
194
|
-
p[4] == object.body
|
|
195
|
-
) {
|
|
196
|
-
isExtractable = true;
|
|
197
|
-
p[3].$callExpression = "VariableDeclarator";
|
|
198
|
-
p[3].$fnName = o.name;
|
|
199
|
-
} else if (
|
|
200
|
-
p[1].type == "AssignmentExpression" &&
|
|
201
|
-
p[1].operator == "=" &&
|
|
202
|
-
p[1].right === p[0] &&
|
|
203
|
-
p[2] &&
|
|
204
|
-
p[2].type == "ExpressionStatement" &&
|
|
205
|
-
p[3] == object.body
|
|
206
|
-
) {
|
|
207
|
-
isExtractable = true;
|
|
208
|
-
p[2].$callExpression = "AssignmentExpression";
|
|
209
|
-
p[2].$fnName = o.name;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (!isExtractable) {
|
|
214
|
-
illegalFnNames.add(o.name);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// redefined function,
|
|
222
|
-
if (functionDeclarations.size !== fnNames.size) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
illegalFnNames.forEach((illegal) => {
|
|
227
|
-
fnNames.delete(illegal);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
var importDeclarations = [];
|
|
231
|
-
for (var stmt of body) {
|
|
232
|
-
if (stmt.type === "ImportDeclaration") {
|
|
233
|
-
importDeclarations.push(stmt);
|
|
169
|
+
.map((_, i) => this.getPlaceholder() + `_c${cffIndex}_S${i}`);
|
|
170
|
+
|
|
171
|
+
// How often should chunks be split up?
|
|
172
|
+
// Percentage between 10% and 90% based on block size
|
|
173
|
+
var splitPercent = Math.max(10, 90 - objectBody.length * 5);
|
|
174
|
+
|
|
175
|
+
// Find functions and import declarations
|
|
176
|
+
var importDeclarations: Node[] = [];
|
|
177
|
+
var functionDeclarationNames = new Set<string>();
|
|
178
|
+
var functionDeclarationValues = new Map<string, Node>();
|
|
179
|
+
|
|
180
|
+
// Find all parent control-nodes
|
|
181
|
+
const allControlNodes = [object];
|
|
182
|
+
parents
|
|
183
|
+
.filter((x) => x.$controlVar)
|
|
184
|
+
.forEach((node) => allControlNodes.push(node));
|
|
185
|
+
|
|
186
|
+
const addControlMapConstant = (literalValue: number | string) => {
|
|
187
|
+
// Choose a random control node to add to
|
|
188
|
+
var controlNode = choice(allControlNodes);
|
|
189
|
+
var selectedControlVar = controlNode.$controlVar;
|
|
190
|
+
var selectedControlConstantMap = controlNode.$controlConstantMap;
|
|
191
|
+
var selectedControlProperties = controlNode.$controlProperties;
|
|
192
|
+
|
|
193
|
+
var key = selectedControlConstantMap.get(literalValue)?.key;
|
|
194
|
+
|
|
195
|
+
// Not found, create
|
|
196
|
+
if (!key) {
|
|
197
|
+
key = controlNode.$controlGen.generate();
|
|
198
|
+
selectedControlConstantMap.set(literalValue, { key: key });
|
|
199
|
+
|
|
200
|
+
selectedControlProperties.push(
|
|
201
|
+
Property(Literal(key), Literal(literalValue), false)
|
|
202
|
+
);
|
|
234
203
|
}
|
|
235
|
-
}
|
|
236
204
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
fraction /= Math.max(1.2, body.length - 18);
|
|
240
|
-
}
|
|
241
|
-
fraction = Math.min(0.1, fraction);
|
|
242
|
-
if (isNaN(fraction) || !isFinite(fraction)) {
|
|
243
|
-
fraction = 0.5;
|
|
244
|
-
}
|
|
205
|
+
return getControlMember(key, selectedControlVar);
|
|
206
|
+
};
|
|
245
207
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
var stringBankVar = this.getPlaceholder();
|
|
250
|
-
var stringBank: { [strValue: string]: string } = Object.create(null);
|
|
251
|
-
var stringBankByLabels: { [label: string]: Set<string> } =
|
|
252
|
-
Object.create(null);
|
|
253
|
-
let stringBankGen = this.getGenerator();
|
|
254
|
-
|
|
255
|
-
var needsTestVar = false;
|
|
256
|
-
var needsResultAndArgVar = false;
|
|
257
|
-
var needsStringBankVar = false;
|
|
258
|
-
var fnToLabel: { [fnName: string]: string } = Object.create(null);
|
|
259
|
-
|
|
260
|
-
fnNames.forEach((fnName) => {
|
|
261
|
-
fnToLabel[fnName] = this.getPlaceholder();
|
|
262
|
-
});
|
|
208
|
+
// Helper function to easily make control object accessors
|
|
209
|
+
const getControlMember = (key: string, objectName = controlVar) =>
|
|
210
|
+
MemberExpression(Identifier(objectName), Literal(key), true);
|
|
263
211
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
var chunks = [];
|
|
269
|
-
var currentBody = [];
|
|
212
|
+
// This function recursively calls itself to flatten and split up code into 'chunks'
|
|
213
|
+
const flattenBody = (body: Node[], startingLabel: string): Chunk[] => {
|
|
214
|
+
var chunks: Chunk[] = [];
|
|
215
|
+
var currentBody: Node[] = [];
|
|
270
216
|
var currentLabel = startingLabel;
|
|
217
|
+
|
|
218
|
+
// This function ends the current chunk being created ('currentBody')
|
|
271
219
|
const finishCurrentChunk = (
|
|
272
220
|
pointingLabel?: string,
|
|
273
221
|
newLabel?: string,
|
|
@@ -289,36 +237,65 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
289
237
|
body: [...currentBody],
|
|
290
238
|
});
|
|
291
239
|
|
|
240
|
+
// Random chance of this chunk being flagged (First label cannot be flagged)
|
|
241
|
+
if (
|
|
242
|
+
!this.isDebug &&
|
|
243
|
+
this.addFlaggedLabels &&
|
|
244
|
+
currentLabel !== startLabel &&
|
|
245
|
+
chance(25)
|
|
246
|
+
) {
|
|
247
|
+
flaggedLabels[currentLabel] = {
|
|
248
|
+
flagKey: controlGen.generate(),
|
|
249
|
+
flagValue: choice([true, false]),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
292
253
|
walk(currentBody, [], (o, p) => {
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
254
|
+
if (o.type === "Literal" && !this.isDebug) {
|
|
255
|
+
// Add strings to the control object
|
|
256
|
+
if (
|
|
257
|
+
this.addToControlObject &&
|
|
258
|
+
typeof o.value === "string" &&
|
|
259
|
+
o.value.length >= 3 &&
|
|
260
|
+
o.value.length <= 100 &&
|
|
261
|
+
!isModuleSource(o, p) &&
|
|
262
|
+
!isDirective(o, p) &&
|
|
263
|
+
!o.regex &&
|
|
264
|
+
chance(
|
|
265
|
+
50 -
|
|
266
|
+
controlConstantMap.size -
|
|
267
|
+
this.mangledExpressionsMade / 100
|
|
268
|
+
)
|
|
269
|
+
) {
|
|
270
|
+
return () => {
|
|
271
|
+
this.replaceIdentifierOrLiteral(
|
|
272
|
+
o,
|
|
273
|
+
addControlMapConstant(o.value),
|
|
274
|
+
p
|
|
275
|
+
);
|
|
276
|
+
};
|
|
303
277
|
}
|
|
304
278
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
279
|
+
// Add numbers to the control object
|
|
280
|
+
if (
|
|
281
|
+
this.addToControlObject &&
|
|
282
|
+
typeof o.value === "number" &&
|
|
283
|
+
Math.floor(o.value) === o.value &&
|
|
284
|
+
Math.abs(o.value) < 100_000 &&
|
|
285
|
+
chance(
|
|
286
|
+
50 -
|
|
287
|
+
controlConstantMap.size -
|
|
288
|
+
this.mangledExpressionsMade / 100
|
|
289
|
+
)
|
|
290
|
+
) {
|
|
291
|
+
return () => {
|
|
292
|
+
this.replaceIdentifierOrLiteral(
|
|
293
|
+
o,
|
|
294
|
+
addControlMapConstant(o.value),
|
|
295
|
+
p
|
|
296
|
+
);
|
|
297
|
+
};
|
|
309
298
|
}
|
|
310
|
-
|
|
311
|
-
return () => {
|
|
312
|
-
this.replaceIdentifierOrLiteral(
|
|
313
|
-
o,
|
|
314
|
-
MemberExpression(
|
|
315
|
-
Identifier(stringBankVar),
|
|
316
|
-
Literal(stringBank[o.value]),
|
|
317
|
-
true
|
|
318
|
-
),
|
|
319
|
-
p
|
|
320
|
-
);
|
|
321
|
-
};
|
|
322
299
|
}
|
|
323
300
|
});
|
|
324
301
|
|
|
@@ -326,63 +303,52 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
326
303
|
currentBody = [];
|
|
327
304
|
};
|
|
328
305
|
|
|
329
|
-
body
|
|
330
|
-
|
|
331
|
-
functionDeclarations.has(stmt) ||
|
|
332
|
-
stmt.type === "ImportDeclaration"
|
|
333
|
-
) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
306
|
+
if (body !== objectBody) {
|
|
307
|
+
// This code is nested. Move function declarations up
|
|
336
308
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
309
|
+
var newBody = [];
|
|
310
|
+
for (var stmt of body) {
|
|
311
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
312
|
+
newBody.unshift(stmt);
|
|
313
|
+
} else {
|
|
314
|
+
newBody.push(stmt);
|
|
315
|
+
}
|
|
342
316
|
}
|
|
343
317
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
var args = [];
|
|
347
|
-
|
|
348
|
-
switch (stmt.$callExpression) {
|
|
349
|
-
// var a = fn();
|
|
350
|
-
case "VariableDeclarator":
|
|
351
|
-
args = stmt.declarations[0].init.arguments;
|
|
352
|
-
stmt.declarations[0].init = Identifier(resultVar);
|
|
353
|
-
break;
|
|
354
|
-
|
|
355
|
-
// fn();
|
|
356
|
-
case "ExpressionStatement":
|
|
357
|
-
args = stmt.expression.arguments;
|
|
358
|
-
stmt.expression = Identifier("undefined");
|
|
359
|
-
break;
|
|
360
|
-
|
|
361
|
-
// a = fn();
|
|
362
|
-
case "AssignmentExpression":
|
|
363
|
-
args = stmt.expression.right.arguments;
|
|
364
|
-
stmt.expression.right = Identifier(resultVar);
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
318
|
+
body = newBody;
|
|
319
|
+
}
|
|
367
320
|
|
|
368
|
-
|
|
321
|
+
body.forEach((stmt, i) => {
|
|
322
|
+
if (stmt.type === "ImportDeclaration") {
|
|
323
|
+
// The 'importDeclarations' hold statements that are required to be left untouched at the top of the block
|
|
324
|
+
importDeclarations.push(stmt);
|
|
325
|
+
return;
|
|
326
|
+
} else if (stmt.type === "FunctionDeclaration") {
|
|
327
|
+
var functionName = stmt.id.name;
|
|
369
328
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
])
|
|
329
|
+
stmt.type = "FunctionExpression";
|
|
330
|
+
stmt.id = null;
|
|
331
|
+
|
|
332
|
+
functionDeclarationNames.add(functionName);
|
|
333
|
+
if (objectBody === body) {
|
|
334
|
+
functionDeclarationValues.set(functionName, stmt);
|
|
335
|
+
return;
|
|
336
|
+
} else {
|
|
337
|
+
currentBody.push(
|
|
338
|
+
ExpressionStatement(
|
|
339
|
+
AssignmentExpression("=", Identifier(functionName), stmt)
|
|
382
340
|
)
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return;
|
|
345
|
+
} else if (stmt.directive) {
|
|
346
|
+
if (objectBody === body) {
|
|
347
|
+
importDeclarations.push(stmt);
|
|
348
|
+
} else {
|
|
349
|
+
this.error(new Error("Unimplemented directive support."));
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
386
352
|
}
|
|
387
353
|
|
|
388
354
|
if (stmt.type == "GotoStatement" && i !== body.length - 1) {
|
|
@@ -390,9 +356,13 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
390
356
|
return;
|
|
391
357
|
}
|
|
392
358
|
|
|
393
|
-
|
|
359
|
+
// The Preparation transform adds labels to every Control-Flow node
|
|
360
|
+
if (
|
|
361
|
+
this.flattenControlStructures &&
|
|
362
|
+
stmt.type == "LabeledStatement"
|
|
363
|
+
) {
|
|
394
364
|
var lbl = stmt.label.name;
|
|
395
|
-
var control = stmt.body;
|
|
365
|
+
var control: Node = stmt.body;
|
|
396
366
|
|
|
397
367
|
var isSwitchStatement = control.type === "SwitchStatement";
|
|
398
368
|
|
|
@@ -404,22 +374,7 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
404
374
|
control.body.type == "BlockStatement")
|
|
405
375
|
) {
|
|
406
376
|
if (isSwitchStatement) {
|
|
407
|
-
if (
|
|
408
|
-
control.cases.length == 0 || // at least 1 case
|
|
409
|
-
control.cases.find(
|
|
410
|
-
(x) =>
|
|
411
|
-
!x.test || // cant be default case
|
|
412
|
-
!x.consequent.length || // must have body
|
|
413
|
-
x.consequent.findIndex(
|
|
414
|
-
(node) => node.type == "BreakStatement"
|
|
415
|
-
) !==
|
|
416
|
-
x.consequent.length - 1 || // break statement must be at the end
|
|
417
|
-
x.consequent[x.consequent.length - 1].type !== // must end with break statement
|
|
418
|
-
"BreakStatement" ||
|
|
419
|
-
!x.consequent[x.consequent.length - 1].label || // must be labeled and correct
|
|
420
|
-
x.consequent[x.consequent.length - 1].label.name != lbl
|
|
421
|
-
)
|
|
422
|
-
) {
|
|
377
|
+
if (control.cases.length == 0) {
|
|
423
378
|
currentBody.push(stmt);
|
|
424
379
|
return;
|
|
425
380
|
}
|
|
@@ -435,12 +390,31 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
435
390
|
var possible = true;
|
|
436
391
|
var toReplace = [];
|
|
437
392
|
|
|
438
|
-
|
|
393
|
+
// Find all break; and continue; statements and change them into 'GotoStatement's
|
|
394
|
+
walk(control.body || control.cases, [], (o, p) => {
|
|
439
395
|
if (
|
|
440
|
-
o.type
|
|
441
|
-
|
|
396
|
+
o.type === "BreakStatement" ||
|
|
397
|
+
o.type === "ContinueStatement"
|
|
442
398
|
) {
|
|
443
|
-
|
|
399
|
+
var allowedLabels = new Set(
|
|
400
|
+
p
|
|
401
|
+
.filter(
|
|
402
|
+
(x) =>
|
|
403
|
+
x.type === "LabeledStatement" &&
|
|
404
|
+
x.body.type === "SwitchStatement"
|
|
405
|
+
)
|
|
406
|
+
.map((x) => x.label.name)
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
var isUnsupportedContinue =
|
|
410
|
+
!supportContinueStatement && o.type === "ContinueStatement";
|
|
411
|
+
|
|
412
|
+
var isInvalidLabel =
|
|
413
|
+
!o.label ||
|
|
414
|
+
(o.label.name !== lbl && !allowedLabels.has(o.label.name));
|
|
415
|
+
|
|
416
|
+
// This seems like the best solution:
|
|
417
|
+
if (isUnsupportedContinue || isInvalidLabel) {
|
|
444
418
|
possible = false;
|
|
445
419
|
return "EXIT";
|
|
446
420
|
}
|
|
@@ -465,58 +439,114 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
465
439
|
toReplace.forEach((v) => this.replace(v[0], v[1]));
|
|
466
440
|
|
|
467
441
|
if (isSwitchStatement) {
|
|
468
|
-
var
|
|
442
|
+
var switchDiscriminantName = this.getPlaceholder() + "_switchD"; // Stores the value of the discriminant
|
|
443
|
+
var switchTestName = this.getPlaceholder() + "_switchT"; // Set to true when a Switch case is matched
|
|
469
444
|
|
|
470
445
|
currentBody.push(
|
|
471
446
|
VariableDeclaration(
|
|
472
|
-
VariableDeclarator(
|
|
447
|
+
VariableDeclarator(
|
|
448
|
+
switchDiscriminantName,
|
|
449
|
+
control.discriminant
|
|
450
|
+
)
|
|
473
451
|
)
|
|
474
452
|
);
|
|
475
453
|
|
|
476
|
-
|
|
477
|
-
|
|
454
|
+
currentBody.push(
|
|
455
|
+
VariableDeclaration(
|
|
456
|
+
VariableDeclarator(switchTestName, Literal(false))
|
|
457
|
+
)
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// case labels are:
|
|
461
|
+
// `${caseLabelPrefix}_test_${index}`
|
|
462
|
+
// `${caseLabelPrefix}_entry_${index}`
|
|
463
|
+
var caseLabelPrefix = this.getPlaceholder();
|
|
464
|
+
var defaultCaseIndex = control.cases.findIndex(
|
|
465
|
+
(x) => x.test === null
|
|
466
|
+
);
|
|
467
|
+
|
|
478
468
|
control.cases.forEach((switchCase, i) => {
|
|
479
|
-
var
|
|
469
|
+
var testPath = caseLabelPrefix + "_test_" + i;
|
|
470
|
+
var entryPath = caseLabelPrefix + "_entry_" + i;
|
|
471
|
+
var nextEntryPath =
|
|
472
|
+
i === control.cases.length - 1 // Last path goes to afterPath
|
|
473
|
+
? afterPath // Else go to next entry path (fall-through behavior)
|
|
474
|
+
: caseLabelPrefix + "_entry_" + (i + 1);
|
|
475
|
+
var nextTestPath =
|
|
476
|
+
i === control.cases.length - 1
|
|
477
|
+
? afterPath
|
|
478
|
+
: caseLabelPrefix + "_test_" + (i + 1);
|
|
479
|
+
|
|
480
|
+
finishCurrentChunk(testPath, testPath, i == 0);
|
|
481
|
+
|
|
482
|
+
if (switchCase.test) {
|
|
483
|
+
// Check the case condition and goto statement
|
|
484
|
+
currentBody.push(
|
|
485
|
+
IfStatement(
|
|
486
|
+
BinaryExpression(
|
|
487
|
+
"===",
|
|
488
|
+
Identifier(switchDiscriminantName),
|
|
489
|
+
switchCase.test
|
|
490
|
+
),
|
|
491
|
+
[
|
|
492
|
+
ExpressionStatement(
|
|
493
|
+
AssignmentExpression(
|
|
494
|
+
"=",
|
|
495
|
+
Identifier(switchTestName),
|
|
496
|
+
Literal(true)
|
|
497
|
+
)
|
|
498
|
+
),
|
|
499
|
+
{
|
|
500
|
+
type: "GotoStatement",
|
|
501
|
+
label: entryPath,
|
|
502
|
+
},
|
|
503
|
+
]
|
|
504
|
+
)
|
|
505
|
+
);
|
|
506
|
+
} else {
|
|
507
|
+
// Default case: No test needed.
|
|
508
|
+
}
|
|
480
509
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
510
|
+
// If default case, on last test, if no case was matched, goto default case
|
|
511
|
+
if (
|
|
512
|
+
i === control.cases.length - 1 &&
|
|
513
|
+
defaultCaseIndex !== -1
|
|
514
|
+
) {
|
|
515
|
+
currentBody.push(
|
|
516
|
+
IfStatement(
|
|
517
|
+
UnaryExpression("!", Identifier(switchTestName)),
|
|
518
|
+
[
|
|
519
|
+
{
|
|
520
|
+
type: "GotoStatement",
|
|
521
|
+
label:
|
|
522
|
+
caseLabelPrefix + "_entry_" + defaultCaseIndex,
|
|
523
|
+
},
|
|
524
|
+
]
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Jump to next test
|
|
530
|
+
currentBody.push({
|
|
531
|
+
type: "GotoStatement",
|
|
532
|
+
label: nextTestPath,
|
|
533
|
+
});
|
|
496
534
|
|
|
497
535
|
chunks.push(
|
|
498
536
|
...flattenBody(
|
|
499
537
|
[
|
|
500
|
-
...switchCase.consequent
|
|
501
|
-
0,
|
|
502
|
-
switchCase.consequent.length - 1
|
|
503
|
-
),
|
|
538
|
+
...switchCase.consequent,
|
|
504
539
|
{
|
|
505
540
|
type: "GotoStatement",
|
|
506
|
-
label:
|
|
541
|
+
label: nextEntryPath,
|
|
507
542
|
},
|
|
508
543
|
],
|
|
509
544
|
entryPath
|
|
510
545
|
)
|
|
511
546
|
);
|
|
512
|
-
|
|
513
|
-
if (i === control.cases.length - 1) {
|
|
514
|
-
} else {
|
|
515
|
-
finishCurrentChunk();
|
|
516
|
-
}
|
|
517
547
|
});
|
|
518
548
|
|
|
519
|
-
finishCurrentChunk(afterPath, afterPath);
|
|
549
|
+
finishCurrentChunk(afterPath, afterPath, false);
|
|
520
550
|
return;
|
|
521
551
|
} else if (isLoop) {
|
|
522
552
|
var isPostTest = control.type == "DoWhileStatement";
|
|
@@ -537,18 +567,16 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
537
567
|
ExpressionStatement(
|
|
538
568
|
AssignmentExpression(
|
|
539
569
|
"=",
|
|
540
|
-
|
|
570
|
+
getControlMember(controlTestKey),
|
|
541
571
|
control.test || Literal(true)
|
|
542
572
|
)
|
|
543
573
|
)
|
|
544
574
|
);
|
|
545
575
|
|
|
546
|
-
needsTestVar = true;
|
|
547
|
-
|
|
548
576
|
finishCurrentChunk();
|
|
549
577
|
|
|
550
578
|
currentBody.push(
|
|
551
|
-
IfStatement(
|
|
579
|
+
IfStatement(getControlMember(controlTestKey), [
|
|
552
580
|
{
|
|
553
581
|
type: "GotoStatement",
|
|
554
582
|
label: bodyPath,
|
|
@@ -586,6 +614,7 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
586
614
|
}
|
|
587
615
|
|
|
588
616
|
if (
|
|
617
|
+
this.flattenControlStructures &&
|
|
589
618
|
stmt.type == "IfStatement" &&
|
|
590
619
|
stmt.consequent.type == "BlockStatement" &&
|
|
591
620
|
(!stmt.alternate || stmt.alternate.type == "BlockStatement")
|
|
@@ -594,12 +623,14 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
594
623
|
|
|
595
624
|
currentBody.push(
|
|
596
625
|
ExpressionStatement(
|
|
597
|
-
AssignmentExpression(
|
|
626
|
+
AssignmentExpression(
|
|
627
|
+
"=",
|
|
628
|
+
getControlMember(controlTestKey),
|
|
629
|
+
stmt.test
|
|
630
|
+
)
|
|
598
631
|
)
|
|
599
632
|
);
|
|
600
633
|
|
|
601
|
-
needsTestVar = true;
|
|
602
|
-
|
|
603
634
|
finishCurrentChunk();
|
|
604
635
|
|
|
605
636
|
var hasAlternate = !!stmt.alternate;
|
|
@@ -610,7 +641,7 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
610
641
|
var afterPath = this.getPlaceholder();
|
|
611
642
|
|
|
612
643
|
currentBody.push(
|
|
613
|
-
IfStatement(
|
|
644
|
+
IfStatement(getControlMember(controlTestKey), [
|
|
614
645
|
{
|
|
615
646
|
type: "GotoStatement",
|
|
616
647
|
label: yesPath,
|
|
@@ -653,7 +684,7 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
653
684
|
return;
|
|
654
685
|
}
|
|
655
686
|
|
|
656
|
-
if (!currentBody.length ||
|
|
687
|
+
if (!currentBody.length || !chance(splitPercent)) {
|
|
657
688
|
currentBody.push(stmt);
|
|
658
689
|
} else {
|
|
659
690
|
// Start new chunk
|
|
@@ -668,105 +699,123 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
668
699
|
return chunks;
|
|
669
700
|
};
|
|
670
701
|
|
|
671
|
-
|
|
702
|
+
/**
|
|
703
|
+
* Executable code segments are broken down into `chunks` typically 1-3 statements each
|
|
704
|
+
*
|
|
705
|
+
* Chunked Code has a special `GotoStatement` node that get processed later on
|
|
706
|
+
* This allows more complex control structures like `IfStatement`s and `ForStatement`s to be converted into basic
|
|
707
|
+
* conditional jumps and flattened in the switch body
|
|
708
|
+
*
|
|
709
|
+
* IfStatement would be converted like this:
|
|
710
|
+
*
|
|
711
|
+
* MAIN:
|
|
712
|
+
* if ( TEST ) {
|
|
713
|
+
* GOTO consequent_label;
|
|
714
|
+
* } else? {
|
|
715
|
+
* GOTO alternate_label;
|
|
716
|
+
* }
|
|
717
|
+
* GOTO NEXT_CHUNK;
|
|
718
|
+
*/
|
|
719
|
+
const chunks: Chunk[] = [];
|
|
720
|
+
|
|
721
|
+
// Flagged labels have addition code protecting the control state
|
|
722
|
+
const flaggedLabels: {
|
|
723
|
+
[label: string]: { flagKey: string; flagValue: boolean };
|
|
724
|
+
} = Object.create(null);
|
|
672
725
|
|
|
673
726
|
/**
|
|
674
727
|
* label: switch(a+b+c){...break label...}
|
|
675
728
|
*/
|
|
676
|
-
|
|
729
|
+
const switchLabel = this.getPlaceholder();
|
|
677
730
|
|
|
678
|
-
|
|
679
|
-
if (node.id && fnNames.has(node.id.name)) {
|
|
680
|
-
var exitStateName = this.getPlaceholder();
|
|
681
|
-
var argumentsName = this.getPlaceholder();
|
|
731
|
+
const startLabel = this.getPlaceholder();
|
|
682
732
|
|
|
683
|
-
|
|
733
|
+
chunks.push(...flattenBody(objectBody, startLabel));
|
|
734
|
+
chunks[chunks.length - 1].body.push({
|
|
735
|
+
type: "GotoStatement",
|
|
736
|
+
label: "END_LABEL",
|
|
737
|
+
});
|
|
738
|
+
chunks.push({
|
|
739
|
+
label: "END_LABEL",
|
|
740
|
+
body: [],
|
|
741
|
+
});
|
|
684
742
|
|
|
685
|
-
|
|
743
|
+
const endLabel = chunks[Object.keys(chunks).length - 1].label;
|
|
744
|
+
|
|
745
|
+
if (!this.isDebug && this.addDeadCode) {
|
|
746
|
+
// DEAD CODE 1/3: Add fake chunks that are never reached
|
|
747
|
+
var fakeChunkCount = getRandomInteger(1, 5);
|
|
748
|
+
for (var i = 0; i < fakeChunkCount; i++) {
|
|
749
|
+
// These chunks just jump somewhere random, they are never executed
|
|
750
|
+
// so it could contain any code
|
|
751
|
+
var fakeChunkBody = [
|
|
752
|
+
// This a fake assignment expression
|
|
753
|
+
ExpressionStatement(
|
|
754
|
+
AssignmentExpression(
|
|
755
|
+
"=",
|
|
756
|
+
Identifier(choice(stateVars)),
|
|
757
|
+
Literal(getRandomInteger(-150, 150))
|
|
758
|
+
)
|
|
759
|
+
),
|
|
686
760
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
AssignmentExpression(
|
|
693
|
-
"=",
|
|
694
|
-
ArrayPattern(stateVars.map(Identifier)),
|
|
695
|
-
Identifier(exitStateName)
|
|
696
|
-
),
|
|
697
|
-
AssignmentExpression(
|
|
698
|
-
"=",
|
|
699
|
-
Identifier(resultVar),
|
|
700
|
-
o.argument || Identifier("undefined")
|
|
701
|
-
),
|
|
702
|
-
]);
|
|
761
|
+
{
|
|
762
|
+
type: "GotoStatement",
|
|
763
|
+
label: choice(chunks).label,
|
|
764
|
+
},
|
|
765
|
+
];
|
|
703
766
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
767
|
+
chunks.push({
|
|
768
|
+
label: this.getPlaceholder(),
|
|
769
|
+
body: fakeChunkBody,
|
|
770
|
+
impossible: true,
|
|
708
771
|
});
|
|
772
|
+
}
|
|
709
773
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
Identifier(argumentsName),
|
|
715
|
-
]),
|
|
716
|
-
Identifier(argVar)
|
|
717
|
-
),
|
|
718
|
-
];
|
|
774
|
+
// DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators
|
|
775
|
+
chunks.forEach((chunk) => {
|
|
776
|
+
if (chance(25)) {
|
|
777
|
+
var randomLabel = choice(chunks).label;
|
|
719
778
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
779
|
+
// The `false` literal will be mangled
|
|
780
|
+
chunk.body.unshift(
|
|
781
|
+
IfStatement(Literal(false), [
|
|
782
|
+
{
|
|
783
|
+
type: "GotoStatement",
|
|
784
|
+
label: randomLabel,
|
|
785
|
+
impossible: true,
|
|
786
|
+
},
|
|
787
|
+
])
|
|
726
788
|
);
|
|
727
789
|
}
|
|
790
|
+
});
|
|
728
791
|
|
|
729
|
-
|
|
792
|
+
// DEAD CODE 3/3: Clone chunks but these chunks are never ran
|
|
793
|
+
var cloneChunkCount = getRandomInteger(1, 5);
|
|
794
|
+
for (var i = 0; i < cloneChunkCount; i++) {
|
|
795
|
+
var randomChunk = choice(chunks);
|
|
796
|
+
var clonedChunk = {
|
|
797
|
+
body: clone(randomChunk.body),
|
|
798
|
+
label: this.getPlaceholder(),
|
|
799
|
+
impossible: true,
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Don't double define functions
|
|
803
|
+
var hasDeclaration = clonedChunk.body.find((stmt) => {
|
|
804
|
+
return (
|
|
805
|
+
stmt.type === "FunctionDeclaration" ||
|
|
806
|
+
stmt.type === "ClassDeclaration"
|
|
807
|
+
);
|
|
808
|
+
});
|
|
730
809
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
FunctionDeclaration(
|
|
735
|
-
innerName,
|
|
736
|
-
[],
|
|
737
|
-
[VariableDeclaration(declarations), ...node.body.body]
|
|
738
|
-
),
|
|
739
|
-
this.objectAssign(
|
|
740
|
-
ExpressionStatement(
|
|
741
|
-
CallExpression(Identifier(innerName), [])
|
|
742
|
-
),
|
|
743
|
-
{
|
|
744
|
-
$exit: true,
|
|
745
|
-
} as any
|
|
746
|
-
),
|
|
747
|
-
],
|
|
748
|
-
fnToLabel[node.id.name]
|
|
749
|
-
)
|
|
750
|
-
);
|
|
810
|
+
if (!hasDeclaration) {
|
|
811
|
+
chunks.unshift(clonedChunk);
|
|
812
|
+
}
|
|
751
813
|
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
var startLabel = this.getPlaceholder();
|
|
755
|
-
|
|
756
|
-
chunks.push(...flattenBody(body, startLabel));
|
|
757
|
-
chunks[chunks.length - 1].body.push({
|
|
758
|
-
type: "GotoStatement",
|
|
759
|
-
label: "END_LABEL",
|
|
760
|
-
});
|
|
761
|
-
chunks.push({
|
|
762
|
-
label: "END_LABEL",
|
|
763
|
-
body: [],
|
|
764
|
-
});
|
|
814
|
+
}
|
|
765
815
|
|
|
816
|
+
// Generate a unique 'state' number for each chunk
|
|
766
817
|
var caseSelection: Set<number> = new Set();
|
|
767
|
-
|
|
768
818
|
var uniqueStatesNeeded = chunks.length;
|
|
769
|
-
var endLabel = chunks[Object.keys(chunks).length - 1].label;
|
|
770
819
|
|
|
771
820
|
do {
|
|
772
821
|
var newState = getRandomInteger(1, chunks.length * 15);
|
|
@@ -794,12 +843,18 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
794
843
|
*/
|
|
795
844
|
var labelToStates: { [label: string]: number[] } = Object.create(null);
|
|
796
845
|
|
|
846
|
+
var lastLabel;
|
|
847
|
+
|
|
797
848
|
Object.values(chunks).forEach((chunk, i) => {
|
|
798
849
|
var state = caseStates[i];
|
|
799
850
|
|
|
800
851
|
var stateValues = Array(stateVars.length)
|
|
801
852
|
.fill(0)
|
|
802
|
-
.map(() =>
|
|
853
|
+
.map((_, i) =>
|
|
854
|
+
lastLabel && chance(95) // Try to make state changes not as drastic (If last label, re-use some of it's values)
|
|
855
|
+
? labelToStates[lastLabel][i]
|
|
856
|
+
: getRandomInteger(-500, 500)
|
|
857
|
+
);
|
|
803
858
|
|
|
804
859
|
const getCurrentState = () => {
|
|
805
860
|
return stateValues.reduce((a, b) => b + a, 0);
|
|
@@ -810,82 +865,600 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
810
865
|
state - (getCurrentState() - stateValues[correctIndex]);
|
|
811
866
|
|
|
812
867
|
labelToStates[chunk.label] = stateValues;
|
|
868
|
+
lastLabel = chunk.label;
|
|
813
869
|
});
|
|
814
870
|
|
|
815
|
-
// console.log(labelToStates);
|
|
816
|
-
|
|
817
871
|
var initStateValues = [...labelToStates[startLabel]];
|
|
818
872
|
var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0);
|
|
819
873
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
874
|
+
// Creates a predicate based on the state-variables and control-object properties
|
|
875
|
+
const createPredicate = (
|
|
876
|
+
stateValues: number[]
|
|
877
|
+
): { test: Node; testValue: boolean } => {
|
|
878
|
+
this.mangledExpressionsMade++;
|
|
825
879
|
|
|
826
|
-
var
|
|
880
|
+
var index = getRandomInteger(0, stateVars.length);
|
|
827
881
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
operator == "<"
|
|
833
|
-
? x < stateValues[opposing]
|
|
834
|
-
: x > stateValues[opposing];
|
|
835
|
-
var correct = numberLiteral(num, depth + 1, stateValues);
|
|
836
|
-
var incorrect = numberLiteral(
|
|
837
|
-
getRandomInteger(-250, 250),
|
|
838
|
-
depth + 1,
|
|
839
|
-
stateValues
|
|
840
|
-
);
|
|
882
|
+
var compareValue = choice([
|
|
883
|
+
stateValues[index],
|
|
884
|
+
getRandomInteger(-100, 100),
|
|
885
|
+
]);
|
|
841
886
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
887
|
+
// 'state equality' test
|
|
888
|
+
var test: Node = BinaryExpression(
|
|
889
|
+
"==",
|
|
890
|
+
Identifier(stateVars[index]),
|
|
891
|
+
createStateBoundNumberLiteral(compareValue, stateValues)
|
|
892
|
+
);
|
|
893
|
+
var testValue = stateValues[index] === compareValue;
|
|
894
|
+
|
|
895
|
+
// 'control' equality test
|
|
896
|
+
if (controlConstantMap.size && chance(50)) {
|
|
897
|
+
// The controlMap maps LITERAL-values to STRING property names
|
|
898
|
+
var actualValue = choice(Array.from(controlConstantMap.keys()));
|
|
899
|
+
var controlKey = controlConstantMap.get(actualValue)?.key;
|
|
900
|
+
|
|
901
|
+
var controlCompareValue = choice([
|
|
902
|
+
actualValue,
|
|
903
|
+
stateValues[index],
|
|
904
|
+
getRandomInteger(-100, 100),
|
|
905
|
+
controlGen.generate(),
|
|
906
|
+
]);
|
|
907
|
+
|
|
908
|
+
// 'control equality' test
|
|
909
|
+
test = BinaryExpression(
|
|
910
|
+
"==",
|
|
911
|
+
getControlMember(controlKey),
|
|
912
|
+
Literal(controlCompareValue)
|
|
850
913
|
);
|
|
914
|
+
testValue = actualValue == controlCompareValue;
|
|
915
|
+
|
|
916
|
+
// 'control typeof' test
|
|
917
|
+
if (chance(10)) {
|
|
918
|
+
var compareTypeofValue = choice([
|
|
919
|
+
"number",
|
|
920
|
+
"string",
|
|
921
|
+
"object",
|
|
922
|
+
"function",
|
|
923
|
+
"undefined",
|
|
924
|
+
]);
|
|
925
|
+
|
|
926
|
+
test = BinaryExpression(
|
|
927
|
+
"==",
|
|
928
|
+
UnaryExpression("typeof", getControlMember(controlKey)),
|
|
929
|
+
Literal(compareTypeofValue)
|
|
930
|
+
);
|
|
931
|
+
testValue = typeof actualValue === compareTypeofValue;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// 'control hasOwnProperty' test
|
|
935
|
+
if (chance(10)) {
|
|
936
|
+
var hasOwnProperty = choice([controlKey, controlGen.generate()]);
|
|
937
|
+
test = CallExpression(
|
|
938
|
+
MemberExpression(
|
|
939
|
+
Identifier(controlVar),
|
|
940
|
+
Literal("hasOwnProperty"),
|
|
941
|
+
true
|
|
942
|
+
),
|
|
943
|
+
[Literal(hasOwnProperty)]
|
|
944
|
+
);
|
|
945
|
+
testValue = hasOwnProperty === controlKey;
|
|
946
|
+
}
|
|
851
947
|
}
|
|
852
948
|
|
|
853
|
-
return
|
|
854
|
-
"+",
|
|
855
|
-
Identifier(stateVars[opposing]),
|
|
856
|
-
numberLiteral(num - stateValues[opposing], depth + 1, stateValues)
|
|
857
|
-
);
|
|
949
|
+
return { test, testValue };
|
|
858
950
|
};
|
|
859
951
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
952
|
+
// A "state-less" number literal is a Number Literal that is mangled in with the Control properties.
|
|
953
|
+
// Example: X = CONTROL.Y + Z. These can be used anywhere because control properties are constant (unlike state variables)
|
|
954
|
+
const createStatelessNumberLiteral = (num: number, depth = 0) => {
|
|
955
|
+
if (
|
|
956
|
+
!controlConstantMap.size ||
|
|
957
|
+
depth > 4 ||
|
|
958
|
+
chance(75 + depth * 5 + this.mangledExpressionsMade / 25)
|
|
959
|
+
) {
|
|
960
|
+
// Add to control constant map?
|
|
961
|
+
if (
|
|
962
|
+
chance(
|
|
963
|
+
25 - controlConstantMap.size - this.mangledExpressionsMade / 100
|
|
964
|
+
)
|
|
965
|
+
) {
|
|
966
|
+
return addControlMapConstant(num);
|
|
967
|
+
}
|
|
968
|
+
return Literal(num);
|
|
969
|
+
}
|
|
970
|
+
this.mangledExpressionsMade++;
|
|
971
|
+
|
|
972
|
+
if (controlConstantMap.has(num)) {
|
|
973
|
+
return getControlMember(controlConstantMap.get(num)?.key);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
var allControlNodes = [object];
|
|
977
|
+
parents
|
|
978
|
+
.filter((x) => x.$controlVar && x.$controlConstantMap.size > 0)
|
|
979
|
+
.forEach((node) => allControlNodes.push(node));
|
|
980
|
+
|
|
981
|
+
var controlNode = choice(allControlNodes);
|
|
982
|
+
var selectedControlConstantMap = controlNode.$controlConstantMap;
|
|
983
|
+
var selectedControlVar = controlNode.$controlVar;
|
|
984
|
+
|
|
985
|
+
var actualValue = choice(Array.from(selectedControlConstantMap.keys()));
|
|
986
|
+
var controlKey = selectedControlConstantMap.get(actualValue)?.key;
|
|
987
|
+
|
|
988
|
+
if (typeof actualValue === "number") {
|
|
989
|
+
var difference = actualValue - num;
|
|
990
|
+
|
|
991
|
+
return BinaryExpression(
|
|
992
|
+
"-",
|
|
993
|
+
getControlMember(controlKey, selectedControlVar),
|
|
994
|
+
createStatelessNumberLiteral(difference, depth + 1)
|
|
995
|
+
);
|
|
996
|
+
} else if (typeof actualValue === "string") {
|
|
997
|
+
// 'control string length' test
|
|
998
|
+
var compareValue = choice([
|
|
999
|
+
actualValue.length,
|
|
1000
|
+
getRandomInteger(0, 50),
|
|
1001
|
+
]);
|
|
1002
|
+
|
|
1003
|
+
var test = BinaryExpression(
|
|
1004
|
+
"==",
|
|
1005
|
+
MemberExpression(
|
|
1006
|
+
getControlMember(controlKey, selectedControlVar),
|
|
1007
|
+
Literal("length"),
|
|
1008
|
+
true
|
|
1009
|
+
),
|
|
1010
|
+
createStatelessNumberLiteral(compareValue, depth + 1)
|
|
1011
|
+
);
|
|
1012
|
+
var testValue = actualValue.length == compareValue;
|
|
1013
|
+
|
|
1014
|
+
var consequent: Node = createStatelessNumberLiteral(num, depth + 1);
|
|
1015
|
+
var alternate: Node = Literal(getRandomInteger(-100, 100));
|
|
1016
|
+
|
|
1017
|
+
return ConditionalExpression(
|
|
1018
|
+
test,
|
|
1019
|
+
testValue ? consequent : alternate,
|
|
1020
|
+
!testValue ? consequent : alternate
|
|
1021
|
+
);
|
|
1022
|
+
} else {
|
|
1023
|
+
throw new Error("Unknown: " + typeof actualValue);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// A "state-bound" number literal is a Number Literal that is mangled in with the current state variables
|
|
1028
|
+
// Example: X = STATE + Y. This can only be used when the state-values are guaranteed to be known.
|
|
1029
|
+
const createStateBoundNumberLiteral = (
|
|
1030
|
+
num: number,
|
|
1031
|
+
stateValues: number[],
|
|
1032
|
+
depth = 0
|
|
1033
|
+
): Node => {
|
|
1034
|
+
ok(Array.isArray(stateValues));
|
|
1035
|
+
|
|
1036
|
+
// Base case: After 4 depth, OR random chance
|
|
1037
|
+
if (
|
|
1038
|
+
depth > 4 ||
|
|
1039
|
+
chance(75 + depth * 5 + this.mangledExpressionsMade / 25)
|
|
1040
|
+
) {
|
|
1041
|
+
// Add this number to the control object?
|
|
1042
|
+
// Add to control constant map?
|
|
1043
|
+
if (chance(25 - controlConstantMap.size)) {
|
|
1044
|
+
return addControlMapConstant(num);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return Literal(num);
|
|
1048
|
+
}
|
|
1049
|
+
this.mangledExpressionsMade++;
|
|
1050
|
+
|
|
1051
|
+
if (chance(10)) {
|
|
1052
|
+
return createStatelessNumberLiteral(num, depth + 1);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Terminated predicate
|
|
1056
|
+
if (chance(50)) {
|
|
1057
|
+
var { test, testValue } = createPredicate(stateValues);
|
|
1058
|
+
|
|
1059
|
+
var alternateNode = choice([
|
|
1060
|
+
Literal(getRandomInteger(-100, 100)),
|
|
1061
|
+
Literal(controlGen.generate()),
|
|
1062
|
+
getControlMember(controlGen.generate()),
|
|
1063
|
+
]);
|
|
1064
|
+
|
|
1065
|
+
return ConditionalExpression(
|
|
1066
|
+
test,
|
|
1067
|
+
testValue ? Literal(num) : alternateNode,
|
|
1068
|
+
!testValue ? Literal(num) : alternateNode
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Recursive predicate
|
|
1073
|
+
var opposing = getRandomInteger(0, stateVars.length);
|
|
1074
|
+
|
|
1075
|
+
if (chance(10)) {
|
|
1076
|
+
// state > compare ? real : fake
|
|
1077
|
+
|
|
1078
|
+
var compareValue: number = choice([
|
|
1079
|
+
stateValues[opposing],
|
|
1080
|
+
getRandomInteger(-150, 150),
|
|
1081
|
+
]);
|
|
1082
|
+
|
|
1083
|
+
var operator = choice(["<", ">", "==", "!="]);
|
|
1084
|
+
var answer: boolean = {
|
|
1085
|
+
">": compareValue > stateValues[opposing],
|
|
1086
|
+
"<": compareValue < stateValues[opposing],
|
|
1087
|
+
"==": compareValue === stateValues[opposing],
|
|
1088
|
+
"!=": compareValue !== stateValues[opposing],
|
|
1089
|
+
}[operator];
|
|
1090
|
+
|
|
1091
|
+
var correct = createStateBoundNumberLiteral(
|
|
1092
|
+
num,
|
|
1093
|
+
stateValues,
|
|
1094
|
+
depth + 1
|
|
1095
|
+
);
|
|
1096
|
+
var incorrect = createStateBoundNumberLiteral(
|
|
1097
|
+
getRandomInteger(-150, 150),
|
|
1098
|
+
stateValues,
|
|
1099
|
+
depth + 1
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
return ConditionalExpression(
|
|
1103
|
+
BinaryExpression(
|
|
1104
|
+
operator,
|
|
1105
|
+
createStateBoundNumberLiteral(
|
|
1106
|
+
compareValue,
|
|
1107
|
+
stateValues,
|
|
1108
|
+
depth + 1
|
|
1109
|
+
),
|
|
1110
|
+
Identifier(stateVars[opposing])
|
|
1111
|
+
),
|
|
1112
|
+
answer ? correct : incorrect,
|
|
1113
|
+
answer ? incorrect : correct
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// state + 10 = <REAL>
|
|
1118
|
+
var difference = num - stateValues[opposing];
|
|
1119
|
+
|
|
1120
|
+
if (difference === 0) {
|
|
1121
|
+
return Identifier(stateVars[opposing]);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
return BinaryExpression(
|
|
1125
|
+
"+",
|
|
1126
|
+
Identifier(stateVars[opposing]),
|
|
1127
|
+
createStateBoundNumberLiteral(difference, stateValues, depth + 1)
|
|
1128
|
+
);
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
var outlinesCreated = 0;
|
|
1132
|
+
|
|
1133
|
+
const isExpression = (object: Node, parents: Node[]) => {
|
|
1134
|
+
var fnIndex = parents.findIndex((x) => isFunction(x));
|
|
1135
|
+
if (fnIndex != -1) {
|
|
1136
|
+
// This does NOT mutate
|
|
1137
|
+
parents = parents.slice(0, fnIndex);
|
|
1138
|
+
}
|
|
1139
|
+
var assignmentIndex = parents.findIndex(
|
|
1140
|
+
(x) => x.type === "AssignmentExpression"
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
// Left-hand assignment validation
|
|
1144
|
+
if (assignmentIndex != -1) {
|
|
1145
|
+
if (
|
|
1146
|
+
parents[assignmentIndex].left ===
|
|
1147
|
+
(parents[assignmentIndex - 1] || object)
|
|
1148
|
+
) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// For in/of left validation
|
|
1154
|
+
var forInOfIndex = parents.findIndex(
|
|
1155
|
+
(x) => x.type === "ForInStatement" || x.type === "ForOfStatement"
|
|
1156
|
+
);
|
|
1157
|
+
if (forInOfIndex != -1) {
|
|
1158
|
+
if (
|
|
1159
|
+
parents[forInOfIndex].left === (parents[forInOfIndex - 1] || object)
|
|
1160
|
+
) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Bound call-expression validation
|
|
1166
|
+
var callExpressionIndex = parents.findIndex(
|
|
1167
|
+
(x) => x.type === "CallExpression"
|
|
1168
|
+
);
|
|
1169
|
+
if (callExpressionIndex != -1) {
|
|
1170
|
+
if (
|
|
1171
|
+
parents[callExpressionIndex].callee ==
|
|
1172
|
+
(parents[callExpressionIndex - 1] || object)
|
|
1173
|
+
) {
|
|
1174
|
+
var callee = parents[callExpressionIndex].callee;
|
|
1175
|
+
|
|
1176
|
+
// Detected bound call expression. Not supported.
|
|
1177
|
+
if (callee.type === "MemberExpression") {
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Update-expression validation:
|
|
1184
|
+
var updateExpressionIndex = parents.findIndex(
|
|
1185
|
+
(x) => x.type === "UpdateExpression"
|
|
1186
|
+
);
|
|
1187
|
+
if (updateExpressionIndex !== -1) return false;
|
|
1188
|
+
|
|
1189
|
+
return true;
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// This function checks if the expression or statements is possible to be outlined
|
|
1193
|
+
const canOutline = (object: Node | Node[], parents: Node[]) => {
|
|
1194
|
+
var isIllegal = false;
|
|
1195
|
+
|
|
1196
|
+
var breakStatements: Location[] = [];
|
|
1197
|
+
var returnStatements: Location[] = [];
|
|
1198
|
+
|
|
1199
|
+
if (!Array.isArray(object) && !isExpression(object, parents)) {
|
|
1200
|
+
return { isIllegal: true, breakStatements: [], returnStatements: [] };
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
walk(object, parents, (o, p) => {
|
|
1204
|
+
if (
|
|
1205
|
+
o.type === "ThisExpression" ||
|
|
1206
|
+
o.type === "MetaProperty" ||
|
|
1207
|
+
o.type === "Super"
|
|
1208
|
+
) {
|
|
1209
|
+
isIllegal = true;
|
|
1210
|
+
return "EXIT";
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (o.type === "BreakStatement") {
|
|
1214
|
+
// This can be safely outlined
|
|
1215
|
+
if (o.label && o.label.name === switchLabel) {
|
|
1216
|
+
breakStatements.push([o, p]);
|
|
1217
|
+
} else {
|
|
1218
|
+
isIllegal = true;
|
|
1219
|
+
return "EXIT";
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (
|
|
1224
|
+
(o.type === "ContinueStatement" ||
|
|
1225
|
+
o.type === "AwaitExpression" ||
|
|
1226
|
+
o.type === "YieldExpression" ||
|
|
1227
|
+
o.type === "ReturnStatement" ||
|
|
1228
|
+
o.type === "VariableDeclaration" ||
|
|
1229
|
+
o.type === "FunctionDeclaration" ||
|
|
1230
|
+
o.type === "ClassDeclaration") &&
|
|
1231
|
+
!p.find((x) => isVarContext(x))
|
|
1232
|
+
) {
|
|
1233
|
+
// This can be safely outlined
|
|
1234
|
+
if (o.type === "ReturnStatement") {
|
|
1235
|
+
returnStatements.push([o, p]);
|
|
1236
|
+
} else {
|
|
1237
|
+
isIllegal = true;
|
|
1238
|
+
return "EXIT";
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (o.type === "Identifier") {
|
|
1243
|
+
if (o.name === "arguments") {
|
|
1244
|
+
isIllegal = true;
|
|
1245
|
+
return "EXIT";
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
return { isIllegal, breakStatements, returnStatements };
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
const createOutlineFunction = (
|
|
1254
|
+
body: Node[],
|
|
1255
|
+
stateValues: number[],
|
|
1256
|
+
label: string
|
|
1257
|
+
) => {
|
|
1258
|
+
var key = controlGen.generate();
|
|
1259
|
+
|
|
1260
|
+
var functionExpression = FunctionExpression([], body);
|
|
1261
|
+
if (!this.options.es5 && chance(50)) {
|
|
1262
|
+
functionExpression.type = "ArrowFunctionExpression";
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
controlProperties.push(
|
|
1266
|
+
Property(Literal(key), functionExpression, false)
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
// Add dead code to function
|
|
1270
|
+
if (!this.isDebug && chance(25)) {
|
|
1271
|
+
var { test, testValue } = createPredicate(stateValues);
|
|
1272
|
+
var deadCodeVar = this.getPlaceholder();
|
|
1273
|
+
functionExpression.params.push(
|
|
1274
|
+
AssignmentPattern(Identifier(deadCodeVar), test)
|
|
1275
|
+
);
|
|
1276
|
+
var alternate = [
|
|
1277
|
+
ReturnStatement(
|
|
1278
|
+
choice([
|
|
1279
|
+
BinaryExpression(
|
|
1280
|
+
"==",
|
|
1281
|
+
Identifier(choice(stateVars)),
|
|
1282
|
+
Literal(getRandomInteger(-100, 100))
|
|
1283
|
+
),
|
|
1284
|
+
Literal(controlGen.generate()),
|
|
1285
|
+
Identifier("arguments"),
|
|
1286
|
+
Identifier(choice(stateVars)),
|
|
1287
|
+
Identifier(controlVar),
|
|
1288
|
+
CallExpression(getControlMember(controlGen.generate()), []),
|
|
1289
|
+
])
|
|
1290
|
+
),
|
|
1291
|
+
];
|
|
1292
|
+
|
|
1293
|
+
functionExpression.body.body.unshift(
|
|
1294
|
+
IfStatement(
|
|
1295
|
+
testValue
|
|
1296
|
+
? UnaryExpression("!", Identifier(deadCodeVar))
|
|
1297
|
+
: Identifier(deadCodeVar),
|
|
1298
|
+
alternate
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
outlinesCreated++;
|
|
1304
|
+
|
|
1305
|
+
return key;
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
const attemptOutlineStatements = (
|
|
1309
|
+
statements: Node[],
|
|
1310
|
+
parentBlock: Node[],
|
|
1311
|
+
stateValues: number[],
|
|
1312
|
+
label: string
|
|
1313
|
+
) => {
|
|
1314
|
+
if (
|
|
1315
|
+
this.isDebug ||
|
|
1316
|
+
!this.outlineStatements ||
|
|
1317
|
+
chance(75 + outlinesCreated - this.mangledExpressionsMade / 25)
|
|
1318
|
+
) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
var index = parentBlock.indexOf(statements[0]);
|
|
1323
|
+
if (index === -1) return;
|
|
1324
|
+
|
|
1325
|
+
var outlineInfo = canOutline(statements, parentBlock);
|
|
1326
|
+
if (outlineInfo.isIllegal) return;
|
|
1327
|
+
|
|
1328
|
+
var breakFlag = controlGen.generate();
|
|
1329
|
+
|
|
1330
|
+
outlineInfo.breakStatements.forEach(([breakStatement, p]) => {
|
|
1331
|
+
this.replace(breakStatement, ReturnStatement(Literal(breakFlag)));
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
var returnFlag = controlGen.generate();
|
|
1335
|
+
|
|
1336
|
+
outlineInfo.returnStatements.forEach(([returnStatement, p]) => {
|
|
1337
|
+
var argument = returnStatement.argument || Identifier("undefined");
|
|
1338
|
+
|
|
1339
|
+
this.replace(
|
|
1340
|
+
returnStatement,
|
|
1341
|
+
ReturnStatement(
|
|
1342
|
+
ObjectExpression([Property(Literal(returnFlag), argument, false)])
|
|
1343
|
+
)
|
|
1344
|
+
);
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
// Outline these statements!
|
|
1348
|
+
var key = createOutlineFunction(clone(statements), stateValues, label);
|
|
1349
|
+
var callExpression = CallExpression(getControlMember(key), []);
|
|
1350
|
+
|
|
1351
|
+
var newStatements: Node[] = [];
|
|
1352
|
+
if (
|
|
1353
|
+
outlineInfo.breakStatements.length === 0 &&
|
|
1354
|
+
outlineInfo.returnStatements.length === 0
|
|
1355
|
+
) {
|
|
1356
|
+
newStatements.push(ExpressionStatement(callExpression));
|
|
1357
|
+
} else if (outlineInfo.returnStatements.length === 0) {
|
|
1358
|
+
newStatements.push(
|
|
1359
|
+
IfStatement(
|
|
1360
|
+
BinaryExpression("==", callExpression, Literal(breakFlag)),
|
|
1361
|
+
[BreakStatement(switchLabel)]
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
} else {
|
|
1365
|
+
var tempVar = this.getPlaceholder();
|
|
1366
|
+
newStatements.push(
|
|
1367
|
+
VariableDeclaration(VariableDeclarator(tempVar, callExpression))
|
|
1368
|
+
);
|
|
1369
|
+
|
|
1370
|
+
const t = (str): Node => Template(str).single().expression;
|
|
1371
|
+
|
|
1372
|
+
newStatements.push(
|
|
1373
|
+
IfStatement(
|
|
1374
|
+
t(`${tempVar} === "${breakFlag}"`),
|
|
1375
|
+
[BreakStatement(switchLabel)],
|
|
1376
|
+
[
|
|
1377
|
+
IfStatement(t(`typeof ${tempVar} == "object"`), [
|
|
1378
|
+
ReturnStatement(t(`${tempVar}["${returnFlag}"]`)),
|
|
1379
|
+
]),
|
|
1380
|
+
]
|
|
1381
|
+
)
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Remove the original statements from the block and replace it with the call expression
|
|
1386
|
+
parentBlock.splice(index, statements.length, ...newStatements);
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
const attemptOutlineExpression = (
|
|
1390
|
+
expression: Node,
|
|
1391
|
+
expressionParents: Node[],
|
|
1392
|
+
stateValues: number[],
|
|
1393
|
+
label: string
|
|
1394
|
+
) => {
|
|
1395
|
+
if (
|
|
1396
|
+
this.isDebug ||
|
|
1397
|
+
!this.outlineExpressions ||
|
|
1398
|
+
chance(75 + outlinesCreated - this.mangledExpressionsMade / 25)
|
|
1399
|
+
) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
var outlineInfo = canOutline(expression, expressionParents);
|
|
1404
|
+
if (
|
|
1405
|
+
outlineInfo.isIllegal ||
|
|
1406
|
+
outlineInfo.breakStatements.length ||
|
|
1407
|
+
outlineInfo.returnStatements.length
|
|
1408
|
+
)
|
|
1409
|
+
return;
|
|
1410
|
+
|
|
1411
|
+
// Outline this expression!
|
|
1412
|
+
var key = createOutlineFunction(
|
|
1413
|
+
[ReturnStatement(clone(expression))],
|
|
1414
|
+
stateValues,
|
|
1415
|
+
label
|
|
1416
|
+
);
|
|
1417
|
+
|
|
1418
|
+
var callExpression = CallExpression(getControlMember(key), []);
|
|
1419
|
+
|
|
1420
|
+
this.replaceIdentifierOrLiteral(
|
|
1421
|
+
expression,
|
|
1422
|
+
callExpression,
|
|
1423
|
+
expressionParents
|
|
1424
|
+
);
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
const createTransitionExpression = (
|
|
1428
|
+
index: number,
|
|
1429
|
+
add: number,
|
|
1430
|
+
mutatingStateValues: number[],
|
|
1431
|
+
label: string
|
|
1432
|
+
) => {
|
|
1433
|
+
var beforeStateValues = [...mutatingStateValues];
|
|
1434
|
+
var newValue = mutatingStateValues[index] + add;
|
|
1435
|
+
|
|
1436
|
+
var expr = null;
|
|
868
1437
|
|
|
869
1438
|
if (this.isDebug) {
|
|
1439
|
+
// state = NEW_STATE
|
|
870
1440
|
expr = AssignmentExpression(
|
|
871
1441
|
"=",
|
|
872
1442
|
Identifier(stateVars[index]),
|
|
873
1443
|
Literal(newValue)
|
|
874
1444
|
);
|
|
875
|
-
} else if (
|
|
1445
|
+
} else if (chance(90)) {
|
|
1446
|
+
// state += (NEW_STATE - CURRENT_STATE)
|
|
876
1447
|
expr = AssignmentExpression(
|
|
877
1448
|
"+=",
|
|
878
1449
|
Identifier(stateVars[index]),
|
|
879
|
-
|
|
1450
|
+
createStateBoundNumberLiteral(add, mutatingStateValues)
|
|
880
1451
|
);
|
|
881
1452
|
} else {
|
|
1453
|
+
// state *= 2
|
|
1454
|
+
// state -= DIFFERENCE
|
|
882
1455
|
var double = mutatingStateValues[index] * 2;
|
|
883
1456
|
var diff = double - newValue;
|
|
884
1457
|
|
|
885
1458
|
var first = AssignmentExpression(
|
|
886
1459
|
"*=",
|
|
887
1460
|
Identifier(stateVars[index]),
|
|
888
|
-
|
|
1461
|
+
createStateBoundNumberLiteral(2, mutatingStateValues)
|
|
889
1462
|
);
|
|
890
1463
|
mutatingStateValues[index] = double;
|
|
891
1464
|
|
|
@@ -894,23 +1467,27 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
894
1467
|
AssignmentExpression(
|
|
895
1468
|
"-=",
|
|
896
1469
|
Identifier(stateVars[index]),
|
|
897
|
-
|
|
1470
|
+
createStateBoundNumberLiteral(diff, mutatingStateValues)
|
|
898
1471
|
),
|
|
899
1472
|
]);
|
|
900
1473
|
}
|
|
901
1474
|
|
|
902
1475
|
mutatingStateValues[index] = newValue;
|
|
903
1476
|
|
|
1477
|
+
// These are lower quality outlines vs. the entire transition outline
|
|
1478
|
+
if (chance(50)) {
|
|
1479
|
+
attemptOutlineExpression(expr, [], [...beforeStateValues], label);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
904
1482
|
return expr;
|
|
905
1483
|
};
|
|
906
1484
|
|
|
907
1485
|
interface Case {
|
|
908
1486
|
state: number;
|
|
909
1487
|
body: Node[];
|
|
910
|
-
|
|
1488
|
+
label: string;
|
|
911
1489
|
}
|
|
912
1490
|
|
|
913
|
-
var order = Object.create(null);
|
|
914
1491
|
var cases: Case[] = [];
|
|
915
1492
|
|
|
916
1493
|
chunks.forEach((chunk, i) => {
|
|
@@ -921,34 +1498,201 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
921
1498
|
|
|
922
1499
|
ok(labelToStates[chunk.label]);
|
|
923
1500
|
var state = caseStates[i];
|
|
924
|
-
var made = 1;
|
|
925
1501
|
|
|
926
|
-
var breaksInsertion = [];
|
|
927
1502
|
var staticStateValues = [...labelToStates[chunk.label]];
|
|
928
1503
|
var potentialBranches = new Set<string>();
|
|
929
1504
|
|
|
930
|
-
chunk.body.forEach((stmt
|
|
931
|
-
var addBreak = false;
|
|
1505
|
+
[...chunk.body].forEach((stmt) => {
|
|
932
1506
|
walk(stmt, [], (o, p) => {
|
|
1507
|
+
// This mangles certain literals with the state variables
|
|
1508
|
+
// Ex: A number literal (50) changed to a expression (stateVar + 40), when stateVar = 10
|
|
933
1509
|
if (
|
|
934
1510
|
!this.isDebug &&
|
|
935
|
-
o.type
|
|
936
|
-
typeof o.value === "number" &&
|
|
937
|
-
Math.floor(o.value) === o.value &&
|
|
938
|
-
Math.abs(o.value) < 100_000 &&
|
|
939
|
-
Math.random() < 4 / made &&
|
|
1511
|
+
o.type === "Literal" &&
|
|
940
1512
|
!p.find((x) => isVarContext(x))
|
|
941
1513
|
) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1514
|
+
if (
|
|
1515
|
+
typeof o.value === "number" &&
|
|
1516
|
+
Math.floor(o.value) === o.value && // Only whole numbers
|
|
1517
|
+
Math.abs(o.value) < 100_000 && // Hard-coded limit
|
|
1518
|
+
this.mangleNumberLiterals &&
|
|
1519
|
+
chance(50 - this.mangledExpressionsMade / 100)
|
|
1520
|
+
) {
|
|
1521
|
+
// 50 -> state1 - 10, when state1 = 60. The result is still 50
|
|
1522
|
+
|
|
1523
|
+
return () => {
|
|
1524
|
+
this.replaceIdentifierOrLiteral(
|
|
1525
|
+
o,
|
|
1526
|
+
createStateBoundNumberLiteral(o.value, staticStateValues),
|
|
1527
|
+
p
|
|
1528
|
+
);
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if (
|
|
1533
|
+
typeof o.value === "boolean" &&
|
|
1534
|
+
this.mangleBooleanLiterals &&
|
|
1535
|
+
chance(50 - this.mangledExpressionsMade / 100)
|
|
1536
|
+
) {
|
|
1537
|
+
// true -> state1 == 10, when state1 = 10. The result is still true
|
|
1538
|
+
|
|
1539
|
+
// Choose a random state var to compare again
|
|
1540
|
+
var index = getRandomInteger(0, stateVars.length);
|
|
1541
|
+
|
|
1542
|
+
var compareValue = staticStateValues[index];
|
|
1543
|
+
|
|
1544
|
+
// When false, always choose a different number, so the expression always equals false
|
|
1545
|
+
while (!o.value && compareValue === staticStateValues[index]) {
|
|
1546
|
+
compareValue = getRandomInteger(-150, 150);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
var mangledExpression: Node = BinaryExpression(
|
|
1550
|
+
"==",
|
|
1551
|
+
Identifier(stateVars[index]),
|
|
1552
|
+
createStateBoundNumberLiteral(compareValue, staticStateValues)
|
|
948
1553
|
);
|
|
1554
|
+
|
|
1555
|
+
return () => {
|
|
1556
|
+
this.replaceIdentifierOrLiteral(o, mangledExpression, p);
|
|
1557
|
+
|
|
1558
|
+
attemptOutlineExpression(
|
|
1559
|
+
o,
|
|
1560
|
+
p,
|
|
1561
|
+
staticStateValues,
|
|
1562
|
+
chunk.label
|
|
1563
|
+
);
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Mangle certain referenced identifiers
|
|
1569
|
+
// console.log("hi") -> (x ? console : window).log("hi"), when is x true. The result is the same
|
|
1570
|
+
if (
|
|
1571
|
+
!this.isDebug &&
|
|
1572
|
+
o.type === "Identifier" &&
|
|
1573
|
+
this.mangleIdentifiers &&
|
|
1574
|
+
chance(50 - this.mangledExpressionsMade / 100) &&
|
|
1575
|
+
!p.find((x) => isVarContext(x))
|
|
1576
|
+
) {
|
|
1577
|
+
// ONLY referenced identifiers (like actual variable names) can be changed
|
|
1578
|
+
var info = getIdentifierInfo(o, p);
|
|
1579
|
+
if (
|
|
1580
|
+
!info.spec.isReferenced ||
|
|
1581
|
+
info.spec.isDefined ||
|
|
1582
|
+
info.spec.isModified ||
|
|
1583
|
+
info.spec.isExported
|
|
1584
|
+
) {
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// TYPEOF expression check
|
|
1589
|
+
if (
|
|
1590
|
+
p[0] &&
|
|
1591
|
+
p[0].type === "UnaryExpression" &&
|
|
1592
|
+
p[0].operator === "typeof" &&
|
|
1593
|
+
p[0].argument === o
|
|
1594
|
+
) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Update expression check
|
|
1599
|
+
if (p[0] && p[0].type === "UpdateExpression") {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// FOR-in/of initializer check
|
|
1604
|
+
if (isForInitialize(o, p) === "left-hand") {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
var { test, testValue } = createPredicate(staticStateValues);
|
|
1609
|
+
|
|
1610
|
+
// test && real
|
|
1611
|
+
var mangledExpression: Node = LogicalExpression(
|
|
1612
|
+
testValue ? "&&" : "||",
|
|
1613
|
+
test,
|
|
1614
|
+
Identifier(o.name)
|
|
1615
|
+
);
|
|
1616
|
+
|
|
1617
|
+
// control.fake = real
|
|
1618
|
+
if (chance(50)) {
|
|
1619
|
+
mangledExpression = AssignmentExpression(
|
|
1620
|
+
"=",
|
|
1621
|
+
getControlMember(controlGen.generate()),
|
|
1622
|
+
Identifier(o.name)
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// test ? real : fake
|
|
1627
|
+
if (chance(50)) {
|
|
1628
|
+
var alternateName = choice([
|
|
1629
|
+
controlVar,
|
|
1630
|
+
...stateVars,
|
|
1631
|
+
...this.options.globalVariables,
|
|
1632
|
+
...reservedIdentifiers,
|
|
1633
|
+
]);
|
|
1634
|
+
|
|
1635
|
+
// Don't use 'arguments'
|
|
1636
|
+
if (alternateName === "arguments") alternateName = "undefined";
|
|
1637
|
+
|
|
1638
|
+
mangledExpression = ConditionalExpression(
|
|
1639
|
+
test,
|
|
1640
|
+
Identifier(testValue ? o.name : alternateName),
|
|
1641
|
+
Identifier(!testValue ? o.name : alternateName)
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
return () => {
|
|
1646
|
+
this.replaceIdentifierOrLiteral(o, mangledExpression, p);
|
|
949
1647
|
};
|
|
950
1648
|
}
|
|
951
1649
|
|
|
1650
|
+
// Function outlining: bring out certain expressions
|
|
1651
|
+
if (
|
|
1652
|
+
!this.isDebug &&
|
|
1653
|
+
o.type &&
|
|
1654
|
+
[
|
|
1655
|
+
"BinaryExpression",
|
|
1656
|
+
"LogicalExpression",
|
|
1657
|
+
"CallExpression",
|
|
1658
|
+
"AssignmentExpression",
|
|
1659
|
+
"MemberExpression",
|
|
1660
|
+
"ObjectExpression",
|
|
1661
|
+
"ConditionalExpression",
|
|
1662
|
+
].includes(o.type) &&
|
|
1663
|
+
!chance(p.length * 5) && // The further down the tree the lower quality of expression
|
|
1664
|
+
!p.find((x) => isContext(x) || x.$outlining)
|
|
1665
|
+
) {
|
|
1666
|
+
o.$outlining = true;
|
|
1667
|
+
return () => {
|
|
1668
|
+
attemptOutlineExpression(o, p, staticStateValues, chunk.label);
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// Opaque predicates: If Statements, Conditional Statements, Switch Case test
|
|
1673
|
+
if (
|
|
1674
|
+
!this.isDebug &&
|
|
1675
|
+
this.addOpaquePredicates &&
|
|
1676
|
+
p[0] &&
|
|
1677
|
+
chance(50 - outlinesCreated - this.mangledExpressionsMade / 100)
|
|
1678
|
+
) {
|
|
1679
|
+
var isTestExpression =
|
|
1680
|
+
(p[0].type == "IfStatement" && p[0].test === o) ||
|
|
1681
|
+
(p[0].type === "ConditionalExpression" && p[0].test === o) ||
|
|
1682
|
+
(p[0].type === "SwitchCase" && p[0].test === o);
|
|
1683
|
+
|
|
1684
|
+
if (isTestExpression && !p.find((x) => isContext(x))) {
|
|
1685
|
+
return () => {
|
|
1686
|
+
var { test, testValue } = createPredicate(staticStateValues);
|
|
1687
|
+
|
|
1688
|
+
this.replace(
|
|
1689
|
+
o,
|
|
1690
|
+
LogicalExpression(testValue ? "&&" : "||", test, clone(o))
|
|
1691
|
+
);
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
952
1696
|
if (o.type == "StateIdentifier") {
|
|
953
1697
|
return () => {
|
|
954
1698
|
ok(labelToStates[o.label]);
|
|
@@ -961,172 +1705,440 @@ export default class ControlFlowFlattening extends Transform {
|
|
|
961
1705
|
|
|
962
1706
|
if (o.type == "GotoStatement") {
|
|
963
1707
|
return () => {
|
|
964
|
-
var blockIndex = p.findIndex(
|
|
1708
|
+
var blockIndex = p.findIndex(
|
|
1709
|
+
(node) => isBlock(node) || node.type === "SwitchCase"
|
|
1710
|
+
);
|
|
965
1711
|
if (blockIndex === -1) {
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
var child = p[blockIndex - 2] || o;
|
|
969
|
-
var childIndex = p[blockIndex].body.indexOf(child);
|
|
1712
|
+
var index = chunk.body.indexOf(stmt);
|
|
1713
|
+
ok(index != -1);
|
|
970
1714
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1715
|
+
// Top level: Insert break statement in the chunk body
|
|
1716
|
+
// This is OKAY because this forEach uses a cloned version of the body `[...chunk.body]`
|
|
1717
|
+
chunk.body.splice(index + 1, 0, BreakStatement(switchLabel));
|
|
1718
|
+
} else {
|
|
1719
|
+
var block = p[blockIndex];
|
|
1720
|
+
|
|
1721
|
+
if (block.type === "SwitchCase") {
|
|
1722
|
+
// Handle switch case break placement (Important!)
|
|
1723
|
+
block.consequent.splice(
|
|
1724
|
+
block.consequent.indexOf(p[blockIndex - 2] || o) + 1,
|
|
1725
|
+
0,
|
|
1726
|
+
BreakStatement(switchLabel)
|
|
1727
|
+
);
|
|
1728
|
+
} else {
|
|
1729
|
+
// Standard block placement
|
|
1730
|
+
var child = p[blockIndex - 2] || o;
|
|
1731
|
+
var childIndex = block.body.indexOf(child);
|
|
1732
|
+
|
|
1733
|
+
block.body.splice(
|
|
1734
|
+
childIndex + 1,
|
|
1735
|
+
0,
|
|
1736
|
+
BreakStatement(switchLabel)
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
976
1739
|
}
|
|
977
1740
|
|
|
978
|
-
|
|
1741
|
+
if (!o.impossible) {
|
|
1742
|
+
potentialBranches.add(o.label);
|
|
1743
|
+
}
|
|
979
1744
|
|
|
980
1745
|
var mutatingStateValues = [...labelToStates[chunk.label]];
|
|
981
1746
|
var nextStateValues = labelToStates[o.label];
|
|
982
1747
|
ok(nextStateValues, o.label);
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1748
|
+
|
|
1749
|
+
var transitionExpressions: Node[] = [];
|
|
1750
|
+
for (
|
|
1751
|
+
var stateValueIndex = 0;
|
|
1752
|
+
stateValueIndex < stateVars.length;
|
|
1753
|
+
stateValueIndex++
|
|
1754
|
+
) {
|
|
1755
|
+
var diff =
|
|
1756
|
+
nextStateValues[stateValueIndex] -
|
|
1757
|
+
mutatingStateValues[stateValueIndex];
|
|
1758
|
+
|
|
1759
|
+
// Only add if state value changed
|
|
1760
|
+
// If pointing to itself then always add to ensure SequenceExpression isn't empty
|
|
1761
|
+
if (diff !== 0 || o.label === chunk.label) {
|
|
1762
|
+
transitionExpressions.push(
|
|
1763
|
+
createTransitionExpression(
|
|
1764
|
+
stateValueIndex,
|
|
1765
|
+
diff,
|
|
1766
|
+
mutatingStateValues,
|
|
1767
|
+
chunk.label
|
|
1768
|
+
)
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
ok(transitionExpressions.length !== 0);
|
|
1774
|
+
|
|
1775
|
+
var sequenceExpression = SequenceExpression(
|
|
1776
|
+
transitionExpressions
|
|
1777
|
+
);
|
|
1778
|
+
|
|
1779
|
+
// Check if flagged and additional code here
|
|
1780
|
+
if (typeof flaggedLabels[o.label] === "object") {
|
|
1781
|
+
var { flagKey, flagValue } = flaggedLabels[o.label];
|
|
1782
|
+
|
|
1783
|
+
sequenceExpression.expressions.push(
|
|
1784
|
+
AssignmentExpression(
|
|
1785
|
+
"=",
|
|
1786
|
+
getControlMember(flagKey),
|
|
1787
|
+
Literal(flagValue)
|
|
997
1788
|
)
|
|
998
|
-
)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
attemptOutlineExpression(
|
|
1793
|
+
sequenceExpression,
|
|
1794
|
+
[],
|
|
1795
|
+
staticStateValues,
|
|
1796
|
+
chunk.label
|
|
999
1797
|
);
|
|
1798
|
+
|
|
1799
|
+
this.replace(o, ExpressionStatement(sequenceExpression));
|
|
1000
1800
|
};
|
|
1001
1801
|
}
|
|
1002
1802
|
});
|
|
1003
|
-
|
|
1004
|
-
if (addBreak) {
|
|
1005
|
-
breaksInsertion.push(stmtIndex);
|
|
1006
|
-
}
|
|
1007
1803
|
});
|
|
1008
1804
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
chunk.
|
|
1014
|
-
|
|
1805
|
+
attemptOutlineStatements(
|
|
1806
|
+
chunk.body,
|
|
1807
|
+
chunk.body,
|
|
1808
|
+
staticStateValues,
|
|
1809
|
+
chunk.label
|
|
1810
|
+
);
|
|
1015
1811
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
Array.from(strings).map((strValue) => {
|
|
1023
|
-
return AssignmentExpression(
|
|
1024
|
-
"=",
|
|
1025
|
-
MemberExpression(
|
|
1026
|
-
Identifier(stringBankVar),
|
|
1027
|
-
Literal(stringBank[strValue]),
|
|
1028
|
-
true
|
|
1029
|
-
),
|
|
1030
|
-
Literal(strValue)
|
|
1031
|
-
);
|
|
1032
|
-
})
|
|
1033
|
-
)
|
|
1034
|
-
)
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1812
|
+
if (!chunk.impossible) {
|
|
1813
|
+
// FUTURE OBFUSCATION IDEA: Update controlObject based on 'potentialBranches' code
|
|
1814
|
+
// This idea would require a lot of work but would make some seriously effective obfuscation
|
|
1815
|
+
// for protecting the data. In 'inactive' states the data could be overwritten to fake values
|
|
1816
|
+
// And in the 'active' state the data would brought back just in time. This would require the controlObject
|
|
1817
|
+
// state to be known in all chunks
|
|
1037
1818
|
}
|
|
1038
1819
|
|
|
1039
|
-
|
|
1040
|
-
// this.addComment(c, stateValues.join(", "));
|
|
1041
|
-
// transitionStatements.push(c);
|
|
1042
|
-
|
|
1043
|
-
var caseObject = {
|
|
1820
|
+
var caseObject: Case = {
|
|
1044
1821
|
body: chunk.body,
|
|
1045
1822
|
state: state,
|
|
1046
|
-
|
|
1823
|
+
label: chunk.label,
|
|
1047
1824
|
};
|
|
1048
|
-
order[i] = caseObject;
|
|
1049
1825
|
|
|
1050
1826
|
cases.push(caseObject);
|
|
1051
1827
|
});
|
|
1052
1828
|
|
|
1829
|
+
if (!this.isDebug && this.addDeadCode) {
|
|
1830
|
+
// Add fake control object updates
|
|
1831
|
+
chunks.forEach((chunk) => {
|
|
1832
|
+
if (chance(10)) {
|
|
1833
|
+
// These deadCode variants can NOT break the state/control variables
|
|
1834
|
+
// They are executed!
|
|
1835
|
+
var deadCodeChoices = [
|
|
1836
|
+
ExpressionStatement(
|
|
1837
|
+
AssignmentExpression(
|
|
1838
|
+
"=",
|
|
1839
|
+
getControlMember(controlGen.generate()),
|
|
1840
|
+
Literal(controlGen.generate())
|
|
1841
|
+
)
|
|
1842
|
+
),
|
|
1843
|
+
ExpressionStatement(
|
|
1844
|
+
UnaryExpression(
|
|
1845
|
+
"delete",
|
|
1846
|
+
getControlMember(controlGen.generate())
|
|
1847
|
+
)
|
|
1848
|
+
),
|
|
1849
|
+
];
|
|
1850
|
+
|
|
1851
|
+
// These deadCode variants can make breaking changes
|
|
1852
|
+
// because they are never ran
|
|
1853
|
+
if (chunk.impossible) {
|
|
1854
|
+
var randomControlKey =
|
|
1855
|
+
choice(
|
|
1856
|
+
controlProperties
|
|
1857
|
+
.map((prop) => prop.key?.value)
|
|
1858
|
+
.filter((x) => x && typeof x === "string")
|
|
1859
|
+
) || controlGen.generate();
|
|
1860
|
+
|
|
1861
|
+
deadCodeChoices = deadCodeChoices.concat([
|
|
1862
|
+
ExpressionStatement(
|
|
1863
|
+
AssignmentExpression(
|
|
1864
|
+
"=",
|
|
1865
|
+
Identifier(controlVar),
|
|
1866
|
+
Literal(false)
|
|
1867
|
+
)
|
|
1868
|
+
),
|
|
1869
|
+
ExpressionStatement(
|
|
1870
|
+
AssignmentExpression(
|
|
1871
|
+
"=",
|
|
1872
|
+
Identifier(controlVar),
|
|
1873
|
+
Identifier("undefined")
|
|
1874
|
+
)
|
|
1875
|
+
),
|
|
1876
|
+
ExpressionStatement(
|
|
1877
|
+
AssignmentExpression(
|
|
1878
|
+
"=",
|
|
1879
|
+
getControlMember(randomControlKey),
|
|
1880
|
+
Identifier("undefined")
|
|
1881
|
+
)
|
|
1882
|
+
),
|
|
1883
|
+
ExpressionStatement(
|
|
1884
|
+
UnaryExpression("delete", getControlMember(randomControlKey))
|
|
1885
|
+
),
|
|
1886
|
+
]);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
chunk.body.unshift(choice(deadCodeChoices));
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1053
1894
|
if (!this.isDebug) {
|
|
1054
1895
|
shuffle(cases);
|
|
1896
|
+
shuffle(controlProperties);
|
|
1055
1897
|
}
|
|
1056
1898
|
|
|
1057
1899
|
var discriminant = Template(`${stateVars.join("+")}`).single().expression;
|
|
1058
1900
|
|
|
1059
|
-
|
|
1901
|
+
objectBody.length = 0;
|
|
1902
|
+
// Perverse position of import declarations
|
|
1060
1903
|
for (var importDeclaration of importDeclarations) {
|
|
1061
|
-
|
|
1904
|
+
objectBody.push(importDeclaration);
|
|
1062
1905
|
}
|
|
1063
1906
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1907
|
+
// As well as functions are brought up
|
|
1908
|
+
for (var functionName of functionDeclarationNames) {
|
|
1909
|
+
objectBody.push(
|
|
1910
|
+
VariableDeclaration(
|
|
1911
|
+
VariableDeclarator(
|
|
1912
|
+
functionName,
|
|
1913
|
+
functionDeclarationValues.get(functionName)
|
|
1914
|
+
)
|
|
1915
|
+
)
|
|
1916
|
+
);
|
|
1070
1917
|
}
|
|
1071
1918
|
|
|
1072
|
-
var
|
|
1073
|
-
|
|
1074
|
-
cases.map((x, i) => {
|
|
1075
|
-
var statements = [];
|
|
1919
|
+
var defaultCaseIndex = getRandomInteger(0, cases.length);
|
|
1920
|
+
var switchCases: Node[] = [];
|
|
1076
1921
|
|
|
1077
|
-
|
|
1922
|
+
cases.forEach((caseObject, i) => {
|
|
1923
|
+
// Empty case OR single break statement is skipped
|
|
1924
|
+
if (
|
|
1925
|
+
caseObject.body.length === 0 ||
|
|
1926
|
+
(caseObject.body.length === 1 &&
|
|
1927
|
+
caseObject.body[0].type === "BreakStatement" &&
|
|
1928
|
+
caseObject.body[0].label?.name === switchLabel)
|
|
1929
|
+
)
|
|
1930
|
+
return;
|
|
1078
1931
|
|
|
1079
|
-
|
|
1932
|
+
var test = Literal(caseObject.state);
|
|
1933
|
+
var isEligibleForOutlining = false;
|
|
1080
1934
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1935
|
+
// Check if Control Map has this value
|
|
1936
|
+
if (!this.isDebug && controlConstantMap.has(caseObject.state)) {
|
|
1937
|
+
test = getControlMember(
|
|
1938
|
+
controlConstantMap.get(caseObject.state)?.key
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1084
1941
|
|
|
1085
|
-
|
|
1942
|
+
// Create complex test expressions for each switch case
|
|
1943
|
+
if (!this.isDebug && this.addComplexTest && chance(25)) {
|
|
1944
|
+
isEligibleForOutlining = true;
|
|
1086
1945
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1946
|
+
// case STATE+X:
|
|
1947
|
+
var stateVarIndex = getRandomInteger(0, stateVars.length);
|
|
1090
1948
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1949
|
+
var stateValues = labelToStates[caseObject.label];
|
|
1950
|
+
var difference = stateValues[stateVarIndex] - caseObject.state;
|
|
1951
|
+
|
|
1952
|
+
var conditionNodes: Node[] = [];
|
|
1953
|
+
var alreadyConditionedItems = new Set<string>();
|
|
1954
|
+
|
|
1955
|
+
// This code finds clash conditions and adds them to 'conditionNodes' array
|
|
1956
|
+
Object.keys(labelToStates).forEach((label) => {
|
|
1957
|
+
if (label !== caseObject.label) {
|
|
1958
|
+
var labelStates = labelToStates[label];
|
|
1959
|
+
var totalState = labelStates.reduce((a, b) => a + b, 0);
|
|
1095
1960
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1961
|
+
if (totalState === labelStates[stateVarIndex] - difference) {
|
|
1962
|
+
var differentIndex = labelStates.findIndex(
|
|
1963
|
+
(v, i) => v !== stateValues[i]
|
|
1964
|
+
);
|
|
1965
|
+
if (differentIndex !== -1) {
|
|
1966
|
+
var expressionAsString =
|
|
1967
|
+
stateVars[differentIndex] +
|
|
1968
|
+
"!=" +
|
|
1969
|
+
labelStates[differentIndex];
|
|
1970
|
+
if (!alreadyConditionedItems.has(expressionAsString)) {
|
|
1971
|
+
alreadyConditionedItems.add(expressionAsString);
|
|
1972
|
+
|
|
1973
|
+
conditionNodes.push(
|
|
1974
|
+
BinaryExpression(
|
|
1975
|
+
"!=",
|
|
1976
|
+
Identifier(stateVars[differentIndex]),
|
|
1977
|
+
Literal(labelStates[differentIndex])
|
|
1978
|
+
)
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
} else {
|
|
1982
|
+
conditionNodes.push(
|
|
1983
|
+
BinaryExpression(
|
|
1984
|
+
"!=",
|
|
1985
|
+
clone(discriminant),
|
|
1986
|
+
Literal(totalState)
|
|
1107
1987
|
)
|
|
1108
|
-
)
|
|
1109
|
-
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1994
|
+
// case STATE!=Y && STATE+X
|
|
1995
|
+
test = BinaryExpression(
|
|
1996
|
+
"-",
|
|
1997
|
+
Identifier(stateVars[stateVarIndex]),
|
|
1998
|
+
Literal(difference)
|
|
1999
|
+
);
|
|
2000
|
+
|
|
2001
|
+
// Use the 'conditionNodes' to not cause state clashing issues
|
|
2002
|
+
conditionNodes.forEach((conditionNode) => {
|
|
2003
|
+
test = LogicalExpression("&&", conditionNode, test);
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// A 'flagged' label has addition 'flagKey' that gets switched before jumped to
|
|
2008
|
+
if (flaggedLabels[caseObject.label]) {
|
|
2009
|
+
isEligibleForOutlining = true;
|
|
2010
|
+
|
|
2011
|
+
var { flagKey, flagValue } = flaggedLabels[caseObject.label];
|
|
2012
|
+
|
|
2013
|
+
var alternateNum: number;
|
|
2014
|
+
do {
|
|
2015
|
+
alternateNum = getRandomInteger(-1000, 1000 + chunks.length);
|
|
2016
|
+
} while (caseSelection.has(alternateNum));
|
|
2017
|
+
|
|
2018
|
+
var alternate = Literal(alternateNum);
|
|
2019
|
+
|
|
2020
|
+
// case FLAG ? <REAL> : <FAKE>:
|
|
2021
|
+
test = ConditionalExpression(
|
|
2022
|
+
getControlMember(flagKey),
|
|
2023
|
+
|
|
2024
|
+
flagValue ? test : alternate,
|
|
2025
|
+
!flagValue ? test : alternate
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// Outline this switch case test
|
|
2030
|
+
if (
|
|
2031
|
+
!this.isDebug &&
|
|
2032
|
+
this.outlineExpressions &&
|
|
2033
|
+
isEligibleForOutlining &&
|
|
2034
|
+
chance(75 - outlinesCreated - this.mangledExpressionsMade / 25)
|
|
2035
|
+
) {
|
|
2036
|
+
this.mangledExpressionsMade++;
|
|
2037
|
+
|
|
2038
|
+
// Selected a random parent node (or this node) to insert this function in
|
|
2039
|
+
var selectedControlNode = choice(allControlNodes);
|
|
2040
|
+
var selectedControlProperties =
|
|
2041
|
+
selectedControlNode.$controlProperties;
|
|
2042
|
+
var selectedControlVar = selectedControlNode.$controlVar;
|
|
2043
|
+
var selectedControlGen = selectedControlNode.$controlGen;
|
|
2044
|
+
|
|
2045
|
+
var fnKey = selectedControlGen.generate();
|
|
2046
|
+
|
|
2047
|
+
// Pass in the:
|
|
2048
|
+
// - controlVar for 'flagged labels' code check
|
|
2049
|
+
// - stateVars for 'complex test expressions'
|
|
2050
|
+
// (Check which identifiers are actually needed)
|
|
2051
|
+
var argumentList = [],
|
|
2052
|
+
watchingFor = new Set([controlVar, ...stateVars]);
|
|
2053
|
+
walk(test, [], (o, p) => {
|
|
2054
|
+
if (o.type === "Identifier" && watchingFor.has(o.name)) {
|
|
2055
|
+
watchingFor.delete(o.name);
|
|
2056
|
+
argumentList.push(Identifier(o.name));
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
|
|
2060
|
+
selectedControlProperties.push(
|
|
2061
|
+
Property(
|
|
2062
|
+
Literal(fnKey),
|
|
2063
|
+
FunctionExpression(argumentList, [ReturnStatement(test)]),
|
|
2064
|
+
true
|
|
1110
2065
|
)
|
|
1111
|
-
)
|
|
1112
|
-
|
|
1113
|
-
|
|
2066
|
+
);
|
|
2067
|
+
|
|
2068
|
+
// case control.a(control, s1, s2):
|
|
2069
|
+
test = CallExpression(
|
|
2070
|
+
getControlMember(fnKey, selectedControlVar),
|
|
2071
|
+
clone(argumentList)
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// One random case gets to be default
|
|
2076
|
+
if (!this.isDebug && i === defaultCaseIndex) test = null;
|
|
2077
|
+
|
|
2078
|
+
var testArray: Node[] = [test];
|
|
2079
|
+
if (!this.isDebug && this.addFakeTest && chance(50)) {
|
|
2080
|
+
// Add fake test
|
|
2081
|
+
// case <FAKE>:
|
|
2082
|
+
// case <REAL>:
|
|
2083
|
+
// case <FAKE>:
|
|
2084
|
+
var fakeTestCount = getRandomInteger(1, 4);
|
|
2085
|
+
for (var i = 0; i < fakeTestCount; i++) {
|
|
2086
|
+
// Create a fake test number that doesn't interfere with the actual states
|
|
2087
|
+
var fakeTestNum;
|
|
2088
|
+
do {
|
|
2089
|
+
fakeTestNum = getRandomInteger(1, 1000 + caseSelection.size);
|
|
2090
|
+
} while (caseSelection.has(fakeTestNum));
|
|
2091
|
+
|
|
2092
|
+
// Add this fake test
|
|
2093
|
+
testArray.push(Literal(fakeTestNum));
|
|
2094
|
+
}
|
|
1114
2095
|
|
|
2096
|
+
shuffle(testArray);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
testArray.forEach((test, i) => {
|
|
2100
|
+
var body = i === testArray.length - 1 ? caseObject.body : [];
|
|
2101
|
+
|
|
2102
|
+
switchCases.push(SwitchCase(test, body));
|
|
2103
|
+
});
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
// switch(state) { case ... }
|
|
2107
|
+
var switchStatement: Node = SwitchStatement(discriminant, switchCases);
|
|
2108
|
+
|
|
2109
|
+
var declarations: Node[] = [];
|
|
2110
|
+
|
|
2111
|
+
// var state = START_STATE
|
|
1115
2112
|
declarations.push(
|
|
1116
2113
|
...stateVars.map((stateVar, i) => {
|
|
1117
2114
|
return VariableDeclarator(stateVar, Literal(initStateValues[i]));
|
|
1118
2115
|
})
|
|
1119
2116
|
);
|
|
1120
2117
|
|
|
1121
|
-
|
|
1122
|
-
|
|
2118
|
+
// var control = { strings, numbers, outlined functions, etc... }
|
|
2119
|
+
var objectExpression = ObjectExpression(controlProperties);
|
|
2120
|
+
declarations.push(VariableDeclarator(controlVar, objectExpression));
|
|
1123
2121
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
2122
|
+
objectBody.push(
|
|
2123
|
+
// Use individual variable declarations instead so Stack can apply
|
|
2124
|
+
...declarations.map((declaration) =>
|
|
2125
|
+
VariableDeclaration(declaration, "var")
|
|
1127
2126
|
)
|
|
1128
2127
|
);
|
|
1129
2128
|
|
|
2129
|
+
// while (state != END_STATE) {...}
|
|
2130
|
+
var whileTest = BinaryExpression(
|
|
2131
|
+
"!=",
|
|
2132
|
+
clone(discriminant),
|
|
2133
|
+
Literal(endState)
|
|
2134
|
+
);
|
|
2135
|
+
|
|
2136
|
+
objectBody.push(
|
|
2137
|
+
WhileStatement(whileTest, [
|
|
2138
|
+
LabeledStatement(switchLabel, switchStatement),
|
|
2139
|
+
])
|
|
2140
|
+
);
|
|
2141
|
+
|
|
1130
2142
|
// mark this object for switch case obfuscation
|
|
1131
2143
|
switchStatement.$controlFlowFlattening = true;
|
|
1132
2144
|
};
|