|
| 1 | +# Flow Chart |
| 2 | + |
| 3 | +这是一个附加功能,它将为每个函数生成流程图,以 `.dot` 文件格式输出,`.dot` 文件可通过 |
| 4 | +- [Graphviz](https://graphviz.org/) |
| 5 | +- [Graphviz Online](https://dreampuf.github.io/GraphvizOnline) |
| 6 | + |
| 7 | +查看。 |
| 8 | + |
| 9 | +## 依赖 |
| 10 | + |
| 11 | +- [petgraph](https://github.com/petgraph/petgraph) |
| 12 | + |
| 13 | +## 实现 |
| 14 | + |
| 15 | +流程图的生成包含两个步骤: |
| 16 | + |
| 17 | +- 由 AST 生成 `图` 数据结构 |
| 18 | +- 根据 `图` 生成 `.dot文件` |
| 19 | + |
| 20 | +### 图的生成 |
| 21 | + |
| 22 | +我们以函数为单位生成 `graph` ,而 AST 根节点为 `ProgramNode`,因此我们需要遍历其 `fntypes` |
| 23 | + ,逐个生成 `graph` 最终得到一个 `Vec` : |
| 24 | + |
| 25 | + ```rust,no_run,noplayground |
| 26 | +{{#include ../../../src/flow/mod.rs:creategraphs}} |
| 27 | +``` |
| 28 | + |
| 29 | +接下来实现 `from_ast()` 函数,它接收一个 `NodeEnum`(这是一个 `Statements` 节点), 返回一个完整的 `Graph`,具体分为两步: |
| 30 | +- 初步构建图(`build_graph()`) |
| 31 | +- 去除不必要节点(`0入度节点`,`虚节点`,`空节点`)) |
| 32 | + ```rust,no_run,noplayground |
| 33 | +{{#include ../../../src/flow/mod.rs:fromast}} |
| 34 | +``` |
| 35 | +主要介绍构建图的环节。 |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +定义图的 `节点` 与 `边` : |
| 40 | + |
| 41 | + ```rust,no_run,noplayground |
| 42 | +{{#include ../../../src/flow/mod.rs:nodeandedge}} |
| 43 | +``` |
| 44 | + |
| 45 | +`build_graph()`函数以 `Statement` 为单位,针对不同节点构建不同的图结构,为了方便的连接节点,我们定义了 `GraphContext` 用于存储上下文信息: |
| 46 | + |
| 47 | + ```rust,no_run,noplayground |
| 48 | +{{#include ../../../src/flow/mod.rs:GraphContext}} |
| 49 | +``` |
| 50 | + |
| 51 | +每次调用 `build_graph()` 前,我们需要为构建部分提供两个 `锚点(local_source, local_sink)` ,第一次调用时,`锚点` 即为起点和终点, |
| 52 | +以后每次调用前,均需构建两个虚节点,作为`锚点`(虚节点之后将被去掉)。 |
| 53 | + |
| 54 | +对于不涉及分支、循环、跳转的简单语句,图结构较为简单: |
| 55 | + |
| 56 | + ``` |
| 57 | + local_source -> current -> local_sink |
| 58 | +
|
| 59 | + local_source ------------> local_sink // 注释 |
| 60 | +
|
| 61 | + local_source ---> ERR ---> local_sink // 错误 |
| 62 | +``` |
| 63 | + |
| 64 | +分支及循环语句则较为复杂(以 `IF语句` 为例): |
| 65 | + ``` |
| 66 | +IF: /--Y--> sub_source -----> [...body...] ------> sub_sink ->-\ |
| 67 | + / \ |
| 68 | +local_source -> cond local_sink |
| 69 | + \ / |
| 70 | + \--N--> sub_source1 -> Option<[...els...]> -> sub_sink ->-/ |
| 71 | +
|
| 72 | +``` |
| 73 | + |
| 74 | +`if.body` 及 `if.els` 部分可以通过递归调用 `build_graph()` 构建,但是需要先生成两个虚节点,并**暂时**赋给 `ctx`,构建完毕后, |
| 75 | +`ctx` 的 `local_source/sink`需要还原 |
| 76 | + |
| 77 | +对于语句块,对每个语句调用 `build_graph()` ,每次将 `sub_source` 更改为 `sub_sink` ,`sub_sink` 则重新创建: |
| 78 | + ```rust,no_run,noplayground |
| 79 | +{{#include ../../../src/flow/mod.rs:stsloop}} |
| 80 | +``` |
| 81 | + |
| 82 | +### .dot 文件生成 |
| 83 | + |
| 84 | +我们只需按dot语法格式生成图的点/边的信息即可。下面是一个简单的dot文件: |
| 85 | + |
| 86 | +``` |
| 87 | +digraph _pointer_struct__name_params { |
| 88 | + D0 [shape=box, style=rounded, label="begin", fontname=""]; |
| 89 | + {rank = sink; D1 [shape=box, style=rounded, label="end", fontname=""];} |
| 90 | + D4 [shape=box, label="return\l", fontname=""]; |
| 91 | + D4 -> D1; |
| 92 | + D0 -> D4; |
| 93 | +} |
| 94 | +``` |
| 95 | +可以在[Graphviz Online](https://dreampuf.github.io/GraphvizOnline)查看对应流程图。 |
0 commit comments