可视化 C 结构依赖项

Visualising C struct dependencies

在大型 C 项目中,有许多 struct 具有其他 struct 或指向它们的指针作为字段。我想创建一个有向图来显示 "types" 之间的依赖关系。一个例子是

typedef struct javaStat {
    int idNo;
    struct idIdentList *className;
    struct typeModifiers *thisType;
    struct symbol thisClass;
} ...

由此我想生成一个 DOT 结构,它看起来像

digraph {
    javaStat -> idIdentList
    javaStat -> typeModifiers
    javaStat -> symbol
}

或者,使用 DOT 简写:

digraph {
    javaStat -> {idIdentList typeModifiers symbol}
}

当然可以手动添加第一行和最后一行,所以主要问题是将结构引用转换为图 "pointer" 行。

此时我对第一级解决方案很满意,这意味着可以忽略更深的嵌套。

我首先尝试了一个简单的 grep struct *.h,它产生了一些可行的东西:

typedef struct javaStat {
    struct idIdentList *className;
    struct typeModifiers *thisType;
    struct symbol thisClass;
typedef struct <next struct> {

这是一个简单的问题,几行 Python 就可以解决,但是还有其他方便的解决方案吗,也许使用 sedgrepawk 和他们的兄弟?

编辑:我意识到我想这样做的原因是因为我需要找到一个或多个位于 "struct tree".

底部的结构

Clang 9 允许 JSON 表示 c 文件的 AST(在 question 中找到)。 JSON 可以进一步处理 AST 以生成目标输出。

例如这个 Python 脚本:

#clang_ast_to_dot.py
from jsonpath_rw_ext import parse;
import sys, json;

def extract_struct_name(fieldDefinition):
  return fieldDefinition["type"]["qualType"].replace("struct", "").replace("*", "").replace(" ","")

def is_struct_field(fieldDefinition, knownStructs):
  return (fieldDefinition["kind"] == "FieldDecl" and 
          ("struct " in fieldDefinition["type"]["qualType"] or 
           extract_struct_name(fieldDefinition) in knownStructs))


data = json.load(sys.stdin)

allStructs = {}

for structDef in parse('$.inner[?(@.kind=="RecordDecl")]').find(data):
    allStructs[structDef.value["name"]]=structDef.value

print("digraph {")
for name, structDescription in allStructs.items():
    print("    %s -> {%s}"
          % (name, ", ".join(extract_struct_name(field) for field in structDescription["inner"] if is_struct_field(field, allStructs))))
print("}")

称为:

clang -Xclang -ast-dump=json MyCFile.c | python clang_ast_to_dot.py

产生:

digraph {
    javaStat -> {idIdentList, typeModifiers, symbol}
}

当然这是一个玩具示例,我敢肯定它不会适用于所有情况。

我将从 运行 Doxygen 代码库开始。它可以很容易地配置为创建结构的点图。正确解析所有这些信息并生成正确的输出涉及足够多的怪癖和极端情况,使用现有解决方案可以节省很多时间。

@gavinb 的答案的扩展,带有一些示例。

有一个带有 EXTRACT_ALL = YESHAVE_DOT=YES 的 doxygen 配置文件(对于更复杂的情况,将 DOT_GRAPH_MAX_NODES = 设置为适当的值并设置 DOT_IMAGE_FORMAT = svg 可能会很有用; 同样有趣的可能是 UML_LOOK = YES).

我用了一个简单的例子:

typedef  struct idIdentList {
     int member;
};
typedef  struct typeModifiers {
     int member;
};
typedef  struct symbol {
     int member;
};
typedef  struct s1 {
     struct s2 member;
};
typedef  struct s2 {
     struct s3 member;
};
typedef  struct s3 {
     struct s4 member;
};
typedef  struct s4 {
     struct s5 member;
};
typedef  struct s5 {
     struct s6 member;
};
typedef  struct s6 {
     struct s6 member;
};
typedef struct javaStat {
    int idNo;
    struct idIdentList *className;
    struct typeModifiers *thisType;
    struct symbol thisClass;
    struct s1 member;
};

由此我得到:

Doxygen 没有完整的概览图,但通过一些脚本可以创建一个“超级结构”,例如(我还在此处添加了 struct not_ref 没有额外参考的地方):

typedef struct super_script
{
  struct idIdentList a1;
  struct typeModifiers a2;
  struct symbol a3;
  struct s1 a4;
  struct s2 a5;
  struct s3 a6;
  struct s4 a7;
  struct s5 a8;
  struct s6 a9;
  struct javaStat a10;
  struct not_ref a11;
};

typedef struct not_ref
{
  int member;
};

导致:

当您设置 DOT_CLEANUP = NO 时,使用的 dot 文件将在 html 目录中可用

尝试了@gavinb 提出的 doxygen 建议,@albert 对此进行了补充,这需要对源进行一些操作,以及@Renats 使用 Clangs python 绑定的建议,这对我来说有点复杂,我尝试了 pycparser.

Here's a link to that script在我需要的项目里

这是两个重要部分中的第一个:

ast = parse_file(args[-1], use_cpp=True,
                 cpp_args=cpp_args + args[0:-1])
print("digraph {")
for node in (node for node in ast.ext if isinstance(node, c_ast.Typedef)):
    if isinstance(node.type.type, c_ast.Struct):
        node2dot(node)
print("}")

主循环,其中 pycparser 将文件解析为 AST,然后对其进行过滤以仅获取输入 node2dot 的 typedef,该部分位于以下部分:

def node2dot(node):
    if isinstance(node.type, c_ast.TypeDecl) and isinstance(node.type.type, c_ast.Struct):
        print("   ", node.type.type.name, "-> {", end="")
        if node.type.type.decls:  # has fields?
            for field in node.type.type.decls:
                if isstruct(field.type):
                    print("", struct_name_of(field.type), end="")
        print(" }")

def struct_name_of(node):
    if isinstance(node, c_ast.Struct):
        return node.name
    elif isinstance(node, c_ast.TypeDecl) or isinstance(node, c_ast.PtrDecl):
        return struct_name_of(node.type)

def isstruct(node):
    if isinstance(node, c_ast.Struct):
        return True
    elif isinstance(node, c_ast.TypeDecl) or isinstance(node, c_ast.PtrDecl):
        return isstruct(node.type)