You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生成传入调用图:美化打印指定方法的调用链路

修复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


问题根源

  1. 原方法在遍历所有方法时重复调用FindCallersAsync,导致同一调用者被反复添加到列表中
  2. 未处理递归调用场景,也未记录已遍历方法,引发无限循环

修复后的代码

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);
}

修复核心点

  1. 添加已访问集合:用HashSet<IMethodSymbol>记录已处理的方法,避免重复处理和递归循环
  2. 单次查询调用者:移除遍历所有方法的循环,直接查询目标方法的调用者,避免重复添加
  3. 过滤自调用:排除方法自身调用的情况,切断递归循环
  4. 递归构建完整链:对每个调用者递归查找上层调用,构建完整路径
  5. 回溯打印调用链:通过回溯逻辑打印从入口到目标方法的完整调用路径,符合预期格式

内容的提问来源于stack exchange,提问作者ravid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:27:12