golang[ssa callgraph] 获取调用图实战

news/2024/12/22 15:05:48/

最近在拆分一个旧服务,需要从几十万行代码中,按业务功能拆分出对应代码,并部署新服务;然而,面对这种巨型服务,代码调用错综复杂,纯人力拆分需要耗费很多时间;基于此,这里借助golang自带callgraph调用图能力,帮我们找到需要拆出的代码;

package mainimport ("fmt""io/ioutil""path/filepath""sort""strings""github.com/pkg/errors""golang.org/x/tools/go/packages""golang.org/x/tools/go/ssa/ssautil""golang.org/x/tools/go/callgraph""golang.org/x/tools/go/pointer"
)// getProjectUsedCall 获取项目使用中的调用方法
func getProjectUsedCall(projectPath string) ([]string, error) {projectModule, err := parseProjectModule(projectPath)if err != nil {return nil, errors.Wrap(err, "parseProjectModule fail")}log.Debugf("projectModule: %+v", projectModule)callMap, err := parseProjectCallMap(projectPath)if err != nil {return nil, errors.Wrap(err, "parseProjectCallMap fail")}log.Debugf("callMap: %+v", callMap)srcCall := fmt.Sprintf("%v.main", projectModule)isDeleteEdgeFunc := func(caller, callee string) bool {// 非本项目调用if !strings.Contains(caller, projectModule) || !strings.Contains(callee, projectModule) {return true}// 非初始化调用if isInitCall(caller) || isInitCall(callee) {return true}// 非自我调用if caller == callee {return true}return false}// 过滤不需要的边for caller, callees := range callMap {for callee := range callees {if isDeleteEdgeFunc(caller, callee) {delete(callees, callee)}}if len(callees) == 0 {delete(callMap, caller)}}// 广度搜索图for {srcCallees := callMap[srcCall]srcSize := len(srcCallees)for srcCallee := range srcCallees {for nextCallee := range callMap[srcCallee] {callMap[srcCall][nextCallee] = true}}if srcSize == len(callMap[srcCall]) {break}}// 调用源涉及到的所有方法var callees []stringfor c := range callMap[srcCall] {callees = append(callees, c)}sort.Strings(callees)return callees, nil
}// parseProjectCallMap 解析项目调用图
func parseProjectCallMap(projectPath string) (map[string]map[string]bool, error) {projectModule, err := parseProjectModule(projectPath)if err != nil {return nil, errors.Wrap(err, "parseProjectModule fail")}log.Debugf("projectModule: %+v", projectModule)result, err := analyzeProject(projectPath)if err != nil {return nil, errors.Wrap(err, "analyzeProject fail")}log.Debugf("analyzeProject: %+v", result)// 遍历调用链路var callMap = make(map[string]map[string]bool)visitFunc := func(edge *callgraph.Edge) error {if edge == nil {return nil}// 解析调用者和被调用者caller, callee, err := parseCallEdge(edge)if err != nil {return errors.Wrap(err, "parseCallEdge fail")}// 记录调用关系if callMap[caller] == nil {callMap[caller] = make(map[string]bool)}callMap[caller][callee] = truereturn nil}err = callgraph.GraphVisitEdges(result.CallGraph, visitFunc)if err != nil {return nil, errors.Wrap(err, "GraphVisitEdges fail")}return callMap, nil
}func parseProjectModule(projectPath string) (string, error) {modFilename := filepath.Join(projectPath, "go.mod")content, err := ioutil.ReadFile(modFilename)if err != nil {return "", errors.Wrap(err, "ioutil.ReadFile fail")}lines := strings.Split(string(content), "\n")module := strings.TrimPrefix(lines[0], "module ")module = strings.TrimSpace(module)return module, nil
}func analyzeProject(projectPath string) (*pointer.Result, error) {// 生成Go Packagespkgs, err := packages.Load(&packages.Config{Mode: packages.LoadAllSyntax,Dir:  projectPath,})if err != nil {return nil, errors.Wrap(err, "packages.Load fail")}log.Debugf("pkgs: %+v", pkgs)// 生成ssa 构建编译prog, ssaPkgs := ssautil.AllPackages(pkgs, 0)prog.Build()log.Debugf("ssaPkgs: %+v", ssaPkgs)// 使用pointer生成调用链路return pointer.Analyze(&pointer.Config{Mains:          ssaPkgs,BuildCallGraph: true,})
}func parseCallEdge(edge *callgraph.Edge) (string, string, error) {const callArrow = "-->"edgeStr := fmt.Sprintf("%+v", edge)strArray := strings.Split(edgeStr, callArrow)if len(strArray) != 2 {return "", "", fmt.Errorf("invalid format: %v", edgeStr)}callerNodeStr, calleeNodeStr := strArray[0], strArray[1]caller, callee := getCallRoute(callerNodeStr), getCallRoute(calleeNodeStr)return caller, callee, nil
}func getCallRoute(nodeStr string) string {nodeStr = strings.TrimSpace(nodeStr)if strings.Contains(nodeStr, ":") {nodeStr = nodeStr[strings.Index(nodeStr, ":")+1:]}nodeStr = strings.ReplaceAll(nodeStr, "*", "")nodeStr = strings.ReplaceAll(nodeStr, "(", "")nodeStr = strings.ReplaceAll(nodeStr, ")", "")nodeStr = strings.ReplaceAll(nodeStr, "<", "")nodeStr = strings.ReplaceAll(nodeStr, ">", "")if strings.Contains(nodeStr, "$") {nodeStr = nodeStr[:strings.Index(nodeStr, "$")]}if strings.Contains(nodeStr, "#") {nodeStr = nodeStr[:strings.Index(nodeStr, "#")]}return strings.TrimSpace(nodeStr)
}func isInitCall(call string) bool {return strings.HasSuffix(call, ".init")
}


http://www.ppmy.cn/news/1230255.html

相关文章

Avoiding Row-by-Row Processing 避免逐行处理

Avoiding Row-by-Row Processing 避免逐行处理 A set-based program and row-by-row processing are not mutually exclusive: some rules do call for rowby-row processing, but these rules are the exceptions. You can have a row-by-row component within a mostly set-…

邮件|gitpushgithub报错|Lombok注解

基于 Spring Boot 搭建一个定时发送邮件的项目可以按照以下步骤进行&#xff1a; 创建一个新的 Spring Boot 项目&#xff0c;并添加所需的依赖。在 pom.xml 文件中添加以下依赖项&#xff08;根据你的需要进行调整&#xff09;&#xff1a; xml org.springframework.boot sp…

crmchat安装搭建教程文档 bug问题调试

一、安装PHP插件&#xff1a;fileinfo、redis、swoole4。 二、删除PHP对应版本中的 proc_open禁用函数。 一、设置网站运行目录public&#xff0c; 二、设置PHP版本选择纯静态。 三、可选项如有需求则开启SSL,配置SSL证书&#xff0c;开启强制https域名。 四、添加反向代理。 …

C#,怎么修改(VS)Visual Studio 2022支持的C#版本

一些文字来自于 Microsoft . &#xff08;只需要读下面的红色文字即可&#xff01;&#xff09; 1 C# 语言版本控制 最新的 C# 编译器根据项目的一个或多个目标框架确定默认语言版本。 Visual Studio 不提供用于更改值的 UI&#xff0c;但可以通过编辑 .csproj 文件来更改值。…

Apache POI(Java)

一、Apache POI介绍 Apache POI是Apache组织提供的开源的工具包&#xff08;jar包&#xff09;。大多数中小规模的应用程序开发主要依赖于Apache POI&#xff08;HSSF XSSF&#xff09;。它支持Excel 库的所有基本功能; 文本的导入和导出是它的主要特点。 我们可以使用 POI 在…

怎么在echarts图上左右滑动切换数据区间

说在前面 不管前端还是后端&#xff0c;大家或多或少都了解使用过echarts图表吧&#xff0c;很多时候我们只是需要展示指定区间的数据&#xff0c;但有时我们希望在图表上能够轻松地切换数据的展示区间&#xff0c;以便更清晰地观察特定时间段或区域的变化。在本文中&#xff0…

clickhouse分布式之弹性扩缩容的故事

现状 社区不支持喔&#xff0c;以后也不会有了。曾经尝试过&#xff0c;难道是是太难了&#xff0c;无法实现吗&#xff1f;因为他们企业版支持了&#xff0c;可能是利益相关吧&#xff0c;谁知道呢&#xff0c;毕竟开源也要赚钱&#xff0c;谁乐意一直付出没有回报呢。 社区…

【鸿蒙应用ArkTS开发系列】- 云开发入门简介

目录 概述开发流程工程概览工程模板工程结构 工程创建与配置 概述 HarmonyOS云开发是DevEco Studio新推出的功能&#xff0c;可以让您在一个项目工程中&#xff0c;使用一种语言完成端侧和云侧功能的开发。 基于AppGallery Connect Serverless构建的云侧能力&#xff0c;开发…