js-confuser 2.0.0-alpha.2 → 2.0.0-alpha.4
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/.prettierrc +4 -0
- package/CHANGELOG.md +42 -8
- package/Migration.md +23 -8
- package/README.md +2 -2
- package/dist/constants.js +11 -2
- package/dist/index.js +49 -6
- package/dist/obfuscator.js +121 -10
- package/dist/order.js +0 -1
- package/dist/probability.js +1 -96
- package/dist/templates/getGlobalTemplate.js +4 -1
- package/dist/templates/integrityTemplate.js +1 -1
- package/dist/templates/stringCompressionTemplate.js +3 -3
- package/dist/templates/tamperProtectionTemplates.js +1 -1
- package/dist/templates/template.js +17 -12
- package/dist/transforms/controlFlowFlattening.js +112 -83
- package/dist/transforms/deadCode.js +21 -22
- package/dist/transforms/dispatcher.js +62 -37
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +5 -0
- package/dist/transforms/extraction/objectExtraction.js +1 -2
- package/dist/transforms/finalizer.js +1 -1
- package/dist/transforms/flatten.js +2 -19
- package/dist/transforms/identifier/globalConcealing.js +3 -4
- package/dist/transforms/identifier/movedDeclarations.js +12 -5
- package/dist/transforms/identifier/renameVariables.js +40 -6
- package/dist/transforms/lock/integrity.js +9 -1
- package/dist/transforms/lock/lock.js +16 -9
- package/dist/transforms/minify.js +64 -27
- package/dist/transforms/opaquePredicates.js +6 -7
- package/dist/transforms/pack.js +32 -5
- package/dist/transforms/plugin.js +20 -39
- package/dist/transforms/preparation.js +25 -36
- package/dist/transforms/renameLabels.js +1 -2
- package/dist/transforms/rgf.js +36 -16
- package/dist/transforms/shuffle.js +10 -11
- package/dist/transforms/string/stringCompression.js +14 -10
- package/dist/transforms/string/stringConcealing.js +7 -5
- package/dist/transforms/string/stringEncoding.js +4 -2
- package/dist/transforms/string/stringSplitting.js +4 -2
- package/dist/transforms/variableMasking.js +3 -2
- package/dist/utils/NameGen.js +5 -2
- package/dist/utils/PredicateGen.js +62 -0
- package/dist/utils/ast-utils.js +24 -9
- package/dist/utils/random-utils.js +10 -0
- package/dist/validateOptions.js +2 -2
- package/index.d.ts +16 -2
- package/package.json +2 -2
- package/src/constants.ts +15 -5
- package/src/index.ts +15 -5
- package/src/obfuscationResult.ts +7 -1
- package/src/obfuscator.ts +152 -12
- package/src/options.ts +26 -8
- package/src/order.ts +0 -2
- package/src/templates/getGlobalTemplate.ts +5 -1
- package/src/templates/integrityTemplate.ts +14 -19
- package/src/templates/stringCompressionTemplate.ts +4 -28
- package/src/templates/tamperProtectionTemplates.ts +7 -3
- package/src/templates/template.ts +5 -3
- package/src/transforms/controlFlowFlattening.ts +139 -83
- package/src/transforms/deadCode.ts +27 -30
- package/src/transforms/dispatcher.ts +24 -5
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +10 -1
- package/src/transforms/extraction/objectExtraction.ts +1 -2
- package/src/transforms/finalizer.ts +1 -1
- package/src/transforms/flatten.ts +3 -22
- package/src/transforms/identifier/globalConcealing.ts +26 -17
- package/src/transforms/identifier/movedDeclarations.ts +18 -6
- package/src/transforms/identifier/renameVariables.ts +48 -6
- package/src/transforms/lock/integrity.ts +11 -1
- package/src/transforms/lock/lock.ts +26 -10
- package/src/transforms/minify.ts +85 -38
- package/src/transforms/opaquePredicates.ts +6 -9
- package/src/transforms/pack.ts +41 -5
- package/src/transforms/plugin.ts +47 -69
- package/src/transforms/preparation.ts +33 -46
- package/src/transforms/renameLabels.ts +1 -2
- package/src/transforms/rgf.ts +52 -23
- package/src/transforms/shuffle.ts +28 -26
- package/src/transforms/string/encoding.ts +1 -1
- package/src/transforms/string/stringCompression.ts +22 -13
- package/src/transforms/string/stringConcealing.ts +13 -7
- package/src/transforms/string/stringEncoding.ts +6 -2
- package/src/transforms/string/stringSplitting.ts +9 -4
- package/src/transforms/variableMasking.ts +2 -2
- package/src/utils/NameGen.ts +13 -3
- package/src/utils/PredicateGen.ts +61 -0
- package/src/utils/ast-utils.ts +16 -9
- package/src/utils/random-utils.ts +14 -0
- package/src/validateOptions.ts +7 -4
- package/src/probability.ts +0 -110
- package/src/transforms/functionOutlining.ts +0 -225
- package/src/utils/ControlObject.ts +0 -141
|
@@ -3,7 +3,6 @@ import { NodePath } from "@babel/traverse";
|
|
|
3
3
|
import Template from "../../templates/template";
|
|
4
4
|
import { PluginArg, PluginObject } from "../plugin";
|
|
5
5
|
import { Order } from "../../order";
|
|
6
|
-
import { computeProbabilityMap } from "../../probability";
|
|
7
6
|
import { ok } from "assert";
|
|
8
7
|
import { BufferToStringTemplate } from "../../templates/bufferToStringTemplate";
|
|
9
8
|
import { createGetGlobalTemplate } from "../../templates/getGlobalTemplate";
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
import { CustomStringEncoding } from "../../options";
|
|
23
22
|
import { createDefaultStringEncoding } from "./encoding";
|
|
24
23
|
import { numericLiteral } from "../../utils/node";
|
|
24
|
+
import { NO_REMOVE } from "../../constants";
|
|
25
25
|
|
|
26
26
|
interface StringConcealingInterface {
|
|
27
27
|
encodingImplementation: CustomStringEncoding;
|
|
@@ -38,6 +38,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
38
38
|
const me = Plugin(Order.StringConcealing, {
|
|
39
39
|
changeData: {
|
|
40
40
|
strings: 0,
|
|
41
|
+
decryptionFunctions: 0,
|
|
41
42
|
},
|
|
42
43
|
});
|
|
43
44
|
|
|
@@ -93,6 +94,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
93
94
|
encoding.code = new Template(encoding.code);
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
me.changeData.decryptionFunctions++;
|
|
96
98
|
encodingImplementations[encoding.identity] = encoding;
|
|
97
99
|
|
|
98
100
|
return encoding;
|
|
@@ -139,7 +141,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
139
141
|
|
|
140
142
|
// Check user setting
|
|
141
143
|
if (
|
|
142
|
-
!computeProbabilityMap(
|
|
144
|
+
!me.computeProbabilityMap(
|
|
143
145
|
me.options.stringConcealing,
|
|
144
146
|
originalValue
|
|
145
147
|
)
|
|
@@ -271,17 +273,21 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
271
273
|
ok(encodingImplementation.code instanceof Template);
|
|
272
274
|
|
|
273
275
|
// The decoder function
|
|
274
|
-
const decoder = encodingImplementation.code
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
const decoder = encodingImplementation.code
|
|
277
|
+
.addSymbols(NO_REMOVE)
|
|
278
|
+
.compile({
|
|
279
|
+
fnName: decodeFnName,
|
|
280
|
+
__bufferToStringFunction__: bufferToStringName,
|
|
281
|
+
});
|
|
278
282
|
|
|
279
283
|
// The main function to get the string value
|
|
280
284
|
const retrieveFunctionDeclaration = new Template(`
|
|
281
285
|
function ${fnName}(index) {
|
|
282
286
|
return ${decodeFnName}(${stringArrayName}[index]);
|
|
283
287
|
}
|
|
284
|
-
`)
|
|
288
|
+
`)
|
|
289
|
+
.addSymbols(NO_REMOVE)
|
|
290
|
+
.single<t.FunctionDeclaration>();
|
|
285
291
|
|
|
286
292
|
prepend(block, [...decoder, retrieveFunctionDeclaration]);
|
|
287
293
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PluginInstance, PluginObject } from "../plugin";
|
|
2
2
|
import * as t from "@babel/types";
|
|
3
3
|
import { choice } from "../../utils/random-utils";
|
|
4
|
-
import { computeProbabilityMap } from "../../probability";
|
|
5
4
|
import { GEN_NODE, NodeSymbol } from "../../constants";
|
|
5
|
+
import { isModuleImport } from "../../utils/ast-utils";
|
|
6
6
|
|
|
7
7
|
function pad(x: string, len: number): string {
|
|
8
8
|
while (x.length < len) {
|
|
@@ -51,10 +51,14 @@ export default (me: PluginInstance): PluginObject => {
|
|
|
51
51
|
visitor: {
|
|
52
52
|
StringLiteral: {
|
|
53
53
|
exit(path) {
|
|
54
|
+
// Ignore module imports
|
|
55
|
+
if (isModuleImport(path)) return;
|
|
56
|
+
|
|
54
57
|
const { value } = path.node;
|
|
55
58
|
|
|
56
59
|
// Allow percentages
|
|
57
|
-
if (!computeProbabilityMap(me.options.stringEncoding, value))
|
|
60
|
+
if (!me.computeProbabilityMap(me.options.stringEncoding, value))
|
|
61
|
+
return;
|
|
58
62
|
|
|
59
63
|
var type = choice(["hexadecimal", "unicode"]);
|
|
60
64
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { PluginArg,
|
|
1
|
+
import { PluginArg, PluginObject } from "../plugin";
|
|
2
2
|
import { getRandomInteger, splitIntoChunks } from "../../utils/random-utils";
|
|
3
|
-
import { computeProbabilityMap } from "../../probability";
|
|
4
3
|
import { binaryExpression, stringLiteral } from "@babel/types";
|
|
5
4
|
import { ok } from "assert";
|
|
6
5
|
import { Order } from "../../order";
|
|
7
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ensureComputedExpression,
|
|
8
|
+
isModuleImport,
|
|
9
|
+
} from "../../utils/ast-utils";
|
|
8
10
|
|
|
9
11
|
export default ({ Plugin }: PluginArg): PluginObject => {
|
|
10
12
|
const me = Plugin(Order.StringSplitting, {
|
|
@@ -19,6 +21,9 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
19
21
|
exit(path) {
|
|
20
22
|
var object = path.node;
|
|
21
23
|
|
|
24
|
+
// Don't change module imports
|
|
25
|
+
if (isModuleImport(path)) return;
|
|
26
|
+
|
|
22
27
|
var size = Math.round(
|
|
23
28
|
Math.max(6, object.value.length / getRandomInteger(3, 8))
|
|
24
29
|
);
|
|
@@ -32,7 +37,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
if (
|
|
35
|
-
!computeProbabilityMap(me.options.stringSplitting, object.value)
|
|
40
|
+
!me.computeProbabilityMap(me.options.stringSplitting, object.value)
|
|
36
41
|
) {
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
@@ -2,7 +2,6 @@ import { Binding, NodePath } from "@babel/traverse";
|
|
|
2
2
|
import { PluginArg, PluginObject } from "./plugin";
|
|
3
3
|
import * as t from "@babel/types";
|
|
4
4
|
import Template from "../templates/template";
|
|
5
|
-
import { computeProbabilityMap } from "../probability";
|
|
6
5
|
import { Order } from "../order";
|
|
7
6
|
import {
|
|
8
7
|
NodeSymbol,
|
|
@@ -65,7 +64,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
65
64
|
|
|
66
65
|
const functionName = getFunctionName(fnPath);
|
|
67
66
|
|
|
68
|
-
if (!computeProbabilityMap(me.options.variableMasking, functionName)) {
|
|
67
|
+
if (!me.computeProbabilityMap(me.options.variableMasking, functionName)) {
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -155,6 +154,7 @@ export default ({ Plugin }: PluginArg): PluginObject => {
|
|
|
155
154
|
fnPath.traverse({
|
|
156
155
|
Identifier(path) {
|
|
157
156
|
if (!isVariableIdentifier(path)) return;
|
|
157
|
+
if (fnPath.get("id") === path) return; // Skip this function's name (Test #21)
|
|
158
158
|
|
|
159
159
|
if (reservedIdentifiers.has(path.node.name)) return;
|
|
160
160
|
if (me.options.globalVariables.has(path.node.name)) return;
|
package/src/utils/NameGen.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { ok } from "assert";
|
|
2
2
|
import { ObfuscateOptions } from "../options";
|
|
3
3
|
import { alphabeticalGenerator, createZeroWidthGenerator } from "./gen-utils";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
choice,
|
|
6
|
+
getRandomChineseString,
|
|
7
|
+
getRandomHexString,
|
|
8
|
+
getRandomInteger,
|
|
9
|
+
} from "./random-utils";
|
|
6
10
|
import { reservedKeywords, reservedObjectPrototype } from "../constants";
|
|
11
|
+
import Obfuscator from "../obfuscator";
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Generate random names for variables and properties.
|
|
@@ -33,7 +38,9 @@ export class NameGen {
|
|
|
33
38
|
return value;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
var mode = computeProbabilityMap(
|
|
41
|
+
var mode = Obfuscator.prototype.computeProbabilityMap(
|
|
42
|
+
this.identifierGenerator
|
|
43
|
+
);
|
|
37
44
|
|
|
38
45
|
const randomizedLength = getRandomInteger(6, 8);
|
|
39
46
|
|
|
@@ -68,6 +75,9 @@ export class NameGen {
|
|
|
68
75
|
case "zeroWidth":
|
|
69
76
|
return this.zeroWidthGenerator.generate();
|
|
70
77
|
|
|
78
|
+
case "chinese":
|
|
79
|
+
return getRandomChineseString(randomizedLength);
|
|
80
|
+
|
|
71
81
|
default:
|
|
72
82
|
throw new Error(
|
|
73
83
|
"Invalid identifier generator mode: " + this.identifierGenerator
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NodePath } from "@babel/traverse";
|
|
2
|
+
import { PluginInstance } from "../transforms/plugin";
|
|
3
|
+
import * as t from "@babel/types";
|
|
4
|
+
import { prepend } from "./ast-utils";
|
|
5
|
+
import { NameGen } from "./NameGen";
|
|
6
|
+
import Template from "../templates/template";
|
|
7
|
+
|
|
8
|
+
export default class PredicateGen {
|
|
9
|
+
constructor(public plugin: PluginInstance) {}
|
|
10
|
+
|
|
11
|
+
dummyFunctionName: string | null = null;
|
|
12
|
+
programPath: NodePath<t.Program> | null = null;
|
|
13
|
+
|
|
14
|
+
ensureCreated() {
|
|
15
|
+
if (this.dummyFunctionName) return;
|
|
16
|
+
|
|
17
|
+
this.dummyFunctionName = this.plugin.getPlaceholder("dummyFunction");
|
|
18
|
+
|
|
19
|
+
// Insert dummy function
|
|
20
|
+
prepend(
|
|
21
|
+
this.programPath,
|
|
22
|
+
|
|
23
|
+
this.plugin.skip(
|
|
24
|
+
t.functionDeclaration(
|
|
25
|
+
t.identifier(this.dummyFunctionName),
|
|
26
|
+
[],
|
|
27
|
+
t.blockStatement([])
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
generateTrueExpression(path: NodePath): t.Expression {
|
|
34
|
+
return t.unaryExpression("!", this.generateFalseExpression(path));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
generateFalseExpression(path: NodePath): t.Expression {
|
|
38
|
+
this.programPath = path.find((p) => p.isProgram()) as NodePath<t.Program>;
|
|
39
|
+
this.ensureCreated();
|
|
40
|
+
|
|
41
|
+
// Overcomplicated way to get a random property name that doesn't exist on the Function
|
|
42
|
+
var randomProperty: string;
|
|
43
|
+
var nameGen = new NameGen("randomized");
|
|
44
|
+
|
|
45
|
+
function PrototypeCollision() {}
|
|
46
|
+
PrototypeCollision(); // Call it for code coverage :D
|
|
47
|
+
|
|
48
|
+
do {
|
|
49
|
+
randomProperty = nameGen.generate();
|
|
50
|
+
} while (
|
|
51
|
+
!randomProperty ||
|
|
52
|
+
PrototypeCollision[randomProperty] !== undefined
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return this.plugin.skip(
|
|
56
|
+
new Template(
|
|
57
|
+
`"${randomProperty}" in ${this.dummyFunctionName}`
|
|
58
|
+
).expression()
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -312,17 +312,21 @@ export function prepend(
|
|
|
312
312
|
// Preserve import declarations
|
|
313
313
|
// Filter out import declarations
|
|
314
314
|
const body = listParent.get("body");
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
let afterImport = 0;
|
|
316
|
+
for (var stmt of body) {
|
|
317
|
+
if (!stmt.isImportDeclaration()) {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
afterImport++;
|
|
321
|
+
}
|
|
318
322
|
|
|
319
|
-
if (
|
|
320
|
-
// No
|
|
323
|
+
if (afterImport === 0) {
|
|
324
|
+
// No import declarations, so we can safely unshift everything
|
|
321
325
|
return registerPaths(listParent.unshiftContainer("body", nodes));
|
|
322
|
-
} else {
|
|
323
|
-
// Insert the nodes after the last import declaration
|
|
324
|
-
return registerPaths(body[lastImportIndex - 1].insertAfter(nodes));
|
|
325
326
|
}
|
|
327
|
+
|
|
328
|
+
// Insert the nodes after the last import declaration
|
|
329
|
+
return registerPaths(body[afterImport - 1].insertAfter(nodes));
|
|
326
330
|
}
|
|
327
331
|
|
|
328
332
|
if (listParent.isFunction()) {
|
|
@@ -343,7 +347,9 @@ export function prepend(
|
|
|
343
347
|
|
|
344
348
|
if (listParent.isBlock()) {
|
|
345
349
|
return registerPaths(listParent.unshiftContainer("body", nodes));
|
|
346
|
-
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (listParent.isSwitchCase()) {
|
|
347
353
|
return registerPaths(listParent.unshiftContainer("consequent", nodes));
|
|
348
354
|
}
|
|
349
355
|
|
|
@@ -356,6 +362,7 @@ export function prependProgram(
|
|
|
356
362
|
) {
|
|
357
363
|
var program = path.find((p) => p.isProgram());
|
|
358
364
|
ok(program);
|
|
365
|
+
ok(program.isProgram());
|
|
359
366
|
return prepend(program, ...nodes);
|
|
360
367
|
}
|
|
361
368
|
|
|
@@ -41,6 +41,20 @@ export function getRandomHexString(length: number) {
|
|
|
41
41
|
.toUpperCase();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @see https://github.com/MichaelXF/js-confuser/issues/150#issuecomment-2466159582
|
|
46
|
+
*/
|
|
47
|
+
export function getRandomChineseString(length: number) {
|
|
48
|
+
const characters: string[] = [];
|
|
49
|
+
for (let i = 0; i < length; i++)
|
|
50
|
+
characters.push(
|
|
51
|
+
String.fromCharCode(
|
|
52
|
+
Math.floor(Math.random() * (0x9fff - 0x4e00)) + 0x4e00
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
return characters.join("");
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
/**
|
|
45
59
|
* Returns a random string.
|
|
46
60
|
*/
|
package/src/validateOptions.ts
CHANGED
|
@@ -190,10 +190,6 @@ export function applyDefaultsToOptions(
|
|
|
190
190
|
"alert",
|
|
191
191
|
"confirm",
|
|
192
192
|
"location",
|
|
193
|
-
"btoa",
|
|
194
|
-
"atob",
|
|
195
|
-
"unescape",
|
|
196
|
-
"encodeURIComponent",
|
|
197
193
|
].forEach((x) => options.globalVariables.add(x));
|
|
198
194
|
} else {
|
|
199
195
|
// node
|
|
@@ -248,7 +244,14 @@ export function applyDefaultsToOptions(
|
|
|
248
244
|
"Uint8Array",
|
|
249
245
|
"Uint16Array",
|
|
250
246
|
"Uint32Array",
|
|
247
|
+
"Int8Array",
|
|
248
|
+
"Int16Array",
|
|
249
|
+
"Int32Array",
|
|
251
250
|
"ArrayBuffer",
|
|
251
|
+
"btoa",
|
|
252
|
+
"atob",
|
|
253
|
+
"unescape",
|
|
254
|
+
"encodeURIComponent",
|
|
252
255
|
].forEach((x) => options.globalVariables.add(x));
|
|
253
256
|
}
|
|
254
257
|
|
package/src/probability.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { ok } from "assert";
|
|
2
|
-
import { createObject } from "./utils/object-utils";
|
|
3
|
-
import { ProbabilityMap } from "./options";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Evaluates a ProbabilityMap.
|
|
7
|
-
* @param map The setting object.
|
|
8
|
-
* @param customFnArgs Args given to user-implemented function, such as a variable name.
|
|
9
|
-
*/
|
|
10
|
-
export function computeProbabilityMap<
|
|
11
|
-
T,
|
|
12
|
-
F extends (...args: any[]) => any = (...args: any[]) => any
|
|
13
|
-
>(
|
|
14
|
-
map: ProbabilityMap<T, F>,
|
|
15
|
-
...customImplementationArgs: F extends (...args: infer P) => any ? P : never
|
|
16
|
-
): boolean | string {
|
|
17
|
-
if (!map) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
if (map === true || map === 1) {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
if (typeof map === "number") {
|
|
24
|
-
return Math.random() < map;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (typeof map === "function") {
|
|
28
|
-
return (map as Function)(...customImplementationArgs);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (typeof map === "string") {
|
|
32
|
-
return map;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
var asObject: { [mode: string]: number } = {};
|
|
36
|
-
if (Array.isArray(map)) {
|
|
37
|
-
map.forEach((x: any) => {
|
|
38
|
-
asObject[x.toString()] = 1;
|
|
39
|
-
});
|
|
40
|
-
} else {
|
|
41
|
-
asObject = map as any;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
var total = Object.values(asObject).reduce((a, b) => a + b);
|
|
45
|
-
var percentages = createObject(
|
|
46
|
-
Object.keys(asObject),
|
|
47
|
-
Object.values(asObject).map((x) => x / total)
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
var ticket = Math.random();
|
|
51
|
-
|
|
52
|
-
var count = 0;
|
|
53
|
-
var winner = null;
|
|
54
|
-
Object.keys(percentages).forEach((key) => {
|
|
55
|
-
var x = Number(percentages[key]);
|
|
56
|
-
|
|
57
|
-
if (ticket >= count && ticket < count + x) {
|
|
58
|
-
winner = key;
|
|
59
|
-
}
|
|
60
|
-
count += x;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return winner;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Determines if a probability map can return a positive result (true, or some string mode).
|
|
68
|
-
* - Negative probability maps are used to remove transformations from running entirely.
|
|
69
|
-
* @param map
|
|
70
|
-
*/
|
|
71
|
-
export function isProbabilityMapProbable<T>(map: ProbabilityMap<T>): boolean {
|
|
72
|
-
ok(!Number.isNaN(map), "Numbers cannot be NaN");
|
|
73
|
-
|
|
74
|
-
if (!map || typeof map === "undefined") {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (typeof map === "function") {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
if (typeof map === "number") {
|
|
81
|
-
if (map > 1 || map < 0) {
|
|
82
|
-
throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (Array.isArray(map)) {
|
|
86
|
-
ok(
|
|
87
|
-
map.length != 0,
|
|
88
|
-
"Empty arrays are not allowed for options. Use false instead."
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
if (map.length == 1) {
|
|
92
|
-
return !!map[0];
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (typeof map === "object") {
|
|
96
|
-
if (map instanceof Date) return true;
|
|
97
|
-
if (map instanceof RegExp) return true;
|
|
98
|
-
|
|
99
|
-
var keys = Object.keys(map);
|
|
100
|
-
ok(
|
|
101
|
-
keys.length != 0,
|
|
102
|
-
"Empty objects are not allowed for options. Use false instead."
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
if (keys.length == 1) {
|
|
106
|
-
return !!keys[0];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { NodePath } from "@babel/traverse";
|
|
2
|
-
import { PluginArg, PluginObject } from "./plugin";
|
|
3
|
-
import { Order } from "../order";
|
|
4
|
-
import { ensureComputedExpression, prepend } from "../utils/ast-utils";
|
|
5
|
-
import * as t from "@babel/types";
|
|
6
|
-
import { NameGen } from "../utils/NameGen";
|
|
7
|
-
import { chance, getRandomInteger } from "../utils/random-utils";
|
|
8
|
-
import { Binding, Visitor } from "@babel/traverse";
|
|
9
|
-
import { computeProbabilityMap } from "../probability";
|
|
10
|
-
|
|
11
|
-
function isSafeForOutlining(path: NodePath): {
|
|
12
|
-
isSafe: boolean;
|
|
13
|
-
bindings?: Binding[];
|
|
14
|
-
} {
|
|
15
|
-
if (path.isIdentifier() || path.isLiteral()) return { isSafe: false };
|
|
16
|
-
|
|
17
|
-
// Skip direct invocations ('this' will be different)
|
|
18
|
-
if (path.key === "callee" && path.parentPath.isCallExpression()) {
|
|
19
|
-
return { isSafe: false };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Skip typeof and delete expressions (identifier behavior is different)
|
|
23
|
-
if (path.key === "argument" && path.parentPath.isUnaryExpression()) {
|
|
24
|
-
return { isSafe: false };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
path.isReturnStatement() ||
|
|
29
|
-
path.isYieldExpression() ||
|
|
30
|
-
path.isAwaitExpression() ||
|
|
31
|
-
path.isContinueStatement() ||
|
|
32
|
-
path.isBreakStatement() ||
|
|
33
|
-
path.isThrowStatement() ||
|
|
34
|
-
path.isDebuggerStatement() ||
|
|
35
|
-
path.isImportDeclaration() ||
|
|
36
|
-
path.isExportDeclaration()
|
|
37
|
-
) {
|
|
38
|
-
return { isSafe: false };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
var isSafe = true;
|
|
42
|
-
var bindings: Binding[] = [];
|
|
43
|
-
var fnPath = path.getFunctionParent();
|
|
44
|
-
|
|
45
|
-
var visitor: Visitor = {
|
|
46
|
-
ThisExpression(path) {
|
|
47
|
-
isSafe = false;
|
|
48
|
-
path.stop();
|
|
49
|
-
},
|
|
50
|
-
Identifier(path) {
|
|
51
|
-
if (["arguments", "eval"].includes(path.node.name)) {
|
|
52
|
-
isSafe = false;
|
|
53
|
-
path.stop();
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
BindingIdentifier(path) {
|
|
57
|
-
var binding = path.scope.getBinding(path.node.name);
|
|
58
|
-
if (binding) {
|
|
59
|
-
bindings.push(binding);
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
// Function flow guard
|
|
63
|
-
"ReturnStatement|YieldExpression|AwaitExpression"(path) {
|
|
64
|
-
if (path.getFunctionParent() === fnPath) {
|
|
65
|
-
isSafe = false;
|
|
66
|
-
path.stop();
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Exclude 'ThisExpression' and semantic 'Identifier' nodes
|
|
72
|
-
if (visitor[path.type]) return { isSafe: false };
|
|
73
|
-
|
|
74
|
-
path.traverse(visitor);
|
|
75
|
-
|
|
76
|
-
return { isSafe, bindings };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export default ({ Plugin }: PluginArg): PluginObject => {
|
|
80
|
-
const me = Plugin(Order.FunctionOutlining, {
|
|
81
|
-
changeData: {
|
|
82
|
-
functionsMade: 0,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
var changesMade = 0;
|
|
87
|
-
|
|
88
|
-
function checkProbability() {
|
|
89
|
-
if (!computeProbabilityMap(me.options.functionOutlining)) return false;
|
|
90
|
-
|
|
91
|
-
if (changesMade > 100 && chance(changesMade - 100)) return false;
|
|
92
|
-
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
visitor: {
|
|
98
|
-
Block: {
|
|
99
|
-
exit(blockPath) {
|
|
100
|
-
if (blockPath.isProgram()) {
|
|
101
|
-
blockPath.scope.crawl();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (blockPath.find((p) => me.isSkipped(p))) return;
|
|
105
|
-
|
|
106
|
-
if (!checkProbability()) return;
|
|
107
|
-
|
|
108
|
-
// Extract a random number of statements
|
|
109
|
-
|
|
110
|
-
var statements = blockPath.get("body");
|
|
111
|
-
// var startIndex = getRandomInteger(0, statements.length);
|
|
112
|
-
// var endIndex = getRandomInteger(startIndex, statements.length);
|
|
113
|
-
|
|
114
|
-
var startIndex = 0;
|
|
115
|
-
var endIndex = statements.length;
|
|
116
|
-
|
|
117
|
-
var extractedStatements = statements.slice(startIndex, endIndex);
|
|
118
|
-
if (!extractedStatements.length) return;
|
|
119
|
-
|
|
120
|
-
var bindings: Binding[] = [];
|
|
121
|
-
|
|
122
|
-
for (var statement of extractedStatements) {
|
|
123
|
-
// Don't override the control node
|
|
124
|
-
if (me.isSkipped(statement)) return;
|
|
125
|
-
|
|
126
|
-
var result = isSafeForOutlining(statement);
|
|
127
|
-
if (!result.isSafe) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
bindings.push(...result.bindings);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const extractedStatementSet = new Set<NodePath>(extractedStatements);
|
|
135
|
-
|
|
136
|
-
for (var binding of bindings) {
|
|
137
|
-
for (var referencePath of binding.referencePaths) {
|
|
138
|
-
var found = referencePath.find((p) =>
|
|
139
|
-
extractedStatementSet.has(p)
|
|
140
|
-
);
|
|
141
|
-
if (!found) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
for (var constantViolation of binding.constantViolations) {
|
|
146
|
-
var found = constantViolation.find((p) =>
|
|
147
|
-
extractedStatementSet.has(p)
|
|
148
|
-
);
|
|
149
|
-
if (!found) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
changesMade++;
|
|
156
|
-
|
|
157
|
-
var isFirst = true;
|
|
158
|
-
for (var statement of extractedStatements) {
|
|
159
|
-
if (isFirst) {
|
|
160
|
-
isFirst = false;
|
|
161
|
-
var memberExpression = me
|
|
162
|
-
.getControlObject(blockPath)
|
|
163
|
-
.addProperty(
|
|
164
|
-
t.functionExpression(
|
|
165
|
-
null,
|
|
166
|
-
[],
|
|
167
|
-
t.blockStatement(extractedStatements.map((x) => x.node))
|
|
168
|
-
)
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
me.changeData.functionsMade++;
|
|
172
|
-
|
|
173
|
-
var callExpression = t.callExpression(memberExpression, []);
|
|
174
|
-
|
|
175
|
-
statement.replaceWith(callExpression);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
statement.remove();
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
Expression: {
|
|
183
|
-
exit(path) {
|
|
184
|
-
// Skip assignment left
|
|
185
|
-
if (
|
|
186
|
-
path.find(
|
|
187
|
-
(p) =>
|
|
188
|
-
p.key === "left" &&
|
|
189
|
-
p.parentPath?.type === "AssignmentExpression"
|
|
190
|
-
)
|
|
191
|
-
) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (!checkProbability()) return;
|
|
196
|
-
|
|
197
|
-
if (path.find((p) => me.isSkipped(p))) return;
|
|
198
|
-
if (!isSafeForOutlining(path).isSafe) return;
|
|
199
|
-
|
|
200
|
-
changesMade++;
|
|
201
|
-
|
|
202
|
-
var blockPath = path.find((p) => p.isBlock()) as NodePath<t.Block>;
|
|
203
|
-
|
|
204
|
-
var memberExpression = me
|
|
205
|
-
.getControlObject(blockPath)
|
|
206
|
-
.addProperty(
|
|
207
|
-
t.functionExpression(
|
|
208
|
-
null,
|
|
209
|
-
[],
|
|
210
|
-
t.blockStatement([t.returnStatement(t.cloneNode(path.node))])
|
|
211
|
-
)
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
me.changeData.functionsMade++;
|
|
215
|
-
|
|
216
|
-
var callExpression = t.callExpression(memberExpression, []);
|
|
217
|
-
|
|
218
|
-
ensureComputedExpression(path);
|
|
219
|
-
path.replaceWith(callExpression);
|
|
220
|
-
me.skip(path);
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
};
|