CodeQL 文档

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 源代码。 ModuleClassFunction 类分别对应于 Python 模块、类和函数,统称为 Scope 类。每个 Scope 包含一个语句列表,每个语句都由类 Stmt 的子类表示。语句本身可以包含其他语句或表达式,这些语句或表达式由 Expr 的子类表示。最后,还有几个用于更复杂表达式(如列表推导)部分的附加类。这些类统称为 AstNode 的子类,并构成抽象语法树 (AST)。每个 AST 的根是一个 Module。符号信息以变量的形式附加到 AST 上(由类 Variable 表示)。有关更多信息,请参阅维基百科上的 抽象语法树符号信息

范围

Python 程序是一组模块。从技术上讲,模块只是一个语句列表,但我们通常认为它由类和函数组成。这些顶级实体,即模块、类和函数,由 CodeQL 中的三个类 ModuleClassFunction 表示,它们都是 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() 返回表示变量 varExpr
  • getIter() 返回表示变量 seqExpr
  • getBody() 返回语句列表主体。
  • getStmt(0) 返回 pass Stmt
  • getOrElse() 返回包含 return 语句的 StmtList

表达式

大多数语句都由表达式组成。 Expr 类是所有表达式类的超类,其中大约有 30 个子类,包括调用、推导、元组、列表和算术运算。例如,Python 表达式 a+2BinaryExpr 类表示

  • getLeft() 返回表示 aExpr
  • getRight() 返回表示 2Expr

例如,要查找 a+2 形式的表达式,其中左侧是简单名称,右侧是数字常量,我们可以使用以下查询

查找“a+2”形式的表达式

import python

from BinaryExpr bin
where bin.getLeft() instanceof Name and bin.getRight() instanceof Num
select bin

许多代码库包含此模式的示例。

变量

变量由 CodeQL 库中的 Variable 类表示。有两个子类, LocalVariable 用于函数级和类级变量, GlobalVariable 用于模块级变量。

其他源代码元素

虽然程序的含义由语法元素 ScopeStmtExpr 编码,但抽象语法树中没有涵盖源代码的某些部分。其中最有用的是 Comment 类,它描述源代码中的注释。

示例

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 是一个 ExceptStmtPass 是表示 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 块。

总结

库中语法部分最常用的标准类按以下方式组织

ModuleClassFunctionStmtExpr - 它们都是 AstNode 的子类。

抽象语法树

  • AstNode
    • Module – Python 模块
    • Class – 类定义体
    • Function – 函数定义体
    • Stmt – 语句
      • Assert – 一个 assert 语句
      • Assign – 赋值
        • AssignStmt – 赋值语句,x = y
        • ClassDef – 类定义语句
        • FunctionDef – 函数定义语句
      • AugAssign – 增量赋值,x += y
      • Break – 一个 break 语句
      • Continue – 一个 continue 语句
      • Delete – 一个 del 语句
      • ExceptStmttry 语句的 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 – 数字字面量,34.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 yx 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
}

每个 ControlFlowNodeBasicBlock 内从零开始连续编号,因此 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(_) 是任何块的长度。

摘要

库的控制流部分中的类是

  • ControlFlowNode – 控制流节点。AST 节点和控制流节点之间存在一对多关系。
  • BasicBlock – 非分支控制流节点列表。
  • ©GitHub, Inc.
  • 条款
  • 隐私