生成传入调用图:美化打印指定方法的调用链路
修复Roslyn调用链分析的循环重复问题
我需要实现从指定方法向上回溯打印完整调用链,比如针对DisplayHelp方法,期望输出类似Shell.ctor -> GetHelp -> DisplayHelp的结构。但目前用Roslyn编写的代码输出出现循环重复,需要修正这个问题。
原代码
using Broslyn; // dotnet add package Broslyn --version 1.2.0 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.CSharp.Syntax; var csproj = "./clisharp/clisharp.csproj"; // git clone https://github.com/citrus-thunder/clisharp var methodName = "DisplayHelp"; var workspace = CSharpCompilationCapture.Build(csproj).Workspace; var project = workspace.CurrentSolution.Projects.FirstOrDefault(); var compilation = await project.GetCompilationAsync(); var targetMethod = compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()) .FirstOrDefault(method => method.Identifier.ValueText == methodName); var incomingCallGraph = new Dictionary<IMethodSymbol, List<IMethodSymbol>>(); BuildIncomingCallGraph(compilation, targetMethod, incomingCallGraph); void BuildIncomingCallGraph(Compilation compilation, MethodDeclarationSyntax targetMethodSyntax, Dictionary<IMethodSymbol, List<IMethodSymbol>> incomingCallGraph) { var targetMethodSymbol = compilation.GetSemanticModel(targetMethodSyntax.SyntaxTree).GetDeclaredSymbol(targetMethodSyntax) as IMethodSymbol; var allMethods = compilation .SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()) .Select(methodSyntax => compilation.GetSemanticModel(methodSyntax.SyntaxTree).GetDeclaredSymbol(methodSyntax) as IMethodSymbol) .Where(methodSymbol => methodSymbol != null); foreach (var _mtd in allMethods) { var invocations = SymbolFinder .FindCallersAsync(targetMethodSymbol, workspace.CurrentSolution, cancellationToken: default).Result; var callers = invocations .Select(invocation => invocation.CallingSymbol as IMethodSymbol) .Where(caller => caller != null); foreach (var caller in callers) { if (!incomingCallGraph.ContainsKey(targetMethodSymbol)) { incomingCallGraph[targetMethodSymbol] = new List<IMethodSymbol>(); } incomingCallGraph[targetMethodSymbol].Add(caller); } } }
当前错误输出
Method: DisplayHelp -> Caller: GetHelp -> Caller: DisplayHelp ->
Caller: GetHelp -> Caller: DisplayHelp -> Caller: GetHelp ->
Caller: DisplayHelp -> Caller: GetHelp -> Caller: DisplayHelp
问题根源
- 原方法在遍历所有方法时重复调用
FindCallersAsync,导致同一调用者被反复添加到列表中 - 未处理递归调用场景,也未记录已遍历方法,引发无限循环
修复后的代码
using Broslyn; // dotnet add package Broslyn --version 1.2.0 using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; var csproj = "./clisharp/clisharp.csproj"; var methodName = "DisplayHelp"; var workspace = CSharpCompilationCapture.Build(csproj).Workspace; var project = workspace.CurrentSolution.Projects.FirstOrDefault(); var compilation = await project.GetCompilationAsync(); var targetMethod = compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>()) .FirstOrDefault(method => method.Identifier.ValueText == methodName); var incomingCallGraph = new Dictionary<IMethodSymbol, List<IMethodSymbol>>(); var visitedMethods = new HashSet<IMethodSymbol>(); var targetMethodSymbol = compilation.GetSemanticModel(targetMethod.SyntaxTree).GetDeclaredSymbol(targetMethod) as IMethodSymbol; await BuildIncomingCallGraph(compilation, targetMethod, incomingCallGraph, visitedMethods); PrintCallChains(incomingCallGraph, targetMethodSymbol, new List<string>()); async Task BuildIncomingCallGraph(Compilation compilation, MethodDeclarationSyntax targetMethodSyntax, Dictionary<IMethodSymbol, List<IMethodSymbol>> incomingCallGraph, HashSet<IMethodSymbol> visited) { var targetMethodSymbol = compilation.GetSemanticModel(targetMethodSyntax.SyntaxTree).GetDeclaredSymbol(targetMethodSyntax) as IMethodSymbol; if (targetMethodSymbol == null || !visited.Add(targetMethodSymbol)) return; var invocations = await SymbolFinder.FindCallersAsync(targetMethodSymbol, workspace.CurrentSolution); var callers = invocations .Select(invocation => invocation.CallingSymbol as IMethodSymbol) .Where(caller => caller != null && !caller.Equals(targetMethodSymbol)); if (!incomingCallGraph.ContainsKey(targetMethodSymbol)) { incomingCallGraph[targetMethodSymbol] = new List<IMethodSymbol>(); } foreach (var caller in callers) { if (!incomingCallGraph[targetMethodSymbol].Contains(caller)) { incomingCallGraph[targetMethodSymbol].Add(caller); var callerSyntax = caller.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax; if (callerSyntax != null) { await BuildIncomingCallGraph(compilation, callerSyntax, incomingCallGraph, visited); } } } } void PrintCallChains(Dictionary<IMethodSymbol, List<IMethodSymbol>> callGraph, IMethodSymbol currentMethod, List<string> chain) { var methodFullName = $"{currentMethod.ContainingType.Name}.{currentMethod.Name}"; chain.Insert(0, methodFullName); if (!callGraph.ContainsKey(currentMethod) || callGraph[currentMethod].Count == 0) { Console.WriteLine(string.Join(" -> ", chain)); } else { foreach (var caller in callGraph[currentMethod]) { PrintCallChains(callGraph, caller, new List<string>(chain)); } } chain.RemoveAt(0); }
修复核心点
- 添加已访问集合:用
HashSet<IMethodSymbol>记录已处理的方法,避免重复处理和递归循环 - 单次查询调用者:移除遍历所有方法的循环,直接查询目标方法的调用者,避免重复添加
- 过滤自调用:排除方法自身调用的情况,切断递归循环
- 递归构建完整链:对每个调用者递归查找上层调用,构建完整路径
- 回溯打印调用链:通过回溯逻辑打印从入口到目标方法的完整调用路径,符合预期格式
内容的提问来源于stack exchange,提问作者ravid
相关产品推荐
相关产品推荐

