├── .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 | icon 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 | ![complete_callgraph](example/graph_out/complete_callgraph.svg) 105 | 106 | Simplified version: 107 | 108 | ![simple_callgraph](example/graph_out/simple_callgraph.svg) 109 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | icon 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 | ![complete_callgraph](example/graph_out/complete_callgraph.svg) 105 | 106 | 简化版: 107 | 108 | ![simple_callgraph](example/graph_out/simple_callgraph.svg) 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 | --------------------------------------------------------------------------------