Skip to content

Instantly share code, notes, and snippets.

@lavn0
Created September 11, 2025 13:55
Show Gist options
  • Select an option

  • Save lavn0/22b0b10cc0d4a5a6384ff4bab32a2612 to your computer and use it in GitHub Desktop.

Select an option

Save lavn0/22b0b10cc0d4a5a6384ff4bab32a2612 to your computer and use it in GitHub Desktop.
Analyze Function to PlantUML
// C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\Microsoft.CodeAnalysis.dll
// C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\Microsoft.CodeAnalysis.CSharp.dll
// C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\System.Collections.Immutable.dll
// C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\System.Reflection.Metadata.dll
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
class Program
{
static async Task Main(string[] args)
{
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: <dll path> <method or property full name>");
return;
}
var dllPath = args[0];
var targetSymbolName = args[1];
var ignoreAssemblyNameList = new List<string>()
{
"System.Private.CoreLib",
};
var compilation = CreateCompilationFromPdb(dllPath);
var symbol = ResolveTargetSymbol(compilation, targetSymbolName);
var method = symbol switch
{
IMethodSymbol methodSymbol => methodSymbol,
IPropertySymbol { SetMethod: IMethodSymbol setMethod } => setMethod,
_ => null
};
if (method == null)
{
Console.Error.WriteLine("Error: Target must be a method or property setter.");
return;
}
var ls = new List<string>()
{
$"hide footbox",
$"Caller -> {method.ContainingSymbol.ToDisplayString()}",
$"{method.ContainingSymbol.ToDisplayString()} -> {method.ContainingSymbol.ToDisplayString()} : {method.Name} method call",
};
foreach (var line in ls)
{
Debug.WriteLine(line);
Console.WriteLine(line);
}
var results = AnalyzeMethod(method, compilation, ignoreAssemblyNameList);
await foreach (var line in results)
{
ls.Add(line);
Debug.WriteLine(line);
Console.Error.WriteLine(line);
}
}
static CSharpCompilation CreateCompilationFromPdb(string exePath)
{
var syntaxTrees = new List<SyntaxTree>();
var pdbPath = Path.ChangeExtension(exePath, ".pdb");
using var peStream = File.OpenRead(exePath);
using var peReader = new PEReader(peStream);
using var pdbStream = File.OpenRead(pdbPath);
var metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
var metadataReader = metadataReaderProvider.GetMetadataReader();
var documentPaths = new HashSet<string>();
foreach (var docHandle in metadataReader.Documents)
{
var doc = metadataReader.GetDocument(docHandle);
var name = metadataReader.GetString(doc.Name);
var fullPath = name.Replace('/', Path.DirectorySeparatorChar);
if (File.Exists(fullPath))
{
documentPaths.Add(fullPath);
}
}
foreach (var path in documentPaths)
{
var code = File.ReadAllText(path);
syntaxTrees.Add(CSharpSyntaxTree.ParseText(code, path: path));
}
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(exePath)
};
return CSharpCompilation.Create("Analysis", syntaxTrees, references);
}
public static ISymbol? ResolveTargetSymbol(Compilation compilation, string fullName)
{
var parts = fullName.Split('.');
if (parts.Length < 2) return null;
var namespaceParts = parts.Take(parts.Length - 2).ToArray();
var className = parts[parts.Length - 2];
var memberName = parts[parts.Length - 1];
INamespaceSymbol? currentNamespace = compilation.GlobalNamespace;
foreach (var nsPart in namespaceParts)
{
currentNamespace = currentNamespace?.GetNamespaceMembers().FirstOrDefault(n => n.Name == nsPart);
if (currentNamespace == null) return null;
}
var type = currentNamespace.GetTypeMembers().FirstOrDefault(t => t.Name == className);
if (type == null) return null;
var members = type.GetMembers().Where(m => m.Name == memberName).ToList();
if (members.Count == 0) return null;
foreach (var member in members)
{
if (member is IMethodSymbol ||
(member is IPropertySymbol prop && prop.SetMethod != null))
return member;
}
return null;
}
static async IAsyncEnumerable<string> AnalyzeMethod(
IMethodSymbol method,
Compilation compilation,
List<string> ignoreAssemblyNameList)
{
var visited = new HashSet<IMethodSymbol>();
await foreach (var result in TraverseFromEntryPoint(method, compilation, visited, ignoreAssemblyNameList)) yield return result;
}
static async IAsyncEnumerable<string> TraverseFromEntryPoint(
IMethodSymbol entryMethod,
Compilation compilation,
HashSet<IMethodSymbol> visited,
List<string> ignoreAssemblyNameList)
{
if (!visited.Add(entryMethod)) yield break;
yield return $"activate {entryMethod.ContainingSymbol.ToDisplayString()}";
foreach (var syntaxRef in entryMethod.DeclaringSyntaxReferences)
{
var syntax = await syntaxRef.GetSyntaxAsync();
var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree);
var nodes = syntax.DescendantNodes();
foreach (var node in nodes)
{
switch (node)
{
case AssignmentExpressionSyntax assign:
{
var targetSymbol = semanticModel.GetSymbolInfo(assign.Left).Symbol;
if (targetSymbol is IFieldSymbol field)
{
if (entryMethod.ContainingSymbol == field.ContainingSymbol)
{
yield return $"{field.ContainingSymbol.ToDisplayString()} o<-? : {field.Name} field set";
}
else
{
yield return $"{entryMethod.ContainingSymbol.ToDisplayString()} -> {field.ContainingSymbol.ToDisplayString()} : {field.Name} field set";
}
}
else if (targetSymbol is IPropertySymbol prop && prop.SetMethod != null)
{
await foreach (var ret in TraverseFromEntryPoint(prop.SetMethod, compilation, visited, ignoreAssemblyNameList)) yield return ret;
}
break;
}
case InvocationExpressionSyntax invoke:
{
var symbolInfo = semanticModel.GetSymbolInfo(invoke);
var targetMethod = symbolInfo.Symbol as IMethodSymbol
?? symbolInfo.CandidateSymbols.FirstOrDefault() as IMethodSymbol
?? null;
if (targetMethod != null)
{
if (!ignoreAssemblyNameList.Contains(targetMethod.ContainingAssembly.Name))
{
yield return $"{entryMethod.ContainingSymbol.ToDisplayString()} -> {targetMethod.ContainingSymbol.ToDisplayString()} : {targetMethod.Name}メソッド呼び出し";
await foreach (var ret in TraverseFromEntryPoint(targetMethod, compilation, visited, ignoreAssemblyNameList)) yield return ret;
}
}
else
{
yield return $"{entryMethod.ToDisplayString()} -> {invoke.ToFullString()}";
}
break;
}
case ExpressionStatementSyntax expr:
{
if (semanticModel.GetSymbolInfo(expr.Expression).Symbol is IPropertySymbol prop &&
prop.SetMethod != null)
{
await foreach (var ret in TraverseFromEntryPoint(prop.SetMethod, compilation, visited, ignoreAssemblyNameList)) yield return ret;
}
break;
}
default:
//Debugger.Break();
break;
}
}
}
yield return $"deactivate {entryMethod.ContainingSymbol.ToDisplayString()}";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment