From cdf53b2655e3e312310eb5adec12f0c5c9d7f8b0 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 21 Nov 2018 15:02:45 +0000 Subject: [PATCH] Keeps message (#79) * Test for exception message * Revert "Allow demystifier to add strings to existing StringBuilder (#73)" This reverts commit 7aa753d5c731f6607ebc2c26104b7fa6f5bb09c2. * Bump version --- .../EnhancedStackTrace.Frames.cs | 32 ++++++++-- src/Ben.Demystifier/ExceptionExtentions.cs | 43 +++++++++++-- src/Ben.Demystifier/ResolvedMethod.cs | 22 +++---- src/Ben.Demystifier/ResolvedParameter.cs | 17 +---- .../StringBuilderExtentions.cs | 49 --------------- src/Ben.Demystifier/TypeNameHelper.cs | 8 +-- .../ValueTupleResolvedParameter.cs | 62 ------------------- .../ToDemystifiedStringTests.cs | 34 ++++++++++ version.json | 2 +- 9 files changed, 113 insertions(+), 156 deletions(-) delete mode 100644 src/Ben.Demystifier/StringBuilderExtentions.cs delete mode 100644 src/Ben.Demystifier/ValueTupleResolvedParameter.cs diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index 80d809c..5897f70 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -171,10 +171,11 @@ namespace System.Diagnostics // ResolveStateMachineMethod may have set declaringType to null if (type != null) { - methodDisplayInfo.DeclaringType = type; + var declaringTypeName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true); + methodDisplayInfo.DeclaringTypeName = declaringTypeName; } - if (method is MethodInfo mi) + if (method is System.Reflection.MethodInfo mi) { var returnParameter = mi.ReturnParameter; if (returnParameter != null) @@ -187,6 +188,7 @@ namespace System.Diagnostics { Prefix = "", Name = "", + Type = TypeNameHelper.GetTypeDisplayName(mi.ReturnType, fullName: false, includeGenericParameterNames: true).ToString(), ResolvedType = mi.ReturnType, }; } @@ -538,6 +540,7 @@ namespace System.Diagnostics { var parameterType = parameter.ParameterType; var prefix = GetPrefix(parameter, parameterType); + var parameterTypeString = "?"; if (parameterType == null) { @@ -545,6 +548,7 @@ namespace System.Diagnostics { Prefix = prefix, Name = parameter.Name, + Type = parameterTypeString, ResolvedType = parameterType, }; } @@ -568,22 +572,40 @@ namespace System.Diagnostics parameterType = parameterType.GetElementType(); } + parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); + return new ResolvedParameter { Prefix = prefix, Name = parameter.Name, + Type = parameterTypeString, ResolvedType = parameterType, }; } private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string name, Type parameterType) { - return new ValueTupleResolvedParameter + string typeName; + + if (parameterType.IsValueTuple()) + { + typeName = GetValueTupleParameterName(tupleNames, parameterType); + + } + else + { + // Need to unwrap the first generic argument first. + var genericTypeName = TypeNameHelper.GetTypeNameForGenericType(parameterType); + var valueTupleFullName = GetValueTupleParameterName(tupleNames, parameterType.GetGenericArguments()[0]); + typeName = $"{genericTypeName}<{valueTupleFullName}>"; + } + + return new ResolvedParameter { - TupleNames = tupleNames, Prefix = prefix, Name = name, - ResolvedType = parameterType + Type = typeName, + ResolvedType = parameterType, }; } diff --git a/src/Ben.Demystifier/ExceptionExtentions.cs b/src/Ben.Demystifier/ExceptionExtentions.cs index beb1d9c..23203d1 100644 --- a/src/Ben.Demystifier/ExceptionExtentions.cs +++ b/src/Ben.Demystifier/ExceptionExtentions.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Generic.Enumerable; using System.Reflection; -using System.Text; namespace System.Diagnostics { @@ -13,16 +12,27 @@ namespace System.Diagnostics { private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); + public static T Demystify(this T exception) where T : Exception + => Demystify(exception, originalStacksTracker: null); + + private static string GetStackTracesString(this Exception exception) + => (string)stackTraceString.GetValue(exception); + private static void SetStackTracesString(this Exception exception, string value) => stackTraceString.SetValue(exception, value); /// /// Demystifies the given and tracks the original stack traces for the whole exception tree. /// - public static T Demystify(this T exception) where T : Exception + private static T Demystify(this T exception, Dictionary originalStacksTracker) where T : Exception { try { + if (originalStacksTracker?.ContainsKey(exception) == false) + { + originalStacksTracker[exception] = exception.GetStackTracesString(); + } + var stackTrace = new EnhancedStackTrace(exception); if (stackTrace.FrameCount > 0) @@ -34,11 +44,11 @@ namespace System.Diagnostics { foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) { - ex.Demystify(); + ex.Demystify(originalStacksTracker); } } - exception.InnerException?.Demystify(); + exception.InnerException?.Demystify(originalStacksTracker); } catch { @@ -58,7 +68,28 @@ namespace System.Diagnostics /// computes a demystified string representation and then restores the original state of the exception back. /// [Contracts.Pure] - public static string ToStringDemystified(this Exception exception) - => new StringBuilder().AppendDemystified(exception).ToString(); + public static string ToStringDemystified(this Exception exception) + { + try + { + var originalStacks = new Dictionary(); + exception.Demystify(originalStacks); + + var result = exception.ToString(); + + foreach (var kvp in originalStacks) + { + kvp.Key.SetStackTracesString(kvp.Value); + } + + return result; + } + catch + { + // Processing exceptions shouldn't throw exceptions; if it fails + } + + return exception.ToString(); + } } } diff --git a/src/Ben.Demystifier/ResolvedMethod.cs b/src/Ben.Demystifier/ResolvedMethod.cs index d4142d7..41ad3a3 100644 --- a/src/Ben.Demystifier/ResolvedMethod.cs +++ b/src/Ben.Demystifier/ResolvedMethod.cs @@ -11,8 +11,8 @@ namespace System.Diagnostics { public MethodBase MethodBase { get; set; } - public Type DeclaringType { get; set; } - + public string DeclaringTypeName { get; set; } + public bool IsAsync { get; set; } public bool IsLambda { get; set; } @@ -39,9 +39,11 @@ namespace System.Diagnostics internal StringBuilder Append(StringBuilder builder) { + if (IsAsync) { - builder.Append("async "); + builder + .Append("async "); } if (ReturnParameter != null) @@ -50,7 +52,7 @@ namespace System.Diagnostics builder.Append(" "); } - if (DeclaringType != null) + if (!string.IsNullOrEmpty(DeclaringTypeName)) { if (Name == ".ctor") @@ -58,16 +60,17 @@ namespace System.Diagnostics if (string.IsNullOrEmpty(SubMethod) && !IsLambda) builder.Append("new "); - AppendDeclaringTypeName(builder); + builder.Append(DeclaringTypeName); } else if (Name == ".cctor") { builder.Append("static "); - AppendDeclaringTypeName(builder); + builder.Append(DeclaringTypeName); } else { - AppendDeclaringTypeName(builder) + builder + .Append(DeclaringTypeName) .Append(".") .Append(Name); } @@ -142,10 +145,5 @@ namespace System.Diagnostics return builder; } - - private StringBuilder AppendDeclaringTypeName(StringBuilder builder) - { - return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: true, includeGenericParameterNames: true) : builder; - } } } diff --git a/src/Ben.Demystifier/ResolvedParameter.cs b/src/Ben.Demystifier/ResolvedParameter.cs index d611b2e..3843e8d 100644 --- a/src/Ben.Demystifier/ResolvedParameter.cs +++ b/src/Ben.Demystifier/ResolvedParameter.cs @@ -9,6 +9,8 @@ namespace System.Diagnostics { public string Name { get; set; } + public string Type { get; set; } + public Type ResolvedType { get; set; } public string Prefix { get; set; } @@ -23,15 +25,7 @@ namespace System.Diagnostics .Append(" "); } - if (ResolvedType != null) - { - AppendTypeName(sb); - } - else - { - sb.Append("?"); - } - + sb.Append(Type); if (!string.IsNullOrEmpty(Name)) { sb.Append(" ") @@ -40,10 +34,5 @@ namespace System.Diagnostics return sb; } - - protected virtual void AppendTypeName(StringBuilder sb) - { - sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true); - } } } diff --git a/src/Ben.Demystifier/StringBuilderExtentions.cs b/src/Ben.Demystifier/StringBuilderExtentions.cs deleted file mode 100644 index 84b7487..0000000 --- a/src/Ben.Demystifier/StringBuilderExtentions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Collections.Generic.Enumerable; -using System.Text; - -namespace System.Diagnostics -{ - public static class StringBuilderExtentions - { - public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) - { - try - { - var stackTrace = new EnhancedStackTrace(exception); - - if (stackTrace.FrameCount > 0) - { - stackTrace.Append(builder); - } - - if (exception is AggregateException aggEx) - { - foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) - { - builder.AppendInnerException(ex); - } - } - - if (exception.InnerException != null) - { - builder.AppendInnerException(exception.InnerException); - } - } - catch - { - // Processing exceptions shouldn't throw exceptions; if it fails - } - - return builder; - } - - private static void AppendInnerException(this StringBuilder builder, Exception exception) - => builder.Append(" ---> ") - .AppendDemystified(exception) - .AppendLine() - .Append(" --- End of inner exception stack trace ---"); - } -} diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs index a1426ad..a4ca3a8 100644 --- a/src/Ben.Demystifier/TypeNameHelper.cs +++ b/src/Ben.Demystifier/TypeNameHelper.cs @@ -7,7 +7,7 @@ using System.Text; namespace System.Diagnostics { // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs - public static class TypeNameHelper + public class TypeNameHelper { public static readonly Dictionary BuiltInTypeNames = new Dictionary { @@ -43,12 +43,6 @@ namespace System.Diagnostics return builder.ToString(); } - public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true, bool includeGenericParameterNames = false) - { - ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); - return builder; - } - /// /// Returns a name of given generic type without '`'. /// diff --git a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs deleted file mode 100644 index 6750ac9..0000000 --- a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.Collections.Generic; -using System.Diagnostics.Internal; -using System.Text; - -namespace System.Diagnostics -{ - public class ValueTupleResolvedParameter : ResolvedParameter - { - public IList TupleNames { get; set; } - - protected override void AppendTypeName(StringBuilder sb) - { - if (ResolvedType.IsValueTuple()) - { - AppendValueTupleParameterName(sb, ResolvedType); - } - else - { - // Need to unwrap the first generic argument first. - sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType)); - sb.Append("<"); - AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]); - sb.Append(">"); - } - } - - - private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType) - { - sb.Append("("); - var args = parameterType.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) - { - if (i > 0) - { - sb.Append(", "); - } - - sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true); - - if (i >= TupleNames.Count) - { - continue; - } - - var argName = TupleNames[i]; - if (argName == null) - { - continue; - } - - sb.Append(" "); - sb.Append(argName); - } - - sb.Append(")"); - } - } -} diff --git a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs index 16a2ee4..f4b6cd2 100644 --- a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs +++ b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs @@ -47,5 +47,39 @@ namespace Ben.Demystifier.Test await Task.Yield(); } } + + + [Fact] + public void DemystifyKeepsMessage() + { + Exception ex = null; + try + { + throw new InvalidOperationException("aaa") + { + Data = + { + ["bbb"] = "ccc", + ["ddd"] = "eee", + } + }; + } + catch (Exception e) + { + ex = e; + } + + var original = ex.ToString(); + var endLine = (int)Math.Min((uint)original.IndexOf('\n'), original.Length); + + original = original.Substring(0, endLine); + + var stringDemystified = ex.ToStringDemystified(); + endLine = (int)Math.Min((uint)stringDemystified.IndexOf('\n'), stringDemystified.Length); + + stringDemystified = stringDemystified.Substring(0, endLine); + + Assert.Equal(original, stringDemystified); + } } } diff --git a/version.json b/version.json index ffaa207..8f8aa98 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "0.1.2", + "version": "0.1.3", "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master "^refs/heads/dev$", // we release out of develop