CodeQL 文档

分析 Python 中的控制流

您可以编写 CodeQL 查询来探索 Python 程序的控制流图,例如,发现不可到达的代码或互斥的代码块。

关于分析控制流

要分析 Scope 的控制流图,我们可以使用两个 CodeQL 类 ControlFlowNodeBasicBlock。这些类允许您提出诸如“您能否从点 A 抵达点 B?”或“是否可以到达点 B 而不 经过点 A?”之类的问题。为了报告结果,我们使用类 AstNode,它表示语法元素并对应于源代码 - 使查询的结果更易于理解。有关更多信息,请参阅维基百科上的 控制流图

ControlFlowNode

ControlFlowNode 表示控制流图中的节点。AST 节点与控制流节点之间存在一对多关系。每个语法元素,即 AstNode, 映射到零个、一个或多个 ControlFlowNode 类,但每个 ControlFlowNode 映射到正好一个 AstNode

为了说明为什么需要这种复杂的关系,请考虑以下 Python 代码

try:
    might_raise()
    if cond:
        break
finally:
    close_resource()

上面代码中存在许多路径。对 close_resource(); 的调用有三种不同的路径:一条正常路径、一条跳出循环的路径以及一条由 might_raise() 引发异常的路径。

带注释的流程图

Python control flow graph

ControlFlowNodeAstNode 最简单的用法是查找不可到达的代码。任何 AstNode 的每条路径都有一个 ControlFlowNode,任何不可到达的 AstNode 都没有路径流经它。因此,任何没有相应 ControlFlowNodeAstNode 都是不可到达的。

查找不可到达的 AST 节点的示例

import python

from AstNode node
where not exists(node.getAFlowNode())
select node

许多代码库都包含一些没有控制流节点的代码,因此这些代码是不可到达的。但是,由于 Module 类也是 AstNode 类的子类,因此查询还会找到以 C 语言实现或没有源代码的任何模块。因此,最好查找所有不可到达的语句。

查找不可到达的语句的示例

import python

from Stmt s
where not exists(s.getAFlowNode())
select s

此查询应该会产生更少的結果。您也可以使用标准的“不可到达的代码”查询来查找不可到达的代码。有关更多信息,请参阅 不可到达的代码

BasicBlock

BasicBlock 表示控制流节点的基本块。类 BasicBlock 对直接编写查询来说不是很有用,但对于构建复杂分析(例如数据流)非常有用。它之所以有用,是因为它共享了控制流节点的许多有趣属性,例如,什么可以到达什么以及什么支配什么,但基本块比控制流节点少 - 从而导致查询速度更快且使用更少的内存。有关更多信息,请参阅维基百科上的 基本块支配器

查找互斥基本块的示例

假设我们有以下 Python 代码

if condition():
    return 0
pass

我们能否确定在一次执行此代码时不可能同时到达 return 0 语句和 pass 语句?对于两个基本块要互斥,必须不可能从另一个基本块到达其中任何一个。我们可以编写

import python

from BasicBlock b1, BasicBlock b2
where b1 != b2 and not b1.strictlyReaches(b2) and not b2.strictlyReaches(b1)
select b1, b2

但是,根据这个定义,如果两个基本块位于不同的作用域中,那么它们是互斥的。为了使结果更有用,我们需要这两个基本块都能够从同一个函数入口点到达

exists(Function shared, BasicBlock entry |
    entry.contains(shared.getEntryNode()) and
    entry.strictlyReaches(b1) and entry.strictlyReaches(b2)
)

结合这些条件,我们得到

查找同一个函数内互斥块的示例

import python

from BasicBlock b1, BasicBlock b2
where b1 != b2 and not b1.strictlyReaches(b2) and not b2.strictlyReaches(b1) and
exists(Function shared, BasicBlock entry |
    entry.contains(shared.getEntryNode()) and
    entry.strictlyReaches(b1) and entry.strictlyReaches(b2)
)
select b1, b2

这通常会产生大量结果,因为它在正常的控制流中很常见。但是,它说明了可能进行的控制流分析类型。此类控制流分析对于数据流分析来说是一种重要的辅助工具。有关更多信息,请参阅“分析 Python 中的数据流。”

  • ©GitHub, Inc.
  • 条款
  • 隐私