├── src
├── version.json
├── Key.snk
├── Terrajobst.StackTraceExplorer
│ ├── Resources
│ │ ├── StackTraceExplorer.ico
│ │ └── StackTraceExplorer.png
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── VSPackage.cs
│ ├── StackTraceExplorerPaneControl.xaml
│ ├── StackTraceExplorerPane.cs
│ ├── source.extension.vsixmanifest
│ ├── ExploreStackTraceCommand.cs
│ ├── app.config
│ ├── StackTraceExplorerPaneControl.xaml.cs
│ ├── VisualStudioStackTraceWriter.cs
│ ├── VSPackage.vsct
│ ├── VSPackage.resx
│ └── Terrajobst.StackTraceExplorer.csproj
├── Terrajobst.StackTraces.Tests
│ ├── App.config
│ ├── Terrajobst.StackTraces.Tests.csproj
│ ├── Helpers
│ │ ├── RecordingCompilationStackTraceWriter.cs
│ │ ├── AnnotatedText.cs
│ │ └── AnnotatedText.Parser.cs
│ └── StackTraceWriterTests.cs
└── Terrajobst.StackTraces
│ ├── Terrajobst.StackTraces.csproj
│ ├── StackTraceWriter.cs
│ └── CompilationStackTraceWriter.cs
├── docs
└── screen.gif
├── README.md
├── LICENSE
├── StackTraceExplorer.sln
├── .gitattributes
└── .gitignore
/src/version.json:
--------------------------------------------------------------------------------
1 | { "version": "0.1.0-alpha" }
--------------------------------------------------------------------------------
/src/Key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrajobst/stack-trace-explorer/HEAD/src/Key.snk
--------------------------------------------------------------------------------
/docs/screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrajobst/stack-trace-explorer/HEAD/docs/screen.gif
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/Resources/StackTraceExplorer.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrajobst/stack-trace-explorer/HEAD/src/Terrajobst.StackTraceExplorer/Resources/StackTraceExplorer.ico
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/Resources/StackTraceExplorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terrajobst/stack-trace-explorer/HEAD/src/Terrajobst.StackTraceExplorer/Resources/StackTraceExplorer.png
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("Terrajobst.StackExplorer.Vsix")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("")]
8 | [assembly: AssemblyProduct("Terrajobst.StackExplorer.Vsix")]
9 | [assembly: AssemblyCopyright("")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 | [assembly: ComVisible(false)]
13 |
14 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces/Terrajobst.StackTraces.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net461
5 | true
6 | ..\Key.snk
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/VSPackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using Microsoft.VisualStudio.Shell;
4 |
5 | namespace Terrajobst.StackTraceExplorer
6 | {
7 | [PackageRegistration(UseManagedResourcesOnly = true)]
8 | [ProvideMenuResource("Menus.ctmenu", 1)]
9 | [ProvideToolWindow(typeof(StackTraceExplorerPane))]
10 | [Guid("cdc68824-5e11-4207-8caa-41f91a2716f4")]
11 | internal sealed class VSPackage : Package
12 | {
13 | protected override void Initialize()
14 | {
15 | ExploreStackTraceCommand.Initialize(this);
16 | base.Initialize();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/StackTraceExplorerPaneControl.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stack Trace Explorer
2 |
3 | [](https://ci.appveyor.com/project/terrajobst/stack-trace-explorer/branch/master)
4 |
5 | Stack Trace Explorer is a Visual Studio plug-in that pretty prints a stack trace
6 | by turning types, methods, and paths into hyper links:
7 |
8 | 
9 |
10 | ## Download
11 |
12 | You can find the latest version in the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ImmoLandwerthMSFT.StackTraceExplorer).
13 |
14 | ## Usage
15 |
16 | Using it is super simple:
17 |
18 | 1. Copy a stack trace to the clipboard
19 | 2. Invoke Ctrl Shift E, S
20 |
21 | Enjoy navigating your stack trace!
22 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/Terrajobst.StackTraces.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net461
5 | false
6 |
7 |
8 |
9 | true
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/StackTraceExplorerPane.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using Microsoft.VisualStudio.Shell;
4 |
5 | namespace Terrajobst.StackTraceExplorer
6 | {
7 | [Guid("0100a14e-3984-4732-a2b8-766393de4859")]
8 | public class StackTraceExplorerPane : ToolWindowPane
9 | {
10 | private string _stackTrace;
11 |
12 | public StackTraceExplorerPane()
13 | : base(null)
14 | {
15 | Caption = "Stack Trace Explorer";
16 | }
17 |
18 | private StackTraceExplorerPaneControl Control => (StackTraceExplorerPaneControl)Content;
19 |
20 | public string StackTrace
21 | {
22 | get => _stackTrace;
23 | set
24 | {
25 | _stackTrace = value;
26 | Control.SetStackTrace(value);
27 | }
28 | }
29 |
30 | protected override void Initialize()
31 | {
32 | base.Initialize();
33 | Content = new StackTraceExplorerPaneControl(this);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2012 Immo Landwerth
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/Helpers/RecordingCompilationStackTraceWriter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.Immutable;
3 | using System.Text;
4 | using Microsoft.CodeAnalysis;
5 |
6 | namespace Terrajobst.StackTraces.Tests.Helpers
7 | {
8 | internal sealed class RecordingCompilationStackTraceWriter : CompilationStackTraceWriter
9 | {
10 | private List _methods = new List();
11 | private StringBuilder _stringBuilder = new StringBuilder();
12 |
13 | public RecordingCompilationStackTraceWriter(Compilation compilation)
14 | : base(ImmutableArray.Create(compilation))
15 | {
16 | }
17 |
18 | public string GetText()
19 | {
20 | return _stringBuilder.ToString();
21 | }
22 |
23 | public ImmutableArray GetMethods()
24 | {
25 | return _methods.ToImmutableArray();
26 | }
27 |
28 | protected override void WritePath(string path, int lineNumber)
29 | {
30 | _stringBuilder.Append(path);
31 | _stringBuilder.Append(':');
32 | _stringBuilder.Append(lineNumber);
33 | }
34 |
35 | protected override void WriteSymbol(string text, ISymbol symbol)
36 | {
37 | if (symbol is IMethodSymbol m)
38 | _methods.Add(m);
39 |
40 | WriteText(text);
41 | }
42 |
43 | protected override void WriteText(string text)
44 | {
45 | _stringBuilder.Append(text);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Stack Trace Explorer
6 | Exploring stack traces for the 21st century.
7 | https://github.com/terrajobst/stack-trace-explorer
8 | Resources\StackTraceExplorer.ico
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/ExploreStackTraceCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Design;
3 | using System.Windows;
4 | using Microsoft.VisualStudio;
5 | using Microsoft.VisualStudio.Shell;
6 | using Microsoft.VisualStudio.Shell.Interop;
7 |
8 | namespace Terrajobst.StackTraceExplorer
9 | {
10 | internal sealed class ExploreStackTraceCommand
11 | {
12 | public static readonly Guid CommandSet = new Guid("af9da84c-2742-4bc4-a2c1-2370f8bff5ee");
13 | public const int CommandId = 0x0100;
14 |
15 | public static ExploreStackTraceCommand Instance { get; private set; }
16 |
17 | public static void Initialize(Package package)
18 | {
19 | Instance = new ExploreStackTraceCommand(package);
20 | }
21 |
22 | private readonly Package _package;
23 |
24 | private ExploreStackTraceCommand(Package package)
25 | {
26 | _package = package ?? throw new ArgumentNullException("package");
27 |
28 | var serviceProvider = (IServiceProvider)_package;
29 |
30 | if (serviceProvider.GetService(typeof(IMenuCommandService)) is OleMenuCommandService commandService)
31 | {
32 | var menuCommandID = new CommandID(CommandSet, CommandId);
33 | var menuItem = new MenuCommand(ShowToolWindow, menuCommandID);
34 | commandService.AddCommand(menuItem);
35 | }
36 | }
37 |
38 | private void ShowToolWindow(object sender, EventArgs e)
39 | {
40 | var window = _package.FindToolWindow(typeof(StackTraceExplorerPane), 0, true) as StackTraceExplorerPane;
41 | if (window == null || window.Frame == null)
42 | throw new NotSupportedException("Cannot create tool window");
43 |
44 | var stackTrace = Clipboard.GetText();
45 | if (!string.IsNullOrEmpty(stackTrace))
46 | window.StackTrace = stackTrace;
47 |
48 | var windowFrame = (IVsWindowFrame)window.Frame;
49 | ErrorHandler.ThrowOnFailure(windowFrame.Show());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/Helpers/AnnotatedText.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.IO;
4 | using System.Text;
5 | using Microsoft.CodeAnalysis.Text;
6 |
7 | namespace Terrajobst.StackTraces.Tests.Helpers
8 | {
9 | public sealed partial class AnnotatedText
10 | {
11 | private AnnotatedText(string text, ImmutableArray spans)
12 | {
13 | Text = text;
14 | Spans = spans;
15 | }
16 |
17 | public static AnnotatedText Parse(string text)
18 | {
19 | if (text == null)
20 | throw new ArgumentNullException(nameof(text));
21 |
22 | var parser = new Parser(text);
23 | return parser.Parse();
24 | }
25 |
26 | public static string NormalizeCode(string text)
27 | {
28 | return Unindent(text).Trim();
29 | }
30 |
31 | private static string Unindent(string text)
32 | {
33 | var minIndent = int.MaxValue;
34 |
35 | using (var stringReader = new StringReader(text))
36 | {
37 | string line;
38 | while ((line = stringReader.ReadLine()) != null)
39 | {
40 | if (string.IsNullOrWhiteSpace(line))
41 | continue;
42 |
43 | var indent = line.Length - line.TrimStart().Length;
44 | minIndent = Math.Min(minIndent, indent);
45 | }
46 | }
47 |
48 | var sb = new StringBuilder();
49 | using (var stringReader = new StringReader(text))
50 | {
51 | string line;
52 | while ((line = stringReader.ReadLine()) != null)
53 | {
54 | var unindentedLine = line.Length < minIndent
55 | ? line
56 | : line.Substring(minIndent);
57 | sb.AppendLine(unindentedLine);
58 | }
59 | }
60 |
61 | return sb.ToString();
62 | }
63 |
64 | public string Text { get; }
65 |
66 | public ImmutableArray Spans { get; }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/StackTraceExplorer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27004.2008
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terrajobst.StackTraceExplorer", "src\Terrajobst.StackTraceExplorer\Terrajobst.StackTraceExplorer.csproj", "{7BD4BA8A-B0E0-42DB-A035-C57C9430B447}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terrajobst.StackTraces", "src\Terrajobst.StackTraces\Terrajobst.StackTraces.csproj", "{E82F0CB5-DF4C-4842-8307-0769537EB098}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terrajobst.StackTraces.Tests", "src\Terrajobst.StackTraces.Tests\Terrajobst.StackTraces.Tests.csproj", "{BF9A27C2-0CEB-4505-8483-3C5E965C6F49}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {7BD4BA8A-B0E0-42DB-A035-C57C9430B447}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {7BD4BA8A-B0E0-42DB-A035-C57C9430B447}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {7BD4BA8A-B0E0-42DB-A035-C57C9430B447}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {7BD4BA8A-B0E0-42DB-A035-C57C9430B447}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {E82F0CB5-DF4C-4842-8307-0769537EB098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {E82F0CB5-DF4C-4842-8307-0769537EB098}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {E82F0CB5-DF4C-4842-8307-0769537EB098}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {E82F0CB5-DF4C-4842-8307-0769537EB098}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {BF9A27C2-0CEB-4505-8483-3C5E965C6F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {BF9A27C2-0CEB-4505-8483-3C5E965C6F49}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {BF9A27C2-0CEB-4505-8483-3C5E965C6F49}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {BF9A27C2-0CEB-4505-8483-3C5E965C6F49}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {0E77C69C-33C2-4D33-8A50-1762875C3650}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/StackTraceExplorerPaneControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Media;
8 | using Microsoft.CodeAnalysis;
9 | using Microsoft.VisualStudio.ComponentModelHost;
10 | using Microsoft.VisualStudio.LanguageServices;
11 | using Terrajobst.StackTraces;
12 |
13 | namespace Terrajobst.StackTraceExplorer
14 | {
15 | public partial class StackTraceExplorerPaneControl : UserControl
16 | {
17 | public StackTraceExplorerPaneControl(IServiceProvider serviceProvider)
18 | {
19 | InitializeComponent();
20 | ServiceProvider = serviceProvider;
21 | }
22 |
23 | public IServiceProvider ServiceProvider { get; }
24 |
25 | public async void SetStackTrace(string text)
26 | {
27 | var componentModel = (IComponentModel)ServiceProvider.GetService(typeof(SComponentModel));
28 | var workspace = componentModel.GetService();
29 |
30 | var solution = workspace.CurrentSolution;
31 | var compilations = await GetCompilationsAsync(solution);
32 |
33 | var writer = new VisualStudioStackTraceWriter(compilations, ServiceProvider);
34 | StackTraceWriter.Write(text, writer);
35 |
36 | var textBlock = new TextBlock
37 | {
38 | Padding = new Thickness(16),
39 | FontFamily = new FontFamily("Consolas")
40 | };
41 | textBlock.Inlines.AddRange(writer.GetInlines());
42 |
43 | Content = new ScrollViewer
44 | {
45 | VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
46 | HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
47 | Content = textBlock
48 | };
49 | }
50 |
51 | public async Task> GetCompilationsAsync(Solution solution)
52 | {
53 | var compilationTasks = new Task[solution.ProjectIds.Count];
54 | for (var i = 0; i < compilationTasks.Length; i++)
55 | {
56 | var project = solution.GetProject(solution.ProjectIds[i]);
57 | compilationTasks[i] = project.GetCompilationAsync();
58 | }
59 |
60 | await Task.WhenAll(compilationTasks);
61 |
62 | return compilationTasks.Select(t => t.Result).ToImmutableArray();
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/VisualStudioStackTraceWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Windows;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using Microsoft.CodeAnalysis;
11 | using Microsoft.VisualStudio;
12 | using Microsoft.VisualStudio.Shell;
13 | using Microsoft.VisualStudio.Shell.Interop;
14 | using Microsoft.VisualStudio.TextManager.Interop;
15 | using Terrajobst.StackTraces;
16 |
17 | namespace Terrajobst.StackTraceExplorer
18 | {
19 | internal sealed class VisualStudioStackTraceWriter : CompilationStackTraceWriter
20 | {
21 | private readonly IServiceProvider _serviceProvider;
22 | private readonly List _inlines = new List();
23 |
24 | public VisualStudioStackTraceWriter(ImmutableArray compilations, IServiceProvider serviceProvider)
25 | : base(compilations)
26 | {
27 | _serviceProvider = serviceProvider;
28 | }
29 |
30 | public IReadOnlyList GetInlines() => _inlines.ToArray();
31 |
32 | protected override void WriteText(string text)
33 | {
34 | _inlines.Add(new Run(text));
35 | }
36 |
37 | protected override void WriteSymbol(string text, ISymbol symbol)
38 | {
39 | var location = symbol.Locations.First();
40 | var isMethod = symbol is IMethodSymbol;
41 |
42 | var run = new Run(text)
43 | {
44 | ToolTip = symbol.ToString()
45 | };
46 |
47 | if (location.IsInSource)
48 | {
49 | var path = location.SourceTree.FilePath;
50 | var position = location.GetLineSpan().StartLinePosition;
51 | var line = position.Line;
52 | var character = position.Character;
53 |
54 | RegisterNavigationCommand(run, path, line, character);
55 | }
56 |
57 | _inlines.Add(run);
58 | }
59 |
60 | protected override void WritePath(string path, int lineNumber)
61 | {
62 | var text = Path.GetFileName(path) + ":" + lineNumber;
63 |
64 | var run = new Run(text)
65 | {
66 | ToolTip = path
67 | };
68 |
69 | RegisterNavigationCommand(run, path, lineNumber - 1, 0);
70 |
71 | _inlines.Add(run);
72 | }
73 |
74 | private void RegisterNavigationCommand(Run run, string path, int line, int character)
75 | {
76 | run.Cursor = Cursors.Hand;
77 | run.MouseEnter += (s, e) => run.TextDecorations = TextDecorations.Underline;
78 | run.MouseLeave += (s, e) => run.TextDecorations = null;
79 | run.MouseDown += (s, e) => NavigateTo(path, line, character);
80 | }
81 |
82 | private void NavigateTo(string fileName, int line, int character)
83 | {
84 | VsShellUtilities.OpenDocument(_serviceProvider, fileName);
85 |
86 | var docTable = (IVsRunningDocumentTable)_serviceProvider.GetService(typeof(SVsRunningDocumentTable));
87 | if (docTable.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, fileName, out var hierarchy,
88 | out var itemid, out var bufferPtr, out var cookie) != VSConstants.S_OK)
89 | return;
90 |
91 | try
92 | {
93 | var lines = Marshal.GetObjectForIUnknown(bufferPtr) as IVsTextLines;
94 | if (lines == null)
95 | return;
96 |
97 | var textManager = (IVsTextManager)_serviceProvider.GetService(typeof(SVsTextManager));
98 | if (textManager == null)
99 | return;
100 |
101 | textManager.NavigateToLineAndColumn(lines, VSConstants.LOGVIEWID.TextView_guid, line, character, line, character);
102 | }
103 | finally
104 | {
105 | if (bufferPtr != IntPtr.Zero)
106 | Marshal.Release(bufferPtr);
107 | }
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/Helpers/AnnotatedText.Parser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Text;
5 | using Microsoft.CodeAnalysis.Text;
6 |
7 | namespace Terrajobst.StackTraces.Tests.Helpers
8 | {
9 | public sealed partial class AnnotatedText
10 | {
11 | private sealed class Parser
12 | {
13 | private static readonly IComparer SpanComparer = Comparer.Create((x, y) => x.Start.CompareTo(y.Start));
14 |
15 | private readonly string _text;
16 | private int _position;
17 | private readonly StringBuilder _textBuilder = new StringBuilder();
18 | private readonly ImmutableArray.Builder _spanBuilder = ImmutableArray.CreateBuilder();
19 |
20 | public Parser(string text)
21 | {
22 | _text = text;
23 | }
24 |
25 | private char Char
26 | {
27 | get { return _position < _text.Length ? _text[_position] : '\0'; }
28 | }
29 |
30 | private char Lookahead
31 | {
32 | get { return _position + 1 < _text.Length ? _text[_position + 1] : '\0'; }
33 | }
34 |
35 | private bool IsSpanStart()
36 | {
37 | return Char == '{' && Lookahead == '{';
38 | }
39 |
40 | private bool IsSpanEnd()
41 | {
42 | return Char == '}' && Lookahead == '}';
43 | }
44 |
45 | public AnnotatedText Parse()
46 | {
47 | ParseRoot();
48 |
49 | var text = _textBuilder.ToString();
50 |
51 | _spanBuilder.Sort(SpanComparer);
52 | var spans = _spanBuilder.ToImmutable();
53 |
54 | return new AnnotatedText(text, spans);
55 | }
56 |
57 | private void ParseRoot()
58 | {
59 | while (_position < _text.Length)
60 | {
61 | if (IsSpanStart())
62 | ParseSpan();
63 | else
64 | ParseText();
65 | }
66 | }
67 |
68 | private void ParseSpan()
69 | {
70 | // Skip intitial braces
71 | _position += 2;
72 |
73 | var spanStart = _textBuilder.Length;
74 |
75 | while (true)
76 | {
77 | if (IsSpanEnd())
78 | {
79 | // Skip closing braces
80 | _position += 2;
81 | var spanEnd = _textBuilder.Length;
82 | var span = TextSpan.FromBounds(spanStart, spanEnd);
83 | _spanBuilder.Add(span);
84 | break;
85 | }
86 | else if (IsSpanStart())
87 | {
88 | ParseSpan();
89 | }
90 | else if (Char == '\0')
91 | {
92 | throw MissingClosingBrace();
93 | }
94 | else
95 | {
96 | ParseText();
97 | }
98 | }
99 | }
100 |
101 | private FormatException MissingClosingBrace()
102 | {
103 | var message = $"Missing '}}}}' at position {_position}.";
104 | return new FormatException(message);
105 | }
106 |
107 | private void ParseText()
108 | {
109 | var start = _position;
110 | while (_position < _text.Length &&
111 | !IsSpanStart() &&
112 | !IsSpanEnd())
113 | {
114 | _position++;
115 | }
116 |
117 | var end = _position;
118 | var length = end - start;
119 | var text = _text.Substring(start, length);
120 | _textBuilder.Append(text);
121 | }
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces/StackTraceWriter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.RegularExpressions;
3 |
4 | namespace Terrajobst.StackTraces
5 | {
6 | public abstract class StackTraceWriter
7 | {
8 | public static void Write(string text, StackTraceWriter writer)
9 | {
10 | var lastPosition = 0;
11 |
12 | foreach (var lineExtent in GetLineExtents(text))
13 | {
14 | var line = text.Substring(lineExtent.start, lineExtent.length);
15 | var frameMatch = Regex.Match(line, @"^.*?(?([^. ])+\..*\([^.]*\)).*?((?[a-zA-Z]:.*?):.*?(?[0-9]+))?$");
16 |
17 | if (frameMatch.Success)
18 | {
19 | var methodNameGroup = frameMatch.Groups["method"];
20 | var pathGroup = frameMatch.Groups["path"];
21 | var lineNumberGroup = frameMatch.Groups["line"];
22 |
23 | var methodNameStart = lineExtent.start + methodNameGroup.Index;
24 | var methodNameLength = methodNameGroup.Length;
25 | var methodName = text.Substring(methodNameStart, methodNameLength);
26 |
27 | var pathStart = lineExtent.start + pathGroup.Index;
28 | var pathLength = pathGroup.Length;
29 | var path = text.Substring(pathStart, pathLength);
30 |
31 | var lineNumberStart = lineExtent.start + lineNumberGroup.Index;
32 | var lineNumberLength = lineNumberGroup.Length;
33 | var hasLineNumber = lineNumberLength > 0;
34 | var lineNumberText = text.Substring(lineNumberStart, lineNumberLength);
35 | var lineNumber = hasLineNumber ? int.Parse(lineNumberText) : 0;
36 |
37 | writer.WriteText(text, ref lastPosition, methodNameStart);
38 |
39 | writer.WriteMethodText(methodName);
40 |
41 | lastPosition = methodNameStart + methodNameLength;
42 |
43 | writer.WriteText(text, ref lastPosition, pathStart);
44 |
45 | if (path.Length > 0)
46 | writer.WritePath(path, lineNumber);
47 |
48 | lastPosition = lineExtent.start + lineExtent.length;
49 | }
50 | }
51 |
52 | writer.WriteText(text, ref lastPosition, text.Length);
53 | }
54 |
55 | private static IEnumerable<(int start, int length)> GetLineExtents(string text)
56 | {
57 | var start = 0;
58 | var position = start;
59 |
60 | while (position < text.Length)
61 | {
62 | var c = text[position];
63 | var l = position == text.Length - 1 ? '\0' : text[position + 1];
64 |
65 | var end = position;
66 | var length = end - start;
67 | var returnLine = false;
68 |
69 | if (c == '\r')
70 | {
71 | if (l == '\n')
72 | position++;
73 |
74 | returnLine = true;
75 | }
76 | else if (c == '\n')
77 | {
78 | returnLine = true;
79 | }
80 |
81 | position++;
82 |
83 | if (returnLine)
84 | {
85 | yield return (start, length);
86 | start = position;
87 | }
88 | }
89 |
90 | if (start < position)
91 | yield return (start, text.Length - start);
92 | }
93 |
94 | private void WriteText(string text, ref int lastPosition, int position)
95 | {
96 | var start = lastPosition;
97 | var length = position - lastPosition;
98 |
99 | if (length > 0)
100 | {
101 | WriteText(text.Substring(start, length));
102 | lastPosition = position;
103 | }
104 | }
105 |
106 | protected abstract void WriteText(string text);
107 |
108 | protected abstract void WriteMethodText(string methodName);
109 |
110 | protected abstract void WritePath(string path, int lineNumber);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/VSPackage.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
33 |
34 |
35 |
37 |
38 |
45 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
66 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/VSPackage.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces/CompilationStackTraceWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Linq;
5 | using System.Text;
6 | using Microsoft.CodeAnalysis;
7 |
8 | namespace Terrajobst.StackTraces
9 | {
10 | public abstract class CompilationStackTraceWriter : StackTraceWriter
11 | {
12 | private readonly ImmutableArray _compilations;
13 |
14 | public CompilationStackTraceWriter(ImmutableArray compilations)
15 | {
16 | _compilations = compilations;
17 | }
18 |
19 | protected override void WriteMethodText(string methodName)
20 | {
21 | var method = _compilations.Select(c => Resolve(c, methodName))
22 | .Where(s => s != null)
23 | .FirstOrDefault();
24 |
25 | if (method == null)
26 | {
27 | WriteText(methodName);
28 | }
29 | else
30 | {
31 | var displayParts = method.ToDisplayParts(SymbolDisplayFormat.CSharpShortErrorMessageFormat);
32 |
33 | foreach (var part in displayParts)
34 | {
35 | var partSymbol = part.Symbol;
36 | var partText = part.ToString();
37 |
38 | if (partSymbol == null && method.AssociatedSymbol != null)
39 | {
40 | // For some reason, accessors don't have symbols
41 |
42 | if (method.AssociatedSymbol.Kind == SymbolKind.Property ||
43 | method.AssociatedSymbol.Kind == SymbolKind.Event)
44 | {
45 | if (part.Kind == SymbolDisplayPartKind.Keyword &&
46 | method.Name == partText + "_" + method.AssociatedSymbol.Name)
47 | {
48 | partSymbol = method;
49 | }
50 | }
51 | }
52 |
53 | if (partSymbol != null)
54 | WriteSymbol(partText, partSymbol);
55 | else
56 | WriteText(partText);
57 | }
58 | }
59 | }
60 |
61 | protected abstract void WriteSymbol(string text, ISymbol symbol);
62 |
63 | private static IMethodSymbol Resolve(Compilation compilation, string methodName)
64 | {
65 | var parts = methodName.ToString().Replace(".ctor", "#ctor").Split('.');
66 |
67 | var currentContainer = (INamespaceOrTypeSymbol)compilation.Assembly.Modules.Single().GlobalNamespace;
68 |
69 | for (var i = 0; currentContainer != null && i < parts.Length - 1; i++)
70 | {
71 | ParseTypeName(parts[i], out var typeOrNamespaceName, out var typeArity);
72 | currentContainer = currentContainer.GetMembers(typeOrNamespaceName)
73 | .Where(n => typeArity == 0 ||
74 | n is INamedTypeSymbol t && t.Arity == typeArity)
75 | .FirstOrDefault() as INamespaceOrTypeSymbol;
76 | }
77 |
78 | if (currentContainer == null)
79 | return null;
80 |
81 | var methodNameAndSignature = parts.Last();
82 | var name = GetMethodName(methodNameAndSignature);
83 | var methodArity = GetMethodArity(methodNameAndSignature);
84 | var parameterTypes = GetMethodParameterTypes(methodNameAndSignature);
85 |
86 | var method = currentContainer.GetMembers(name)
87 | .OfType()
88 | .Where(m => m.Arity == methodArity)
89 | .Where(m => IsMatch(m, parameterTypes))
90 | .FirstOrDefault();
91 | return method;
92 | }
93 |
94 | private static void ParseTypeName(string typeName, out string name, out int arity)
95 | {
96 | var backtick = typeName.IndexOf('`');
97 |
98 | if (backtick < 0)
99 | {
100 | name = typeName;
101 | arity = 0;
102 | }
103 | else
104 | {
105 | name = typeName.Substring(0, backtick);
106 | var arityText = typeName.Substring(backtick + 1);
107 | arity = int.Parse(arityText);
108 | }
109 | }
110 |
111 | private static string GetMethodName(string methodNameAndSignature)
112 | {
113 | var bracket = methodNameAndSignature.IndexOf('[');
114 | var parenthesis = methodNameAndSignature.IndexOf('(');
115 | var nameEnd = bracket >= 0 && bracket < parenthesis
116 | ? bracket
117 | : parenthesis;
118 | var result = methodNameAndSignature.Substring(0, nameEnd);
119 | if (result == "#ctor")
120 | return ".ctor";
121 | return result;
122 | }
123 |
124 | private static int GetMethodArity(string methodNameAndSignature)
125 | {
126 | var parenthesis = methodNameAndSignature.IndexOf('(');
127 |
128 | var openBracket = methodNameAndSignature.IndexOf('[', 0, parenthesis);
129 | if (openBracket < 0)
130 | return 0;
131 |
132 | var closeBracket = methodNameAndSignature.IndexOf(']', 0, parenthesis);
133 | if (closeBracket < 0)
134 | return 0;
135 |
136 | var result = 1;
137 | for (var i = openBracket; i <= closeBracket; i++)
138 | {
139 | if (methodNameAndSignature[i] == ',')
140 | result++;
141 | }
142 | return result;
143 | }
144 |
145 | private static IReadOnlyList GetMethodParameterTypes(string methodNameAndSignature)
146 | {
147 | var openParenthesis = methodNameAndSignature.IndexOf('(');
148 | var closeParenthesis = methodNameAndSignature.IndexOf(')');
149 | var signatureStart = openParenthesis + 1;
150 | var signatureLength = closeParenthesis - signatureStart;
151 | var signature = methodNameAndSignature.Substring(signatureStart, signatureLength);
152 | var parameters = signature.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
153 |
154 | for (var i = 0; i < parameters.Length; i++)
155 | parameters[i] = parameters[i].Trim();
156 |
157 | var result = new List(parameters.Length);
158 | foreach (var parameter in parameters)
159 | {
160 | var space = parameter.IndexOf(' ');
161 | var typeName = parameter.Substring(0, space);
162 | result.Add(typeName);
163 | }
164 |
165 | return result;
166 | }
167 |
168 | private static bool IsMatch(IMethodSymbol method, IReadOnlyList parameterTypes)
169 | {
170 | if (method.Parameters.Length != parameterTypes.Count)
171 | return false;
172 |
173 | for (var i = 0; i < method.Parameters.Length; i++)
174 | {
175 | var symbolTypeName = GetTypeName(method.Parameters[i]);
176 | var frameTypename = parameterTypes[i];
177 |
178 | if (symbolTypeName != frameTypename)
179 | return false;
180 | }
181 |
182 | return true;
183 | }
184 |
185 | private static string GetTypeName(IParameterSymbol symbol)
186 | {
187 | var sb = new StringBuilder();
188 | if (symbol.Type is IArrayTypeSymbol array)
189 | {
190 | sb.Append(array.ElementType.MetadataName);
191 | sb.Append("[]");
192 | }
193 | else if (symbol.Type is IPointerTypeSymbol pointer)
194 | {
195 | sb.Append(pointer.PointedAtType.MetadataName);
196 | sb.Append('*');
197 | }
198 | else
199 | {
200 | sb.Append(symbol.Type.MetadataName);
201 | }
202 |
203 | if (symbol.RefKind != RefKind.None)
204 | sb.Append("&");
205 |
206 | return sb.ToString();
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraceExplorer/Terrajobst.StackTraceExplorer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 15.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 | true
7 | true
8 | ..\Key.snk
9 |
10 |
11 |
12 | Debug
13 | AnyCPU
14 | 2.0
15 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
16 | {7BD4BA8A-B0E0-42DB-A035-C57C9430B447}
17 | Library
18 | Properties
19 | Terrajobst.StackTraceExplorer
20 | Terrajobst.StackTraceExplorer
21 | v4.6.1
22 | true
23 | true
24 | true
25 | true
26 | true
27 | false
28 | Program
29 | $(DevEnvDir)devenv.exe
30 | /rootsuffix Exp
31 |
32 |
33 | true
34 | full
35 | false
36 | bin\Debug\
37 | DEBUG;TRACE
38 | prompt
39 | 4
40 |
41 |
42 | pdbonly
43 | true
44 | bin\Release\
45 | TRACE
46 | prompt
47 | 4
48 |
49 |
50 |
51 |
52 | StackTraceExplorerPaneControl.xaml
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Designer
63 |
64 |
65 |
66 |
67 | Always
68 | true
69 |
70 |
71 | Menus.ctmenu
72 |
73 |
74 |
75 |
76 |
77 | Designer
78 | MSBuild:Compile
79 |
80 |
81 |
82 |
83 | False
84 |
85 |
86 | False
87 |
88 |
89 | False
90 |
91 |
92 | False
93 |
94 |
95 |
96 | False
97 |
98 |
99 |
100 |
101 | False
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | true
116 | VSPackage
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | {e82f0cb5-df4c-4842-8307-0769537eb098}
147 | Terrajobst.StackTraces
148 | BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b
149 | DebugSymbolsProjectOutputGroup%3b
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/src/Terrajobst.StackTraces.Tests/StackTraceWriterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Text;
7 | using Microsoft.CodeAnalysis;
8 | using Microsoft.CodeAnalysis.CSharp;
9 | using Terrajobst.StackTraces.Tests.Helpers;
10 | using Xunit;
11 |
12 | namespace Terrajobst.StackTraces.Tests
13 | {
14 | public class StackTraceWriterTests
15 | {
16 | private static void AssertIsMatch(string sourceWithMarkers, string expectedStackTrace)
17 | {
18 | var annotatedSource = AnnotatedText.Parse(sourceWithMarkers);
19 |
20 | var syntaxTree = CSharpSyntaxTree.ParseText(annotatedSource.Text);
21 | var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
22 | var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true);
23 | var compilation = CSharpCompilation.Create("dummy", new[] { syntaxTree }, new[] { mscorlib }, options);
24 |
25 | var runtimeStackTrace = RunAndGetStackTrace(compilation);
26 |
27 | WriteStackTraceAndMethods(compilation, runtimeStackTrace, out var producedStackTrace, out var methods);
28 |
29 | Assert.Equal(AnnotatedText.NormalizeCode(expectedStackTrace), producedStackTrace);
30 |
31 | var root = syntaxTree.GetRoot();
32 | var markedNodes = annotatedSource.Spans.Select(s => root.FindNode(s)).Reverse().ToArray();
33 | var semanticModel = compilation.GetSemanticModel(syntaxTree);
34 |
35 | Assert.Equal(markedNodes.Length, methods.Length);
36 |
37 | for (var i = 0; i < markedNodes.Length; i++)
38 | {
39 | var expectedSymbol = semanticModel.GetDeclaredSymbol(markedNodes[i]);
40 | Assert.NotNull(expectedSymbol);
41 |
42 | var actualSymbol = methods[i];
43 | Assert.Equal(expectedSymbol, methods[i]);
44 | }
45 | }
46 |
47 | private static string RunAndGetStackTrace(CSharpCompilation compilation)
48 | {
49 | var actualStackTrace = (string)null;
50 |
51 | using (var ms = new MemoryStream())
52 | {
53 | var result = compilation.Emit(ms);
54 |
55 | if (!result.Success)
56 | {
57 | var sb = new StringBuilder();
58 | foreach (var d in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
59 | sb.AppendLine(d.ToString());
60 |
61 | throw new Exception("Failing to compile:" + sb);
62 | }
63 |
64 | Assert.True(result.Success);
65 | ms.Seek(0, SeekOrigin.Begin);
66 | var assembly = Assembly.Load(ms.ToArray());
67 |
68 | try
69 | {
70 | assembly.EntryPoint.Invoke(null, null);
71 | }
72 | catch (TargetInvocationException ex)
73 | {
74 | actualStackTrace = ex.InnerException.ToString();
75 | }
76 |
77 | Assert.NotNull(actualStackTrace);
78 | }
79 |
80 | return actualStackTrace;
81 | }
82 |
83 | private static void WriteStackTraceAndMethods(CSharpCompilation compilation, string actualStackTrace, out string stackTrace, out ImmutableArray methods)
84 | {
85 | var recorder = new RecordingCompilationStackTraceWriter(compilation);
86 | StackTraceWriter.Write(actualStackTrace, recorder);
87 |
88 | stackTrace = recorder.GetText();
89 | methods = recorder.GetMethods();
90 | }
91 |
92 | [Fact]
93 | public void StackTraceWriter_Constructor()
94 | {
95 | var source = @"
96 | using System;
97 |
98 | public static class Program
99 | {
100 | public static void {{Main}}()
101 | {
102 | new Customer();
103 | }
104 | }
105 |
106 | public class Customer
107 | {
108 | public {{Customer}}()
109 | {
110 | throw new Exception(""Boom!"");
111 | }
112 | }
113 | ";
114 |
115 | var expected = @"
116 | System.Exception: Boom!
117 | at Customer.Customer()
118 | at Program.Main()
119 | ";
120 |
121 | AssertIsMatch(source, expected);
122 | }
123 |
124 | [Fact]
125 | public void StackTraceWriter_Method()
126 | {
127 | var source = @"
128 | using System;
129 |
130 | public static class Program
131 | {
132 | public static void {{Main}}()
133 | {
134 | Test();
135 | }
136 |
137 | public static void {{Test}}()
138 | {
139 | throw new Exception(""Boom!"");
140 | }
141 | }
142 | ";
143 |
144 | var expected = @"
145 | System.Exception: Boom!
146 | at Program.Test()
147 | at Program.Main()
148 | ";
149 |
150 | AssertIsMatch(source, expected);
151 | }
152 |
153 | [Fact]
154 | public void StackTraceWriter_Method_GenericMethod()
155 | {
156 | var source = @"
157 | using System;
158 |
159 | public static class Program
160 | {
161 | public static void {{Main}}()
162 | {
163 | Test(1.0f);
164 | }
165 |
166 | public static void {{Test}}(T x)
167 | {
168 | throw new Exception(""Boom!"");
169 | }
170 | }
171 | ";
172 |
173 | var expected = @"
174 | System.Exception: Boom!
175 | at Program.Test(T)
176 | at Program.Main()
177 | ";
178 |
179 | AssertIsMatch(source, expected);
180 | }
181 |
182 | [Fact]
183 | public void StackTraceWriter_Method_GenericType()
184 | {
185 | var source = @"
186 | using System;
187 |
188 | public static class Program
189 | {
190 | public static void {{Main}}()
191 | {
192 | var g = new GenericType();
193 | g.Test(2);
194 | }
195 | }
196 |
197 | class GenericType
198 | {
199 | public void {{Test}}(T value)
200 | {
201 | throw new Exception(""Boom!"");
202 | }
203 | }
204 | ";
205 |
206 | var expected = @"
207 | System.Exception: Boom!
208 | at GenericType.Test(T)
209 | at Program.Main()
210 | ";
211 |
212 | AssertIsMatch(source, expected);
213 | }
214 |
215 | [Fact]
216 | public void StackTraceWriter_Method_Overloaded()
217 | {
218 | var source = @"
219 | using System;
220 |
221 | public static class Program
222 | {
223 | public static void {{Main}}()
224 | {
225 | Test(2);
226 | }
227 |
228 | static void {{Test}}(int value)
229 | {
230 | throw new Exception(""Boom!"");
231 | }
232 |
233 | static void Test(float value)
234 | {
235 | }
236 | }
237 | ";
238 |
239 | var expected = @"
240 | System.Exception: Boom!
241 | at Program.Test(int)
242 | at Program.Main()
243 | ";
244 |
245 | AssertIsMatch(source, expected);
246 | }
247 |
248 | [Fact]
249 | public void StackTraceWriter_Method_Argument_Out()
250 | {
251 | var source = @"
252 | using System;
253 |
254 | public static class Program
255 | {
256 | public static void {{Main}}()
257 | {
258 | Test(out var x);
259 | }
260 |
261 | static void {{Test}}(out int value)
262 | {
263 | throw new Exception(""Boom!"");
264 | }
265 | }
266 | ";
267 |
268 | var expected = @"
269 | System.Exception: Boom!
270 | at Program.Test(out int)
271 | at Program.Main()
272 | ";
273 |
274 | AssertIsMatch(source, expected);
275 | }
276 |
277 | [Fact]
278 | public void StackTraceWriter_Method_Argument_Ref()
279 | {
280 | var source = @"
281 | using System;
282 |
283 | public static class Program
284 | {
285 | public static void {{Main}}()
286 | {
287 | var x = 0;
288 | Test(ref x);
289 | }
290 |
291 | static void {{Test}}(ref int value)
292 | {
293 | throw new Exception(""Boom!"");
294 | }
295 | }
296 | ";
297 |
298 | var expected = @"
299 | System.Exception: Boom!
300 | at Program.Test(ref int)
301 | at Program.Main()
302 | ";
303 |
304 | AssertIsMatch(source, expected);
305 | }
306 |
307 | [Fact]
308 | public void StackTraceWriter_Method_Argument_Array()
309 | {
310 | var source = @"
311 | using System;
312 |
313 | public static class Program
314 | {
315 | public static void {{Main}}()
316 | {
317 | Test(null);
318 | }
319 |
320 | static void {{Test}}(int[] value)
321 | {
322 | throw new Exception(""Boom!"");
323 | }
324 | }
325 | ";
326 |
327 | var expected = @"
328 | System.Exception: Boom!
329 | at Program.Test(int[])
330 | at Program.Main()
331 | ";
332 |
333 | AssertIsMatch(source, expected);
334 | }
335 |
336 | [Fact]
337 | public void StackTraceWriter_Method_Argument_Params()
338 | {
339 | var source = @"
340 | using System;
341 |
342 | public static class Program
343 | {
344 | public static void {{Main}}()
345 | {
346 | Test();
347 | }
348 |
349 | static void {{Test}}(params int[] value)
350 | {
351 | throw new Exception(""Boom!"");
352 | }
353 | }
354 | ";
355 |
356 | var expected = @"
357 | System.Exception: Boom!
358 | at Program.Test(params int[])
359 | at Program.Main()
360 | ";
361 |
362 | AssertIsMatch(source, expected);
363 | }
364 |
365 | [Fact]
366 | public void StackTraceWriter_Method_Argument_GenericInstance()
367 | {
368 | var source = @"
369 | using System;
370 | using System.Collections.Generic;
371 |
372 | public static class Program
373 | {
374 | public static void {{Main}}()
375 | {
376 | Test(null);
377 | }
378 |
379 | static void {{Test}}(IEnumerable value)
380 | {
381 | throw new Exception(""Boom!"");
382 | }
383 | }
384 | ";
385 |
386 | var expected = @"
387 | System.Exception: Boom!
388 | at Program.Test(IEnumerable)
389 | at Program.Main()
390 | ";
391 |
392 | AssertIsMatch(source, expected);
393 | }
394 |
395 | [Fact]
396 | public void StackTraceWriter_Method_Argument_GenericInstance_Array()
397 | {
398 | var source = @"
399 | using System;
400 | using System.Collections.Generic;
401 |
402 | public static class Program
403 | {
404 | public static void {{Main}}()
405 | {
406 | Test(null);
407 | }
408 |
409 | static void {{Test}}(IEnumerable[] value)
410 | {
411 | throw new Exception(""Boom!"");
412 | }
413 | }
414 | ";
415 |
416 | var expected = @"
417 | System.Exception: Boom!
418 | at Program.Test(IEnumerable[])
419 | at Program.Main()
420 | ";
421 |
422 | AssertIsMatch(source, expected);
423 | }
424 |
425 | [Fact]
426 | public void StackTraceWriter_Method_Argument_Pointer()
427 | {
428 | var source = @"
429 | using System;
430 | using System.Collections.Generic;
431 |
432 | unsafe static class Program
433 | {
434 | public static void {{Main}}()
435 | {
436 | Test(null);
437 | }
438 |
439 | unsafe static void {{Test}}(int* value)
440 | {
441 | throw new Exception(""Boom!"");
442 | }
443 | }
444 | ";
445 |
446 | var expected = @"
447 | System.Exception: Boom!
448 | at Program.Test(int*)
449 | at Program.Main()
450 | ";
451 |
452 | AssertIsMatch(source, expected);
453 | }
454 |
455 | [Fact]
456 | public void StackTraceWriter_Property_Getter()
457 | {
458 | var source = @"
459 | using System;
460 |
461 | public static class Program
462 | {
463 | public static void {{Main}}()
464 | {
465 | Console.WriteLine(X);
466 | }
467 |
468 | public static int X
469 | {
470 | {{get}}
471 | {
472 | throw new Exception(""Boom!"");
473 | }
474 | }
475 | }
476 | ";
477 |
478 | var expected = @"
479 | System.Exception: Boom!
480 | at Program.X.get
481 | at Program.Main()
482 | ";
483 |
484 | AssertIsMatch(source, expected);
485 | }
486 |
487 | [Fact]
488 | public void StackTraceWriter_Property_Setter()
489 | {
490 | var source = @"
491 | using System;
492 |
493 | public static class Program
494 | {
495 | public static void {{Main}}()
496 | {
497 | X = 10;
498 | }
499 |
500 | public static int X
501 | {
502 | get => 42;
503 | {{set}}
504 | {
505 | throw new Exception(""Boom!"");
506 | }
507 | }
508 | }
509 | ";
510 |
511 | var expected = @"
512 | System.Exception: Boom!
513 | at Program.X.set
514 | at Program.Main()
515 | ";
516 |
517 | AssertIsMatch(source, expected);
518 | }
519 |
520 | [Fact]
521 | public void StackTraceWriter_Event_Adder()
522 | {
523 | var source = @"
524 | using System;
525 |
526 | public static class Program
527 | {
528 | public static void {{Main}}()
529 | {
530 | Changed += null;
531 | }
532 |
533 | public static event EventHandler Changed
534 | {
535 | {{add}}
536 | {
537 | throw new Exception(""Boom!"");
538 | }
539 | remove { }
540 | }
541 | }
542 | ";
543 |
544 | var expected = @"
545 | System.Exception: Boom!
546 | at Program.Changed.add
547 | at Program.Main()
548 | ";
549 |
550 | AssertIsMatch(source, expected);
551 | }
552 |
553 | [Fact]
554 | public void StackTraceWriter_Event_Remover()
555 | {
556 | var source = @"
557 | using System;
558 |
559 | public static class Program
560 | {
561 | public static void {{Main}}()
562 | {
563 | Changed -= null;
564 | }
565 |
566 | public static event EventHandler Changed
567 | {
568 | add { }
569 | {{remove}}
570 | {
571 | throw new Exception(""Boom!"");
572 | }
573 | }
574 | }
575 | ";
576 |
577 | var expected = @"
578 | System.Exception: Boom!
579 | at Program.Changed.remove
580 | at Program.Main()
581 | ";
582 |
583 | AssertIsMatch(source, expected);
584 | }
585 | }
586 | }
587 |
--------------------------------------------------------------------------------