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 的子类。
抽象语法树¶
AstNode
Module
– Python 模块Class
– 类定义体Function
– 函数定义体Stmt
– 语句Assert
– 一个assert
语句Assign
– 赋值AssignStmt
– 赋值语句,x = y
ClassDef
– 类定义语句FunctionDef
– 函数定义语句
AugAssign
– 增量赋值,x += y
Break
– 一个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.attr
Call
– 函数调用,f(arg)
IfExp
– 条件表达式,x if cond else y
Lambda – A lambda expression
Yield
– 一个yield
表达式Bytes
– 字节字面量,b"x"
或(在 Python 2 中)"x"
Unicode
– Unicode 字面量,u"x"
或(在 Python 3 中)"x"
Num
– 数字字面量,3
或4.2
IntegerLiteral
FloatLiteral
ImaginaryLiteral
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
– 变量引用,var
UnaryExpr
– 一元运算,-x
BinaryExpr
– 二元运算,x+y
Compare
– 比较运算,0 < x < 10
BoolExpr
– 短路逻辑运算,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(_)
是任何块的长度。