This commit is contained in:
George Duckett
2018-02-22 09:43:00 +00:00
28 changed files with 525 additions and 99 deletions

View File

@@ -9,17 +9,19 @@
<PackageProjectUrl>https://github.com/benaadams/Ben.Demystifier</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/benaadams/Ben.Demystifier/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<Version>0.0.7</Version>
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<LangVersion>7.1</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="2.1.17" PrivateAssets="all" />
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.7.6" PrivateAssets="all" />
<PackageReference Include="System.Reflection.Metadata">
<Version>1.5.0</Version>
</PackageReference>

View File

@@ -19,7 +19,6 @@ namespace System.Diagnostics
{
public partial class EnhancedStackTrace
{
private static List<EnhancedStackFrame> GetFrames(Exception exception)
{
if (exception == null)
@@ -516,7 +515,7 @@ namespace System.Diagnostics
{
foreach (var attrib in attribs)
{
if (attrib is Attribute att && att.GetType().Namespace == "System.Runtime.CompilerServices" && att.GetType().Name == "IsReadOnlyAttribute")
if (attrib is Attribute att && att.GetType().IsReadOnlyAttribute())
{
return "in";
}
@@ -579,6 +578,32 @@ namespace System.Diagnostics
}
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string name, Type parameterType)
{
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
{
Prefix = prefix,
Name = name,
Type = typeName,
ResolvedType = parameterType,
};
}
private static string GetValueTupleParameterName(IList<string> tupleNames, Type parameterType)
{
var sb = new StringBuilder();
sb.Append("(");
@@ -608,14 +633,7 @@ namespace System.Diagnostics
}
sb.Append(")");
return new ResolvedParameter
{
Prefix = prefix,
Name = name,
Type = sb.ToString(),
ResolvedType = parameterType,
};
return sb.ToString();
}
private static bool ShowInStackTrace(MethodBase method)

View File

@@ -1,9 +1,10 @@
// Copyright (c) Ben A Adams. All rights reserved.
// 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;
using System.Collections.Generic;
using System.Collections.Generic.Enumerable;
using System.IO;
using System.Text;
namespace System.Diagnostics
@@ -104,21 +105,9 @@ namespace System.Diagnostics
var filePath = frame.GetFileName();
if (!string.IsNullOrEmpty(filePath))
{
try
{
sb.Append(" in ");
var uri = new Uri(filePath);
if (uri.IsFile)
{
sb.Append(System.IO.Path.GetFullPath(filePath));
}
else
{
sb.Append(uri);
}
}
catch
{ }
sb.Append(" in ");
sb.Append(TryGetFullPath(filePath));
}
var lineNo = frame.GetFileLineNumber();
@@ -133,5 +122,27 @@ namespace System.Diagnostics
EnumerableIList<EnhancedStackFrame> GetEnumerator() => EnumerableIList.Create(_frames);
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
/// <summary>
/// Tries to convert a given <paramref name="filePath"/> to a full path.
/// Returns original value if the conversion isn't possible or a given path is relative.
/// </summary>
public static string TryGetFullPath(string filePath)
{
try
{
var uri = new Uri(filePath);
if (uri.IsFile)
{
return uri.AbsolutePath;
}
return uri.ToString();
}
catch (ArgumentException) { }
catch (UriFormatException) { }
return filePath;
}
}
}

View File

@@ -1,35 +1,54 @@
// Copyright (c) Ben A Adams. All rights reserved.
// 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.Collections.Generic.Enumerable;
using System.Reflection;
namespace System.Diagnostics
{
/// <nodoc />
public static class ExceptionExtentions
{
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>
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)
{
stackTraceString.SetValue(exception, stackTrace.ToString());
exception.SetStackTracesString(stackTrace.ToString());
}
if (exception is AggregateException aggEx)
{
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
{
ex.Demystify();
ex.Demystify(originalStacksTracker);
}
}
exception.InnerException?.Demystify();
exception.InnerException?.Demystify(originalStacksTracker);
}
catch
{
@@ -38,5 +57,39 @@ namespace System.Diagnostics
return exception;
}
/// <summary>
/// Gets demystified string representation of the <paramref name="exception"/>.
/// </summary>
/// <remarks>
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
/// issues if a system relies on the stack trace be in the specific form.
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
/// 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)
{
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

@@ -0,0 +1,30 @@
// 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.Reflection;
using System.Reflection.Emit;
namespace System.Diagnostics.Internal
{
/// <summary>
/// A helper class that contains utilities methods for dealing with reflection.
/// </summary>
public static class ReflectionHelper
{
/// <summary>
/// Returns true if <paramref name="type"/> is <code>System.Runtime.CompilerServices.IsReadOnlyAttribute</code>.
/// </summary>
public static bool IsReadOnlyAttribute(this Type type)
{
return type.Namespace == "System.Runtime.CompilerServices" && type.Name == "IsReadOnlyAttribute";
}
/// <summary>
/// Returns true if the <paramref name="type"/> is a value tuple type.
/// </summary>
public static bool IsValueTuple(this Type type)
{
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
}
}
}

View File

@@ -43,6 +43,22 @@ namespace System.Diagnostics
return builder.ToString();
}
/// <summary>
/// Returns a name of given generic type without '`'.
/// </summary>
public static string GetTypeNameForGenericType(Type type)
{
if (!type.IsGenericType)
{
throw new ArgumentException("The given type should be generic", nameof(type));
}
var genericPartIndex = type.Name.IndexOf('`');
Debug.Assert(genericPartIndex >= 0);
return type.Name.Substring(0, genericPartIndex);
}
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
{
if (type.IsGenericType)

BIN
src/Ben.Demystifier/key.snk Normal file

Binary file not shown.