☰
Pysa 端到端集成测试完全指南:基于 pyre-check 的污点分析测试体系详解
2026/9/29 2:30:13 网站建设 项目流程
  • 静态分析
  • 开发工具
  • 代码质量

【免费下载链接】pyre-check

Performant type-checking for python.

项目地址:https://gitcode.com/gh_mirrors/py/pyre-check
点击查看免费下载

导读

Pysa(Python Static Analyzer)是 pyre-check 仓库内置的污点分析引擎,用于检测数据从 source 流向 sink 的安全漏洞。本文围绕.llms/skills/pysa-integration-tests/SKILL.md这份操作手册,系统讲解 Pysa 端到端集成测试的完整工作流:从运行命令、测试文件结构、失败调试,到更新期望输出与新建测试用例,并结合仓库源码(integrationTest.ml 与 testHelper.ml)深入剖析底层机制。读完本文,你将能够在 pyre-check 仓库中独立运行、调试、更新与创建 Pysa 集成测试。

Pysa 集成测试概览

Pysa 的端到端集成测试用于验证污点分析引擎的完整工作链路:每个测试是一个位于source/interprocedural_analyses/taint/test/integration/目录下的.py文件,测试运行完整污点分析流水线(类型检查 → 调用图构建 → 高阶调用图 → 覆盖图 → 污点不动点求解),并将输出与期望文件(.models、.cg、.hofcg、.overrides)逐字比较。

从源码结构看,测试入口位于 integrationTest.ml:当设置了PYSA_INTEGRATION_TEST环境变量时,仅运行指定的单个测试;否则通过TestHelper.end_to_end_test_paths扫描目录下所有*.py文件批量执行。核心的比对逻辑集中在 testHelper.ml 的end_to_end_integration_test函数中,它负责读取被测源码、解析模型与配置、初始化环境、执行分析并生成/比对期望文件。

运行测试

所有命令必须在source/目录下执行(dune exec依赖当前目录定位测试根路径):

cd source # 运行全部测试(16 个分片并行) OUNIT_SHARDS=16 dune exec interprocedural_analyses/taint/test/integrationTest.exe # 仅运行单个测试(以 format.py 为例) PYSA_INTEGRATION_TEST=format.py dune exec interprocedural_analyses/taint/test/integrationTest.exe

环境变量说明

  • OUNIT_SHARDS=16:OUnit 测试框架的分片数,用于并行加速。原文档强调,不设置该变量时全量测试会明显变慢。
  • PYSA_INTEGRATION_TEST:精确指定要运行的测试文件。从 integrationTest.ml 的源码可以看到,该变量被读取后与测试目录source/interprocedural_analyses/taint/test/integration/拼接成完整路径,再交给TestHelper.end_to_end_integration_test执行。
  • PYREFLY_BINARY:指定自定义 Pyrefly 前端二进制(见下文)。

使用 Pyrefly 前端运行

pyre-check 的污点分析流水线支持两种前端:传统的 Pyre1 前端与新一代 Pyrefly 前端。执行./facebook/scripts/setup.sh --local后,Pyrefly 成为默认后端,集成测试会自动使用source/pyrefly.exe。如需覆盖为自定义二进制:

PYREFLY_BINARY=<path-to-binary> PYSA_INTEGRATION_TEST=format.py dune exec interprocedural_analyses/taint/test/integrationTest.exe

从 testHelper.ml 可以看到,测试初始化默认以~force_pyrefly:true运行。Pyrefly 前端的一个显著差异是:它不会类型检查未被源码文件传递包含的模块(见filter_unused_test_modules_errors的注释与实现,testHelper.ml),因此测试框架会过滤掉pysa、django等初始模型模块的 "BaseModuleNotInEnvironment" 类验证错误。

测试文件结构

每个测试<name>.py可以有如下伴随文件:

文件是否必需用途
<name>.py是待分析的 Python 源码
<name>.py.pysa否Pysa 模型文件:声明 sources、sinks、TITO 等
<name>.py.config否污点配置:规则(rules)、sources、sinks、options
<name>.py.models是期望输出:污点模型与问题报告(JSON)
<name>.py.cg是期望输出:调用图(call graph)
<name>.py.hofcg是期望输出:高阶调用图(higher-order call graph)
<name>.py.overrides是期望输出:覆盖图(override graph)
<name>.py.pyrefly.models否Pyrefly 前端下的期望模型输出
<name>.py.pyrefly.cg否Pyrefly 前端下的期望调用图输出
<name>.py.pyrefly.hofcg否Pyrefly 前端下的期望高阶调用图输出
<name>.py.pyrefly.overrides否Pyrefly 前端下的期望覆盖图输出

默认测试模型机制

当测试文件同时没有.pysa模型文件和.config配置文件时,测试运行器会自动注入默认测试模型,如_test_sink、_test_source等。这一逻辑在 testHelper.ml 中体现为:

let add_initial_models = Option.is_none models_source && Option.is_none taint_configuration in

即只有两者都不存在时才启用默认模型。这些默认模型定义在initial_models_source字符串中(testHelper.ml),包括:

def pysa._test_sink(arg: TaintSink[Test, Via[special_sink]]): ... def pysa._test_source() -> TaintSource[Test, Via[special_source]]: ... def pysa._tito( *x: TaintInTaintOut, **kw: TaintInTaintOut): ... def pysa._user_controlled() -> TaintSource[UserControlled]: ... def pysa._cookies() -> TaintSource[Cookies]: ... def pysa._rce(argument: TaintSink[RemoteCodeExecution]): ... def pysa._sql(argument: TaintSink[SQL]): ... def eval(source: TaintSink[RemoteCodeExecution], /): ... pysa._global_sink: TaintSink[Test] = ... pysa.ClassWithSinkAttribute.attribute: TaintSink[Test] = ...

一旦提供了.pysa或.config中的任意一个,测试就必须自包含:即自行声明所需的全部模型与配置,不再依赖默认模型。这正是原文档强调的 "the test must be self-contained" 的源码依据。

配置与模型文件实例

一个典型的.config文件(以 add_breadcrumb_to_state.py.config 为例)包含 sources、sinks、features 与 rules 四个区块:

{ "sources": [ { "name": "Test" } ], "sinks": [ { "name": "Test" } ], "features": [ { "name": "special_sink", "comment": "From _test_sink()" }, { "name": "special_source", "comment": "From _test_source()" }, { "name": "add_breadcrumb_to_state", "comment": "From add_breadcrumb_to_state()" } ], "rules": [ { "name": "Test", "sources": ["Test"], "sinks": ["Test"], "code": 5002, "message_format": "Data from [{$sources}] source(s) may reach [{$sinks}] sink(s)" } ] }

配套的.pysa模型文件则声明具体函数与类的污点语义(add_breadcrumb_to_state.py.pysa):

def pysa._test_sink(arg: TaintSink[Test]): ... def pysa._test_source() -> TaintSource[Test]: ... @AddBreadcrumbToState(Via[add_breadcrumb_to_state]) def add_breadcrumb_to_state.add_breadcrumb_to_state(): ... @AddBreadcrumbToState(Via[add_breadcrumb_to_state]) def add_breadcrumb_to_state.BreadcrumbOnEnter.__enter__(): ...

期望输出文件示例

以 format.py.models 为例,期望输出是@generated开头的换行分隔 JSON(NDJSON),每条记录描述一个 callable 的模型信息,包括端口(port)、污点种类(kinds)与模式(modes):

@generated { "kind": "model", "data": { "callable": "builtins.eval", "filename": "builtins.pyi", "callable_line": 4540, "sinks": [ { "port": "formal(source, position=0, positional_only)", "taint": [ { "kinds": [ { "kind": "RemoteCodeExecution" } ], "declaration": null } ] } ], "modes": [ "Obscure" ] } }

调用图文件(format.py.cg)则以 JSON 映射形式列出每个 callable 的调用依赖:

@generated Call dependencies { "format.issue_in_format (fun)": [ "builtins.object.__repr__ (method)", "pysa._test_sink (fun)", "pysa._test_source (fun)" ], ... }

这些文件的生成逻辑位于 testHelper.ml:.cg由CallGraph.WholeProgramCallGraph.to_target_graph序列化而来,.hofcg由 fixpoint 状态中非空的高阶调用图聚合而成,.models则由TaintReporting.fetch_and_externalize产出并通过NewlineDelimitedJson逐行序列化。

调试测试失败

当测试因期望输出不匹配而失败时:

  1. 测试运行器会打印差异(diff),对于大型 diff 可能被截断;
  2. 它会为每个不匹配的输出创建.actual文件(例如format.py.models.actual);
  3. 使用diff命令对比期望与实际输出:
diff source/interprocedural_analyses/taint/test/integration/format.py.models \ source/interprocedural_analyses/taint/test/integration/format.py.models.actual

底层实现印证了这一流程:在 testHelper.ml 中,get_expected读取期望文件——若期望文件不存在则直接写入当前输出(首次运行自动生成);若两者相等则清理旧的.actual文件;若不等则写入.actual文件并记入divergent_files列表,最终所有差异文件统一通过error_on_actual_files打印 diff 并以断言失败收尾。

如果测试失败伴随的是类型错误或分析错误(而非输出不匹配),则问题出在 Python 源码或模型定义本身。此时应检查被测文件是否存在类型标注问题、.pysa中的模型写法是否与目标 callable 匹配、.config中的规则引用是否一致。注意initialize阶段会调用initialize_pyre_and_fail_on_errors(testHelper.ml),一旦被测源码存在类型错误,测试会直接以failwithf抛错,列出错误位置与描述。

更新期望文件

当分析逻辑被有意修改、输出变化符合预期时,需要更新期望文件:

facebook/scripts/in_path/pysa-update-expected

该脚本会自动把所有.actual文件移动到对应的期望文件位置,即用<name>.py.<ext>.actual替换<name>.py.<ext>。

务必先 review diff 再更新——运行sl diff核对变更是否符合预期,避免把意外的行为变化固化进期望文件。此外原文档特别提醒:期望文件带有@generated标记,严禁手工编辑,应一律通过pysa-update-expected更新。

创建新测试

在source/interprocedural_analyses/taint/test/integration/下新建一个测试的完整步骤:

  1. 创建<name>.py(被测 Python 源码);
  2. 可选地创建<name>.py.pysa(模型文件)与/或<name>.py.config(污点配置);
  3. 运行测试——首次运行时运行器会自动创建缺失的期望输出文件:
PYSA_INTEGRATION_TEST=<name>.py dune exec interprocedural_analyses/taint/test/integrationTest.exe

这一步的自动生成机制已在前文说明:期望文件缺失时create_expected_and_actual_files会以~initial:true直接把实际输出写入期望路径(testHelper.ml);

  1. 仔细审查生成的.models、.cg、.hofcg、.overrides四个文件,确认污点模型、调用图、高阶调用图与覆盖图符合预期;
  2. 将源码与全部伴随文件一起提交。

一个良好的参考范例是 format.py,它覆盖了 f-string 格式化场景下的污点传播(source 进入格式串、sink 出现在格式串、格式说明符中的 source/sink、以及 source→sink 的完整 issue),并且该测试不带.pysa与.config文件,完全依赖默认测试模型运行。

常见错误清单

原文档总结的常见失误如下,务必规避:

  • 环境变量名写错:正确的变量是PYSA_INTEGRATION_TEST,不是PYSA_TEST或类似名称。从 integrationTest.ml 可以看出,运行时只读取PYSA_INTEGRATION_TEST这一确切键名。
  • 忘记.py扩展名:应使用PYSA_INTEGRATION_TEST=format.py,而不是format。环境变量值会与测试目录路径直接拼接,缺少扩展名会导致文件查找失败。
  • 在错误的目录下运行:dune exec必须在source/下执行,因为测试目录常量是相对路径source/interprocedural_analyses/taint/test/integration/(integrationTest.ml),并在Test.find_pyre_source_code_root ()解析出的仓库根下拼接。
  • 猜测 OUnit 过滤参数:不要用-- format这类 OUnit 参数来过滤测试,应使用PYSA_INTEGRATION_TEST环境变量——这是唯一受支持的过滤方式。
  • 手工编辑期望文件:期望文件都是@generated生成物,应使用pysa-update-expected更新,不要直接改文件内容。
  • 忘记OUNIT_SHARDS:全量运行时不加OUNIT_SHARDS=16会慢得多;并行分片是官方推荐的运行方式。

小结

Pysa 集成测试是 pyre-check 污点分析质量保障的核心手段,其"源码 + 模型 + 配置 + 四类期望输出"的文件约定清晰、自动化程度高(首次运行自动生成期望文件、失败自动产出.actual供 diff 对比)。本文所述的运行、调试、更新与创建流程,与 integrationTest.ml 和 testHelper.ml 中的实现一一对应,读者可直接在仓库的source/interprocedural_analyses/taint/test/integration/目录中动手实践:修改一个测试的源码或模型,运行单测观察.actual的产生,再通过pysa-update-expected完成期望文件更新,即可完整走通 Pysa 集成测试的迭代闭环。

  • 静态分析
  • 开发工具
  • 代码质量

【免费下载链接】pyre-check

Performant type-checking for python.

项目地址:https://gitcode.com/gh_mirrors/py/pyre-check
点击查看免费下载

相关推荐

上一篇:终极指南:Shairport Sync音频延迟测量与同步优化方法
下一篇:Realtek RTL8125 2.5G网卡驱动终极配置指南:3步实现高效网络加速

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询