98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
import os
|
||
import xml.etree.ElementTree as ET
|
||
from collections import defaultdict
|
||
import argparse
|
||
import json
|
||
|
||
def analyze_analyze_xml(xml_dir: str):
|
||
print(f"开始分析................")
|
||
func_info = {}
|
||
reverse_calls = defaultdict(list)
|
||
|
||
for fname in os.listdir(xml_dir):
|
||
if not fname.endswith(".xml"):
|
||
continue
|
||
|
||
fpath = os.path.join(xml_dir, fname)
|
||
try:
|
||
tree = ET.parse(fpath)
|
||
root = tree.getroot()
|
||
except Exception as e:
|
||
print(f"[WARN] 无法解析 XML 文件: {fpath}, 错误: {e}")
|
||
continue
|
||
|
||
for memberdef in root.findall(".//memberdef[@kind='function']"):
|
||
name = memberdef.findtext('name')
|
||
if not name:
|
||
continue
|
||
|
||
location = memberdef.find('location')
|
||
decl_file = location.attrib.get('file', '') if location is not None else ''
|
||
decl_line = location.attrib.get('line', '') if location is not None else ''
|
||
body_file = location.attrib.get('bodyfile', '') if location is not None else ''
|
||
body_line = location.attrib.get('bodystart', '') if location is not None else ''
|
||
|
||
# 替换为映射后的路径(比如 /opt/output -> /files)
|
||
adjusted_path = fpath.replace("/opt/output", "/files")
|
||
|
||
# 初始化函数信息
|
||
if name not in func_info:
|
||
func_info[name] = {
|
||
"function": name,
|
||
"declaration": f"{decl_file}:{decl_line}" if decl_file else "unknown",
|
||
"definition": f"{body_file}:{body_line}" if body_file else "unknown",
|
||
"calls": [],
|
||
"called_by": [],
|
||
"call_count": 0,
|
||
"called_count": 0,
|
||
"xml_file": adjusted_path
|
||
}
|
||
|
||
# 当前函数调用了哪些函数
|
||
for ref in memberdef.findall("references"):
|
||
callee = ref.text
|
||
if callee and callee != name:
|
||
func_info[name]["calls"].append(callee)
|
||
reverse_calls[callee].append(name)
|
||
|
||
# 当前函数被哪些函数调用
|
||
for ref_by in memberdef.findall("referencedby"):
|
||
caller = ref_by.text
|
||
if caller and caller != name:
|
||
func_info[name]["called_by"].append(caller)
|
||
reverse_calls[name].append(caller)
|
||
|
||
# 更新调用次数统计
|
||
for name, data in func_info.items():
|
||
data["call_count"] = len(set(data["calls"]))
|
||
data["called_count"] = len(set(data["called_by"] + reverse_calls.get(name, [])))
|
||
|
||
# 处理未定义但被调用的函数
|
||
for func, callers in reverse_calls.items():
|
||
if func not in func_info:
|
||
func_info[func] = {
|
||
"function": func,
|
||
"declaration": "unknown",
|
||
"definition": "unknown",
|
||
"calls": [],
|
||
"called_by": list(set(callers)),
|
||
"call_count": 0,
|
||
"called_count": len(set(callers)),
|
||
"xml_file": "unknown"
|
||
}
|
||
print(f"分析结束,正在生成结果................")
|
||
return list(func_info.values())
|
||
|
||
def export_json(results, path):
|
||
with open(path, "w", encoding="utf-8") as f:
|
||
json.dump(results, f, indent=2, ensure_ascii=False)
|
||
print(f"✅ 已导出 JSON 文件:{path}")
|
||
|
||
if __name__ == "__main__":
|
||
parser = argparse.ArgumentParser(description="分析 analyze XML,输出 JSON 格式函数调用统计")
|
||
parser.add_argument("xml_dir", help="analyze 生成的 XML 文件夹路径")
|
||
parser.add_argument("--json", required=True, help="输出 JSON 文件路径")
|
||
args = parser.parse_args()
|
||
results = analyze_analyze_xml(args.xml_dir)
|
||
export_json(results, args.json)
|