概述
利用golang.org/x/tools
工具函数获取函数调用链
package main
import (
"fmt"
"go/build"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
var projectPath = "your project path"
func TestPointer(m *testing.T) {
// 生成Go Packages
cfg := &packages.Config{
Mode: packages.LoadAllSyntax,
Dir: projectPath,
}
pkgs, err := packages.Load(cfg)
if err != nil {
fmt.Printf("load: %vn", err)
}
// 生成ssa
prog, ssaPkgs := ssautil.AllPackages(pkgs, 0)
prog.Build()
// 找出main package
mains, err := mainPackages(ssaPkgs)
if err != nil {
fmt.Println(err)
}
// 使用pointer生成调用链路
config := &pointer.Config{
Mains: mains,
BuildCallGraph: true,
}
result, err := pointer.Analyze(config)
if err != nil {
fmt.Println(err)
}
// 遍历调用链路
err = callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error {
// 过滤非源代码
if isSynthetic(edge) {
return nil
}
caller := edge.Caller
callee := edge.Callee
// 过滤标准库代码
if inStd(caller) || inStd(callee) {
return nil
}
// 过滤依赖包
limits := []string{"git.", "github.", "golang.", "go.", "google.", "gopkg."}
if inLimits(caller, limits) || inLimits(callee, limits) {
return nil
}
// 调用方信息
// 包路径
callerPkgPath := caller.Func.Pkg.Pkg.Path()
posCaller := prog.Fset.Position(caller.Func.Pos())
// 文件名
callerFileName := strings.TrimSuffix(filepath.Base(posCaller.Filename), ".go")
// 函数名
callerFuncName := caller.Func.Name()
// 被调用方信息
calleePkgPath := callee.Func.Pkg.Pkg.Path()
posCallee := prog.Fset.Position(callee.Func.Pos())
calleeFileName := strings.TrimSuffix(filepath.Base(posCallee.Filename), ".go")
calleeFuncName := callee.Func.Name()
// $ 排除重复调用、递归
// . 排除 -> init
if strings.Contains(callerFuncName, "$") || callerFileName == "." || strings.Contains(calleeFuncName, "$") {
return nil
}
fmt.Printf("%s/%s:%s -调用-> %s/%s:%sn", callerPkgPath, callerFileName, callerFuncName, calleePkgPath, calleeFileName, calleeFuncName)
return nil
})
if err != nil {
return
}
}
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
var mains []*ssa.Package
for _, p := range pkgs {
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
mains = append(mains, p)
}
}
if len(mains) == 0 {
return nil, fmt.Errorf("no main packages")
}
return mains, nil
}
func isSynthetic(edge *callgraph.Edge) bool {
return edge.Caller.Func.Pkg == nil || edge.Callee.Func.Synthetic != ""
}
func inStd(node *callgraph.Node) bool {
pkg, _ := build.Import(node.Func.Pkg.Pkg.Path(), "", 0)
return pkg.Goroot
}
func inLimits(node *callgraph.Node, limitPaths []string) bool {
pkgPath := node.Func.Pkg.Pkg.Path()
for _, p := range limitPaths {
if strings.HasPrefix(pkgPath, p) {
return true
}
}
return false
}
实际使用时,发现在inStd
和inLimits
内部会花费大量时间,所以重写了pointer.Analyze()
方法,再其内部进行过滤优化。
最后
以上就是辛勤高山为你收集整理的Go 获取函数调用链的全部内容,希望文章能够帮你解决Go 获取函数调用链所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复