CodeQL Python 库¶
当您需要分析 Python 程序时,您可以利用 CodeQL Python 库中大量的类。
关于 CodeQL Python 库¶
每种编程语言的 CodeQL 库都使用具有抽象和谓词的类以面向对象的形式呈现数据。
每个 CodeQL 库都作为一组 QL 模块实现,即扩展名为 .qll 的文件。模块 python.qll 导入所有核心 Python 库模块,因此您可以通过在查询开头添加以下内容来包含整个库
import python
CodeQL Python 库包含大量类。每个类都对应于 Python 源代码中的一种实体,或使用静态分析从源代码派生的实体。这些类可以分为四类
- 语法 - 表示 Python 源代码中实体的类。
- 控制流 - 表示控制流图中实体的类。
- 数据流 - 表示数据流图中实体的类。
- API 图 - 表示 API 图中实体的类。
前两类将在下面描述。有关数据流和相关类的描述,请参阅“分析 Python 中的数据流”。有关 API 图及其用途的描述,请参阅“在 Python 中使用 API 图。”
语法类¶
库的这一部分表示 Python 源代码。 Module、 Class 和 Function 类分别对应于 Python 模块、类和函数,统称为 Scope 类。每个 Scope 包含一个语句列表,每个语句都由类 Stmt 的子类表示。语句本身可以包含其他语句或表达式,这些语句或表达式由 Expr 的子类表示。最后,还有几个用于更复杂表达式(如列表推导)部分的附加类。这些类统称为 AstNode 的子类,并构成抽象语法树 (AST)。每个 AST 的根是一个 Module。符号信息以变量的形式附加到 AST 上(由类 Variable 表示)。有关更多信息,请参阅维基百科上的 抽象语法树 和 符号信息。
范围¶
Python 程序是一组模块。从技术上讲,模块只是一个语句列表,但我们通常认为它由类和函数组成。这些顶级实体,即模块、类和函数,由 CodeQL 中的三个类 Module、 Class 和 Function 表示,它们都是 Scope 的子类。
范围模块类函数
所有范围基本上都是一个语句列表,尽管 Scope 类具有其他属性,如名称。例如,以下查询查找其范围(声明它们的范围)也是函数的所有函数
import python
from Function f
where f.getScope() instanceof Function
select f
许多代码库使用嵌套函数。
语句¶
语句由 Stmt 类表示,该类大约有 20 个子类,代表各种类型的语句,例如 Pass 语句、 Return 语句或 For 语句。语句通常由多个部分组成。其中最常见的是表达式,由 Expr 类表示。例如,考虑以下 Python for 语句
for var in seq:
pass
else:
return 0
表示 for 语句的 For 类具有许多成员谓词,用于访问其各部分
getTarget()返回表示变量var的Expr。getIter()返回表示变量seq的Expr。getBody()返回语句列表主体。getStmt(0)返回 passStmt。getOrElse()返回包含 return 语句的StmtList。
表达式¶
大多数语句都由表达式组成。 Expr 类是所有表达式类的超类,其中大约有 30 个子类,包括调用、推导、元组、列表和算术运算。例如,Python 表达式 a+2 由 BinaryExpr 类表示
getLeft()返回表示a的Expr。getRight()返回表示2的Expr。
例如,要查找 a+2 形式的表达式,其中左侧是简单名称,右侧是数字常量,我们可以使用以下查询
查找“a+2”形式的表达式
import python
from BinaryExpr bin
where bin.getLeft() instanceof Name and bin.getRight() instanceof Num
select bin
许多代码库包含此模式的示例。
示例¶
Python 源代码中的每个语法元素都在 CodeQL 数据库中记录。可以通过相应的类查询这些元素。让我们从几个简单的例子开始。
1. 查找所有 finally 块¶
对于我们的第一个示例,我们可以使用 Try 类来查找所有 finally 块
查找所有 finally 块
import python
from Try t
select t.getFinalbody()
许多代码库包含此模式的示例。
2. 查找什么也不做的 except 块¶
对于我们的第二个示例,我们可以使用标准查询集中查询的简化版本。我们查找什么也不做的所有 except 块。
什么也不做的块是只包含 pass 语句的块。我们可以将其编码为
not exists(Stmt s | s = ex.getAStmt() | not s instanceof Pass)
其中 ex 是一个 ExceptStmt, Pass 是表示 pass 语句的类。与其使用双重否定,“不 是 不 是 pass 语句的语句”,不如使用肯定形式,“所有语句都必须是 pass 语句。” 肯定形式使用 forall 量词来表达
forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)
两种形式都是等效的。使用肯定表达式,整个查询如下所示
查找仅包含 pass 的 except 块
import python
from ExceptStmt ex
where forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)
select ex
许多代码库包含仅包含 pass 的 except 块。
总结¶
库中语法部分最常用的标准类按以下方式组织
Module、 Class、 Function、 Stmt 和 Expr - 它们都是 AstNode 的子类。
抽象语法树¶
AstNodeModule– Python 模块Class– 类定义体Function– 函数定义体Stmt– 语句Assert– 一个assert语句Assign– 赋值AssignStmt– 赋值语句,x = yClassDef– 类定义语句FunctionDef– 函数定义语句
AugAssign– 增量赋值,x += yBreak– 一个break语句Continue– 一个continue语句Delete– 一个del语句ExceptStmt–try语句的except部分Exec– 一个 exec 语句For– 一个for语句If– 一个if语句Pass– 一个pass语句Print– 一个print语句(仅限 Python 2)Raise– 一个 raise 语句Return– 一个return语句Try– 一个try语句While– 一个while语句With– 一个with语句
Expr– 表达式Attribute– 属性,obj.attrCall– 函数调用,f(arg)IfExp– 条件表达式,x if cond else yLambda – A lambda expressionYield– 一个yield表达式Bytes– 字节字面量,b"x"或(在 Python 2 中)"x"Unicode– Unicode 字面量,u"x"或(在 Python 3 中)"x"Num– 数字字面量,3或4.2IntegerLiteralFloatLiteralImaginaryLiteral
Dict– 字典字面量,{'a': 2}Set– 集合字面量,{'a', 'b'}List– 列表字面量,['a', 'b']Tuple– 元组字面量,('a', 'b')DictComp– 字典推导,{k: v for ...}SetComp– 集合推导,{x for ...}ListComp– 列表推导,[x for ...]GenExpr– 生成器表达式,(x for ...)Subscript– 下标运算,seq[index]Name– 变量引用,varUnaryExpr– 一元运算,-xBinaryExpr– 二元运算,x+yCompare– 比较运算,0 < x < 10BoolExpr– 短路逻辑运算,x and y,x or y
变量¶
Variable– 变量LocalVariable– 函数或类中的局部变量GlobalVariable– 模块级变量
其他¶
Comment– 注释
控制流类¶
库的这一部分表示每个 Scope(类、函数和模块)的控制流图。每个 Scope 包含一个 ControlFlowNode 元素的图。每个作用域都有一个唯一的入口点和至少一个(可能很多)出口点。为了加快控制和数据流分析,控制流节点被分组到基本块中。有关更多信息,请参阅维基百科上的 基本块。
示例¶
如果我们想要找到没有分支的最长代码序列,我们需要考虑控制流。根据定义,BasicBlock 是一个没有分支的代码序列,因此我们只需要找到最长的 BasicBlock。
首先,我们引入一个简单的谓词 bb_length(),它将 BasicBlock 与它们的长度相关联。
int bb_length(BasicBlock b) {
result = max(int i | exists(b.getNode(i))) + 1
}
每个 ControlFlowNode 在 BasicBlock 内从零开始连续编号,因此 BasicBlock 的长度等于该 BasicBlock 内最大索引加一。
使用这个谓词,我们可以通过选择长度等于任何 BasicBlock 的最大长度的 BasicBlock 来选择最长的 BasicBlock
查找没有分支的最长代码序列
import python
int bb_length(BasicBlock b) {
result = max(int i | exists(b.getNode(i)) | i) + 1
}
from BasicBlock b
where bb_length(b) = max(bb_length(_))
select b
注意
特殊的下划线变量
_表示任何值;因此bb_length(_)是任何块的长度。