CodeQL 文档

Python 中的表达式和语句

您可以使用 CodeQL 库中的语法类来探索 Python 表达式和语句如何在代码库中使用。

语句

大部分 Python 代码采用语句的形式。Python 中的每种不同类型的语句都由一个单独的 CodeQL 类表示。

以下是完整的类层次结构

  • Stmt – 语句
    • Assertassert 语句
    • 赋值
      • AssignStmt – 赋值语句,x = y
      • ClassDef – 类定义语句
      • FunctionDef – 函数定义语句
    • AugAssign – 增强赋值,x += y
    • Breakbreak 语句
    • Continuecontinue 语句
    • Deletedel 语句
    • ExceptStmttry 语句的 except 部分
    • Execexec 语句
    • Forfor 语句
    • Globalglobal 语句
    • Ifif 语句
    • ImportStarfrom xxx import * 语句
    • Import – 任何其他 import 语句
    • Nonlocalnonlocal 语句
    • Passpass 语句
    • Printprint 语句(仅限 Python 2)
    • Raiseraise 语句
    • Returnreturn 语句
    • Trytry 语句
    • Whilewhile 语句
    • Withwith 语句

查找冗余的“global”语句的示例

Python 中的 global 语句声明一个具有全局(模块级)作用域的变量,否则它将是局部的。在类或函数之外使用 global 语句是多余的,因为该变量已经是全局的。

import python

from Global g
where g.getScope() instanceof Module
select g

这行代码:g.getScope() instanceof Module 确保 Global gScope 是一个 Module,而不是一个类或函数。

查找具有冗余分支的“if”语句的示例

一个 if 语句,其中一个分支仅由 pass 语句组成,可以通过否定条件并删除 else 子句来简化。

if cond():
    pass
else:
    do_something

为了找到可以简化的类似语句,我们可以编写一个查询。

import python

from If i, StmtList l
where (l = i.getBody() or l = i.getOrelse())
  and forall(Stmt p | p = l.getAnItem() | p instanceof Pass)
select i

许多代码库都有一些 if 语句符合这种模式。

这行代码:(l = i.getBody() or l = i.getOrelse())StmtList l 限制在 if 语句的分支中。

这行代码:forall(Stmt p | p = l.getAnItem() | p instanceof Pass) 确保 l 中的所有语句都是 pass 语句。

表达式

每种类型的 Python 表达式都有自己的类。以下是完整的类层次结构

  • Expr – 表达式
    • Attribute – 属性,obj.attr
    • BinaryExpr – 二元运算,x+y
    • BoolExpr – 短路逻辑运算,x and yx or y
    • Bytes – 字节字面量,b"x" 或(在 Python 2 中)"x"
    • Call – 函数调用,f(arg)
    • Compare – 比较运算,0 < x < 10
    • Dict – 字典字面量,{'a': 2}
    • DictComp – 字典推导,{k: v for ...}
    • Ellipsis – 省略号表达式,...
    • GeneratorExp – 生成器表达式
    • IfExp – 条件表达式,x if cond else y
    • ImportExpr – 表示导入模块的人工表达式
    • ImportMember An 人工表达式,表示从模块导入值(from xxx import * 语句的一部分)
    • Lambda A lambda expression
    • List – 列表字面量,['a', 'b']
    • ListComp – 列表推导,[x for ...]
    • Name – 变量引用,var
    • Num – 数字字面量,34.2
      • FloatLiteral
      • ImaginaryLiteral
      • IntegerLiteral
    • Repr – 反引号表达式,x(仅限 Python 2)
    • Set – 集合字面量,{'a', 'b'}
    • SetComp – 集合推导,{x for ...}
    • Slice – 切片;表达式 seq[0:1] 中的 0:1
    • Starred – 星号表达式,在多重赋值上下文中使用 *xy, *x = 1,2,3(仅限 Python 3)
    • StrConst – 字符串字面量。在 Python 2 中,可以是字节或 Unicode。在 Python 3 中,仅限 Unicode。
    • Subscript – 下标操作,seq[index]
    • UnaryExpr – 一元运算,-x
    • Unicode – Unicode 字面量,u"x" 或(在 Python 3 中)"x"
    • Yieldyield 表达式
    • YieldFromyield from 表达式(Python 3.3+)

使用“is”查找与整数或字符串字面量比较的示例

Python 实现通常会缓存小的整数和单字符字符串,这意味着以下比较通常会正常工作,但这并非保证,我们可能需要检查它们。

x is 10
x is "A"

我们可以使用查询来检查这些内容。

import python

from Compare cmp, Expr literal
where (literal instanceof StrConst or literal instanceof Num)
  and cmp.getOp(0) instanceof Is and cmp.getComparator(0) = literal
select cmp

子句 cmp.getOp(0) instanceof Is and cmp.getComparator(0) = literal 检查第一个比较运算符是否为“is”以及第一个比较器是否为字面量。

提示

我们必须使用 cmp.getOp(0)cmp.getComparator(0),因为没有 cmp.getOp()cmp.getComparator()。这样做的原因是,Compare 表达式可以有多个运算符。例如,表达式 3 < x < 7 具有两个运算符和两个比较器。您可以使用 cmp.getComparator(0) 获取第一个比较器(在本例中为 x),并使用 cmp.getComparator(1) 获取第二个比较器(在本例中为 7)。

在字典字面量中查找重复项的示例

如果 Python 字典中存在重复键,则第二个键将覆盖第一个键,这几乎肯定是一个错误。我们可以使用 CodeQL 找到这些重复项,但查询比以前的示例更复杂,需要我们编写一个 predicate 作为辅助。

import python

predicate same_key(Expr k1, Expr k2) {
  k1.(Num).getN() = k2.(Num).getN()
  or
  k1.(StrConst).getText() = k2.(StrConst).getText()
}

from Dict d, Expr k1, Expr k2
where k1 = d.getAKey() and k2 = d.getAKey()
  and k1 != k2 and same_key(k1, k2)
select k1, "Duplicate key in dict literal"

当我们在某些测试代码库上运行此查询时,我们发现了字典键重复的示例。标准“字典字面量中的重复键”查询也将这些结果突出显示为警报。有关更多信息,请参阅 字典字面量中的重复键

支持谓词 same_key 检查键是否具有相同的标识符。将此逻辑部分分离到一个支持谓词中,而不是直接将其包含在查询中,可以更轻松地理解整个查询。谓词中定义的强制转换将表达式限制为指定的类型,并允许对强制转换到的类型进行谓词调用。例如

x = k1.(Num).getN()

等效于

exists(Num num | num = k1 | x = num.getN())

通常使用简短版本,因为它更易于阅读。

查找 Java 风格的 getter 的示例

回到“Python 中的函数”中的示例,该查询标识了所有只有一行代码且名称以 get 开头的函数。

import python

from Function f
where f.getName().matches("get%") and f.isMethod()
    and count(f.getAStmt()) = 1
select f, "This function is (probably) a getter."

可以通过检查该行代码是否为 return self.attr 形式的 Java 风格 getter 来改进此基本查询。

import python

from Function f, Return ret, Attribute attr, Name self
where f.getName().matches("get%") and f.isMethod()
    and ret = f.getStmt(0) and ret.getValue() = attr
    and attr.getObject() = self and self.getId() = "self"
select f, "This function is a Java-style getter."
ret = f.getStmt(0) and ret.getValue() = attr

此条件检查方法中的第一行是否为 return 语句,以及返回的表达式 (ret.getValue()) 是否为 Attribute 表达式。请注意,等式 ret.getValue() = attr 意味着 ret.getValue() 仅限于 Attribute,因为 attrAttribute

attr.getObject() = self and self.getId() = "self"

此条件检查属性的值(value.attr 中点左侧的表达式)是否为对名为 "self" 的变量的访问。

类和函数定义

由于 Python 是一种动态类型语言,因此类和函数定义是可执行语句。这意味着类语句既是一个语句,也是一个包含语句的范围。为了清晰地表示这一点,类定义被分成多个部分。在运行时,当执行类定义时,将创建一个类对象,然后将其分配给封闭类范围中具有相同名称的变量。此类是根据表示类主体源代码的代码对象创建的。为了表示这一点,ClassDef 类(代表 class 语句)是 Assign 的子类。可以通 ClassDef.getDefinedClass() 访问代表类主体的 Class 类。 FunctionDefFunction 的处理方式类似。

以下是类层次结构的相关部分

  • Stmt
    • 赋值
      • ClassDef
      • FunctionDef
  • Scope
    • Class
    • Function
  • ©2025GitHub, Inc.
  • 条款
  • 隐私