Skip to content

Commit 9a18a14

Browse files
authored
refactor: new ProbeRunner class (#195)
1 parent b74044b commit 9a18a14

22 files changed

+288
-113
lines changed

src/ProbeRunner.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Import all the probes
2+
import isUnsafeCallee from "./probes/isUnsafeCallee.js";
3+
import isLiteral from "./probes/isLiteral.js";
4+
import isLiteralRegex from "./probes/isLiteralRegex.js";
5+
import isRegexObject from "./probes/isRegexObject.js";
6+
import isVariableDeclaration from "./probes/isVariableDeclaration.js";
7+
import isRequire from "./probes/isRequire.js";
8+
import isImportDeclaration from "./probes/isImportDeclaration.js";
9+
import isMemberExpression from "./probes/isMemberExpression.js";
10+
import isArrayExpression from "./probes/isArrayExpression.js";
11+
import isFunction from "./probes/isFunction.js";
12+
import isAssignmentExpression from "./probes/isAssignmentExpression.js";
13+
import isObjectExpression from "./probes/isObjectExpression.js";
14+
import isUnaryExpression from "./probes/isUnaryExpression.js";
15+
import isWeakCrypto from "./probes/isWeakCrypto.js";
16+
import isClassDeclaration from "./probes/isClassDeclaration.js";
17+
import isMethodDefinition from "./probes/isMethodDefinition.js";
18+
19+
// Import Internal Dependencies
20+
import { SourceFile } from "./SourceFile.js";
21+
22+
/**
23+
* @typedef {Object} Probe
24+
* @property {string} name
25+
* @property {any} validateNode
26+
* @property {(node: any, options: any) => any} main
27+
* @property {(options: any) => void} teardown
28+
* @property {boolean} [breakOnMatch=false]
29+
* @property {string} [breakGroup]
30+
*/
31+
32+
export const ProbeSignals = Object.freeze({
33+
Break: Symbol.for("breakWalk"),
34+
Skip: Symbol.for("skipWalk")
35+
});
36+
37+
export class ProbeRunner {
38+
/**
39+
* Note:
40+
* The order of the table has an importance/impact on the correct execution of the probes
41+
*
42+
* @type {Probe[]}
43+
*/
44+
static Defaults = [
45+
isUnsafeCallee,
46+
isLiteral,
47+
isLiteralRegex,
48+
isRegexObject,
49+
isVariableDeclaration,
50+
isRequire,
51+
isImportDeclaration,
52+
isMemberExpression,
53+
isAssignmentExpression,
54+
isObjectExpression,
55+
isArrayExpression,
56+
isFunction,
57+
isUnaryExpression,
58+
isWeakCrypto,
59+
isClassDeclaration,
60+
isMethodDefinition
61+
];
62+
63+
/**
64+
*
65+
* @param {!SourceFile} sourceFile
66+
* @param {Probe[]} [probes=ProbeRunner.Defaults]
67+
*/
68+
constructor(
69+
sourceFile,
70+
probes = ProbeRunner.Defaults
71+
) {
72+
this.sourceFile = sourceFile;
73+
74+
// TODO: Assert/validate all probes? (Good first issue)
75+
this.probes = probes;
76+
}
77+
78+
/**
79+
* @param {!Probe} probe
80+
* @param {!any} node
81+
* @returns {null|void|Symbol}
82+
*/
83+
#runProbe(probe, node) {
84+
const validationFns = Array.isArray(probe.validateNode) ?
85+
probe.validateNode : [probe.validateNode];
86+
for (const validateNode of validationFns) {
87+
const [isMatching, data = null] = validateNode(
88+
node,
89+
this.sourceFile
90+
);
91+
92+
if (isMatching) {
93+
return probe.main(node, {
94+
analysis: this.sourceFile,
95+
data
96+
});
97+
}
98+
}
99+
100+
return null;
101+
}
102+
103+
walk(node) {
104+
/** @type {Set<string>} */
105+
const breakGroups = new Set();
106+
107+
for (const probe of this.probes) {
108+
if (breakGroups.has(probe.breakGroup)) {
109+
continue;
110+
}
111+
112+
try {
113+
const result = this.#runProbe(probe, node);
114+
if (result === null) {
115+
continue;
116+
}
117+
118+
if (result === ProbeSignals.Skip) {
119+
return "skip";
120+
}
121+
if (result === ProbeSignals.Break || probe.breakOnMatch) {
122+
const breakGroup = probe.breakGroup || null;
123+
124+
if (breakGroup === null) {
125+
break;
126+
}
127+
else {
128+
breakGroups.add(breakGroup);
129+
}
130+
}
131+
}
132+
finally {
133+
if (probe.teardown) {
134+
probe.teardown({ analysis: this.sourceFile });
135+
}
136+
}
137+
}
138+
139+
return null;
140+
}
141+
}

src/SourceFile.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { VariableTracer } from "@nodesecure/estree-ast-utils";
66
import { rootLocation, toArrayLocation } from "./utils.js";
77
import { generateWarning } from "./warnings.js";
88
import { isObfuscatedCode, hasTrojanSource } from "./obfuscators/index.js";
9-
import { runOnProbes } from "./probes/index.js";
9+
import { ProbeRunner } from "./ProbeRunner.js";
1010

1111
// CONSTANTS
1212
const kDictionaryStrParts = [
@@ -45,6 +45,7 @@ export class SourceFile {
4545
this.encodedLiterals = new Map();
4646
this.warnings = [];
4747
this.literalScores = [];
48+
this.probesRunner = new ProbeRunner(this);
4849

4950
if (hasTrojanSource(sourceCodeString)) {
5051
this.addWarning("obfuscated-code", "trojan-source");
@@ -161,7 +162,7 @@ export class SourceFile {
161162
this.inTryStatement = false;
162163
}
163164

164-
return runOnProbes(node, this);
165+
return this.probesRunner.walk(node);
165166
}
166167
}
167168

src/probes/index.js

Lines changed: 0 additions & 76 deletions
This file was deleted.

src/probes/isArrayExpression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ function main(node, { analysis }) {
2626

2727
export default {
2828
name: "isArrayExpression",
29-
validateNode, main, breakOnMatch: false
29+
validateNode,
30+
main,
31+
breakOnMatch: false
3032
};

src/probes/isAssignmentExpression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ function main(node, options) {
2525

2626
export default {
2727
name: "isAssignmentExpression",
28-
validateNode, main, breakOnMatch: false
28+
validateNode,
29+
main,
30+
breakOnMatch: false
2931
};

src/probes/isBinaryExpression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,7 @@ function walkBinaryExpression(expr, level = 1) {
4949

5050
export default {
5151
name: "isBinaryExpression",
52-
validateNode, main, breakOnMatch: false
52+
validateNode,
53+
main,
54+
breakOnMatch: false
5355
};

src/probes/isClassDeclaration.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ function main(node, options) {
2121

2222
export default {
2323
name: "isClassDeclaration",
24-
validateNode, main, breakOnMatch: false
24+
validateNode,
25+
main,
26+
breakOnMatch: false
2527
};

src/probes/isFunction.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ function main(node, options) {
3434

3535
export default {
3636
name: "isFunctionDeclaration",
37-
validateNode, main, breakOnMatch: false
37+
validateNode,
38+
main,
39+
breakOnMatch: false
3840
};

src/probes/isImportDeclaration.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@ function main(node, options) {
2626

2727
export default {
2828
name: "isImportDeclaration",
29-
validateNode, main, breakOnMatch: true, breakGroup: "import"
29+
validateNode,
30+
main,
31+
breakOnMatch: true,
32+
breakGroup: "import"
3033
};

src/probes/isLiteral.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,7 @@ function main(node, options) {
5757

5858
export default {
5959
name: "isLiteral",
60-
validateNode, main, breakOnMatch: false
60+
validateNode,
61+
main,
62+
breakOnMatch: false
6163
};

src/probes/isLiteralRegex.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ function main(node, options) {
2525

2626
export default {
2727
name: "isLiteralRegex",
28-
validateNode, main, breakOnMatch: false
28+
validateNode,
29+
main,
30+
breakOnMatch: false
2931
};

src/probes/isMemberExpression.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ function main(node, options) {
1212

1313
export default {
1414
name: "isMemberExpression",
15-
validateNode, main, breakOnMatch: true, breakGroup: "import"
15+
validateNode,
16+
main,
17+
breakOnMatch: true,
18+
breakGroup: "import"
1619
};

src/probes/isMethodDefinition.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ function main(node, options) {
2121

2222
export default {
2323
name: "isMethodDefinition",
24-
validateNode, main, breakOnMatch: false
24+
validateNode,
25+
main,
26+
breakOnMatch: false
2527
};

src/probes/isObjectExpression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@ function main(node, options) {
2525

2626
export default {
2727
name: "isObjectExpression",
28-
validateNode, main, breakOnMatch: false
28+
validateNode,
29+
main,
30+
breakOnMatch: false
2931
};

src/probes/isRegexObject.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,7 @@ function isRegexConstructor(node) {
4343

4444
export default {
4545
name: "isRegexObject",
46-
validateNode, main, breakOnMatch: false
46+
validateNode,
47+
main,
48+
breakOnMatch: false
4749
};

src/probes/isRequire.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
getCallExpressionArguments
1212
} from "@nodesecure/estree-ast-utils";
1313

14+
// Import Internal Dependencies
15+
import { ProbeSignals } from "../ProbeRunner.js";
16+
1417
function validateNode(node, { tracer }) {
1518
const id = getCallExpressionIdentifier(node);
1619
if (id === null) {
@@ -96,7 +99,7 @@ function main(node, options) {
9699
analysis.addWarning("unsafe-import", null, node.loc);
97100

98101
// We skip walking the tree to avoid anymore warnings...
99-
return Symbol.for("skipWalk");
102+
return ProbeSignals.Skip;
100103
}
101104

102105
default:
@@ -160,5 +163,8 @@ function walkRequireCallExpression(nodeToWalk, tracer) {
160163

161164
export default {
162165
name: "isRequire",
163-
validateNode, main, breakOnMatch: true, breakGroup: "import"
166+
validateNode,
167+
main,
168+
breakOnMatch: true,
169+
breakGroup: "import"
164170
};

src/probes/isUnaryExpression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ function main(node, options) {
2222

2323
export default {
2424
name: "isUnaryExpression",
25-
validateNode, main, breakOnMatch: false
25+
validateNode,
26+
main,
27+
breakOnMatch: false
2628
};

0 commit comments

Comments
 (0)