C 和 C++ 中的表达式、类型和语句¶
您可以使用 CodeQL 探索 C 和 C++ 代码中的表达式、类型和语句,例如,查找错误的赋值。
CodeQL 中的表达式和类型¶
C 中的每个表达式部分都成为 Expr 类的实例。例如,C 代码 x = x + 1 变成一个 AssignExpr、一个 AddExpr、两个 VariableAccess 实例和一个 Literal。所有这些 CodeQL 类都扩展了 Expr。
查找赋值为零¶
在以下示例中,我们找到 AssignExpr 的实例,这些实例将常量值零赋值给它
import cpp
from AssignExpr e
where e.getRValue().getValue().toInt() = 0
select e, "Assigning the value 0 to something."
此示例中的 where 子句获取赋值右侧的表达式 getRValue(),并将其与零进行比较。请注意,没有检查以确保赋值右侧是整数或它有一个值(即,它是编译时常量,而不是变量)。对于其中任何一个假设错误的表达式,关联的谓词根本不返回任何内容,并且 where 子句不会产生结果。您可以将其视为在这一行的开头有一个隐式的 exists(e.getRValue().getValue().toInt())。
还值得注意的是,上面的查询会找到以下 C 代码
yPtr = NULL;
这是因为数据库包含预处理器转换运行后的代码库的表示。这意味着,在数据库创建期间,任何宏调用(如这里使用的 NULL 定义)都将被扩展。如果您想编写关于宏的查询,那么有一些专门为此目的设计的特殊库类(例如,Macro、MacroInvocation 类和 Element.isInMacroExpansion() 等谓词)。在这种情况下,宏被扩展是件好事,但我们不想找到对指针的赋值。有关更多信息,请参阅 数据库创建。
查找将 0 赋值给整数¶
我们可以通过为表达式的左侧定义一个条件来使查询更加具体。例如
import cpp
from AssignExpr e
where e.getRValue().getValue().toInt() = 0
and e.getLValue().getType().getUnspecifiedType() instanceof IntegralType
select e, "Assigning the value 0 to an integer."
这将检查赋值的左侧是否具有某种整数类型。注意对 Type.getUnspecifiedType() 的调用。这会将 typedef 类型解析为它们的底层类型,以便查询找到诸如以下赋值之类的赋值
typedef int myInt;
myInt i;
i = 0;
CodeQL 中的语句¶
我们可以使用语句进一步细化查询。在这种情况下,我们使用 ForStmt 类
Stmt- C/C++ 语句循环WhileStmtForStmtDoStmt
ConditionalStmtIfStmtSwitchStmt
TryStmtExprStmt- 用作语句的表达式;例如,赋值Block- 包含更多语句的 { } 块
查找在 'for' 循环初始化中将 0 赋值给它¶
我们可以限制先前的查询,使其仅考虑 for 语句内部的赋值,方法是将 ForStmt 类添加到查询中。然后,我们要将表达式与 ForStmt.getInitialization() 进行比较
import cpp
from AssignExpr e, ForStmt f
// the assignment is the for loop initialization
where e = f.getInitialization()
...
不幸的是,这并不完全奏效,因为循环初始化实际上是一个 Stmt 而不是一个 Expr - AssignExpr 类被包装在一个 ExprStmt 类中。相反,我们需要使用 Expr.getEnclosingStmt() 查找表达式的最接近的封闭 Stmt
import cpp
from AssignExpr e, ForStmt f
// the assignment is in the 'for' loop initialization statement
where e.getEnclosingStmt() = f.getInitialization()
and e.getRValue().getValue().toInt() = 0
and e.getLValue().getType().getUnspecifiedType() instanceof IntegralType
select e, "Assigning the value 0 to an integer, inside a for loop initialization."
查找循环体内的 0 赋值¶
我们可以使用类似的代码使用谓词 Loop.getStmt(): 查找循环体内的赋值
import cpp
from AssignExpr e, ForStmt f
// the assignment is in the for loop body
where e.getEnclosingStmt().getParentStmt*() = f.getStmt()
and e.getRValue().getValue().toInt() = 0
and e.getLValue().getType().getUnderlyingType() instanceof IntegralType
select e, "Assigning the value 0 to an integer, inside a for loop body."
请注意,我们用 e.getEnclosingStmt().getParentStmt*() 替换了 e.getEnclosingStmt(),以查找深度嵌套在循环体内的赋值表达式。这里的传递闭包修饰符 * 表示 Stmt.getParentStmt() 可以跟随零次或多次,而不仅仅是一次,这会给我们语句、其父语句、其父的父语句等等。