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++ 语句循环
WhileStmt
ForStmt
DoStmt
ConditionalStmt
IfStmt
SwitchStmt
TryStmt
ExprStmt
- 用作语句的表达式;例如,赋值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()
可以跟随零次或多次,而不仅仅是一次,这会给我们语句、其父语句、其父的父语句等等。