Python 中的表达式和语句¶
您可以使用 CodeQL 库中的语法类来探索 Python 表达式和语句如何在代码库中使用。
语句¶
大部分 Python 代码采用语句的形式。Python 中的每种不同类型的语句都由一个单独的 CodeQL 类表示。
以下是完整的类层次结构
Stmt– 语句Assert–assert语句赋值AssignStmt– 赋值语句,x = yClassDef– 类定义语句FunctionDef– 函数定义语句
AugAssign– 增强赋值,x += yBreak–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.attrBinaryExpr– 二元运算,x+yBoolExpr– 短路逻辑运算,x and y,x or yBytes– 字节字面量,b"x"或(在 Python 2 中)"x"Call– 函数调用,f(arg)Compare– 比较运算,0 < x < 10Dict– 字典字面量,{'a': 2}DictComp– 字典推导,{k: v for ...}Ellipsis– 省略号表达式,...GeneratorExp– 生成器表达式IfExp– 条件表达式,x if cond else yImportExpr– 表示导入模块的人工表达式ImportMember – An 人工表达式,表示从模块导入值(from xxx import *语句的一部分)Lambda – A lambda expressionList– 列表字面量,['a', 'b']ListComp– 列表推导,[x for ...]Name– 变量引用,varNum– 数字字面量,3或4.2FloatLiteralImaginaryLiteralIntegerLiteral
Repr– 反引号表达式,x(仅限 Python 2)Set– 集合字面量,{'a', 'b'}SetComp– 集合推导,{x for ...}Slice– 切片;表达式seq[0:1]中的0:1Starred– 星号表达式,在多重赋值上下文中使用*x:y, *x = 1,2,3(仅限 Python 3)StrConst– 字符串字面量。在 Python 2 中,可以是字节或 Unicode。在 Python 3 中,仅限 Unicode。Subscript– 下标操作,seq[index]UnaryExpr– 一元运算,-xUnicode– 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赋值ClassDefFunctionDef
ScopeClassFunction