C 和 C++ 中的转换和类¶
可以使用适用于 C 和 C++ 的标准 CodeQL 库来检测何时更改表达式的类型。
转换¶
在 C 和 C++ 中,转换会更改表达式的类型。它们可能是编译器生成的隐式转换,也可能是用户请求的显式转换。
让我们看一下标准库中的 Conversion 类
Expr
Conversion
Cast
CStyleCast
StaticCast
ConstCastReinterpretCast
DynamicCast
ArrayToPointerConversion
VirtualMemberToFunctionPointerConversion
探索赋值的子表达式¶
让我们考虑以下 C 代码
typedef signed int myInt;
int main(int argc, char *argv[])
{
unsigned int i;
i = (myInt)1;
return 0;
}
以及这个简单的查询
import cpp
from AssignExpr a
select a, a.getLValue().getType(), a.getRValue().getType()
该查询检查代码中的赋值,并告诉我们其左右子表达式的类型。在上面的 C 代码示例中,只有一个赋值。值得注意的是,该赋值在右侧有两个转换(类型为 CStyleCast
)
- 将整数
1
显式转换为myInt
。 - 编译器生成的隐式转换,用于准备赋值,将该表达式转换为
unsigned int
。
该查询实际上报告了以下结果
... = ... | unsigned int | int
就好像转换不存在一样!原因是 Conversion
表达式不会包装它们转换的对象;相反,转换附加到表达式,并且可以使用 Expr.getConversion()
访问。标准库类将我们的示例中的整个赋值视为以下内容
AssignExpr, i = (myInt)1
VariableAccess, i
Literal, 1
CStyleCast, myInt (explicit)
CStyleCast, unsigned int (implicit)
访问赋值的各个部分
- 左侧 - 使用
Assignment.getLValue()
访问值。 - 右侧 - 使用
Assignment.getRValue()
访问值。 - 右侧
Literal
的转换 - 使用对Expr.getConversion()
的调用来访问两者。作为快捷方式,可以使用Expr.GetFullyConverted()
一直跟踪到结果类型,或者使用Expr.GetExplicitlyConverted()
查找表达式中的最后一个显式转换。
使用这些谓词,可以细化查询,使其报告我们期望的结果
import cpp
from AssignExpr a
select a, a.getLValue().getExplicitlyConverted().getType(), a.getRValue().getExplicitlyConverted().getType()
现在结果为
... = ... | unsigned int | myInt
可以进一步细化查询,通过添加 Type.getUnderlyingType()
来解析 typedef
import cpp
from AssignExpr a
select a, a.getLValue().getExplicitlyConverted().getType().getUnderlyingType(), a.getRValue().getExplicitlyConverted().getType().getUnderlyingType()
现在结果为
... = ... | unsigned int | signed int
如果您只是想要获取表达式中所有赋值的值,无论其位置如何,都可以将 Assignment.getLValue()
和 Assignment.getRValue()
替换为 Operation.getAnOperand()
import cpp
from AssignExpr a
select a, a.getAnOperand().getExplicitlyConverted().getType()
与查询的早期版本不同,此查询会将表达式的每一侧都作为单独的结果返回
... = ... | unsigned int
... = ... | myInt
注意
通常,名为getAXxx
的谓词利用了返回多个结果(Xxx
的多个实例)的能力,而普通getXxx
谓词通常最多返回一个特定的Xxx
实例。
类¶
接下来,我们将使用以下 CodeQL 类来查看 C++ 类
Type
UserType
- 包括类、typedef 和枚举Class
- 类或结构Struct
- 结构,被视为Class
的子类型TemplateClass
- C++ 类模板
查找派生类¶
我们想要创建一个查询,用于检查应该为 virtual
的析构函数。具体而言,当类及其派生类都具有析构函数时,基类析构函数通常应该为 virtual。这可以确保始终调用派生类析构函数。在 CodeQL 库中,Destructor
是 MemberFunction
的子类型
Function
MemberFunction
Constructor
Destructor
我们查询的起点是基类和派生类的对,它们使用 Class.getABaseClass()
连接起来
import cpp
from Class base, Class derived
where derived.getABaseClass+() = base
select base, derived, "The second class is derived from the first."
请注意,传递闭包符号 +
表示 Class.getABaseClass()
可以跟随一次或多次,而不是只接受直接基类。
许多结果都是不重要的模板参数。可以通过如下更新 where
子句来删除这些结果
where derived.getABaseClass+() = base
and not exists(base.getATemplateArgument())
and not exists(derived.getATemplateArgument())
查找具有析构函数的派生类¶
现在,可以使用 Class.getDestructor()
谓词来扩展查询,以查找具有析构函数的派生类
import cpp
from Class base, Class derived, Destructor d1, Destructor d2
where derived.getABaseClass+() = base
and not exists(base.getATemplateArgument())
and not exists(derived.getATemplateArgument())
and d1 = base.getDestructor()
and d2 = derived.getDestructor()
select base, derived, "The second class is derived from the first, and both have a destructor."
请注意,获取析构函数隐式断言存在析构函数。因此,此版本的查询返回的结果比以前少。
查找析构函数不是 virtual 的基类¶
最后一步是使用 Function.isVirtual()
来查找基析构函数不是 virtual 的情况
import cpp
from Class base, Destructor d1, Class derived, Destructor d2
where derived.getABaseClass+() = base
and d1.getDeclaringType() = base
and d2.getDeclaringType() = derived
and not d1.isVirtual()
select d1, "This destructor should probably be virtual."
这样就完成了查询。
有一个类似的标准查询 基类中的非 virtual 析构函数,用于查找 C/C++ 项目中具有 virtual 函数但没有 virtual 析构函数的类。