|
|
"""
|
|
|
主程序入口
|
|
|
"""
|
|
|
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:]))
|