├── .gitignore
├── LICENSE
├── README.md
├── README_zh.md
├── analysis
├── analysis.go
└── prog.go
├── cache
└── cache.go
├── config
└── config.go
├── docs
└── image
│ └── header-cover.jpg
├── example.toml
├── example
├── go_eth_example.sh
├── graph_out
│ ├── complete_callgraph.dot
│ ├── complete_callgraph.svg
│ ├── simple_callgraph.dot
│ └── simple_callgraph.svg
├── init_genesis_analysis.toml
└── path_out
│ └── 0-1.csv
├── flow
├── algo.go
└── flow.go
├── go.mod
├── go.sum
├── ir
└── ir.go
├── log
└── log.go
├── main.go
├── mode
└── mode.go
├── render
└── render.go
├── static
├── dot
│ └── index.html
├── dot_simple
│ └── index.html
└── render
│ └── index.html
└── util
└── util.go
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Go template
2 | # If you prefer the allow list template instead of the deny list, see community template:
3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
4 | #
5 | # Binaries for programs and plugins
6 | *.exe
7 | *.exe~
8 | *.dll
9 | *.so
10 | *.dylib
11 |
12 | # Test binary, built with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # Dependency directories (remove the comment below to include it)
19 | # vendor/
20 |
21 | # Go workspace file
22 | go.work
23 |
24 | *.go_callflow_vis_cache
25 |
26 | *.toml
27 |
28 | *_out
29 |
30 | example
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Laindream
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
go-callflow-vis
6 |
7 | English / [简体中文](README_zh.md)
8 |
9 |
go-callflow-vis is a tool for analyzing and visualizing complex software architecture hierarchies
10 |
11 | ---
12 |
13 |
14 |
15 | ## Introduction
16 |
17 | Traditional static analysis tools(like [go-callvis](https://github.com/ondrajz/go-callvis)) produce a complete call graph for a project. However, when dealing with complex projects, the result is often a tangled mess, difficult to discern, and loses practical value. In contrast, go-callflow-vis offers a more refined and controllable method for analyzing the architectural hierarchies of complex software.
18 |
19 | go-callflow-vis allows users to define a series of ordered call hierarchies (for instance, in a typical Web project, these layers might be API->Operator->Manager->DAO) and enables specifying key functions or function categories for each layer through a configuration file. go-callflow-vis can analyze and visualize the reachability and call flow between these hierarchical functions. For two reachable functions across adjacent layers, go-callflow-vis only provides one example path to prevent the call graph from becoming overly complicated, ensuring that the functions' reachability is accurately represented.
20 |
21 | ## Features
22 |
23 | - **Hierarchical Call Flow Output**: Focuses on the reachability and call flow between functions of adjacent layers, avoiding overly complex results.
24 |
25 | - **Flexible Configuration**: Allows users to define key functions or function categories for each layer, enabling more precise project structure analysis.
26 |
27 | - **Visualization and Interaction**: Offers excellent, interactive visual results, helping developers understand and optimize code structure more intuitively.
28 |
29 | ## Installation
30 |
31 | ```shell
32 | go install github.com/laindream/go-callflow-vis@latest
33 | ```
34 |
35 | ## Usage
36 |
37 | Here, we use the analysis of [go-ethereum](https://github.com/ethereum/go-ethereum) as an example (see the [example](example) directory for details).
38 |
39 | - **Writing Configuration File**
40 |
41 | Suppose you want to quickly analyze the call relationship to the MPT (Merkle Patricia Trie) DB during the creation of the genesis block in go-ethereum, you can write the configuration file as follows ([example.toml](example.toml) introduces how to make detailed configurations):
42 |
43 | ```toml
44 | # file:init_genesis_analysis.toml
45 |
46 | # package_prefix is for trimming the function name in graph for human readability
47 | package_prefix = "github.com/ethereum/go-ethereum/"
48 |
49 |
50 | # [optional] focus is a set of rules to filter the functions to be analyzed
51 | [[focus]]
52 | rules = [{ type = "contain", content = "github.com/ethereum" }]
53 |
54 |
55 | # [optional] ignore is a set of rules to filter the functions to be ignored
56 | # [[ignore]]
57 | # rules = [{ type = "contain", content = "fmt" }]
58 |
59 |
60 | # layer is a set of matched functions used to generate flow graph. layers must be defined in order.
61 | [[layer]]
62 | name = "CMD Layer"
63 | [[layer.entities]]
64 | # match rule for the function name
65 | # there are match type: "contain", "prefix", "suffix", "equal", "regexp", default to use "equal" if not set type
66 | # can set exclude = true to exclude the matched functions
67 | name = { rules = [{ type = "suffix", content = "initGenesis" }] }
68 |
69 |
70 | [[layer]]
71 | name = "DB Layer"
72 | [[layer.entities]]
73 | name = { rules = [{ type = "contain", content = "triedb.Database" }] }
74 | ```
75 |
76 | - **Starting the Analysis**
77 |
78 | Next, assuming you have downloaded the source code of go-ethereum and installed go-callflow-vis; then, entering the cmd/geth directory, you can start the analysis with the following command (see the quick script in [go_eth_example.sh](example/go_eth_example.sh)):
79 |
80 | ```shell
81 | # run go-callflow-vis directly to see detailed command usage
82 | go-callflow-vis -config init_genesis_analysis.toml -web .
83 | ```
84 |
85 | - **Viewing the Analysis Results**
86 |
87 | If everything goes well, you will be able to see your browser pop up and display the visualized and interactive analysis results.
88 |
89 | In addition, the program will output the analysis call graph([dot file](example/graph_out)) and the call chain list([csv file](example/path_out)), default location: `./graph_out` and `./path_out` .
90 |
91 | You can also obtain visualized svg files from the call graph's dot files (requires installing [graphviz](https://graphviz.org/)).
92 |
93 | Run the following command in the graph_out directory:
94 |
95 | ```shell
96 | dot -Tsvg -o complete_callgraph.svg complete_callgraph.dot
97 | dot -Tsvg -o simple_callgraph.svg simple_callgraph.dot
98 | ```
99 |
100 | You will be able to see two versions of the call graph, the complete version and the simplified version.
101 |
102 | Complete version:
103 |
104 | 
105 |
106 | Simplified version:
107 |
108 | 
109 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
go-callflow-vis
6 |
7 | [English](README.md) / 简体中文
8 |
9 |
go-callflow-vis 是一个用于分析和可视化复杂软件架构层级的工具
10 |
11 | ---
12 |
13 |
14 |
15 | ## 介绍
16 |
17 | 传统的静态分析工具(如[go-callvis](https://github.com/ondrajz/go-callvis))输出项目中全部的调用关系; 面对复杂的项目, 结果往往是一团乱麻, 难以辨认, 失去实际的使用价值. 相比之下, go-callflow-vis提供了一种更加精细和可控的方式来分析复杂软件的架构层级.
18 |
19 | go-callflow-vis允许用户自定义一系列有序的调用层级(例如在一个典型的Web项目中, 这些层级可能是API->Operator->Manager->DAO), 并且可以通过配置文件指定每一层的关键函数或函数类别; go-callflow-vis能够分析并可视化这些层级函数之间的可达性与调用流,对于相邻层级的两个可达的函数,go-callflow-vis只给出一条示例路径避免整个调用图过于复杂,而函数间的可达性是完全正确的。
20 |
21 | ## 特性
22 |
23 | - **按层级输出调用流**: 聚焦相邻层级函数之间的可达性与调用流, 避免结果过于复杂.
24 |
25 | - **灵活配置**: 允许用户自定义每一层的关键函数或函数类别, 更精确地分析项目结构.
26 |
27 | - **可视化与交互**: 提供良好的可交互的可视化结果, 更直观地帮助开发者了解与优化代码结构.
28 |
29 | ## 安装
30 |
31 | ```shell
32 | go install github.com/laindream/go-callflow-vis@latest
33 | ```
34 |
35 | ## 使用
36 |
37 | 这里我们以对[go-ethereum](https://github.com/ethereum/go-ethereum)的分析为例(详情见[example](example)目录).
38 |
39 | - **编写配置文件**
40 |
41 | 假设你想要快速分析go-ethereum中创建创世区块时对MPT(Merkle Patricia Trie)DB的调用关系, 你可以如下编写配置文件([example.toml](example.toml)中介绍了如何进行详细配置):
42 |
43 | ```toml
44 | # file:init_genesis_analysis.toml
45 |
46 | # package_prefix is for trimming the function name in graph for human readability
47 | package_prefix = "github.com/ethereum/go-ethereum/"
48 |
49 |
50 | # [optional] focus is a set of rules to filter the functions to be analyzed
51 | [[focus]]
52 | rules = [{ type = "contain", content = "github.com/ethereum" }]
53 |
54 |
55 | # [optional] ignore is a set of rules to filter the functions to be ignored
56 | # [[ignore]]
57 | # rules = [{ type = "contain", content = "fmt" }]
58 |
59 |
60 | # layer is a set of matched functions used to generate flow graph. layers must be defined in order.
61 | [[layer]]
62 | name = "CMD Layer"
63 | [[layer.entities]]
64 | # match rule for the function name
65 | # there are match type: "contain", "prefix", "suffix", "equal", "regexp", default to use "equal" if not set type
66 | # can set exclude = true to exclude the matched functions
67 | name = { rules = [{ type = "suffix", content = "initGenesis" }] }
68 |
69 |
70 | [[layer]]
71 | name = "DB Layer"
72 | [[layer.entities]]
73 | name = { rules = [{ type = "contain", content = "triedb.Database" }] }
74 | ```
75 |
76 | - **开始分析**
77 |
78 | 接下来, 假设你已经下载了go-ethereum的源码, 并且已经安装了go-callflow-vis; 那么进入cmd/geth目录, 你可以通过以下命令开始分析(快速脚本见[go_eth_example.sh](example/go_eth_example.sh)):
79 |
80 | ```shell
81 | # run go-callflow-vis directly to see detailed command usage
82 | go-callflow-vis -config init_genesis_analysis.toml -web .
83 | ```
84 |
85 | - **查看分析结果**
86 |
87 | 如果一切正常, 那么你将能够看到你的浏览器弹出并显示可视化可交互的分析结果.
88 |
89 | 此外, 程序还会输出分析调用图([dot文件](example/graph_out))和调用链列表([csv文件](example/path_out)), 默认位置: `./graph_out` 和 `./path_out` .
90 |
91 | 你也可以通过调用图的dot文件得到可视化的svg文件(需要安装[graphviz](https://graphviz.org/)).
92 |
93 | 在graph_out目录下运行如下命令:
94 |
95 | ```shell
96 | dot -Tsvg -o complete_callgraph.svg complete_callgraph.dot
97 | dot -Tsvg -o simple_callgraph.svg simple_callgraph.dot
98 | ```
99 |
100 | 你将可以看到两个版本的调用图, 完整版和简化版.
101 |
102 | 完整版:
103 |
104 | 
105 |
106 | 简化版:
107 |
108 | 
109 |
--------------------------------------------------------------------------------
/analysis/analysis.go:
--------------------------------------------------------------------------------
1 | package analysis
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/laindream/go-callflow-vis/cache"
7 | "github.com/laindream/go-callflow-vis/config"
8 | "github.com/laindream/go-callflow-vis/flow"
9 | "github.com/laindream/go-callflow-vis/ir"
10 | "github.com/laindream/go-callflow-vis/log"
11 | "github.com/laindream/go-callflow-vis/util"
12 | "golang.org/x/tools/go/callgraph"
13 | )
14 |
15 | type CallgraphType string
16 |
17 | const (
18 | CallGraphTypeStatic CallgraphType = "static"
19 | CallGraphTypeCha = "cha"
20 | CallGraphTypeRta = "rta"
21 | CallGraphTypePta = "pta"
22 | CallGraphTypeVta = "vta"
23 | )
24 |
25 | type Analysis struct {
26 | *ProgramAnalysisParam
27 | callgraph *ir.Callgraph
28 | config *config.Config
29 | flow *flow.Flow
30 | cachePath string
31 | cache *cache.FileCache
32 | fastMode bool
33 | }
34 |
35 | type ProgramAnalysisParam struct {
36 | Algo CallgraphType `json:"algo"`
37 | Tests bool `json:"tests"`
38 | Dir string `json:"dir"`
39 | Args []string `json:"args"`
40 | Build []string `json:"build"`
41 | }
42 |
43 | func NewAnalysis(
44 | config *config.Config,
45 | cachePath string,
46 | algo CallgraphType,
47 | tests bool,
48 | dir string,
49 | args []string,
50 | build []string,
51 | fastMode bool) *Analysis {
52 | if config == nil {
53 | fmt.Printf("Analysis.NewAnalysis: config is nil\n")
54 | return nil
55 | }
56 | return &Analysis{
57 | config: config,
58 | cache: cache.NewFileCache(cachePath),
59 | fastMode: fastMode,
60 | ProgramAnalysisParam: &ProgramAnalysisParam{
61 | Algo: algo,
62 | Tests: tests,
63 | Dir: dir,
64 | Args: args,
65 | Build: build,
66 | },
67 | }
68 | }
69 |
70 | func (a *Analysis) Run() error {
71 | filterCacheKey := fmt.Sprintf("%s_filter_%s", a.GetCacheKeyPrefix(), util.GetHash(a.config))
72 | var filterCacheObj *ir.Callgraph
73 | err := a.cache.Get(filterCacheKey, &filterCacheObj)
74 | if err == nil && filterCacheObj != nil {
75 | log.GetLogger().Debugf("Analysis.Run: cache hit: %s", filterCacheKey)
76 | a.callgraph = filterCacheObj
77 | }
78 | if a.callgraph == nil {
79 | err = a.InitCallgraph()
80 | if err != nil {
81 | log.GetLogger().Errorf("Analysis.Run: init callgraph error: %v", err)
82 | return err
83 | }
84 | err = a.FilterCallGraph()
85 | if err != nil {
86 | log.GetLogger().Errorf("Analysis.Run: filter callgraph error: %v", err)
87 | return err
88 | }
89 | }
90 | err = a.GenerateFlow()
91 | if err != nil {
92 | log.GetLogger().Errorf("Analysis.Run: generate flow error: %v", err)
93 | return err
94 | }
95 | return nil
96 | }
97 |
98 | func (a *Analysis) InitCallgraph() error {
99 | cacheKey := a.GetCacheKeyPrefix()
100 | var cacheObj *ir.Callgraph
101 | err := a.cache.Get(cacheKey, &cacheObj)
102 | if err == nil && cacheObj != nil {
103 | log.GetLogger().Debugf("Analysis.InitCallgraph: cache hit: %s", cacheKey)
104 | a.callgraph = cacheObj
105 | return nil
106 | }
107 | log.GetLogger().Debugf("Analysis.InitCallgraph: Program Analysis Start...")
108 | programAnalysis, err := RunAnalysis(a.Tests, a.Build, a.Args, a.Dir)
109 | if err != nil {
110 | log.GetLogger().Errorf("Analysis.InitCallgraph: program analysis error: %v", err)
111 | return err
112 | }
113 | var cg *callgraph.Graph
114 | log.GetLogger().Debugf("Analysis.InitCallgraph: Callgraph Compute Start(algo:%s)...", a.Algo)
115 | cg, err = a.ComputeCallgraph(programAnalysis)
116 | if err != nil {
117 | log.GetLogger().Errorf("Analysis.InitCallgraph: callgraph compute error: %v", err)
118 | return err
119 | }
120 | log.GetLogger().Debugf("Analysis.InitCallgraph: Callgraph Convert Start...")
121 | cg.DeleteSyntheticNodes()
122 | a.callgraph = ir.ConvertToIR(cg)
123 | log.GetLogger().Debugf("Analysis.InitCallgraph: cache set: %s", cacheKey)
124 | err = a.cache.Set(cacheKey, a.callgraph)
125 | if err != nil {
126 | log.GetLogger().Errorf("Analysis.InitCallgraph cache set error: %v", err)
127 | }
128 | return nil
129 | }
130 |
131 | func (a *Analysis) GetCacheKeyPrefix() string {
132 | programAnalysisParamHash := util.GetHash(a.ProgramAnalysisParam)
133 | return fmt.Sprintf("%s_%s", "callgraph", programAnalysisParamHash)
134 | }
135 |
136 | func (a *Analysis) GenerateFlow() error {
137 | if a.callgraph == nil {
138 | return errors.New("callgraph is nil")
139 | }
140 | if a.config == nil {
141 | return errors.New("config is nil")
142 | }
143 | f, err := flow.NewFlow(a.config, a.callgraph, a.fastMode)
144 | if err != nil {
145 | log.GetLogger().Errorf("Analysis.GenerateFlow: flow new error: %v", err)
146 | return err
147 | }
148 | err = f.Generate()
149 | if err != nil {
150 | log.GetLogger().Errorf("Analysis.GenerateFlow: flow generate error: %v", err)
151 | return err
152 | }
153 | a.flow = f
154 | return nil
155 | }
156 |
157 | func (a *Analysis) GetFlow() *flow.Flow {
158 | return a.flow
159 | }
160 |
161 | func (a *Analysis) FilterCallGraph() error {
162 | cacheKey := fmt.Sprintf("%s_filter_%s", a.GetCacheKeyPrefix(), util.GetHash(a.config))
163 | var cacheObj *ir.Callgraph
164 | err := a.cache.Get(cacheKey, &cacheObj)
165 | if err == nil && cacheObj != nil {
166 | log.GetLogger().Debugf("Analysis.FilterCallGraph: cache hit: %s", cacheKey)
167 | a.callgraph = cacheObj
168 | return nil
169 | }
170 | if a.callgraph == nil {
171 | return errors.New("callgraph is nil")
172 | }
173 | log.GetLogger().Debugf("Analysis.FilterCallGraph: Filter Callgraph Start...")
174 | a.callgraph = ir.GetFilteredCallgraph(a.callgraph, func(funcName string) bool {
175 | if (len(a.config.Focus) != 0 && !a.config.Focus.Match(funcName)) ||
176 | (len(a.config.Ignore) != 0 && a.config.Ignore.Match(funcName)) {
177 | return false
178 | }
179 | return true
180 | })
181 | log.GetLogger().Debugf("Analysis.FilterCallGraph: cache set: %s", cacheKey)
182 | err = a.cache.Set(cacheKey, a.callgraph)
183 | if err != nil {
184 | log.GetLogger().Errorf("Analysis.FilterCallGraph cache set error: %v", err)
185 | }
186 | return nil
187 | }
188 |
--------------------------------------------------------------------------------
/analysis/prog.go:
--------------------------------------------------------------------------------
1 | package analysis
2 |
3 | import (
4 | "fmt"
5 | "go/build"
6 | "golang.org/x/tools/go/callgraph/vta"
7 | "strings"
8 |
9 | "golang.org/x/tools/go/callgraph"
10 | "golang.org/x/tools/go/callgraph/cha"
11 | "golang.org/x/tools/go/callgraph/rta"
12 | "golang.org/x/tools/go/callgraph/static"
13 | "golang.org/x/tools/go/packages"
14 | "golang.org/x/tools/go/ssa"
15 | "golang.org/x/tools/go/ssa/ssautil"
16 | )
17 |
18 | type ProgramAnalysis struct {
19 | Prog *ssa.Program
20 | Pkgs []*ssa.Package
21 | Mains []*ssa.Package
22 | }
23 |
24 | const pkgLoadMode = packages.NeedName |
25 | packages.NeedFiles |
26 | packages.NeedCompiledGoFiles |
27 | packages.NeedImports |
28 | packages.NeedDeps |
29 | packages.NeedExportsFile |
30 | packages.NeedTypes |
31 | packages.NeedSyntax |
32 | packages.NeedTypesInfo |
33 | packages.NeedTypesSizes |
34 | packages.NeedModule
35 |
36 | func getBuildFlags() []string {
37 | buildFlagTags := getBuildFlagTags(build.Default.BuildTags)
38 | if len(buildFlagTags) == 0 {
39 | return nil
40 | }
41 |
42 | return []string{buildFlagTags}
43 | }
44 |
45 | func getBuildFlagTags(buildTags []string) string {
46 | if len(buildTags) > 0 {
47 | return "-tags=" + strings.Join(buildTags, ",")
48 | }
49 | return ""
50 | }
51 |
52 | func RunAnalysis(withTests bool, buildFlags []string, pkgPatterns []string, queryDir string) (*ProgramAnalysis, error) {
53 | if len(pkgPatterns) == 0 {
54 | return nil, fmt.Errorf("no package patterns provided")
55 | }
56 | if len(buildFlags) == 0 {
57 | buildFlags = getBuildFlags()
58 | }
59 | cfg := &packages.Config{
60 | Mode: packages.LoadAllSyntax,
61 | Tests: withTests,
62 | BuildFlags: buildFlags,
63 | Dir: queryDir,
64 | }
65 | //if gopath != "" {
66 | // cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
67 | //}
68 | initial, err := packages.Load(cfg, pkgPatterns...)
69 | if err != nil {
70 | return nil, fmt.Errorf("loading packages: %v", err)
71 | }
72 | if packages.PrintErrors(initial) > 0 {
73 | return nil, fmt.Errorf("packages contain errors")
74 | }
75 |
76 | // Create and build SSA-form program representation.
77 | mode := ssa.InstantiateGenerics // instantiate generics by default for soundness
78 | prog, pkgs := ssautil.AllPackages(initial, mode)
79 | prog.Build()
80 |
81 | return &ProgramAnalysis{
82 | Prog: prog,
83 | Pkgs: pkgs,
84 | //Mains: mains,
85 | }, nil
86 | }
87 |
88 | func (a *Analysis) ComputeCallgraph(data *ProgramAnalysis) (*callgraph.Graph, error) {
89 | switch a.Algo {
90 | case CallGraphTypeStatic:
91 | return static.CallGraph(data.Prog), nil
92 | case CallGraphTypeCha:
93 | return cha.CallGraph(data.Prog), nil
94 | case CallGraphTypePta:
95 | return nil, fmt.Errorf("pointer analysis is no longer supported (see Go issue #59676)")
96 | case CallGraphTypeRta:
97 | mains, err := mainPackages(data.Pkgs)
98 | if err != nil {
99 | return nil, err
100 | }
101 | var roots []*ssa.Function
102 | for _, main := range mains {
103 | roots = append(roots, main.Func("init"), main.Func("main"))
104 | }
105 | return rta.Analyze(roots, true).CallGraph, nil
106 | case CallGraphTypeVta:
107 | return vta.CallGraph(ssautil.AllFunctions(data.Prog), cha.CallGraph(data.Prog)), nil
108 | default:
109 | return nil, fmt.Errorf("unknown callgraph type: %v", a.Algo)
110 | }
111 | }
112 |
113 | func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
114 | var mains []*ssa.Package
115 | for _, p := range pkgs {
116 | if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
117 | mains = append(mains, p)
118 | }
119 | }
120 | if len(mains) == 0 {
121 | return nil, fmt.Errorf("no main packages")
122 | }
123 | return mains, nil
124 | }
125 |
--------------------------------------------------------------------------------
/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "github.com/apache/incubator-fury/go/fury"
7 | "github.com/laindream/go-callflow-vis/ir"
8 | "io"
9 | "log"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | )
14 |
15 | var (
16 | defaultCachePath = "./.go_callflow_vis_cache"
17 | furyC *fury.Fury
18 | )
19 |
20 | func GetFury() *fury.Fury {
21 | return furyC
22 | }
23 |
24 | func init() {
25 | if furyC != nil {
26 | return
27 | }
28 | fr := fury.NewFury(true)
29 | fr.RegisterTagType("ir.Callgraph", ir.Callgraph{})
30 | fr.RegisterTagType("ir.Node", ir.Node{})
31 | fr.RegisterTagType("ir.Edge", ir.Edge{})
32 | fr.RegisterTagType("ir.Func", ir.Func{})
33 | fr.RegisterTagType("ir.Site", ir.Site{})
34 | furyC = fr
35 | }
36 |
37 | type FileCache struct {
38 | path string
39 | }
40 |
41 | func NewFileCache(path string) *FileCache {
42 | if path == "" {
43 | path = defaultCachePath
44 | }
45 | if strings.HasSuffix(path, "/") {
46 | path = path[:len(path)-1]
47 | }
48 | return &FileCache{path: path}
49 | }
50 |
51 | func (f *FileCache) Set(key string, value interface{}) error {
52 | filename := fmt.Sprintf("%s/%s.fury", f.path, key)
53 | dir := filepath.Dir(filename)
54 | if err := os.MkdirAll(dir, os.ModePerm); err != nil {
55 | return err
56 | }
57 | file, err := os.Create(filename)
58 | if err != nil {
59 | return err
60 | }
61 | defer file.Close()
62 | writer := bufio.NewWriter(file)
63 | bytes, err := GetFury().Marshal(value)
64 | if err != nil {
65 | return err
66 | }
67 | _, err = writer.Write(bytes)
68 | if err != nil {
69 | return err
70 | }
71 | if err := writer.Flush(); err != nil {
72 | return err
73 | }
74 | return nil
75 | }
76 |
77 | func (f *FileCache) Get(key string, valuePtr interface{}) error {
78 | file, err := os.Open(fmt.Sprintf("%s/%s.fury", f.path, key))
79 | if err != nil {
80 | return err
81 | }
82 | defer file.Close()
83 | reader := bufio.NewReader(file)
84 | bytes, err := io.ReadAll(reader)
85 | if err != nil {
86 | log.Printf("Error reading from reader: %v", err)
87 | return err
88 | }
89 | err = GetFury().Unmarshal(bytes, valuePtr)
90 | if err != nil {
91 | return err
92 | }
93 | return nil
94 | }
95 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "github.com/BurntSushi/toml"
6 | "github.com/laindream/go-callflow-vis/log"
7 | "github.com/laindream/go-callflow-vis/mode"
8 | )
9 |
10 | type Config struct {
11 | PackagePrefix string `toml:"package_prefix" json:"-"`
12 | Focus mode.Set `toml:"focus" json:"focus"`
13 | Ignore mode.Set `toml:"ignore" json:"ignore"`
14 | Layers []*Layer `toml:"layer" json:"-"`
15 | }
16 |
17 | type Layer struct {
18 | Name string `toml:"name"`
19 | Entities []*Entity `toml:"entities"`
20 | }
21 |
22 | type Entity struct {
23 | Name *mode.Mode `toml:"name" json:"name,omitempty"`
24 | InSite *mode.Mode `toml:"in_site" json:"in_site,omitempty"`
25 | OutSite *mode.Mode `toml:"out_site" json:"out_site,omitempty"`
26 | Signature *mode.Mode `toml:"signature" json:"signature,omitempty"`
27 | }
28 |
29 | func LoadConfig(path string) (*Config, error) {
30 | var config Config
31 | log.GetLogger().Debugf("loading config from %s", path)
32 | if _, err := toml.DecodeFile(path, &config); err != nil {
33 | return nil, err
34 | }
35 | if err := config.Validate(); err != nil {
36 | return nil, err
37 | }
38 | log.GetLogger().Debugf("config loaded")
39 | return &config, nil
40 | }
41 |
42 | func (c *Config) Validate() error {
43 | if c == nil {
44 | return fmt.Errorf("config is nil")
45 | }
46 | if len(c.Layers) < 2 {
47 | return fmt.Errorf("number of layers must be at least 2")
48 | }
49 | for i, layer := range c.Layers {
50 | if len(layer.Entities) == 0 {
51 | return fmt.Errorf("layer %d has no entities", i)
52 | }
53 | }
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/docs/image/header-cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laindream/go-callflow-vis/6af8081bc0c0559c7d59ef835768e47f10a3fdc8/docs/image/header-cover.jpg
--------------------------------------------------------------------------------
/example.toml:
--------------------------------------------------------------------------------
1 | # package_prefix is for trimming the function name in graph for human readability
2 | package_prefix = "github.com/username/project/"
3 |
4 |
5 | # focus is for filtering the functions to be included in the graph
6 | [[focus]]
7 | # choose "and" or "or" logic to combine the rules if there are multiple rules
8 | and = true
9 | # match rule for the function name
10 | # there are match type: "contain", "prefix", "suffix", "equal", "regexp", default to use "equal" if not set type
11 | # can set exclude = true to exclude the matched functions
12 | rules = [{ type = "contain", content = "funcNameB", exclude = true }, { type = "prefix", content = "github.com/username/project/pkgB." }]
13 |
14 | # there can be multiple focus
15 | [[focus]]
16 | # can ignore "and" or "or" if there is only one rule
17 | rules = [{ type = "prefix", content = "(*github.com/username/project/pkgAA.structAA)" }]
18 |
19 |
20 | # ignore is for filtering the functions to be excluded in the graph
21 | [[ignore]]
22 | and = true
23 | rules = [{ type = "regexp", content = ".*funcName.*" }, { type = "equal", content = "(*github.com/username/project/pkgC.structC).FuncC" }]
24 |
25 | # ignore is also can be multiple
26 | [[ignore]]
27 | or = true
28 | rules = [{ type = "suffix", content = "funcNameD" }, { type = "prefix", content = "(*github.com/username/project/pkgD.structD)" }]
29 |
30 |
31 | # layer is a set of matched functions used to generate flow graph. layers must be defined in order.
32 | [[layer]]
33 | name = "Layer1"
34 | [[layer.entities]]
35 | # match rule for the function name
36 | name = { and = true, rules = [{ type = "contain", content = "github.com/username/project/pkgAA" }, { type = "regexp", content = ".*funcNameD.*" }] }
37 | # match rule for the function signature
38 | signature = { rules = [{ type = "contain", content = "bool" }] }
39 | # match rule for the function insite
40 | in_site = { rules = [{ type = "contain", content = "invoke FuncBB" }] }
41 | # match rule for the function outsite
42 | out_site = { rules = [{ type = "contain", content = "invoke FuncCC" }] }
43 | #another entity
44 | #[[layer.entities]] ...
45 |
46 | # the next layer
47 | [[layer]]
48 | name = "Layer2"
49 | # entities ...
50 |
51 | # the next layer
52 | [[layer]]
53 | name = "Layer3"
54 | # entities ...
--------------------------------------------------------------------------------
/example/go_eth_example.sh:
--------------------------------------------------------------------------------
1 | git clone --filter=blob:none --no-checkout --single-branch --branch master https://github.com/ethereum/go-ethereum.git
2 |
3 | cd go-ethereum || exit
4 |
5 | git sparse-checkout init --no-cone
6 |
7 | git sparse-checkout set '*.go' '*go.mod' '*go.sum' '*.c' '*.h'
8 |
9 | git checkout
10 |
11 | cd cmd/geth || exit
12 |
13 | go install github.com/laindream/go-callflow-vis@latest
14 |
15 | go-callflow-vis -config ../../../init_genesis_analysis.toml -web -debug -out-dir ../../.. -cache-dir ../../../.go_callflow_vis_cache .
16 |
--------------------------------------------------------------------------------
/example/graph_out/complete_callgraph.dot:
--------------------------------------------------------------------------------
1 | digraph Flow {
2 | newrank=true;
3 | rankdir=LR;
4 | 8266->57931[ label="(*Node).stopServices(n, t12)" ];
5 | 13117->46336[ label="(*triedb.Database).Size(t51)" ];
6 | 41202->12100[ label="invoke db.OpenTrie(root)" ];
7 | 41203->60738[ label="invoke t62.Commit(true:bool)" ];
8 | 60738->67906[ label="invoke t19.InsertPreimage(t3)" ];
9 | 12100->68785[ label="trie.NewVerkleTrie(root, t6, t5)" ];
10 | 11597->20404[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)" ];
11 | 41203->49385[ label="(*triedb.Database).Update(t200, t185, t190, block, t11, t197)" ];
12 | 57931->13111[ label="invoke t9.Stop()" ];
13 | 20404->16891[ label="(*Genesis).Commit(t20, db, triedb)" ];
14 | 16895->41203[ label="(*core/state.StateDB).Commit(t3, 0:uint64, false:bool)" ];
15 | 11597->8282[ label="defer (*triedb.Database).Close(t81)" ];
16 | 11597->8266[ label="defer (*node.Node).Close(t30)" ];
17 | 13117->32899[ label="(*triedb.Database).Scheme(t15)" ];
18 | 20404->36719[ label="(*triedb.Database).Initialized(triedb, t37)" ];
19 | 16895->46333[ label="(*triedb.Database).Commit(triedb, t17, true:bool)" ];
20 | 13117->58461[ label="(*triedb.Database).Journal(t29, t32)" ];
21 | 16891->16895[ label="flushAlloc(t16, db, triedb, t17)" ];
22 | 13117->40713[ label="(*triedb.Database).Dereference(t51, t117)" ];
23 | 16895->41202[ label="core/state.New(t0, t1, nil:*core/state/snapshot.Tree)" ];
24 | 12100->33296[ label="(*triedb.Database).IsVerkle(t1)" ];
25 | 68785->20196[ label="newTrieReader(root, [32]byte{}:common.Hash, db)" ];
26 | 20196->43858[ label="invoke db.Reader(stateRoot)" ];
27 | 13111->13117[ label="(*core.BlockChain).Stop(t19)" ];
28 | subgraph cluster_0 {
29 | rank=same;
30 | style=invis;
31 | 11597 [ color=red, label="cmd/geth.initGenesis" ];
32 |
33 | }
34 | ;
35 | subgraph cluster_1 {
36 | rank=same;
37 | style=invis;
38 | 32899 [ color=red, label="(*triedb.Database).Scheme" ];
39 | 33296 [ color=red, label="(*triedb.Database).IsVerkle" ];
40 | 36719 [ color=red, label="(*triedb.Database).Initialized" ];
41 | 40713 [ color=red, label="(*triedb.Database).Dereference" ];
42 | 43858 [ color=red, label="(*triedb.Database).Reader" ];
43 | 46333 [ color=red, label="(*triedb.Database).Commit" ];
44 | 46336 [ color=red, label="(*triedb.Database).Size" ];
45 | 49385 [ color=red, label="(*triedb.Database).Update" ];
46 | 58461 [ color=red, label="(*triedb.Database).Journal" ];
47 | 67906 [ color=red, label="(*triedb.Database).InsertPreimage" ];
48 | 8282 [ color=red, label="(*triedb.Database).Close" ];
49 |
50 | }
51 | ;
52 | 12100 [ label="(*core/state.cachingDB).OpenTrie" ];
53 | 13111 [ label="(*eth.Ethereum).Stop" ];
54 | 13117 [ label="(*core.BlockChain).Stop" ];
55 | 16891 [ label="(*core.Genesis).Commit" ];
56 | 16895 [ label="core.flushAlloc" ];
57 | 20196 [ label="trie.newTrieReader" ];
58 | 20404 [ label="core.SetupGenesisBlockWithOverride" ];
59 | 41202 [ label="core/state.New" ];
60 | 41203 [ label="(*core/state.StateDB).Commit" ];
61 | 57931 [ label="(*node.Node).stopServices" ];
62 | 60738 [ label="(*trie.StateTrie).Commit" ];
63 | 68785 [ label="trie.NewVerkleTrie" ];
64 | 8266 [ label="(*node.Node).Close" ];
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/example/graph_out/complete_callgraph.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 | Flow
11 |
12 |
13 | cluster_0
14 |
15 |
16 | cluster_1
17 |
18 |
19 |
20 | 8266
21 |
22 | (*node.Node).Close
23 |
24 |
25 |
26 | 57931
27 |
28 | (*node.Node).stopServices
29 |
30 |
31 |
32 | 8266->57931
33 |
34 |
35 | (*Node).stopServices(n, t12)
36 |
37 |
38 |
39 | 13111
40 |
41 | (*eth.Ethereum).Stop
42 |
43 |
44 |
45 | 57931->13111
46 |
47 |
48 | invoke t9.Stop()
49 |
50 |
51 |
52 | 13117
53 |
54 | (*core.BlockChain).Stop
55 |
56 |
57 |
58 | 46336
59 |
60 | (*triedb.Database).Size
61 |
62 |
63 |
64 | 13117->46336
65 |
66 |
67 | (*triedb.Database).Size(t51)
68 |
69 |
70 |
71 | 32899
72 |
73 | (*triedb.Database).Scheme
74 |
75 |
76 |
77 | 13117->32899
78 |
79 |
80 | (*triedb.Database).Scheme(t15)
81 |
82 |
83 |
84 | 58461
85 |
86 | (*triedb.Database).Journal
87 |
88 |
89 |
90 | 13117->58461
91 |
92 |
93 | (*triedb.Database).Journal(t29, t32)
94 |
95 |
96 |
97 | 40713
98 |
99 | (*triedb.Database).Dereference
100 |
101 |
102 |
103 | 13117->40713
104 |
105 |
106 | (*triedb.Database).Dereference(t51, t117)
107 |
108 |
109 |
110 | 41202
111 |
112 | core/state.New
113 |
114 |
115 |
116 | 12100
117 |
118 | (*core/state.cachingDB).OpenTrie
119 |
120 |
121 |
122 | 41202->12100
123 |
124 |
125 | invoke db.OpenTrie(root)
126 |
127 |
128 |
129 | 68785
130 |
131 | trie.NewVerkleTrie
132 |
133 |
134 |
135 | 12100->68785
136 |
137 |
138 | trie.NewVerkleTrie(root, t6, t5)
139 |
140 |
141 |
142 | 33296
143 |
144 | (*triedb.Database).IsVerkle
145 |
146 |
147 |
148 | 12100->33296
149 |
150 |
151 | (*triedb.Database).IsVerkle(t1)
152 |
153 |
154 |
155 | 41203
156 |
157 | (*core/state.StateDB).Commit
158 |
159 |
160 |
161 | 60738
162 |
163 | (*trie.StateTrie).Commit
164 |
165 |
166 |
167 | 41203->60738
168 |
169 |
170 | invoke t62.Commit(true:bool)
171 |
172 |
173 |
174 | 49385
175 |
176 | (*triedb.Database).Update
177 |
178 |
179 |
180 | 41203->49385
181 |
182 |
183 | (*triedb.Database).Update(t200, t185, t190, block, t11, t197)
184 |
185 |
186 |
187 | 67906
188 |
189 | (*triedb.Database).InsertPreimage
190 |
191 |
192 |
193 | 60738->67906
194 |
195 |
196 | invoke t19.InsertPreimage(t3)
197 |
198 |
199 |
200 | 20196
201 |
202 | trie.newTrieReader
203 |
204 |
205 |
206 | 68785->20196
207 |
208 |
209 | newTrieReader(root, [32]byte{}:common.Hash, db)
210 |
211 |
212 |
213 | 11597
214 |
215 | cmd/geth.initGenesis
216 |
217 |
218 |
219 | 11597->8266
220 |
221 |
222 | defer (*node.Node).Close(t30)
223 |
224 |
225 |
226 | 20404
227 |
228 | core.SetupGenesisBlockWithOverride
229 |
230 |
231 |
232 | 11597->20404
233 |
234 |
235 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)
236 |
237 |
238 |
239 | 8282
240 |
241 | (*triedb.Database).Close
242 |
243 |
244 |
245 | 11597->8282
246 |
247 |
248 | defer (*triedb.Database).Close(t81)
249 |
250 |
251 |
252 | 16891
253 |
254 | (*core.Genesis).Commit
255 |
256 |
257 |
258 | 20404->16891
259 |
260 |
261 | (*Genesis).Commit(t20, db, triedb)
262 |
263 |
264 |
265 | 36719
266 |
267 | (*triedb.Database).Initialized
268 |
269 |
270 |
271 | 20404->36719
272 |
273 |
274 | (*triedb.Database).Initialized(triedb, t37)
275 |
276 |
277 |
278 | 13111->13117
279 |
280 |
281 | (*core.BlockChain).Stop(t19)
282 |
283 |
284 |
285 | 16895
286 |
287 | core.flushAlloc
288 |
289 |
290 |
291 | 16891->16895
292 |
293 |
294 | flushAlloc(t16, db, triedb, t17)
295 |
296 |
297 |
298 | 16895->41202
299 |
300 |
301 | core/state.New(t0, t1, nil:*core/state/snapshot.Tree)
302 |
303 |
304 |
305 | 16895->41203
306 |
307 |
308 | (*core/state.StateDB).Commit(t3, 0:uint64, false:bool)
309 |
310 |
311 |
312 | 46333
313 |
314 | (*triedb.Database).Commit
315 |
316 |
317 |
318 | 16895->46333
319 |
320 |
321 | (*triedb.Database).Commit(triedb, t17, true:bool)
322 |
323 |
324 |
325 | 43858
326 |
327 | (*triedb.Database).Reader
328 |
329 |
330 |
331 | 20196->43858
332 |
333 |
334 | invoke db.Reader(stateRoot)
335 |
336 |
337 |
338 |
--------------------------------------------------------------------------------
/example/graph_out/simple_callgraph.dot:
--------------------------------------------------------------------------------
1 | digraph Flow {
2 | newrank=true;
3 | rankdir=LR;
4 | 11597->40713[ label="defer (*node.Node).Close(t30)->...->(*triedb.Database).Dereference(t51, t117)" ];
5 | 11597->8282[ label="defer (*triedb.Database).Close(t81)" ];
6 | 11597->46333[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Commit(triedb, t17, true:bool)" ];
7 | 11597->46336[ label="defer (*node.Node).Close(t30)->...->(*triedb.Database).Size(t51)" ];
8 | 11597->32899[ label="defer (*node.Node).Close(t30)->...->(*triedb.Database).Scheme(t15)" ];
9 | 11597->49385[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Update(t200, t185, t190, block, t11, t197)" ];
10 | 11597->58461[ label="defer (*node.Node).Close(t30)->...->(*triedb.Database).Journal(t29, t32)" ];
11 | 11597->33296[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).IsVerkle(t1)" ];
12 | 11597->67906[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->invoke t19.InsertPreimage(t3)" ];
13 | 11597->43858[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->invoke db.Reader(stateRoot)" ];
14 | 11597->36719[ label="core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Initialized(triedb, t37)" ];
15 | subgraph cluster_0 {
16 | rank=same;
17 | style=invis;
18 | 11597 [ color=red, label="cmd/geth.initGenesis" ];
19 |
20 | }
21 | ;
22 | subgraph cluster_1 {
23 | rank=same;
24 | style=invis;
25 | 32899 [ color=red, label="(*triedb.Database).Scheme" ];
26 | 33296 [ color=red, label="(*triedb.Database).IsVerkle" ];
27 | 36719 [ color=red, label="(*triedb.Database).Initialized" ];
28 | 40713 [ color=red, label="(*triedb.Database).Dereference" ];
29 | 43858 [ color=red, label="(*triedb.Database).Reader" ];
30 | 46333 [ color=red, label="(*triedb.Database).Commit" ];
31 | 46336 [ color=red, label="(*triedb.Database).Size" ];
32 | 49385 [ color=red, label="(*triedb.Database).Update" ];
33 | 58461 [ color=red, label="(*triedb.Database).Journal" ];
34 | 67906 [ color=red, label="(*triedb.Database).InsertPreimage" ];
35 | 8282 [ color=red, label="(*triedb.Database).Close" ];
36 |
37 | }
38 | ;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/example/graph_out/simple_callgraph.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 | Flow
11 |
12 |
13 | cluster_0
14 |
15 |
16 | cluster_1
17 |
18 |
19 |
20 | 11597
21 |
22 | cmd/geth.initGenesis
23 |
24 |
25 |
26 | 40713
27 |
28 | (*triedb.Database).Dereference
29 |
30 |
31 |
32 | 11597->40713
33 |
34 |
35 | defer (*node.Node).Close(t30)->...->(*triedb.Database).Dereference(t51, t117)
36 |
37 |
38 |
39 | 8282
40 |
41 | (*triedb.Database).Close
42 |
43 |
44 |
45 | 11597->8282
46 |
47 |
48 | defer (*triedb.Database).Close(t81)
49 |
50 |
51 |
52 | 46333
53 |
54 | (*triedb.Database).Commit
55 |
56 |
57 |
58 | 11597->46333
59 |
60 |
61 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Commit(triedb, t17, true:bool)
62 |
63 |
64 |
65 | 46336
66 |
67 | (*triedb.Database).Size
68 |
69 |
70 |
71 | 11597->46336
72 |
73 |
74 | defer (*node.Node).Close(t30)->...->(*triedb.Database).Size(t51)
75 |
76 |
77 |
78 | 32899
79 |
80 | (*triedb.Database).Scheme
81 |
82 |
83 |
84 | 11597->32899
85 |
86 |
87 | defer (*node.Node).Close(t30)->...->(*triedb.Database).Scheme(t15)
88 |
89 |
90 |
91 | 49385
92 |
93 | (*triedb.Database).Update
94 |
95 |
96 |
97 | 11597->49385
98 |
99 |
100 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Update(t200, t185, t190, block, t11, t197)
101 |
102 |
103 |
104 | 58461
105 |
106 | (*triedb.Database).Journal
107 |
108 |
109 |
110 | 11597->58461
111 |
112 |
113 | defer (*node.Node).Close(t30)->...->(*triedb.Database).Journal(t29, t32)
114 |
115 |
116 |
117 | 33296
118 |
119 | (*triedb.Database).IsVerkle
120 |
121 |
122 |
123 | 11597->33296
124 |
125 |
126 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).IsVerkle(t1)
127 |
128 |
129 |
130 | 67906
131 |
132 | (*triedb.Database).InsertPreimage
133 |
134 |
135 |
136 | 11597->67906
137 |
138 |
139 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->invoke t19.InsertPreimage(t3)
140 |
141 |
142 |
143 | 43858
144 |
145 | (*triedb.Database).Reader
146 |
147 |
148 |
149 | 11597->43858
150 |
151 |
152 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->invoke db.Reader(stateRoot)
153 |
154 |
155 |
156 | 36719
157 |
158 | (*triedb.Database).Initialized
159 |
160 |
161 |
162 | 11597->36719
163 |
164 |
165 | core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)->...->(*triedb.Database).Initialized(triedb, t37)
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/example/init_genesis_analysis.toml:
--------------------------------------------------------------------------------
1 | # file:init_genesis_analysis.toml
2 |
3 | # package_prefix is for trimming the function name in graph for human readability
4 | package_prefix = "github.com/ethereum/go-ethereum/"
5 |
6 |
7 | # layer is a set of matched functions used to generate flow graph. layers must be defined in order.
8 | [[layer]]
9 | name = "CMD Layer"
10 | [[layer.entities]]
11 | # match rule for the function name
12 | # there are match type: "contain", "prefix", "suffix", "equal", "regexp", default to use "equal" if not set type
13 | # can set exclude = true to exclude the matched functions
14 | name = { rules = [{ type = "suffix", content = "initGenesis" }] }
15 |
16 |
17 | [[layer]]
18 | name = "DB Layer"
19 | [[layer.entities]]
20 | name = { rules = [{ type = "contain", content = "triedb.Database" }] }
--------------------------------------------------------------------------------
/example/path_out/0-1.csv:
--------------------------------------------------------------------------------
1 | "Caller","Callee","ExamplePath"
2 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Close","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|defer (*github.com/ethereum/go-ethereum/triedb.Database).Close(t81)|->(*github.com/ethereum/go-ethereum/triedb.Database).Close="
3 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Scheme","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|defer (*github.com/ethereum/go-ethereum/node.Node).Close(t30)|->(*github.com/ethereum/go-ethereum/node.Node).Close=(*github.com/ethereum/go-ethereum/node.Node).Close-|(*Node).stopServices(n, t12)|->(*github.com/ethereum/go-ethereum/node.Node).stopServices=(*github.com/ethereum/go-ethereum/node.Node).stopServices-|invoke t9.Stop()|->(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop=(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop-|(*github.com/ethereum/go-ethereum/core.BlockChain).Stop(t19)|->(*github.com/ethereum/go-ethereum/core.BlockChain).Stop=(*github.com/ethereum/go-ethereum/core.BlockChain).Stop-|(*github.com/ethereum/go-ethereum/triedb.Database).Scheme(t15)|->(*github.com/ethereum/go-ethereum/triedb.Database).Scheme="
4 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Dereference","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|defer (*github.com/ethereum/go-ethereum/node.Node).Close(t30)|->(*github.com/ethereum/go-ethereum/node.Node).Close=(*github.com/ethereum/go-ethereum/node.Node).Close-|(*Node).stopServices(n, t12)|->(*github.com/ethereum/go-ethereum/node.Node).stopServices=(*github.com/ethereum/go-ethereum/node.Node).stopServices-|invoke t9.Stop()|->(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop=(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop-|(*github.com/ethereum/go-ethereum/core.BlockChain).Stop(t19)|->(*github.com/ethereum/go-ethereum/core.BlockChain).Stop=(*github.com/ethereum/go-ethereum/core.BlockChain).Stop-|(*github.com/ethereum/go-ethereum/triedb.Database).Dereference(t51, t117)|->(*github.com/ethereum/go-ethereum/triedb.Database).Dereference="
5 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Update","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*Genesis).Commit(t20, db, triedb)|->(*github.com/ethereum/go-ethereum/core.Genesis).Commit=(*github.com/ethereum/go-ethereum/core.Genesis).Commit-|flushAlloc(t16, db, triedb, t17)|->github.com/ethereum/go-ethereum/core.flushAlloc=github.com/ethereum/go-ethereum/core.flushAlloc-|(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit(t3, 0:uint64, false:bool)|->(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit=(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit-|(*github.com/ethereum/go-ethereum/triedb.Database).Update(t200, t185, t190, block, t11, t197)|->(*github.com/ethereum/go-ethereum/triedb.Database).Update="
6 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Initialized","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*github.com/ethereum/go-ethereum/triedb.Database).Initialized(triedb, t37)|->(*github.com/ethereum/go-ethereum/triedb.Database).Initialized="
7 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Commit","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*Genesis).Commit(t20, db, triedb)|->(*github.com/ethereum/go-ethereum/core.Genesis).Commit=(*github.com/ethereum/go-ethereum/core.Genesis).Commit-|flushAlloc(t16, db, triedb, t17)|->github.com/ethereum/go-ethereum/core.flushAlloc=github.com/ethereum/go-ethereum/core.flushAlloc-|(*github.com/ethereum/go-ethereum/triedb.Database).Commit(triedb, t17, true:bool)|->(*github.com/ethereum/go-ethereum/triedb.Database).Commit="
8 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Journal","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|defer (*github.com/ethereum/go-ethereum/node.Node).Close(t30)|->(*github.com/ethereum/go-ethereum/node.Node).Close=(*github.com/ethereum/go-ethereum/node.Node).Close-|(*Node).stopServices(n, t12)|->(*github.com/ethereum/go-ethereum/node.Node).stopServices=(*github.com/ethereum/go-ethereum/node.Node).stopServices-|invoke t9.Stop()|->(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop=(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop-|(*github.com/ethereum/go-ethereum/core.BlockChain).Stop(t19)|->(*github.com/ethereum/go-ethereum/core.BlockChain).Stop=(*github.com/ethereum/go-ethereum/core.BlockChain).Stop-|(*github.com/ethereum/go-ethereum/triedb.Database).Journal(t29, t32)|->(*github.com/ethereum/go-ethereum/triedb.Database).Journal="
9 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Size","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|defer (*github.com/ethereum/go-ethereum/node.Node).Close(t30)|->(*github.com/ethereum/go-ethereum/node.Node).Close=(*github.com/ethereum/go-ethereum/node.Node).Close-|(*Node).stopServices(n, t12)|->(*github.com/ethereum/go-ethereum/node.Node).stopServices=(*github.com/ethereum/go-ethereum/node.Node).stopServices-|invoke t9.Stop()|->(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop=(*github.com/ethereum/go-ethereum/eth.Ethereum).Stop-|(*github.com/ethereum/go-ethereum/core.BlockChain).Stop(t19)|->(*github.com/ethereum/go-ethereum/core.BlockChain).Stop=(*github.com/ethereum/go-ethereum/core.BlockChain).Stop-|(*github.com/ethereum/go-ethereum/triedb.Database).Size(t51)|->(*github.com/ethereum/go-ethereum/triedb.Database).Size="
10 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).IsVerkle","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*Genesis).Commit(t20, db, triedb)|->(*github.com/ethereum/go-ethereum/core.Genesis).Commit=(*github.com/ethereum/go-ethereum/core.Genesis).Commit-|flushAlloc(t16, db, triedb, t17)|->github.com/ethereum/go-ethereum/core.flushAlloc=github.com/ethereum/go-ethereum/core.flushAlloc-|github.com/ethereum/go-ethereum/core/state.New(t0, t1, nil:*github.com/ethereum/go-ethereum/core/state/snapshot.Tree)|->github.com/ethereum/go-ethereum/core/state.New=github.com/ethereum/go-ethereum/core/state.New-|invoke db.OpenTrie(root)|->(*github.com/ethereum/go-ethereum/core/state.cachingDB).OpenTrie=(*github.com/ethereum/go-ethereum/core/state.cachingDB).OpenTrie-|(*github.com/ethereum/go-ethereum/triedb.Database).IsVerkle(t1)|->(*github.com/ethereum/go-ethereum/triedb.Database).IsVerkle="
11 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).InsertPreimage","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*Genesis).Commit(t20, db, triedb)|->(*github.com/ethereum/go-ethereum/core.Genesis).Commit=(*github.com/ethereum/go-ethereum/core.Genesis).Commit-|flushAlloc(t16, db, triedb, t17)|->github.com/ethereum/go-ethereum/core.flushAlloc=github.com/ethereum/go-ethereum/core.flushAlloc-|(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit(t3, 0:uint64, false:bool)|->(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit=(*github.com/ethereum/go-ethereum/core/state.StateDB).Commit-|invoke t62.Commit(true:bool)|->(*github.com/ethereum/go-ethereum/trie.StateTrie).Commit=(*github.com/ethereum/go-ethereum/trie.StateTrie).Commit-|invoke t19.InsertPreimage(t3)|->(*github.com/ethereum/go-ethereum/triedb.Database).InsertPreimage="
12 | "github.com/ethereum/go-ethereum/cmd/geth.initGenesis","(*github.com/ethereum/go-ethereum/triedb.Database).Reader","github.com/ethereum/go-ethereum/cmd/geth.initGenesis-|github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride(t68, t81, t18, t32)|->github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride=github.com/ethereum/go-ethereum/core.SetupGenesisBlockWithOverride-|(*Genesis).Commit(t20, db, triedb)|->(*github.com/ethereum/go-ethereum/core.Genesis).Commit=(*github.com/ethereum/go-ethereum/core.Genesis).Commit-|flushAlloc(t16, db, triedb, t17)|->github.com/ethereum/go-ethereum/core.flushAlloc=github.com/ethereum/go-ethereum/core.flushAlloc-|github.com/ethereum/go-ethereum/core/state.New(t0, t1, nil:*github.com/ethereum/go-ethereum/core/state/snapshot.Tree)|->github.com/ethereum/go-ethereum/core/state.New=github.com/ethereum/go-ethereum/core/state.New-|invoke db.OpenTrie(root)|->(*github.com/ethereum/go-ethereum/core/state.cachingDB).OpenTrie=(*github.com/ethereum/go-ethereum/core/state.cachingDB).OpenTrie-|github.com/ethereum/go-ethereum/trie.NewVerkleTrie(root, t6, t5)|->github.com/ethereum/go-ethereum/trie.NewVerkleTrie=github.com/ethereum/go-ethereum/trie.NewVerkleTrie-|newTrieReader(root, [32]byte{}:github.com/ethereum/go-ethereum/common.Hash, db)|->github.com/ethereum/go-ethereum/trie.newTrieReader=github.com/ethereum/go-ethereum/trie.newTrieReader-|invoke db.Reader(stateRoot)|->(*github.com/ethereum/go-ethereum/triedb.Database).Reader="
13 |
--------------------------------------------------------------------------------
/flow/algo.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/eapache/queue/v2"
7 | "github.com/laindream/go-callflow-vis/cache"
8 | "github.com/laindream/go-callflow-vis/ir"
9 | "github.com/laindream/go-callflow-vis/log"
10 | )
11 |
12 | func (f *Flow) resetCallgraphIR() error {
13 | return cache.GetFury().Unmarshal(f.furyBuffer, &f.callgraph)
14 | }
15 |
16 | func (f *Flow) GetMinNodeSet() (map[*ir.Node]bool, error) {
17 | minNodeSet := make(map[*ir.Node]bool)
18 | if f == nil {
19 | return nil, errors.New("flow is nil")
20 | }
21 | if len(f.Layers) < 2 {
22 | return nil, errors.New("number of layers must be at least 2")
23 | }
24 | for i, _ := range f.Layers {
25 | if i == len(f.Layers)-1 {
26 | continue
27 | }
28 | startNodes := f.Layers[i].GetOutAllNodeSet(f.callgraph)
29 | endNodes := f.Layers[i+1].GetInAllNodeSet(f.callgraph)
30 | nodeSet, _ := f.findReachableNodesIR(startNodes, endNodes, nil)
31 | for k, _ := range nodeSet {
32 | minNodeSet[k] = true
33 | }
34 | }
35 | f.resetLayer()
36 | return minNodeSet, nil
37 | }
38 |
39 | func (f *Flow) findAllBipartite(isCallgraphJustReset bool) error {
40 | if f == nil {
41 | return errors.New("flow is nil")
42 | }
43 | if len(f.Layers) < 2 {
44 | return errors.New("number of layers must be at least 2")
45 | }
46 | issueFuncs := make(map[string]bool)
47 | for i, _ := range f.Layers {
48 | if i == len(f.Layers)-1 {
49 | continue
50 | }
51 | startNodes := f.Layers[i].GetOutAllNodeSet(f.callgraph)
52 | endNodes := f.Layers[i+1].GetInAllNodeSet(f.callgraph)
53 | var examplePath map[*ir.Node]map[*ir.Node][]*ir.Edge
54 | _, examplePath = f.findReachableNodesIR(startNodes, endNodes, nil)
55 | f.Layers[i].ExamplePath = examplePath
56 | for _, v := range examplePath {
57 | for _, v2 := range v {
58 | isCheckPass, issueFunc := f.checkCallEdgeChain(v2)
59 | if !isCheckPass && issueFunc != nil {
60 | issueFuncs[issueFunc.Addr] = true
61 | }
62 | }
63 | }
64 | starts, ends := GetStartAndEndFromExamplePath(examplePath)
65 | for j, _ := range f.Layers[i].Entities {
66 | f.Layers[i].Entities[j].TrimOutNodeSet(starts, f.callgraph)
67 | }
68 | for j, _ := range f.Layers[i+1].Entities {
69 | f.Layers[i+1].Entities[j].TrimInNodeSet(ends, f.callgraph)
70 | }
71 | if i == 0 {
72 | for j, _ := range f.Layers[i].Entities {
73 | f.Layers[i].Entities[j].UpdateInSiteNodeSetWithNodeSet(f.callgraph)
74 | }
75 | }
76 | if i == len(f.Layers)-2 {
77 | for j, _ := range f.Layers[i+1].Entities {
78 | f.Layers[i+1].Entities[j].UpdateOutSiteNodeSetWithNodeSet(f.callgraph)
79 | }
80 | }
81 | }
82 | if len(issueFuncs) > 0 {
83 | addCount := 0
84 | for k, _ := range issueFuncs {
85 | if f.allIssueFuncs == nil {
86 | f.allIssueFuncs = make(map[string]bool)
87 | }
88 | if _, ok := f.allIssueFuncs[k]; !ok {
89 | f.allIssueFuncs[k] = true
90 | addCount++
91 | }
92 | }
93 | log.GetLogger().Debugf("Find Incremental Issue Funcs:%d, Total Issue Funcs:%d, Regenerate Flow",
94 | addCount, len(f.allIssueFuncs))
95 | f.skipNodesIR(f.allIssueFuncs)
96 | f.resetLayer()
97 | return f.findAllBipartite(false)
98 | }
99 | if !isCallgraphJustReset && len(issueFuncs) == 0 {
100 | if f.fastMode {
101 | log.GetLogger().Debugf("No Incremental Issue Funcs, Skip No Issue Check In Fast Mode")
102 | } else {
103 | log.GetLogger().Debugf("No Incremental Issue Funcs, Try No Issue Check")
104 | if err := f.resetCallgraphIR(); err != nil {
105 | return err
106 | }
107 | f.skipNodesIR(f.allIssueFuncs)
108 | f.resetLayer()
109 | return f.findAllBipartite(true)
110 | }
111 | }
112 | for i := len(f.Layers) - 2; i >= 0; i-- {
113 | for j, _ := range f.Layers[i].Entities {
114 | originalEntityIn := make(map[*ir.Node]bool)
115 | for k, _ := range f.Layers[i].Entities[j].GetInAllNodeSetOnlyRead(f.callgraph) {
116 | originalEntityIn[k] = true
117 | }
118 | originalEntityNode := make(map[*ir.Node]bool)
119 | for k, _ := range f.Layers[i].Entities[j].GetNodeSet(f.callgraph) {
120 | originalEntityNode[k] = true
121 | }
122 | f.Layers[i].Entities[j].UpdateInSiteNodeSetWithNodeSet(f.callgraph)
123 | f.Layers[i].Entities[j].TrimInNodeSet(originalEntityIn, f.callgraph)
124 | f.Layers[i].Entities[j].TrimNodeSet(originalEntityNode, f.callgraph)
125 | }
126 | if i == 0 {
127 | break
128 | }
129 | filterOutSet := make(map[*ir.Node]bool)
130 | for k, _ := range f.Layers[i-1].ExamplePath {
131 | for k2, _ := range f.Layers[i-1].ExamplePath[k] {
132 | if f.Layers[i].GetInAllNodeSetOnlyRead(f.callgraph) != nil &&
133 | f.Layers[i].GetInAllNodeSetOnlyRead(f.callgraph)[k2] {
134 | continue
135 | }
136 | delete(f.Layers[i-1].ExamplePath[k], k2)
137 | }
138 | if len(f.Layers[i-1].ExamplePath[k]) == 0 {
139 | delete(f.Layers[i-1].ExamplePath, k)
140 | }
141 | }
142 | for k, _ := range f.Layers[i-1].ExamplePath {
143 | filterOutSet[k] = true
144 | }
145 | for j, _ := range f.Layers[i-1].Entities {
146 | f.Layers[i-1].Entities[j].TrimOutNodeSet(filterOutSet, f.callgraph)
147 | }
148 | }
149 | return nil
150 | }
151 |
152 | func (f *Flow) skipNodesIR(issueFuncs map[string]bool) {
153 | hasDoSkip := true
154 | for hasDoSkip {
155 | _, hasDoSkip = f.skipNodeIR(issueFuncs)
156 | }
157 | }
158 |
159 | func (f *Flow) skipNodeIR(issueFuncs map[string]bool) (hasFound, hasDoSkip bool) {
160 | isFuncTarget := func(name string) bool {
161 | for k, _ := range issueFuncs {
162 | if k == name {
163 | return true
164 | }
165 | }
166 | return false
167 | }
168 | var nodeToSkip *ir.Node
169 | for k, v := range f.callgraph.Nodes {
170 | if v.Func != nil && isFuncTarget(k) {
171 | nodeToSkip = v
172 | break
173 | }
174 | }
175 | if nodeToSkip == nil {
176 | return false, false
177 | }
178 | hasFound = true
179 | cacheIn := make([]*ir.Edge, 0)
180 | cacheOut := make([]*ir.Edge, 0)
181 | for _, v := range nodeToSkip.In {
182 | cacheIn = append(cacheIn, v)
183 | }
184 | for _, v := range nodeToSkip.Out {
185 | cacheOut = append(cacheOut, v)
186 | }
187 | f.callgraph = f.callgraph.DeleteNode(nodeToSkip)
188 | for _, in := range cacheIn {
189 | for _, out := range cacheOut {
190 | if in.Caller == nil || out.Callee == nil ||
191 | in.Caller.Func == nil || out.Callee.Func == nil {
192 | continue
193 | }
194 | inCallerFuncAddr := in.Caller.Func.Addr
195 | outCalleeFuncAddr := out.Callee.Func.Addr
196 | doMerge := false
197 | if isFuncTarget(outCalleeFuncAddr) || isFuncTarget(inCallerFuncAddr) {
198 | doMerge = true
199 | }
200 | if out.Callee.Func.Parent != nil && out.Callee.Func.Parent.Name == in.Caller.Func.Name {
201 | doMerge = true
202 | }
203 | mergedSite := &ir.Site{
204 | Name: fmt.Sprintf("%s->Skip(%s)->%s", in.Site.Name, nodeToSkip.Func.Name, out.Site.Name),
205 | Addr: fmt.Sprintf("%s->Skip(%s)->%s", in.Site.Addr, nodeToSkip.Func.Addr, out.Site.Addr),
206 | }
207 | if doMerge {
208 | f.callgraph.AddEdge(inCallerFuncAddr, outCalleeFuncAddr, mergedSite)
209 | hasDoSkip = true
210 | }
211 | }
212 | }
213 | if !hasDoSkip && hasFound {
214 | return f.skipNodeIR(issueFuncs)
215 | }
216 | return hasFound, hasDoSkip
217 | }
218 |
219 | func (f *Flow) checkCallEdgeChain(path []*ir.Edge) (isCheckPass bool, issueFunc *ir.Func) {
220 | if len(path) == 0 {
221 | return false, nil
222 | }
223 | fChain := make([]*ir.Func, 0)
224 | for i, _ := range path {
225 | var lastFunc *ir.Func
226 | if len(fChain) > 0 {
227 | lastFunc = fChain[len(fChain)-1]
228 | }
229 | if path[i].Caller != nil &&
230 | path[i].Caller.Func != nil &&
231 | path[i].Caller.Func != lastFunc {
232 | fChain = append(fChain, path[i].Caller.Func)
233 | }
234 | if path[i].Callee != nil &&
235 | path[i].Callee.Func != nil &&
236 | path[i].Callee.Func != lastFunc {
237 | fChain = append(fChain, path[i].Callee.Func)
238 | }
239 | }
240 | fChainMap := make(map[string]bool)
241 | for i, _ := range fChain {
242 | if fChain[i].Parent != nil {
243 | if _, ok := fChainMap[fChain[i].Parent.Addr]; !ok {
244 | if i == 0 {
245 | fChainMap[fChain[i].Parent.Addr] = true
246 | } else {
247 | return false, fChain[i-1]
248 | }
249 | }
250 | }
251 | fChainMap[fChain[i].Addr] = true
252 | }
253 | return true, nil
254 | }
255 |
256 | func (f *Flow) findReachableNodesIR(starts map[*ir.Node]bool, ends map[*ir.Node]bool,
257 | innerContainSet map[*ir.Node]bool) (containSet map[*ir.Node]bool, examplePath map[*ir.Node]map[*ir.Node][]*ir.Edge) {
258 | containSet = f.searchReachableNodesFromEnds(ends, innerContainSet)
259 | containSet = f.searchReachableNodesFromStarts(starts, containSet)
260 | examplePath = make(map[*ir.Node]map[*ir.Node][]*ir.Edge)
261 | for k, _ := range starts {
262 | ep := f.findExamplePath(k, ends, containSet)
263 | if len(ep) > 0 {
264 | examplePath[k] = ep
265 | }
266 | }
267 | return containSet, examplePath
268 | }
269 |
270 | func (f *Flow) findExamplePath(src *ir.Node, dsts map[*ir.Node]bool,
271 | containSet map[*ir.Node]bool) (examplePath map[*ir.Node][]*ir.Edge) {
272 | examplePath = make(map[*ir.Node][]*ir.Edge)
273 | q := queue.New[*ir.Node]()
274 | visited := make(map[*ir.Node]bool)
275 | visited[src] = true
276 | q.Add(src)
277 | for q.Length() > 0 {
278 | node := q.Remove()
279 | if dsts[node] && len(node.GetPath()) > 0 {
280 | examplePath[node] = node.GetPath()
281 | continue
282 | }
283 | for _, e := range node.Out {
284 | w := e.Callee
285 | if len(containSet) == 0 || (len(containSet) != 0 && containSet[w]) {
286 | if _, ok := visited[w]; ok {
287 | continue
288 | }
289 | newPath := make([]*ir.Edge, 0)
290 | newPath = append(newPath, node.GetPath()...)
291 | w.SetPath(append(newPath, e))
292 | visited[w] = true
293 | q.Add(w)
294 | }
295 | }
296 | }
297 | for _, v := range f.callgraph.Nodes {
298 | v.ClearPath()
299 | }
300 | return examplePath
301 | }
302 |
303 | func (f *Flow) searchReachableNodesFromEnds(ends map[*ir.Node]bool, containSet map[*ir.Node]bool) map[*ir.Node]bool {
304 | var (
305 | visited = make(map[*ir.Node]bool)
306 | q = queue.New[*ir.Node]()
307 | )
308 | layerCount := 0
309 | layerList := make([]map[*ir.Node]bool, 0)
310 | layerList = append(layerList, make(map[*ir.Node]bool))
311 | for nodes, _ := range ends {
312 | q.Add(nodes)
313 | layerCount++
314 | layerList[0][nodes] = true
315 | visited[nodes] = true
316 | }
317 | for q.Length() > 0 {
318 | innerLayerCount := 0
319 | stepMap := make(map[*ir.Node]bool)
320 | for i := 0; i < layerCount; i++ {
321 | v := q.Remove()
322 | for _, edge := range v.In {
323 | w := edge.Caller
324 | if len(containSet) == 0 || (len(containSet) != 0 && containSet[w]) {
325 | if _, ok := visited[w]; ok {
326 | continue
327 | }
328 | visited[w] = true
329 | q.Add(w)
330 | innerLayerCount++
331 | stepMap[w] = true
332 | }
333 | }
334 | }
335 | layerCount = innerLayerCount
336 | if len(stepMap) > 0 {
337 | layerList = append(layerList, stepMap)
338 | }
339 | }
340 | return visited
341 | }
342 |
343 | func (f *Flow) searchReachableNodesFromStarts(starts map[*ir.Node]bool, containSet map[*ir.Node]bool) map[*ir.Node]bool {
344 | var (
345 | visited = make(map[*ir.Node]bool)
346 | q = queue.New[*ir.Node]()
347 | )
348 | layerCount := 0
349 | layerList := make([]map[*ir.Node]bool, 0)
350 | layerList = append(layerList, make(map[*ir.Node]bool))
351 | for nodes, _ := range starts {
352 | q.Add(nodes)
353 | layerCount++
354 | layerList[0][nodes] = true
355 | visited[nodes] = true
356 | }
357 | for q.Length() > 0 {
358 | innerLayerCount := 0
359 | stepMap := make(map[*ir.Node]bool)
360 | for i := 0; i < layerCount; i++ {
361 | v := q.Remove()
362 | for _, edge := range v.Out {
363 | w := edge.Callee
364 | if len(containSet) == 0 || (len(containSet) != 0 && containSet[w]) {
365 | if _, ok := visited[w]; ok {
366 | continue
367 | }
368 | visited[w] = true
369 | q.Add(w)
370 | innerLayerCount++
371 | stepMap[w] = true
372 | }
373 | }
374 | }
375 | layerCount = innerLayerCount
376 | if len(stepMap) > 0 {
377 | layerList = append(layerList, stepMap)
378 | }
379 | }
380 | return visited
381 | }
382 |
--------------------------------------------------------------------------------
/flow/flow.go:
--------------------------------------------------------------------------------
1 | package flow
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/awalterschulze/gographviz"
7 | "github.com/laindream/go-callflow-vis/cache"
8 | "github.com/laindream/go-callflow-vis/config"
9 | "github.com/laindream/go-callflow-vis/ir"
10 | "github.com/laindream/go-callflow-vis/log"
11 | "github.com/laindream/go-callflow-vis/mode"
12 | "github.com/laindream/go-callflow-vis/render"
13 | "github.com/laindream/go-callflow-vis/util"
14 | "strconv"
15 | )
16 |
17 | func NewFlow(config *config.Config, callGraph *ir.Callgraph, fastMode bool) (*Flow, error) {
18 | if config == nil {
19 | return nil, fmt.Errorf("config is nil")
20 | }
21 | if len(config.Layers) == 0 {
22 | return nil, fmt.Errorf("no layers in config")
23 | }
24 | if callGraph == nil {
25 | return nil, fmt.Errorf("callgraph is nil")
26 | }
27 | layers := make([]*Layer, 0)
28 | for _, l := range config.Layers {
29 | layer := &Layer{
30 | Name: l.Name,
31 | Entities: make([]*Entity, 0),
32 | }
33 | for _, e := range l.Entities {
34 | entity := &Entity{
35 | Entity: e,
36 | }
37 | layer.Entities = append(layer.Entities, entity)
38 | }
39 | layers = append(layers, layer)
40 | }
41 | f := &Flow{
42 | pkgPrefix: config.PackagePrefix,
43 | callgraph: callGraph,
44 | Layers: layers,
45 | fastMode: fastMode,
46 | }
47 | f.CheckFlowEntities()
48 | log.GetLogger().Debugf("NewFlow: Generate Min Graph...")
49 | err := f.UpdateMinGraph()
50 | if err != nil {
51 | return nil, err
52 | }
53 | log.GetLogger().Debugf("NewFlow: Generate Min Graph Nodes:%d", len(f.callgraph.Nodes))
54 | err = f.initFuryBuffer()
55 | if err != nil {
56 | return nil, err
57 | }
58 | return f, nil
59 | }
60 |
61 | type Flow struct {
62 | pkgPrefix string
63 | furyBuffer []byte
64 | callgraph *ir.Callgraph
65 | allIssueFuncs map[string]bool
66 | Layers []*Layer
67 | isCompleteGenerate bool
68 | fastMode bool
69 | }
70 |
71 | func (f *Flow) CheckFlowEntities() {
72 | for _, l := range f.Layers {
73 | for _, e := range l.Entities {
74 | if len(e.GetNodeSet(f.callgraph)) == 0 {
75 | log.GetLogger().Warnf("No Node Matched for Entity:%s", e.String())
76 | }
77 | }
78 | }
79 | f.resetLayer()
80 | }
81 |
82 | func (f *Flow) initFuryBuffer() error {
83 | bytes, err := cache.GetFury().Marshal(f.callgraph)
84 | if err != nil {
85 | return err
86 | }
87 | f.furyBuffer = bytes
88 | return nil
89 | }
90 |
91 | func (f *Flow) reset() {
92 | f.allIssueFuncs = nil
93 | f.resetLayer()
94 | }
95 |
96 | func (f *Flow) resetLayer() {
97 | for _, l := range f.Layers {
98 | l.ResetLayer()
99 | }
100 | }
101 |
102 | func (f *Flow) UpdateMinGraph() error {
103 | minSet, err := f.GetMinNodeSet()
104 | if err != nil {
105 | return err
106 | }
107 | funcNameSet := make(map[string]bool)
108 | for n, _ := range minSet {
109 | if n.Func == nil {
110 | continue
111 | }
112 | funcNameSet[n.Func.Name] = true
113 | }
114 | f.callgraph = ir.GetFilteredCallgraph(f.callgraph, func(funcName string) bool {
115 | return funcNameSet[funcName]
116 | })
117 | return nil
118 | }
119 |
120 | func (f *Flow) Generate() error {
121 | log.GetLogger().Debugf("Flow.Generate: Start...")
122 | if f.isCompleteGenerate {
123 | return nil
124 | }
125 | f.PrintOriginalFlow()
126 | err := f.findAllBipartite(false)
127 | if err != nil {
128 | f.reset()
129 | return err
130 | }
131 | log.GetLogger().Debugf("Flow.Generate: Done")
132 | f.PrintFlow()
133 | f.isCompleteGenerate = true
134 | return nil
135 | }
136 |
137 | func (f *Flow) PrintOriginalFlow() {
138 | for i, _ := range f.Layers {
139 | inAllNode := len(f.Layers[i].GetInAllNodeSet(f.callgraph))
140 | outAllNode := len(f.Layers[i].GetOutAllNodeSet(f.callgraph))
141 | log.GetLogger().Debugf("Orignial Layers[%d] InAllNode:%d, OutAllNode:%d",
142 | i, inAllNode, outAllNode)
143 | }
144 | f.resetLayer()
145 | }
146 |
147 | func (f *Flow) PrintFlow() {
148 | log.GetLogger().Debugf("Flow issueFuncs:%d", len(f.allIssueFuncs))
149 | for i, _ := range f.Layers {
150 | inAllNode := len(f.Layers[i].GetInAllNodeSetOnlyRead(f.callgraph))
151 | outAllNode := len(f.Layers[i].GetOutAllNodeSetOnlyRead(f.callgraph))
152 | start, end := GetStartAndEndFromExamplePath(f.Layers[i].ExamplePath)
153 | log.GetLogger().Debugf("Layers[%d] InAllNode:%d, OutAllNode:%d, start:%d, end:%d",
154 | i, inAllNode, outAllNode, len(start), len(end))
155 | }
156 | }
157 |
158 | func (f *Flow) SavePaths(path string, separator string) error {
159 | if separator == "" {
160 | separator = ","
161 | }
162 | for i, _ := range f.Layers {
163 | if i == len(f.Layers)-1 {
164 | continue
165 | }
166 | t := fmt.Sprintf("%s%s%s%s%s", "\"Caller\"", separator, "\"Callee\"", separator, "\"ExamplePath\"\n")
167 | for from, _ := range f.Layers[i].ExamplePath {
168 | for to, p := range f.Layers[i].ExamplePath[from] {
169 | if len(p) == 0 {
170 | continue
171 | }
172 | path := ""
173 | for _, e := range p {
174 | path += fmt.Sprintf("%s", e.ReadableString())
175 | }
176 | t += fmt.Sprintf("%s%s%s%s%s", "\""+util.Escape(from.Func.Name)+"\"", separator, "\""+util.Escape(to.Func.Name)+"\"", separator, "\""+util.Escape(path)+"\"")
177 | t += "\n"
178 | }
179 | }
180 | if err := util.WriteToFile(t, fmt.Sprintf("%s/%d-%d.csv", path, i, i+1)); err != nil {
181 | return err
182 | }
183 | }
184 | return nil
185 | }
186 |
187 | func (f *Flow) GetRenderGraph() *render.Graph {
188 | nodes := make(map[string]*render.Node)
189 | edges := make(map[string]*render.Edge)
190 | for _, l := range f.Layers {
191 | for k, _ := range l.ExamplePath {
192 | for k2, _ := range l.ExamplePath[k] {
193 | for _, e := range l.ExamplePath[k][k2] {
194 | edges[e.String()] = e.ToRenderEdge(f.pkgPrefix)
195 | nodes[e.Caller.Func.Addr] = e.Caller.ToRenderNode(f.pkgPrefix)
196 | nodes[e.Callee.Func.Addr] = e.Callee.ToRenderNode(f.pkgPrefix)
197 | }
198 | }
199 | }
200 | for k, _ := range l.GetInToOutEdgeSet(f.callgraph) {
201 | for k2, _ := range l.GetInToOutEdgeSet(f.callgraph)[k] {
202 | for _, e := range l.GetInToOutEdgeSet(f.callgraph)[k][k2] {
203 | for _, e2 := range e {
204 | if e2.Callee == nil && e2.Caller == nil {
205 | continue
206 | }
207 | if e2.Callee == nil && e2.Caller != nil {
208 | nodes[e2.Caller.Func.Addr] = e2.Caller.ToRenderNode(f.pkgPrefix)
209 | continue
210 | }
211 | if e2.Callee != nil && e2.Caller == nil {
212 | nodes[e2.Callee.Func.Addr] = e2.Callee.ToRenderNode(f.pkgPrefix)
213 | continue
214 | }
215 | edges[e2.String()] = e2.ToRenderEdge(f.pkgPrefix)
216 | nodes[e2.Caller.Func.Addr] = e2.Caller.ToRenderNode(f.pkgPrefix)
217 | nodes[e2.Callee.Func.Addr] = e2.Callee.ToRenderNode(f.pkgPrefix)
218 | }
219 | }
220 | }
221 | }
222 | }
223 | setIndex := 0
224 | for _, l := range f.Layers {
225 | layerInSet := make(map[*ir.Node]bool)
226 | layerNodeSet := make(map[*ir.Node]bool)
227 | layerOutSet := make(map[*ir.Node]bool)
228 | for _, entity := range l.Entities {
229 | for n, _ := range entity.InNodeSet {
230 | layerInSet[n] = true
231 | }
232 | for n, _ := range entity.NodeSet {
233 | layerNodeSet[n] = true
234 | }
235 | for n, _ := range entity.OutNodeSet {
236 | layerOutSet[n] = true
237 | }
238 | }
239 | if len(layerInSet) > 0 {
240 | for n, _ := range layerInSet {
241 | nodes[n.Func.Addr] = n.ToRenderNode(f.pkgPrefix)
242 | nodes[n.Func.Addr].Set = setIndex
243 | }
244 | setIndex++
245 | }
246 | if len(layerNodeSet) > 0 {
247 | for n, _ := range layerNodeSet {
248 | nodes[n.Func.Addr] = n.ToRenderNode(f.pkgPrefix)
249 | nodes[n.Func.Addr].Set = setIndex
250 | }
251 | setIndex++
252 | }
253 | if len(layerOutSet) > 0 {
254 | for n, _ := range layerOutSet {
255 | nodes[n.Func.Addr] = n.ToRenderNode(f.pkgPrefix)
256 | nodes[n.Func.Addr].Set = setIndex
257 | }
258 | setIndex++
259 | }
260 | }
261 | nodeSet := make([]*render.Node, 0)
262 | for _, n := range nodes {
263 | nodeSet = append(nodeSet, n)
264 | }
265 | edgeSet := make([]*render.Edge, 0)
266 | for _, e := range edges {
267 | edgeSet = append(edgeSet, e)
268 | }
269 | return &render.Graph{
270 | NodeSet: nodeSet,
271 | EdgeSet: edgeSet,
272 | }
273 | }
274 |
275 | func (f *Flow) GetDot(isSimple bool) string {
276 | mainGraph := gographviz.NewGraph()
277 | graphName := "Flow"
278 | mainGraph.SetName(graphName)
279 | mainGraph.SetDir(true)
280 | mainGraph.AddAttr(graphName, "rankdir", "LR")
281 | mainGraph.AddAttr(graphName, "newrank", "true")
282 | nodes := make(map[string]*ir.Node)
283 | edges := make(map[string]*ir.Edge)
284 | for _, l := range f.Layers {
285 | for k, _ := range l.ExamplePath {
286 | for k2, _ := range l.ExamplePath[k] {
287 | if isSimple {
288 | simpleEdge := getSimpleEdgeForPath(l.ExamplePath[k][k2])
289 | if simpleEdge == nil {
290 | continue
291 | }
292 | edges[simpleEdge.String()] = simpleEdge
293 | nodes[simpleEdge.Caller.Func.Addr] = simpleEdge.Caller
294 | nodes[simpleEdge.Callee.Func.Addr] = simpleEdge.Callee
295 | continue
296 | }
297 | for _, e := range l.ExamplePath[k][k2] {
298 | edges[e.String()] = e
299 | nodes[e.Caller.Func.Addr] = e.Caller
300 | nodes[e.Callee.Func.Addr] = e.Callee
301 | }
302 | }
303 | }
304 | for k, _ := range l.GetInToOutEdgeSet(f.callgraph) {
305 | for k2, _ := range l.GetInToOutEdgeSet(f.callgraph)[k] {
306 | for _, e := range l.GetInToOutEdgeSet(f.callgraph)[k][k2] {
307 | for _, e2 := range e {
308 | if e2.Callee == nil && e2.Caller == nil {
309 | continue
310 | }
311 | if e2.Callee == nil && e2.Caller != nil {
312 | nodes[e2.Caller.Func.Addr] = e2.Caller
313 | continue
314 | }
315 | if e2.Callee != nil && e2.Caller == nil {
316 | nodes[e2.Callee.Func.Addr] = e2.Callee
317 | continue
318 | }
319 | edges[e2.String()] = e2
320 | nodes[e2.Caller.Func.Addr] = e2.Caller
321 | nodes[e2.Callee.Func.Addr] = e2.Callee
322 | }
323 | }
324 | }
325 | }
326 | }
327 | for _, n := range nodes {
328 | if n.Func == nil {
329 | continue
330 | }
331 | mainGraph.AddNode(graphName, strconv.Itoa(n.ID), map[string]string{
332 | "label": "\"" + util.Escape(util.GetFuncSimpleName(n.Func.Name, f.pkgPrefix)) + "\"",
333 | })
334 | }
335 | for _, e := range edges {
336 | if e.Callee == nil || e.Caller == nil {
337 | continue
338 | }
339 | callerID := strconv.Itoa(e.Caller.ID)
340 | calleeID := strconv.Itoa(e.Callee.ID)
341 | mainGraph.AddEdge(callerID, calleeID, true, map[string]string{
342 | "label": "\"" + util.Escape(util.GetSiteSimpleName(e.Site.Name, f.pkgPrefix)) + "\"",
343 | //"constraint": "false",
344 | })
345 | }
346 | for i, l := range f.Layers {
347 | layerInSet := make(map[*ir.Node]bool)
348 | layerNodeSet := make(map[*ir.Node]bool)
349 | layerOutSet := make(map[*ir.Node]bool)
350 | for _, entity := range l.Entities {
351 | for n, _ := range entity.InNodeSet {
352 | layerInSet[n] = true
353 | }
354 | for n, _ := range entity.NodeSet {
355 | layerNodeSet[n] = true
356 | }
357 | for n, _ := range entity.OutNodeSet {
358 | layerOutSet[n] = true
359 | }
360 | }
361 | attr := map[string]string{
362 | "rank": "same",
363 | "style": "invis",
364 | }
365 | if len(layerInSet) > 0 {
366 | subGraphName := fmt.Sprintf("\"cluster_%d-in\"", i)
367 | mainGraph.AddSubGraph(graphName, subGraphName, attr)
368 | for n, _ := range layerInSet {
369 | nodeID := strconv.Itoa(n.ID)
370 | mainGraph.AddNode(subGraphName, nodeID, map[string]string{
371 | "label": "\"" + util.Escape(util.GetFuncSimpleName(n.Func.Name, f.pkgPrefix)) + "\"",
372 | "color": "green",
373 | })
374 | }
375 | }
376 | if len(layerNodeSet) > 0 {
377 | subGraphName := fmt.Sprintf("cluster_%d", i)
378 | mainGraph.AddSubGraph(graphName, subGraphName, attr)
379 | for n, _ := range layerNodeSet {
380 | nodeID := strconv.Itoa(n.ID)
381 | mainGraph.AddNode(subGraphName, nodeID, map[string]string{
382 | "label": "\"" + util.Escape(util.GetFuncSimpleName(n.Func.Name, f.pkgPrefix)) + "\"",
383 | "color": "red",
384 | })
385 | }
386 | }
387 | if len(layerOutSet) > 0 {
388 | subGraphName := fmt.Sprintf("\"cluster_%d-out\"", i)
389 | mainGraph.AddSubGraph(graphName, subGraphName, attr)
390 | for n, _ := range layerOutSet {
391 | nodeID := strconv.Itoa(n.ID)
392 | mainGraph.AddNode(subGraphName, nodeID, map[string]string{
393 | "label": "\"" + util.Escape(util.GetFuncSimpleName(n.Func.Name, f.pkgPrefix)) + "\"",
394 | "color": "yellow",
395 | })
396 | }
397 | }
398 | }
399 | return mainGraph.String()
400 | }
401 |
402 | func (f *Flow) SaveDot(filename string, isSimple bool) error {
403 | dotString := f.GetDot(isSimple)
404 | return util.WriteToFile(dotString, filename)
405 | }
406 |
407 | type Layer struct {
408 | Name string
409 | Entities []*Entity
410 | NodeSet map[*ir.Node]bool
411 | ExamplePath map[*ir.Node]map[*ir.Node][]*ir.Edge
412 | }
413 |
414 | func (l *Layer) GetInToOutEdgeSet(callgraphIR *ir.Callgraph) map[*ir.Node]map[*ir.Node][][]*ir.Edge {
415 | inToOutSet := make(map[*ir.Node]map[*ir.Node][][]*ir.Edge)
416 | for _, e := range l.Entities {
417 | entityInToOutSet := e.GetInToOutEdgeSet(callgraphIR)
418 | for k, _ := range entityInToOutSet {
419 | if _, ok := inToOutSet[k]; !ok {
420 | inToOutSet[k] = make(map[*ir.Node][][]*ir.Edge)
421 | }
422 | for k2, _ := range entityInToOutSet[k] {
423 | inToOutSet[k][k2] = entityInToOutSet[k][k2]
424 | }
425 | }
426 | }
427 | return inToOutSet
428 | }
429 |
430 | func (l *Layer) ResetLayer() {
431 | l.NodeSet = nil
432 | l.ExamplePath = nil
433 | for _, e := range l.Entities {
434 | e.ResetEntity()
435 | }
436 | }
437 |
438 | func (l *Layer) GetExamplePath(callgraphIR *ir.Callgraph) map[*ir.Node]map[*ir.Node][]*ir.Edge {
439 | if callgraphIR == nil {
440 | return nil
441 | }
442 | if l.ExamplePath != nil {
443 | return l.ExamplePath
444 | }
445 | examplePath := make(map[*ir.Node]map[*ir.Node][]*ir.Edge)
446 | for _, v := range l.Entities {
447 | for k, _ := range v.ExamplePath {
448 | if _, ok := examplePath[k]; !ok {
449 | examplePath[k] = make(map[*ir.Node][]*ir.Edge)
450 | }
451 | for k2, _ := range v.ExamplePath[k] {
452 | examplePath[k][k2] = v.ExamplePath[k][k2]
453 | }
454 | }
455 | }
456 | l.ExamplePath = examplePath
457 | return examplePath
458 | }
459 |
460 | func (l *Layer) GetStartAndEndFromExamplePath() (startNodeSet, endNodeSet map[*ir.Node]bool) {
461 | if l.ExamplePath == nil {
462 | return
463 | }
464 | return GetStartAndEndFromExamplePath(l.ExamplePath)
465 | }
466 |
467 | func GetStartAndEndFromExamplePath(examplePath map[*ir.Node]map[*ir.Node][]*ir.Edge) (startNodeSet, endNodeSet map[*ir.Node]bool) {
468 | if examplePath == nil {
469 | return
470 | }
471 | startNodeSet = make(map[*ir.Node]bool)
472 | endNodeSet = make(map[*ir.Node]bool)
473 | for from, v := range examplePath {
474 | for to, _ := range v {
475 | if len(examplePath[from][to]) > 0 {
476 | startNodeSet[from] = true
477 | endNodeSet[to] = true
478 | }
479 | }
480 | }
481 | return
482 | }
483 |
484 | func (l *Layer) GetNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
485 | if callgraphIR == nil {
486 | return nil
487 | }
488 | nodesSet := make(map[*ir.Node]bool)
489 | for i, _ := range l.Entities {
490 | for v, _ := range l.Entities[i].GetNodeSet(callgraphIR) {
491 | if matchesEntityIR(v, l.Entities[i]) {
492 | nodesSet[v] = true
493 | }
494 | }
495 | }
496 | l.NodeSet = nodesSet
497 | return nodesSet
498 | }
499 |
500 | func (l *Layer) GetOutEdgeSet(callgraphIR *ir.Callgraph) map[string]*ir.Edge {
501 | if callgraphIR == nil {
502 | return nil
503 | }
504 | edgesSet := make(map[string]*ir.Edge)
505 | for i, _ := range l.Entities {
506 | if l.Entities[i].OutSite == nil {
507 | continue
508 | }
509 | for n, _ := range l.Entities[i].OutNodeSet {
510 | for _, e := range n.In {
511 | if !isSiteMatchIR(e.Site, l.Entities[i].OutSite) {
512 | continue
513 | }
514 | edgesSet[e.String()] = e
515 | }
516 | }
517 | }
518 | return edgesSet
519 | }
520 |
521 | func (l *Layer) GetOutAllNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
522 | if callgraphIR == nil {
523 | return nil
524 | }
525 | nodesSet := make(map[*ir.Node]bool)
526 | for i, _ := range l.Entities {
527 | for n, _ := range l.Entities[i].GetOutAllNodeSet(callgraphIR) {
528 | nodesSet[n] = true
529 | }
530 | }
531 | return nodesSet
532 | }
533 |
534 | func (l *Layer) GetOutAllNodeSetOnlyRead(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
535 | if callgraphIR == nil {
536 | return nil
537 | }
538 | nodesSet := make(map[*ir.Node]bool)
539 | for i, _ := range l.Entities {
540 | for n, _ := range l.Entities[i].GetOutAllNodeSetOnlyRead(callgraphIR) {
541 | nodesSet[n] = true
542 | }
543 | }
544 | return nodesSet
545 | }
546 |
547 | func (l *Layer) GetInEdgeSet(callgraphIR *ir.Callgraph) map[string]*ir.Edge {
548 | if callgraphIR == nil {
549 | return nil
550 | }
551 | edgesSet := make(map[string]*ir.Edge)
552 | for i, _ := range l.Entities {
553 | if l.Entities[i].InSite == nil {
554 | continue
555 | }
556 | for n, _ := range l.Entities[i].InNodeSet {
557 | for _, e := range n.Out {
558 | if !isSiteMatchIR(e.Site, l.Entities[i].InSite) {
559 | continue
560 | }
561 | edgesSet[e.String()] = e
562 | }
563 | }
564 | }
565 | return edgesSet
566 | }
567 |
568 | func (l *Layer) GetInAllNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
569 | if callgraphIR == nil {
570 | return nil
571 | }
572 | nodesSet := make(map[*ir.Node]bool)
573 | for i, _ := range l.Entities {
574 | for n, _ := range l.Entities[i].GetInAllNodeSet(callgraphIR) {
575 | nodesSet[n] = true
576 | }
577 | }
578 | return nodesSet
579 | }
580 |
581 | func (l *Layer) GetInAllNodeSetOnlyRead(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
582 | if callgraphIR == nil {
583 | return nil
584 | }
585 | nodesSet := make(map[*ir.Node]bool)
586 | for i, _ := range l.Entities {
587 | for n, _ := range l.Entities[i].GetInAllNodeSetOnlyRead(callgraphIR) {
588 | nodesSet[n] = true
589 | }
590 | }
591 | return nodesSet
592 | }
593 |
594 | type Entity struct {
595 | *config.Entity
596 | NodeSet map[*ir.Node]bool
597 | InNodeSet map[*ir.Node]bool
598 | OutNodeSet map[*ir.Node]bool
599 | ExamplePath map[*ir.Node]map[*ir.Node][]*ir.Edge
600 | }
601 |
602 | func (e *Entity) String() string {
603 | eStr, _ := json.Marshal(e.Entity)
604 | return string(eStr)
605 | }
606 |
607 | func (e *Entity) GetInToOutEdgeSet(callgraphIR *ir.Callgraph) map[*ir.Node]map[*ir.Node][][]*ir.Edge {
608 | inToOutSet := make(map[*ir.Node]map[*ir.Node][][]*ir.Edge)
609 | if e.InSite == nil && e.OutSite == nil {
610 | for k, _ := range e.GetNodeSet(callgraphIR) {
611 | oneNodeEdge := &ir.Edge{Caller: k}
612 | oneNodeEdgeSet := make(map[*ir.Node][][]*ir.Edge)
613 | oneNodeEdgeSet[k] = append(make([][]*ir.Edge, 0), append(make([]*ir.Edge, 0), oneNodeEdge))
614 | inToOutSet[k] = oneNodeEdgeSet
615 | }
616 | return inToOutSet
617 | }
618 | if e.InSite == nil && e.OutSite != nil {
619 | for k, _ := range e.OutNodeSet {
620 | for _, eOut := range k.In {
621 | if eOut.Caller != nil && e.GetNodeSet(callgraphIR)[eOut.Caller] {
622 | nodeToOutEdge := &ir.Edge{Caller: eOut.Caller, Callee: k, Site: eOut.Site}
623 | if _, ok := inToOutSet[eOut.Caller]; !ok {
624 | inToOutSet[eOut.Caller] = make(map[*ir.Node][][]*ir.Edge)
625 | }
626 | inToOutSet[eOut.Caller][k] = append(make([][]*ir.Edge, 0), append(make([]*ir.Edge, 0), nodeToOutEdge))
627 | }
628 | }
629 | }
630 | }
631 | if e.InSite != nil && e.OutSite == nil {
632 | for k, _ := range e.InNodeSet {
633 | for _, eIn := range k.Out {
634 | if eIn.Callee != nil && e.GetNodeSet(callgraphIR)[eIn.Callee] {
635 | inToNodeEdge := &ir.Edge{Caller: k, Callee: eIn.Callee, Site: eIn.Site}
636 | if _, ok := inToOutSet[k]; !ok {
637 | inToOutSet[k] = make(map[*ir.Node][][]*ir.Edge)
638 | }
639 | inToOutSet[k][eIn.Callee] = append(make([][]*ir.Edge, 0), append(make([]*ir.Edge, 0), inToNodeEdge))
640 | }
641 | }
642 | }
643 | }
644 | if e.InSite != nil && e.OutSite != nil {
645 | for k, _ := range e.OutNodeSet {
646 | for _, eOut := range k.In {
647 | if eOut.Caller != nil && e.GetNodeSet(callgraphIR)[eOut.Caller] {
648 | node := eOut.Caller
649 | for _, eIn := range node.In {
650 | if eIn.Caller != nil && e.InNodeSet[eIn.Caller] {
651 | inToNodeEdge := &ir.Edge{Caller: eIn.Caller, Callee: node, Site: eIn.Site}
652 | nodeToOutEdge := &ir.Edge{Caller: node, Callee: k, Site: eOut.Site}
653 | if _, ok := inToOutSet[eIn.Caller]; !ok {
654 | inToOutSet[eIn.Caller] = make(map[*ir.Node][][]*ir.Edge)
655 | }
656 | if _, ok := inToOutSet[eIn.Caller][k]; !ok {
657 | inToOutSet[eIn.Caller][k] = make([][]*ir.Edge, 0)
658 | }
659 | path := append(make([]*ir.Edge, 0), inToNodeEdge, nodeToOutEdge)
660 | inToOutSet[eIn.Caller][k] = append(inToOutSet[eIn.Caller][k], path)
661 | }
662 | }
663 | }
664 | }
665 | }
666 | }
667 | return inToOutSet
668 | }
669 |
670 | func (e *Entity) ResetEntity() {
671 | e.NodeSet = nil
672 | e.InNodeSet = nil
673 | e.OutNodeSet = nil
674 | e.ExamplePath = nil
675 | }
676 |
677 | func (e *Entity) AddExamplePath(ep map[*ir.Node]map[*ir.Node][]*ir.Edge) {
678 | if e.ExamplePath == nil {
679 | e.ExamplePath = ep
680 | }
681 | for k, _ := range ep {
682 | if _, ok := e.ExamplePath[k]; !ok {
683 | e.ExamplePath[k] = make(map[*ir.Node][]*ir.Edge)
684 | }
685 | for k2, _ := range ep[k] {
686 | e.ExamplePath[k][k2] = ep[k][k2]
687 | }
688 | }
689 | }
690 |
691 | func (e *Entity) GetNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
692 | if callgraphIR == nil {
693 | return nil
694 | }
695 | if e.NodeSet != nil {
696 | return e.NodeSet
697 | }
698 | nodesSet := make(map[*ir.Node]bool)
699 | for _, v := range callgraphIR.Nodes {
700 | if matchesEntityIR(v, e) {
701 | nodesSet[v] = true
702 | }
703 | }
704 | e.NodeSet = nodesSet
705 | return e.NodeSet
706 | }
707 |
708 | func (e *Entity) TrimNodeSet(nodeSet map[*ir.Node]bool, callgraphIR *ir.Callgraph) {
709 | if e.NodeSet == nil {
710 | e.GetNodeSet(callgraphIR)
711 | }
712 | for n, _ := range e.NodeSet {
713 | if _, ok := nodeSet[n]; !ok {
714 | delete(e.NodeSet, n)
715 | }
716 | }
717 | }
718 |
719 | func (e *Entity) TrimInNodeSet(in map[*ir.Node]bool, callgraphIR *ir.Callgraph) {
720 | if e.InSite != nil {
721 | if e.InNodeSet == nil {
722 | e.UpdateInSiteNodeSetWithNodeSet(callgraphIR)
723 | }
724 | for n, _ := range e.InNodeSet {
725 | if _, ok := in[n]; !ok {
726 | delete(e.InNodeSet, n)
727 | }
728 | }
729 | e.UpdateNodeSetWithInSiteNodeSet(callgraphIR)
730 | return
731 | }
732 | e.TrimNodeSet(in, callgraphIR)
733 | }
734 |
735 | func (e *Entity) TrimOutNodeSet(out map[*ir.Node]bool, callgraphIR *ir.Callgraph) {
736 | if e.OutSite != nil {
737 | if e.OutNodeSet == nil {
738 | e.UpdateOutSiteNodeSetWithNodeSet(callgraphIR)
739 | }
740 | for n, _ := range e.OutNodeSet {
741 | if _, ok := out[n]; !ok {
742 | delete(e.OutNodeSet, n)
743 | }
744 | }
745 | e.UpdateNodeSetWithOutSiteNodeSet(callgraphIR)
746 | return
747 | }
748 | e.TrimNodeSet(out, callgraphIR)
749 | }
750 |
751 | func (e *Entity) GetInAllNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
752 | if callgraphIR == nil {
753 | return nil
754 | }
755 | if e.InSite == nil {
756 | return e.GetNodeSet(callgraphIR)
757 | }
758 | if e.InNodeSet == nil {
759 | e.UpdateInSiteNodeSetWithNodeSet(callgraphIR)
760 | }
761 | return e.InNodeSet
762 | }
763 |
764 | func (e *Entity) GetInAllNodeSetOnlyRead(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
765 | if callgraphIR == nil {
766 | return nil
767 | }
768 | if e.InSite == nil {
769 | return e.NodeSet
770 | }
771 | return e.InNodeSet
772 | }
773 |
774 | func (e *Entity) GetOutAllNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
775 | if callgraphIR == nil {
776 | return nil
777 | }
778 | if e.OutSite == nil {
779 | return e.GetNodeSet(callgraphIR)
780 | }
781 | if e.OutNodeSet == nil {
782 | e.UpdateOutSiteNodeSetWithNodeSet(callgraphIR)
783 | }
784 | return e.OutNodeSet
785 | }
786 |
787 | func (e *Entity) GetOutAllNodeSetOnlyRead(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
788 | if callgraphIR == nil {
789 | return nil
790 | }
791 | if e.OutSite == nil {
792 | return e.NodeSet
793 | }
794 | return e.OutNodeSet
795 | }
796 |
797 | func (e *Entity) UpdateNodeSetWithInSiteNodeSet(callgraphIR *ir.Callgraph) {
798 | if callgraphIR == nil {
799 | return
800 | }
801 | if e.InSite == nil {
802 | e.GetNodeSet(callgraphIR)
803 | return
804 | }
805 | if e.InNodeSet == nil {
806 | e.UpdateInSiteNodeSetWithNodeSet(callgraphIR)
807 | }
808 | nodeSet := make(map[*ir.Node]bool)
809 | for n, _ := range e.InNodeSet {
810 | for _, eOut := range n.Out {
811 | if matchesEntityIR(eOut.Callee, e) {
812 | nodeSet[eOut.Callee] = true
813 | }
814 | }
815 | }
816 | e.NodeSet = nodeSet
817 | }
818 |
819 | func (e *Entity) UpdateNodeSetWithOutSiteNodeSet(callgraphIR *ir.Callgraph) {
820 | if callgraphIR == nil {
821 | return
822 | }
823 | if e.OutSite == nil {
824 | e.GetNodeSet(callgraphIR)
825 | return
826 | }
827 | if e.OutNodeSet == nil {
828 | e.UpdateOutSiteNodeSetWithNodeSet(callgraphIR)
829 | }
830 | nodeSet := make(map[*ir.Node]bool)
831 | for n, _ := range e.OutNodeSet {
832 | for _, eIn := range n.In {
833 | if matchesEntityIR(eIn.Caller, e) {
834 | nodeSet[eIn.Caller] = true
835 | }
836 | }
837 | }
838 | e.NodeSet = nodeSet
839 | }
840 |
841 | func (e *Entity) UpdateInSiteNodeSetWithNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
842 | if callgraphIR == nil {
843 | return nil
844 | }
845 | if e.InSite == nil {
846 | return make(map[*ir.Node]bool)
847 | }
848 | nodesSet := make(map[*ir.Node]bool)
849 | for v, _ := range e.GetNodeSet(callgraphIR) {
850 | if matchesEntityIR(v, e) {
851 | for _, eIn := range v.In {
852 | if isSiteMatchIR(eIn.Site, e.InSite) {
853 | nodesSet[eIn.Caller] = true
854 | }
855 | }
856 | }
857 | }
858 | e.InNodeSet = nodesSet
859 | return nodesSet
860 | }
861 |
862 | func (e *Entity) UpdateOutSiteNodeSetWithNodeSet(callgraphIR *ir.Callgraph) map[*ir.Node]bool {
863 | if callgraphIR == nil {
864 | return nil
865 | }
866 | if e.OutSite == nil {
867 | return make(map[*ir.Node]bool)
868 | }
869 | nodesSet := make(map[*ir.Node]bool)
870 | for v, _ := range e.GetNodeSet(callgraphIR) {
871 | if matchesEntityIR(v, e) {
872 | for _, eOut := range v.Out {
873 | if isSiteMatchIR(eOut.Site, e.OutSite) {
874 | nodesSet[eOut.Callee] = true
875 | }
876 | }
877 | }
878 | }
879 | e.OutNodeSet = nodesSet
880 | return nodesSet
881 | }
882 |
883 | func matchesEntityIR(n *ir.Node, entity *Entity) bool {
884 | if n == nil || n.Func == nil || n.Func.Name == "" || n.Func.Signature == "" {
885 | return false
886 | }
887 | if entity == nil || (entity.Name == nil && entity.InSite == nil && entity.OutSite == nil) {
888 | return false
889 | }
890 | fName := n.Func.Name
891 | nameCheckPass := true
892 | if entity.Name != nil {
893 | nameCheckPass = false
894 | if entity.Name.Match(fName) {
895 | nameCheckPass = true
896 | }
897 | }
898 | inSiteCheckPass := true
899 | if entity.InSite != nil {
900 | inSiteCheckPass = false
901 | for _, e := range n.In {
902 | if isSiteMatchIR(e.Site, entity.InSite) {
903 | inSiteCheckPass = true
904 | break
905 | }
906 | }
907 | }
908 | outSiteCheckPass := true
909 | if entity.OutSite != nil {
910 | outSiteCheckPass = false
911 | for _, e := range n.Out {
912 | if isSiteMatchIR(e.Site, entity.OutSite) {
913 | outSiteCheckPass = true
914 | break
915 | }
916 | }
917 |
918 | }
919 | signatureCheckPass := true
920 | if entity.Signature != nil {
921 | signatureCheckPass = false
922 | fSig := n.Func.Signature
923 | if entity.Signature.Match(fSig) {
924 | signatureCheckPass = true
925 | }
926 | }
927 | if nameCheckPass && inSiteCheckPass && outSiteCheckPass && signatureCheckPass {
928 | return true
929 | }
930 | return false
931 | }
932 |
933 | func isSiteMatchIR(s *ir.Site, site *mode.Mode) bool {
934 | if s == nil || s.Name == "" {
935 | return false
936 | }
937 | if site == nil {
938 | return false
939 | }
940 | return site.Match(s.Name)
941 | }
942 |
943 | func getSimpleEdgeForPath(path []*ir.Edge) *ir.Edge {
944 | if len(path) == 0 {
945 | return nil
946 | }
947 | if len(path) == 1 {
948 | return path[0]
949 | }
950 | caller := path[0].Caller
951 | callee := path[len(path)-1].Callee
952 | site := fmt.Sprintf("%s->...->%s", path[0].Site.Name, path[len(path)-1].Site.Name)
953 | return &ir.Edge{Caller: caller, Callee: callee, Site: &ir.Site{Name: site}}
954 | }
955 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/laindream/go-callflow-vis
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.3.2
7 | github.com/apache/incubator-fury/go/fury v0.0.0-20240325154417-20a1a78b17a7
8 | github.com/awalterschulze/gographviz v2.0.3+incompatible
9 | github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4
10 | github.com/gin-gonic/gin v1.9.1
11 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
12 | golang.org/x/tools v0.19.0
13 | )
14 |
15 | require (
16 | github.com/bytedance/sonic v1.11.3 // indirect
17 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
18 | github.com/chenzhuoyu/iasm v0.9.1 // indirect
19 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect
20 | github.com/gin-contrib/sse v0.1.0 // indirect
21 | github.com/go-playground/locales v0.14.1 // indirect
22 | github.com/go-playground/universal-translator v0.18.1 // indirect
23 | github.com/go-playground/validator/v10 v10.19.0 // indirect
24 | github.com/goccy/go-json v0.10.2 // indirect
25 | github.com/json-iterator/go v1.1.12 // indirect
26 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
27 | github.com/leodido/go-urn v1.4.0 // indirect
28 | github.com/mattn/go-isatty v0.0.20 // indirect
29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
30 | github.com/modern-go/reflect2 v1.0.2 // indirect
31 | github.com/pelletier/go-toml/v2 v2.2.0 // indirect
32 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
33 | github.com/ugorji/go/codec v1.2.12 // indirect
34 | golang.org/x/arch v0.7.0 // indirect
35 | golang.org/x/crypto v0.21.0 // indirect
36 | golang.org/x/mod v0.16.0 // indirect
37 | golang.org/x/net v0.22.0 // indirect
38 | golang.org/x/sys v0.18.0 // indirect
39 | golang.org/x/text v0.14.0 // indirect
40 | google.golang.org/protobuf v1.33.0 // indirect
41 | gopkg.in/yaml.v3 v3.0.1 // indirect
42 | )
43 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
2 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
3 | github.com/apache/incubator-fury/go/fury v0.0.0-20240325154417-20a1a78b17a7 h1:tEiXqN12AcbBja/L2KY7s35dWAZQ4uDdqiRbuzYUJxo=
4 | github.com/apache/incubator-fury/go/fury v0.0.0-20240325154417-20a1a78b17a7/go.mod h1:OcCouXjDpXu82F59qgJsjbgkBqLiherWohTCxuhqngs=
5 | github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E=
6 | github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs=
7 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
8 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
9 | github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
10 | github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
11 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
13 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
14 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
15 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
16 | github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
17 | github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg=
22 | github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds=
23 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
24 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
25 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
26 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
27 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
28 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
29 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
30 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
31 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
32 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
33 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
34 | github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
35 | github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
36 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
37 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
38 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
39 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
40 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
41 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
42 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
43 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
44 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
45 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
46 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
47 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
48 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
49 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
50 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
51 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
53 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
54 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
55 | github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
56 | github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
57 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
58 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
62 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
63 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
64 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
65 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
66 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
67 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
68 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
69 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
70 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
71 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
72 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
73 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
74 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
75 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
76 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
77 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
78 | golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
79 | golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
80 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
81 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
82 | golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
83 | golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
84 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
85 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
86 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
87 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
88 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
89 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
91 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
92 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
93 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
94 | golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
95 | golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
96 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
97 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
98 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
99 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
101 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
102 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
103 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
104 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
105 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
106 |
--------------------------------------------------------------------------------
/ir/ir.go:
--------------------------------------------------------------------------------
1 | package ir
2 |
3 | import (
4 | "fmt"
5 | "github.com/laindream/go-callflow-vis/render"
6 | "github.com/laindream/go-callflow-vis/util"
7 | "golang.org/x/tools/go/callgraph"
8 | "math"
9 | "strings"
10 | "unsafe"
11 | )
12 |
13 | type Callgraph struct {
14 | Root *Node
15 | Nodes map[string]*Node
16 | }
17 |
18 | func (c *Callgraph) AddEdge(callerFn, calleeFn string, site *Site) {
19 | caller := c.Nodes[callerFn]
20 | callee := c.Nodes[calleeFn]
21 | if caller == nil || callee == nil {
22 | return
23 | }
24 | edge := &Edge{
25 | Caller: caller,
26 | Site: site,
27 | Callee: callee,
28 | }
29 | callee.AddIn(edge)
30 | caller.AddOut(edge)
31 | }
32 |
33 | func (c *Callgraph) ResetSearched() {
34 | c.Root.ResetSearched()
35 | for _, v := range c.Nodes {
36 | v.ResetSearched()
37 | }
38 | return
39 | }
40 |
41 | func (c *Callgraph) DeleteNode(n *Node) *Callgraph {
42 | inOutNumMultiplier := len(n.In) * len(n.Out)
43 | inOutNumAdder := len(n.In) + len(n.Out)
44 | nodeRate := len(c.Nodes) * int(math.Sqrt(float64(len(c.Nodes))))
45 | if inOutNumMultiplier > nodeRate ||
46 | inOutNumAdder*inOutNumAdder > nodeRate {
47 | return GetFilteredCallgraph(c, func(s string) bool {
48 | return s != n.Func.Name
49 | })
50 | }
51 | n.DeleteIns()
52 | n.DeleteOuts()
53 | if n.Func != nil {
54 | delete(c.Nodes, n.Func.Addr)
55 | }
56 | return c
57 | }
58 |
59 | func ConvertToIR(graph *callgraph.Graph) *Callgraph {
60 | getPointerStr := func(p unsafe.Pointer) string {
61 | return fmt.Sprintf("%p", p)
62 | }
63 | var root *Node
64 | if graph.Root != nil {
65 | var name string
66 | var fp *Func
67 | var fSig string
68 | if graph.Root.Func != nil {
69 | name = graph.Root.Func.String()
70 | if graph.Root.Func.Signature != nil {
71 | fSig = graph.Root.Func.Signature.String()
72 | }
73 | if graph.Root.Func.Parent() != nil {
74 | fpSig := ""
75 | if graph.Root.Func.Parent().Signature != nil {
76 | fpSig = graph.Root.Func.Parent().Signature.String()
77 | }
78 | fp = &Func{
79 | Name: graph.Root.Func.Parent().String(),
80 | Addr: getPointerStr(unsafe.Pointer(graph.Root.Func.Parent())),
81 | Signature: fpSig,
82 | }
83 | }
84 | }
85 | root = &Node{
86 | Func: &Func{
87 | Name: name,
88 | Addr: getPointerStr(unsafe.Pointer(graph.Root.Func)),
89 | //Addr: name,
90 | Parent: fp,
91 | Signature: fSig,
92 | },
93 | ID: graph.Root.ID,
94 | }
95 | }
96 | nodes := make(map[string]*Node)
97 | for f, node := range graph.Nodes {
98 | if f == nil {
99 | continue
100 | }
101 | if node.Func == nil {
102 | continue
103 | }
104 | var fp *Func
105 | var fSig string
106 | if node.Func.Signature != nil {
107 | fSig = node.Func.Signature.String()
108 | }
109 | if node.Func.Parent() != nil {
110 | fpSig := ""
111 | if node.Func.Parent().Signature != nil {
112 | fpSig = node.Func.Parent().Signature.String()
113 | }
114 | fp = &Func{
115 | Name: node.Func.Parent().String(),
116 | Addr: getPointerStr(unsafe.Pointer(node.Func.Parent())),
117 | Signature: fpSig,
118 | }
119 | }
120 | funcIR := &Func{
121 | Name: node.Func.String(),
122 | Addr: getPointerStr(unsafe.Pointer(node.Func)),
123 | Parent: fp,
124 | Signature: fSig,
125 | }
126 | nodes[funcIR.Addr] = &Node{
127 | Func: funcIR,
128 | ID: node.ID,
129 | }
130 | }
131 | for attr, node := range graph.Nodes {
132 | if attr == nil {
133 | continue
134 | }
135 | if node.Func == nil {
136 | continue
137 | }
138 | nodeIR := nodes[getPointerStr(unsafe.Pointer(attr))]
139 | for _, edge := range node.Out {
140 | caller := nodes[getPointerStr(unsafe.Pointer(edge.Caller.Func))]
141 | callee := nodes[getPointerStr(unsafe.Pointer(edge.Callee.Func))]
142 | var site *Site
143 | if edge.Site != nil {
144 | site = &Site{
145 | Name: edge.Site.String(),
146 | Addr: getPointerStr(unsafe.Pointer(&edge.Site)),
147 | }
148 | }
149 | edgeIR := &Edge{
150 | Caller: caller,
151 | Site: site,
152 | Callee: callee,
153 | }
154 | nodeIR.Out = append(caller.Out, edgeIR)
155 | }
156 | for _, edge := range node.In {
157 | caller := nodes[getPointerStr(unsafe.Pointer(edge.Caller.Func))]
158 | callee := nodes[getPointerStr(unsafe.Pointer(edge.Callee.Func))]
159 | var site *Site
160 | if edge.Site != nil {
161 | site = &Site{
162 | Name: edge.Site.String(),
163 | Addr: getPointerStr(unsafe.Pointer(&edge.Site)),
164 | }
165 | }
166 | edgeIR := &Edge{
167 | Caller: caller,
168 | Site: site,
169 | Callee: callee,
170 | }
171 | nodeIR.In = append(callee.In, edgeIR)
172 | }
173 | }
174 | return &Callgraph{
175 | Root: root,
176 | Nodes: nodes,
177 | }
178 | }
179 |
180 | func GetFilteredCallgraph(graph *Callgraph, filter func(string) bool) *Callgraph {
181 | var root *Node
182 | if graph.Root != nil {
183 | var name string
184 | var fp *Func
185 | var fSig string
186 | if graph.Root.Func != nil {
187 | name = graph.Root.Func.Name
188 | if graph.Root.Func.Signature != "" {
189 | fSig = graph.Root.Func.Signature
190 | }
191 | if graph.Root.Func.Parent != nil {
192 | fpSig := ""
193 | if graph.Root.Func.Parent.Signature != "" {
194 | fpSig = graph.Root.Func.Parent.Signature
195 | }
196 | fp = &Func{
197 | Name: graph.Root.Func.Parent.Name,
198 | Addr: graph.Root.Func.Parent.Addr,
199 | Signature: fpSig,
200 | }
201 | }
202 | }
203 | root = &Node{
204 | Func: &Func{
205 | Name: name,
206 | Addr: graph.Root.Func.Addr,
207 | //Addr: name,
208 | Parent: fp,
209 | Signature: fSig,
210 | },
211 | ID: graph.Root.ID,
212 | }
213 | }
214 | nodes := make(map[string]*Node)
215 | for f, node := range graph.Nodes {
216 | if f == "" {
217 | continue
218 | }
219 | if node.Func == nil {
220 | continue
221 | }
222 | if !filter(node.Func.Name) {
223 | continue
224 | }
225 | var fp *Func
226 | var fSig string
227 | if node.Func.Signature != "" {
228 | fSig = node.Func.Signature
229 | }
230 | if node.Func.Parent != nil {
231 | fpSig := ""
232 | if node.Func.Parent.Signature != "" {
233 | fpSig = node.Func.Parent.Signature
234 | }
235 | fp = &Func{
236 | Name: node.Func.Parent.Name,
237 | Addr: node.Func.Parent.Addr,
238 | Signature: fpSig,
239 | }
240 | }
241 | funcIR := &Func{
242 | Name: node.Func.Name,
243 | Addr: node.Func.Addr,
244 | Parent: fp,
245 | Signature: fSig,
246 | }
247 | nodes[funcIR.Addr] = &Node{
248 | Func: funcIR,
249 | ID: node.ID,
250 | }
251 | }
252 | for attr, node := range graph.Nodes {
253 | if attr == "" {
254 | continue
255 | }
256 | if node.Func == nil {
257 | continue
258 | }
259 | if !filter(node.Func.Name) {
260 | continue
261 | }
262 | nodeIR := nodes[attr]
263 | for _, edge := range node.Out {
264 | if !filter(edge.Callee.Func.Name) {
265 | continue
266 | }
267 | caller := nodes[edge.Caller.Func.Addr]
268 | callee := nodes[edge.Callee.Func.Addr]
269 | var site *Site
270 | if edge.Site != nil {
271 | site = &Site{
272 | Name: edge.Site.Name,
273 | Addr: edge.Site.Addr,
274 | }
275 | }
276 | edgeIR := &Edge{
277 | Caller: caller,
278 | Site: site,
279 | Callee: callee,
280 | }
281 | nodeIR.Out = append(caller.Out, edgeIR)
282 | }
283 | for _, edge := range node.In {
284 | if !filter(edge.Caller.Func.Name) {
285 | continue
286 | }
287 | caller := nodes[edge.Caller.Func.Addr]
288 | callee := nodes[edge.Callee.Func.Addr]
289 | var site *Site
290 | if edge.Site != nil {
291 | site = &Site{
292 | Name: edge.Site.Name,
293 | Addr: edge.Site.Addr,
294 | }
295 | }
296 | edgeIR := &Edge{
297 | Caller: caller,
298 | Site: site,
299 | Callee: callee,
300 | }
301 | nodeIR.In = append(callee.In, edgeIR)
302 | }
303 | }
304 | return &Callgraph{
305 | Root: root,
306 | Nodes: nodes,
307 | }
308 | }
309 |
310 | type Func struct {
311 | Name string
312 | Addr string
313 | Parent *Func
314 | Signature string
315 | }
316 |
317 | type Site struct {
318 | Name string
319 | Addr string
320 | }
321 |
322 | type Node struct {
323 | Func *Func
324 | ID int
325 | In []*Edge
326 | Out []*Edge
327 | humanReadableInMap map[string]*Edge
328 | humanReadableOutMap map[string]*Edge
329 | inMap map[string]*Edge
330 | outMap map[string]*Edge
331 | searched bool
332 | path []*Edge
333 | tags map[string]bool
334 | }
335 |
336 | func (n *Node) ToRenderNode(prefix string) *render.Node {
337 | return &render.Node{
338 | ID: n.ID,
339 | Set: -1,
340 | Name: util.GetFuncSimpleName(n.Func.Name, prefix),
341 | Detail: n.Func.Name,
342 | }
343 | }
344 |
345 | func (n *Node) ResetTags() {
346 | n.tags = nil
347 | }
348 |
349 | func (n *Node) AddTag(tag string) {
350 | if n.tags == nil {
351 | n.tags = make(map[string]bool)
352 | }
353 | n.tags[tag] = true
354 | }
355 |
356 | func (n *Node) HasTag(tag string) bool {
357 | if n.tags == nil {
358 | return false
359 | }
360 | return n.tags[tag]
361 | }
362 |
363 | func (n *Node) UpdateHumanReadableMap() {
364 | n.humanReadableInMap = make(map[string]*Edge)
365 | for i, _ := range n.In {
366 | n.humanReadableInMap[n.In[i].ReadableString()] = n.In[i]
367 | }
368 | n.humanReadableOutMap = make(map[string]*Edge)
369 | for i, _ := range n.Out {
370 | n.humanReadableOutMap[n.Out[i].ReadableString()] = n.Out[i]
371 | }
372 | }
373 |
374 | func (n *Node) UpdateInOutMap() {
375 | n.inMap = make(map[string]*Edge)
376 | for i, _ := range n.In {
377 | n.inMap[n.In[i].String()] = n.In[i]
378 | }
379 | n.outMap = make(map[string]*Edge)
380 | for i, _ := range n.Out {
381 | n.outMap[n.Out[i].String()] = n.Out[i]
382 | }
383 | }
384 |
385 | func (n *Node) AddIn(e *Edge) {
386 | if len(n.inMap) == 0 {
387 | n.UpdateInOutMap()
388 | }
389 | if _, ok := n.inMap[e.String()]; ok {
390 | return
391 | }
392 | n.inMap[e.String()] = e
393 | n.In = append(n.In, e)
394 | }
395 |
396 | func (n *Node) AddOut(e *Edge) {
397 | if len(n.outMap) == 0 {
398 | n.UpdateInOutMap()
399 | }
400 | if _, ok := n.outMap[e.String()]; ok {
401 | return
402 | }
403 | n.outMap[e.String()] = e
404 | n.Out = append(n.Out, e)
405 | }
406 |
407 | func (n *Node) AddEnhancementIn(e *Edge) {
408 | if len(n.humanReadableInMap) == 0 {
409 | n.UpdateHumanReadableMap()
410 | }
411 | if _, ok := n.humanReadableInMap[e.ReadableString()]; ok {
412 | return
413 | }
414 | n.humanReadableInMap[e.ReadableString()] = e
415 | n.In = append(n.In, e)
416 | }
417 |
418 | func (n *Node) AddEnhancementOut(e *Edge) {
419 | if len(n.humanReadableOutMap) == 0 {
420 | n.UpdateHumanReadableMap()
421 | }
422 | if _, ok := n.humanReadableOutMap[e.ReadableString()]; ok {
423 | return
424 | }
425 | n.humanReadableOutMap[e.ReadableString()] = e
426 | n.Out = append(n.Out, e)
427 | }
428 |
429 | func (n *Node) SetPath(path []*Edge) {
430 | n.path = path
431 | }
432 |
433 | func (n *Node) GetPath() []*Edge {
434 | return n.path
435 | }
436 |
437 | func (n *Node) ClearPath() {
438 | n.path = nil
439 | }
440 |
441 | func (n *Node) IsSearched() bool {
442 | return n.searched
443 | }
444 |
445 | func (n *Node) SetSearched() {
446 | n.searched = true
447 | }
448 |
449 | func (n *Node) ResetSearched() {
450 | n.searched = false
451 | }
452 |
453 | func (n *Node) DeleteIns() {
454 | for _, e := range n.In {
455 | removeOutEdge(e)
456 | }
457 | n.In = nil
458 | }
459 |
460 | func removeOutEdge(edge *Edge) {
461 | caller := edge.Caller
462 | n := len(caller.Out)
463 | for i, e := range caller.Out {
464 | if e.String() == edge.String() {
465 | caller.Out[i] = caller.Out[n-1]
466 | caller.Out[n-1] = nil
467 | caller.Out = caller.Out[:n-1]
468 | return
469 | }
470 | }
471 | }
472 |
473 | func (n *Node) DeleteOuts() {
474 | for _, e := range n.Out {
475 | removeInEdge(e)
476 | }
477 | n.Out = nil
478 | }
479 |
480 | func removeInEdge(edge *Edge) {
481 | caller := edge.Callee
482 | n := len(caller.In)
483 | for i, e := range caller.In {
484 | if e.String() == edge.String() {
485 | caller.In[i] = caller.In[n-1]
486 | caller.In[n-1] = nil
487 | caller.In = caller.In[:n-1]
488 | return
489 | }
490 | }
491 | }
492 |
493 | type Edge struct {
494 | Caller *Node
495 | Site *Site
496 | Callee *Node
497 | }
498 |
499 | func (e *Edge) ToRenderEdge(prefix string) *render.Edge {
500 | return &render.Edge{
501 | From: e.Caller.ID,
502 | To: e.Callee.ID,
503 | Name: util.GetSiteSimpleName(e.Site.Name, prefix),
504 | Detail: e.Site.Name,
505 | }
506 | }
507 |
508 | func (e *Edge) ReadableString() string {
509 | if e == nil {
510 | return ""
511 | }
512 | callerFuncName := ""
513 | if e.Caller != nil && e.Caller.Func != nil {
514 | callerFuncName = e.Caller.Func.Name
515 | }
516 | siteName := ""
517 | if e.Site != nil {
518 | siteName = e.Site.Name
519 | if strings.Contains(siteName, "->Skip(") {
520 | siteName = "Skip()"
521 | }
522 | }
523 | calleeFuncName := ""
524 | if e.Callee != nil && e.Callee.Func != nil {
525 | calleeFuncName = e.Callee.Func.Name
526 | }
527 | return fmt.Sprintf("%s-|%s|->%s=", callerFuncName, siteName, calleeFuncName)
528 | }
529 |
530 | func (e *Edge) String() string {
531 | if e == nil {
532 | return ""
533 | }
534 | callerFuncAddr := ""
535 | if e.Caller != nil && e.Caller.Func != nil {
536 | callerFuncAddr = e.Caller.Func.Addr
537 | }
538 | siteAddr := ""
539 | if e.Site != nil {
540 | siteAddr = e.Site.Addr
541 | if strings.Contains(siteAddr, "->Skip(") {
542 | siteAddr = "Skip()"
543 | }
544 | }
545 | calleeFuncAddr := ""
546 | if e.Callee != nil && e.Callee.Func != nil {
547 | calleeFuncAddr = e.Callee.Func.Addr
548 | }
549 | return fmt.Sprintf("%s-|%s|->%s=", callerFuncAddr, siteAddr, calleeFuncAddr)
550 | }
551 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | var logger = NewLogger(false)
9 |
10 | func GetLogger() *Logger {
11 | return logger
12 | }
13 |
14 | func SetLogger(debugFlag bool) {
15 | logger = NewLogger(debugFlag)
16 | }
17 |
18 | type Logger struct {
19 | *log.Logger
20 | debugFlag bool
21 | }
22 |
23 | func NewLogger(debugFlag bool) *Logger {
24 | return &Logger{
25 | Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
26 | debugFlag: debugFlag,
27 | }
28 | }
29 |
30 | func (l *Logger) Debug(v ...interface{}) {
31 | if l.debugFlag {
32 | l.Println("[DEBUG]", v)
33 | }
34 | }
35 |
36 | func (l *Logger) Debugf(format string, v ...interface{}) {
37 | if l.debugFlag {
38 | l.Printf("[DEBUG] "+format+"\n", v...)
39 | }
40 | }
41 |
42 | func (l *Logger) Info(v ...interface{}) {
43 | l.Println("[INFO]", v)
44 | }
45 |
46 | func (l *Logger) Infof(format string, v ...interface{}) {
47 | l.Printf("[INFO] "+format+"\n", v...)
48 | }
49 |
50 | func (l *Logger) Warn(v ...interface{}) {
51 | l.Println("[WARN]", v)
52 | }
53 |
54 | func (l *Logger) Warnf(format string, v ...interface{}) {
55 | l.Printf("[WARN] "+format+"\n", v...)
56 | }
57 |
58 | func (l *Logger) Error(v ...interface{}) {
59 | l.Println("[ERROR]", v)
60 | }
61 |
62 | func (l *Logger) Errorf(format string, v ...interface{}) {
63 | l.Printf("[ERROR] "+format+"\n", v...)
64 | }
65 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "flag"
6 | "fmt"
7 | "github.com/gin-gonic/gin"
8 | "github.com/laindream/go-callflow-vis/analysis"
9 | "github.com/laindream/go-callflow-vis/config"
10 | "github.com/laindream/go-callflow-vis/log"
11 | "github.com/pkg/browser"
12 | "io/fs"
13 | "net/http"
14 | "os"
15 | "strings"
16 | "time"
17 | )
18 |
19 | const version = "0.1.0"
20 |
21 | const Usage = `Usage: go-callflow-vis [OPTIONS] PACKAGE...
22 | Examples: (When you are in the root directory of the project)
23 | go-callflow-vis -config ./config.toml .
24 | Options:
25 | -config (Required): Path to the layer configuration file (e.g., config.toml).
26 | -cache-dir (Optional, Default: ./go_callflow_vis_cache): Directory to store cache files.
27 | -out-dir (Optional, Default: .): Output directory for the generated files.
28 | -algo (Optional, Default: cha): The algorithm used to construct the call graph. Possible values include: static, cha, rta, pta, vta.
29 | -fast (Optional): Use fast mode to generate flow, which may lose some connectivity.
30 | -query-dir (Optional, Default: ""): Directory to query from for Go packages. Uses the current directory if empty.
31 | -tests (Optional): Consider test files as entry points for the call graph.
32 | -build (Optional, Default: ""): Build flags to pass to the Go build tool. Flags should be separated with spaces.
33 | -skip-browser (Optional): Skip opening the browser automatically.
34 | -web (Optional): Serve the web visualization interface.
35 | -web-host (Optional, Default: localhost): Host to serve the web interface on.
36 | -web-port (Optional, Default: 45789): Port to serve the web interface on.
37 | -debug (Optional): Print debug information.
38 | Arguments:
39 | PACKAGE...: One or more Go packages to analyze.
40 |
41 | `
42 |
43 | var (
44 | configPath = flag.String("config", "", "(Required)Path to the layer configuration file (e.g., config.toml)")
45 | cacheDir = flag.String("cache-dir", "", "Directory to store cache files")
46 | outDir = flag.String("out-dir", ".", "Output directory for the generated files")
47 | callgraphAlgo = flag.String("algo", analysis.CallGraphTypeCha, fmt.Sprintf("The algorithm used to construct the call graph. Possible values inlcude: %q, %q, %q, %q, %q",
48 | analysis.CallGraphTypeStatic, analysis.CallGraphTypeCha, analysis.CallGraphTypeRta, analysis.CallGraphTypePta, analysis.CallGraphTypeVta))
49 | fastFlag = flag.Bool("fast", false, "Use fast mode to generate flow, which may lose some connectivity")
50 | queryDir = flag.String("query-dir", "", "Directory to query from for go packages. Current dir if empty")
51 | testFlag = flag.Bool("tests", false, "Consider tests files as entry points for call-graph")
52 | buildFlag = flag.String("build", "", "Build flags to pass to Go build tool. Separated with spaces")
53 | skipBrowser = flag.Bool("skip-browser", false, "Skip opening browser")
54 | webFlag = flag.Bool("web", false, "Serve web visualisation")
55 | webHost = flag.String("web-host", "localhost", "Host to serve the web on")
56 | webPort = flag.String("web-port", "45789", "Port to serve the web on")
57 | debugFlag = flag.Bool("debug", false, "Print debug information")
58 | showVersion = flag.Bool("version", false, "Show version")
59 | )
60 |
61 | //go:embed static
62 | var FS embed.FS
63 |
64 | func main() {
65 | flag.Parse()
66 |
67 | if *showVersion {
68 | fmt.Printf("go-callflow-vis version %s\n", version)
69 | os.Exit(0)
70 | }
71 |
72 | args := flag.Args()
73 |
74 | if flag.NArg() == 0 {
75 | _, _ = fmt.Fprintf(os.Stderr, Usage)
76 | flag.PrintDefaults()
77 | os.Exit(2)
78 | }
79 | var buildFlags []string
80 | if len(*buildFlag) > 0 {
81 | buildFlags = strings.Split(*buildFlag, " ")
82 | }
83 | if *debugFlag {
84 | log.SetLogger(*debugFlag)
85 | }
86 | conf, err := config.LoadConfig(*configPath)
87 | if err != nil {
88 | log.GetLogger().Errorf("failed to load config: %v", err)
89 | os.Exit(1)
90 | }
91 | a := analysis.NewAnalysis(
92 | conf,
93 | *cacheDir,
94 | analysis.CallgraphType(*callgraphAlgo),
95 | *testFlag,
96 | *queryDir,
97 | args,
98 | buildFlags,
99 | *fastFlag,
100 | )
101 | err = a.Run()
102 | if err != nil {
103 | log.GetLogger().Errorf("failed to run analysis: %v", err)
104 | os.Exit(1)
105 | }
106 | f := a.GetFlow()
107 | if f == nil {
108 | log.GetLogger().Errorf("failed to get flow")
109 | os.Exit(1)
110 | }
111 | out := *outDir
112 | if strings.HasSuffix(out, "/") {
113 | out = out[:len(out)-1]
114 | }
115 | log.GetLogger().Debugf("saving paths to %s", fmt.Sprintf("%s/path_out", out))
116 | err = f.SavePaths(fmt.Sprintf("%s/path_out", out), "")
117 | if err != nil {
118 | log.GetLogger().Errorf("failed to save paths: %v", err)
119 | }
120 | log.GetLogger().Debugf("saving simple callgraph to %s", fmt.Sprintf("%s/graph_out/simple_callgraph.dot", out))
121 | err = f.SaveDot(fmt.Sprintf("%s/graph_out/simple_callgraph.dot", out), true)
122 | if err != nil {
123 | log.GetLogger().Errorf("failed to save simple graph: %v", err)
124 | }
125 | log.GetLogger().Debugf("saving complete callgraph to %s", fmt.Sprintf("%s/graph_out/complete_callgraph.dot", out))
126 | err = f.SaveDot(fmt.Sprintf("%s/graph_out/complete_callgraph.dot", out), false)
127 | if err != nil {
128 | log.GetLogger().Errorf("failed to save complete graph: %v", err)
129 | }
130 | if *webFlag {
131 | gin.SetMode(gin.ReleaseMode)
132 | r := gin.Default()
133 | r.GET("/graph", func(c *gin.Context) {
134 | graph := f.GetRenderGraph()
135 | c.JSON(200, graph)
136 | })
137 | r.GET("/dot", func(c *gin.Context) {
138 | graph := f.GetDot(false)
139 | c.String(200, graph)
140 | })
141 | r.GET("/dot_simple", func(c *gin.Context) {
142 | graph := f.GetDot(true)
143 | c.String(200, graph)
144 | })
145 | renderGraph, _ := fs.Sub(FS, "static/render")
146 | r.StaticFS("/render/graph", http.FS(renderGraph))
147 | renderDot, _ := fs.Sub(FS, "static/dot")
148 | r.StaticFS("/render/dot", http.FS(renderDot))
149 | renderDotSimple, _ := fs.Sub(FS, "static/dot_simple")
150 | r.StaticFS("/render/dot_simple", http.FS(renderDotSimple))
151 | if !*skipBrowser {
152 | go openBrowser(fmt.Sprintf("http://%s:%s/render/dot/", *webHost, *webPort))
153 | }
154 | log.GetLogger().Infof("serving web on %s:%s", *webHost, *webPort)
155 | r.Run(fmt.Sprintf("%s:%s", *webHost, *webPort))
156 | }
157 | }
158 |
159 | func openBrowser(url string) {
160 | time.Sleep(time.Millisecond * 100)
161 | if err := browser.OpenURL(url); err != nil {
162 | log.GetLogger().Errorf("failed to open browser: %v", err)
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/mode/mode.go:
--------------------------------------------------------------------------------
1 | package mode
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | )
7 |
8 | type MatchType string
9 |
10 | var (
11 | MatchTypePrefix MatchType = "prefix"
12 | MatchTypeSuffix MatchType = "suffix"
13 | MatchTypeContain MatchType = "contain"
14 | MatchTypeEqual MatchType = "equal"
15 | MatchTypeRegexp MatchType = "regexp"
16 | )
17 |
18 | type Mode struct {
19 | OR bool `toml:"or" json:"or"`
20 | AND bool `toml:"and" json:"and"`
21 | Rules []Rule `toml:"rules" json:"rules"`
22 | }
23 |
24 | func (m *Mode) Match(s string) bool {
25 | if len(m.Rules) == 0 {
26 | return false
27 | }
28 | if len(m.Rules) == 1 {
29 | return m.Rules[0].Match(s)
30 | }
31 | if m.OR == m.AND {
32 | return false
33 | }
34 | if m.OR {
35 | for _, item := range m.Rules {
36 | if item.Match(s) {
37 | return true
38 | }
39 | }
40 | return false
41 | }
42 | for _, item := range m.Rules {
43 | if !item.Match(s) {
44 | return false
45 | }
46 | }
47 | return true
48 | }
49 |
50 | type Rule struct {
51 | Exclude bool `toml:"exclude" json:"exclude"`
52 | Type MatchType `toml:"type" json:"type"`
53 | Content string `toml:"content" json:"content"`
54 | }
55 |
56 | func (m *Rule) Match(s string) (result bool) {
57 | defer func() {
58 | if m.Exclude {
59 | result = !result
60 | }
61 | }()
62 | if m.Content == "" {
63 | return false
64 | }
65 | if m.Type == "" {
66 | m.Type = MatchTypeEqual
67 | }
68 | switch m.Type {
69 | case MatchTypePrefix:
70 | return strings.HasPrefix(s, m.Content)
71 | case MatchTypeSuffix:
72 | return strings.HasSuffix(s, m.Content)
73 | case MatchTypeContain:
74 | return strings.Contains(s, m.Content)
75 | case MatchTypeEqual:
76 | return s == m.Content
77 | case MatchTypeRegexp:
78 | r, err := regexp.Compile(m.Content)
79 | if err != nil {
80 | return false
81 | }
82 | return r.MatchString(s)
83 | }
84 | return false
85 | }
86 |
87 | type Set []*Mode
88 |
89 | func (ms Set) Match(s string) bool {
90 | for _, m := range ms {
91 | if m.Match(s) {
92 | return true
93 | }
94 | }
95 | return false
96 | }
97 |
--------------------------------------------------------------------------------
/render/render.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | type Graph struct {
4 | NodeSet []*Node `json:"nodes"`
5 | EdgeSet []*Edge `json:"links"`
6 | }
7 |
8 | type Node struct {
9 | ID int `json:"id"`
10 | Set int `json:"group"`
11 | Name string `json:"name"`
12 | Detail string `json:"detail"`
13 | }
14 |
15 | type Edge struct {
16 | From int `json:"source"`
17 | To int `json:"target"`
18 | Name string `json:"name"`
19 | Detail string `json:"detail"`
20 | }
21 |
--------------------------------------------------------------------------------
/static/dot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
237 |
--------------------------------------------------------------------------------
/static/dot_simple/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
236 |
--------------------------------------------------------------------------------
/static/render/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Graph Visualization
7 |
8 |
14 |
15 |
16 |
17 |
145 |
146 |
--------------------------------------------------------------------------------
/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 | )
11 |
12 | func GetFuncSimpleName(name, prefix string) string {
13 | //if strings.Contains(name, ".") {
14 | // return name[strings.LastIndex(name, ".")+1:]
15 | //}
16 | return strings.ReplaceAll(name, prefix, "")
17 | }
18 |
19 | func GetSiteSimpleName(name, prefix string) string {
20 | //if strings.Contains(name, "(") {
21 | // return name[:strings.Index(name, "(")]
22 | //}
23 | return strings.ReplaceAll(name, prefix, "")
24 | }
25 |
26 | func Escape(escape string) string {
27 | escape = strings.ReplaceAll(escape, "\\", "\\\\")
28 | escape = strings.ReplaceAll(escape, "\"", "\\\"")
29 | return escape
30 | }
31 |
32 | func WriteToFile(content, filename string) error {
33 | dir := filepath.Dir(filename)
34 | if err := os.MkdirAll(dir, 0755); err != nil {
35 | return err
36 | }
37 |
38 | file, err := os.Create(filename)
39 | if err != nil {
40 | return err
41 | }
42 | defer file.Close()
43 | _, err = file.WriteString(content)
44 | if err != nil {
45 | return err
46 | }
47 | return nil
48 | }
49 |
50 | func GetHash(o interface{}) string {
51 | jsonStr, _ := json.Marshal(o)
52 | bytes := md5.Sum(jsonStr)
53 | return hex.EncodeToString(bytes[:])
54 | }
55 |
--------------------------------------------------------------------------------