reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const parser = require("@babel/parser");
|
||||
const traverse = require("@babel/traverse").default;
|
||||
const generator = require("@babel/generator").default;
|
||||
const t = require("@babel/types");
|
||||
|
||||
function countMatches(text, regex) {
|
||||
const matches = text.match(regex);
|
||||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
function parseText(text) {
|
||||
return parser.parse(text, {
|
||||
sourceType: "unambiguous",
|
||||
plugins: ["jsx"]
|
||||
});
|
||||
}
|
||||
|
||||
function printNode(node) {
|
||||
return generator(node, {
|
||||
compact: true,
|
||||
comments: false,
|
||||
jsescOption: { minimal: true }
|
||||
}).code;
|
||||
}
|
||||
|
||||
function isLiteralLike(node) {
|
||||
return (
|
||||
t.isStringLiteral(node) ||
|
||||
t.isNumericLiteral(node) ||
|
||||
t.isBooleanLiteral(node) ||
|
||||
t.isNullLiteral(node) ||
|
||||
t.isBigIntLiteral(node) ||
|
||||
t.isIdentifier(node, { name: "undefined" })
|
||||
);
|
||||
}
|
||||
|
||||
function isOpcodeDiscriminant(node) {
|
||||
return t.isIdentifier(node) || t.isMemberExpression(node);
|
||||
}
|
||||
|
||||
function getOpcodeIfDescriptor(node) {
|
||||
if (!t.isIfStatement(node) || node.alternate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const test = node.test;
|
||||
if (!t.isBinaryExpression(test) || !["==", "===", "!=", "!=="].includes(test.operator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLiteralLike(test.left) && isOpcodeDiscriminant(test.right)) {
|
||||
return {
|
||||
discriminantKey: printNode(test.right),
|
||||
literalKey: printNode(test.left)
|
||||
};
|
||||
}
|
||||
|
||||
if (isLiteralLike(test.right) && isOpcodeDiscriminant(test.left)) {
|
||||
return {
|
||||
discriminantKey: printNode(test.left),
|
||||
literalKey: printNode(test.right)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function countOpcodeIfChains(ast) {
|
||||
const statementLists = [];
|
||||
|
||||
traverse(ast, {
|
||||
Program(path) {
|
||||
statementLists.push(path.node.body);
|
||||
},
|
||||
BlockStatement(path) {
|
||||
statementLists.push(path.node.body);
|
||||
},
|
||||
SwitchCase(path) {
|
||||
statementLists.push(path.node.consequent);
|
||||
}
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
|
||||
statementLists.forEach((statements) => {
|
||||
for (let index = 0; index < statements.length; index += 1) {
|
||||
const firstDescriptor = getOpcodeIfDescriptor(statements[index]);
|
||||
if (!firstDescriptor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const seenLiterals = new Set([firstDescriptor.literalKey]);
|
||||
let chainLength = 1;
|
||||
let cursor = index + 1;
|
||||
while (cursor < statements.length) {
|
||||
const nextDescriptor = getOpcodeIfDescriptor(statements[cursor]);
|
||||
if (!nextDescriptor || nextDescriptor.discriminantKey !== firstDescriptor.discriminantKey) {
|
||||
break;
|
||||
}
|
||||
seenLiterals.add(nextDescriptor.literalKey);
|
||||
chainLength += 1;
|
||||
cursor += 1;
|
||||
}
|
||||
|
||||
if (chainLength >= 2 && seenLiterals.size >= 2) {
|
||||
count += chainLength;
|
||||
index = cursor - 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function getReturnArgument(fnNode) {
|
||||
if (t.isArrowFunctionExpression(fnNode) && !t.isBlockStatement(fnNode.body)) {
|
||||
return fnNode.body;
|
||||
}
|
||||
|
||||
if (!t.isFunctionExpression(fnNode) && !t.isArrowFunctionExpression(fnNode)) {
|
||||
return null;
|
||||
}
|
||||
if (!t.isBlockStatement(fnNode.body) || fnNode.body.body.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [statement] = fnNode.body.body;
|
||||
return t.isReturnStatement(statement) ? statement.argument : null;
|
||||
}
|
||||
|
||||
function collectReferencedParameterNames(node) {
|
||||
const names = new Set();
|
||||
if (!node) {
|
||||
return names;
|
||||
}
|
||||
|
||||
traverse(t.file(t.program([t.expressionStatement(t.cloneNode(node, true))])), {
|
||||
noScope: true,
|
||||
Identifier(path) {
|
||||
if (!path.isReferencedIdentifier()) {
|
||||
return;
|
||||
}
|
||||
names.add(path.node.name);
|
||||
}
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
function isThinWrapperFunction(fnNode) {
|
||||
const returnArgument = getReturnArgument(fnNode);
|
||||
if (!returnArgument) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const paramNames = new Set(
|
||||
fnNode.params.filter((param) => t.isIdentifier(param)).map((param) => param.name)
|
||||
);
|
||||
if (paramNames.size === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const referencedNames = collectReferencedParameterNames(returnArgument);
|
||||
for (const name of referencedNames) {
|
||||
if (!paramNames.has(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (t.isBinaryExpression(returnArgument) || t.isLogicalExpression(returnArgument)) {
|
||||
return true;
|
||||
}
|
||||
if (t.isUnaryExpression(returnArgument) && t.isIdentifier(returnArgument.argument)) {
|
||||
return true;
|
||||
}
|
||||
if (t.isCallExpression(returnArgument)) {
|
||||
return t.isIdentifier(returnArgument.callee) || t.isMemberExpression(returnArgument.callee);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function countDispatcherWrappers(ast) {
|
||||
let count = 0;
|
||||
|
||||
traverse(ast, {
|
||||
ObjectExpression(path) {
|
||||
let thinWrapperCount = 0;
|
||||
let literalPropertyCount = 0;
|
||||
|
||||
path.get("properties").forEach((propertyPath) => {
|
||||
if (!propertyPath.isObjectProperty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valuePath = propertyPath.get("value");
|
||||
if (valuePath.isStringLiteral() || valuePath.isNumericLiteral() || valuePath.isBooleanLiteral() || valuePath.isNullLiteral()) {
|
||||
literalPropertyCount += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isThinWrapperFunction(valuePath.node)) {
|
||||
thinWrapperCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
const looksLikeDispatcherObject =
|
||||
thinWrapperCount >= 2 || (thinWrapperCount >= 1 && literalPropertyCount >= 2);
|
||||
if (looksLikeDispatcherObject) {
|
||||
count += thinWrapperCount;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
function collectMetrics(text) {
|
||||
let opcodeIfChainCount = countMatches(text, /if\s*\(\s*(?:0x[\da-f]+|\d+|["'][^"'\\]*(?:\\.[^"'\\]*)*["'])\s*={2,3}\s*[A-Za-z_$][\w$]*\s*\)/gi);
|
||||
let dispatcherWrapperCount = countMatches(text, /:\s*function\s*\([^)]*\)\s*\{\s*return\b/g);
|
||||
|
||||
try {
|
||||
const ast = parseText(text);
|
||||
opcodeIfChainCount = countOpcodeIfChains(ast);
|
||||
dispatcherWrapperCount = countDispatcherWrappers(ast);
|
||||
} catch (error) {
|
||||
// Keep regex fallbacks when the file is not parseable.
|
||||
}
|
||||
|
||||
return {
|
||||
lineCount: text.split(/\r?\n/).length,
|
||||
splitPipeCount: countMatches(text, /\.split\(\s*["']\|["']\s*\)/g),
|
||||
loopSwitchCount: countMatches(text, /(?:while|for)\s*\([^)]*\)\s*\{[\s\S]{0,120}?switch\s*\(/g),
|
||||
opcodeIfChainCount,
|
||||
dispatcherWrapperCount,
|
||||
hexIdentifierCount: countMatches(text, /\b_0x[a-f0-9]+\b/gi)
|
||||
};
|
||||
}
|
||||
|
||||
function collectMetricsFromFile(filePath) {
|
||||
const text = fs.readFileSync(filePath, "utf8");
|
||||
return {
|
||||
filePath: path.resolve(filePath),
|
||||
...collectMetrics(text)
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const [, , inputPath, outputPath = ""] = process.argv;
|
||||
if (!inputPath) {
|
||||
console.error("Usage: node collect-residue-metrics.js <input.js> [output.json]");
|
||||
process.exit(1);
|
||||
}
|
||||
const result = collectMetricsFromFile(inputPath);
|
||||
const json = JSON.stringify(result, null, 2);
|
||||
if (outputPath) {
|
||||
fs.writeFileSync(outputPath, json, "utf8");
|
||||
} else {
|
||||
process.stdout.write(`${json}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
collectMetrics,
|
||||
collectMetricsFromFile
|
||||
};
|
||||
Reference in New Issue
Block a user