diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index 65ba4df..4265d4b 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -12,29 +12,38 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Text; +using System.Threading.Tasks; namespace System.Diagnostics { public partial class EnhancedStackTrace { + private static List GetFrames(Exception exception) { - var frames = new List(); if (exception == null) + { + return new List(); + } + + var needFileInfo = true; + var stackTrace = new StackTrace(exception, needFileInfo); + + return GetFrames(stackTrace); + } + + private static List GetFrames(StackTrace stackTrace) + { + var frames = new List(); + var stackFrames = stackTrace.GetFrames(); + + if (stackFrames == null) { return frames; } using (var portablePdbReader = new PortablePdbReader()) { - var needFileInfo = true; - var stackTrace = new StackTrace(exception, needFileInfo); - var stackFrames = stackTrace.GetFrames(); - - if (stackFrames == null) - { - return default; - } for (var i = 0; i < stackFrames.Length; i++) { @@ -557,6 +566,16 @@ namespace System.Diagnostics Debug.Assert(method != null); try { + var type = method.DeclaringType; + if (type == typeof(Task<>) && method.Name == "InnerInvoke") + { + return false; + } + if (type == typeof(Task) && method.Name == "ExecuteWithThreadLocal") + { + return false; + } + // Don't show any methods marked with the StackTraceHiddenAttribute // https://github.com/dotnet/coreclr/pull/14652 foreach (var attibute in EnumerableIList.Create(method.GetCustomAttributesData())) @@ -568,7 +587,6 @@ namespace System.Diagnostics } } - var type = method.DeclaringType; if (type == null) { return true; diff --git a/src/Ben.Demystifier/EnhancedStackTrace.cs b/src/Ben.Demystifier/EnhancedStackTrace.cs index b46dd2c..181bfef 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.cs @@ -1,17 +1,17 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections; using System.Collections.Generic; using System.Collections.Generic.Enumerable; -using System.Diagnostics; using System.Text; namespace System.Diagnostics { public partial class EnhancedStackTrace : StackTrace, IEnumerable { + public static EnhancedStackTrace Current() => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true)); + private readonly List _frames; // Summary: @@ -35,6 +35,17 @@ namespace System.Diagnostics _frames = GetFrames(e); } + + public EnhancedStackTrace(StackTrace stackTrace) + { + if (stackTrace == null) + { + throw new ArgumentNullException(nameof(stackTrace)); + } + + _frames = GetFrames(stackTrace); + } + /// /// Gets the number of frames in the stack trace. /// @@ -63,7 +74,7 @@ namespace System.Diagnostics /// A readable representation of the stack trace. public override string ToString() { - if (_frames == null) return ""; + if (_frames == null || _frames.Count == 0) return ""; var sb = new StringBuilder(); @@ -82,7 +93,7 @@ namespace System.Diagnostics { if (i > 0) { - sb.AppendLine(); + sb.Append(Environment.NewLine); } var frame = frames[i]; diff --git a/src/Ben.Demystifier/ExceptionExtentions.cs b/src/Ben.Demystifier/ExceptionExtentions.cs index 8c32c1e..f14ee45 100644 --- a/src/Ben.Demystifier/ExceptionExtentions.cs +++ b/src/Ben.Demystifier/ExceptionExtentions.cs @@ -15,7 +15,11 @@ namespace System.Diagnostics { var stackTrace = new EnhancedStackTrace(exception); - stackTraceString.SetValue(exception, stackTrace.ToString()); + if (stackTrace.FrameCount > 0) + { + stackTraceString.SetValue(exception, stackTrace.ToString()); + } + exception.InnerException?.Demystify(); } catch diff --git a/test/Ben.Demystifier.Test/NonThrownException.cs b/test/Ben.Demystifier.Test/NonThrownException.cs new file mode 100644 index 0000000..679a1c9 --- /dev/null +++ b/test/Ben.Demystifier.Test/NonThrownException.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Xunit; + +namespace Demystify +{ + public class NonThrownException + { + [Fact] + public async Task DoesNotPreventThrowStackTrace() + { + // Arrange + Exception innerException = null; + try + { + await Task.Run(() => throw new Exception()).ConfigureAwait(false); + } + catch(Exception ex) + { + innerException = ex; + } + + // Act + Exception demystifiedException = new Exception(innerException.Message, innerException).Demystify(); + + // Assert + var stackTrace = demystifiedException.ToString(); + stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); + var trace = stackTrace.Split(Environment.NewLine); + + Assert.Equal( + new[] { + "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", + " at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}", + " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)", + " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()", + " --- End of inner exception stack trace ---"}, + trace); + + // Act + try + { + throw demystifiedException; + } + catch (Exception ex) + { + demystifiedException = ex.Demystify(); + } + + // Assert + stackTrace = demystifiedException.ToString(); + stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); + trace = stackTrace.Split(Environment.NewLine); + + Assert.Equal( + new[] { + "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", + " at Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()+()=>{}", + " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)", + " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()", + " --- End of inner exception stack trace ---", + " at async Task Demystify.NonThrownException.DoesNotPreventThrowStackTrace()" + }, + trace); + } + + [Fact] + public async Task Current() + { + // Arrange + EnhancedStackTrace est = null; + + // Act + await Task.Run(() => est = EnhancedStackTrace.Current()).ConfigureAwait(false); + + // Assert + var stackTrace = est.ToString(); + stackTrace = ReplaceLineEndings.Replace(stackTrace, ""); + var trace = stackTrace.Split(Environment.NewLine); + + Assert.Equal( + new[] { + " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)", + " at bool System.Threading.ThreadPoolWorkQueue.Dispatch()"}, + trace); + } + + private Regex ReplaceLineEndings = new Regex(" in [^\n\r]+"); + } +}