我是靠谱客的博主 辛勤高山,最近开发中收集的这篇文章主要介绍Go 获取函数调用链,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

利用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
}

实际使用时,发现在inStdinLimits内部会花费大量时间,所以重写了pointer.Analyze()方法,再其内部进行过滤优化。

最后

以上就是辛勤高山为你收集整理的Go 获取函数调用链的全部内容,希望文章能够帮你解决Go 获取函数调用链所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部