You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
主程序入口
"""
import argparse
import sys
from pathlib import Path
from typing import List, Set
from .models import CppcheckIssue
from .parsers import parse_cppcheck_xml, parse_cppcheck_text
from .analysis import (
analyze_project_structure,
filter_and_clean_issues,
write_cleaned_report,
get_enhanced_issue_analysis
)
from .generation import (
generate_test_for_issue,
smart_select_issues,
write_issue_output
)
from .verification import (
auto_verify_tests,
generate_verification_report,
generate_json_report
)
def main(argv: list[str]) -> int:
parser = argparse.ArgumentParser(description="根据 cppcheck XML 与源码生成可运行的 C++ 复现用例")
parser.add_argument("report", help="cppcheck 报告路径:支持 XML--xml或文本日志自动识别或 --text")
parser.add_argument("--out", default="cppcheck_tests", help="输出目录,默认 cppcheck_tests")
parser.add_argument("--model", default="deepseek-chat", help="模型名称,默认 deepseek-chat")
parser.add_argument("--emit-runner", action="store_true", help="为每个用例生成一键编译运行的 PowerShell 脚本")
parser.add_argument("--text", action="store_true", help="强制按文本日志格式解析")
parser.add_argument("--xml", action="store_true", help="强制按 XML 格式解析")
parser.add_argument("--max", type=int, default=10, help="最多处理前 N 条问题(默认 10设为 0 表示不限)")
parser.add_argument(
"--severities",
default="warning,error",
help="过滤等级,逗号分隔(如 warning,error,information,note默认 warning,error",
)
parser.add_argument(
"--include-ids",
default="",
help="仅包含这些 ruleId逗号分隔留空表示不限",
)
parser.add_argument(
"--exclude-ids",
default="missingInclude,missingIncludeSystem,toomanyconfigs,normalCheckLevelMaxBranches,checkLevelNormal,unknown",
help="排除这些 ruleId逗号分隔默认排除若干低价值项",
)
parser.add_argument(
"--smart-select",
action="store_true",
help="使用AI智能选择最有代表性的测试用例推荐用于大量问题",
)
parser.add_argument(
"--smart-max",
type=int,
default=10,
help="智能选择模式下的最大测试用例数量默认10",
)
parser.add_argument(
"--auto-verify",
action="store_true",
help="生成测试用例后自动运行验证并生成结果报告",
)
parser.add_argument(
"--verify-timeout",
type=int,
default=30,
help="验证超时时间默认30",
)
parser.add_argument(
"--verify-tests",
action="store_true",
help="生成测试用例时立即验证每个测试用例的有效性",
)
parser.add_argument(
"--use-templates",
action="store_true",
help="使用预定义的测试用例模板确保能有效触发cppcheck检测",
)
parser.add_argument(
"--project-root",
help="原始项目根目录路径(用于包含头文件和依赖)",
)
parser.add_argument(
"--include-dirs",
help="额外的头文件包含目录(逗号分隔)",
)
parser.add_argument(
"--integration-test",
action="store_true",
help="生成集成测试用例(需要原始项目)",
)
parser.add_argument(
"--enhanced-analysis",
action="store_true",
help="启用增强分析模式,基于代码上下文和项目结构进行智能筛选",
)
parser.add_argument(
"--clean-report",
action="store_true",
help="生成清理后的cppcheck报告文件过滤掉不可靠的问题",
)
parser.add_argument(
"--cleaned-report",
help="使用已清理的报告文件(跳过问题过滤步骤)",
)
args = parser.parse_args(argv)
# 处理报告文件路径
if args.cleaned_report:
# 使用已清理的报告文件
report_path = Path(args.cleaned_report).expanduser().resolve()
if not report_path.exists():
raise SystemExit(f"找不到已清理的报告文件: {report_path}")
print(f"使用已清理的报告文件: {report_path}")
else:
# 使用原始报告文件
report_path = Path(args.report).expanduser().resolve()
if not report_path.exists():
raise SystemExit(f"找不到报告文件: {report_path}")
# 解析报告文件
issues: List[CppcheckIssue] = []
if args.xml or (report_path.suffix.lower() in {".xml"} and not args.text):
issues = parse_cppcheck_xml(report_path)
else:
issues = parse_cppcheck_text(report_path)
print(f"原始报告包含 {len(issues)} 个问题")
# 基本过滤:按严重级别、包含/排除的 ruleId、去重
sev_set: Set[str] = {s.strip().lower() for s in (args.severities or "").split(",") if s.strip()}
include_ids: Set[str] = {s.strip() for s in (args.include_ids or "").split(",") if s.strip()}
exclude_ids: Set[str] = {s.strip() for s in (args.exclude_ids or "").split(",") if s.strip()}
filtered: List[CppcheckIssue] = []
seen: Set[tuple] = set()
for iss in issues:
if sev_set and iss.severity and iss.severity.lower() not in sev_set:
continue
if include_ids and iss.id not in include_ids:
continue
if exclude_ids and iss.id in exclude_ids:
continue
# 以 (id, first_file, first_line) 去重
key = (iss.id, str(iss.locations[0].file_path) if iss.locations else "", iss.locations[0].line if iss.locations else None)
if key in seen:
continue
seen.add(key)
filtered.append(iss)
print(f"基本过滤后剩余 {len(filtered)} 个问题")
if not filtered:
print("未在报告中发现问题项。")
return 0
# 处理项目上下文
project_root = None
include_dirs = []
project_info = None
if args.project_root:
project_root = Path(args.project_root).expanduser().resolve()
if not project_root.exists():
print(f"警告: 项目根目录不存在: {project_root}")
project_root = None
else:
print("正在分析项目结构...")
project_info = analyze_project_structure(project_root)
print(f"项目分析完成: 发现 {len(project_info['source_files'])} 个源文件, {len(project_info['header_files'])} 个头文件")
if args.include_dirs:
include_dirs = [d.strip() for d in args.include_dirs.split(",") if d.strip()]
valid_include_dirs = []
for include_dir in include_dirs:
include_path = Path(include_dir).expanduser().resolve()
if include_path.exists():
valid_include_dirs.append(str(include_path))
else:
print(f"警告: 头文件目录不存在: {include_path}")
include_dirs = valid_include_dirs
# 问题过滤和清理
if args.clean_report and not args.cleaned_report:
print("\n" + "="*50)
print("开始问题过滤和清理...")
print("="*50)
cleaned_issues = filter_and_clean_issues(filtered, project_info)
# 生成清理后的报告文件
cleaned_report_path = Path(args.out) / "cleaned_cppcheck_report.txt"
write_cleaned_report(cleaned_issues, cleaned_report_path)
print(f"\n清理完成!")
print(f"原始问题数量: {len(issues)}")
print(f"基本过滤后: {len(filtered)}")
print(f"智能清理后: {len(cleaned_issues)}")
print(f"清理后的报告已保存: {cleaned_report_path}")
# 使用清理后的问题继续处理
filtered = cleaned_issues
elif args.enhanced_analysis:
# 使用增强分析进行智能筛选
print("\n" + "="*50)
print("开始增强分析...")
print("="*50)
cleaned_issues = filter_and_clean_issues(filtered, project_info)
filtered = cleaned_issues
# 智能选择模式
if args.smart_select or args.enhanced_analysis:
if args.enhanced_analysis:
print(f"启用增强分析模式,从 {len(filtered)} 个问题中选择最多 {args.smart_max} 个最有代表性的测试用例...")
else:
print(f"启用AI智能选择模式{len(filtered)} 个问题中选择最多 {args.smart_max} 个最有代表性的测试用例...")
issues = smart_select_issues(filtered, args.smart_max, args.model)
else:
# 传统模式:简单限制数量
if args.max and args.max > 0:
issues = filtered[: args.max]
else:
issues = filtered
output_dir = Path(args.out).expanduser().resolve()
# 为每个问题生成增强的测试用例
for idx, issue in enumerate(issues, start=1):
print(f"生成测试用例 {idx}/{len(issues)}: {issue.id}")
# 获取增强的问题分析
code_context, relevance_analysis = get_enhanced_issue_analysis(issue, project_info)
# 显示分析结果
print(f" 相关性分数: {relevance_analysis['relevance_score']}, 置信度: {relevance_analysis['confidence']}%")
if code_context.function_name:
print(f" 所在函数: {code_context.function_name}")
if code_context.class_name:
print(f" 所在类: {code_context.class_name}")
# 使用AI生成模式这是核心功能
content = generate_test_for_issue(
issue,
model=args.model,
project_root=project_root,
include_dirs=include_dirs,
integration_test=args.integration_test,
code_context=code_context,
relevance_analysis=relevance_analysis
)
out_path = write_issue_output(output_dir, idx, issue, content, emit_runner=args.emit_runner, verify=args.verify_tests)
print(f" 已生成: {out_path}")
print(f"完成,共生成 {len(issues)} 条用例说明。")
# 自动验证
if args.auto_verify:
print("\n" + "="*50)
print("开始自动验证测试用例...")
print("="*50)
verification_results = auto_verify_tests(output_dir, args.verify_timeout, project_root, include_dirs)
# 生成报告
print("\n生成验证报告...")
md_report = generate_verification_report(output_dir, verification_results)
json_report = generate_json_report(output_dir, verification_results)
print(f"Markdown报告: {md_report}")
print(f"JSON报告: {json_report}")
# 显示汇总
summary = verification_results["summary"]
print(f"\n验证汇总:")
print(f" 总测试用例: {summary['total']}")
print(f" 编译成功: {summary['compiled']}")
print(f" 执行成功: {summary['executed']}")
print(f" 漏洞确认: {summary['vulnerabilities_confirmed']}")
print(f" 验证超时: {summary['timeouts']}")
print(f" 验证错误: {summary['errors']}")
# 显示确认的漏洞
confirmed_vulns = [r for r in verification_results["results"] if r["vulnerability_confirmed"]]
if confirmed_vulns:
print(f"\n确认的漏洞 ({len(confirmed_vulns)} 个):")
for result in confirmed_vulns:
print(f"{result['file']}: {result['vulnerability_type']}")
else:
print("\n未确认任何漏洞")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))