258 lines
7.3 KiB
JavaScript
258 lines
7.3 KiB
JavaScript
const { clone } = require("../shared");
|
|
const { runPatternPass } = require("./shared-pattern-pass");
|
|
const { createSandbox, evaluateExpression, evaluateNodes, foldWrapperObjects, traverse, t } = require("./pattern-utils");
|
|
|
|
function getOpcodeTestInfo(testPath) {
|
|
if (!testPath.isBinaryExpression()) {
|
|
return null;
|
|
}
|
|
if (!["===", "=="].includes(testPath.node.operator)) {
|
|
return null;
|
|
}
|
|
|
|
if (testPath.get("left").isNumericLiteral() && testPath.get("right").isIdentifier()) {
|
|
return {
|
|
discriminant: clone(testPath.get("right").node),
|
|
discriminantName: testPath.get("right").node.name,
|
|
caseValue: clone(testPath.get("left").node)
|
|
};
|
|
}
|
|
|
|
if (testPath.get("right").isNumericLiteral() && testPath.get("left").isIdentifier()) {
|
|
return {
|
|
discriminant: clone(testPath.get("left").node),
|
|
discriminantName: testPath.get("left").node.name,
|
|
caseValue: clone(testPath.get("right").node)
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function blockify(node) {
|
|
if (!node) {
|
|
return [];
|
|
}
|
|
if (t.isBlockStatement(node)) {
|
|
return node.body.map((stmt) => clone(stmt));
|
|
}
|
|
return [clone(node)];
|
|
}
|
|
|
|
function withTrailingBreak(statements) {
|
|
if (statements.length === 0) {
|
|
return [t.breakStatement()];
|
|
}
|
|
const last = statements[statements.length - 1];
|
|
if (t.isBreakStatement(last) || t.isReturnStatement(last) || t.isThrowStatement(last) || t.isContinueStatement(last)) {
|
|
return statements;
|
|
}
|
|
return [...statements, t.breakStatement()];
|
|
}
|
|
|
|
function collectOpcodeCasesFromAlternate(node, discriminantName, cases) {
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
if (t.isIfStatement(node)) {
|
|
const info = getOpcodeTestInfo({
|
|
isBinaryExpression: () => t.isBinaryExpression(node.test),
|
|
node: node.test,
|
|
get(key) {
|
|
if (key === "left") {
|
|
return {
|
|
isNumericLiteral: () => t.isNumericLiteral(node.test.left),
|
|
isIdentifier: () => t.isIdentifier(node.test.left),
|
|
node: node.test.left
|
|
};
|
|
}
|
|
return {
|
|
isNumericLiteral: () => t.isNumericLiteral(node.test.right),
|
|
isIdentifier: () => t.isIdentifier(node.test.right),
|
|
node: node.test.right
|
|
};
|
|
}
|
|
});
|
|
|
|
if (info && info.discriminantName === discriminantName) {
|
|
cases.push(t.switchCase(info.caseValue, withTrailingBreak(blockify(node.consequent))));
|
|
collectOpcodeCasesFromAlternate(node.alternate, discriminantName, cases);
|
|
return;
|
|
}
|
|
|
|
cases.push(t.switchCase(null, withTrailingBreak(blockify(node))));
|
|
return;
|
|
}
|
|
|
|
if (t.isBlockStatement(node)) {
|
|
node.body.forEach((stmt) => {
|
|
if (t.isIfStatement(stmt)) {
|
|
const info = getOpcodeTestInfo({
|
|
isBinaryExpression: () => t.isBinaryExpression(stmt.test),
|
|
node: stmt.test,
|
|
get(key) {
|
|
if (key === "left") {
|
|
return {
|
|
isNumericLiteral: () => t.isNumericLiteral(stmt.test.left),
|
|
isIdentifier: () => t.isIdentifier(stmt.test.left),
|
|
node: stmt.test.left
|
|
};
|
|
}
|
|
return {
|
|
isNumericLiteral: () => t.isNumericLiteral(stmt.test.right),
|
|
isIdentifier: () => t.isIdentifier(stmt.test.right),
|
|
node: stmt.test.right
|
|
};
|
|
}
|
|
});
|
|
|
|
if (info && info.discriminantName === discriminantName) {
|
|
cases.push(t.switchCase(info.caseValue, withTrailingBreak(blockify(stmt.consequent))));
|
|
collectOpcodeCasesFromAlternate(stmt.alternate, discriminantName, cases);
|
|
return;
|
|
}
|
|
}
|
|
|
|
cases.push(t.switchCase(null, withTrailingBreak([clone(stmt)])));
|
|
});
|
|
return;
|
|
}
|
|
|
|
cases.push(t.switchCase(null, withTrailingBreak([clone(node)])));
|
|
}
|
|
|
|
function convertOpcodeIfChains(ast) {
|
|
let changed = false;
|
|
|
|
traverse(ast, {
|
|
IfStatement(path) {
|
|
const info = getOpcodeTestInfo(path.get("test"));
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
const cases = [
|
|
t.switchCase(info.caseValue, withTrailingBreak(blockify(path.node.consequent)))
|
|
];
|
|
collectOpcodeCasesFromAlternate(path.node.alternate, info.discriminantName, cases);
|
|
path.replaceWith(t.switchStatement(info.discriminant, cases));
|
|
path.skip();
|
|
changed = true;
|
|
}
|
|
});
|
|
|
|
return changed;
|
|
}
|
|
|
|
function stripPseudoForLoops(ast) {
|
|
let changed = false;
|
|
|
|
traverse(ast, {
|
|
ForStatement(path) {
|
|
const testPath = path.get("test");
|
|
if (
|
|
!testPath.isBinaryExpression() ||
|
|
path.node.update !== null ||
|
|
!(
|
|
testPath.get("left").isNumericLiteral() ||
|
|
testPath.get("right").isNumericLiteral()
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (path.node.init) {
|
|
path.replaceWith(clone(path.node.init));
|
|
} else {
|
|
path.remove();
|
|
}
|
|
changed = true;
|
|
}
|
|
});
|
|
|
|
return changed;
|
|
}
|
|
|
|
function xiaohongshuWrapperPass(ast) {
|
|
let changed = false;
|
|
const body = ast.program.body;
|
|
|
|
if (
|
|
body.length >= 3 &&
|
|
body[1].type === "VariableDeclaration" &&
|
|
body[1].declarations.length > 0 &&
|
|
t.isIdentifier(body[1].declarations[0].id)
|
|
) {
|
|
const sandbox = createSandbox();
|
|
const decoderName = body[1].declarations[0].id.name;
|
|
|
|
if (evaluateNodes(body.slice(0, 3), sandbox) && typeof sandbox[decoderName] === "function") {
|
|
ast.program.body = body.slice(3).map((node) => clone(node));
|
|
changed = true;
|
|
|
|
const aliasNames = new Set([decoderName]);
|
|
let progress = false;
|
|
do {
|
|
progress = false;
|
|
|
|
traverse(ast, {
|
|
VariableDeclarator(path) {
|
|
if (!path.get("id").isIdentifier() || !path.get("init").isIdentifier()) {
|
|
return;
|
|
}
|
|
if (aliasNames.has(path.node.init.name) && !aliasNames.has(path.node.id.name)) {
|
|
aliasNames.add(path.node.id.name);
|
|
progress = true;
|
|
}
|
|
},
|
|
CallExpression(path) {
|
|
if (!path.get("callee").isIdentifier() || !aliasNames.has(path.node.callee.name) || path.node.arguments.length !== 1) {
|
|
return;
|
|
}
|
|
const result = evaluateExpression(`${decoderName}(${path.get("arguments.0").toString()})`, sandbox);
|
|
if (!result.ok) {
|
|
return;
|
|
}
|
|
path.replaceWith(t.valueToNode(result.value));
|
|
progress = true;
|
|
changed = true;
|
|
}
|
|
});
|
|
} while (progress);
|
|
|
|
traverse(ast, {
|
|
VariableDeclarator(path) {
|
|
if (!path.get("id").isIdentifier() || !path.get("init").isIdentifier()) {
|
|
return;
|
|
}
|
|
if (aliasNames.has(path.node.id.name) && aliasNames.has(path.node.init.name)) {
|
|
path.remove();
|
|
changed = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
let localChanged = false;
|
|
do {
|
|
({ changed: localChanged } = foldWrapperObjects(ast));
|
|
if (localChanged) {
|
|
changed = true;
|
|
}
|
|
} while (localChanged);
|
|
|
|
if (convertOpcodeIfChains(ast)) {
|
|
changed = true;
|
|
}
|
|
|
|
if (stripPseudoForLoops(ast)) {
|
|
changed = true;
|
|
}
|
|
|
|
return { ast, changed };
|
|
}
|
|
|
|
runPatternPass(xiaohongshuWrapperPass);
|