Created
September 11, 2025 13:55
-
-
Save lavn0/22b0b10cc0d4a5a6384ff4bab32a2612 to your computer and use it in GitHub Desktop.
Analyze Function to PlantUML
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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