252 lines
8.7 KiB
JavaScript
252 lines
8.7 KiB
JavaScript
/**
|
||
* 移除死代码分支
|
||
* 处理 if ("xxx" === "xxx") 和 if ("xxx" !== "yyy") 等模式
|
||
*/
|
||
const fs = require('fs');
|
||
const babel = require('@babel/core');
|
||
const traverse = require('@babel/traverse').default;
|
||
const generate = require('@babel/generator').default;
|
||
const t = require('@babel/types');
|
||
|
||
const inputPath = 'D:/temp/破解/cursorpro-0.4.5/deobfuscated_full/extension/out/webview/provider.js';
|
||
const outputPath = 'D:/temp/破解/cursorpro-0.4.5/deobfuscated_full/extension/out/webview/provider.js';
|
||
|
||
console.log('╔════════════════════════════════════════════════════╗');
|
||
console.log('║ 死代码移除工具 ║');
|
||
console.log('╚════════════════════════════════════════════════════╝');
|
||
|
||
const code = fs.readFileSync(inputPath, 'utf8');
|
||
console.log(`读取文件: ${(code.length / 1024).toFixed(2)} KB`);
|
||
|
||
console.log('\n[1] 解析 AST...');
|
||
let ast;
|
||
try {
|
||
ast = babel.parseSync(code, {
|
||
sourceType: 'script',
|
||
plugins: []
|
||
});
|
||
console.log('AST 解析成功');
|
||
} catch (e) {
|
||
console.error('AST 解析失败:', e.message);
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log('\n[2] 移除死代码分支...');
|
||
let removedBranches = 0;
|
||
|
||
// 评估常量条件
|
||
function evaluateCondition(node) {
|
||
// "xxx" === "xxx" -> true
|
||
if (t.isBinaryExpression(node, { operator: '===' }) ||
|
||
t.isBinaryExpression(node, { operator: '==' })) {
|
||
if (t.isStringLiteral(node.left) && t.isStringLiteral(node.right)) {
|
||
return node.left.value === node.right.value;
|
||
}
|
||
}
|
||
|
||
// "xxx" !== "yyy" -> true if different
|
||
if (t.isBinaryExpression(node, { operator: '!==' }) ||
|
||
t.isBinaryExpression(node, { operator: '!=' })) {
|
||
if (t.isStringLiteral(node.left) && t.isStringLiteral(node.right)) {
|
||
return node.left.value !== node.right.value;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
traverse(ast, {
|
||
IfStatement(path) {
|
||
const result = evaluateCondition(path.node.test);
|
||
|
||
if (result === true) {
|
||
// 条件永远为 true,保留 consequent,移除 if 结构
|
||
if (t.isBlockStatement(path.node.consequent)) {
|
||
path.replaceWithMultiple(path.node.consequent.body);
|
||
} else {
|
||
path.replaceWith(path.node.consequent);
|
||
}
|
||
removedBranches++;
|
||
} else if (result === false) {
|
||
// 条件永远为 false,保留 alternate 或移除整个 if
|
||
if (path.node.alternate) {
|
||
if (t.isBlockStatement(path.node.alternate)) {
|
||
path.replaceWithMultiple(path.node.alternate.body);
|
||
} else {
|
||
path.replaceWith(path.node.alternate);
|
||
}
|
||
} else {
|
||
path.remove();
|
||
}
|
||
removedBranches++;
|
||
}
|
||
},
|
||
|
||
ConditionalExpression(path) {
|
||
const result = evaluateCondition(path.node.test);
|
||
|
||
if (result === true) {
|
||
path.replaceWith(path.node.consequent);
|
||
removedBranches++;
|
||
} else if (result === false) {
|
||
path.replaceWith(path.node.alternate);
|
||
removedBranches++;
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log(`移除了 ${removedBranches} 个死代码分支`);
|
||
|
||
console.log('\n[3] 简化剩余控制流对象...');
|
||
let simplifiedProps = 0;
|
||
|
||
// 再次处理剩余的控制流对象
|
||
const objectMappings = new Map();
|
||
|
||
traverse(ast, {
|
||
VariableDeclarator(path) {
|
||
if (!t.isIdentifier(path.node.id)) return;
|
||
if (!t.isObjectExpression(path.node.init)) return;
|
||
|
||
const varName = path.node.id.name;
|
||
const props = {};
|
||
|
||
for (const prop of path.node.init.properties) {
|
||
if (!t.isObjectProperty(prop)) continue;
|
||
|
||
let key;
|
||
if (t.isStringLiteral(prop.key)) {
|
||
key = prop.key.value;
|
||
} else if (t.isIdentifier(prop.key)) {
|
||
key = prop.key.name;
|
||
} else {
|
||
continue;
|
||
}
|
||
|
||
if (t.isFunctionExpression(prop.value)) {
|
||
const func = prop.value;
|
||
if (func.body.body.length === 1 && t.isReturnStatement(func.body.body[0])) {
|
||
const ret = func.body.body[0].argument;
|
||
|
||
if (t.isBinaryExpression(ret) && func.params.length === 2) {
|
||
const [p1, p2] = func.params;
|
||
if (t.isIdentifier(ret.left) && t.isIdentifier(ret.right) &&
|
||
ret.left.name === p1.name && ret.right.name === p2.name) {
|
||
props[key] = { type: 'binary', operator: ret.operator };
|
||
}
|
||
} else if (t.isLogicalExpression(ret) && func.params.length === 2) {
|
||
const [p1, p2] = func.params;
|
||
if (t.isIdentifier(ret.left) && t.isIdentifier(ret.right) &&
|
||
ret.left.name === p1.name && ret.right.name === p2.name) {
|
||
props[key] = { type: 'logical', operator: ret.operator };
|
||
}
|
||
} else if (t.isCallExpression(ret)) {
|
||
if (t.isIdentifier(ret.callee) && ret.callee.name === func.params[0]?.name) {
|
||
props[key] = { type: 'call', argCount: ret.arguments.length };
|
||
}
|
||
}
|
||
}
|
||
} else if (t.isStringLiteral(prop.value)) {
|
||
props[key] = { type: 'string', value: prop.value.value };
|
||
}
|
||
}
|
||
|
||
if (Object.keys(props).length > 0) {
|
||
objectMappings.set(varName, props);
|
||
}
|
||
}
|
||
});
|
||
|
||
traverse(ast, {
|
||
CallExpression(path) {
|
||
const callee = path.node.callee;
|
||
if (!t.isMemberExpression(callee)) return;
|
||
if (!t.isIdentifier(callee.object)) return;
|
||
|
||
const objName = callee.object.name;
|
||
const mapping = objectMappings.get(objName);
|
||
if (!mapping) return;
|
||
|
||
let propName;
|
||
if (t.isStringLiteral(callee.property)) {
|
||
propName = callee.property.value;
|
||
} else if (t.isIdentifier(callee.property) && !callee.computed) {
|
||
propName = callee.property.name;
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
const propInfo = mapping[propName];
|
||
if (!propInfo) return;
|
||
|
||
const args = path.node.arguments;
|
||
|
||
if (propInfo.type === 'binary' && args.length === 2) {
|
||
path.replaceWith(t.binaryExpression(propInfo.operator, args[0], args[1]));
|
||
simplifiedProps++;
|
||
} else if (propInfo.type === 'logical' && args.length === 2) {
|
||
path.replaceWith(t.logicalExpression(propInfo.operator, args[0], args[1]));
|
||
simplifiedProps++;
|
||
} else if (propInfo.type === 'call' && args.length >= 1) {
|
||
const fn = args[0];
|
||
const fnArgs = args.slice(1);
|
||
path.replaceWith(t.callExpression(fn, fnArgs));
|
||
simplifiedProps++;
|
||
}
|
||
},
|
||
|
||
MemberExpression(path) {
|
||
if (path.parent && t.isCallExpression(path.parent) && path.parent.callee === path.node) {
|
||
return;
|
||
}
|
||
|
||
if (!t.isIdentifier(path.node.object)) return;
|
||
|
||
const objName = path.node.object.name;
|
||
const mapping = objectMappings.get(objName);
|
||
if (!mapping) return;
|
||
|
||
let propName;
|
||
if (t.isStringLiteral(path.node.property)) {
|
||
propName = path.node.property.value;
|
||
} else if (t.isIdentifier(path.node.property) && !path.node.computed) {
|
||
propName = path.node.property.name;
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
const propInfo = mapping[propName];
|
||
if (!propInfo) return;
|
||
|
||
if (propInfo.type === 'string') {
|
||
path.replaceWith(t.stringLiteral(propInfo.value));
|
||
simplifiedProps++;
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log(`简化了 ${simplifiedProps} 处属性访问`);
|
||
|
||
console.log('\n[4] 生成代码...');
|
||
const output = generate(ast, {
|
||
comments: false,
|
||
compact: false,
|
||
concise: false
|
||
}, code);
|
||
|
||
// 后处理
|
||
let finalCode = output.code;
|
||
finalCode = finalCode.replace(/\["([a-zA-Z_$][a-zA-Z0-9_$]*)"\]/g, '.$1');
|
||
finalCode = finalCode.replace(/'(enumerable|value|get|writable|configurable)':/g, '$1:');
|
||
|
||
console.log('\n[5] 保存文件...');
|
||
fs.writeFileSync(outputPath, finalCode);
|
||
console.log(`保存到: ${outputPath}`);
|
||
console.log(`新文件大小: ${(finalCode.length / 1024).toFixed(2)} KB`);
|
||
|
||
// 统计
|
||
const deadCodeRemaining = (finalCode.match(/"[A-Za-z]{5}"\s*===\s*"[A-Za-z]{5}"/g) || []).length;
|
||
console.log(`\n剩余死代码条件: ${deadCodeRemaining}`);
|
||
|
||
console.log('\n✅ 完成!');
|