Python 中的表达式和语句¶
您可以使用 CodeQL 库中的语法类来探索 Python 表达式和语句如何在代码库中使用。
语句¶
大部分 Python 代码采用语句的形式。Python 中的每种不同类型的语句都由一个单独的 CodeQL 类表示。
以下是完整的类层次结构
Stmt
– 语句Assert
–assert
语句赋值
AssignStmt
– 赋值语句,x = y
ClassDef
– 类定义语句FunctionDef
– 函数定义语句
AugAssign
– 增强赋值,x += y
Break
–break
语句Continue
–continue
语句Delete
–del
语句ExceptStmt
–try
语句的except
部分Exec
–exec
语句For
–for
语句Global
–global
语句If
–if
语句ImportStar
–from xxx import *
语句Import
– 任何其他import
语句Nonlocal
–nonlocal
语句Pass
–pass
语句Print
–print
语句(仅限 Python 2)Raise
–raise
语句Return
–return
语句Try
–try
语句While
–while
语句With
–with
语句
查找冗余的“global”语句的示例¶
Python 中的 global
语句声明一个具有全局(模块级)作用域的变量,否则它将是局部的。在类或函数之外使用 global
语句是多余的,因为该变量已经是全局的。
import python
from Global g
where g.getScope() instanceof Module
select g
这行代码:g.getScope() instanceof Module
确保 Global g
的 Scope
是一个 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 y
,x 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 – A
n 人工表达式,表示从模块导入值(from xxx import *
语句的一部分)Lambda – A lambda expression
List
– 列表字面量,['a', 'b']
ListComp
– 列表推导,[x for ...]
Name
– 变量引用,var
Num
– 数字字面量,3
或4.2
FloatLiteral
ImaginaryLiteral
IntegerLiteral
Repr
– 反引号表达式,x
(仅限 Python 2)Set
– 集合字面量,{'a', 'b'}
SetComp
– 集合推导,{x for ...}
Slice
– 切片;表达式seq[0:1]
中的0:1
Starred
– 星号表达式,在多重赋值上下文中使用*x
:y, *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"
Yield
–yield
表达式YieldFrom
–yield 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
,因为 attr
为 Attribute
。
attr.getObject() = self and self.getId() = "self"
此条件检查属性的值(value.attr
中点左侧的表达式)是否为对名为 "self"
的变量的访问。
类和函数定义¶
由于 Python 是一种动态类型语言,因此类和函数定义是可执行语句。这意味着类语句既是一个语句,也是一个包含语句的范围。为了清晰地表示这一点,类定义被分成多个部分。在运行时,当执行类定义时,将创建一个类对象,然后将其分配给封闭类范围中具有相同名称的变量。此类是根据表示类主体源代码的代码对象创建的。为了表示这一点,ClassDef
类(代表 class
语句)是 Assign
的子类。可以通 ClassDef.getDefinedClass()
访问代表类主体的 Class
类。 FunctionDef
和 Function
的处理方式类似。
以下是类层次结构的相关部分
Stmt
赋值
ClassDef
FunctionDef
Scope
Class
Function