CodeQL 文档

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. 将整数 1 显式转换为 myInt
  2. 编译器生成的隐式转换,用于准备赋值,将该表达式转换为 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 库中,DestructorMemberFunction 的子类型

  • 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 析构函数的类。

  • ©GitHub, Inc.
  • 条款
  • 隐私