【问题标题】:How to process c and c++ source code to calculate metrics for static code analysis?如何处理 c 和 c++ 源代码以计算静态代码分析的指标?
【发布时间】:2019-08-12 08:15:58
【问题描述】:

我正在扩展一个软件工具来计算软件项目的指标。 然后使用这些指标进行静态代码分析。 我的任务是实现 c 和 c++ 项目的指标计算。

在开发过程中,我遇到了导致重置并使用不同的工具或编程语言重新开始的问题。 我将按时间顺序并尽可能好地说明我试图解决的过程、问题和事情。

一些指标:

  • 用于类、结构、联合、函数/方法和源文件的代码行
  • 类和结构的方法计数
  • 类、结构和函数/方法的复杂性
  • 类和结构之间的依赖关系

由于 c++ 是一种难以解析的语言,并且我自己编写一个 c++ 解析器超出了规模,我倾向于使用现有的 c++ 解析器。 因此,我开始使用 LLVM 项目 中的库来收集有关源文件的句法和语义信息。

LLVM 工具链接:https://clang.llvm.org/docs/Tooling.html


首先我从用 c++ 编写的 LibTooling 开始,因为它承诺我可以“完全控制”抽象语法树 (AST)。 我尝试了RecursiveASTVistorMatchfinder 方法但没有成功。

所以 LibTooling 被解雇了,因为我无法在 AST 中检索有关节点周围的上下文信息。 当访问 AST 中的特定节点时,我只能对回调做出反应。但我不知道我目前所处的环境。 例如。当我访问 C++RecordDeclaration(类、结构、联合)时,我不知道它是否是嵌套记录。 但是需要这些信息来计算单个类的代码行数。


第二种方法是通过 Python 绑定使用 LibClang 接口。 使用 LibClang 接口,我能够以递归方式逐个节点遍历 AST 节点,并将所需的上下文信息存储在堆栈中。 这里我遇到了一个 LibClang 的一般问题:

在为文件创建 AST 之前,预处理器会启动并解析所有预处理器指令。就像他应该做的那样。

  • 这很好,因为如果预处理器无法解析所有包含指令,则输出 AST 将不完整。
  • 这很糟糕,因为我无法为任何 c++ 项目提供所有包含文件或目录。
  • 这很糟糕,因为无论是否定义了预处理器变量,被条件预处理器指令包围的代码都不是 AST 的一部分。使用已定义或未定义的预处理器变量的不同设置多次解析同一个文件超出范围。

这导致了第三次使用由Antlr 生成的c++ 解析器的当前尝试提供了c++14 grammar

在解析器之前不执行预处理器。 这很好,因为解析了完整的源代码并且忽略了预处理器指令。 坏事是解析器似乎没有那么难。它在可以编译导致 AST 损坏的代码上失败。所以这个解决方案也是不够的。


我的问题是:

  • 在使用 libClang 解析 c/c++ 源文件或头文件之前,是否有停用预处理器的选项? 所以源代码原封不动,AST 完整而详细。
  • 有没有一种方法可以解析 c/c++ 源代码文件而不提供所有必要的包含目录,但仍会产生详细的 AST?
  • 因为我没有选项了。在分析/解析 c/c++ 源代码时,还有哪些其他方法值得研究?

如果您认为这里不适合提出此类问题,请随时将我重定向到另一个地方。

【问题讨论】:

  • 为什么您无法提供正确的包含路径?不运行预处理器就不能“解析完整的源代码”。如果没有看到给定 C++ 所引用的所有内容的声明,就不可能为 C++ 构建正确的 AST。在实践中大部分时间都可能获得您寻求的指标的OK近似值。但是要做到这一点,您可能既不需要也不想一开始就构建 AST。无论你做什么,你几乎肯定不得不诉诸启发式方法来弥补你没有的所有信息……
  • @MichaelKenzel 分析项目的工作流程是我获取项目的根源代码文件夹,而不包含类似的第三方文件,例如。 boost 库文件,因为这些不是客户开发的代码,因此对静态代码分析不感兴趣。所以我无法为预处理器提供所需的包含。 tl:dr 我没有这些包含文件/目录。
  • 我不明白关于 libTooling 无法"retrieve context information about the surrounding of a node in the AST" 的评论。你有完整的 AST(我认为)所以缺少什么上下文?
  • @G.M.对于节点的周围,我指的是父节点或其子节点。但是 RecursiveASTVisitor 仅在遍历 AST 时遇到指定类型的节点时才提供回调。因此,在我看来,我无法确定当前节点(导致回调的节点)是否是例如另一个类声明中的类声明。因为我不知道回调将以什么顺序发生。也许我对树遍历的看法是有限的。如果我无法在堆栈上推送和弹出节点以跟踪当前节点之前的内容,我会非常丢失。
  • 这是一个有趣的问题/项目,但 IMO 对 SO 来说太宽泛了,所以我倾向于关闭它。不过,我的一个注释:一般来说,如果没有预处理包含和宏,你就无法解析 C++。很多时候,宏包含源代码的一部分,如果不解析它们,您将没有有效的 C++ 代码。没有办法运行预处理器,因此如果没有必要的包含文件,就无法做你想做的事。

标签: c++ parsing antlr4 metrics llvm-clang


【解决方案1】:

回答你的最后一个问题,

因为我已经没有选项了。还有哪些其他方法可能值得 看看何时分析/解析 c/c++ 源代码?

另一种方法是解析源代码,就好像它只是文本一样。这避免了对源进行预处理并引入复杂的解析器的需要。有关示例/介绍,请参见本文:Andrian Marcus、Denys Poshyvanyk 的“类的概念凝聚力”。您仍然可以通过这种方法收集诸如 LOC 和方法数量等信息,而无需完整的解析器。

这种方法有缺点(与任何方法一样):

  • 1) 将 cmets 与源代码一起解析,或者 2) 要求您从源代码中删除 cmets。但后者是一个简单的步骤。可能没问题的原因是,即使 cmets 也包含有关代码的信息,这可能有助于确定哪些模块耦合更紧密,等等。
  • 它会将局部变量、方法名称、参数名称等全部集中到您正在使用的“词袋”中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-11
    • 2011-10-11
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-04
    相关资源
    最近更新 更多