| File: StackTraceExplorer\StackTraceAnalyzer.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Collections.Immutable; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.StackTraceExplorer; internal static class StackTraceAnalyzer { /// <summary> /// List of parsers to use. Order is important because /// take the result from the first parser that returns /// success. /// </summary> private static readonly ImmutableArray<IStackFrameParser> s_parsers = [new DotnetStackFrameParser(), new VSDebugCallstackParser(), new DefaultStackParser()]; public static Task<StackTraceAnalysisResult> AnalyzeAsync(string callstack, CancellationToken cancellationToken) { var result = new StackTraceAnalysisResult(callstack, Parse(callstack, cancellationToken)); return Task.FromResult(result); } private static ImmutableArray<ParsedFrame> Parse(string callstack, CancellationToken cancellationToken) { using var _ = ArrayBuilder<ParsedFrame>.GetInstance(out var builder); // if the callstack comes from ActivityLog.xml it has been // encoding to be passed over HTTP. This should only decode // specific characters like ">" and "<" to their "normal" // equivalents ">" and "<" so we can parse correctly callstack = WebUtility.HtmlDecode(callstack); var sequence = VirtualCharSequence.Create(0, callstack); foreach (var line in SplitLines(sequence)) { cancellationToken.ThrowIfCancellationRequested(); // For now do the work to removing leading and trailing whitespace. // This keeps behavior we've had, but may not actually be the desired behavior in the long run. // Specifically if we ever want to add a copy feature to copy back contents from a frame var trimmedLine = Trim(line); if (trimmedLine.IsEmpty()) { continue; } foreach (var parser in s_parsers) { if (parser.TryParseLine(trimmedLine, out var parsedFrame)) { builder.Add(parsedFrame); break; } } } return builder.ToImmutableAndClear(); } private static IEnumerable<VirtualCharSequence> SplitLines(VirtualCharSequence callstack) { var position = 0; for (var i = 0; i < callstack.Length; i++) { if (callstack[i] == '\n') { yield return callstack[position..i]; // +1 to skip over the \n character position = i + 1; } } if (position < callstack.Length) { yield return callstack[position..]; } } private static VirtualCharSequence Trim(VirtualCharSequence virtualChars) { if (virtualChars.Length == 0) { return virtualChars; } var start = 0; var end = virtualChars.Length - 1; while (char.IsWhiteSpace(virtualChars[start]) && start < end) { start++; } while (char.IsWhiteSpace(virtualChars[end]) && end > start) { end--; } return virtualChars[start..(end + 1)]; } }