6

我一直在寻找工具来帮助检测阻止程序作为 64 位代码正常运行的错误。最近,我一直在玩弄 Klocwork 及其自定义检查器功能,它让我可以使用 XPath 将源代码作为树导航。这作为正则表达式的“更智能”替代品很有用,但我无法让它知道类型。

例如,假设我想查找for使用 anint或 along进行计数的循环的每个实例。下面的代码很容易找到。

for (int i = 0; i < 10; i++)
    // ...

搜索此代码很简单,因为变量定义就在循环内。但是,请考虑以下示例。

int i;
// ...
for (i = 0; i < 10; i++)
    // ...

这很难找到,因为变量定义与循环是分开的,而且必要的 XPath 表达式要么笨重,要么容易出错。

那么,自定义 Klocwork 规则能否找到像这样需要类型感知的表达式,包括解析typedef#define语句?有没有其他工具可以做到这一点?

编辑 1:考虑以下示例。

typedef int myint;

void Foo() {
    int i;
    for (i = 0; i < 10; i++) {
        Bar();
    }

    myint j;
    for (j = 0; j < 10; j++) {
        Bar();
    }
}

ahmeddirie 提供的解决方案找到了第一个循环,因为 的类型i被明确定义为int。但是,没有找到第二个循环,因为 typedef 已经掩盖了基础类型。j哪些工具以将第二个循环变量识别为确实是 的方式跟踪类型int

4

3 回答 3

2

您可以使用 Clang ( http://clang.llvm.org ) 甚至 Elsa ( https://github.com/dsw/oink-stack/ ) 在类型传播和模板实例化之后生成 AST。两者都提供了不错的 C++ API 和一些将 AST 转储为可读文本的方法。而且这两种选择都是免费的。

于 2011-06-23T09:18:48.483 回答
1

我工作的公司 Semantic Designs Inc. 提供的工具包含用于分析和转换程序的通用基础架构以及用于各种编程语言的特定分析组件。这些统称为 DMS。对于 C++,DMS 包括针对 GCC3、GCC4、ISO14882c1998 (ANSI)、Visual C++ 6.0 和非托管 Visual Studio 2005 C++ 中的每一个的集成词法分析器、预处理器、解析器以及名称和类型解析组件。对于 C 的各种方言,还存在控制流分析、副作用分析器和符号依赖性分析器,其中已实现指针检查器、非活动代码删除器、函数分析器和程序切片器等工具。

名称和类型解析组件提供了完整的符号表信息和查找功能,因此对标识符的引用可以很容易地与其类型和其他声明性信息相关联。该信息类似于编译器捕获和使用的信息,但与抽象语法树一起以适合任何包含该组件的工具自适应重用的形式保留。

Semantic Designs 最近构建了一个自定义工具,该工具专门与循环声明中的索引变量类型相关,例如在您的示例中。在这种情况下,问题是升级使用 -fno-for-scope 编译器开关的 GCC2 代码,该开关为后来的 GCC 方言中不支持的循环变量提供了范围解析规则。该工具必须转换循环,将循环变量的声明移动到保留 -fno-for-scope 范围规则的外部上下文中。如果不需要进行此类更改,则不进行更改。

因此,该工具必须识别与循环变量的每个引用关联的类型,在屏蔽范围的情况下进行区分,并重新构建代码,以便 GCC3 和 GCC4 名称解析将产生与带有 -fno 的 GCC2 相同的语义解释- 范围。这需要能够访问与每个变量引用关联的符号表信息,并且在代码被移动的情况下,为声明被移动的任何变量重建类型声明的正确语法。DMS C++ 名称和类型解析组件提供的符号表和标识符引用表包含所有必需的信息,以及用于重构规定类型语法的模块,允许合成正确的新类型声明。

例如,考虑以下示例:

// loop variable hides variable in global scope
// will change meaning without -fno-for-scope
// fix: move decl. of cnt before for-loop
//   optionally rename globcnt loop variable

float globcnt = 0.0;

int Foo::foo3() {
    for (int globcnt = 0; globcnt < 5; globcnt++) {
        globalInt += globcnt;
    }
    globalInt += 2*globcnt + 1;
    return 0;
}

GCC2 -fno-for-scope 语义表明循环外对 globcnt 的引用是对循环变量的引用,即使 GCC3 会认为循环变量超出范围并解析对全局变量的引用。该工具将此代码转换为:

float globcnt = 0.0;

int Foo::foo3() {
    int globcnt = 0;
    for (; globcnt < 5; globcnt++) {
        globalInt += globcnt;
    }
    globalInt += 2*globcnt + 1;
    return 0;
}

如果代码没有被转换,GCC4 总是会从 Foo:foo3 返回值 1。但是,转换后的值会受到最初为 GCC2 设计的循环迭代的影响。该工具必须认识到对 globcnt 的最终引用是对 int 类型的局部变量,而不是对 float 类型的全局变量,它可以通过符号表查找来完成,并采取相应的行动。

另一方面,该工具在以下代码中识别出循环外没有对 i 的引用,因此可以接受(并且首选)保持循环变量声明不变。

int Foo::foo0() {
    for (int i = 0; i < 10; i++) {
        globalInt += i*i;
    }
    return 0;
}
于 2011-06-22T22:48:30.527 回答
1

不完全确定这是否是您想要的,但您始终可以使用内置函数轻松解析类型。例如,回答您的问题(尽管可能不是您的潜在需求):

//ForStmt / Init::ExprStmt / Expr::BinaryExpr [ $type := Left.getTypeName() ] [ $type = 'int' | $type.contains('long') ]

这将非常方便地找到使用“int”或“long int”计数器类型的“for”循环,并且显然可以应用于基于表达式的语句的任何元素。

类型定义适用于这种操作,无论是程序员定义的还是语言定义的。然而,预处理器定义只会产生它们的本地语言类型(即宏本身不能用于通过 KAST 进行操作,只能用于扩展它的内容)。

于 2011-06-23T15:13:15.197 回答