From 9d52c5cb7d513ce1b36e94fc61490c6150cbd549 Mon Sep 17 00:00:00 2001 From: Egor Bochkarev Date: Sun, 9 Nov 2025 03:40:56 +0300 Subject: [PATCH 01/21] Cleanup code (1) Clean up the code at the syntax level. Remove old-style namespace scopes. Fix modifiers. Force use 'var' instead of explicit type declarations. Remove empty lines and add additional lines where necessary. --- Disasmo/Utils/DisassemblyPrettifier.cs | 22 +- Disasmo/Utils/IntrinsicsSourcesService.cs | 205 +-- Disasmo/Utils/LoaderAppManager.cs | 22 +- Disasmo/Utils/ProcessUtils.cs | 16 +- Disasmo/Utils/SymbolUtils.cs | 10 +- Disasmo/Utils/TextUtils.cs | 11 +- .../Analyzers/Base/BaseSuggestedAction.cs | 21 +- .../Base/CommonSuggestedActionsSource.cs | 2 +- .../CommonSuggestedActionsSourceProvider.cs | 2 +- .../Analyzers/DisasmMethodOrClassAction.cs | 25 +- src/Vsix/DisasmoPackage.cs | 49 +- src/Vsix/Utils/IdeUtils.cs | 22 +- src/Vsix/Utils/TfmVersion.cs | 5 +- src/Vsix/Utils/ValidationRuleMinMaxInt.cs | 41 +- src/Vsix/Utils/ValidationRuleStringAsInt.cs | 38 +- src/Vsix/ViewModels/FlowgraphItemViewModel.cs | 2 +- src/Vsix/ViewModels/IntrinsicsViewModel.cs | 4 + src/Vsix/ViewModels/MainViewModel.cs | 1460 +++++++++-------- src/Vsix/ViewModels/SettingsViewModel.cs | 753 +++++---- .../InversedBooleanToVisibilityConverter.cs | 17 +- src/Vsix/Views/DisasmWindow.cs | 4 +- 21 files changed, 1385 insertions(+), 1346 deletions(-) diff --git a/Disasmo/Utils/DisassemblyPrettifier.cs b/Disasmo/Utils/DisassemblyPrettifier.cs index b63612b..c10c059 100644 --- a/Disasmo/Utils/DisassemblyPrettifier.cs +++ b/Disasmo/Utils/DisassemblyPrettifier.cs @@ -31,6 +31,7 @@ public static string Prettify(string rawAsm, bool minimalComments) { if (!minimalComments) return rawAsm; + try { var lines = rawAsm.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); @@ -42,14 +43,19 @@ public static string Prettify(string rawAsm, bool minimalComments) foreach (var line in lines) { if (line.Contains("; Assembly listing for method ")) + { currentMethod = line.Remove(0, "; Assembly listing for method ".Length); + } else if (currentMethod == "") + { return rawAsm; // in case if format is changed + } var currentBlock = BlockType.Unknown; - if (line.StartsWith(";")) + { currentBlock = BlockType.Comments; + } else if (string.IsNullOrWhiteSpace(line)) { continue; @@ -69,7 +75,9 @@ public static string Prettify(string rawAsm, bool minimalComments) prevBlock = currentBlock; } else + { blocks[blocks.Count - 1].Data += line + "\n"; + } } var blocksByMethods = blocks.GroupBy(b => b.MethodName); @@ -77,9 +85,9 @@ public static string Prettify(string rawAsm, bool minimalComments) foreach (var method in blocksByMethods) { - List methodBlocks = method.ToList(); + var methodBlocks = method.ToList(); - int size = ParseMethodTotalSizes(methodBlocks); + var size = ParseMethodTotalSizes(methodBlocks); if (minimalComments) { @@ -110,11 +118,13 @@ public static string Prettify(string rawAsm, bool minimalComments) private static int ParseMethodTotalSizes(List methodBlocks) { const string marker = "; Total bytes of code "; - string lineToParse = methodBlocks.First(b => b.Data.Contains(marker)).Data; - int comma = lineToParse.IndexOf(','); - string size = comma == -1 ? + + var lineToParse = methodBlocks.First(b => b.Data.Contains(marker)).Data; + var comma = lineToParse.IndexOf(','); + var size = comma == -1 ? lineToParse.Substring(marker.Length) : lineToParse.Substring(marker.Length, lineToParse.IndexOf(',') - marker.Length); + return int.Parse(size); } diff --git a/Disasmo/Utils/IntrinsicsSourcesService.cs b/Disasmo/Utils/IntrinsicsSourcesService.cs index 5da98ef..5a4338e 100644 --- a/Disasmo/Utils/IntrinsicsSourcesService.cs +++ b/Disasmo/Utils/IntrinsicsSourcesService.cs @@ -7,119 +7,122 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public static class IntrinsicsSourcesService { - public static class IntrinsicsSourcesService + public static async Task> ParseIntrinsics(Action progressReporter) { - public static async Task> ParseIntrinsics(Action progressReporter) + var result = new List(600); + const string baseUrl = + "https://raw.githubusercontent.com/dotnet/runtime/main/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; + string[] files = { + "X86/Aes.cs", + "X86/Avx.cs", + "X86/Avx2.cs", + "X86/Bmi1.cs", + "X86/Bmi2.cs", + "X86/Fma.cs", + "X86/Lzcnt.cs", + "X86/Pclmulqdq.cs", + "X86/Popcnt.cs", + "X86/Sse.cs", + "X86/Sse2.cs", + "X86/Sse3.cs", + "X86/Sse41.cs", + "X86/Sse42.cs", + "X86/Ssse3.cs", + "X86/X86Base.cs", + "X86/X86Serialize.cs", + "X86/AvxVnni.cs", + + "X86/Avx512BW.cs", + "X86/Avx512CD.cs", + "X86/Avx512DQ.cs", + "X86/Avx512F.cs", + "X86/Avx512Vbmi.cs", + + "Arm/AdvSimd.cs", + "Arm/Aes.cs", + "Arm/ArmBase.cs", + "Arm/Crc32.cs", + "Arm/Dp.cs", + "Arm/Rdm.cs", + "Arm/Sha1.cs", + "Arm/Sha256.cs", + + "Vector64.cs", + "Vector64_1.cs", + "Vector128.cs", + "Vector128_1.cs", + "Vector256.cs", + "Vector256_1.cs", + "Vector512.cs", + "Vector512_1.cs", + }; + + foreach (var file in files) { - List result = new List(600); - const string baseUrl = - "https://raw.githubusercontent.com/dotnet/runtime/main/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; - string[] files = { - "X86/Aes.cs", - "X86/Avx.cs", - "X86/Avx2.cs", - "X86/Bmi1.cs", - "X86/Bmi2.cs", - "X86/Fma.cs", - "X86/Lzcnt.cs", - "X86/Pclmulqdq.cs", - "X86/Popcnt.cs", - "X86/Sse.cs", - "X86/Sse2.cs", - "X86/Sse3.cs", - "X86/Sse41.cs", - "X86/Sse42.cs", - "X86/Ssse3.cs", - "X86/X86Base.cs", - "X86/X86Serialize.cs", - "X86/AvxVnni.cs", - - "X86/Avx512BW.cs", - "X86/Avx512CD.cs", - "X86/Avx512DQ.cs", - "X86/Avx512F.cs", - "X86/Avx512Vbmi.cs", - - "Arm/AdvSimd.cs", - "Arm/Aes.cs", - "Arm/ArmBase.cs", - "Arm/Crc32.cs", - "Arm/Dp.cs", - "Arm/Rdm.cs", - "Arm/Sha1.cs", - "Arm/Sha256.cs", - - "Vector64.cs", - "Vector64_1.cs", - "Vector128.cs", - "Vector128_1.cs", - "Vector256.cs", - "Vector256_1.cs", - "Vector512.cs", - "Vector512_1.cs", - }; - foreach (var file in files) - { - progressReporter(file); - result.AddRange(await ParseSourceFile(baseUrl + file)); - } - return result; + progressReporter(file); + result.AddRange(await ParseSourceFile(baseUrl + file)); } - public static async Task> ParseSourceFile(string url) + return result; + } + + public static async Task> ParseSourceFile(string url) + { + var client = new HttpClient(); + var content = await client.GetStringAsync(url); + var result = new List(); + using var workspace = new AdhocWorkspace(); + + var proj = + workspace + .AddProject("ParseIntrinsics", LanguageNames.CSharp) + .WithMetadataReferences(new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); + var doc = proj.AddDocument("foo", SourceText.From(content)); + var compilation = await doc.Project.GetCompilationAsync(); + var root = await doc.GetSyntaxRootAsync(); + var model = compilation.GetSemanticModel(root.SyntaxTree); + var methods = root.DescendantNodes().OfType().ToList(); + + foreach (var method in methods) { - var client = new HttpClient(); - string content = await client.GetStringAsync(url); - var result = new List(); - using (var workspace = new AdhocWorkspace()) + var tokens = method.ChildTokens().ToArray(); + if (tokens.Length > 0) { - Project proj = workspace.AddProject("ParseIntrinsics", LanguageNames.CSharp) - .WithMetadataReferences(new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); - Document doc = proj.AddDocument("foo", SourceText.From(content)); - Compilation compilation = await doc.Project.GetCompilationAsync(); - SyntaxNode root = await doc.GetSyntaxRootAsync(); - var model = compilation.GetSemanticModel(root.SyntaxTree); - var methods = root.DescendantNodes().OfType().ToList(); - - foreach (var method in methods) - { - var tokens = method.ChildTokens().ToArray(); - if (tokens.Length > 0) - { - var trivia = tokens.FirstOrDefault().LeadingTrivia; - string comments = string.Join("\n", - trivia.ToString().Split('\n').Select(i => i.Trim(' ', '\r', '\t')) - .Where(i => !string.IsNullOrWhiteSpace(i))); - var symbol = model.GetDeclaredSymbol(method); - var methodName = symbol.ToString() - .Replace("System.Runtime.Intrinsics.X86.", "") - .Replace("System.Runtime.Intrinsics.Arm.", "") - .Replace("System.Runtime.Intrinsics.", ""); - - var returnType = method.ReturnType.ToString(); - result.Add(new IntrinsicsInfo { Method = returnType + " " + methodName, Comments = comments }); - } - } - } + var trivia = tokens.FirstOrDefault().LeadingTrivia; + var comments = string.Join("\n", + trivia + .ToString().Split('\n').Select(i => i.Trim(' ', '\r', '\t')) + .Where(i => !string.IsNullOrWhiteSpace(i))); + var symbol = model.GetDeclaredSymbol(method); + var methodName = symbol.ToString() + .Replace("System.Runtime.Intrinsics.X86.", "") + .Replace("System.Runtime.Intrinsics.Arm.", "") + .Replace("System.Runtime.Intrinsics.", ""); - return result; + var returnType = method.ReturnType.ToString(); + result.Add(new IntrinsicsInfo { Method = returnType + " " + methodName, Comments = comments }); + } } - } + return result; + } +} - public class IntrinsicsInfo - { - public string Comments { get; set; } - public string Method { get; set; } - public bool Contains(string str) - { - return Comments.ToLowerInvariant().Contains(str.ToLowerInvariant()) || - Method.ToLowerInvariant().Contains(str.ToLowerInvariant()); - } +public class IntrinsicsInfo +{ + public string Comments { get; set; } + public string Method { get; set; } - public override string ToString() => Method; + public bool Contains(string str) + { + return Comments.ToLowerInvariant().Contains(str.ToLowerInvariant()) || + Method.ToLowerInvariant().Contains(str.ToLowerInvariant()); } + + public override string ToString() => Method; } diff --git a/Disasmo/Utils/LoaderAppManager.cs b/Disasmo/Utils/LoaderAppManager.cs index 99ea4e4..db376d1 100644 --- a/Disasmo/Utils/LoaderAppManager.cs +++ b/Disasmo/Utils/LoaderAppManager.cs @@ -14,15 +14,15 @@ public static class LoaderAppManager private static async Task GetPathToLoader(string tf, Version addinVersion, CancellationToken ct) { - ProcessResult dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: ct); + var dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: ct); UserLogger.Log($"dotnet --version: {dotnetVersion.Output} ({dotnetVersion.Error})"); - string version = dotnetVersion.Output.Trim(); + var version = dotnetVersion.Output.Trim(); if (!char.IsDigit(version[0])) { // Something went wrong, use a random to proceed version = Guid.NewGuid().ToString("N"); } - string folderName = $"{addinVersion}_{tf}_{version}"; + var folderName = $"{addinVersion}_{tf}_{version}"; UserLogger.Log($"LoaderAppManager.GetPathToLoader: {folderName}"); return Path.Combine(Path.GetTempPath(), DisasmoLoaderName, folderName); } @@ -43,17 +43,15 @@ public static async Task InitLoaderAndCopyTo(string tf, string dest, Action RunProcess( - string path, + string path, string args = "", Dictionary envVars = null, string workingDir = null, @@ -53,11 +53,13 @@ public static async Task RunProcess( logger.AppendLine(e.Data); loggerForErrors.AppendLine(e.Data); }; + process.OutputDataReceived += (sender, e) => { outputLogger?.Invoke(false, e.Data + "\n"); logger.AppendLine(e.Data); }; + process.BeginOutputReadLine(); process.BeginErrorReadLine(); @@ -78,16 +80,18 @@ public static async Task RunProcess( } } - public static Task WaitForExitAsync(this Process process, - CancellationToken cancellationToken = default(CancellationToken)) + public static Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) { if (process.HasExited) return Task.CompletedTask; + var tcs = new TaskCompletionSource(); process.EnableRaisingEvents = true; process.Exited += (sender, args) => tcs.TrySetResult(null); - if (cancellationToken != default(CancellationToken)) + + if (cancellationToken != default) cancellationToken.Register(() => tcs.TrySetCanceled()); + return process.HasExited ? Task.CompletedTask : tcs.Task; } @@ -95,6 +99,7 @@ private static void KillProccessSafe(this Process process) { if (process == null) return; + try { if (!process.HasExited) @@ -111,9 +116,10 @@ private static string DumpEnvVars(Dictionary envVars) if (envVars == null) return ""; - string envVar = ""; + var envVar = ""; foreach (var ev in envVars) envVar += ev.Key + "=" + ev.Value + "\n"; + return envVar; } } diff --git a/Disasmo/Utils/SymbolUtils.cs b/Disasmo/Utils/SymbolUtils.cs index be9f2f2..f0b50ff 100644 --- a/Disasmo/Utils/SymbolUtils.cs +++ b/Disasmo/Utils/SymbolUtils.cs @@ -10,16 +10,17 @@ public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) string hostType; string methodName; - string prefix = ""; - ISymbol containingType = symbol as ITypeSymbol ?? symbol.ContainingType; + var prefix = ""; + var containingType = symbol as ITypeSymbol ?? symbol.ContainingType; // match all for nested types if (containingType.ContainingType is { }) + { prefix = "*"; - + } else { - INamespaceSymbol ns = containingType.ContainingNamespace; + var ns = containingType.ContainingNamespace; while (ns?.Name is { Length: > 0 } containingNamespace) { prefix = containingNamespace + "." + prefix; @@ -67,6 +68,7 @@ public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) hostType = symbol.ToString(); methodName = "*"; } + return new DisasmoSymbolInfo(target, hostType, methodName); } } diff --git a/Disasmo/Utils/TextUtils.cs b/Disasmo/Utils/TextUtils.cs index e9221d0..c290b73 100644 --- a/Disasmo/Utils/TextUtils.cs +++ b/Disasmo/Utils/TextUtils.cs @@ -5,18 +5,19 @@ namespace Disasmo; public static class TextUtils { - public static void SaveEmbeddedResourceTo(string resource, string folder, + public static void SaveEmbeddedResourceTo( + string resource, + string folder, Func contentProcessor = null) { - string filePath = Path.Combine(folder, resource.Replace("_template", "")); + var filePath = Path.Combine(folder, resource.Replace("_template", "")); if (File.Exists(filePath)) return; - using Stream stream = typeof(TextUtils).Assembly - .GetManifestResourceStream("Disasmo.Resources." + resource); + using Stream stream = typeof(TextUtils).Assembly.GetManifestResourceStream("Disasmo.Resources." + resource); using StreamReader reader = new StreamReader(stream); var content = reader.ReadToEnd(); - File.WriteAllText(filePath, contentProcessor is { } ? contentProcessor(content) : content); + File.WriteAllText(filePath, contentProcessor != null ? contentProcessor(content) : content); } public static string NormalizeLineEndings(this string text) => diff --git a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs index f30057a..7056d52 100644 --- a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs +++ b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs @@ -13,7 +13,7 @@ namespace Disasmo; -internal abstract class BaseSuggestedAction : ISuggestedAction +public abstract class BaseSuggestedAction : ISuggestedAction { protected readonly CommonSuggestedActionsSource _actionsSource; @@ -46,18 +46,31 @@ public async Task ValidateAsync(CancellationToken cancellationToken) } public int LastTokenPos { get; set; } + public Document LastDocument { get; set; } - protected abstract Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken); + public abstract string DisplayText { get; } + public string IconAutomationText => "Disamo"; - ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CSLightswitch; + public string InputGestureText => null; + public bool HasActionSets => false; - public Task> GetActionSetsAsync(CancellationToken cancellationToken) => null; + public bool HasPreview => false; + + ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CSLightswitch; + + protected abstract Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken); + + public Task> GetActionSetsAsync(CancellationToken cancellationToken) => null; + public Task GetPreviewAsync(CancellationToken cancellationToken) => Task.FromResult(null); + public void Dispose() { } + public abstract void Invoke(CancellationToken cancellationToken); + public bool TryGetTelemetryId(out Guid telemetryId) { telemetryId = Guid.Empty; diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs index 004e367..bc2e1bd 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs @@ -11,7 +11,7 @@ namespace Disasmo; -internal class CommonSuggestedActionsSource : ISuggestedActionsSource +public class CommonSuggestedActionsSource : ISuggestedActionsSource { private BaseSuggestedAction[] _baseActions; diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs index 5c6dd0b..657a248 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs @@ -9,7 +9,7 @@ namespace Disasmo.Analyzers; [Export(typeof(ISuggestedActionsSourceProvider))] [Name("Disasmo Suggested Actions")] [ContentType("text")] -internal class CommonSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider +public class CommonSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider { public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { diff --git a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs index b890a62..448dbbd 100644 --- a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs +++ b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs @@ -9,7 +9,7 @@ namespace Disasmo; -internal class DisasmMethodOrClassAction : BaseSuggestedAction +public class DisasmMethodOrClassAction : BaseSuggestedAction { public DisasmMethodOrClassAction(CommonSuggestedActionsSource actionsSource) : base(actionsSource) {} @@ -17,13 +17,13 @@ public override async void Invoke(CancellationToken cancellationToken) { try { - if (LastDocument != null) + if (LastDocument == null) + return; + + var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); + if (window?.ViewModel is {} viewModel) { - var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); - if (window?.ViewModel is {} viewModel) - { - viewModel.RunOperationAsync(await GetSymbol(LastDocument, LastTokenPos, cancellationToken), LastDocument.Project); - } + viewModel.RunOperationAsync(await GetSymbol(LastDocument, LastTokenPos, cancellationToken), LastDocument.Project); } } catch (Exception exc) @@ -39,7 +39,7 @@ protected override async Task IsValidSymbol(Document document, int tokenPo if (Settings.Default.DisableLightBulb) return false; - SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); if (semanticModel == null) return false; @@ -101,13 +101,13 @@ public static async Task GetSymbolStatic(Document doc, int tok, Cancell { try { - SemanticModel semanticModel = await doc.GetSemanticModelAsync(ct); + var semanticModel = await doc.GetSemanticModelAsync(ct); if (semanticModel == null) return null; - SyntaxNode syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(ct); - SyntaxToken token = syntaxTree.FindToken(tok); - SyntaxNode parent = token.Parent; + var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(ct); + var token = syntaxTree.FindToken(tok); + var parent = token.Parent; if (parent == null) return null; @@ -145,6 +145,7 @@ public override string DisplayText { if (string.IsNullOrWhiteSpace(DisasmoPackage.HotKey)) return "Disasm this"; + return $"Disasm this ({DisasmoPackage.HotKey})"; } } diff --git a/src/Vsix/DisasmoPackage.cs b/src/Vsix/DisasmoPackage.cs index 1fd1c23..387a40c 100644 --- a/src/Vsix/DisasmoPackage.cs +++ b/src/Vsix/DisasmoPackage.cs @@ -34,30 +34,31 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke try { Current = this; - await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var disasmoCmd = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); - if (disasmoCmd != null) + if (disasmoCmd == null) + return; + + var binding = ""; + if (disasmoCmd.Bindings is object[] bindingArray) { - string binding = ""; - if (disasmoCmd.Bindings is object[] bindingArray) + var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); + // prefer Text Editor over Global + var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); + if (bindingPair != null && bindingPair.Contains("::")) { - var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); - // prefer Text Editor over Global - var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); - if (bindingPair != null && bindingPair.Contains("::")) - binding = bindingPair.Substring(bindingPair.IndexOf("::", StringComparison.Ordinal) + 2); + binding = bindingPair.Substring(bindingPair.IndexOf("::", StringComparison.Ordinal) + 2); } - else + } + else + { + if (disasmoCmd.Bindings is string bindingStr && bindingStr.Contains("::")) { - if (disasmoCmd.Bindings is string bindingStr) - { - if (bindingStr.Contains("::")) - binding = bindingStr.Substring(bindingStr.IndexOf("::", StringComparison.Ordinal) + 2); - } + binding = bindingStr.Substring(bindingStr.IndexOf("::", StringComparison.Ordinal) + 2); } - HotKey = binding; } + HotKey = binding; } catch { @@ -76,9 +77,9 @@ public static async Task GetLatestVersionOnline() // is there an API to do it? var client = new HttpClient(); - string str = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); - string marker = "extensions/egorbogatov/disasmo/"; - int index = str.IndexOf(marker); + var str = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); + var marker = "extensions/egorbogatov/disasmo/"; + var index = str.IndexOf(marker); return Version.Parse(str.Substring(index + marker.Length, str.IndexOf('/', index + marker.Length) - index - marker.Length)); } catch { return new Version(0, 0); } @@ -121,7 +122,6 @@ public class DisasmoCommandBinding internal CommandBindingDefinition disasmoCommandBinding; } - [Export(typeof(ICommandHandler))] [ContentType("text")] [Name(nameof(DisasmoCommandHandler))] @@ -129,10 +129,7 @@ public class DisasmoCommandHandler : ICommandHandler { public string DisplayName => "Disasmo this"; - public CommandState GetCommandState(DisasmoCommandArgs args) - { - return CommandState.Available; - } + public CommandState GetCommandState(DisasmoCommandArgs args) => CommandState.Available; public int GetCaretPosition(ITextView view) { @@ -166,7 +163,9 @@ async void CallBack(object _) MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); return; } + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + var symbol = await DisasmMethodOrClassAction.GetSymbolStatic(document, pos, default, true); var window = await IdeUtils.ShowWindowAsync(true, default); if (window?.ViewModel is {} viewModel) @@ -179,9 +178,11 @@ async void CallBack(object _) Debug.WriteLine(exc); } } + ThreadPool.QueueUserWorkItem(CallBack); } } + return true; } } \ No newline at end of file diff --git a/src/Vsix/Utils/IdeUtils.cs b/src/Vsix/Utils/IdeUtils.cs index 517bfd5..188c292 100644 --- a/src/Vsix/Utils/IdeUtils.cs +++ b/src/Vsix/Utils/IdeUtils.cs @@ -35,8 +35,10 @@ public static Project GetActiveProject(this DTE dte, string filePath) var activeSolutionProjects = dte.ActiveSolutionProjects as Array; if (activeSolutionProjects != null && activeSolutionProjects.Length > 0) return activeSolutionProjects.GetValue(0) as Project; + return null; } + public static void SaveActiveDocument(this DTE dte) { try @@ -71,16 +73,18 @@ public static async Task ShowWindowAsync(bool tryTwice, CancellationToken MessageBox.Show("DisasmoPackage is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); return null; } + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(DisasmWindow), 0, create: true, cancellationToken: cancellationToken); + if (tryTwice) { await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // no idea why I have to call it twice, it doesn't work if I do it only once on the first usage - window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(T), 0, create: true, - cancellationToken: cancellationToken); + window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(T), 0, create: true, cancellationToken: cancellationToken); await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); } + return window as T; } catch @@ -98,8 +102,8 @@ public static void RunDiffTools(string contentLeft, string contentRight) var diffDir = Path.Combine(tempPath, "Disasmo_diffs_" + Guid.NewGuid().ToString("N").Substring(0, 10)); Directory.CreateDirectory(diffDir); - string tmpFileLeft = Path.Combine(diffDir, "previous.asm"); - string tmpFileRight = Path.Combine(diffDir, "current.asm"); + var tmpFileLeft = Path.Combine(diffDir, "previous.asm"); + var tmpFileRight = Path.Combine(diffDir, "current.asm"); File.WriteAllText(tmpFileLeft, contentLeft.NormalizeLineEndings()); File.WriteAllText(tmpFileRight, contentRight.NormalizeLineEndings()); @@ -107,7 +111,7 @@ public static void RunDiffTools(string contentLeft, string contentRight) try { // Copied from https://github.com/madskristensen/FileDiffer/blob/main/src/Commands/DiffFilesCommand.cs#L48-L56 (c) madskristensen - object args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; + var args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", 256, ref args, ref args); } catch (Exception exc) @@ -147,7 +151,7 @@ public static async Task GetProjectProperties(UnconfiguredPr { // it will throw "Release config was not found" to the Output if there is no such config in the project projectConfiguration ??= await unconfiguredProject.Services.ProjectConfigurationsService.GetProjectConfigurationAsync("Release"); - ConfiguredProject configuredProject = await unconfiguredProject.LoadConfiguredProjectAsync(projectConfiguration); + var configuredProject = await unconfiguredProject.LoadConfiguredProjectAsync(projectConfiguration); return configuredProject.Services.ProjectPropertiesProvider.GetCommonProperties(); } catch (Exception exc) @@ -181,10 +185,10 @@ public static async void OpenInVSCode(string output) try { - string file = Path.GetTempFileName() + ".txt"; + var file = Path.GetTempFileName() + ".txt"; File.WriteAllText(file, output.NormalizeLineEndings()); - ProcessStartInfo psi = new ProcessStartInfo(file); + var psi = new ProcessStartInfo(file); psi.Verb = "open"; psi.UseShellExecute = true; Process.Start(psi); @@ -204,7 +208,7 @@ public static void OpenInVS(string output) { // Let's try .asm file and hope VS will be able to apply some highlighting // even for JitDump... - string file = Path.GetTempFileName() + ".asm"; + var file = Path.GetTempFileName() + ".asm"; File.WriteAllText(file, output.NormalizeLineEndings()); DTE().ItemOperations.OpenFile(file); diff --git a/src/Vsix/Utils/TfmVersion.cs b/src/Vsix/Utils/TfmVersion.cs index cbe2d49..af998a2 100644 --- a/src/Vsix/Utils/TfmVersion.cs +++ b/src/Vsix/Utils/TfmVersion.cs @@ -30,10 +30,7 @@ public int CompareTo(TfmVersion other) return Nullable.Compare(Patch, other.Patch); } - public override string ToString() - { - return $"{Moniker} {Major}.{Minor}.{Patch}"; - } + public override string ToString() => $"{Moniker} {Major}.{Minor}.{Patch}"; public static TfmVersion Parse(string tfm) { diff --git a/src/Vsix/Utils/ValidationRuleMinMaxInt.cs b/src/Vsix/Utils/ValidationRuleMinMaxInt.cs index e892255..dce0990 100644 --- a/src/Vsix/Utils/ValidationRuleMinMaxInt.cs +++ b/src/Vsix/Utils/ValidationRuleMinMaxInt.cs @@ -1,30 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; using System.Windows.Controls; -namespace Disasmo.Utils -{ - internal class ValidationRuleMinMaxInt : ValidationRuleStringAsInt - { - public int Min { get; set; } - public int Max { get; set; } +namespace Disasmo.Utils; - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - ValidationResult result = ValidateInternal(value, cultureInfo, out int parsedValue); +public class ValidationRuleMinMaxInt : ValidationRuleStringAsInt +{ + public int Min { get; set; } + public int Max { get; set; } - if (result.IsValid) { - if (parsedValue < Min) - result = new ValidationResult(false, $"Please enter a value greater than {Min}!"); - else if (parsedValue > Max) - result = new ValidationResult(false, $"Please enter a value less than {Max}!"); - } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + var result = ValidateInternal(value, cultureInfo, out int parsedValue); - return result; + if (result.IsValid) + { + if (parsedValue < Min) + result = new ValidationResult(false, $"Please enter a value greater than {Min}!"); + else if (parsedValue > Max) + result = new ValidationResult(false, $"Please enter a value less than {Max}!"); } + + return result; } -} +} \ No newline at end of file diff --git a/src/Vsix/Utils/ValidationRuleStringAsInt.cs b/src/Vsix/Utils/ValidationRuleStringAsInt.cs index b6a7f31..5f982a4 100644 --- a/src/Vsix/Utils/ValidationRuleStringAsInt.cs +++ b/src/Vsix/Utils/ValidationRuleStringAsInt.cs @@ -1,31 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; using System.Windows.Controls; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public class ValidationRuleStringAsInt : ValidationRule { - internal class ValidationRuleStringAsInt : ValidationRule - { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - return ValidateInternal(value, cultureInfo, out _); - } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) => + ValidateInternal(value, cultureInfo, out _); - protected ValidationResult ValidateInternal(object value, CultureInfo cultureInfo, out int parsedValue) - { - parsedValue = 0; + protected ValidationResult ValidateInternal(object value, CultureInfo cultureInfo, out int parsedValue) + { + parsedValue = 0; - if (value is int) - return ValidationResult.ValidResult; + if (value is int) + return ValidationResult.ValidResult; - if (value is string valueAsString) - return int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue) ? ValidationResult.ValidResult : new ValidationResult(false, "Please enter a valid number!"); + if (value is string valueAsString) + return int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue) ? ValidationResult.ValidResult : new ValidationResult(false, "Please enter a valid number!"); - return new ValidationResult(false, "Please enter a valid number!"); - } + return new ValidationResult(false, "Please enter a valid number!"); } -} +} \ No newline at end of file diff --git a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs index 04fc8b0..520031d 100644 --- a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs +++ b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs @@ -58,7 +58,7 @@ public async Task LoadImageAsync(CancellationToken ct) try { var img = DotFileUrl + ".png"; - string dotExeArgs = $"-Tpng -o\"{img}\" -Kdot \"{DotFileUrl}\""; + var dotExeArgs = $"-Tpng -o\"{img}\" -Kdot \"{DotFileUrl}\""; await ProcessUtils.RunProcess(_settingsView.GraphvisDotPath, dotExeArgs, cancellationToken: ct); ImageUrl = img; } diff --git a/src/Vsix/ViewModels/IntrinsicsViewModel.cs b/src/Vsix/ViewModels/IntrinsicsViewModel.cs index b1345c1..7b1bd1f 100644 --- a/src/Vsix/ViewModels/IntrinsicsViewModel.cs +++ b/src/Vsix/ViewModels/IntrinsicsViewModel.cs @@ -54,9 +54,13 @@ public string Input Set(ref _input, value); StartDownloadSources(); if (_intrinsics == null || string.IsNullOrWhiteSpace(value) || value.Length < 3) + { Suggestions = null; + } else + { Suggestions = _intrinsics.Where(i => i.Contains(value)).Take(15).ToList(); + } } } diff --git a/src/Vsix/ViewModels/MainViewModel.cs b/src/Vsix/ViewModels/MainViewModel.cs index c42b49f..9a98026 100644 --- a/src/Vsix/ViewModels/MainViewModel.cs +++ b/src/Vsix/ViewModels/MainViewModel.cs @@ -17,926 +17,942 @@ using System.Collections.ObjectModel; using CAProject = Microsoft.CodeAnalysis.Project; -namespace Disasmo +namespace Disasmo; + +public class MainViewModel : ViewModelBase { - public class MainViewModel : ViewModelBase + private string _output; + private string _previousOutput; + private string _loadingStatus; + private string _stopwatchStatus; + private string[] _jitDumpPhases; + private bool _isLoading; + private ISymbol _currentSymbol; + private CAProject _currentProject; + private bool _success; + private string _currentProjectPath; + private string _currentTf; + private string _fgPngPath; + private string DisasmoOutDir = ""; + private ObservableCollection _fgPhases = new(); + private FlowgraphItemViewModel _selectedPhase; + + // let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) + private string DisasmoFolder => "Disasmo-v" + DisasmoPackage.Current?.GetCurrentVersion(); + + public SettingsViewModel SettingsVm { get; } = new(); + public IntrinsicsViewModel IntrinsicsVm { get; } = new(); + + public event Action MainPageRequested; + + public string[] JitDumpPhases { - private string _output; - private string _previousOutput; - private string _loadingStatus; - private string _stopwatchStatus; - private string[] _jitDumpPhases; - private bool _isLoading; - private ISymbol _currentSymbol; - private CAProject _currentProject; - private bool _success; - private string _currentProjectPath; - private string _currentTf; - private string _fgPngPath; - private string DisasmoOutDir = ""; - private ObservableCollection _fgPhases = new(); - private FlowgraphItemViewModel _selectedPhase; - - // let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) - private string DisasmoFolder => "Disasmo-v" + DisasmoPackage.Current?.GetCurrentVersion(); - - public SettingsViewModel SettingsVm { get; } = new(); - public IntrinsicsViewModel IntrinsicsVm { get; } = new(); - - public event Action MainPageRequested; - - public string[] JitDumpPhases - { - get => _jitDumpPhases; - set => Set(ref _jitDumpPhases, value); - } + get => _jitDumpPhases; + set => Set(ref _jitDumpPhases, value); + } - public string Output + public string Output + { + get => _output; + set { - get => _output; - set - { - if (!string.IsNullOrWhiteSpace(_output)) - PreviousOutput = _output; - Set(ref _output, value); - - const string phasePrefix = "*************** Starting PHASE "; - JitDumpPhases = (Output ?? "") - .Split('\n') - .Where(l => l.StartsWith(phasePrefix)) - .Select(i => i.Replace(phasePrefix, "")) - .ToArray(); - } + if (!string.IsNullOrWhiteSpace(_output)) + PreviousOutput = _output; + Set(ref _output, value); + + const string phasePrefix = "*************** Starting PHASE "; + JitDumpPhases = (Output ?? "") + .Split('\n') + .Where(l => l.StartsWith(phasePrefix)) + .Select(i => i.Replace(phasePrefix, "")) + .ToArray(); } + } - public string PreviousOutput - { - get => _previousOutput; - set => Set(ref _previousOutput, value); - } + public string PreviousOutput + { + get => _previousOutput; + set => Set(ref _previousOutput, value); + } - public string LoadingStatus - { - get => _loadingStatus; - set => Set(ref _loadingStatus, value); - } + public string LoadingStatus + { + get => _loadingStatus; + set => Set(ref _loadingStatus, value); + } - public CancellationTokenSource UserCts { get; set; } + public CancellationTokenSource UserCts { get; set; } - public CancellationToken UserCt => UserCts?.Token ?? default; + public CancellationToken UserCt => UserCts?.Token ?? default; - public void ThrowIfCanceled() - { - if (UserCts?.IsCancellationRequested == true) - throw new OperationCanceledException(); - } + public void ThrowIfCanceled() + { + if (UserCts?.IsCancellationRequested == true) + throw new OperationCanceledException(); + } - public ICommand CancelCommand => new RelayCommand(() => - { - try { UserCts?.Cancel(); } catch { } - }); + public ICommand CancelCommand => new RelayCommand(() => + { + try { UserCts?.Cancel(); } catch { } + }); - public string DefaultHotKey => DisasmoPackage.HotKey; + public string DefaultHotKey => DisasmoPackage.HotKey; - public bool Success - { - get => _success; - set => Set(ref _success, value); - } + public bool Success + { + get => _success; + set => Set(ref _success, value); + } - public bool IsLoading + public bool IsLoading + { + get => _isLoading; + set { - get => _isLoading; - set + if (!_isLoading && value) { - if (!_isLoading && value) - { - UserCts = new CancellationTokenSource(); - } - Set(ref _isLoading, value); + UserCts = new CancellationTokenSource(); } + Set(ref _isLoading, value); } + } - public string StopwatchStatus - { - get => _stopwatchStatus; - set => Set(ref _stopwatchStatus, value); - } + public string StopwatchStatus + { + get => _stopwatchStatus; + set => Set(ref _stopwatchStatus, value); + } - public string FgPngPath - { - get => _fgPngPath; - set => Set(ref _fgPngPath, value); - } + public string FgPngPath + { + get => _fgPngPath; + set => Set(ref _fgPngPath, value); + } - public ICommand RefreshCommand => new RelayCommand(() => RunOperationAsync(_currentSymbol, _currentProject)); + public ICommand RefreshCommand => new RelayCommand(() => RunOperationAsync(_currentSymbol, _currentProject)); - public ICommand RunDiffWithPrevious => new RelayCommand(() => IdeUtils.RunDiffTools(PreviousOutput, Output)); + public ICommand RunDiffWithPrevious => new RelayCommand(() => IdeUtils.RunDiffTools(PreviousOutput, Output)); - public ICommand OpenInVSCode => new RelayCommand(() => IdeUtils.OpenInVSCode(Output)); + public ICommand OpenInVSCode => new RelayCommand(() => IdeUtils.OpenInVSCode(Output)); - public ICommand OpenInVS => new RelayCommand(() => IdeUtils.OpenInVS(Output)); + public ICommand OpenInVS => new RelayCommand(() => IdeUtils.OpenInVS(Output)); - public ObservableCollection FgPhases - { - get => _fgPhases; - set => Set(ref _fgPhases, value); - } + public ObservableCollection FgPhases + { + get => _fgPhases; + set => Set(ref _fgPhases, value); + } - public FlowgraphItemViewModel SelectedPhase + public FlowgraphItemViewModel SelectedPhase + { + get => _selectedPhase; + set { - get => _selectedPhase; - set - { - Set(ref _selectedPhase, value); - _selectedPhase?.LoadImageAsync(UserCt); - } + Set(ref _selectedPhase, value); + _selectedPhase?.LoadImageAsync(UserCt); } + } - public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) + public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) + { + try { - try - { - if (_currentSymbol == null || string.IsNullOrWhiteSpace(_currentProjectPath)) - return; + if (_currentSymbol == null || string.IsNullOrWhiteSpace(_currentProjectPath)) + return; - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - Success = false; - IsLoading = true; - FgPngPath = null; - LoadingStatus = "Loading..."; + Success = false; + IsLoading = true; + FgPngPath = null; + LoadingStatus = "Loading..."; - string dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) - dstFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), DisasmoOutDir); + var dstFolder = DisasmoOutDir; + if (!Path.IsPathRooted(dstFolder)) + dstFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), DisasmoOutDir); - // TODO: respect AssemblyName property (if it doesn't match csproj name) - string fileName = Path.GetFileNameWithoutExtension(_currentProjectPath); + // TODO: respect AssemblyName property (if it doesn't match csproj name) + var fileName = Path.GetFileNameWithoutExtension(_currentProjectPath); - try + try + { + if (projectProperties != null) { - if (projectProperties != null) + var customAsmName = await projectProperties.GetEvaluatedPropertyValueAsync("AssemblyName"); + if (!string.IsNullOrWhiteSpace(customAsmName)) { - string customAsmName = await projectProperties.GetEvaluatedPropertyValueAsync("AssemblyName"); - if (!string.IsNullOrWhiteSpace(customAsmName)) - { - fileName = customAsmName; - } + fileName = customAsmName; } } - catch { } + } + catch { } - var envVars = new Dictionary(); + var envVars = new Dictionary(); - if (!SettingsVm.RunAppMode && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected) - { - var addinVersion = DisasmoPackage.Current.GetCurrentVersion(); - await LoaderAppManager.InitLoaderAndCopyTo(_currentTf, dstFolder, log => { /*TODO: update UI*/ }, addinVersion, UserCt); - } + if (!SettingsVm.RunAppMode && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected) + { + var addinVersion = DisasmoPackage.Current.GetCurrentVersion(); + await LoaderAppManager.InitLoaderAndCopyTo(_currentTf, dstFolder, log => { /*TODO: update UI*/ }, addinVersion, UserCt); + } - if (SettingsVm.JitDumpInsteadOfDisasm) - envVars["DOTNET_JitDump"] = symbolInfo.Target; - else if (SettingsVm.PrintInlinees) - envVars["DOTNET_JitPrintInlinedMethods"] = symbolInfo.Target; - else - envVars["DOTNET_JitDisasm"] = symbolInfo.Target; + if (SettingsVm.JitDumpInsteadOfDisasm) + { + envVars["DOTNET_JitDump"] = symbolInfo.Target; + } + else if (SettingsVm.PrintInlinees) + { + envVars["DOTNET_JitPrintInlinedMethods"] = symbolInfo.Target; + } + else + { + envVars["DOTNET_JitDisasm"] = symbolInfo.Target; + } - if (!string.IsNullOrWhiteSpace(SettingsVm.SelectedCustomJit) && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && - !SettingsVm.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && SettingsVm.UseCustomRuntime) - { - envVars["DOTNET_AltJitName"] = SettingsVm.SelectedCustomJit; - envVars["DOTNET_AltJit"] = symbolInfo.Target; - } + if (!string.IsNullOrWhiteSpace(SettingsVm.SelectedCustomJit) && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && + !SettingsVm.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && SettingsVm.UseCustomRuntime) + { + envVars["DOTNET_AltJitName"] = SettingsVm.SelectedCustomJit; + envVars["DOTNET_AltJit"] = symbolInfo.Target; + } - envVars["DOTNET_TieredPGO"] = SettingsVm.UsePGO ? "1" : "0"; - envVars["DOTNET_JitDisasmDiffable"] = SettingsVm.Diffable ? "1" : "0"; + envVars["DOTNET_TieredPGO"] = SettingsVm.UsePGO ? "1" : "0"; + envVars["DOTNET_JitDisasmDiffable"] = SettingsVm.Diffable ? "1" : "0"; + + if (!SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) + { + var (runtimePackPath, success) = GetPathToRuntimePack(); + if (!success) + return; + + // tell jit to look for BCL libs in the locally built runtime pack + envVars["CORE_LIBRARIES"] = runtimePackPath; + } + + envVars["DOTNET_TieredCompilation"] = SettingsVm.UseTieredJit ? "1" : "0"; + + // User is free to override any of those ^ + SettingsVm.FillWithUserVars(envVars); - if (!SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) - { - var (runtimePackPath, success) = GetPathToRuntimePack(); - if (!success) - return; - // tell jit to look for BCL libs in the locally built runtime pack - envVars["CORE_LIBRARIES"] = runtimePackPath; + string currentFgFile = null; + if (SettingsVm.FgEnable) + { + if (symbolInfo.MethodName == "*") + { + Output = "Flowgraph for classes (all methods) is not supported yet."; + return; } - envVars["DOTNET_TieredCompilation"] = SettingsVm.UseTieredJit ? "1" : "0"; + currentFgFile = Path.GetTempFileName(); + envVars["DOTNET_JitDumpFg"] = symbolInfo.Target; + envVars["DOTNET_JitDumpFgDot"] = "1"; + envVars["DOTNET_JitDumpFgPhase"] = "*"; + envVars["DOTNET_JitDumpFgFile"] = currentFgFile; + } + + var command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsVm.UseUnloadableContext}"; + if (SettingsVm.RunAppMode) + { + command = $"\"{fileName}.dll\""; + } + + var executable = "dotnet"; + if (SettingsVm.CrossgenIsSelected && SettingsVm.UseCustomRuntime) + { + var (clrCheckedFilesDir, checkedFound) = GetPathToCoreClrChecked(); + if (!checkedFound) + return; - // User is free to override any of those ^ - SettingsVm.FillWithUserVars(envVars); + var (runtimePackPath, runtimePackFound) = GetPathToRuntimePack(); + if (!runtimePackFound) + return; + executable = Path.Combine(SettingsVm.PathToLocalCoreClr, "dotnet.cmd"); + command = $"{Path.Combine(clrCheckedFilesDir, "crossgen2", "crossgen2.dll")} --out aot "; - string currentFgFile = null; - if (SettingsVm.FgEnable) + foreach (var envVar in envVars) { - if (symbolInfo.MethodName == "*") + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - Output = "Flowgraph for classes (all methods) is not supported yet."; - return; + continue; } - currentFgFile = Path.GetTempFileName(); - envVars["DOTNET_JitDumpFg"] = symbolInfo.Target; - envVars["DOTNET_JitDumpFgDot"] = "1"; - envVars["DOTNET_JitDumpFgPhase"] = "*"; - envVars["DOTNET_JitDumpFgFile"] = currentFgFile; + keyLower = keyLower + .Replace("dotnet_jitdump", "--codegenopt:jitdump") + .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + command += keyLower + "=\"" + envVar.Value + "\" "; } + envVars.Clear(); - string command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsVm.UseUnloadableContext}"; - if (SettingsVm.RunAppMode) + // These are needed for faster crossgen itself - they're not changing output codegen + envVars["DOTNET_TieredPGO"] = "0"; + envVars["DOTNET_ReadyToRun"] = "1"; + envVars["DOTNET_TC_QuickJitForLoops"] = "1"; + envVars["DOTNET_TC_CallCountingDelayMs"] = "0"; + envVars["DOTNET_TieredCompilation"] = "1"; + command += SettingsVm.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; + + if (SettingsVm.UseDotnetPublishForReload) { - command = $"\"{fileName}.dll\""; + // Reference everything in the publish dir + command += $" -r: \"{dstFolder}\\*.dll\" "; } - - string executable = "dotnet"; - - if (SettingsVm.CrossgenIsSelected && SettingsVm.UseCustomRuntime) + else { - var (clrCheckedFilesDir, checkedFound) = GetPathToCoreClrChecked(); - if (!checkedFound) - return; - - var (runtimePackPath, runtimePackFound) = GetPathToRuntimePack(); - if (!runtimePackFound) - return; - - executable = Path.Combine(SettingsVm.PathToLocalCoreClr, "dotnet.cmd"); - command = $"{Path.Combine(clrCheckedFilesDir, "crossgen2", "crossgen2.dll")} --out aot "; + // the runtime pack we use doesn't contain corelib so let's use "checked" corelib + // TODO: build proper core_root with release version of corelib + var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); + command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + } - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + LoadingStatus = $"Executing crossgen2..."; + } + else if (SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + { + var (clrReleaseFolder, clrFound) = GetPathToCoreClrCheckedForNativeAot(); + if (!clrFound) + return; - keyLower = keyLower - .Replace("dotnet_jitdump", "--codegenopt:jitdump") - .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - command += keyLower + "=\"" + envVar.Value + "\" "; - } - envVars.Clear(); + command = ""; + executable = Path.Combine(clrReleaseFolder, "ilc", "ilc.exe"); - // These are needed for faster crossgen itself - they're not changing output codegen - envVars["DOTNET_TieredPGO"] = "0"; - envVars["DOTNET_ReadyToRun"] = "1"; - envVars["DOTNET_TC_QuickJitForLoops"] = "1"; - envVars["DOTNET_TC_CallCountingDelayMs"] = "0"; - envVars["DOTNET_TieredCompilation"] = "1"; - command += SettingsVm.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; + command += $" \"{fileName}.dll\" "; - if (SettingsVm.UseDotnetPublishForReload) - { - // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; - } - else + foreach (var envVar in envVars) + { + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib - var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); - command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + continue; } - LoadingStatus = $"Executing crossgen2..."; + keyLower = keyLower + .Replace("dotnet_jitdump", "--codegenopt:jitdump") + .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + command += keyLower + "=\"" + envVar.Value + "\" "; + } + envVars.Clear(); + command += SettingsVm.IlcArgs.Replace("%DOTNET_REPO%", SettingsVm.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); + + if (SettingsVm.UseDotnetPublishForReload) + { + // Reference everything in the publish dir + command += $" -r: \"{dstFolder}\\*.dll\" "; } - else if (SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + else { - var (clrReleaseFolder, clrFound) = GetPathToCoreClrCheckedForNativeAot(); - if (!clrFound) - return; + // the runtime pack we use doesn't contain corelib so let's use "checked" corelib + // TODO: build proper core_root with release version of corelib + //var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); + //command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + } - command = ""; - executable = Path.Combine(clrReleaseFolder, "ilc", "ilc.exe"); + LoadingStatus = "Executing ILC... Make sure your method is not inlined and is reachable as NativeAOT runs IL Link. It might take some time..."; + } + else if (SettingsVm.IsNonCustomNativeAOTMode()) + { + LoadingStatus = "Compiling for NativeAOT (.NET 8.0+ is required) ..."; - command += $" \"{fileName}.dll\" "; + // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs + // namely, we need to re-direct jit's output to a file (JitStdOutFile). - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + var tmpJitStdout = Path.GetTempFileName() + ".asm"; - keyLower = keyLower - .Replace("dotnet_jitdump", "--codegenopt:jitdump") - .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - command += keyLower + "=\"" + envVar.Value + "\" "; - } - envVars.Clear(); - command += SettingsVm.IlcArgs.Replace("%DOTNET_REPO%", SettingsVm.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); + envVars["DOTNET_JitStdOutFile"] = tmpJitStdout; - if (SettingsVm.UseDotnetPublishForReload) - { - // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; - } - else + var customIlcArgs = ""; + foreach (var envVar in envVars) + { + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib - //var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); - //command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + continue; } - LoadingStatus = "Executing ILC... Make sure your method is not inlined and is reachable as NativeAOT runs IL Link. It might take some time..."; + keyLower = keyLower + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + customIlcArgs += $"\t\t\n"; } - else if (SettingsVm.IsNonCustomNativeAOTMode()) - { - LoadingStatus = "Compiling for NativeAOT (.NET 8.0+ is required) ..."; + envVars.Clear(); - // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs - // namely, we need to re-direct jit's output to a file (JitStdOutFile). - - var tmpJitStdout = Path.GetTempFileName() + ".asm"; - - envVars["DOTNET_JitStdOutFile"] = tmpJitStdout; + var tmpProps = Path.GetTempFileName() + ".props"; + File.WriteAllText(tmpProps, $""" + + + + $(DefineConstants);DISASMO + + + {customIlcArgs} + + + """); + + var tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; + + // NOTE: CustomBeforeDirectoryBuildProps is probably not a good idea to overwrite, but we need to pass IlcArgs somehow + var dotnetPublishArgs = + $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release" + + $" /p:PublishAot=true /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\"" + + $" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; + + var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCt); - string customIlcArgs = ""; - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + ThrowIfCanceled(); - keyLower = keyLower - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - customIlcArgs += $"\t\t\n"; - } - envVars.Clear(); - - var tmpProps = Path.GetTempFileName() + ".props"; - File.WriteAllText(tmpProps, $""" - - - - $(DefineConstants);DISASMO - - - {customIlcArgs} - - - """); - - string tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; - - // NOTE: CustomBeforeDirectoryBuildProps is probably not a good idea to overwrite, but we need to pass IlcArgs somehow - string dotnetPublishArgs = - $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release" + - $" /p:PublishAot=true /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\"" + - $" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - - var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCt); - - ThrowIfCanceled(); - - if (string.IsNullOrEmpty(publishResult.Error)) + if (string.IsNullOrEmpty(publishResult.Error)) + { + if (!File.Exists(tmpJitStdout)) { - if (!File.Exists(tmpJitStdout)) - { - Output = $""" - JitDisasm didn't produce any output :(. Make sure your method is not inlined by the code generator - (it's a good idea to mark it as [MethodImpl(MethodImplOptions.NoInlining)]) and is reachable from Main() as - NativeAOT may delete unused methods. Also, JitDisasm doesn't work well for Main() in NativeAOT mode." - + Output = $""" + JitDisasm didn't produce any output :(. Make sure your method is not inlined by the code generator + (it's a good idea to mark it as [MethodImpl(MethodImplOptions.NoInlining)]) and is reachable from Main() as + NativeAOT may delete unused methods. Also, JitDisasm doesn't work well for Main() in NativeAOT mode." - {publishResult.Output} - """; - } - else - { - Success = true; - Output = File.ReadAllText(tmpJitStdout); - // Keep the temp files around for debugging if it failed. - // and delete them if it succeeded. - File.Delete(tmpProps); - File.Delete(tmpJitStdout); - } + {publishResult.Output} + """; } else { - Output = publishResult.Error; + Success = true; + Output = File.ReadAllText(tmpJitStdout); + + // Keep the temp files around for debugging if it failed. + // and delete them if it succeeded. + File.Delete(tmpProps); + File.Delete(tmpJitStdout); } - return; } else { - LoadingStatus = $"Executing DisasmoLoader..."; + Output = publishResult.Error; } + return; + } + else + { + LoadingStatus = $"Executing DisasmoLoader..."; + } - if (!SettingsVm.UseDotnetPublishForReload && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) - { - var (clrCheckedFilesDir, success) = GetPathToCoreClrChecked(); - if (!success) - return; - executable = Path.Combine(clrCheckedFilesDir, "CoreRun.exe"); - } + if (!SettingsVm.UseDotnetPublishForReload && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + { + var (clrCheckedFilesDir, success) = GetPathToCoreClrChecked(); + if (!success) + return; - if ((SettingsVm.RunAppMode) && - !string.IsNullOrWhiteSpace(SettingsVm.OverridenJitDisasm)) - { - envVars["DOTNET_JitDisasm"] = SettingsVm.OverridenJitDisasm; - } + executable = Path.Combine(clrCheckedFilesDir, "CoreRun.exe"); + } - ProcessResult result = await ProcessUtils.RunProcess( - executable, command, envVars, dstFolder, cancellationToken: UserCt); + if (SettingsVm.RunAppMode && + !string.IsNullOrWhiteSpace(SettingsVm.OverridenJitDisasm)) + { + envVars["DOTNET_JitDisasm"] = SettingsVm.OverridenJitDisasm; + } - ThrowIfCanceled(); + var result = await ProcessUtils.RunProcess(executable, command, envVars, dstFolder, cancellationToken: UserCt); + ThrowIfCanceled(); - if (string.IsNullOrEmpty(result.Error)) - { - Success = true; - Output = PreprocessOutput(result.Output); - } - else + if (string.IsNullOrEmpty(result.Error)) + { + Success = true; + Output = PreprocessOutput(result.Output); + } + else + { + Output = result.Output + "\nERROR:\n" + result.Error; + } + + if (SettingsVm.FgEnable && SettingsVm.JitDumpInsteadOfDisasm) + { + currentFgFile += ".dot"; + if (!File.Exists(currentFgFile)) { - Output = result.Output + "\nERROR:\n" + result.Error; + Output = $"Oops, JitDumpFgFile ('{currentFgFile}') doesn't exist :(\nInvalid Phase name?"; + return; } - if (SettingsVm.FgEnable && SettingsVm.JitDumpInsteadOfDisasm) + if (new FileInfo(currentFgFile).Length == 0) { - currentFgFile += ".dot"; - if (!File.Exists(currentFgFile)) - { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') doesn't exist :(\nInvalid Phase name?"; - return; - } - - if (new FileInfo(currentFgFile).Length == 0) - { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') file is empty :(\nInvalid Phase name?"; - return; - } + Output = $"Oops, JitDumpFgFile ('{currentFgFile}') file is empty :(\nInvalid Phase name?"; + return; + } - string fgLines = File.ReadAllText(currentFgFile); + var fgLines = File.ReadAllText(currentFgFile); - FgPhases.Clear(); - var graphs = fgLines.Split(new [] {"digraph FlowGraph {"}, StringSplitOptions.RemoveEmptyEntries); - int graphIndex = 0; + FgPhases.Clear(); + var graphs = fgLines.Split(new [] {"digraph FlowGraph {"}, StringSplitOptions.RemoveEmptyEntries); + var graphIndex = 0; - var fgBaseDir = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(fgBaseDir); - foreach (var graph in graphs) + var fgBaseDir = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(fgBaseDir); + foreach (var graph in graphs) + { + try { - try - { - var name = graph.Substring(graph.IndexOf("graph [label = ") + "graph [label = ".Length); - name = name.Substring(0, name.IndexOf("\"];")); - name = name.Replace("\\n", " "); - name = name.Substring(name.IndexOf(" after ") + " after ".Length).Trim(); + var name = graph.Substring(graph.IndexOf("graph [label = ") + "graph [label = ".Length); + name = name.Substring(0, name.IndexOf("\"];")); + name = name.Replace("\\n", " "); + name = name.Substring(name.IndexOf(" after ") + " after ".Length).Trim(); - // Reset counter if tier0 and tier1 are merged together - if (name == "Pre-import") - { - graphIndex = 0; - } + // Reset counter if tier0 and tier1 are merged together + if (name == "Pre-import") + { + graphIndex = 0; + } - name = (++graphIndex) + ". " + name; + name = (++graphIndex) + ". " + name; - // Ignore invalid path chars - name = Path.GetInvalidFileNameChars().Aggregate(name, (current, ic) => current.Replace(ic, '_')); + // Ignore invalid path chars + name = Path.GetInvalidFileNameChars().Aggregate(name, (current, ic) => current.Replace(ic, '_')); - var dotPath = Path.Combine(fgBaseDir, $"{name}.dot"); - File.WriteAllText(dotPath, "digraph FlowGraph {\n" + graph); + var dotPath = Path.Combine(fgBaseDir, $"{name}.dot"); + File.WriteAllText(dotPath, "digraph FlowGraph {\n" + graph); - FgPhases.Add(new FlowgraphItemViewModel(SettingsVm) { Name = name, DotFileUrl = dotPath, ImageUrl = "" }); - } - catch (Exception exc) - { - Debug.WriteLine(exc); - } + FgPhases.Add(new FlowgraphItemViewModel(SettingsVm) { Name = name, DotFileUrl = dotPath, ImageUrl = "" }); + } + catch (Exception exc) + { + Debug.WriteLine(exc); } } - - } - catch (OperationCanceledException e) - { - Output = e.Message; - } - catch (Exception e) - { - Output = e.ToString(); - } - finally - { - IsLoading = false; } - } - private string PreprocessOutput(string output) + } + catch (OperationCanceledException e) { - if (SettingsVm.JitDumpInsteadOfDisasm || SettingsVm.PrintInlinees) - return output; - return DisassemblyPrettifier.Prettify(output, !SettingsVm.ShowAsmComments && !SettingsVm.RunAppMode); + Output = e.Message; } - - private UnconfiguredProject GetUnconfiguredProject(Project project) + catch (Exception e) { - var context = project as IVsBrowseObjectContext; - if (context == null && project != null) - context = project.Object as IVsBrowseObjectContext; - - return context?.UnconfiguredProject; + Output = e.ToString(); } + finally + { + IsLoading = false; + } + } + + private string PreprocessOutput(string output) + { + if (SettingsVm.JitDumpInsteadOfDisasm || SettingsVm.PrintInlinees) + return output; + return DisassemblyPrettifier.Prettify(output, !SettingsVm.ShowAsmComments && !SettingsVm.RunAppMode); + } - private (string, bool) GetPathToRuntimePack() + private UnconfiguredProject GetUnconfiguredProject(Project project) + { + var context = project as IVsBrowseObjectContext; + if (context == null && project != null) + context = project.Object as IVsBrowseObjectContext; + + return context?.UnconfiguredProject; + } + + private (string, bool) GetPathToRuntimePack() + { + var arch = SettingsViewModel.Arch; + var (_, success) = GetPathToCoreClrChecked(); + if (!success) + return (null, false); + + var runtimePacksPath = Path.Combine(SettingsVm.PathToLocalCoreClr, @"artifacts\bin\runtime"); + string runtimePackPath = null; + if (Directory.Exists(runtimePacksPath)) { - var arch = SettingsViewModel.Arch; - var (_, success) = GetPathToCoreClrChecked(); - if (!success) - return (null, false); + var packs = Directory.GetDirectories(runtimePacksPath, "*-windows-Release-" + arch); + runtimePackPath = packs.OrderByDescending(i => i).FirstOrDefault(); + } - string runtimePacksPath = Path.Combine(SettingsVm.PathToLocalCoreClr, @"artifacts\bin\runtime"); - string runtimePackPath = null; - if (Directory.Exists(runtimePacksPath)) - { - var packs = Directory.GetDirectories(runtimePacksPath, "*-windows-Release-" + arch); - runtimePackPath = packs.OrderByDescending(i => i).FirstOrDefault(); - } + if (!Directory.Exists(runtimePackPath)) + { + Output = "Please, build a runtime-pack in your local repo:\n\n" + + $"Run 'build.cmd Clr+Clr.Aot+Libs -c Release -a {arch}' in the repo root\n" + + "Don't worry, you won't have to re-build it every time you change something in jit, vm or corelib."; - if (!Directory.Exists(runtimePackPath)) - { - Output = "Please, build a runtime-pack in your local repo:\n\n" + - $"Run 'build.cmd Clr+Clr.Aot+Libs -c Release -a {arch}' in the repo root\n" + - "Don't worry, you won't have to re-build it every time you change something in jit, vm or corelib."; - return (null, false); - } - return (runtimePackPath, true); + return (null, false); } - private (string, bool) GetPathToCoreClrChecked() + return (runtimePackPath, true); + } + + private (string, bool) GetPathToCoreClrChecked() + { + var arch = SettingsViewModel.Arch; + var clrCheckedFilesDir = FindJitDirectory(SettingsVm.PathToLocalCoreClr, arch); + if (string.IsNullOrWhiteSpace(clrCheckedFilesDir)) { - var arch = SettingsViewModel.Arch; - var clrCheckedFilesDir = FindJitDirectory(SettingsVm.PathToLocalCoreClr, arch); - if (string.IsNullOrWhiteSpace(clrCheckedFilesDir)) - { - Output = $"Path to a local dotnet/runtime repository is either not set or it's not built for {arch} arch yet" + - (SettingsVm.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + - "\nPlease clone it and build it in `Checked` mode, e.g.:\n\n" + - "git clone git@github.com:dotnet/runtime.git\n" + - "cd runtime\n" + - $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; - return (null, false); - } - return (clrCheckedFilesDir, true); + Output = $"Path to a local dotnet/runtime repository is either not set or it's not built for {arch} arch yet" + + (SettingsVm.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + + "\nPlease clone it and build it in `Checked` mode, e.g.:\n\n" + + "git clone git@github.com:dotnet/runtime.git\n" + + "cd runtime\n" + + $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; + + return (null, false); } + return (clrCheckedFilesDir, true); + } + - private (string, bool) GetPathToCoreClrCheckedForNativeAot() + private (string, bool) GetPathToCoreClrCheckedForNativeAot() + { + var arch = SettingsViewModel.Arch; + var releaseFolder = Path.Combine(SettingsVm.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); + if (!Directory.Exists(releaseFolder) || !Directory.Exists(Path.Combine(releaseFolder, "aotsdk")) || !Directory.Exists(Path.Combine(releaseFolder, "ilc"))) { - var arch = SettingsViewModel.Arch; - var releaseFolder = Path.Combine(SettingsVm.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); - if (!Directory.Exists(releaseFolder) || !Directory.Exists(Path.Combine(releaseFolder, "aotsdk")) || !Directory.Exists(Path.Combine(releaseFolder, "ilc"))) - { - Output = $"Path to a local dotnet/runtime repository is either not set or it's not correctly built for {arch} arch yet for NativeAOT" + - "\nPlease clone it and build it using the following steps.:\n\n" + - "git clone git@github.com:dotnet/runtime.git\n" + - "cd runtime\n" + - $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; - return (null, false); - } - return (releaseFolder, true); + Output = $"Path to a local dotnet/runtime repository is either not set or it's not correctly built for {arch} arch yet for NativeAOT" + + "\nPlease clone it and build it using the following steps.:\n\n" + + "git clone git@github.com:dotnet/runtime.git\n" + + "cd runtime\n" + + $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; + + return (null, false); } - public async void RunOperationAsync(ISymbol symbol, CAProject project) + return (releaseFolder, true); + } + + public async void RunOperationAsync(ISymbol symbol, CAProject project) + { + var stopwatch = Stopwatch.StartNew(); + var dte = IdeUtils.DTE(); + + // It's possible that the last modified C# document is not active (e.g. Disasmo itself is in the focus) + // so we have no choice but to run Save() for all the opened documents + dte.SaveAllDocuments(); + + try { - var stopwatch = Stopwatch.StartNew(); - DTE dte = IdeUtils.DTE(); + IsLoading = true; + FgPngPath = null; + MainPageRequested?.Invoke(); + Success = false; + _currentSymbol = symbol; + _currentProject = project; + Output = ""; + + if (symbol == null) + { + Output = "Symbol is not recognized, put cursor on a function/class name"; + return; + } + + string clrCheckedFilesDir = null; + if (SettingsVm.UseCustomRuntime) + { + var (dir, success) = GetPathToCoreClrChecked(); + if (!success) + return; - // It's possible that the last modified C# document is not active (e.g. Disasmo itself is in the focus) - // so we have no choice but to run Save() for all the opened documents - dte.SaveAllDocuments(); + clrCheckedFilesDir = dir; + } - try + if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsVm.RunAppMode) { - IsLoading = true; - FgPngPath = null; - MainPageRequested?.Invoke(); - Success = false; - _currentSymbol = symbol; - _currentProject = project; - Output = ""; + // TODO: ask user to specify type parameters + Output = "Generic methods are only supported in 'Run' mode"; + return; + } - if (symbol == null) - { - Output = "Symbol is not recognized, put cursor on a function/class name"; - return; - } - - string clrCheckedFilesDir = null; - if (SettingsVm.UseCustomRuntime) - { - var (dir, success) = GetPathToCoreClrChecked(); - if (!success) - return; - clrCheckedFilesDir = dir; - } + ThrowIfCanceled(); - if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsVm.RunAppMode) - { - // TODO: ask user to specify type parameters - Output = "Generic methods are only supported in 'Run' mode"; - return; - } + // Find Release-{SettingsViewModel.Arch} configuration: + var currentProject = dte.GetActiveProject(project.FilePath); + if (currentProject == null) + { + Output = "There no active project. Please re-open solution."; + return; + } - ThrowIfCanceled(); + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + _currentProjectPath = currentProject.FileName; - // Find Release-{SettingsViewModel.Arch} configuration: - Project currentProject = dte.GetActiveProject(project.FilePath); - if (currentProject is null) - { - Output = "There no active project. Please re-open solution."; - return; - } + var unconfiguredProject = GetUnconfiguredProject(currentProject); + // find all configurations, ordered by version descending + var projectConfigurations = (await IdeUtils.GetProjectConfigurations(unconfiguredProject)) + .OrderByDescending(IdeUtils.GetTargetFrameworkVersionDimension) + .ToList(); - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - _currentProjectPath = currentProject.FileName; - - UnconfiguredProject unconfiguredProject = GetUnconfiguredProject(currentProject); - // find all configurations, ordered by version descending - var projectConfigurations = (await IdeUtils.GetProjectConfigurations(unconfiguredProject)) - .OrderByDescending(IdeUtils.GetTargetFrameworkVersionDimension) - .ToList(); - // filter Release configurations - var releaseConfigurations = projectConfigurations - .Where(cfg => string.Equals(IdeUtils.GetConfigurationDimension(cfg), "Release", StringComparison.OrdinalIgnoreCase)) - .ToList(); - // Use Release configurations only if we have any - if (releaseConfigurations.Any()) - { - projectConfigurations = releaseConfigurations; - } + // filter Release configurations + var releaseConfigurations = projectConfigurations + .Where(cfg => string.Equals(IdeUtils.GetConfigurationDimension(cfg), "Release", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // Use Release configurations only if we have any + if (releaseConfigurations.Any()) + { + projectConfigurations = releaseConfigurations; + } - ProjectConfiguration projectConfiguration; - if (string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM)) + ProjectConfiguration projectConfiguration; + if (string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM)) + { + // choose first (highest) + projectConfiguration = projectConfigurations.FirstOrDefault(); + // resolve later + _currentTf = null; + } + else + { + // No validation in this case + _currentTf = SettingsVm.OverridenTFM.Trim(); + var currentTfmVersion = TfmVersion.Parse(_currentTf); + // find the best suitable project configuration + projectConfiguration = projectConfigurations + .FirstOrDefault(cfg => currentTfmVersion != null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) + ?? projectConfigurations.FirstOrDefault(); + } + + var projectProperties = await IdeUtils.GetProjectProperties(unconfiguredProject, projectConfiguration); + ThrowIfCanceled(); + + // resolve target framework + if (_currentTf == null) + { + int? major; + if (projectProperties != null) { - // choose first (highest) - projectConfiguration = projectConfigurations.FirstOrDefault(); - // resolve later - _currentTf = null; + _currentTf = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); + major = TfmVersion.Parse(_currentTf)?.Major; } else { - // No validation in this case - _currentTf = SettingsVm.OverridenTFM.Trim(); - var currentTfmVersion = TfmVersion.Parse(_currentTf); - // find the best suitable project configuration - projectConfiguration = projectConfigurations - .FirstOrDefault(cfg => currentTfmVersion != null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) - ?? projectConfigurations.FirstOrDefault(); + // fallback to net 7.0 + _currentTf = "net7.0"; + major = 7; } - IProjectProperties projectProperties = await IdeUtils.GetProjectProperties(unconfiguredProject, projectConfiguration); ThrowIfCanceled(); - // resolve target framework - if (_currentTf == null) + if (major >= 6) { - int? major; - if (projectProperties != null) + if (!SettingsVm.UseCustomRuntime && major < 7) { - _currentTf = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); - major = TfmVersion.Parse(_currentTf)?.Major; - } - else - { - // fallback to net 7.0 - _currentTf = "net7.0"; - major = 7; - } - - ThrowIfCanceled(); + Output = + "Only net7.0 (and newer) apps are supported with non-locally built dotnet/runtime.\n" + + "Make sure net7.0 is set in your csproj."; - if (major >= 6) - { - if (!SettingsVm.UseCustomRuntime && major < 7) - { - Output = - "Only net7.0 (and newer) apps are supported with non-locally built dotnet/runtime.\nMake sure net7.0 is set in your csproj."; - return; - } - } - else - { - Output = - "Only net6.0 (and newer) apps are supported.\nMake sure net6.0 is set in your csproj."; return; } } - - ThrowIfCanceled(); - - if (SettingsVm.RunAppMode && SettingsVm.UseDotnetPublishForReload) + else { - // TODO: fix this - Output = "\"Run current app\" mode only works with \"dotnet build\" reload strategy, see Options tab."; + Output = + "Only net6.0 (and newer) apps are supported.\n" + + "Make sure net6.0 is set in your csproj."; + return; } + } - // Validation for Flowgraph tab - if (SettingsVm.FgEnable) - { - if (string.IsNullOrWhiteSpace(SettingsVm.GraphvisDotPath) || - !File.Exists(SettingsVm.GraphvisDotPath)) - { - Output = "Graphvis is not installed or path to dot.exe is incorrect, see 'Settings' tab.\nGraphvis can be installed from https://graphviz.org/download/"; - return; - } + ThrowIfCanceled(); - if (!SettingsVm.JitDumpInsteadOfDisasm) - { - Output = "Either disable flowgraphs in the 'Flowgraph' tab or enable JitDump."; - return; - } - } + if (SettingsVm.RunAppMode && SettingsVm.UseDotnetPublishForReload) + { + // TODO: fix this + Output = "\"Run current app\" mode only works with \"dotnet build\" reload strategy, see Options tab."; + return; + } - if (SettingsVm.CrossgenIsSelected || SettingsVm.NativeAotIsSelected) + // Validation for Flowgraph tab + if (SettingsVm.FgEnable) + { + if (string.IsNullOrWhiteSpace(SettingsVm.GraphvisDotPath) || + !File.Exists(SettingsVm.GraphvisDotPath)) { - if (SettingsVm.UsePGO) - { - Output = "PGO has no effect on R2R'd/NativeAOT code."; - return; - } + Output = + "Graphvis is not installed or path to dot.exe is incorrect, see 'Settings' tab.\n" + + "Graphvis can be installed from https://graphviz.org/download/"; - if (SettingsVm.RunAppMode) - { - Output = "Run mode is not supported for crossgen/NativeAOT"; - return; - } - - if (SettingsVm.UseTieredJit) - { - Output = "TieredJIT has no effect on R2R'd/NativeAOT code."; - return; - } - - if (SettingsVm.FgEnable) - { - Output = "Flowgraphs are not tested with crossgen2/NativeAOT yet (in Disasmo)"; - return; - } + return; } - string outputDir = projectProperties == null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); - DisasmoOutDir = Path.Combine(outputDir, DisasmoFolder + (SettingsVm.UseDotnetPublishForReload ? "_published" : "")); - string currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); - - if (SettingsVm.IsNonCustomDotnetAotMode()) + if (!SettingsVm.JitDumpInsteadOfDisasm) { - ThrowIfCanceled(); - var symbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(symbolInfo, projectProperties); + Output = "Either disable flowgraphs in the 'Flowgraph' tab or enable JitDump."; return; } + } - string tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; - - // Some things can't be set in CLI e.g. appending to DefineConstants - var tmpProps = Path.GetTempFileName() + ".props"; - File.WriteAllText(tmpProps, $""" - - - - $(DefineConstants);DISASMO - - - """); - - ProcessResult publishResult; - if (SettingsVm.UseDotnetPublishForReload) - { - LoadingStatus = $"dotnet publish -r win-{SettingsViewModel.Arch} -c Release -o ..."; - - string dotnetPublishArgs = - $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutDir} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCt); - } - else + if (SettingsVm.CrossgenIsSelected || SettingsVm.NativeAotIsSelected) + { + if (SettingsVm.UsePGO) { - if (SettingsVm.UseCustomRuntime) - { - var (_, rpSuccess) = GetPathToRuntimePack(); - if (!rpSuccess) - return; - } - - LoadingStatus = "dotnet build -c Release -o ..."; - - string dotnetBuildArgs = $"build {tfmPart} -c Release -o {DisasmoOutDir} --no-self-contained " + - "/p:RuntimeIdentifier=\"\" " + - "/p:RuntimeIdentifiers=\"\" " + - "/p:WarningLevel=0 " + - $"/p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" " + - $"/p:TreatWarningsAsErrors=false \"{_currentProjectPath}\""; - - Dictionary fasterBuildEnvVars = new Dictionary - { - ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1", - ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" - }; - - if (SettingsVm.UseNoRestoreFlag) - { - dotnetBuildArgs += " --no-restore --no-dependencies --nologo"; - fasterBuildEnvVars["DOTNET_MULTILEVEL_LOOKUP"] = "0"; - } - - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetBuildArgs, fasterBuildEnvVars, - currentProjectDirPath, - cancellationToken: UserCt); + Output = "PGO has no effect on R2R'd/NativeAOT code."; + return; } - File.Delete(tmpProps); - ThrowIfCanceled(); - - if (!string.IsNullOrEmpty(publishResult.Error)) + if (SettingsVm.RunAppMode) { - Output = publishResult.Error; + Output = "Run mode is not supported for crossgen/NativeAOT"; return; } - // in case if there are compilation errors: - if (publishResult.Output.Contains(": error")) + if (SettingsVm.UseTieredJit) { - Output = publishResult.Output; + Output = "TieredJIT has no effect on R2R'd/NativeAOT code."; return; } - if (SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) + if (SettingsVm.FgEnable) { - LoadingStatus = "Copying files from locally built CoreCLR"; - - string dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) - dstFolder = Path.Combine(currentProjectDirPath, DisasmoOutDir); - if (!Directory.Exists(dstFolder)) - { - Output = - $"Something went wrong, {dstFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; - return; - } - - var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", - $"/e \"{clrCheckedFilesDir}\" \"{dstFolder}", null, cancellationToken: UserCt); - - if (!string.IsNullOrEmpty(copyClrFilesResult.Error)) - { - Output = copyClrFilesResult.Error; - return; - } + Output = "Flowgraphs are not tested with crossgen2/NativeAOT yet (in Disasmo)"; + return; } + } + + var outputDir = projectProperties == null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); + DisasmoOutDir = Path.Combine(outputDir, DisasmoFolder + (SettingsVm.UseDotnetPublishForReload ? "_published" : "")); + var currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); + if (SettingsVm.IsNonCustomDotnetAotMode()) + { ThrowIfCanceled(); - var finalSymbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(finalSymbolInfo, projectProperties); + var symbolInfo = SymbolUtils.FromSymbol(_currentSymbol); + await RunFinalExe(symbolInfo, projectProperties); + return; } - catch (OperationCanceledException e) + + var tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; + + // Some things can't be set in CLI e.g. appending to DefineConstants + var tmpProps = Path.GetTempFileName() + ".props"; + File.WriteAllText(tmpProps, $""" + + + + $(DefineConstants);DISASMO + + + """); + + ProcessResult publishResult; + if (SettingsVm.UseDotnetPublishForReload) { - Output = e.Message; + LoadingStatus = $"dotnet publish -r win-{SettingsViewModel.Arch} -c Release -o ..."; + + var dotnetPublishArgs = $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutDir} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; + + publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCt); } - catch (Exception e) + else { - Output = e.ToString(); + if (SettingsVm.UseCustomRuntime) + { + var (_, rpSuccess) = GetPathToRuntimePack(); + if (!rpSuccess) + return; + } + + LoadingStatus = "dotnet build -c Release -o ..."; + + var dotnetBuildArgs = $"build {tfmPart} -c Release -o {DisasmoOutDir} --no-self-contained " + + "/p:RuntimeIdentifier=\"\" " + + "/p:RuntimeIdentifiers=\"\" " + + "/p:WarningLevel=0 " + + $"/p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" " + + $"/p:TreatWarningsAsErrors=false \"{_currentProjectPath}\""; + + Dictionary fasterBuildEnvVars = new Dictionary + { + ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1", + ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" + }; + + if (SettingsVm.UseNoRestoreFlag) + { + dotnetBuildArgs += " --no-restore --no-dependencies --nologo"; + fasterBuildEnvVars["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + } + + publishResult = await ProcessUtils.RunProcess("dotnet", dotnetBuildArgs, fasterBuildEnvVars, + currentProjectDirPath, + cancellationToken: UserCt); } - finally + + File.Delete(tmpProps); + ThrowIfCanceled(); + + if (!string.IsNullOrEmpty(publishResult.Error)) { - IsLoading = false; - stopwatch.Stop(); - StopwatchStatus = $"Disasm took {stopwatch.Elapsed.TotalSeconds:F1} s."; + Output = publishResult.Error; + return; } - } - private static string FindJitDirectory(string basePath, string arch) - { - string jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); - if (Directory.Exists(jitDir)) + // in case if there are compilation errors: + if (publishResult.Output.Contains(": error")) { - return jitDir; + Output = publishResult.Output; + return; } - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); - if (Directory.Exists(jitDir)) + if (SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) { - return jitDir; + LoadingStatus = "Copying files from locally built CoreCLR"; + + var dstFolder = DisasmoOutDir; + if (!Path.IsPathRooted(dstFolder)) + { + dstFolder = Path.Combine(currentProjectDirPath, DisasmoOutDir); + } + + if (!Directory.Exists(dstFolder)) + { + Output = $"Something went wrong, {dstFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; + return; + } + + var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", $"/e \"{clrCheckedFilesDir}\" \"{dstFolder}", null, cancellationToken: UserCt); + + if (!string.IsNullOrEmpty(copyClrFilesResult.Error)) + { + Output = copyClrFilesResult.Error; + return; + } } - return null; + + ThrowIfCanceled(); + var finalSymbolInfo = SymbolUtils.FromSymbol(_currentSymbol); + await RunFinalExe(finalSymbolInfo, projectProperties); + } + catch (OperationCanceledException e) + { + Output = e.Message; + } + catch (Exception e) + { + Output = e.ToString(); + } + finally + { + IsLoading = false; + stopwatch.Stop(); + StopwatchStatus = $"Disasm took {stopwatch.Elapsed.TotalSeconds:F1} s."; } } + + private static string FindJitDirectory(string basePath, string arch) + { + var jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); + if (Directory.Exists(jitDir)) + return jitDir; + + jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); + if (Directory.Exists(jitDir)) + return jitDir; + + return null; + } } \ No newline at end of file diff --git a/src/Vsix/ViewModels/SettingsViewModel.cs b/src/Vsix/ViewModels/SettingsViewModel.cs index 073b4c6..b3e95d5 100644 --- a/src/Vsix/ViewModels/SettingsViewModel.cs +++ b/src/Vsix/ViewModels/SettingsViewModel.cs @@ -9,501 +9,496 @@ using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.CommandWpf; -namespace Disasmo +namespace Disasmo; + +public class SettingsViewModel : ViewModelBase { - public class SettingsViewModel : ViewModelBase - { - private string _pathToLocalCoreClr; - private bool _jitDumpInsteadOfDisasm; - private string _customEnvVars; - private string _crossgen2Args; - private string _ilcArgs; - private bool _showAsmComments; - private bool _updateIsAvailable; - private Version _currentVersion; - private Version _availableVersion; - private bool _useDotnetPublishForReload; - private bool _useDotnetBuildForReload; - private bool _runAppMode; - private bool _printInlinees; - private double _fontSize; - private bool _useNoRestoreFlag; - private bool _disableLightBulb; - private bool _useTieredJit; - private bool _useUnloadableContext; - private bool _usePGO; - private bool _diffable; - private bool _dontGuessTFM; - private bool _useCustomRuntime; - private ObservableCollection _customJits; - private string _selectedCustomJit; - private string _graphvisDot; - private string _overridenJitDisasm; - private bool _fgEnable; - private string _overridenTfm; - - public SettingsViewModel() - { - PathToLocalCoreClr = Settings.Default.PathToCoreCLR_V9; - ShowAsmComments = Settings.Default.ShowAsmComments_V9; - CustomEnvVars = Settings.Default.CustomEnvVars3_V15.Replace(";;", Environment.NewLine); - Crossgen2Args = Settings.Default.CrossgenArgs_V6; - IlcArgs = Settings.Default.IlcArgs_V9.Replace(";;", Environment.NewLine); - JitDumpInsteadOfDisasm = Settings.Default.JitDumpInsteadOfDisasm_V9; - UseDotnetBuildForReload = Settings.Default.UseDotnetBuildForReload_V9; - RunAppMode = Settings.Default.RunAppMode_V9; - UseNoRestoreFlag = Settings.Default.UseNoRestoreFlag_V9; - FontSize = Settings.Default.FontSize; - UpdateIsAvailable = false; - UseTieredJit = Settings.Default.UseTieredJit_V4; - UseCustomRuntime = Settings.Default.UseCustomRuntime_V4; - GraphvisDotPath = Settings.Default.GraphvisDotPath; - FgEnable = Settings.Default.FgEnable; - PrintInlinees = Settings.Default.PrintInlinees_V3; - UsePGO = Settings.Default.UsePGO; - Diffable = Settings.Default.Diffable; - UseUnloadableContext = Settings.Default.UseUnloadableContext; - DisableLightBulb = Settings.Default.DisableLightBulb; - DontGuessTFM = Settings.Default.DontGuessTFM; - OverridenTFM = Settings.Default.OverridenTFM; - CheckUpdates(); - } + private string _pathToLocalCoreClr; + private bool _jitDumpInsteadOfDisasm; + private string _customEnvVars; + private string _crossgen2Args; + private string _ilcArgs; + private bool _showAsmComments; + private bool _updateIsAvailable; + private Version _currentVersion; + private Version _availableVersion; + private bool _useDotnetPublishForReload; + private bool _useDotnetBuildForReload; + private bool _runAppMode; + private bool _printInlinees; + private double _fontSize; + private bool _useNoRestoreFlag; + private bool _disableLightBulb; + private bool _useTieredJit; + private bool _useUnloadableContext; + private bool _usePGO; + private bool _diffable; + private bool _dontGuessTFM; + private bool _useCustomRuntime; + private ObservableCollection _customJits; + private string _selectedCustomJit; + private string _graphvisDot; + private string _overridenJitDisasm; + private bool _fgEnable; + private string _overridenTfm; + + public SettingsViewModel() + { + PathToLocalCoreClr = Settings.Default.PathToCoreCLR_V9; + ShowAsmComments = Settings.Default.ShowAsmComments_V9; + CustomEnvVars = Settings.Default.CustomEnvVars3_V15.Replace(";;", Environment.NewLine); + Crossgen2Args = Settings.Default.CrossgenArgs_V6; + IlcArgs = Settings.Default.IlcArgs_V9.Replace(";;", Environment.NewLine); + JitDumpInsteadOfDisasm = Settings.Default.JitDumpInsteadOfDisasm_V9; + UseDotnetBuildForReload = Settings.Default.UseDotnetBuildForReload_V9; + RunAppMode = Settings.Default.RunAppMode_V9; + UseNoRestoreFlag = Settings.Default.UseNoRestoreFlag_V9; + FontSize = Settings.Default.FontSize; + UpdateIsAvailable = false; + UseTieredJit = Settings.Default.UseTieredJit_V4; + UseCustomRuntime = Settings.Default.UseCustomRuntime_V4; + GraphvisDotPath = Settings.Default.GraphvisDotPath; + FgEnable = Settings.Default.FgEnable; + PrintInlinees = Settings.Default.PrintInlinees_V3; + UsePGO = Settings.Default.UsePGO; + Diffable = Settings.Default.Diffable; + UseUnloadableContext = Settings.Default.UseUnloadableContext; + DisableLightBulb = Settings.Default.DisableLightBulb; + DontGuessTFM = Settings.Default.DontGuessTFM; + OverridenTFM = Settings.Default.OverridenTFM; + CheckUpdates(); + } - private async void CheckUpdates() - { - CurrentVersion = DisasmoPackage.Current?.GetCurrentVersion(); - AvailableVersion = await DisasmoPackage.GetLatestVersionOnline(); - if (CurrentVersion != null && AvailableVersion > CurrentVersion) - UpdateIsAvailable = true; - } + private async void CheckUpdates() + { + CurrentVersion = DisasmoPackage.Current?.GetCurrentVersion(); + AvailableVersion = await DisasmoPackage.GetLatestVersionOnline(); + if (CurrentVersion != null && AvailableVersion > CurrentVersion) + UpdateIsAvailable = true; + } - public static string Arch { get; set; } = "x64"; + public static string Arch { get; set; } = "x64"; - public bool FgEnable + public bool FgEnable + { + get => _fgEnable; + set { - get => _fgEnable; - set + Set(ref _fgEnable, value); + Settings.Default.FgEnable = value; + Settings.Default.Save(); + if (value) { - Set(ref _fgEnable, value); - Settings.Default.FgEnable = value; - Settings.Default.Save(); - if (value) - { - JitDumpInsteadOfDisasm = true; - } + JitDumpInsteadOfDisasm = true; } } + } - public string GraphvisDotPath + public string GraphvisDotPath + { + get => _graphvisDot; + set { - get => _graphvisDot; - set - { - Set(ref _graphvisDot, value); - Settings.Default.GraphvisDotPath = value; - Settings.Default.Save(); - } + Set(ref _graphvisDot, value); + Settings.Default.GraphvisDotPath = value; + Settings.Default.Save(); } + } - public string OverridenJitDisasm - { - // No need to save it - get => _overridenJitDisasm; - set => Set(ref _overridenJitDisasm, value); - } + public string OverridenJitDisasm + { + // No need to save it + get => _overridenJitDisasm; + set => Set(ref _overridenJitDisasm, value); + } - public string PathToLocalCoreClr + public string PathToLocalCoreClr + { + get => _pathToLocalCoreClr; + set { - get => _pathToLocalCoreClr; - set - { - Set(ref _pathToLocalCoreClr, value); - Settings.Default.PathToCoreCLR_V9 = value; - Settings.Default.Save(); + Set(ref _pathToLocalCoreClr, value); + Settings.Default.PathToCoreCLR_V9 = value; + Settings.Default.Save(); - if (PopulateCustomJits()) - { - return; - } + if (PopulateCustomJits()) + return; - SelectedCustomJit = null; - CustomJits?.Clear(); - } + SelectedCustomJit = null; + CustomJits?.Clear(); } + } - public bool IsNonCustomDotnetAotMode() - { - return !UseCustomRuntime && - (SelectedCustomJit == Crossgen || SelectedCustomJit == Ilc); - } + public bool IsNonCustomDotnetAotMode() => + !UseCustomRuntime && (SelectedCustomJit == Crossgen || SelectedCustomJit == Ilc); - public bool IsNonCustomNativeAOTMode() - { - return !UseCustomRuntime && SelectedCustomJit == Ilc; - } + public bool IsNonCustomNativeAOTMode() => + !UseCustomRuntime && SelectedCustomJit == Ilc; - public const string DefaultJit = "clrjit.dll"; - public const string Crossgen = "crossgen2.dll (R2R)"; - public const string Ilc = "ilc (NativeAOT)"; + public const string DefaultJit = "clrjit.dll"; + public const string Crossgen = "crossgen2.dll (R2R)"; + public const string Ilc = "ilc (NativeAOT)"; - public bool PopulateCustomJits() + public bool PopulateCustomJits() + { + if (!UseCustomRuntime) { - if (!UseCustomRuntime) - { - CustomJits = new ObservableCollection(); - CustomJits.Add(DefaultJit); + CustomJits = new ObservableCollection(); + CustomJits.Add(DefaultJit); - // TODO: - //CustomJits.Add(Crossgen); - CustomJits.Add(Ilc); - SelectedCustomJit = CustomJits[0]; - return true; - } + // TODO: + //CustomJits.Add(Crossgen); + CustomJits.Add(Ilc); + SelectedCustomJit = CustomJits[0]; + return true; + } - if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) + if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) + { + var jitDir = FindJitDirectory(_pathToLocalCoreClr); + if (jitDir != null) { - string jitDir = FindJitDirectory(_pathToLocalCoreClr); - if (jitDir != null) + string[] jits = Directory.GetFiles(jitDir, "clrjit*.dll"); + CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); + SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); + if (SelectedCustomJit != null) { - string[] jits = Directory.GetFiles(jitDir, "clrjit*.dll"); - CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); - SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); - if (SelectedCustomJit != null) - { - CustomJits.Add(Crossgen); - CustomJits.Add(Ilc); - } - return true; + CustomJits.Add(Crossgen); + CustomJits.Add(Ilc); } + return true; } - return false; } + return false; + } - public ObservableCollection CustomJits - { - get => _customJits; - set => Set(ref _customJits, value); - } + public ObservableCollection CustomJits + { + get => _customJits; + set => Set(ref _customJits, value); + } - public string SelectedCustomJit + public string SelectedCustomJit + { + get => _selectedCustomJit; + set { - get => _selectedCustomJit; - set + if (value?.StartsWith("crossgen") == true || value?.StartsWith("ilc") == true) { - if (value?.StartsWith("crossgen") == true || value?.StartsWith("ilc") == true) - { - RunAppMode = false; - UseTieredJit = false; - UsePGO = false; - JitDumpInsteadOfDisasm = false; - } - Set(ref _selectedCustomJit, value); + RunAppMode = false; + UseTieredJit = false; + UsePGO = false; + JitDumpInsteadOfDisasm = false; } + Set(ref _selectedCustomJit, value); } + } - public bool CrossgenIsSelected => SelectedCustomJit?.StartsWith("crossgen") == true; + public bool CrossgenIsSelected => SelectedCustomJit?.StartsWith("crossgen") == true; - public bool NativeAotIsSelected => SelectedCustomJit?.StartsWith("ilc") == true; + public bool NativeAotIsSelected => SelectedCustomJit?.StartsWith("ilc") == true; - public bool RunAppMode + public bool RunAppMode + { + get => _runAppMode; + set { - get => _runAppMode; - set + Set(ref _runAppMode, value); + Settings.Default.RunAppMode_V9 = value; + Settings.Default.Save(); + if (value) { - Set(ref _runAppMode, value); - Settings.Default.RunAppMode_V9 = value; - Settings.Default.Save(); - if (value) - { - UseUnloadableContext = false; - } + UseUnloadableContext = false; } } + } - public string OverridenTFM + public string OverridenTFM + { + get => _overridenTfm; + set { - get => _overridenTfm; - set - { - Set("OverridenTFM", ref _overridenTfm, value); - Settings.Default.OverridenTFM = value; - Settings.Default.Save(); - } + Set("OverridenTFM", ref _overridenTfm, value); + Settings.Default.OverridenTFM = value; + Settings.Default.Save(); } + } - public bool PrintInlinees + public bool PrintInlinees + { + get => _printInlinees; + set { - get => _printInlinees; - set + Set(ref _printInlinees, value); + if (value) { - Set(ref _printInlinees, value); - if (value) - { - // Reset "Use JitDump" flag which should also reset FlowGraph flag - JitDumpInsteadOfDisasm = false; - } - - Settings.Default.PrintInlinees_V3 = value; - Settings.Default.Save(); + // Reset "Use JitDump" flag which should also reset FlowGraph flag + JitDumpInsteadOfDisasm = false; } + + Settings.Default.PrintInlinees_V3 = value; + Settings.Default.Save(); } + } - public bool UsePGO + public bool UsePGO + { + get => _usePGO; + set { - get => _usePGO; - set - { - Set(ref _usePGO, value); - if (value) - this.UseTieredJit = true; + Set(ref _usePGO, value); - Settings.Default.UsePGO = value; - Settings.Default.Save(); + if (value) + { + UseTieredJit = true; } + + Settings.Default.UsePGO = value; + Settings.Default.Save(); } + } - public bool Diffable + public bool Diffable + { + get => _diffable; + set { - get => _diffable; - set - { - Set(ref _diffable, value); - Settings.Default.Diffable = value; - Settings.Default.Save(); - } + Set(ref _diffable, value); + Settings.Default.Diffable = value; + Settings.Default.Save(); } + } - public bool UseNoRestoreFlag + public bool UseNoRestoreFlag + { + get => _useNoRestoreFlag; + set { - get => _useNoRestoreFlag; - set - { - Set(ref _useNoRestoreFlag, value); - Settings.Default.UseNoRestoreFlag_V9 = value; - Settings.Default.Save(); - } + Set(ref _useNoRestoreFlag, value); + Settings.Default.UseNoRestoreFlag_V9 = value; + Settings.Default.Save(); } + } - public bool DisableLightBulb + public bool DisableLightBulb + { + get => _disableLightBulb; + set { - get => _disableLightBulb; - set - { - Set(ref _disableLightBulb, value); - Settings.Default.DisableLightBulb = value; - Settings.Default.Save(); - } + Set(ref _disableLightBulb, value); + Settings.Default.DisableLightBulb = value; + Settings.Default.Save(); } + } - public double FontSize + public double FontSize + { + get => _fontSize; + set { - get => _fontSize; - set - { - Set(ref _fontSize, value); - Settings.Default.FontSize = value; - Settings.Default.Save(); - } + Set(ref _fontSize, value); + Settings.Default.FontSize = value; + Settings.Default.Save(); } + } public bool UseCustomRuntime + { + get => _useCustomRuntime; + set { - get => _useCustomRuntime; - set - { - Set(ref _useCustomRuntime, value); - Settings.Default.UseCustomRuntime_V4 = value; - Settings.Default.Save(); - if (!value) - { - JitDumpInsteadOfDisasm = false; - UseDotnetPublishForReload = false; - UseDotnetBuildForReload = true; - PrintInlinees = false; - } - PopulateCustomJits(); - } - } + Set(ref _useCustomRuntime, value); + Settings.Default.UseCustomRuntime_V4 = value; + Settings.Default.Save(); - public bool UseDotnetPublishForReload - { - get => _useDotnetPublishForReload; - set + if (!value) { - Set(ref _useDotnetPublishForReload, value); - Set(ref _useDotnetBuildForReload, !value); - Settings.Default.UseDotnetBuildForReload_V9 = !value; - Settings.Default.Save(); + JitDumpInsteadOfDisasm = false; + UseDotnetPublishForReload = false; + UseDotnetBuildForReload = true; + PrintInlinees = false; } - } - public bool UseDotnetBuildForReload - { - get => _useDotnetBuildForReload; - set - { - Set(ref _useDotnetBuildForReload, value); - Set(ref _useDotnetPublishForReload, !value); - Settings.Default.UseDotnetBuildForReload_V9 = value; - Settings.Default.Save(); - } + PopulateCustomJits(); } + } - public bool JitDumpInsteadOfDisasm + public bool UseDotnetPublishForReload + { + get => _useDotnetPublishForReload; + set { - get => _jitDumpInsteadOfDisasm; - set - { - Set(ref _jitDumpInsteadOfDisasm, value); - Settings.Default.JitDumpInsteadOfDisasm_V9 = value; - Settings.Default.Save(); - if (!value) - { - FgEnable = false; - } - else - { - PrintInlinees = false; - } - } + Set(ref _useDotnetPublishForReload, value); + Set(ref _useDotnetBuildForReload, !value); + Settings.Default.UseDotnetBuildForReload_V9 = !value; + Settings.Default.Save(); } + } - public bool UseTieredJit + public bool UseDotnetBuildForReload + { + get => _useDotnetBuildForReload; + set { - get => _useTieredJit; - set - { - Set(ref _useTieredJit, value); - Settings.Default.UseTieredJit_V4 = value; - Settings.Default.Save(); - } + Set(ref _useDotnetBuildForReload, value); + Set(ref _useDotnetPublishForReload, !value); + Settings.Default.UseDotnetBuildForReload_V9 = value; + Settings.Default.Save(); } + } - public bool DontGuessTFM + public bool JitDumpInsteadOfDisasm + { + get => _jitDumpInsteadOfDisasm; + set { - get => _dontGuessTFM; - set + Set(ref _jitDumpInsteadOfDisasm, value); + Settings.Default.JitDumpInsteadOfDisasm_V9 = value; + Settings.Default.Save(); + + if (!value) { - Set(ref _dontGuessTFM, value); - Settings.Default.DontGuessTFM = value; - Settings.Default.Save(); + FgEnable = false; } - } - - public bool UseUnloadableContext - { - get => _useUnloadableContext; - set + else { - Set(ref _useUnloadableContext, value); - Settings.Default.UseUnloadableContext = value; - Settings.Default.Save(); - if (value) - { - RunAppMode = false; - } + PrintInlinees = false; } } + } - public bool ShowAsmComments + public bool UseTieredJit + { + get => _useTieredJit; + set { - get => _showAsmComments; - set - { - Set(ref _showAsmComments, value); - Settings.Default.ShowAsmComments_V9 = value; - Settings.Default.Save(); - } + Set(ref _useTieredJit, value); + Settings.Default.UseTieredJit_V4 = value; + Settings.Default.Save(); } + } - public string CustomEnvVars + public bool DontGuessTFM + { + get => _dontGuessTFM; + set { - get => _customEnvVars; - set - { - Set(ref _customEnvVars, value); - Settings.Default.CustomEnvVars3_V15 = value; - Settings.Default.Save(); - } + Set(ref _dontGuessTFM, value); + Settings.Default.DontGuessTFM = value; + Settings.Default.Save(); } + } - public string Crossgen2Args + public bool UseUnloadableContext + { + get => _useUnloadableContext; + set { - get => _crossgen2Args; - set + Set(ref _useUnloadableContext, value); + Settings.Default.UseUnloadableContext = value; + Settings.Default.Save(); + + if (value) { - Set(ref _crossgen2Args, value); - Settings.Default.CrossgenArgs_V6 = value; - Settings.Default.Save(); + RunAppMode = false; } } + } - public string IlcArgs + public bool ShowAsmComments + { + get => _showAsmComments; + set { - get => _ilcArgs; - set - { - Set(ref _ilcArgs, value); - Settings.Default.IlcArgs_V9 = value; - Settings.Default.Save(); - } + Set(ref _showAsmComments, value); + Settings.Default.ShowAsmComments_V9 = value; + Settings.Default.Save(); } + } - public bool UpdateIsAvailable + public string CustomEnvVars + { + get => _customEnvVars; + set { - get => _updateIsAvailable; - set { Set(ref _updateIsAvailable, value); } + Set(ref _customEnvVars, value); + Settings.Default.CustomEnvVars3_V15 = value; + Settings.Default.Save(); } + } - public Version CurrentVersion + public string Crossgen2Args + { + get => _crossgen2Args; + set { - get => _currentVersion; - set => Set(ref _currentVersion, value); + Set(ref _crossgen2Args, value); + Settings.Default.CrossgenArgs_V6 = value; + Settings.Default.Save(); } + } - public Version AvailableVersion + public string IlcArgs + { + get => _ilcArgs; + set { - get => _availableVersion; - set => Set(ref _availableVersion, value); + Set(ref _ilcArgs, value); + Settings.Default.IlcArgs_V9 = value; + Settings.Default.Save(); } + } - public ICommand BrowseCommand => new RelayCommand(() => - { - var dialog = new FolderBrowserDialog(); - var result = dialog.ShowDialog(); - if (result == DialogResult.OK) - PathToLocalCoreClr = dialog.SelectedPath; - }); + public bool UpdateIsAvailable + { + get => _updateIsAvailable; + set { Set(ref _updateIsAvailable, value); } + } - public void FillWithUserVars(Dictionary dictionary) - { - if (string.IsNullOrWhiteSpace(CustomEnvVars)) - return; + public Version CurrentVersion + { + get => _currentVersion; + set => Set(ref _currentVersion, value); + } - var pairs = CustomEnvVars.Split(new [] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries); - foreach (var pair in pairs) - { - var parts = pair.Split('='); - if (parts.Length == 2) - dictionary[parts[0].Trim()] = parts[1].Trim(); - } - } + public Version AvailableVersion + { + get => _availableVersion; + set => Set(ref _availableVersion, value); + } - private static string FindJitDirectory(string basePath) - { - string jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } + public ICommand BrowseCommand => new RelayCommand(() => + { + var dialog = new FolderBrowserDialog(); + var result = dialog.ShowDialog(); + if (result == DialogResult.OK) + PathToLocalCoreClr = dialog.SelectedPath; + }); - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } + public void FillWithUserVars(Dictionary dictionary) + { + if (string.IsNullOrWhiteSpace(CustomEnvVars)) + return; - return null; + var pairs = CustomEnvVars.Split(new [] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries); + foreach (var pair in pairs) + { + var parts = pair.Split('='); + if (parts.Length == 2) + dictionary[parts[0].Trim()] = parts[1].Trim(); } } -} + + private static string FindJitDirectory(string basePath) + { + var jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); + if (Directory.Exists(jitDir)) + return jitDir; + + jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); + if (Directory.Exists(jitDir)) + return jitDir; + + return null; + } +} \ No newline at end of file diff --git a/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs b/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs index de72944..3e2d952 100644 --- a/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs +++ b/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs @@ -3,14 +3,13 @@ using System.Windows; using System.Windows.Data; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public class InversedBooleanToVisibilityConverter : IValueConverter { - public class InversedBooleanToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => - value is true ? Visibility.Collapsed : Visibility.Visible; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value is true ? Visibility.Collapsed : Visibility.Visible; - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => - throw new NotImplementedException(); - } -} + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/Vsix/Views/DisasmWindow.cs b/src/Vsix/Views/DisasmWindow.cs index ea594f0..a994ff2 100644 --- a/src/Vsix/Views/DisasmWindow.cs +++ b/src/Vsix/Views/DisasmWindow.cs @@ -6,11 +6,11 @@ namespace Disasmo; [Guid("97cd0cd6-1d77-4848-8b6e-dc82cdccc6d7")] public class DisasmWindow : ToolWindowPane { - public MainViewModel ViewModel => (MainViewModel) ((DisasmWindowControl) Content).DataContext; + public MainViewModel ViewModel => (MainViewModel)((DisasmWindowControl)Content).DataContext; public DisasmWindow() : base(null) { Caption = "Disasmo"; Content = new DisasmWindowControl(); } -} +} \ No newline at end of file From 930b09c2f0bcbaccd4145c4b6cf2461dfd6e75f9 Mon Sep 17 00:00:00 2001 From: Egor Bochkarev Date: Sun, 9 Nov 2025 12:20:53 +0300 Subject: [PATCH 02/21] Correct the description of DisassemblyPrettifier.Prettify. Fix bottom name of the method. Also add additional info about method compilation. --- Disasmo/Utils/DisassemblyPrettifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Disasmo/Utils/DisassemblyPrettifier.cs b/Disasmo/Utils/DisassemblyPrettifier.cs index c10c059..eec3288 100644 --- a/Disasmo/Utils/DisassemblyPrettifier.cs +++ b/Disasmo/Utils/DisassemblyPrettifier.cs @@ -24,7 +24,7 @@ public static class DisassemblyPrettifier /// G_M42249_IG03: /// C3 ret /// - /// ; Total bytes of code 76, prolog size 5 for method Program:SelectBucketIndex_old(int):int + /// ; Total bytes of code 76, prolog size 5, PerfScore 41.52, instruction count 3, bla-bla for method Program:MyMethod():int (Tier0) /// ; ============================================================ /// public static string Prettify(string rawAsm, bool minimalComments) From df3b8e7fa572c0e3518d3001a7aec353b6505307 Mon Sep 17 00:00:00 2001 From: Egor Bochkarev Date: Tue, 11 Nov 2025 09:01:06 +0300 Subject: [PATCH 03/21] Refactor DisassemblyPrettifier. Refactor DisassemblyPrettifier. Remove unused checks. Get rid of unnecessary ToList() conversions. And other optimizations. --- Disasmo/Utils/DisassemblyPrettifier.cs | 133 +++++++++++++++------- Disasmo/Utils/IntrinsicsSourcesService.cs | 3 +- src/Vsix/Utils/IdeUtils.cs | 2 +- 3 files changed, 96 insertions(+), 42 deletions(-) diff --git a/Disasmo/Utils/DisassemblyPrettifier.cs b/Disasmo/Utils/DisassemblyPrettifier.cs index eec3288..d27303b 100644 --- a/Disasmo/Utils/DisassemblyPrettifier.cs +++ b/Disasmo/Utils/DisassemblyPrettifier.cs @@ -29,26 +29,29 @@ public static class DisassemblyPrettifier /// public static string Prettify(string rawAsm, bool minimalComments) { + const string MethodStartedMarker = "; Assembly listing for method "; + if (!minimalComments) return rawAsm; try - { - var lines = rawAsm.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); + { + var lines = rawAsm.Split(["\r\n", "\n", "\t"], StringSplitOptions.RemoveEmptyEntries); var blocks = new List(); var prevBlock = BlockType.Unknown; - var currentMethod = ""; + var currentMethod = string.Empty; foreach (var line in lines) { - if (line.Contains("; Assembly listing for method ")) + if (line.StartsWith(MethodStartedMarker)) { - currentMethod = line.Remove(0, "; Assembly listing for method ".Length); + currentMethod = line.Remove(0, MethodStartedMarker.Length); } - else if (currentMethod == "") + else if (currentMethod == string.Empty) // In case disasm's output format has changed { - return rawAsm; // in case if format is changed + Log($"Changed disasm's output format was detected."); + return rawAsm; } var currentBlock = BlockType.Unknown; @@ -60,7 +63,7 @@ public static string Prettify(string rawAsm, bool minimalComments) { continue; } - else + else { currentBlock = BlockType.Code; if (Regex.IsMatch(line, @"^\w+:")) @@ -71,12 +74,18 @@ public static string Prettify(string rawAsm, bool minimalComments) if (currentBlock != prevBlock) { - blocks.Add(new Block { MethodName = currentMethod, Type = currentBlock, Data = $"\n{line}\n" }); + var block = new Block(methodName: currentMethod, type: currentBlock); + var data = block.MutableData; + data.AppendLine().Append(line).AppendLine(); + + blocks.Add(block); prevBlock = currentBlock; } else { - blocks[blocks.Count - 1].Data += line + "\n"; + var block = blocks[blocks.Count - 1]; + var data = block.MutableData; + data.Append(line).AppendLine(); } } @@ -85,49 +94,51 @@ public static string Prettify(string rawAsm, bool minimalComments) foreach (var method in blocksByMethods) { - var methodBlocks = method.ToList(); + var methodBlocks = (IEnumerable)method; var size = ParseMethodTotalSizes(methodBlocks); - if (minimalComments) - { - methodBlocks = methodBlocks.Where(m => m.Type != BlockType.Comments).ToList(); - output.Append($"; Method {method.Key}"); - } + methodBlocks = methodBlocks.Where(m => m.Type != BlockType.Comments); + output.Append($"; Method {method.Key}"); foreach (var block in methodBlocks) - output.Append(block.Data); + output.Append(block.ImmutableData); - if (minimalComments) - { - output.Append("; Total bytes of code: ") - .Append(size) - .AppendLine() - .AppendLine(); - } + output.Append("; Total bytes of code: ") + .Append(size) + .AppendLine() + .AppendLine(); } return output.ToString(); } - catch + catch (Exception ex) when (ex is not MemberAccessException) // In case disasm's output format has changed { - return rawAsm; // format is changed - leave it as is + Log($"Exception. Disasm's output format may have changed."); } - } + catch { } - private static int ParseMethodTotalSizes(List methodBlocks) - { - const string marker = "; Total bytes of code "; + return rawAsm; - var lineToParse = methodBlocks.First(b => b.Data.Contains(marker)).Data; - var comma = lineToParse.IndexOf(','); - var size = comma == -1 ? - lineToParse.Substring(marker.Length) : - lineToParse.Substring(marker.Length, lineToParse.IndexOf(',') - marker.Length); + static int ParseMethodTotalSizes(IEnumerable methodBlocks) + { + const string Marker = "; Total bytes of code "; + + var lineToParse = methodBlocks.Last().ImmutableData; + + var sizePartStartIndex = lineToParse.IndexOf(Marker) + Marker.Length; + var commaIndex = lineToParse.IndexOf(',', sizePartStartIndex); - return int.Parse(size); + var sizePartString = commaIndex == -1 ? + lineToParse.Substring(sizePartStartIndex) : + lineToParse.Substring(sizePartStartIndex, commaIndex - sizePartStartIndex); + + return int.Parse(sizePartString); + } } + private static void Log(string message) => UserLogger.Log($"[{nameof(DisassemblyPrettifier)}] {message}"); + private enum BlockType { Unknown, @@ -137,8 +148,52 @@ private enum BlockType private class Block { - public string MethodName { get; set; } - public BlockType Type { get; set; } - public string Data { get; set; } + public Block(string methodName, BlockType type) + { + MethodName = methodName; + Type = type; + + _mutableData = new StringBuilder(64); + } + + private StringBuilder _mutableData; + private string _immutableData; + + public string MethodName { get; private set; } + public BlockType Type { get; private set; } + + public StringBuilder MutableData + { + get + { + if (_mutableData == null) + { + var message = "Undefined behavior was detected. An attempt was made to access cleared data."; + + Log($"Exception. {message}"); + throw new MemberAccessException(message); + } + + return _mutableData; + } + } + + public string ImmutableData + { + get + { + if (_immutableData == null) + { + _immutableData = _mutableData.ToString(); + + // Clear the mutable string field after accessing the immutable string field for the first time + _mutableData = null; + } + + return _immutableData; + } + } + + private static void Log(string message) => UserLogger.Log($"[{typeof(DisassemblyPrettifier)}.{typeof(Block)}] {message}"); } } \ No newline at end of file diff --git a/Disasmo/Utils/IntrinsicsSourcesService.cs b/Disasmo/Utils/IntrinsicsSourcesService.cs index 5a4338e..2f974ce 100644 --- a/Disasmo/Utils/IntrinsicsSourcesService.cs +++ b/Disasmo/Utils/IntrinsicsSourcesService.cs @@ -112,7 +112,6 @@ public static async Task> ParseSourceFile(string url } } - public class IntrinsicsInfo { public string Comments { get; set; } @@ -125,4 +124,4 @@ public bool Contains(string str) } public override string ToString() => Method; -} +} \ No newline at end of file diff --git a/src/Vsix/Utils/IdeUtils.cs b/src/Vsix/Utils/IdeUtils.cs index 188c292..8ea337f 100644 --- a/src/Vsix/Utils/IdeUtils.cs +++ b/src/Vsix/Utils/IdeUtils.cs @@ -111,7 +111,7 @@ public static void RunDiffTools(string contentLeft, string contentRight) try { // Copied from https://github.com/madskristensen/FileDiffer/blob/main/src/Commands/DiffFilesCommand.cs#L48-L56 (c) madskristensen - var args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; + object args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", 256, ref args, ref args); } catch (Exception exc) From 247c25c5fa18a6189b692fb98d9a4ecc790db6a7 Mon Sep 17 00:00:00 2001 From: Egor Bochkarev Date: Tue, 11 Nov 2025 16:57:18 +0300 Subject: [PATCH 04/21] Switch null-checks from the equality operator to the extended version of the equality operator. --- Disasmo/Utils/DisassemblyPrettifier.cs | 4 ++-- Disasmo/Utils/ProcessUtils.cs | 8 ++++---- src/Vsix/Analyzers/Base/BaseSuggestedAction.cs | 2 +- .../Base/CommonSuggestedActionsSource.cs | 2 +- .../CommonSuggestedActionsSourceProvider.cs | 2 +- .../Analyzers/DisasmMethodOrClassAction.cs | 14 +++++++------- src/Vsix/DisasmoPackage.cs | 8 ++++---- src/Vsix/Utils/IdeUtils.cs | 8 ++++---- src/Vsix/Utils/TfmVersion.cs | 2 +- src/Vsix/ViewModels/IntrinsicsViewModel.cs | 2 +- src/Vsix/ViewModels/MainViewModel.cs | 18 +++++++++--------- src/Vsix/ViewModels/SettingsViewModel.cs | 4 ++-- .../Converters/BoolToFontWeightConverter.cs | 2 +- 13 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Disasmo/Utils/DisassemblyPrettifier.cs b/Disasmo/Utils/DisassemblyPrettifier.cs index d27303b..f7cc94c 100644 --- a/Disasmo/Utils/DisassemblyPrettifier.cs +++ b/Disasmo/Utils/DisassemblyPrettifier.cs @@ -166,7 +166,7 @@ public StringBuilder MutableData { get { - if (_mutableData == null) + if (_mutableData is null) { var message = "Undefined behavior was detected. An attempt was made to access cleared data."; @@ -182,7 +182,7 @@ public string ImmutableData { get { - if (_immutableData == null) + if (_immutableData is null) { _immutableData = _mutableData.ToString(); diff --git a/Disasmo/Utils/ProcessUtils.cs b/Disasmo/Utils/ProcessUtils.cs index ea987d6..60dee9e 100644 --- a/Disasmo/Utils/ProcessUtils.cs +++ b/Disasmo/Utils/ProcessUtils.cs @@ -34,10 +34,10 @@ public static async Task RunProcess( Arguments = args, }; - if (workingDir != null) + if (workingDir is not null) processStartInfo.WorkingDirectory = workingDir; - if (envVars != null) + if (envVars is not null) { foreach (var envVar in envVars) processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value; @@ -97,7 +97,7 @@ public static Task WaitForExitAsync(this Process process, CancellationToken canc private static void KillProccessSafe(this Process process) { - if (process == null) + if (process is null) return; try @@ -113,7 +113,7 @@ private static void KillProccessSafe(this Process process) private static string DumpEnvVars(Dictionary envVars) { - if (envVars == null) + if (envVars is null) return ""; var envVar = ""; diff --git a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs index 7056d52..4cf3cb3 100644 --- a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs +++ b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs @@ -30,7 +30,7 @@ public async Task ValidateAsync(CancellationToken cancellationToken) LastDocument = null; LastTokenPos = 0; var document = SnapshotSpan.Snapshot.TextBuffer.GetRelatedDocuments().FirstOrDefault(); - if (document != null && await IsValidSymbol(document, CaretPosition, cancellationToken)) + if (document is not null && await IsValidSymbol(document, CaretPosition, cancellationToken)) { LastDocument = document; LastTokenPos = CaretPosition; diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs index bc2e1bd..553bd59 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs @@ -44,7 +44,7 @@ public IEnumerable GetSuggestedActions( try { return _baseActions - .Where(a => a.LastDocument != null) + .Where(a => a.LastDocument is not null) .Select(a => { a.SnapshotSpan = range; diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs index 657a248..e501032 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs @@ -13,7 +13,7 @@ public class CommonSuggestedActionsSourceProvider : ISuggestedActionsSourceProvi { public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { - if (textBuffer == null && textView == null) + if (textBuffer is null && textView is null) return null; return new CommonSuggestedActionsSource(this, textView, textBuffer); diff --git a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs index 448dbbd..8e50367 100644 --- a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs +++ b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs @@ -17,7 +17,7 @@ public override async void Invoke(CancellationToken cancellationToken) { try { - if (LastDocument == null) + if (LastDocument is null) return; var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); @@ -40,7 +40,7 @@ protected override async Task IsValidSymbol(Document document, int tokenPo return false; var semanticModel = await document.GetSemanticModelAsync(cancellationToken); - if (semanticModel == null) + if (semanticModel is null) return false; var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken); @@ -102,26 +102,26 @@ public static async Task GetSymbolStatic(Document doc, int tok, Cancell try { var semanticModel = await doc.GetSemanticModelAsync(ct); - if (semanticModel == null) + if (semanticModel is null) return null; var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(ct); var token = syntaxTree.FindToken(tok); var parent = token.Parent; - if (parent == null) + if (parent is null) return null; var symbol = FindRelatedSymbol(semanticModel, parent, true, ct); - if (symbol == null && recursive) + if (symbol is null && recursive) { while (true) { parent = parent?.Parent; - if (parent == null) + if (parent is null) return null; symbol = FindRelatedSymbol(semanticModel, parent, false, ct); - if (symbol != null) + if (symbol is not null) { return symbol; } diff --git a/src/Vsix/DisasmoPackage.cs b/src/Vsix/DisasmoPackage.cs index 387a40c..17feb5e 100644 --- a/src/Vsix/DisasmoPackage.cs +++ b/src/Vsix/DisasmoPackage.cs @@ -37,7 +37,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var disasmoCmd = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); - if (disasmoCmd == null) + if (disasmoCmd is null) return; var binding = ""; @@ -46,7 +46,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); // prefer Text Editor over Global var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); - if (bindingPair != null && bindingPair.Contains("::")) + if (bindingPair is not null && bindingPair.Contains("::")) { binding = bindingPair.Substring(bindingPair.IndexOf("::", StringComparison.Ordinal) + 2); } @@ -149,7 +149,7 @@ public bool ExecuteCommand(DisasmoCommandArgs args, CommandExecutionContext cont IdeUtils.DTE().SaveActiveDocument(); var document = args.TextView?.TextBuffer?.GetRelatedDocuments()?.FirstOrDefault(); - if (document != null) + if (document is not null) { var pos = GetCaretPosition(args.TextView); if (pos != -1) @@ -158,7 +158,7 @@ async void CallBack(object _) { try { - if (DisasmoPackage.Current == null) + if (DisasmoPackage.Current is null) { MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); return; diff --git a/src/Vsix/Utils/IdeUtils.cs b/src/Vsix/Utils/IdeUtils.cs index 8ea337f..53f615d 100644 --- a/src/Vsix/Utils/IdeUtils.cs +++ b/src/Vsix/Utils/IdeUtils.cs @@ -21,7 +21,7 @@ public static class IdeUtils public static Project GetActiveProject(this DTE dte, string filePath) { // find project by full name - if (dte.Solution != null) + if (dte.Solution is not null) { foreach (var projectObject in dte.Solution.Projects) { @@ -33,7 +33,7 @@ public static Project GetActiveProject(this DTE dte, string filePath) } var activeSolutionProjects = dte.ActiveSolutionProjects as Array; - if (activeSolutionProjects != null && activeSolutionProjects.Length > 0) + if (activeSolutionProjects is not null && activeSolutionProjects.Length > 0) return activeSolutionProjects.GetValue(0) as Project; return null; @@ -68,7 +68,7 @@ public static async Task ShowWindowAsync(bool tryTwice, CancellationToken { try { - if (DisasmoPackage.Current == null) + if (DisasmoPackage.Current is null) { MessageBox.Show("DisasmoPackage is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); return null; @@ -142,7 +142,7 @@ public static string GetConfigurationDimension(ProjectConfiguration projectConfi public static TfmVersion GetTargetFrameworkVersionDimension(ProjectConfiguration projectConfiguration) { var targetFramework = GetTargetFrameworkDimension(projectConfiguration); - return targetFramework != null ? TfmVersion.Parse(targetFramework) : null; + return targetFramework is not null ? TfmVersion.Parse(targetFramework) : null; } public static async Task GetProjectProperties(UnconfiguredProject unconfiguredProject, ProjectConfiguration projectConfiguration) diff --git a/src/Vsix/Utils/TfmVersion.cs b/src/Vsix/Utils/TfmVersion.cs index af998a2..c29bead 100644 --- a/src/Vsix/Utils/TfmVersion.cs +++ b/src/Vsix/Utils/TfmVersion.cs @@ -16,7 +16,7 @@ public class TfmVersion : IComparable public int CompareTo(TfmVersion other) { - if (other == null) + if (other is null) return 1; var majorCmp = Nullable.Compare(Major, other.Major); diff --git a/src/Vsix/ViewModels/IntrinsicsViewModel.cs b/src/Vsix/ViewModels/IntrinsicsViewModel.cs index 7b1bd1f..e48c65d 100644 --- a/src/Vsix/ViewModels/IntrinsicsViewModel.cs +++ b/src/Vsix/ViewModels/IntrinsicsViewModel.cs @@ -53,7 +53,7 @@ public string Input { Set(ref _input, value); StartDownloadSources(); - if (_intrinsics == null || string.IsNullOrWhiteSpace(value) || value.Length < 3) + if (_intrinsics is null || string.IsNullOrWhiteSpace(value) || value.Length < 3) { Suggestions = null; } diff --git a/src/Vsix/ViewModels/MainViewModel.cs b/src/Vsix/ViewModels/MainViewModel.cs index 9a98026..245cf1f 100644 --- a/src/Vsix/ViewModels/MainViewModel.cs +++ b/src/Vsix/ViewModels/MainViewModel.cs @@ -157,7 +157,7 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p { try { - if (_currentSymbol == null || string.IsNullOrWhiteSpace(_currentProjectPath)) + if (_currentSymbol is null || string.IsNullOrWhiteSpace(_currentProjectPath)) return; await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -176,7 +176,7 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p try { - if (projectProperties != null) + if (projectProperties is not null) { var customAsmName = await projectProperties.GetEvaluatedPropertyValueAsync("AssemblyName"); if (!string.IsNullOrWhiteSpace(customAsmName)) @@ -553,7 +553,7 @@ private string PreprocessOutput(string output) private UnconfiguredProject GetUnconfiguredProject(Project project) { var context = project as IVsBrowseObjectContext; - if (context == null && project != null) + if (context is null && project is not null) context = project.Object as IVsBrowseObjectContext; return context?.UnconfiguredProject; @@ -643,7 +643,7 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) _currentProject = project; Output = ""; - if (symbol == null) + if (symbol is null) { Output = "Symbol is not recognized, put cursor on a function/class name"; return; @@ -670,7 +670,7 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) // Find Release-{SettingsViewModel.Arch} configuration: var currentProject = dte.GetActiveProject(project.FilePath); - if (currentProject == null) + if (currentProject is null) { Output = "There no active project. Please re-open solution."; return; @@ -711,7 +711,7 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) var currentTfmVersion = TfmVersion.Parse(_currentTf); // find the best suitable project configuration projectConfiguration = projectConfigurations - .FirstOrDefault(cfg => currentTfmVersion != null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) + .FirstOrDefault(cfg => currentTfmVersion is not null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) ?? projectConfigurations.FirstOrDefault(); } @@ -719,10 +719,10 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) ThrowIfCanceled(); // resolve target framework - if (_currentTf == null) + if (_currentTf is null) { int? major; - if (projectProperties != null) + if (projectProperties is not null) { _currentTf = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); major = TfmVersion.Parse(_currentTf)?.Major; @@ -813,7 +813,7 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) } } - var outputDir = projectProperties == null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); + var outputDir = projectProperties is null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); DisasmoOutDir = Path.Combine(outputDir, DisasmoFolder + (SettingsVm.UseDotnetPublishForReload ? "_published" : "")); var currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); diff --git a/src/Vsix/ViewModels/SettingsViewModel.cs b/src/Vsix/ViewModels/SettingsViewModel.cs index b3e95d5..3c7c358 100644 --- a/src/Vsix/ViewModels/SettingsViewModel.cs +++ b/src/Vsix/ViewModels/SettingsViewModel.cs @@ -156,12 +156,12 @@ public bool PopulateCustomJits() if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) { var jitDir = FindJitDirectory(_pathToLocalCoreClr); - if (jitDir != null) + if (jitDir is not null) { string[] jits = Directory.GetFiles(jitDir, "clrjit*.dll"); CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); - if (SelectedCustomJit != null) + if (SelectedCustomJit is not null) { CustomJits.Add(Crossgen); CustomJits.Add(Ilc); diff --git a/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs b/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs index 8e02eee..7f871dd 100644 --- a/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs +++ b/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs @@ -8,7 +8,7 @@ namespace Disasmo.Utils; public class BoolToFontWeightConverter : DependencyObject, IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => - value != null && (bool)value ? FontWeights.Bold : FontWeights.Normal; + value is not null && (bool)value ? FontWeights.Bold : FontWeights.Normal; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing; } \ No newline at end of file From 5df1555252e38f08d780862e7731da05034ef013 Mon Sep 17 00:00:00 2001 From: Egor Bochkarev Date: Thu, 13 Nov 2025 17:12:40 +0300 Subject: [PATCH 05/21] Cleanup code (2) Refactor the code by improving naming, simplifying logic and optimization code. Rename constants and variables names. Change comments. --- Disasmo/Utils/IntrinsicsSourcesService.cs | 64 +-- Disasmo/Utils/LoaderAppManager.cs | 70 ++-- Disasmo/Utils/ProcessUtils.cs | 27 +- Disasmo/Utils/SymbolUtils.cs | 30 +- Disasmo/Utils/TextUtils.cs | 6 +- .../Analyzers/Base/BaseSuggestedAction.cs | 13 +- .../Base/CommonSuggestedActionsSource.cs | 35 +- .../Analyzers/DisasmMethodOrClassAction.cs | 104 +++-- src/Vsix/DisasmoPackage.cs | 82 ++-- src/Vsix/Properties/Settings.Designer.cs | 18 +- src/Vsix/Utils/IdeUtils.cs | 68 ++-- src/Vsix/Utils/TfmVersion.cs | 12 +- src/Vsix/Utils/ValidationRuleStringAsInt.cs | 5 +- src/Vsix/ViewModels/FlowgraphItemViewModel.cs | 18 +- src/Vsix/ViewModels/IntrinsicsViewModel.cs | 4 +- src/Vsix/ViewModels/MainViewModel.cs | 366 +++++++++--------- src/Vsix/ViewModels/SettingsViewModel.cs | 58 +-- src/Vsix/Views/DisasmWindowControl.xaml | 108 +++--- src/Vsix/Views/DisasmWindowControl.xaml.cs | 64 +-- 19 files changed, 587 insertions(+), 565 deletions(-) diff --git a/Disasmo/Utils/IntrinsicsSourcesService.cs b/Disasmo/Utils/IntrinsicsSourcesService.cs index 2f974ce..36b0240 100644 --- a/Disasmo/Utils/IntrinsicsSourcesService.cs +++ b/Disasmo/Utils/IntrinsicsSourcesService.cs @@ -13,10 +13,11 @@ public static class IntrinsicsSourcesService { public static async Task> ParseIntrinsics(Action progressReporter) { - var result = new List(600); - const string baseUrl = - "https://raw.githubusercontent.com/dotnet/runtime/main/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; - string[] files = { + const string RuntimeUrl = "https://raw.githubusercontent.com/dotnet/runtime/main/src"; + const string IntrinsicsBaseUrl = RuntimeUrl + "/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; + + string[] files = + [ "X86/Aes.cs", "X86/Avx.cs", "X86/Avx2.cs", @@ -59,56 +60,55 @@ public static async Task> ParseIntrinsics(Action pr "Vector256_1.cs", "Vector512.cs", "Vector512_1.cs", - }; + ]; + var result = new List(8192); foreach (var file in files) { progressReporter(file); - result.AddRange(await ParseSourceFile(baseUrl + file)); + var fullUrl = IntrinsicsBaseUrl + file; + await ParseSourceFile(fullUrl, result); } return result; } - public static async Task> ParseSourceFile(string url) + public static async Task ParseSourceFile(string url, List outputIntrinsics) { - var client = new HttpClient(); + using var client = new HttpClient(); var content = await client.GetStringAsync(url); - var result = new List(); using var workspace = new AdhocWorkspace(); - var proj = + var project = workspace .AddProject("ParseIntrinsics", LanguageNames.CSharp) - .WithMetadataReferences(new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); - var doc = proj.AddDocument("foo", SourceText.From(content)); - var compilation = await doc.Project.GetCompilationAsync(); - var root = await doc.GetSyntaxRootAsync(); + .WithMetadataReferences([MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]); + var document = project.AddDocument("foo", SourceText.From(content)); + var compilation = await document.Project.GetCompilationAsync(); + var root = await document.GetSyntaxRootAsync(); var model = compilation.GetSemanticModel(root.SyntaxTree); var methods = root.DescendantNodes().OfType().ToList(); foreach (var method in methods) { var tokens = method.ChildTokens().ToArray(); - if (tokens.Length > 0) - { - var trivia = tokens.FirstOrDefault().LeadingTrivia; - var comments = string.Join("\n", - trivia - .ToString().Split('\n').Select(i => i.Trim(' ', '\r', '\t')) - .Where(i => !string.IsNullOrWhiteSpace(i))); - var symbol = model.GetDeclaredSymbol(method); - var methodName = symbol.ToString() - .Replace("System.Runtime.Intrinsics.X86.", "") - .Replace("System.Runtime.Intrinsics.Arm.", "") - .Replace("System.Runtime.Intrinsics.", ""); - - var returnType = method.ReturnType.ToString(); - result.Add(new IntrinsicsInfo { Method = returnType + " " + methodName, Comments = comments }); - } - } + if (tokens.Length == 0) + continue; - return result; + var trivia = tokens.FirstOrDefault().LeadingTrivia; + var comments = string.Join("\n", + trivia + .ToString().Split('\n').Select(i => i.Trim(' ', '\r', '\t')) + .Where(i => !string.IsNullOrWhiteSpace(i))); + var symbol = model.GetDeclaredSymbol(method); + var methodName = symbol.ToString() + .Replace("System.Runtime.Intrinsics.X86.", "") + .Replace("System.Runtime.Intrinsics.Arm.", "") + .Replace("System.Runtime.Intrinsics.", ""); + + var returnType = method.ReturnType.ToString(); + outputIntrinsics.Add(new IntrinsicsInfo { Method = returnType + " " + methodName, Comments = comments }); + }; } } diff --git a/Disasmo/Utils/LoaderAppManager.cs b/Disasmo/Utils/LoaderAppManager.cs index db376d1..f2dfe22 100644 --- a/Disasmo/Utils/LoaderAppManager.cs +++ b/Disasmo/Utils/LoaderAppManager.cs @@ -12,9 +12,12 @@ public static class LoaderAppManager { public static readonly string DisasmoLoaderName = "DisasmoLoader4"; - private static async Task GetPathToLoader(string tf, Version addinVersion, CancellationToken ct) + private static async Task GetPathToLoader( + string targetFramework, + Version disasmoVersion, + CancellationToken cancellationToken) { - var dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: ct); + var dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: cancellationToken); UserLogger.Log($"dotnet --version: {dotnetVersion.Output} ({dotnetVersion.Error})"); var version = dotnetVersion.Output.Trim(); if (!char.IsDigit(version[0])) @@ -22,72 +25,77 @@ private static async Task GetPathToLoader(string tf, Version addinVersio // Something went wrong, use a random to proceed version = Guid.NewGuid().ToString("N"); } - var folderName = $"{addinVersion}_{tf}_{version}"; + var folderName = $"{disasmoVersion}_{targetFramework}_{version}"; UserLogger.Log($"LoaderAppManager.GetPathToLoader: {folderName}"); return Path.Combine(Path.GetTempPath(), DisasmoLoaderName, folderName); } - public static async Task InitLoaderAndCopyTo(string tf, string dest, Action logger, Version addinVersion, CancellationToken ct) + public static async Task InitLoaderAndCopyTo( + string targetFramework, + string destination, + Action logger, + Version disasmoVersion, + CancellationToken cancellationToken) { - if (!Directory.Exists(dest)) - throw new InvalidOperationException($"ERROR: dest dir was not found: {dest}"); + if (!Directory.Exists(destination)) + throw new InvalidOperationException($"ERROR: destination directory was not found: {destination}"); - string dir; + string directory; try { logger("Getting SDK version..."); - dir = await GetPathToLoader(tf, addinVersion, ct); + directory = await GetPathToLoader(targetFramework, disasmoVersion, cancellationToken); } - catch (Exception exc) + catch (Exception ex) { - throw new InvalidOperationException("ERROR in LoaderAppManager.GetPathToLoader: " + exc); + throw new InvalidOperationException("ERROR in LoaderAppManager.GetPathToLoader: " + ex); } - var csproj = Path.Combine(dir, $"{DisasmoLoaderName}.csproj"); - var csfile = Path.Combine(dir, $"{DisasmoLoaderName}.cs"); - var outDll = Path.Combine(dir, "out", $"{DisasmoLoaderName}.dll"); - var outJson = Path.Combine(dir, "out", $"{DisasmoLoaderName}.runtimeconfig.json"); - var outDllDest = Path.Combine(dest, DisasmoLoaderName + ".dll"); - var outJsonDest = Path.Combine(dest, DisasmoLoaderName + ".runtimeconfig.json"); + var csproj = Path.Combine(directory, $"{DisasmoLoaderName}.csproj"); + var csfile = Path.Combine(directory, $"{DisasmoLoaderName}.cs"); + var outDll = Path.Combine(directory, "out", $"{DisasmoLoaderName}.dll"); + var outJson = Path.Combine(directory, "out", $"{DisasmoLoaderName}.runtimeconfig.json"); + var outDllDestination = Path.Combine(destination, DisasmoLoaderName + ".dll"); + var outJsonDestination = Path.Combine(destination, DisasmoLoaderName + ".runtimeconfig.json"); - if (File.Exists(outDllDest) && File.Exists(outJsonDest)) + if (File.Exists(outDllDestination) && File.Exists(outJsonDestination)) return; - if (!Directory.Exists(dir)) + if (!Directory.Exists(directory)) { - Directory.CreateDirectory(dir); + Directory.CreateDirectory(directory); } else if (File.Exists(outDll) && File.Exists(outJson)) { - File.Copy(outDll, outDllDest, true); - File.Copy(outJson, outJsonDest, true); + File.Copy(outDll, outDllDestination, overwrite: true); + File.Copy(outJson, outJsonDestination, overwrite: true); return; } logger($"Building '{DisasmoLoaderName}' project..."); - ct.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (!File.Exists(csfile)) - TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.cs_template", dir); + TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.cs_template", directory); if (!File.Exists(csproj)) - TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.csproj_template", dir, content => content.Replace("%tfm%", tf)); + TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.csproj_template", directory, content => content.Replace("%tfm%", targetFramework)); Debug.Assert(File.Exists(csfile)); Debug.Assert(File.Exists(csproj)); - ct.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var msg = await ProcessUtils.RunProcess("dotnet", "build -c Release", workingDir: dir, cancellationToken: ct); + var message = await ProcessUtils.RunProcess("dotnet", "build -c Release", workingDirectory: directory, cancellationToken: cancellationToken); if (!File.Exists(outDll) || !File.Exists(outJson)) { - throw new InvalidOperationException($"ERROR: 'dotnet build' did not produce expected binaries ('{outDll}'" + - $" and '{outJson}'):\n{msg.Output}\n\n{msg.Error}"); + var errorMessage = $"ERROR: 'dotnet build' did not produce expected binaries ('{outDll}' and '{outJson}'):\n{message.Output}\n\n{message.Error}"; + throw new InvalidOperationException(errorMessage); } - ct.ThrowIfCancellationRequested(); - File.Copy(outDll, outDllDest, true); - File.Copy(outJson, outJsonDest, true); + cancellationToken.ThrowIfCancellationRequested(); + File.Copy(outDll, outDllDestination, overwrite: true); + File.Copy(outJson, outJsonDestination, overwrite: true); } } \ No newline at end of file diff --git a/Disasmo/Utils/ProcessUtils.cs b/Disasmo/Utils/ProcessUtils.cs index 60dee9e..323b669 100644 --- a/Disasmo/Utils/ProcessUtils.cs +++ b/Disasmo/Utils/ProcessUtils.cs @@ -13,11 +13,11 @@ public static async Task RunProcess( string path, string args = "", Dictionary envVars = null, - string workingDir = null, + string workingDirectory = null, Action outputLogger = null, CancellationToken cancellationToken = default) { - UserLogger.Log($"\nExecuting a command in directory \"{workingDir}\":\n\t{path} {args}\nEnv.vars:\n{DumpEnvVars(envVars)}"); + UserLogger.Log($"\nExecuting a command in directory \"{workingDirectory}\":\n\t{path} {args}\nEnv.vars:\n{DumpEnvVars(envVars)}"); var logger = new StringBuilder(); var loggerForErrors = new StringBuilder(); @@ -34,8 +34,8 @@ public static async Task RunProcess( Arguments = args, }; - if (workingDir is not null) - processStartInfo.WorkingDirectory = workingDir; + if (workingDirectory is not null) + processStartInfo.WorkingDirectory = workingDirectory; if (envVars is not null) { @@ -69,9 +69,12 @@ public static async Task RunProcess( return new ProcessResult { Error = loggerForErrors.ToString().Trim('\r', '\n'), Output = logger.ToString().Trim('\r', '\n') }; } - catch (Exception e) + catch (Exception ex) { - return new ProcessResult { Error = $"RunProcess failed:{e.Message}.\npath={path}\nargs={args}\nworkingdir={workingDir ?? Environment.CurrentDirectory}\n{loggerForErrors}" }; + workingDirectory ??= Environment.CurrentDirectory; + var errorMessage = $"RunProcess failed:{ex.Message}.\npath={path}\nargs={args}\nworkingdir={workingDirectory}\n{loggerForErrors}"; + + return new ProcessResult { Error = errorMessage }; } finally { @@ -85,14 +88,14 @@ public static Task WaitForExitAsync(this Process process, CancellationToken canc if (process.HasExited) return Task.CompletedTask; - var tcs = new TaskCompletionSource(); + var completionSource = new TaskCompletionSource(); process.EnableRaisingEvents = true; - process.Exited += (sender, args) => tcs.TrySetResult(null); + process.Exited += (sender, args) => completionSource.TrySetResult(null); if (cancellationToken != default) - cancellationToken.Register(() => tcs.TrySetCanceled()); + cancellationToken.Register(() => completionSource.TrySetCanceled()); - return process.HasExited ? Task.CompletedTask : tcs.Task; + return process.HasExited ? Task.CompletedTask : completionSource.Task; } private static void KillProccessSafe(this Process process) @@ -105,9 +108,9 @@ private static void KillProccessSafe(this Process process) if (!process.HasExited) process.Kill(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } diff --git a/Disasmo/Utils/SymbolUtils.cs b/Disasmo/Utils/SymbolUtils.cs index f0b50ff..b823d81 100644 --- a/Disasmo/Utils/SymbolUtils.cs +++ b/Disasmo/Utils/SymbolUtils.cs @@ -6,25 +6,25 @@ public static class SymbolUtils { public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) { - string target; string hostType; + string target; string methodName; var prefix = ""; var containingType = symbol as ITypeSymbol ?? symbol.ContainingType; - // match all for nested types - if (containingType.ContainingType is { }) + // Match all for nested types + if (containingType.ContainingType is not null) { prefix = "*"; } else { - var ns = containingType.ContainingNamespace; - while (ns?.Name is { Length: > 0 } containingNamespace) + var @namespace = containingType.ContainingNamespace; + while (@namespace?.Name is { Length: > 0 } containingNamespace) { prefix = containingNamespace + "." + prefix; - ns = ns.ContainingNamespace; + @namespace = @namespace.ContainingNamespace; } } @@ -33,39 +33,37 @@ public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) if (containingType is INamedTypeSymbol { IsGenericType: true }) prefix += "*"; - if (symbol is IMethodSymbol ms) + if (symbol is IMethodSymbol methodSymbol) { - if (ms.MethodKind == MethodKind.LocalFunction) + hostType = symbol.ContainingType.ToString(); + if (methodSymbol.MethodKind == MethodKind.LocalFunction) { - // hack for mangled names + // Hack for mangled names target = prefix + ":*" + symbol.MetadataName + "*"; - hostType = symbol.ContainingType.ToString(); methodName = "*"; } - else if (ms.MethodKind == MethodKind.Constructor) + else if (methodSymbol.MethodKind == MethodKind.Constructor) { target = prefix + ":.ctor"; - hostType = symbol.ContainingType.ToString(); methodName = "*"; } else { target = prefix + ":" + symbol.MetadataName; - hostType = symbol.ContainingType.ToString(); methodName = symbol.MetadataName; } } else if (symbol is IPropertySymbol) { - target = prefix + ":get_" + symbol.MetadataName + " " + prefix + ":set_" + symbol.MetadataName; hostType = symbol.ContainingType.ToString(); + target = prefix + ":get_" + symbol.MetadataName + " " + prefix + ":set_" + symbol.MetadataName; methodName = symbol.MetadataName; } else { - // the whole class - target = prefix + ":*"; + // The whole class hostType = symbol.ToString(); + target = prefix + ":*"; methodName = "*"; } diff --git a/Disasmo/Utils/TextUtils.cs b/Disasmo/Utils/TextUtils.cs index c290b73..99e4fc2 100644 --- a/Disasmo/Utils/TextUtils.cs +++ b/Disasmo/Utils/TextUtils.cs @@ -14,14 +14,14 @@ public static void SaveEmbeddedResourceTo( if (File.Exists(filePath)) return; - using Stream stream = typeof(TextUtils).Assembly.GetManifestResourceStream("Disasmo.Resources." + resource); - using StreamReader reader = new StreamReader(stream); + using var stream = typeof(TextUtils).Assembly.GetManifestResourceStream("Disasmo.Resources." + resource); + using var reader = new StreamReader(stream); var content = reader.ReadToEnd(); File.WriteAllText(filePath, contentProcessor != null ? contentProcessor(content) : content); } public static string NormalizeLineEndings(this string text) => - // normalize endings (DiffTool constantly complains) + // Normalize endings (DiffTool constantly complains) text.Replace(Environment.NewLine, "\n") .Replace("\n", Environment.NewLine) + Environment.NewLine; } \ No newline at end of file diff --git a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs index 4cf3cb3..766b47c 100644 --- a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs +++ b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs @@ -28,12 +28,12 @@ public async Task ValidateAsync(CancellationToken cancellationToken) try { LastDocument = null; - LastTokenPos = 0; + LastTokenPosition = 0; var document = SnapshotSpan.Snapshot.TextBuffer.GetRelatedDocuments().FirstOrDefault(); - if (document is not null && await IsValidSymbol(document, CaretPosition, cancellationToken)) + if (document is not null && await IsValidSymbolAsync(document, CaretPosition, cancellationToken)) { LastDocument = document; - LastTokenPos = CaretPosition; + LastTokenPosition = CaretPosition; return true; } @@ -45,7 +45,7 @@ public async Task ValidateAsync(CancellationToken cancellationToken) } } - public int LastTokenPos { get; set; } + public int LastTokenPosition { get; set; } public Document LastDocument { get; set; } @@ -61,9 +61,10 @@ public async Task ValidateAsync(CancellationToken cancellationToken) ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CSLightswitch; - protected abstract Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken); + protected abstract Task IsValidSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken); - public Task> GetActionSetsAsync(CancellationToken cancellationToken) => null; + public Task> GetActionSetsAsync(CancellationToken cancellationToken) => + Task.FromResult((IEnumerable)null); public Task GetPreviewAsync(CancellationToken cancellationToken) => Task.FromResult(null); diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs index 553bd59..0896849 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs @@ -27,10 +27,7 @@ public CommonSuggestedActionsSource(CommonSuggestedActionsSourceProvider sourceP SourceProvider = sourceProvider; TextView = textView; TextBuffer = textBuffer; - _baseActions = new BaseSuggestedAction[] - { - new DisasmMethodOrClassAction(this), - }; + _baseActions = [new DisasmMethodOrClassAction(this)]; } public event EventHandler SuggestedActionsChanged; @@ -38,18 +35,19 @@ public CommonSuggestedActionsSource(CommonSuggestedActionsSourceProvider sourceP public void Dispose() { } public IEnumerable GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, CancellationToken cancellationToken) { try { return _baseActions - .Where(a => a.LastDocument is not null) - .Select(a => + .Where(action => action.LastDocument is not null) + .Select(action => { - a.SnapshotSpan = range; - a.CaretPosition = GetCaretPosition(); - return new SuggestedActionSet(PredefinedSuggestedActionCategoryNames.Any, new[] { a }); + action.SnapshotSpan = range; + action.CaretPosition = GetCaretPosition(); + return new SuggestedActionSet(PredefinedSuggestedActionCategoryNames.Any, [action]); }).ToArray(); } catch @@ -58,19 +56,20 @@ public IEnumerable GetSuggestedActions( } } - public async Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, CancellationToken cancellationToken) + public async Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) { try { - foreach (var t in _baseActions) + foreach (var action in _baseActions) { - t.SnapshotSpan = range; - t.CaretPosition = GetCaretPosition(); - if (await t.ValidateAsync(default)) - { + action.SnapshotSpan = range; + action.CaretPosition = GetCaretPosition(); + + if (await action.ValidateAsync(default)) return true; - } } } catch (Exception ex) diff --git a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs index 8e50367..1875a8a 100644 --- a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs +++ b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs @@ -23,16 +23,18 @@ public override async void Invoke(CancellationToken cancellationToken) var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); if (window?.ViewModel is {} viewModel) { - viewModel.RunOperationAsync(await GetSymbol(LastDocument, LastTokenPos, cancellationToken), LastDocument.Project); + var symbol = await GetSymbolAsync(LastDocument, LastTokenPosition, cancellationToken); + var project = LastDocument.Project; + viewModel.RunOperationAsync(symbol, project); } } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } - protected override async Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken) + protected override async Task IsValidSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken) { try { @@ -45,73 +47,64 @@ protected override async Task IsValidSymbol(Document document, int tokenPo var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken); var token = syntaxTree.FindToken(tokenPosition); - if (token.Parent is MethodDeclarationSyntax) - return true; - if (token.Parent is ClassDeclarationSyntax) - return true; - if (token.Parent is StructDeclarationSyntax) - return true; - if (token.Parent is LocalFunctionStatementSyntax) - return true; - if (token.Parent is ConstructorDeclarationSyntax) - return true; - if (token.Parent is PropertyDeclarationSyntax) - return true; - if (token.Parent is OperatorDeclarationSyntax) - return true; + var isValidSymbol = token.Parent + is MethodDeclarationSyntax + or ClassDeclarationSyntax + or StructDeclarationSyntax + or LocalFunctionStatementSyntax + or ConstructorDeclarationSyntax + or PropertyDeclarationSyntax + or OperatorDeclarationSyntax; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } return false; } - static ISymbol FindRelatedSymbol(SemanticModel semanticModel, SyntaxNode node, bool allowClassesAndStructs, CancellationToken ct) + static ISymbol FindRelatedSymbol( + SemanticModel semanticModel, + SyntaxNode node, + bool allowClassesAndStructs, + CancellationToken cancellationToken) { - if (node is LocalFunctionStatementSyntax lf) - return semanticModel.GetDeclaredSymbol(lf, ct); - - if (node is MethodDeclarationSyntax m) - return semanticModel.GetDeclaredSymbol(m, ct); - - if (node is PropertyDeclarationSyntax p) - return semanticModel.GetDeclaredSymbol(p, ct); - - if (node is OperatorDeclarationSyntax o) - return semanticModel.GetDeclaredSymbol(o, ct); - - if (node is ConstructorDeclarationSyntax ctor) - return semanticModel.GetDeclaredSymbol(ctor, ct); - - if (!allowClassesAndStructs) - return null; - - if (node is ClassDeclarationSyntax c) - return semanticModel.GetDeclaredSymbol(c, ct); - - if (node is StructDeclarationSyntax s) - return semanticModel.GetDeclaredSymbol(s, ct); + if ((node + is LocalFunctionStatementSyntax + or MethodDeclarationSyntax + or PropertyDeclarationSyntax + or OperatorDeclarationSyntax + or ConstructorDeclarationSyntax) + || (allowClassesAndStructs && node + is ClassDeclarationSyntax + or StructDeclarationSyntax)) + { + return semanticModel.GetDeclaredSymbol(node, cancellationToken); + } return null; } - public static async Task GetSymbolStatic(Document doc, int tok, CancellationToken ct, bool recursive = false) + public static async Task GetSymbolStaticAsync( + Document document, + int tokenPosition, + CancellationToken cancellationToken, + bool recursive = false) { try { - var semanticModel = await doc.GetSemanticModelAsync(ct); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); if (semanticModel is null) return null; - var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(ct); - var token = syntaxTree.FindToken(tok); + var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken); + var token = syntaxTree.FindToken(tokenPosition); var parent = token.Parent; if (parent is null) return null; - var symbol = FindRelatedSymbol(semanticModel, parent, true, ct); + var symbol = FindRelatedSymbol(semanticModel, parent, true, cancellationToken); if (symbol is null && recursive) { while (true) @@ -120,24 +113,23 @@ public static async Task GetSymbolStatic(Document doc, int tok, Cancell if (parent is null) return null; - symbol = FindRelatedSymbol(semanticModel, parent, false, ct); + symbol = FindRelatedSymbol(semanticModel, parent, false, cancellationToken); if (symbol is not null) - { return symbol; - } } } + return symbol; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); return null; } } - protected virtual Task GetSymbol(Document doc, int pos, CancellationToken ct) => - GetSymbolStatic(doc, pos, ct); + protected virtual Task GetSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken) => + GetSymbolStaticAsync(document, tokenPosition, cancellationToken); public override string DisplayText { diff --git a/src/Vsix/DisasmoPackage.cs b/src/Vsix/DisasmoPackage.cs index 17feb5e..b04082e 100644 --- a/src/Vsix/DisasmoPackage.cs +++ b/src/Vsix/DisasmoPackage.cs @@ -36,15 +36,15 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke Current = this; await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var disasmoCmd = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); - if (disasmoCmd is null) + var disasmoCommand = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); + if (disasmoCommand is null) return; var binding = ""; - if (disasmoCmd.Bindings is object[] bindingArray) + if (disasmoCommand.Bindings is object[] bindingArray) { var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); - // prefer Text Editor over Global + // Prefer Text Editor over Global var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); if (bindingPair is not null && bindingPair.Contains("::")) { @@ -53,9 +53,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke } else { - if (disasmoCmd.Bindings is string bindingStr && bindingStr.Contains("::")) + if (disasmoCommand.Bindings is string bindingString && bindingString.Contains("::")) { - binding = bindingStr.Substring(bindingStr.IndexOf("::", StringComparison.Ordinal) + 2); + binding = bindingString.Substring(bindingString.IndexOf("::", StringComparison.Ordinal) + 2); } } HotKey = binding; @@ -69,25 +69,25 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke public static DisasmoPackage Current { get; set; } - public static async Task GetLatestVersionOnline() + public static async Task GetLatestVersionOnlineAsync() { try { await Task.Delay(3000); - // is there an API to do it? - var client = new HttpClient(); - var str = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); + // Is there an API to do it? + using var client = new HttpClient(); + var content = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); var marker = "extensions/egorbogatov/disasmo/"; - var index = str.IndexOf(marker); - return Version.Parse(str.Substring(index + marker.Length, str.IndexOf('/', index + marker.Length) - index - marker.Length)); + var index = content.IndexOf(marker); + return Version.Parse(content.Substring(index + marker.Length, content.IndexOf('/', index + marker.Length) - index - marker.Length)); } catch { return new Version(0, 0); } } public Version GetCurrentVersion() { - //TODO: fix + //TODO: Fix return new Version(5, 9, 2); //try @@ -149,40 +149,40 @@ public bool ExecuteCommand(DisasmoCommandArgs args, CommandExecutionContext cont IdeUtils.DTE().SaveActiveDocument(); var document = args.TextView?.TextBuffer?.GetRelatedDocuments()?.FirstOrDefault(); - if (document is not null) + if (document is null) + return true; + + var position = GetCaretPosition(args.TextView); + if (position == -1) + return true; + + ThreadPool.QueueUserWorkItem(CallBack); + + return true; + + async void CallBack(object _) { - var pos = GetCaretPosition(args.TextView); - if (pos != -1) + try { - async void CallBack(object _) + if (DisasmoPackage.Current is null) { - try - { - if (DisasmoPackage.Current is null) - { - MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); - return; - } - - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var symbol = await DisasmMethodOrClassAction.GetSymbolStatic(document, pos, default, true); - var window = await IdeUtils.ShowWindowAsync(true, default); - if (window?.ViewModel is {} viewModel) - { - viewModel.RunOperationAsync(symbol, document.Project); - } - } - catch (Exception exc) - { - Debug.WriteLine(exc); - } + MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); + return; } - ThreadPool.QueueUserWorkItem(CallBack); + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var symbol = await DisasmMethodOrClassAction.GetSymbolStaticAsync(document, position, default, true); + var window = await IdeUtils.ShowWindowAsync(true, default); + if (window?.ViewModel is { } viewModel) + { + viewModel.RunOperationAsync(symbol, document.Project); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); } } - - return true; } } \ No newline at end of file diff --git a/src/Vsix/Properties/Settings.Designer.cs b/src/Vsix/Properties/Settings.Designer.cs index 1b14e82..b9e448e 100644 --- a/src/Vsix/Properties/Settings.Designer.cs +++ b/src/Vsix/Properties/Settings.Designer.cs @@ -147,12 +147,12 @@ public string GraphvisDotPath { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool FgEnable { + public bool FlowgraphEnable { get { - return ((bool)(this["FgEnable"])); + return ((bool)(this["FlowgraphEnable"])); } set { - this["FgEnable"] = value; + this["FlowgraphEnable"] = value; } } @@ -244,24 +244,24 @@ public bool DisableLightBulb { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool DontGuessTFM { + public bool DontGuessTargetFramework { get { - return ((bool)(this["DontGuessTFM"])); + return ((bool)(this["DontGuessTargetFramework"])); } set { - this["DontGuessTFM"] = value; + this["DontGuessTargetFramework"] = value; } } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] - public string OverridenTFM { + public string OverridenTargetFramework { get { - return ((string)(this["OverridenTFM"])); + return ((string)(this["OverridenTargetFramework"])); } set { - this["OverridenTFM"] = value; + this["OverridenTargetFramework"] = value; } } diff --git a/src/Vsix/Utils/IdeUtils.cs b/src/Vsix/Utils/IdeUtils.cs index 53f615d..4e68963 100644 --- a/src/Vsix/Utils/IdeUtils.cs +++ b/src/Vsix/Utils/IdeUtils.cs @@ -20,7 +20,7 @@ public static class IdeUtils public static Project GetActiveProject(this DTE dte, string filePath) { - // find project by full name + // Find project by full name if (dte.Solution is not null) { foreach (var projectObject in dte.Solution.Projects) @@ -45,9 +45,9 @@ public static void SaveActiveDocument(this DTE dte) { dte.ActiveDocument?.Save(); } - catch (Exception e) + catch (Exception ex) { - Debug.WriteLine(e); + Debug.WriteLine(ex); } } @@ -58,9 +58,9 @@ public static void SaveAllDocuments(this DTE dte) foreach (Document document in dte.Documents) document?.Save(); } - catch (Exception e) + catch (Exception ex) { - Debug.WriteLine(e); + Debug.WriteLine(ex); } } @@ -80,7 +80,7 @@ public static async Task ShowWindowAsync(bool tryTwice, CancellationToken if (tryTwice) { await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // no idea why I have to call it twice, it doesn't work if I do it only once on the first usage + // No idea why I have to call it twice, it doesn't work if I do it only once on the first usage window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(T), 0, create: true, cancellationToken: cancellationToken); await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); } @@ -99,29 +99,29 @@ public static void RunDiffTools(string contentLeft, string contentRight) contentRight = string.IsNullOrEmpty(contentRight) ? " " : contentRight; var tempPath = Path.GetTempPath(); - var diffDir = Path.Combine(tempPath, "Disasmo_diffs_" + Guid.NewGuid().ToString("N").Substring(0, 10)); - Directory.CreateDirectory(diffDir); + var diffDirectory = Path.Combine(tempPath, "Disasmo_diffs_" + Guid.NewGuid().ToString("N").Substring(0, 10)); + Directory.CreateDirectory(diffDirectory); - var tmpFileLeft = Path.Combine(diffDir, "previous.asm"); - var tmpFileRight = Path.Combine(diffDir, "current.asm"); + var tempFileLeft = Path.Combine(diffDirectory, "previous.asm"); + var tempFileRight = Path.Combine(diffDirectory, "current.asm"); - File.WriteAllText(tmpFileLeft, contentLeft.NormalizeLineEndings()); - File.WriteAllText(tmpFileRight, contentRight.NormalizeLineEndings()); + File.WriteAllText(tempFileLeft, contentLeft.NormalizeLineEndings()); + File.WriteAllText(tempFileRight, contentRight.NormalizeLineEndings()); try { // Copied from https://github.com/madskristensen/FileDiffer/blob/main/src/Commands/DiffFilesCommand.cs#L48-L56 (c) madskristensen - object args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; - ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", 256, ref args, ref args); + object args = $"\"{tempFileLeft}\" \"{tempFileRight}\""; + ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", ID: 256, ref args, ref args); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } finally { - File.Delete(tmpFileLeft); - File.Delete(tmpFileRight); + File.Delete(tempFileLeft); + File.Delete(tempFileRight); } } @@ -145,24 +145,24 @@ public static TfmVersion GetTargetFrameworkVersionDimension(ProjectConfiguration return targetFramework is not null ? TfmVersion.Parse(targetFramework) : null; } - public static async Task GetProjectProperties(UnconfiguredProject unconfiguredProject, ProjectConfiguration projectConfiguration) + public static async Task GetProjectPropertiesAsync(UnconfiguredProject unconfiguredProject, ProjectConfiguration projectConfiguration) { try { - // it will throw "Release config was not found" to the Output if there is no such config in the project + // It will throw "Release config was not found" to the Output if there is no such config in the project projectConfiguration ??= await unconfiguredProject.Services.ProjectConfigurationsService.GetProjectConfigurationAsync("Release"); var configuredProject = await unconfiguredProject.LoadConfiguredProjectAsync(projectConfiguration); return configuredProject.Services.ProjectPropertiesProvider.GetCommonProperties(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); // VS was not able to find the given config (but it still might exist) return null; } } - public static async Task> GetProjectConfigurations(UnconfiguredProject unconfiguredProject) + public static async Task> GetProjectConfigurationsAsync(UnconfiguredProject unconfiguredProject) { try { @@ -170,15 +170,15 @@ public static async Task> GetProjectConfigurat .ProjectConfigurationsService .GetKnownProjectConfigurationsAsync(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); // VS was not able to find the given config (but it still might exist) return []; } } - public static async void OpenInVSCode(string output) + public static void OpenInVSCode(string output) { if (string.IsNullOrWhiteSpace(output)) return; @@ -188,14 +188,14 @@ public static async void OpenInVSCode(string output) var file = Path.GetTempFileName() + ".txt"; File.WriteAllText(file, output.NormalizeLineEndings()); - var psi = new ProcessStartInfo(file); - psi.Verb = "open"; - psi.UseShellExecute = true; - Process.Start(psi); + var processStartInfo = new ProcessStartInfo(file); + processStartInfo.Verb = "open"; + processStartInfo.UseShellExecute = true; + Process.Start(processStartInfo); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } @@ -213,9 +213,9 @@ public static void OpenInVS(string output) DTE().ItemOperations.OpenFile(file); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } } \ No newline at end of file diff --git a/src/Vsix/Utils/TfmVersion.cs b/src/Vsix/Utils/TfmVersion.cs index c29bead..297a671 100644 --- a/src/Vsix/Utils/TfmVersion.cs +++ b/src/Vsix/Utils/TfmVersion.cs @@ -19,13 +19,13 @@ public int CompareTo(TfmVersion other) if (other is null) return 1; - var majorCmp = Nullable.Compare(Major, other.Major); - if (majorCmp != 0) - return majorCmp; + var majorCompare = Nullable.Compare(Major, other.Major); + if (majorCompare != 0) + return majorCompare; - var minorCmp = Nullable.Compare(Minor, other.Minor); - if (minorCmp != 0) - return minorCmp; + var minorCompare = Nullable.Compare(Minor, other.Minor); + if (minorCompare != 0) + return minorCompare; return Nullable.Compare(Patch, other.Patch); } diff --git a/src/Vsix/Utils/ValidationRuleStringAsInt.cs b/src/Vsix/Utils/ValidationRuleStringAsInt.cs index 5f982a4..6e1b918 100644 --- a/src/Vsix/Utils/ValidationRuleStringAsInt.cs +++ b/src/Vsix/Utils/ValidationRuleStringAsInt.cs @@ -16,7 +16,10 @@ protected ValidationResult ValidateInternal(object value, CultureInfo cultureInf return ValidationResult.ValidResult; if (value is string valueAsString) - return int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue) ? ValidationResult.ValidResult : new ValidationResult(false, "Please enter a valid number!"); + { + if (int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue)) + return ValidationResult.ValidResult; + } return new ValidationResult(false, "Please enter a valid number!"); } diff --git a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs index 520031d..023bf21 100644 --- a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs +++ b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs @@ -9,15 +9,15 @@ namespace Disasmo; public class FlowgraphItemViewModel : ViewModelBase { - private readonly SettingsViewModel _settingsView; + private readonly SettingsViewModel _settingsViewModel; private string _imageUrl; private string _dotFileUrl; private string _name; private bool _isBusy; - public FlowgraphItemViewModel(SettingsViewModel settingsView) + public FlowgraphItemViewModel(SettingsViewModel settingsViewModel) { - _settingsView = settingsView; + _settingsViewModel = settingsViewModel; } public string Name @@ -57,14 +57,14 @@ public async Task LoadImageAsync(CancellationToken ct) IsBusy = true; try { - var img = DotFileUrl + ".png"; - var dotExeArgs = $"-Tpng -o\"{img}\" -Kdot \"{DotFileUrl}\""; - await ProcessUtils.RunProcess(_settingsView.GraphvisDotPath, dotExeArgs, cancellationToken: ct); - ImageUrl = img; + var image = DotFileUrl + ".png"; + var dotExeArgs = $"-Tpng -o\"{image}\" -Kdot \"{DotFileUrl}\""; + await ProcessUtils.RunProcess(_settingsViewModel.GraphvisDotPath, dotExeArgs, cancellationToken: ct); + ImageUrl = image; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } IsBusy = false; } diff --git a/src/Vsix/ViewModels/IntrinsicsViewModel.cs b/src/Vsix/ViewModels/IntrinsicsViewModel.cs index e48c65d..2d3764a 100644 --- a/src/Vsix/ViewModels/IntrinsicsViewModel.cs +++ b/src/Vsix/ViewModels/IntrinsicsViewModel.cs @@ -37,9 +37,9 @@ private async void StartDownloadSources() }); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } IsBusy = false; diff --git a/src/Vsix/ViewModels/MainViewModel.cs b/src/Vsix/ViewModels/MainViewModel.cs index 245cf1f..fbd8e8f 100644 --- a/src/Vsix/ViewModels/MainViewModel.cs +++ b/src/Vsix/ViewModels/MainViewModel.cs @@ -1,5 +1,4 @@ -using EnvDTE; -using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.CommandWpf; using Microsoft.CodeAnalysis; using System; @@ -16,6 +15,7 @@ using Microsoft.VisualStudio.ProjectSystem.Properties; using System.Collections.ObjectModel; using CAProject = Microsoft.CodeAnalysis.Project; +using Microsoft.Build.Utilities; namespace Disasmo; @@ -31,17 +31,17 @@ public class MainViewModel : ViewModelBase private CAProject _currentProject; private bool _success; private string _currentProjectPath; - private string _currentTf; - private string _fgPngPath; - private string DisasmoOutDir = ""; - private ObservableCollection _fgPhases = new(); + private string _currentTargetFramework; + private string _flowgraphPngPath; + private string DisasmoOutputDirectory = ""; + private ObservableCollection _flowgraphPhases = new(); private FlowgraphItemViewModel _selectedPhase; - // let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) + // Let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) private string DisasmoFolder => "Disasmo-v" + DisasmoPackage.Current?.GetCurrentVersion(); - public SettingsViewModel SettingsVm { get; } = new(); - public IntrinsicsViewModel IntrinsicsVm { get; } = new(); + public SettingsViewModel SettingsViewModel { get; } = new(); + public IntrinsicsViewModel IntrinsicsViewModel { get; } = new(); public event Action MainPageRequested; @@ -61,11 +61,19 @@ public string Output Set(ref _output, value); const string phasePrefix = "*************** Starting PHASE "; - JitDumpPhases = (Output ?? "") + + if (_output is not null) + { + JitDumpPhases = Output .Split('\n') .Where(l => l.StartsWith(phasePrefix)) .Select(i => i.Replace(phasePrefix, "")) .ToArray(); + } + else + { + JitDumpPhases = []; + } } } @@ -81,19 +89,19 @@ public string LoadingStatus set => Set(ref _loadingStatus, value); } - public CancellationTokenSource UserCts { get; set; } + public CancellationTokenSource UserCancellationTokens { get; set; } - public CancellationToken UserCt => UserCts?.Token ?? default; + public CancellationToken UserCancellationToken => UserCancellationTokens?.Token ?? default; public void ThrowIfCanceled() { - if (UserCts?.IsCancellationRequested == true) + if (UserCancellationTokens?.IsCancellationRequested == true) throw new OperationCanceledException(); } public ICommand CancelCommand => new RelayCommand(() => { - try { UserCts?.Cancel(); } catch { } + try { UserCancellationTokens?.Cancel(); } catch { } }); public string DefaultHotKey => DisasmoPackage.HotKey; @@ -111,7 +119,7 @@ public bool IsLoading { if (!_isLoading && value) { - UserCts = new CancellationTokenSource(); + UserCancellationTokens = new CancellationTokenSource(); } Set(ref _isLoading, value); } @@ -123,10 +131,10 @@ public string StopwatchStatus set => Set(ref _stopwatchStatus, value); } - public string FgPngPath + public string FlowgraphPngPath { - get => _fgPngPath; - set => Set(ref _fgPngPath, value); + get => _flowgraphPngPath; + set => Set(ref _flowgraphPngPath, value); } public ICommand RefreshCommand => new RelayCommand(() => RunOperationAsync(_currentSymbol, _currentProject)); @@ -137,10 +145,10 @@ public string FgPngPath public ICommand OpenInVS => new RelayCommand(() => IdeUtils.OpenInVS(Output)); - public ObservableCollection FgPhases + public ObservableCollection FlowgraphPhases { - get => _fgPhases; - set => Set(ref _fgPhases, value); + get => _flowgraphPhases; + set => Set(ref _flowgraphPhases, value); } public FlowgraphItemViewModel SelectedPhase @@ -149,11 +157,11 @@ public FlowgraphItemViewModel SelectedPhase set { Set(ref _selectedPhase, value); - _selectedPhase?.LoadImageAsync(UserCt); + _selectedPhase?.LoadImageAsync(UserCancellationToken); } } - public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) + public async Task RunFinalExeAsync(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) { try { @@ -164,14 +172,14 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p Success = false; IsLoading = true; - FgPngPath = null; + FlowgraphPngPath = null; LoadingStatus = "Loading..."; - var dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) - dstFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), DisasmoOutDir); + var destinationFolder = DisasmoOutputDirectory; + if (!Path.IsPathRooted(destinationFolder)) + destinationFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), DisasmoOutputDirectory); - // TODO: respect AssemblyName property (if it doesn't match csproj name) + // TODO: Respect AssemblyName property (if it doesn't match csproj name) var fileName = Path.GetFileNameWithoutExtension(_currentProjectPath); try @@ -189,17 +197,17 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p var envVars = new Dictionary(); - if (!SettingsVm.RunAppMode && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected) + if (!SettingsViewModel.RunAppMode && !SettingsViewModel.CrossgenIsSelected && !SettingsViewModel.NativeAotIsSelected) { - var addinVersion = DisasmoPackage.Current.GetCurrentVersion(); - await LoaderAppManager.InitLoaderAndCopyTo(_currentTf, dstFolder, log => { /*TODO: update UI*/ }, addinVersion, UserCt); + var disasmoVersion = DisasmoPackage.Current.GetCurrentVersion(); + await LoaderAppManager.InitLoaderAndCopyTo(_currentTargetFramework, destinationFolder, log => { /*TODO: Update UI*/ }, disasmoVersion, UserCancellationToken); } - if (SettingsVm.JitDumpInsteadOfDisasm) + if (SettingsViewModel.JitDumpInsteadOfDisasm) { envVars["DOTNET_JitDump"] = symbolInfo.Target; } - else if (SettingsVm.PrintInlinees) + else if (SettingsViewModel.PrintInlinees) { envVars["DOTNET_JitPrintInlinedMethods"] = symbolInfo.Target; } @@ -208,34 +216,37 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p envVars["DOTNET_JitDisasm"] = symbolInfo.Target; } - if (!string.IsNullOrWhiteSpace(SettingsVm.SelectedCustomJit) && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && - !SettingsVm.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && SettingsVm.UseCustomRuntime) + if (!string.IsNullOrWhiteSpace(SettingsViewModel.SelectedCustomJit) && + !SettingsViewModel.CrossgenIsSelected && + !SettingsViewModel.NativeAotIsSelected && + !SettingsViewModel.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && + SettingsViewModel.UseCustomRuntime) { - envVars["DOTNET_AltJitName"] = SettingsVm.SelectedCustomJit; + envVars["DOTNET_AltJitName"] = SettingsViewModel.SelectedCustomJit; envVars["DOTNET_AltJit"] = symbolInfo.Target; } - envVars["DOTNET_TieredPGO"] = SettingsVm.UsePGO ? "1" : "0"; - envVars["DOTNET_JitDisasmDiffable"] = SettingsVm.Diffable ? "1" : "0"; + envVars["DOTNET_TieredPGO"] = SettingsViewModel.UsePGO ? "1" : "0"; + envVars["DOTNET_JitDisasmDiffable"] = SettingsViewModel.Diffable ? "1" : "0"; - if (!SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) + if (!SettingsViewModel.UseDotnetPublishForReload && SettingsViewModel.UseCustomRuntime) { var (runtimePackPath, success) = GetPathToRuntimePack(); if (!success) return; - // tell jit to look for BCL libs in the locally built runtime pack + // Tell jit to look for BCL libs in the locally built runtime pack envVars["CORE_LIBRARIES"] = runtimePackPath; } - envVars["DOTNET_TieredCompilation"] = SettingsVm.UseTieredJit ? "1" : "0"; + envVars["DOTNET_TieredCompilation"] = SettingsViewModel.UseTieredJit ? "1" : "0"; // User is free to override any of those ^ - SettingsVm.FillWithUserVars(envVars); + SettingsViewModel.FillWithUserVars(envVars); - string currentFgFile = null; - if (SettingsVm.FgEnable) + string currentFlowgraphFile = null; + if (SettingsViewModel.FlowgraphEnable) { if (symbolInfo.MethodName == "*") { @@ -243,21 +254,21 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p return; } - currentFgFile = Path.GetTempFileName(); + currentFlowgraphFile = Path.GetTempFileName(); envVars["DOTNET_JitDumpFg"] = symbolInfo.Target; envVars["DOTNET_JitDumpFgDot"] = "1"; envVars["DOTNET_JitDumpFgPhase"] = "*"; - envVars["DOTNET_JitDumpFgFile"] = currentFgFile; + envVars["DOTNET_JitDumpFgFile"] = currentFlowgraphFile; } - var command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsVm.UseUnloadableContext}"; - if (SettingsVm.RunAppMode) + var command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsViewModel.UseUnloadableContext}"; + if (SettingsViewModel.RunAppMode) { command = $"\"{fileName}.dll\""; } var executable = "dotnet"; - if (SettingsVm.CrossgenIsSelected && SettingsVm.UseCustomRuntime) + if (SettingsViewModel.CrossgenIsSelected && SettingsViewModel.UseCustomRuntime) { var (clrCheckedFilesDir, checkedFound) = GetPathToCoreClrChecked(); if (!checkedFound) @@ -267,7 +278,7 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p if (!runtimePackFound) return; - executable = Path.Combine(SettingsVm.PathToLocalCoreClr, "dotnet.cmd"); + executable = Path.Combine(SettingsViewModel.PathToLocalCoreClr, "dotnet.cmd"); command = $"{Path.Combine(clrCheckedFilesDir, "crossgen2", "crossgen2.dll")} --out aot "; foreach (var envVar in envVars) @@ -294,24 +305,24 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p envVars["DOTNET_TC_QuickJitForLoops"] = "1"; envVars["DOTNET_TC_CallCountingDelayMs"] = "0"; envVars["DOTNET_TieredCompilation"] = "1"; - command += SettingsVm.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; + command += SettingsViewModel.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; - if (SettingsVm.UseDotnetPublishForReload) + if (SettingsViewModel.UseDotnetPublishForReload) { // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; + command += $" -r: \"{destinationFolder}\\*.dll\" "; } else { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib + // The runtime pack we use doesn't contain corelib so let's use "checked" corelib + // TODO: Build proper core_root with release version of corelib var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; } LoadingStatus = $"Executing crossgen2..."; } - else if (SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + else if (SettingsViewModel.NativeAotIsSelected && SettingsViewModel.UseCustomRuntime) { var (clrReleaseFolder, clrFound) = GetPathToCoreClrCheckedForNativeAot(); if (!clrFound) @@ -339,29 +350,29 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p command += keyLower + "=\"" + envVar.Value + "\" "; } envVars.Clear(); - command += SettingsVm.IlcArgs.Replace("%DOTNET_REPO%", SettingsVm.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); + command += SettingsViewModel.IlcArgs.Replace("%DOTNET_REPO%", SettingsViewModel.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); - if (SettingsVm.UseDotnetPublishForReload) + if (SettingsViewModel.UseDotnetPublishForReload) { // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; + command += $" -r: \"{destinationFolder}\\*.dll\" "; } else { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib + // The runtime pack we use doesn't contain corelib so let's use "checked" corelib. + // TODO: Build proper core_root with release version of corelib //var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); //command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; } LoadingStatus = "Executing ILC... Make sure your method is not inlined and is reachable as NativeAOT runs IL Link. It might take some time..."; } - else if (SettingsVm.IsNonCustomNativeAOTMode()) + else if (SettingsViewModel.IsNonCustomNativeAOTMode()) { LoadingStatus = "Compiling for NativeAOT (.NET 8.0+ is required) ..."; - // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs - // namely, we need to re-direct jit's output to a file (JitStdOutFile). + // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs. + // Namely, we need to re-direct jit's output to a file (JitStdOutFile) var tmpJitStdout = Path.GetTempFileName() + ".asm"; @@ -397,15 +408,15 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p """); - var tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; + var targetFrameworkPart = SettingsViewModel.DontGuessTargetFramework && string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework) ? "" : $"-f {_currentTargetFramework}"; // NOTE: CustomBeforeDirectoryBuildProps is probably not a good idea to overwrite, but we need to pass IlcArgs somehow var dotnetPublishArgs = - $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release" + + $"publish {targetFrameworkPart} -r win-{SettingsViewModel.Arch} -c Release" + $" /p:PublishAot=true /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\"" + $" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCt); + var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCancellationToken); ThrowIfCanceled(); @@ -428,7 +439,7 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p Output = File.ReadAllText(tmpJitStdout); // Keep the temp files around for debugging if it failed. - // and delete them if it succeeded. + // And delete them if it succeeded. File.Delete(tmpProps); File.Delete(tmpJitStdout); } @@ -445,7 +456,10 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p LoadingStatus = $"Executing DisasmoLoader..."; } - if (!SettingsVm.UseDotnetPublishForReload && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + if (!SettingsViewModel.UseDotnetPublishForReload && + !SettingsViewModel.CrossgenIsSelected && + !SettingsViewModel.NativeAotIsSelected && + SettingsViewModel.UseCustomRuntime) { var (clrCheckedFilesDir, success) = GetPathToCoreClrChecked(); if (!success) @@ -454,13 +468,13 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p executable = Path.Combine(clrCheckedFilesDir, "CoreRun.exe"); } - if (SettingsVm.RunAppMode && - !string.IsNullOrWhiteSpace(SettingsVm.OverridenJitDisasm)) + if (SettingsViewModel.RunAppMode && + !string.IsNullOrWhiteSpace(SettingsViewModel.OverridenJitDisasm)) { - envVars["DOTNET_JitDisasm"] = SettingsVm.OverridenJitDisasm; + envVars["DOTNET_JitDisasm"] = SettingsViewModel.OverridenJitDisasm; } - var result = await ProcessUtils.RunProcess(executable, command, envVars, dstFolder, cancellationToken: UserCt); + var result = await ProcessUtils.RunProcess(executable, command, envVars, destinationFolder, cancellationToken: UserCancellationToken); ThrowIfCanceled(); if (string.IsNullOrEmpty(result.Error)) @@ -473,29 +487,29 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p Output = result.Output + "\nERROR:\n" + result.Error; } - if (SettingsVm.FgEnable && SettingsVm.JitDumpInsteadOfDisasm) + if (SettingsViewModel.FlowgraphEnable && SettingsViewModel.JitDumpInsteadOfDisasm) { - currentFgFile += ".dot"; - if (!File.Exists(currentFgFile)) + currentFlowgraphFile += ".dot"; + if (!File.Exists(currentFlowgraphFile)) { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') doesn't exist :(\nInvalid Phase name?"; + Output = $"Oops, JitDumpFgFile ('{currentFlowgraphFile}') doesn't exist :(\nInvalid Phase name?"; return; } - if (new FileInfo(currentFgFile).Length == 0) + if (new FileInfo(currentFlowgraphFile).Length == 0) { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') file is empty :(\nInvalid Phase name?"; + Output = $"Oops, JitDumpFgFile ('{currentFlowgraphFile}') file is empty :(\nInvalid Phase name?"; return; } - var fgLines = File.ReadAllText(currentFgFile); + var flowGraphLines = File.ReadAllText(currentFlowgraphFile); - FgPhases.Clear(); - var graphs = fgLines.Split(new [] {"digraph FlowGraph {"}, StringSplitOptions.RemoveEmptyEntries); + FlowgraphPhases.Clear(); + var graphs = flowGraphLines.Split(["digraph FlowGraph {"], StringSplitOptions.RemoveEmptyEntries); var graphIndex = 0; - var fgBaseDir = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(fgBaseDir); + var flowgraphBaseDirectory = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(flowgraphBaseDirectory); foreach (var graph in graphs) { try @@ -516,26 +530,26 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p // Ignore invalid path chars name = Path.GetInvalidFileNameChars().Aggregate(name, (current, ic) => current.Replace(ic, '_')); - var dotPath = Path.Combine(fgBaseDir, $"{name}.dot"); + var dotPath = Path.Combine(flowgraphBaseDirectory, $"{name}.dot"); File.WriteAllText(dotPath, "digraph FlowGraph {\n" + graph); - FgPhases.Add(new FlowgraphItemViewModel(SettingsVm) { Name = name, DotFileUrl = dotPath, ImageUrl = "" }); + FlowgraphPhases.Add(new FlowgraphItemViewModel(SettingsViewModel) { Name = name, DotFileUrl = dotPath, ImageUrl = "" }); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } } } - catch (OperationCanceledException e) + catch (OperationCanceledException ex) { - Output = e.Message; + Output = ex.Message; } - catch (Exception e) + catch (Exception ex) { - Output = e.ToString(); + Output = ex.ToString(); } finally { @@ -545,15 +559,16 @@ public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties p private string PreprocessOutput(string output) { - if (SettingsVm.JitDumpInsteadOfDisasm || SettingsVm.PrintInlinees) + if (SettingsViewModel.JitDumpInsteadOfDisasm || SettingsViewModel.PrintInlinees) return output; - return DisassemblyPrettifier.Prettify(output, !SettingsVm.ShowAsmComments && !SettingsVm.RunAppMode); + + return DisassemblyPrettifier.Prettify(output, !SettingsViewModel.ShowAsmComments && !SettingsViewModel.RunAppMode); } private UnconfiguredProject GetUnconfiguredProject(Project project) { var context = project as IVsBrowseObjectContext; - if (context is null && project is not null) + if (context is null && project is not null) context = project.Object as IVsBrowseObjectContext; return context?.UnconfiguredProject; @@ -566,7 +581,7 @@ private UnconfiguredProject GetUnconfiguredProject(Project project) if (!success) return (null, false); - var runtimePacksPath = Path.Combine(SettingsVm.PathToLocalCoreClr, @"artifacts\bin\runtime"); + var runtimePacksPath = Path.Combine(SettingsViewModel.PathToLocalCoreClr, @"artifacts\bin\runtime"); string runtimePackPath = null; if (Directory.Exists(runtimePacksPath)) { @@ -589,11 +604,11 @@ private UnconfiguredProject GetUnconfiguredProject(Project project) private (string, bool) GetPathToCoreClrChecked() { var arch = SettingsViewModel.Arch; - var clrCheckedFilesDir = FindJitDirectory(SettingsVm.PathToLocalCoreClr, arch); - if (string.IsNullOrWhiteSpace(clrCheckedFilesDir)) + var clrCheckedFilesDirectory = FindJitDirectory(SettingsViewModel.PathToLocalCoreClr, arch); + if (string.IsNullOrWhiteSpace(clrCheckedFilesDirectory)) { Output = $"Path to a local dotnet/runtime repository is either not set or it's not built for {arch} arch yet" + - (SettingsVm.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + + (SettingsViewModel.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + "\nPlease clone it and build it in `Checked` mode, e.g.:\n\n" + "git clone git@github.com:dotnet/runtime.git\n" + "cd runtime\n" + @@ -602,14 +617,14 @@ private UnconfiguredProject GetUnconfiguredProject(Project project) return (null, false); } - return (clrCheckedFilesDir, true); + return (clrCheckedFilesDirectory, true); } private (string, bool) GetPathToCoreClrCheckedForNativeAot() { var arch = SettingsViewModel.Arch; - var releaseFolder = Path.Combine(SettingsVm.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); + var releaseFolder = Path.Combine(SettingsViewModel.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); if (!Directory.Exists(releaseFolder) || !Directory.Exists(Path.Combine(releaseFolder, "aotsdk")) || !Directory.Exists(Path.Combine(releaseFolder, "ilc"))) { Output = $"Path to a local dotnet/runtime repository is either not set or it's not correctly built for {arch} arch yet for NativeAOT" + @@ -624,19 +639,19 @@ private UnconfiguredProject GetUnconfiguredProject(Project project) return (releaseFolder, true); } - public async void RunOperationAsync(ISymbol symbol, CAProject project) + public async Task RunOperationAsync(ISymbol symbol, CAProject project) { var stopwatch = Stopwatch.StartNew(); var dte = IdeUtils.DTE(); // It's possible that the last modified C# document is not active (e.g. Disasmo itself is in the focus) - // so we have no choice but to run Save() for all the opened documents + // So we have no choice but to run Save() for all the opened documents dte.SaveAllDocuments(); try { IsLoading = true; - FgPngPath = null; + FlowgraphPngPath = null; MainPageRequested?.Invoke(); Success = false; _currentSymbol = symbol; @@ -649,19 +664,19 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) return; } - string clrCheckedFilesDir = null; - if (SettingsVm.UseCustomRuntime) + string clrCheckedFilesDirectory = null; + if (SettingsViewModel.UseCustomRuntime) { var (dir, success) = GetPathToCoreClrChecked(); if (!success) return; - clrCheckedFilesDir = dir; + clrCheckedFilesDirectory = dir; } - if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsVm.RunAppMode) + if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsViewModel.RunAppMode) { - // TODO: ask user to specify type parameters + // TODO: Ask user to specify type parameters Output = "Generic methods are only supported in 'Run' mode"; return; } @@ -680,12 +695,12 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) _currentProjectPath = currentProject.FileName; var unconfiguredProject = GetUnconfiguredProject(currentProject); - // find all configurations, ordered by version descending - var projectConfigurations = (await IdeUtils.GetProjectConfigurations(unconfiguredProject)) + // Find all configurations, ordered by version descending + var projectConfigurations = (await IdeUtils.GetProjectConfigurationsAsync(unconfiguredProject)) .OrderByDescending(IdeUtils.GetTargetFrameworkVersionDimension) .ToList(); - // filter Release configurations + // Filter Release configurations var releaseConfigurations = projectConfigurations .Where(cfg => string.Equals(IdeUtils.GetConfigurationDimension(cfg), "Release", StringComparison.OrdinalIgnoreCase)) .ToList(); @@ -697,40 +712,40 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) } ProjectConfiguration projectConfiguration; - if (string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM)) + if (string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework)) { - // choose first (highest) + // Choose first (highest) projectConfiguration = projectConfigurations.FirstOrDefault(); - // resolve later - _currentTf = null; + // Resolve later + _currentTargetFramework = null; } else { // No validation in this case - _currentTf = SettingsVm.OverridenTFM.Trim(); - var currentTfmVersion = TfmVersion.Parse(_currentTf); - // find the best suitable project configuration + _currentTargetFramework = SettingsViewModel.OverridenTargetFramework.Trim(); + var currentTfmVersion = TfmVersion.Parse(_currentTargetFramework); + // Find the best suitable project configuration projectConfiguration = projectConfigurations .FirstOrDefault(cfg => currentTfmVersion is not null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) ?? projectConfigurations.FirstOrDefault(); } - var projectProperties = await IdeUtils.GetProjectProperties(unconfiguredProject, projectConfiguration); + var projectProperties = await IdeUtils.GetProjectPropertiesAsync(unconfiguredProject, projectConfiguration); ThrowIfCanceled(); - // resolve target framework - if (_currentTf is null) + // Resolve target framework + if (_currentTargetFramework is null) { int? major; if (projectProperties is not null) { - _currentTf = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); - major = TfmVersion.Parse(_currentTf)?.Major; + _currentTargetFramework = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); + major = TfmVersion.Parse(_currentTargetFramework)?.Major; } else { - // fallback to net 7.0 - _currentTf = "net7.0"; + // Fallback to net 7.0 + _currentTargetFramework = "net7.0"; major = 7; } @@ -738,7 +753,7 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) if (major >= 6) { - if (!SettingsVm.UseCustomRuntime && major < 7) + if (!SettingsViewModel.UseCustomRuntime && major < 7) { Output = "Only net7.0 (and newer) apps are supported with non-locally built dotnet/runtime.\n" + @@ -759,18 +774,18 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) ThrowIfCanceled(); - if (SettingsVm.RunAppMode && SettingsVm.UseDotnetPublishForReload) + if (SettingsViewModel.RunAppMode && SettingsViewModel.UseDotnetPublishForReload) { - // TODO: fix this + // TODO: Fix this Output = "\"Run current app\" mode only works with \"dotnet build\" reload strategy, see Options tab."; return; } // Validation for Flowgraph tab - if (SettingsVm.FgEnable) + if (SettingsViewModel.FlowgraphEnable) { - if (string.IsNullOrWhiteSpace(SettingsVm.GraphvisDotPath) || - !File.Exists(SettingsVm.GraphvisDotPath)) + if (string.IsNullOrWhiteSpace(SettingsViewModel.GraphvisDotPath) || + !File.Exists(SettingsViewModel.GraphvisDotPath)) { Output = "Graphvis is not installed or path to dot.exe is incorrect, see 'Settings' tab.\n" + @@ -779,57 +794,57 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) return; } - if (!SettingsVm.JitDumpInsteadOfDisasm) + if (!SettingsViewModel.JitDumpInsteadOfDisasm) { Output = "Either disable flowgraphs in the 'Flowgraph' tab or enable JitDump."; return; } } - if (SettingsVm.CrossgenIsSelected || SettingsVm.NativeAotIsSelected) + if (SettingsViewModel.CrossgenIsSelected || SettingsViewModel.NativeAotIsSelected) { - if (SettingsVm.UsePGO) + if (SettingsViewModel.UsePGO) { Output = "PGO has no effect on R2R'd/NativeAOT code."; return; } - if (SettingsVm.RunAppMode) + if (SettingsViewModel.RunAppMode) { Output = "Run mode is not supported for crossgen/NativeAOT"; return; } - if (SettingsVm.UseTieredJit) + if (SettingsViewModel.UseTieredJit) { Output = "TieredJIT has no effect on R2R'd/NativeAOT code."; return; } - if (SettingsVm.FgEnable) + if (SettingsViewModel.FlowgraphEnable) { Output = "Flowgraphs are not tested with crossgen2/NativeAOT yet (in Disasmo)"; return; } } - var outputDir = projectProperties is null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); - DisasmoOutDir = Path.Combine(outputDir, DisasmoFolder + (SettingsVm.UseDotnetPublishForReload ? "_published" : "")); + var outputDirectory = projectProperties is null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); + DisasmoOutputDirectory = Path.Combine(outputDirectory, DisasmoFolder + (SettingsViewModel.UseDotnetPublishForReload ? "_published" : "")); var currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); - if (SettingsVm.IsNonCustomDotnetAotMode()) + if (SettingsViewModel.IsNonCustomDotnetAotMode()) { ThrowIfCanceled(); var symbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(symbolInfo, projectProperties); + await RunFinalExeAsync(symbolInfo, projectProperties); return; } - var tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; + var targetFrameworkPart = SettingsViewModel.DontGuessTargetFramework && string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework) ? "" : $"-f {_currentTargetFramework}"; // Some things can't be set in CLI e.g. appending to DefineConstants - var tmpProps = Path.GetTempFileName() + ".props"; - File.WriteAllText(tmpProps, $""" + var tempProperties = Path.GetTempFileName() + ".props"; + File.WriteAllText(tempProperties, $""" @@ -839,17 +854,17 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) """); ProcessResult publishResult; - if (SettingsVm.UseDotnetPublishForReload) + if (SettingsViewModel.UseDotnetPublishForReload) { LoadingStatus = $"dotnet publish -r win-{SettingsViewModel.Arch} -c Release -o ..."; - var dotnetPublishArgs = $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutDir} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; + var dotnetPublishArgs = $"publish {targetFrameworkPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutputDirectory} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tempProperties}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCt); + publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCancellationToken); } else { - if (SettingsVm.UseCustomRuntime) + if (SettingsViewModel.UseCustomRuntime) { var (_, rpSuccess) = GetPathToRuntimePack(); if (!rpSuccess) @@ -858,11 +873,11 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) LoadingStatus = "dotnet build -c Release -o ..."; - var dotnetBuildArgs = $"build {tfmPart} -c Release -o {DisasmoOutDir} --no-self-contained " + + var dotnetBuildArgs = $"build {targetFrameworkPart} -c Release -o {DisasmoOutputDirectory} --no-self-contained " + "/p:RuntimeIdentifier=\"\" " + "/p:RuntimeIdentifiers=\"\" " + "/p:WarningLevel=0 " + - $"/p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" " + + $"/p:CustomBeforeDirectoryBuildProps=\"{tempProperties}\" " + $"/p:TreatWarningsAsErrors=false \"{_currentProjectPath}\""; Dictionary fasterBuildEnvVars = new Dictionary @@ -871,18 +886,21 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" }; - if (SettingsVm.UseNoRestoreFlag) + if (SettingsViewModel.UseNoRestoreFlag) { dotnetBuildArgs += " --no-restore --no-dependencies --nologo"; fasterBuildEnvVars["DOTNET_MULTILEVEL_LOOKUP"] = "0"; } - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetBuildArgs, fasterBuildEnvVars, + publishResult = await ProcessUtils.RunProcess( + "dotnet", + dotnetBuildArgs, + fasterBuildEnvVars, currentProjectDirPath, - cancellationToken: UserCt); + cancellationToken: UserCancellationToken); } - File.Delete(tmpProps); + File.Delete(tempProperties); ThrowIfCanceled(); if (!string.IsNullOrEmpty(publishResult.Error)) @@ -891,30 +909,30 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) return; } - // in case if there are compilation errors: + // In case if there are compilation errors: if (publishResult.Output.Contains(": error")) { Output = publishResult.Output; return; } - if (SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) + if (SettingsViewModel.UseDotnetPublishForReload && SettingsViewModel.UseCustomRuntime) { LoadingStatus = "Copying files from locally built CoreCLR"; - var dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) + var destinationFolder = DisasmoOutputDirectory; + if (!Path.IsPathRooted(destinationFolder)) { - dstFolder = Path.Combine(currentProjectDirPath, DisasmoOutDir); + destinationFolder = Path.Combine(currentProjectDirPath, DisasmoOutputDirectory); } - if (!Directory.Exists(dstFolder)) + if (!Directory.Exists(destinationFolder)) { - Output = $"Something went wrong, {dstFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; + Output = $"Something went wrong, {destinationFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; return; } - var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", $"/e \"{clrCheckedFilesDir}\" \"{dstFolder}", null, cancellationToken: UserCt); + var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", $"/e \"{clrCheckedFilesDirectory}\" \"{destinationFolder}", null, cancellationToken: UserCancellationToken); if (!string.IsNullOrEmpty(copyClrFilesResult.Error)) { @@ -925,15 +943,15 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) ThrowIfCanceled(); var finalSymbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(finalSymbolInfo, projectProperties); + await RunFinalExeAsync(finalSymbolInfo, projectProperties); } - catch (OperationCanceledException e) + catch (OperationCanceledException ex) { - Output = e.Message; + Output = ex.Message; } - catch (Exception e) + catch (Exception ex) { - Output = e.ToString(); + Output = ex.ToString(); } finally { @@ -945,13 +963,13 @@ public async void RunOperationAsync(ISymbol symbol, CAProject project) private static string FindJitDirectory(string basePath, string arch) { - var jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); - if (Directory.Exists(jitDir)) - return jitDir; + var jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); - if (Directory.Exists(jitDir)) - return jitDir; + jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; return null; } diff --git a/src/Vsix/ViewModels/SettingsViewModel.cs b/src/Vsix/ViewModels/SettingsViewModel.cs index 3c7c358..e587465 100644 --- a/src/Vsix/ViewModels/SettingsViewModel.cs +++ b/src/Vsix/ViewModels/SettingsViewModel.cs @@ -33,14 +33,14 @@ public class SettingsViewModel : ViewModelBase private bool _useUnloadableContext; private bool _usePGO; private bool _diffable; - private bool _dontGuessTFM; + private bool _dontGuessTargetFramework; private bool _useCustomRuntime; private ObservableCollection _customJits; private string _selectedCustomJit; private string _graphvisDot; private string _overridenJitDisasm; - private bool _fgEnable; - private string _overridenTfm; + private bool _flowgraphEnable; + private string _overridenTargetFramework; public SettingsViewModel() { @@ -58,34 +58,34 @@ public SettingsViewModel() UseTieredJit = Settings.Default.UseTieredJit_V4; UseCustomRuntime = Settings.Default.UseCustomRuntime_V4; GraphvisDotPath = Settings.Default.GraphvisDotPath; - FgEnable = Settings.Default.FgEnable; + FlowgraphEnable = Settings.Default.FlowgraphEnable; PrintInlinees = Settings.Default.PrintInlinees_V3; UsePGO = Settings.Default.UsePGO; Diffable = Settings.Default.Diffable; UseUnloadableContext = Settings.Default.UseUnloadableContext; DisableLightBulb = Settings.Default.DisableLightBulb; - DontGuessTFM = Settings.Default.DontGuessTFM; - OverridenTFM = Settings.Default.OverridenTFM; + DontGuessTargetFramework = Settings.Default.DontGuessTargetFramework; + OverridenTargetFramework = Settings.Default.OverridenTargetFramework; CheckUpdates(); } private async void CheckUpdates() { CurrentVersion = DisasmoPackage.Current?.GetCurrentVersion(); - AvailableVersion = await DisasmoPackage.GetLatestVersionOnline(); + AvailableVersion = await DisasmoPackage.GetLatestVersionOnlineAsync(); if (CurrentVersion != null && AvailableVersion > CurrentVersion) UpdateIsAvailable = true; } public static string Arch { get; set; } = "x64"; - public bool FgEnable + public bool FlowgraphEnable { - get => _fgEnable; + get => _flowgraphEnable; set { - Set(ref _fgEnable, value); - Settings.Default.FgEnable = value; + Set(ref _flowgraphEnable, value); + Settings.Default.FlowgraphEnable = value; Settings.Default.Save(); if (value) { @@ -155,10 +155,10 @@ public bool PopulateCustomJits() if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) { - var jitDir = FindJitDirectory(_pathToLocalCoreClr); - if (jitDir is not null) + var jitDirectory = FindJitDirectory(_pathToLocalCoreClr); + if (jitDirectory is not null) { - string[] jits = Directory.GetFiles(jitDir, "clrjit*.dll"); + string[] jits = Directory.GetFiles(jitDirectory, "clrjit*.dll"); CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); if (SelectedCustomJit is not null) @@ -213,13 +213,13 @@ public bool RunAppMode } } - public string OverridenTFM + public string OverridenTargetFramework { - get => _overridenTfm; + get => _overridenTargetFramework; set { - Set("OverridenTFM", ref _overridenTfm, value); - Settings.Default.OverridenTFM = value; + Set("OverridenTargetFramework", ref _overridenTargetFramework, value); + Settings.Default.OverridenTargetFramework = value; Settings.Default.Save(); } } @@ -358,7 +358,7 @@ public bool JitDumpInsteadOfDisasm if (!value) { - FgEnable = false; + FlowgraphEnable = false; } else { @@ -378,13 +378,13 @@ public bool UseTieredJit } } - public bool DontGuessTFM + public bool DontGuessTargetFramework { - get => _dontGuessTFM; + get => _dontGuessTargetFramework; set { - Set(ref _dontGuessTFM, value); - Settings.Default.DontGuessTFM = value; + Set(ref _dontGuessTargetFramework, value); + Settings.Default.DontGuessTargetFramework = value; Settings.Default.Save(); } } @@ -491,13 +491,13 @@ public void FillWithUserVars(Dictionary dictionary) private static string FindJitDirectory(string basePath) { - var jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); - if (Directory.Exists(jitDir)) - return jitDir; + var jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); - if (Directory.Exists(jitDir)) - return jitDir; + jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; return null; } diff --git a/src/Vsix/Views/DisasmWindowControl.xaml b/src/Vsix/Views/DisasmWindowControl.xaml index 1f00e99..8e308ac 100644 --- a/src/Vsix/Views/DisasmWindowControl.xaml +++ b/src/Vsix/Views/DisasmWindowControl.xaml @@ -20,8 +20,8 @@ - - + + + + @@ -50,7 +50,7 @@ - + @@ -58,15 +58,15 @@ - -