C 中有三种循环:
- “结构化语法”(while、for、...)
[注意 GCC,它可以隐藏语句,因此使用 (stmt; exp) 语法在表达式内循环!]
- 使用 goto 的临时循环;它们与结构化语法交互。
- 递归
要找到第一种,你必须找到结构化的语法和嵌套。
Grep 当然可以找到关键字(如果您忽略 cmets 和字符串中的误报),但它无法找到嵌套结构。您当然可以使用 grep 查找所有循环语法,然后简单地检查同一文件中出现的那些语法以查看它们是否嵌套。
(如果你想在没有误报的情况下这样做,你可以使用我们的Source Code Search Engine,它知道 C 的词法语法,并且永远不会混淆字符串何时是关键字、数字、字符串等.)
如果您想自动找到这些循环,您几乎需要一个完整的 C 解析器并完成扩展预处理。 (否则某些宏可能会隐藏关键的循环语法)。一旦有了 C 的语法树,就可以直接(尽管可能有点不方便)编写一些爬过树的代码,检测循环语法节点,并计算子树中的循环嵌套。您可以使用任何能够解析 C 并为您提供抽象 sytnax 树的工具来执行此操作。 ANTLR 可能会这样做;我认为 ANTLR 有一个 C 语法可以很好地处理 C,但是在使用 ANTLR 之前你必须运行预处理器。
您也可以使用我们的DMS Software Reengineering Toolkit 及其C Front End 来执行此操作。我们的 C 前端内置了完整的预处理器,因此它可以直接读取代码并在解析时进行扩展;它还处理相对广泛的 C 方言和字符编码(曾经处理过包含日语文本的 C 吗?)。 DMS 提供了一个额外的优势:给定一种语言(例如 C)前端,您可以直接使用语言语法为该语言编写模式。所以我们可以很容易地表达我们想要找到的片段:
pattern block_for_loop(t:expression,l:expression,i:expression, s: statements): statement
" for(\t,\l\,\i) { \s } ";
pattern statement_for_loop(t:expression,l:expression,i:expression, s: statement): statement
" for(\t,\l\,\i) \s ";
pattern block_while_loop(c:expression, s: statements): statement
" while(\c) { \s } ";
pattern statement_while_loop(c:expression): statement
" for(\c) \s ";
...
并将它们组合在一起:
pattern_set syntactic_loops
{ block_for_loop,
statement_for_loop,
block_while_loop,
block_statement_loop,
...
}
给定模式集,DMS 可以扫描语法树并找到与任何集合成员的匹配项,而无需编写任何特定的树爬行机制,也无需了解有关树结构的大量细节。 (对于真正的 C 解析器,AST 中有很多节点类型!)以这种方式查找嵌套循环非常简单:自上而下扫描树以查找循环(使用模式集);任何命中都必须是顶级循环。扫描找到的循环 AST 节点的子树(当您知道外循环的树在哪里时很容易)以查找其他循环;任何命中都必须是嵌套循环;如有必要,递归以拾取具有任意嵌套的循环。这也适用于 GCC loops-with-statements 的东西。树节点标有精确的文件/行/列信息,因此很容易生成位置报告。
对于使用 goto 构建的临时循环(什么,您的代码没有?),您需要能够生成实际控制流图的东西,然后将该图构造成嵌套控件流动区域。这里的要点是,包含无条件 goto out 的 while 循环并不是真正的循环,尽管有语法。和一个 if 语句,其 then 子句返回到 if 上游的代码可能真的是一个循环。所有这些循环语法的东西实际上只是你可能有循环的启发式提示!
这些控制流区域包含控制流的真正嵌套; DMS 将construct C flow graphs, and will produce those structured regions。它提供了用于构建和访问该图的库;这样你就可以得到基于 goto 的“真正的”控制流。找到一对嵌套的控制流区域后,可以访问与部分区域关联的 AST 以获取要报告的位置信息。
GCC 总是很有趣,因为它显着增强了 C 版本。例如,它有一个间接的 goto 语句。 (常规 C 隐藏在 setjmp/longjmp 下!)。
面对这种情况,要找出循环,您需要points-to analysis,DMS 也提供了它。该信息由嵌套区域分析使用。点分析的准确性存在(保守的)限制,但以您获得正确的嵌套区域图为模。
递归更难找到。为此,您必须确定 A 是否调用 B ... 调用 Z 调用 A,其中 A 和 B 和 ... 可以位于单独的编译单元中。您需要一个global call graph,其中包含应用程序的所有编译单元。在这一点上,您可能希望我说 DMS 也这样做,瞧,我很高兴地说它确实。构建该调用图当然需要对函数调用进行点对点分析;是的,DMS 也这样做。使用调用图,您可以在调用图中找到可能是递归的循环。同样通过调用图,您可以找到间接嵌套,例如,一个函数中的循环,它调用另一个也包含循环的编译单元中的函数。
要准确地找到这样的循环结构,您需要大量的机器(这需要一些努力,但 C 是一种很难分析的语言),而 DMS 可以提供它。
如果您不关心准确性,也不关心所有类型的循环,您可以使用 grep 和手动过程来获取结构化循环语句所暗示的循环的半精确映射.