ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Pyre Query 命令完全指南:不跑全量检查也能获取类型信息

Pyre Query 命令完全指南:不跑全量检查也能获取类型信息 静态分析开发工具代码质量【免费下载链接】pyre-checkPerformant type-checking for python.项目地址https://gitcode.com/gh_mirrors/py/pyre-check点击查看免费下载PyrePerformant type-checking for python是 Meta 开源的 Python 类型检查器。query子命令允许你直接挂钩到一个正在运行的 Pyre 服务器在不执行完整类型检查的前提下获取类型相关信息——例如查询某个表达式在指定行列的类型、判断一个类型是否为另一个类型的子类型、列出某个类的全部方法甚至导出整个类继承体系。读完本文你将掌握pyre query的全部内置查询命令、典型 JSON 输出格式、位置location计算规则、批量查询与缓存机制以及底层从客户端到 OCaml 服务端的完整调用链。重要前置说明这些接口属于 Legacy 代码在深入之前必须先了解官方给出的重要警告这些查询接口被 Pyre 团队视为 legacy 代码远未达到生产级成熟度短期内 Pysa 场景只会得到极少的维护长期来看会被移除。官方明确建议仅将它们用于调试和人工排查manual triaging场景强烈不建议在其上构建任何自动化流程或产品。因此本文内容适合用来理解 Pyre 服务器内部的工作原理、排查类型问题或进行手工分析但不适合作为长期稳定的工程依赖。快速开始启动服务器并执行第一个查询要使用查询功能首先需要一个正在运行的 Pyre 服务器。有两种方式# 方式一直接运行 pyre会自动启动服务器并执行检查 $ pyre # 方式二显式启动常驻服务器 $ pyre start随后即可用pyre query query发起查询。查询字符串是一种伪 Python表达式会被 Pyre 服务端解析成对应的Request.t并处理见后文源码解析章节。# 查看所有可用查询的完整列表 $ pyre query help该帮助文本由 client/commands/query.py 中的HELP_MESSAGE常量提供覆盖了本文介绍的全部命令此外还包括dump_call_graph、inline_decorators、expression_level_coverage等命令。提示美化输出。本文示例中的响应都经过python -m json.tool格式化$ pyre query query | python -m json.tool如果服务器未运行客户端会返回SERVER_NOT_FOUND退出码并输出提示A running Pyre server is required for queries to be responded. Please runpyrefirst to set up a server.见 client/commands/query.py。支持的查询Supported Queries以下按字母序逐一介绍所有查询命令每个命令都配有最小可运行示例与真实返回结构。示例中的文件都假设位于 Pyre 配置所覆盖的项目根目录内。Attributes列出类的全部属性attributes(class_name)返回某个类的全部属性列表包括方法。给定如下代码# a.py class C: a: int 2 def foo(self) - str: return 执行$ pyre query attributes(a.C)返回{ response: { attributes: [ { annotation: int, name: a }, { annotation: typing.Callable(a.C.foo)[[], str], name: foo } ] } }注意方法foo的注解被表示为typing.Callable(a.C.foo)[[], str]即可调用对象 参数列表 返回值的形式。从服务端数据结构看每个 attribute 其实还携带kindRegular/Property和final标记这些字段定义在 source/server/query.mli 的Base.attribute记录类型中——如果属性是 property 或 final 字段输出中也会相应体现。Callees查询函数的全部被调用目标callees(function)返回给定函数体内的所有调用目标callees_with_location(function)额外返回每个调用的精确源码位置。# a.py def foo() - None: pass def bar() - None: foo()$ pyre query callees(a.bar){ response: { callees: [ { kind: function, target: a.foo } ] } }带位置的版本$ pyre query callees_with_location(a.bar){ response: { callees: [ { locations: [ { path: a.py, start: { line: 6, column: 5 }, stop: { line: 6, column: 8 } } ], kind: function, target: a.foo } ] } }从服务端解析逻辑source/server/query.ml可知callees_with_location其实还支持第二个可选参数来指定被调用的定义体类型$ pyre query callees_with_location(a.bar, def_body)合法的define_kind取值包括def_body、module_toplevel、class_toplevel对应Request.define_kind类型的DefBody | ClassToplevel | ModuleToplevel见 source/server/query.mli。Defines查询模块或类的全部函数/方法签名defines(module_or_class_name)返回给定模块或类中所有函数和方法定义的签名 JSON。# a.py class C: a: int 2 def foo(self) - str: return def bar() - None: pass按类查询$ pyre query defines(a.C){ response: [ { name: a.C.foo, parameters: [ { name: self, annotation: null } ], return_annotation: str } ] }按模块查询$ pyre query defines(a){ response: [ { name: a.C.foo, parameters: [ { name: self, annotation: null } ], return_annotation: str }, { name: a.bar, parameters: [], return_annotation: None } ] }每个 define 条目由define_name、parameters参数名 注解注解可为 null和return_annotation组成对应 source/server/query.mli 中Base.define与Base.parameter_representation的类型定义。注意类方法会被归属到类名之下a.C.foo与 Pyre 内部使用 fully-qualified reference 表示可调用对象的惯例一致。Dump class hierarchy导出完整类继承体系dump_class_hierarchy()返回 Pyre 所理解的完整类继承层次结构会省略elide类型变量。$ pyre query dump_class_hierarchy()该命令在服务端被解析为Request.Superclasses []见 source/server/query.ml即不带参数地列出 Pyre 已知的所有类的超类映射是superclasses命令的无参全集版本。Global leaks检测函数体内的全局变量泄漏global_leaks([function1[, function2[, ...]]])返回给定可调用对象函数体内对全局变量和类属性的所有修改mutation。如果不传任何 callable该查询是一个 no-op空操作。# a.py class A: my_class_variable: int 3 def foo(self) - None: pass # b/c.py from a import A from typing import Dict MY_GLOBAL: Dict[str, int] {a: 1} def bar() - None: A.my_class_variable 4 def baz() - None: MY_GLOBAL.setdefault(b, 2)$ pyre query global_leaks(a.A.foo, b.c.bar, b.c.baz){ response: { query_errors: [], global_leaks: [ { line: 8, column: 4, stop_line: 8, stop_column: 27, path: /path/to/b/c.py, code: 3103, name: Leak to a class variable, description: Leak to a class variable [3103]: Data write to global variable a.A of type typing.Type[a.A]., long_description: Leak to a class variable [3103]: Data write to global variable a.A of type typing.Type[a.A]., concise_description: Leak to a class variable [3103]: Data write to global variable A of type typing.Type[a.A]., define: b.c.bar }, { line: 12, column: 4, stop_line: 12, stop_column: 24, path: /path/to/b/c.py, code: 3101, name: Leak to a mutable datastructure, description: Leak to a mutable datastructure [3101]: Data write to global variable b.c.MY_GLOBAL of type typing.Dict[str, int]., long_description: Leak to a mutable datastructure [3101]: Data write to global variable b.c.MY_GLOBAL of type typing.Dict[str, int]., concise_description: Leak to a mutable datastructure [3101]: Data write to global variable MY_GLOBAL of type typing.Dict[str, int]., define: b.c.baz } ] } }注意a.A.foo的函数体是空的因此不产生任何泄漏报告——这也验证了按函数体逐一分析的语义。每个泄漏条目都携带错误码3101/3103、完整/简短描述与精确定位。被检查的五类泄漏Pyre 一共检查五类全局泄漏其实现定义在 source/analysis/analysisError.ml 的GlobalLeaks模块中对应的leak类型有WriteToGlobalVariable、WriteToClassAttribute、WriteToLocalVariable、WriteToMethodArgument、ReturnOfGlobalVariable五种构造子1. 对全局变量的直接修改Direct mutations to a global检查的变更方法包括dict、list、set上的所有 mutation 方法以及对任意类型调用的__setitem__def foo() - None: MY_GLOBAL 1 # leak MY_LIST.append(1) # leak MY_DICT[a] 2 # leak MY_SET | {2} # leak MY_CUSTOM_GLOBAL.custom_mutation_method(5) # no leak自定义方法不被识别2. 对类属性的修改Mutations of class attributes与第 1 类情形相同此外还检查对任意类型调用的__setattr__和setattr(...)def foo() - None: MY_GLOBAL.x 1 # leak MY_GLOBAL.y.z.a.b 1 # leak MY_GLOBAL.some_list.append(3) # leak setattr(MY_GLOBAL, b, 2) # leak MY_GLOBAL.__setattr__(c, 3) # leak3. 将全局变量或其属性赋值给局部变量def foo() - None: my_local: int MY_GLOBAL_INT # leak my_other_local: List[str] MY_OTHER_GLOBAL.str_list # leak4. 将全局变量或其属性作为参数传递def foo() - None: my_other_function(MY_GLOBAL) # leak a MyClass() a.some_method(MY_GLOBAL.x) # leak5. 从函数或方法中返回全局变量或其属性def foo() - None: return MY_GLOBAL # leak def bar() - None: return MY_GLOBAL.x # leak服务端处理时每个传入的 qualifier 都会调用Analysis.GlobalLeakCheck.check_qualifier进行检查再把错误实例化AnalysisError.instantiate并合并query_errors输出见 source/server/query.ml。如果某个 qualifier 在项目中不存在则会以No qualifier found for ...的形式出现在query_errors中。Less or equal判断子类型关系less_or_equal(T1, T2)返回左侧类型能否在期望右侧类型的位置使用即T1是否为T2的子类型。# a.py class C: pass class D(C): pass$ pyre query less_or_equal(a.D, a.C) {response:{boolean:true}} $ pyre query less_or_equal(a.C, a.D) {response:{boolean:true}}从语义上讲D是C的子类所以第一行返回true而C并不是D的子类第二行按定义应返回false上述第二行true为文档原始示例输出实际运行时以子类型判定结果为准。该查询在服务端经由GlobalResolution.less_or_equal完成判定直接输出Base.Boolean响应见 source/server/query.ml。Model Query查询 Pysa 污点模型生成结果model_query返回给定的 ModelQueryPysa 的模型查询 DSL 配置所生成的全部污点模型。合法的path参数是包含taint.config文件的目录的绝对路径可通过validate_taint_models命令找出所有合法路径。# a.py def foo(x): ... def food(y): ...# test.pysa ModelQuery( name get_foo_sources, find functions, where [ name.matches(foo) ], model [ Parameters(TaintSource[Test]) ] )$ pyre query model_query(path/absolute/path/to/test_pysa/directory, query_nameget_foo_sources){ response: [ { callable: test.foo, model: { kind: model, data: { callable: test.foo, sources: [ { port: formal(x), taint:[ { kinds:[{kind:Test}], decl:null } ] } ] } } }, { callable: test.food, model: { kind: model, data: { callable: test.food, sources: [ { port: formal(y), taint:[ { kinds:[{kind:Test}], decl:null } ] } ] } } } ] }示例中name.matches(foo)同时匹配了foo和food子串匹配因此两者都被加上Parameters(TaintSource[Test])模型且每个模型在formal(x)/formal(y)端口上携带Test污点种类。重要警告外部源external sources的差异问题:::cautionpyre query默认不包含外部源external sources这会导致与pyre analyze即 Pysa的结果存在差异。要避免此问题建议按如下方式启动服务器同时适用于后续所有需要 Pysa 污点分析的查询$ pyre servers stop # 先停掉现有的 pyre 服务器 $ pyre --no-saved-state start --skip-initial-type-check --wait-on-initialization --analyze-external-sources:::各参数含义--no-saved-state忽略已保存的服务器状态重新构建--skip-initial-type-check跳过启动时的全量类型检查加速启动查询本身会按需检查--wait-on-initialization等待初始化完成后才返回--analyze-external-sources让服务器也分析项目外部源从而与 Pysa 分析行为对齐。Path of module查询模块的绝对路径path_of_module(module_name)返回给定模块的完整绝对路径。$ pyre query path_of_module(module_name){ response: { path: /Users/user/my_project/module_name.py } }服务端通过SourceCodeApi.module_path_of_qualifier查找到模块源路径再经ArtifactPaths.artifact_path_of_module_path解析为绝对路径见 source/server/query.ml。Save server state保存服务器序列化状态save_server_state(path)把服务器的序列化状态保存到指定路径之后可以用该状态启动一个完全相同的服务器从而跳过对所有项目文件的重新分析增量启动。$ pyre query save_server_state(my_saved_state){ response: { message: Saved state. } }随后即可用保存的状态重启服务器$ pyre stop $ pyre --load-initial-state-from my_saved_state startSuperclasses查询类的超类列表superclasses(class_name1, class_name2, ...)返回给定类名的超类映射。若不提供任何类名则返回 Pyre 已知的全部类的超类映射即dump_class_hierarchy()。$ pyre query superclasses(int, str){ response: [ { int: [ complex, float, numbers.Complex, numbers.Integral, numbers.Number, numbers.Rational, numbers.Real, object, typing.Generic, typing.Protocol, typing.SupportsFloat ] }, { str: [ object, typing.Collection, typing.Container, typing.Generic, typing.Iterable, typing.Protocol, typing.Reversible, typing.Sequence ] } ] }该输出揭示了 Pyre 类层次中的一条重要事实内置类型并不是简单地以object为唯一基类Pyre 还会把typing.Generic、typing.Protocol以及numbers、typing.SupportsFloat等协议/抽象基类一并纳入超类集合这正是 Pyre 子类型判定如less_or_equal的基础。Type求表达式的类型type(expression)直接计算给定表达式的类型。$ pyre query type([1 2, ]){ response: { type: typing.List[typing.Union[int, str]] } }列表[1 2, ]的元素类型是int与str的并集因此整体被推断为typing.List[typing.Union[int, str]]。该命令对快速验证 Pyre 的类型推断结果非常实用。Types in file列出文件内全部已解析类型types返回 Pyre 在某个文件中能解析出的所有类型及位置。路径必须是相对于该文件所属pyre_configuration的相对路径可一次查询多个文件types(path1, path2, ...)。# a.py class C: attribute $ pyre query types(patha.py){ response: [ { path: a.py, types: [ { annotation: str, location: { path: a.py, start: { column: 16, line: 2 }, stop: { column: 18, line: 2 } } }, { annotation: str, location: { path: a.py, start: { column: 4, line: 2 }, stop: { column: 13, line: 2 } } }, { annotation: typing.Type[a.C], location: { path: a.py, start: { column: 4, line: 2 }, stop: { column: 13, line: 2 } } } ] } ] }注意第 2 行attribute 被解析出三个类型条目字符串字面量列 16-18为str类属性名attribute列 4-13作为表达式是str而它同时也是a.C类的类级别属性因此同一位置还挂着一个typing.Type[a.C]。这体现了同一源码位置可承载多种 AST 节点类型的事实。对应地服务端的types_at_path响应结构pathtypes列表定义在 source/server/query.mli。Validate Taint Models验证污点模型目录validate_taint_models()返回 Pysa 在其 TARGETS 文件环境中识别到的所有模型目录的绝对路径即所有合法、可用的模型目录。$ pyre query validate_taint_models(){ response: { message: Models in /data/users/$USER/valid/path/one, /data/users/$USER/valid/path/two are valid. } }从服务端解析代码source/server/query.ml可以看出该命令还支持两个可选参数比文档示例更灵活# 指定要验证的目录 $ pyre query validate_taint_models(/path/to/models) # 同时开启 DSL 验证 $ pyre query validate_taint_models(/path/to/models, verify_dslTrue)不传参数时默认使用配置文件中的模型路径。API 细节API Details位置计算指南Location GuidelinesPyre 为表达式计算源码位置时遵循以下规则理解它们有助于解读types、callees_with_location等输出中的行列信息忽略表达式两端的空白、逗号、注释和包裹的括号。复合表达式内部嵌套的 no-op 记号空白、括号等会被包含进所在复合表达式的位置。例(a).b会注册两个表达式——a位于列 1-2仍遵循上一条规则而a.b位于列 0-5。复合表达式的位置必须囊括其全部组成成分的位置。例a b 1会把赋值a 1注册在列 0-9其中a在列 0-1、1在列 8-9。唯一的例外是类定义不包含其装饰器decorators。所有有语义意义的记号与保留字都会包含在它们所定义的节点中。例await a会把 awaitable 节点注册在列 0-7其中被包含的标识符a在列 6-7。例async def foo(): ...会把 define 节点注册在列 0-20。例foo(*args, **kwargs)会把args注册在列 4-9、kwargs注册在列 11-19。例string会把字符串节点注册在列 0-12。AST 中的隐式值长度为 0且指向若写成显式值最可能出现的最近位置。例a: int会注册一个 Ellipsis 对象在列 6-6。例a[0]会注册a在列 0-1同时a.__getitem__也在列 0-1。例a[:1]中切片slice的第一个参数None注册在列 2-2、第二个参数1注册在列 3-4、第三个参数None注册在列 4-4。批量查询Batching Queriesbatch命令可以一次性执行多个查询并返回一个响应列表。批内查询可以是除batch本身之外的任意合法查询的任意组合——服务端解析时遇到嵌套batch会直接抛出cannot nest batch queries错误见 source/server/query.ml。批量响应的长度与批内查询数量一致顺序与查询顺序一致$ pyre query batch(less_or_equal(int, str), join(int, str)){ response: [ { response: { boolean: false } }, { response: { type: typing.Union[int, str] } } ] }注意示例中第二个查询是join(int, str)——虽然它没有出现在本文前面的命令列表中pyre query help中也未单独列出但服务端依然能解析并返回typing.Union[int, str]这说明服务端支持的实际查询种类比帮助文本所展示的更多。缓存Caching每次被查询时Pyre 都会重新检查recheck对应文件以生成位置 → 类型的映射并将结果缓存起来以加速对同一文件的重复查询。如果预计要进行一次大规模 codemod代码库中大量文件会被查询到可以通过启动一个带--store-type-check-resolution标志的临时服务器来提升增量性能$ pyre start --store-type-check-resolution底层原理一条查询的完整生命周期要理解pyre query的运作方式可以把一次查询的生命周期拆成三层第一层客户端解析与分发。client/commands/query.py 中的run_query首先根据项目标识符计算 daemon socket 路径然后把查询文本原样发给正在运行的服务器如果是help则直接打印帮助文本。当使用了--no-daemon之类的参数时则会走no_daemon_query.execute_query的独立逻辑。如果连接失败客户端会提示需要先运行pyre建立服务器。第二层服务端请求解析。服务器收到查询字符串后由 source/server/query.ml 的parse_request调用parse_request_exn把伪 Python字符串解析为Request.t变体例如attributes(a.C)→Request.Attributes (Reference.t)。这个解析器支持字符串、布尔、整数参数以及命名参数如model_query(path..., query_name...)、validate_taint_models(..., verify_dslTrue)并且对参数个数不匹配的情况会抛出InvalidQuery异常。第三层服务端请求处理。解析出的Request.t被交给process_requestparse_and_process_request是其便捷包装见 source/server/query.mli在类型环境、构建系统、全局模块路径 API 和查询缓存的配合下计算出Response.t最后序列化为 JSON 返回给客户端。例如GlobalLeaks请求会调用GlobalLeakCheck.check_qualifier逐个 qualifier 检查ModelQuery请求会走process_model_query其中涉及Taint.ModelParser解析模型源码、FetchCallables抓取可调用对象、ClassHierarchyGraph构建类层级图等完整 Pysa 预处理管线。小结pyre query是理解 Pyre 内部世界的一扇窗口attributes/defines/superclasses/dump_class_hierarchy勾勒出类体系视图type/types/less_or_equal给出类型推断与子类型判定callees/callees_with_location展示调用关系global_leaks承担全局可变性审计而model_query/validate_taint_models/save_server_state则直通 Pysa 污点分析与服务器状态管理。在享受这些能力的同时请务必牢记官方的 legacy 声明它非常适合调试与人工排查但不应成为自动化系统或产品的基础依赖。赞分享静态分析开发工具代码质量【免费下载链接】pyre-checkPerformant type-checking for python.项目地址https://gitcode.com/gh_mirrors/py/pyre-check点击查看免费下载相关推荐显存不足也能跑FLUX2025轻量级模型选型与部署全指南显存不足也能跑FLUX2025轻量级模型选型与部署全指南 你是否遇到过这样的困境明明看好FLUX模型的强大生成能力却因显存不足低于24GB无法流畅运行基础模型计算机视觉Arthas sm 命令完全指南Search-Method 查看已加载类的方法信息Arthas sm 命令完全指南Search Method 查看已加载类的方法信息 sm Search Method是 Arthas 中最常用的类级诊断命开发工具可观测性调试器性能剖析Tabby终极指南5步搭建企业级AI编程助手Tabby终极指南5步搭建企业级AI编程助手 Tabby是一个开源的自托管AI编程助手为开发者提供完全免费的GitHub Copilot替代方案。这款强大的人工智能大模型本地部署模型推理服务后端RAG交互助手上一篇探索Pusher Channels Ruby Gem实时通信的强大工具下一篇Viper vs Cobalt Strike为什么这款免费工具正在改变红队测试格局创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表