解析C ++和控制流程

时间:2013-11-25 17:52:53

标签: c++ parsing abstract-syntax-tree control-flow

使用CMake和gcc 4.2.3构建了一个巨大的 C ++项目。该应用程序采用多个流程。

最终目标是列出可以写入日志文件的所有错误消息。信息和调试消息也会写入此文件。

我发现在一些main.cpp(一切都开始的文件)中有一个 catch 表达式,其中写入文件。所以我需要找到满足以下条件的 throw 表达式:

  1. throw表达式中使用的某种错误类型(例如runtime_error,logic_error等)。
  2. 位于main.cpp和 throw 表达式中的 catch 之间的堆栈中没有其他 catch 。如果有 catch ,它可能会附加其他信息(这很重要)并重新抛出。此外,它可能会重新使用不同的错误类型,甚至可以保持沉默。
  3. 该项目非常庞大,很难判断这部分代码是否会在此版本中执行。有些构建使用某些库,而其他构建则不使用。

    也许这个方法我错了,但我认为这个解决方案分为两个步骤:

    1. 在编译器看到它时解析所有C ++代码(确保throw不在注释部分,不是宏等)。
    2. 在已编译的树中查找所有throw表达式并模拟throw。事实上,我在这里看到一个问题,因为条件可能真的涉及,例如:

      string error_msg;
      enum Condition condition;
      switch(condition)
      {
         CONDITION1: error_msg = "sadasda"; break;
         CONDITION2: error_msg = "sadasds1111a"; break;
         CONDITION3: error_msg = "sasdasadasda"; break;
         default: error_msg = "sadasda"; break;
      }
      throw logic_error(error_msg);
      
    3. 也许这一切都是错的,应该采取不同的方法。我很高兴看到你的意见。

2 个答案:

答案 0 :(得分:2)

编写一个C ++有效的解析器确实是一个令人生畏的任务,至少可以说,并且可能不是更快的方式来获得你想要的地方。

基本上,你想要的是为你的目的重用现有的解析器,这也不容易。您需要研究各种编译器插件和静态分析工具。例如,clang static analyzer似乎(相对!)易于扩展。也许更简单的方法是使用现有的静态C ++分析器,如lint,并检测未捕获的异常。然后,修改main以停止捕获您感兴趣的异常,并查看未捕获的异常列表。你还远未完成,但你可以从那里开始工作。 C ++ lint不是免费软件,但AFAIK免费替代品(cppcheck,clang anlyzer)没有高级异常分析。也许coverity也可能是有意义的,他们有脚本和/或SDK来编写扩展。

另一种方法是在异常对象中故意泄漏内存,任何好的静态分析器都会在创建异常对象的位置找到泄漏源,即throw站点甚至可能是你添加的点信息到例外。我不知道你的代码是否真实,但在这个设置中,我认为免费的分析器可以工作。

无论如何,我祝你好运,使用大型代码库绝非易事;)

答案 1 :(得分:1)

(在问题之后很久就做出回应;最近对该问题的编辑突显了这个问题,以便我可见。)

OP基本上是正确的;你需要一个编译器精确的源代码解析,你需要跟踪抛出序列以查看它们的作用。

如果确实如此,您需要编译器准确地解析所有项目中涉及的编译单元,并且您需要立即将所有这些解析从一个编译单元导航到另一个编译单元追踪投掷。这意味着使用传统的编译器前端并不是正确的起始位置;那些只能一次解析一个编译单元,你需要一次所有

然后有关于跟踪"抛出"的一点点。您需要在每个函数/方法中使用控制流来跟随方法中,然后您需要跟踪方法调用之间的抛出。对于后者,您需要一个准确的调用图。标准编译器可能会给出方法内控制流程,但它不会计算全局调用图。

要获得一个好的通话图, 你需要解决从foo到bar的显式调用,你需要通过指针确定间接调用,哪些方法/函数是调用的可能目标,你需要确定多态方法调用(间接调用的特殊情况)一样。所以你需要一个分数分析器。

通过本地控制流程和准确的调用图,您现在可以找到每个初始投掷,并通过捕获链从投掷站点跟踪("模拟")它们是否最终到达主要点(或至少在调用日志记录功能时)。 throw-catch-test-rethrow很容易跟踪;你在复杂的catch子句中遇到麻烦,它包含了很多最终重新抛出的逻辑,跟踪实际重新抛出的异常,甚至是什么东西都被重新抛出。欢迎来到静态分析和图灵焦油坑。

所以实际上你需要一个专门用来做这些事情的工具。

唉,我知道现在没有任何工具可以很好地完成所有这些工具,而且我会试着跟踪这些事情。 (这通常适用于某些人可能想要的特定静态分析)。所以问题就变成了,你在哪里获得可以让你作为自定义工作完成这项任务的基础设施?

Clang可以提供其中一些内容;它肯定会为C ++解析和构建AST。启动LLVM后,您将进行方法内控制流分析。我认为Clang可以配置为解析多个编译单元,因此与使用编译器为您提供的内容相比,这是一个很大的进步。我不知道Clang为点数分析或构建调用图提供了什么。您必须填写该内容,并为"模拟"构建自定义代码。投掷。

用于程序分析和转换的DMS Software Reengineering Toolkit可用于此目的。 DMS还可以用编译器精确的方式解析完整的C ++,并且可以同时解析/处理多个编译单元。

DMS确实产生了方法内控制流分析,并且它具有帧内方法级数据流分析。我们目前没有对C ++进行分析,但是DMS确实有C点的分析和调用图构造,可以投入使用,已经在15,000(不是拼写错误)的应用程序上进行了测试一个图像中的编译单元有大约50,000个函数和间接调用纠缠在一起。 (如果Clang已经没有这种机器,这在起点上是一个巨大的差异)。有了它,那么你就可以在顶部建立投掷模拟。

考虑到所有这些,我的猜测是为Clang和/或DMS做上述工作的重要性。如果你的应用程序不到一百万行,我希望你可以通过使用grep搜索throw子句并通过代码自己跟踪它们来更快地完成(如果不是更加邋))。你说你的申请很大;如果没有特定的数字,很难说出实际意味着什么。这些工具在规模上工作得非常好,但是当问题很小时,这些工具并不值得。 [有趣的是," small"随着工具越来越好,随着时间的推移而移动。