From b3e8977885dd7a9ee9a5fd036ecc98211c6c34a9 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 3 Jan 2021 21:48:34 +0000 Subject: [PATCH] Skip repeats and IValueTaskSource.GetResult (#132) * Skip repeats and IValueTaskSource.GetResult * Add RecursionTests * Varibale counts * More flexible --- src/Ben.Demystifier/EnhancedStackFrame.cs | 14 ++++ .../EnhancedStackTrace.Frames.cs | 41 +++++++--- src/Ben.Demystifier/ResolvedMethod.cs | 18 +++++ .../AsyncEnumerableTests.cs | 10 +-- test/Ben.Demystifier.Test/RecursionTests.cs | 74 +++++++++++++++++++ 5 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 test/Ben.Demystifier.Test/RecursionTests.cs diff --git a/src/Ben.Demystifier/EnhancedStackFrame.cs b/src/Ben.Demystifier/EnhancedStackFrame.cs index 9e8a064..bb48548 100644 --- a/src/Ben.Demystifier/EnhancedStackFrame.cs +++ b/src/Ben.Demystifier/EnhancedStackFrame.cs @@ -13,6 +13,12 @@ public class EnhancedStackFrame : StackFrame public StackFrame StackFrame { get; } + public bool IsRecursive + { + get => MethodInfo.RecurseCount > 0; + internal set => MethodInfo.RecurseCount++; + } + public ResolvedMethod MethodInfo { get; } internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber) @@ -26,6 +32,14 @@ internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, st _colNumber = colNumber; } + internal bool IsEquivalent(ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber) + { + return _lineNumber == lineNumber && + _colNumber == colNumber && + _fileName == fileName && + MethodInfo.IsSequentialEquivalent(methodInfo); + } + /// /// Gets the column number in the file that contains the code that is executing. /// This information is typically extracted from the debugging symbols for the executable. diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index e5084c8..1816ba8 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -44,9 +44,10 @@ private static List GetFrames(StackTrace stackTrace) return frames; } - using (var portablePdbReader = new PortablePdbReader()) + EnhancedStackFrame lastFrame = null; + PortablePdbReader portablePdbReader = null; + try { - for (var i = 0; i < stackFrames.Length; i++) { var frame = stackFrames[i]; @@ -66,17 +67,32 @@ private static List GetFrames(StackTrace stackTrace) { // .NET Framework and older versions of mono don't support portable PDBs // so we read it manually to get file name and line information - portablePdbReader.PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column); + (portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column); } - var stackFrame = new EnhancedStackFrame(frame, GetMethodDisplayString(method), fileName, row, column); - - - frames.Add(stackFrame); + var resolvedMethod = GetMethodDisplayString(method); + if (lastFrame?.IsEquivalent(resolvedMethod, fileName, row, column) ?? false) + { + lastFrame.IsRecursive = true; + } + else + { + var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column); + frames.Add(stackFrame); + lastFrame = stackFrame; + } + } + } + finally + { + if (portablePdbReader is not null) + { + portablePdbReader.Dispose(); } - return frames; } + + return frames; } public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod) @@ -684,6 +700,10 @@ private static bool ShowInStackTrace(MethodBase method) { return false; } + if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult")) + { + return false; + } if (type == typeof(Task) || type.DeclaringType == typeof(Task)) { if (method.Name.Contains(".cctor")) @@ -700,7 +720,6 @@ private static bool ShowInStackTrace(MethodBase method) case "InnerInvoke": case "ExecuteEntryUnsafe": case "ExecuteFromThreadPool": - case "s_ecCallback": return false; } } @@ -829,7 +848,7 @@ static MethodInfo[] GetDeclaredMethods(Type type) => return false; } - foreach (MethodInfo candidateMethod in methods) + foreach (var candidateMethod in methods) { var attributes = candidateMethod.GetCustomAttributes(inherit: false); // ReSharper disable once ConditionIsAlwaysTrueOrFalse - Taken from CoreFX @@ -839,7 +858,7 @@ static MethodInfo[] GetDeclaredMethods(Type type) => } bool foundAttribute = false, foundIteratorAttribute = false; - foreach (StateMachineAttribute asma in attributes) + foreach (var asma in attributes) { if (asma.StateMachineType == declaringType) { diff --git a/src/Ben.Demystifier/ResolvedMethod.cs b/src/Ben.Demystifier/ResolvedMethod.cs index d4142d7..c6a50cf 100644 --- a/src/Ben.Demystifier/ResolvedMethod.cs +++ b/src/Ben.Demystifier/ResolvedMethod.cs @@ -34,6 +34,19 @@ public class ResolvedMethod public EnumerableIList Parameters { get; set; } public EnumerableIList SubMethodParameters { get; set; } + public int RecurseCount { get; internal set; } + + internal bool IsSequentialEquivalent(ResolvedMethod obj) + { + return + IsAsync == obj.IsAsync && + DeclaringType == obj.DeclaringType && + Name == obj.Name && + IsLambda == obj.IsLambda && + Ordinal == obj.Ordinal && + GenericArguments == obj.GenericArguments && + SubMethod == obj.SubMethod; + } public override string ToString() => Append(new StringBuilder()).ToString(); @@ -140,6 +153,11 @@ internal StringBuilder Append(StringBuilder builder) } } + if (RecurseCount > 0) + { + builder.Append($" x {RecurseCount + 1:0}"); + } + return builder; } diff --git a/test/Ben.Demystifier.Test/AsyncEnumerableTests.cs b/test/Ben.Demystifier.Test/AsyncEnumerableTests.cs index d9469a4..487ba27 100644 --- a/test/Ben.Demystifier.Test/AsyncEnumerableTests.cs +++ b/test/Ben.Demystifier.Test/AsyncEnumerableTests.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -33,16 +34,13 @@ public async Task DemystifiesAsyncEnumerable() var stackTrace = demystifiedException.ToString(); stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace); var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => Regex.Replace(s, " x [0-9]+", " x N")) .Skip(1) .ToArray()); var expected = string.Join("", new[] { " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Throw(CancellationToken cancellationToken)+MoveNext()", - " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Throw(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token)", - " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext()", - " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext()", - " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token)", - " at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable()", - " at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable()" + " at async IAsyncEnumerable Ben.Demystifier.Test.AsyncEnumerableTests.Start(CancellationToken cancellationToken)+MoveNext() x N", + " at async Task Ben.Demystifier.Test.AsyncEnumerableTests.DemystifiesAsyncEnumerable() x N" }); Assert.Equal(expected, trace); } diff --git a/test/Ben.Demystifier.Test/RecursionTests.cs b/test/Ben.Demystifier.Test/RecursionTests.cs new file mode 100644 index 0000000..410afc1 --- /dev/null +++ b/test/Ben.Demystifier.Test/RecursionTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; + +namespace Ben.Demystifier.Test +{ + public class RecursionTests + { + [Fact] + public async Task DemystifiesAsyncRecursion() + { + Exception demystifiedException = null; + + try + { + await RecurseAsync(10); + } + catch (Exception ex) + { + demystifiedException = ex.Demystify(); + } + + // Assert + var stackTrace = demystifiedException.ToString(); + stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace); + var traces = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => Regex.Replace(s, " x [0-9]+", " x N")) + .Skip(1) + .ToArray(); + + Assert.Contains(" at async Task Ben.Demystifier.Test.RecursionTests.RecurseAsync(int depth) x N", traces); + } + + [Fact] + public void DemystifiesRecursion() + { + Exception demystifiedException = null; + + try + { + Recurse(10); + } + catch (Exception ex) + { + demystifiedException = ex.Demystify(); + } + + // Assert + var stackTrace = demystifiedException.ToString(); + stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace); + var traces = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .Select(s => Regex.Replace(s, " x [0-9]+", " x N")) + .Skip(1) + .ToArray(); + + Assert.Contains(" at int Ben.Demystifier.Test.RecursionTests.Recurse(int depth) x N", traces); + } + + async Task RecurseAsync(int depth) + { + if (depth > 0) await RecurseAsync(depth - 1); + throw new InvalidOperationException(); + } + + int Recurse(int depth) + { + if (depth > 0) Recurse(depth - 1); + throw new InvalidOperationException(); + } + } +}