Doxygen 终于可以正确生成函数调用图了!


大家都知道, Doxygen 可以用于提取代码的注释生成项目的文档,只要注释满足它规定的格式。我喜欢通过它生成类继承图(inheritance diagram)和函数调用图(callgraph),有了他们能够加快对代码的理解。

MySQL 代码为例, Doxygen 生成的类继承图和调用图分别是这样的:

/img/2021-02-16-doxygen_innodb_classhandler__inherit__graph.png
handler (存储引擎)类继承图
/img/2021-02-16-doxygen_mysql_ha_recover_callgraph.png
ha_recover() crash recovery 函数调用图

之前工作中交接了几个 C++ 项目,由于还不熟悉代码,就想用 Doxygen 来生成这两种图来加快熟悉代码,但结果却生成失败了。

而一个普通的 hello world 程序却没有问题,说明是 Doxygen 对特定的代码处理有 bug 。

几经排查,最后发现只要代码中包含2个以上的 using namespace xxx; 语句,调用图就无法生成。比如下面这样一个简单的类实现,就无法生成调用关系图。

#pragma once

namespace demo {
    class Test {
    public:
        void foo();
        void bar();
    };
}
test.h 代码
#include <iostream>
#include "test.h"

using namespace demo;
using namespace std;

void Test::foo()
{
    bar();
}

void Test::bar()
{
    return;
}

int main()
{
    Test test;
    test.foo();
    return 0;
}
test.cpp 代码

虽然这种直接包含命名空间的用法不推荐,但是在已有项目中却很常见,这样的话 Doxygen 基本就不可用了。

由于当时没有时间,就先给官方报了一个 issue ,等待他们解决。

没想到几个月之后,官方也还没有解决。而最近正好有点时间,就重新去分析这个 bug ,看如何解决。

解决过程虽然花了不少时间,但总结起来有几点:

  1. 缩小范围:对比正常生成调用图和无法生成调用图的两种情况下的 stdout 输出,再根据差异信息 grep 定位到生成 callgraph 图的相关代码;
  2. 添加 printf 调试信息,找出 callgraph 元数据的来源,即 Doxygen 怎么得到调用关系的; 最终找到这部份实现在 code.l (C族语言的 Flex 实现,用于解析工程源码)中, Doxygen 基于 Flex 实现了自己的词法解析;
  3. 学习 Flex 的使用方法;

最后终于找到问题的根源在于, Doxygen 在解析 using namespace xxx; 指令的时候,错误地认为文件中往后的代码属于 xxx 命名空间( pushScope ),从而把函数的绝对命名空间弄错,就没法解析出正确的调用关系了。

提了 PR 修复了之后,前面的测试代码就能正确地生成如下的函数调用图了。

/img/2021-02-16-doxygen_fix_test_callgraph.png
Test::foo() 的调用图

Updates:

  1. 2021.02 这个 PR 已经合并到 master 了,但是还没有发布,需要再等一等。
  2. 2021.08.19 发布的 版本 1.9.2,已经包含了这个 bugfix 。

也可以看看

comments powered by Disqus