Keeps message (#79)

* Test for exception message

* Revert "Allow demystifier to add strings to existing StringBuilder (#73)"

This reverts commit 7aa753d5c7.

* Bump version
This commit is contained in:
Ben Adams 2018-11-21 15:02:45 +00:00 committed by GitHub
parent 9c2917fa47
commit cdf53b2655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 156 deletions

View File

@ -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<string> 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,
};
}

View File

@ -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<T>(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);
/// <summary>
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
/// </summary>
public static T Demystify<T>(this T exception) where T : Exception
private static T Demystify<T>(this T exception, Dictionary<Exception, string> 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.
/// </remarks>
[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, string>();
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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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 ---");
}
}

View File

@ -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<Type, string> BuiltInTypeNames = new Dictionary<Type, string>
{
@ -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;
}
/// <summary>
/// Returns a name of given generic type without '`'.
/// </summary>

View File

@ -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<string> 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(")");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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