Add 0.0.1 code

This commit is contained in:
Ben Adams 2017-10-29 23:36:01 +00:00
parent 3164e5c775
commit 34930738f6
18 changed files with 2029 additions and 0 deletions

48
Ben.Demystifier.sln Normal file
View File

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27019.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A2FCCAAC-BE90-4F7E-B95F-A72D46DDD6B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59CA6310-4AA5-4093-95D4-472B94DC0CD4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier", "src\Ben.Demystifier\Ben.Demystifier.csproj", "{5410A056-89AB-4912-BD1E-A63616AD91D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Test", "test\Ben.Demystifier.Test\Ben.Demystifier.Test.csproj", "{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{455921D3-DD54-4355-85CF-F4009DF2AB70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTrace", "sample\StackTrace\StackTrace.csproj", "{E161FC12-53C2-47CD-A5FC-3684B86723A9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5410A056-89AB-4912-BD1E-A63616AD91D0}.Release|Any CPU.Build.0 = Release|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2}.Release|Any CPU.Build.0 = Release|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E161FC12-53C2-47CD-A5FC-3684B86723A9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5410A056-89AB-4912-BD1E-A63616AD91D0} = {A2FCCAAC-BE90-4F7E-B95F-A72D46DDD6B3}
{B9E150B0-AEEB-4D98-8BE1-92C1296699A2} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
{E161FC12-53C2-47CD-A5FC-3684B86723A9} = {455921D3-DD54-4355-85CF-F4009DF2AB70}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {841B7D5F-E810-4F94-A529-002C7E075216}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Exception exception = null;
try
{
new Program();
}
catch (Exception ex)
{
exception = ex.Demystify();
}
Console.WriteLine(exception);
}
static Action<string, bool> s_action = (string s, bool b) => s_func(s, b);
static Func<string, bool, (string val, bool)> s_func = (string s, bool b) => (RefMethod(s), b);
Action<Action<object>, object> _action = (Action<object> lambda, object state) => lambda(state);
static string s = "";
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
Program() : this(() => Start())
{
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
Program(Action action)
{
RunAction((state) => _action((s) => action(), state), null);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static IEnumerable<string> Iterator(int startAt)
{
var list = new List<int>() { 1, 2, 3, 4 };
foreach (var item in list)
{
// Throws the exception
list.Add(item);
yield return item.ToString();
}
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static async Task<string> MethodAsync(int value)
{
await Task.Delay(0);
return GenericClass<byte>.GenericMethod(ref value);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static async Task<string> MethodAsync<TValue>(TValue value)
{
return await MethodAsync(1);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void RunAction(Action<object> lambda, object state)
{
lambda(state);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static string RunLambda(Func<string> lambda)
{
return lambda();
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static (string val, bool) Method(string value)
{
Func<string> func = () => MethodAsync(value).GetAwaiter().GetResult();
var anonType = new { func };
return (RunLambda(() => anonType.func()), true);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static ref string RefMethod(int value)
{
return ref s;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static string RefMethod(in string value)
{
var val = value;
return LocalFuncParam(value).ToString();
int LocalFuncParam(string s)
{
return int.Parse(LocalFuncRefReturn());
}
ref string LocalFuncRefReturn()
{
Method(val);
return ref s;
}
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static string Start()
{
return LocalFunc2(true, false).ToString();
void LocalFunc1(long l)
{
Start((val: "", true));
}
bool LocalFunc2(bool b1, bool b2)
{
LocalFunc1(1);
return true;
}
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static ref string RefMethod(bool value)
{
return ref s;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Start((string val, bool) param)
{
s_action.Invoke(param.val, param.Item2);
}
class GenericClass<TSuperType>
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static string GenericMethod<TSubType>(ref TSubType value)
{
var returnVal = "";
for (var i = 0; i < 10; i++)
{
try
{
returnVal += string.Join(", ", Iterator(5).Select(s => s));
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
return returnVal;
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ben.Demystifier\Ben.Demystifier.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Product>Ben Core</Product>
<Title>Ben.Demystifier</Title>
<Description>High performance understanding for stack traces (Make error logs more productive)</Description>
<Authors>ben_a_adams</Authors>
<RepositoryUrl>https://github.com/benaadams/Ben.Demystifier</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<IncludeSymbols>true</IncludeSymbols>
<IncludeSource>true</IncludeSource>
<Version>0.0.1</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net46</TargetFrameworks>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp">
<Version>2.4.0</Version>
</PackageReference>
<PackageReference Include="System.Diagnostics.DiagnosticSource">
<Version>4.4.1</Version>
</PackageReference>
<PackageReference Include="System.Reflection.Metadata">
<Version>1.5.0</Version>
</PackageReference>
<PackageReference Include="System.Threading.Tasks.Extensions">
<Version>4.4.0</Version>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,79 @@
// 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;
namespace System.Diagnostics
{
public class EnhancedStackFrame : StackFrame
{
private string _fileName;
private int _lineNumber;
private int _colNumber;
public StackFrame StackFrame { get; }
public ResolvedMethod MethodInfo { get; }
internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string fileName, int lineNumber, int colNumber)
: base(fileName, lineNumber, colNumber)
{
StackFrame = stackFrame;
MethodInfo = methodInfo;
_fileName = fileName;
_lineNumber = lineNumber;
_colNumber = colNumber;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The file column number, or 0 (zero) if the file column number cannot be determined.</returns>
public override int GetFileColumnNumber() => _colNumber;
/// <summary>
/// Gets the line number in the file that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file line number, or 0 (zero) if the file line number cannot be determined.</returns>
public override int GetFileLineNumber() => _lineNumber;
/// <summary>
/// Gets the file name that contains the code that is executing.
/// This information is typically extracted from the debugging symbols for the executable.
/// </summary>
/// <returns>The file name, or null if the file name cannot be determined.</returns>
public override string GetFileName() => _fileName;
/// <summary>
/// Gets the offset from the start of the Microsoft intermediate language (MSIL)
/// code for the method that is executing. This offset might be an approximation
/// depending on whether or not the just-in-time (JIT) compiler is generating debugging
/// code. The generation of this debugging information is controlled by the System.Diagnostics.DebuggableAttribute.
/// </summary>
/// <returns>The offset from the start of the MSIL code for the method that is executing.</returns>
public override int GetILOffset() => StackFrame.GetILOffset();
/// <summary>
/// Gets the method in which the frame is executing.
/// </summary>
/// <returns>The method in which the frame is executing.</returns>
public override MethodBase GetMethod() => StackFrame.GetMethod();
/// <summary>
/// Gets the offset from the start of the native just-in-time (JIT)-compiled code
/// for the method that is being executed. The generation of this debugging information
/// is controlled by the System.Diagnostics.DebuggableAttribute class.
/// </summary>
/// <returns>The offset from the start of the JIT-compiled code for the method that is being executed.</returns>
public override int GetNativeOffset() => StackFrame.GetNativeOffset();
/// <summary>
/// Builds a readable representation of the stack trace.
/// </summary>
/// <returns>A readable representation of the stack trace.</returns>
public override string ToString() => MethodInfo.ToString();
}
}

View File

@ -0,0 +1,698 @@
// 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.
// Copyright (c) .NET Foundation. 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.Diagnostics.Internal;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
namespace System.Diagnostics
{
public partial class EnhancedStackTrace
{
private static List<EnhancedStackFrame> GetFrames(Exception exception)
{
var frames = new List<EnhancedStackFrame>();
if (exception == 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++)
{
var frame = stackFrames[i];
var method = frame.GetMethod();
// Always show last stackFrame
if (!ShowInStackTrace(method) && i < stackFrames.Length - 1)
{
continue;
}
var fileName = frame.GetFileName();
var row = frame.GetFileLineNumber();
var column = frame.GetFileColumnNumber();
if (string.IsNullOrEmpty(fileName))
{
// .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);
}
var stackFrame = new EnhancedStackFrame(frame, GetMethodDisplayString(method), fileName, row, column);
frames.Add(stackFrame);
}
return frames;
}
}
private static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
{
// Special case: no method available
if (originMethod == null)
{
return null;
}
MethodBase method = originMethod;
var methodDisplayInfo = new ResolvedMethod();
methodDisplayInfo.SubMethodBase = method;
// Type name
var type = method.DeclaringType;
var subMethodName = method.Name;
var methodName = method.Name;
if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) &&
(typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type)))
{
methodDisplayInfo.IsAsync = typeof(IAsyncStateMachine).IsAssignableFrom(type);
// Convert StateMachine methods to correct overload +MoveNext()
if (!TryResolveStateMachineMethod(ref method, out type))
{
methodDisplayInfo.SubMethodBase = null;
subMethodName = null;
}
methodName = method.Name;
}
// Method name
methodDisplayInfo.MethodBase = method;
methodDisplayInfo.Name = methodName;
if (method.Name.IndexOf("<") >= 0)
{
if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal))
{
methodName = method.Name;
methodDisplayInfo.MethodBase = method;
methodDisplayInfo.Name = methodName;
methodDisplayInfo.Ordinal = ordinal;
}
else
{
methodDisplayInfo.MethodBase = null;
}
methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod);
if (methodDisplayInfo.IsLambda && type != null)
{
if (methodName == ".cctor")
{
var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
var value = field.GetValue(field);
if (value is Delegate d)
{
if (ReferenceEquals(d.Method, originMethod) &&
d.Target.ToString() == originMethod.DeclaringType.ToString())
{
methodDisplayInfo.Name = field.Name;
methodDisplayInfo.IsLambda = false;
method = originMethod;
break;
}
}
}
}
}
}
if (subMethodName != methodName)
{
methodDisplayInfo.SubMethod = subMethodName;
}
// ResolveStateMachineMethod may have set declaringType to null
if (type != null)
{
var declaringTypeName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true);
methodDisplayInfo.DeclaringTypeName = declaringTypeName;
}
if (method is System.Reflection.MethodInfo mi)
{
methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter);
}
if (method.IsGenericMethod)
{
var genericArguments = string.Join(", ", method.GetGenericArguments()
.Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
methodDisplayInfo.GenericArguments += "<" + genericArguments + ">";
}
// Method parameters
var parameters = method.GetParameters();
if (parameters.Length > 0)
{
var parameterList = new List<ResolvedParameter>(parameters.Length);
foreach (var parameter in parameters)
{
parameterList.Add(GetParameter(parameter));
}
methodDisplayInfo.Parameters = parameterList;
}
if (methodDisplayInfo.SubMethodBase == methodDisplayInfo.MethodBase)
{
methodDisplayInfo.SubMethodBase = null;
}
else if (methodDisplayInfo.SubMethodBase != null)
{
parameters = methodDisplayInfo.SubMethodBase.GetParameters();
if (parameters.Length > 0)
{
var parameterList = new List<ResolvedParameter>(parameters.Length);
foreach (var parameter in parameters)
{
var param = GetParameter(parameter);
if (param.Name?.StartsWith("<") ?? true) continue;
parameterList.Add(param);
}
methodDisplayInfo.SubMethodParameters = parameterList;
}
}
return methodDisplayInfo;
}
private static bool TryResolveGeneratedName(ref MethodBase method, out Type type, out string methodName, out string subMethodName, out GeneratedNameKind kind, out int? ordinal)
{
kind = GeneratedNameKind.None;
type = method.DeclaringType;
subMethodName = null;
ordinal = null;
methodName = method.Name;
var generatedName = methodName;
if (!TryParseGeneratedName(generatedName, out kind, out int openBracketOffset, out int closeBracketOffset))
{
return false;
}
methodName = generatedName.Substring(openBracketOffset + 1, closeBracketOffset - openBracketOffset - 1);
switch (kind)
{
case GeneratedNameKind.LocalFunction:
{
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
if (localNameStart < 0) break;
localNameStart += 3;
if (localNameStart < generatedName.Length)
{
var localNameEnd = generatedName.IndexOf("|", localNameStart);
if (localNameEnd > 0)
{
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
}
}
break;
}
case GeneratedNameKind.LambdaMethod:
subMethodName = "";
break;
}
var dt = method.DeclaringType;
if (dt == null)
{
return false;
}
var matchHint = GetMatchHint(kind, method);
var matchName = methodName;
var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true;
var candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true;
dt = dt.DeclaringType;
if (dt == null)
{
return false;
}
candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true;
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true;
return false;
}
private static bool TryResolveSourceMethod(IEnumerable<MethodBase> candidateMethods, GeneratedNameKind kind, string matchHint, ref MethodBase method, ref Type type, out int? ordinal)
{
ordinal = null;
foreach (var candidateMethod in candidateMethods)
{
var nethodBody = candidateMethod.GetMethodBody();
if (kind == GeneratedNameKind.LambdaMethod)
{
foreach (var v in EnumerableIList.Create(nethodBody?.LocalVariables))
{
if (v.LocalType == type)
{
GetOrdinal(method, ref ordinal);
}
method = candidateMethod;
type = method.DeclaringType;
return true;
}
}
var rawIL = nethodBody?.GetILAsByteArray();
if (rawIL == null) continue;
var reader = new ILReader(rawIL);
while (reader.Read(candidateMethod))
{
if (reader.Operand is MethodBase mb)
{
if (method == mb || (matchHint != null && method.Name.Contains(matchHint)))
{
if (kind == GeneratedNameKind.LambdaMethod)
{
GetOrdinal(method, ref ordinal);
}
method = candidateMethod;
type = method.DeclaringType;
return true;
}
}
else if (reader.Operand is Type t)
{
if (t == type)
{
method = candidateMethod;
type = method.DeclaringType;
return true;
}
}
}
}
return false;
}
private static void GetOrdinal(MethodBase method, ref int? ordinal)
{
var lamdaStart = method.Name.IndexOf((char) GeneratedNameKind.LambdaMethod + "__") + 3;
if (lamdaStart > 3)
{
var secondStart = method.Name.IndexOf("_", lamdaStart) + 1;
if (secondStart > 0)
{
lamdaStart = secondStart;
}
if (!int.TryParse(method.Name.Substring(lamdaStart), out var foundOrdinal))
{
ordinal = null;
return;
}
ordinal = foundOrdinal;
var methods = method.DeclaringType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
var startName = method.Name.Substring(0, lamdaStart);
var count = 0;
foreach (var m in methods)
{
if (m.Name.Length > lamdaStart && m.Name.StartsWith(startName))
{
count++;
if (count > 1)
{
break;
}
}
}
if (count <= 1)
{
ordinal = null;
}
}
}
static string GetMatchHint(GeneratedNameKind kind, MethodBase method)
{
var methodName = method.Name;
switch (kind)
{
case GeneratedNameKind.LocalFunction:
var start = methodName.IndexOf("|");
if (start < 1) return null;
var end = methodName.IndexOf("_", start) + 1;
if (end <= start) return null;
return methodName.Substring(start, end - start);
}
return null;
}
// Parse the generated name. Returns true for names of the form
// [CS$]<[middle]>c[__[suffix]] where [CS$] is included for certain
// generated names, where [middle] and [__[suffix]] are optional,
// and where c is a single character in [1-9a-z]
// (csharp\LanguageAnalysis\LIB\SpecialName.cpp).
internal static bool TryParseGeneratedName(
string name,
out GeneratedNameKind kind,
out int openBracketOffset,
out int closeBracketOffset)
{
openBracketOffset = -1;
if (name.StartsWith("CS$<", StringComparison.Ordinal))
{
openBracketOffset = 3;
}
else if (name.StartsWith("<", StringComparison.Ordinal))
{
openBracketOffset = 0;
}
if (openBracketOffset >= 0)
{
closeBracketOffset = IndexOfBalancedParenthesis(name, openBracketOffset, '>');
if (closeBracketOffset >= 0 && closeBracketOffset + 1 < name.Length)
{
int c = name[closeBracketOffset + 1];
if ((c >= '1' && c <= '9') || (c >= 'a' && c <= 'z')) // Note '0' is not special.
{
kind = (GeneratedNameKind)c;
return true;
}
}
}
kind = GeneratedNameKind.None;
openBracketOffset = -1;
closeBracketOffset = -1;
return false;
}
private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing)
{
char opening = str[openingOffset];
int depth = 1;
for (int i = openingOffset + 1; i < str.Length; i++)
{
var c = str[i];
if (c == opening)
{
depth++;
}
else if (c == closing)
{
depth--;
if (depth == 0)
{
return i;
}
}
}
return -1;
}
private static string GetPrefix(ParameterInfo parameter, Type parameterType)
{
if (parameter.IsOut)
{
return "out";
}
else if (parameterType != null && parameterType.IsByRef)
{
var attribs = parameter.GetCustomAttributes(inherit: false);
if (attribs?.Length > 0)
{
foreach (var attrib in attribs)
{
if (attrib is Attribute att && att.GetType().Namespace == "System.Runtime.CompilerServices" && att.GetType().Name == "IsReadOnlyAttribute")
{
return "in";
}
}
}
return "ref";
}
return string.Empty;
}
private static ResolvedParameter GetParameter(ParameterInfo parameter)
{
var parameterType = parameter.ParameterType;
var prefix = GetPrefix(parameter, parameterType);
var parameterTypeString = "?";
if (parameterType != null)
{
if (parameterType.IsGenericType)
{
var tupleNames = parameter.GetCustomAttributes<TupleElementNamesAttribute>().FirstOrDefault()?.TransformNames;
if (tupleNames != null)
{
var sb = new StringBuilder();
sb.Append("(");
var args = parameterType.GetGenericArguments();
for (var i = 0; i < args.Length; i++)
{
if (i > 0)
{
sb.Append(", ");
}
sb.Append(TypeNameHelper.GetTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true));
if (i >= tupleNames.Count) continue;
var argName = tupleNames[i];
if (argName != null)
{
sb.Append(" ");
sb.Append(argName);
}
}
sb.Append(")");
parameterTypeString = sb.ToString();
return new ResolvedParameter
{
Prefix = prefix,
Name = parameter.Name,
Type = parameterTypeString,
};
}
}
if (parameterType.IsByRef)
{
parameterType = parameterType.GetElementType();
}
parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true);
}
return new ResolvedParameter
{
Prefix = prefix,
Name = parameter.Name,
Type = parameterTypeString,
};
}
private static bool ShowInStackTrace(MethodBase method)
{
Debug.Assert(method != null);
try
{
// Don't show any methods marked with the StackTraceHiddenAttribute
// https://github.com/dotnet/coreclr/pull/14652
foreach (var attibute in EnumerableIList.Create(method.GetCustomAttributesData()))
{
// internal Attribute, match on name
if (attibute.AttributeType.Name == "StackTraceHiddenAttribute")
{
return false;
}
}
var type = method.DeclaringType;
if (type == null)
{
return true;
}
foreach (var attibute in EnumerableIList.Create(type.GetCustomAttributesData()))
{
// internal Attribute, match on name
if (attibute.AttributeType.Name == "StackTraceHiddenAttribute")
{
return false;
}
}
// Fallbacks for runtime pre-StackTraceHiddenAttribute
if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw")
{
return false;
}
else if (type == typeof(TaskAwaiter) ||
type == typeof(TaskAwaiter<>) ||
type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) ||
type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter))
{
switch (method.Name)
{
case "HandleNonSuccessAndDebuggerNotification":
case "ThrowForNonSuccess":
case "ValidateEnd":
case "GetResult":
return false;
}
}
else if (type.FullName == "System.ThrowHelper")
{
return false;
}
}
catch
{
// GetCustomAttributesData can throw
return true;
}
return true;
}
private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
{
Debug.Assert(method != null);
Debug.Assert(method.DeclaringType != null);
declaringType = method.DeclaringType;
var parentType = declaringType.DeclaringType;
if (parentType == null)
{
return false;
}
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (methods == null)
{
return false;
}
foreach (var candidateMethod in methods)
{
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>();
if (attributes == null)
{
continue;
}
foreach (var asma in attributes)
{
if (asma.StateMachineType == declaringType)
{
method = candidateMethod;
declaringType = candidateMethod.DeclaringType;
// Mark the iterator as changed; so it gets the + annotation of the original method
// async statemachines resolve directly to their builder methods so aren't marked as changed
return asma is IteratorStateMachineAttribute;
}
}
}
return false;
}
internal enum GeneratedNameKind
{
None = 0,
// Used by EE:
ThisProxyField = '4',
HoistedLocalField = '5',
DisplayClassLocalOrField = '8',
LambdaMethod = 'b',
LambdaDisplayClass = 'c',
StateMachineType = 'd',
LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names
// Used by EnC:
AwaiterField = 'u',
HoistedSynthesizedLocalField = 's',
// Currently not parsed:
StateMachineStateField = '1',
IteratorCurrentBackingField = '2',
StateMachineParameterProxyField = '3',
ReusableHoistedLocalField = '7',
LambdaCacheField = '9',
FixedBufferField = 'e',
AnonymousType = 'f',
TransparentIdentifier = 'h',
AnonymousTypeField = 'i',
AutoPropertyBackingField = 'k',
IteratorCurrentThreadIdField = 'l',
IteratorFinallyMethod = 'm',
BaseMethodWrapper = 'n',
AsyncBuilderField = 't',
DynamicCallSiteContainerType = 'o',
DynamicCallSiteField = 'p'
}
}
}

View File

@ -0,0 +1,113 @@
// 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<EnhancedStackFrame>
{
private readonly List<EnhancedStackFrame> _frames;
// Summary:
// Initializes a new instance of the System.Diagnostics.StackTrace class using the
// provided exception object.
//
// Parameters:
// e:
// The exception object from which to construct the stack trace.
//
// Exceptions:
// T:System.ArgumentNullException:
// The parameter e is null.
public EnhancedStackTrace(Exception e)
{
if (e == null)
{
throw new ArgumentNullException(nameof(e));
}
_frames = GetFrames(e);
}
/// <summary>
/// Gets the number of frames in the stack trace.
/// </summary>
/// <returns>The number of frames in the stack trace.</returns>
public override int FrameCount => _frames.Count;
/// <summary>
/// Gets the specified stack frame.
/// </summary>
/// <param name="index">The index of the stack frame requested.</param>
/// <returns>The specified stack frame.</returns>
public override StackFrame GetFrame(int index) => _frames[index];
/// <summary>
/// Returns a copy of all stack frames in the current stack trace.
/// </summary>
/// <returns>
/// An array of type System.Diagnostics.StackFrame representing the function calls
/// in the stack trace.
/// </returns>
public override StackFrame[] GetFrames() => _frames.ToArray();
/// <summary>
/// Builds a readable representation of the stack trace.
/// </summary>
/// <returns>A readable representation of the stack trace.</returns>
public override string ToString()
{
if (_frames == null) return "";
var sb = new StringBuilder();
Append(sb);
return sb.ToString();
}
internal void Append(StringBuilder sb)
{
var frames = _frames;
var count = frames.Count;
for (var i = 0; i < count; i++)
{
if (i > 0)
{
sb.AppendLine();
}
var frame = frames[i];
sb.Append(" at ");
frame.MethodInfo.Append(sb);
var filePath = frame.GetFileName();
if (!string.IsNullOrEmpty(filePath))
{
sb.Append(" in ");
sb.Append(System.IO.Path.GetFullPath(filePath));
}
var lineNo = frame.GetFileLineNumber();
if (lineNo != 0)
{
sb.Append(":line ");
sb.Append(lineNo);
}
}
}
EnumerableIList<EnhancedStackFrame> GetEnumerator() => EnumerableIList.Create(_frames);
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
}
}

View File

@ -0,0 +1,68 @@
// 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.
namespace System.Collections.Generic.Enumerable
{
public static class EnumerableIList
{
public static EnumerableIList<T> Create<T>(IList<T> list) => new EnumerableIList<T>(list);
}
public readonly struct EnumerableIList<T> : IEnumerableIList<T>, IList<T>
{
private readonly IList<T> _list;
public EnumerableIList(IList<T> list)
{
_list = list;
}
public EnumeratorIList<T> GetEnumerator() => new EnumeratorIList<T>(_list);
public static implicit operator EnumerableIList<T>(List<T> list) => new EnumerableIList<T>(list);
public static implicit operator EnumerableIList<T>(T[] array) => new EnumerableIList<T>(array);
public static EnumerableIList<T> Empty = default;
// IList pass through
/// <inheritdoc />
public T this[int index] { get => _list[index]; set => _list[index] = value; }
/// <inheritdoc />
public int Count => _list.Count;
/// <inheritdoc />
public bool IsReadOnly => _list.IsReadOnly;
/// <inheritdoc />
public void Add(T item) => _list.Add(item);
/// <inheritdoc />
public void Clear() => _list.Clear();
/// <inheritdoc />
public bool Contains(T item) => _list.Contains(item);
/// <inheritdoc />
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
/// <inheritdoc />
public int IndexOf(T item) => _list.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, T item) => _list.Insert(index, item);
/// <inheritdoc />
public bool Remove(T item) => _list.Remove(item);
/// <inheritdoc />
public void RemoveAt(int index) => _list.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
}
}

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.
namespace System.Collections.Generic.Enumerable
{
public struct EnumeratorIList<T> : IEnumerator<T>
{
private readonly IList<T> _list;
private int _index;
public EnumeratorIList(IList<T> list)
{
_index = -1;
_list = list;
}
public T Current => _list[_index];
public bool MoveNext()
{
_index++;
return _index < (_list?.Count ?? 0);
}
public void Dispose() { }
object IEnumerator.Current => Current;
public void Reset() => _index = -1;
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace System.Collections.Generic.Enumerable
{
interface IEnumerableIList<T> : IEnumerable<T>
{
new EnumeratorIList<T> GetEnumerator();
}
}

View File

@ -0,0 +1,29 @@
// 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;
namespace System.Diagnostics
{
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
{
try
{
var stackTrace = new EnhancedStackTrace(exception);
stackTraceString.SetValue(exception, stackTrace.ToString());
exception.InnerException?.Demystify();
}
catch
{
// Processing exceptions shouldn't throw exceptions; if it fails
}
return exception;
}
}
}

View File

@ -0,0 +1,101 @@
using System.Reflection;
using System.Reflection.Emit;
namespace System.Diagnostics.Internal
{
internal class ILReader
{
private static OpCode[] singleByteOpCode;
private static OpCode[] doubleByteOpCode;
private readonly byte[] _cil;
private int ptr;
public ILReader(byte[] cil)
{
_cil = cil;
}
public OpCode OpCode { get; private set; }
public int MetadataToken { get; private set; }
public object Operand { get; private set; }
public bool Read(MethodBase methodInfo)
{
if (ptr < _cil.Length)
{
OpCode = ReadOpCode();
Operand = ReadOperand(OpCode, methodInfo);
return true;
}
return false;
}
OpCode ReadOpCode()
{
byte instruction = ReadByte();
if (instruction != 254)
return singleByteOpCode[instruction];
else
return doubleByteOpCode[ReadByte()];
}
object ReadOperand(OpCode code, MethodBase methodInfo)
{
MetadataToken = 0;
switch (code.OperandType)
{
case OperandType.InlineMethod:
MetadataToken = ReadInt();
Type[] methodArgs = null;
if (methodInfo.GetType() != typeof(ConstructorInfo) && !methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo)))
methodArgs = methodInfo.GetGenericArguments();
Type[] typeArgs = null;
if (methodInfo.DeclaringType != null)
typeArgs = methodInfo.DeclaringType.GetGenericArguments();
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
}
return null;
}
byte ReadByte()
{
return _cil[ptr++];
}
int ReadInt()
{
byte b1 = ReadByte();
byte b2 = ReadByte();
byte b3 = ReadByte();
byte b4 = ReadByte();
return (int)b1 | (((int)b2) << 8) | (((int)b3) << 16) | (((int)b4) << 24);
}
static ILReader()
{
singleByteOpCode = new OpCode[225];
doubleByteOpCode = new OpCode[31];
FieldInfo[] fields = GetOpCodeFields();
for (int i = 0; i < fields.Length; i++)
{
OpCode code = (OpCode)fields[i].GetValue(null);
if (code.OpCodeType == OpCodeType.Nternal)
continue;
if (code.Size == 1)
singleByteOpCode[code.Value] = code;
else
doubleByteOpCode[code.Value & 0xff] = code;
}
}
static FieldInfo[] GetOpCodeFields()
{
return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
}
}
}

View File

@ -0,0 +1,139 @@
// Copyright (c) .NET Foundation. 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.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
namespace System.Diagnostics.Internal
{
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs
internal class PortablePdbReader : IDisposable
{
private readonly Dictionary<string, MetadataReaderProvider> _cache =
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, out int row, out int column)
{
fileName = "";
row = 0;
column = 0;
if (method.Module.Assembly.IsDynamic)
{
return;
}
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
if (metadataReader == null)
{
return;
}
var methodToken = MetadataTokens.Handle(method.MetadataToken);
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
if (!handle.IsNil)
{
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
var sequencePoints = methodDebugInfo.GetSequencePoints();
SequencePoint? bestPointSoFar = null;
foreach (var point in sequencePoints)
{
if (point.Offset > IlOffset)
{
break;
}
if (point.StartLine != SequencePoint.HiddenLine)
{
bestPointSoFar = point;
}
}
if (bestPointSoFar.HasValue)
{
row = bestPointSoFar.Value.StartLine;
column = bestPointSoFar.Value.StartColumn;
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
}
}
}
private MetadataReader GetMetadataReader(string assemblyPath)
{
MetadataReaderProvider provider = null;
if (!_cache.TryGetValue(assemblyPath, out provider))
{
var pdbPath = GetPdbPath(assemblyPath);
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath))
{
var pdbStream = File.OpenRead(pdbPath);
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
}
_cache[assemblyPath] = provider;
}
return provider?.GetMetadataReader();
}
private static string GetPdbPath(string assemblyPath)
{
if (string.IsNullOrEmpty(assemblyPath))
{
return null;
}
if (File.Exists(assemblyPath))
{
var peStream = File.OpenRead(assemblyPath);
using (var peReader = new PEReader(peStream))
{
foreach (var entry in peReader.ReadDebugDirectory())
{
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
var peDirectory = Path.GetDirectoryName(assemblyPath);
return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
}
}
}
}
return null;
}
private static bool IsPortable(string pdbPath)
{
using (var pdbStream = File.OpenRead(pdbPath))
{
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
}
}
public void Dispose()
{
foreach (var entry in _cache)
{
entry.Value?.Dispose();
}
_cache.Clear();
}
}
}

View File

@ -0,0 +1,159 @@
// Copyright (c) .NET Foundation. 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.Text;
namespace System.Diagnostics.Internal
{
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
internal class TypeNameHelper
{
private static readonly Dictionary<Type, string> _builtInTypeNames = new Dictionary<Type, string>
{
{ typeof(void), "void" },
{ typeof(bool), "bool" },
{ typeof(byte), "byte" },
{ typeof(char), "char" },
{ typeof(decimal), "decimal" },
{ typeof(double), "double" },
{ typeof(float), "float" },
{ typeof(int), "int" },
{ typeof(long), "long" },
{ typeof(object), "object" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(string), "string" },
{ typeof(uint), "uint" },
{ typeof(ulong), "ulong" },
{ typeof(ushort), "ushort" }
};
/// <summary>
/// Pretty print a type name.
/// </summary>
/// <param name="type">The <see cref="Type"/>.</param>
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
/// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
{
var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
return builder.ToString();
}
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
{
if (type.IsGenericType)
{
var genericArguments = type.GetGenericArguments();
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
}
else if (type.IsArray)
{
ProcessArrayType(builder, type, options);
}
else if (_builtInTypeNames.TryGetValue(type, out var builtInName))
{
builder.Append(builtInName);
}
else if (type.Namespace == nameof(System))
{
builder.Append(type.Name);
}
else if (type.IsGenericParameter)
{
if (options.IncludeGenericParameterNames)
{
builder.Append(type.Name);
}
}
else
{
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
}
}
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
{
var innerType = type;
while (innerType.IsArray)
{
innerType = innerType.GetElementType();
}
ProcessType(builder, innerType, options);
while (type.IsArray)
{
builder.Append('[');
builder.Append(',', type.GetArrayRank() - 1);
builder.Append(']');
type = type.GetElementType();
}
}
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options)
{
var offset = 0;
if (type.IsNested)
{
offset = type.DeclaringType.GetGenericArguments().Length;
}
if (options.FullName)
{
if (type.IsNested)
{
ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options);
builder.Append('+');
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
builder.Append(type.Namespace);
builder.Append('.');
}
}
var genericPartIndex = type.Name.IndexOf('`');
if (genericPartIndex <= 0)
{
builder.Append(type.Name);
return;
}
builder.Append(type.Name, 0, genericPartIndex);
builder.Append('<');
for (var i = offset; i < length; i++)
{
ProcessType(builder, genericArguments[i], options);
if (i + 1 == length)
{
continue;
}
builder.Append(',');
if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter)
{
builder.Append(' ');
}
}
builder.Append('>');
}
private struct DisplayNameOptions
{
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
{
FullName = fullName;
IncludeGenericParameterNames = includeGenericParameterNames;
}
public bool FullName { get; }
public bool IncludeGenericParameterNames { get; }
}
}
}

View File

@ -0,0 +1,147 @@
// Copyright (c) .NET Foundation. 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.Reflection;
using System.Text;
namespace System.Diagnostics
{
public class ResolvedMethod
{
public MethodBase MethodBase { get; set; }
public string DeclaringTypeName { get; set; }
public bool IsAsync { get; set; }
public bool IsLambda { get; set; }
public ResolvedParameter ReturnParameter { get; set; }
public string Name { get; set; }
public int? Ordinal { get; set; }
public string GenericArguments { get; set; }
public MethodBase SubMethodBase { get; set; }
public string SubMethod { get; set; }
public EnumerableIList<ResolvedParameter> Parameters { get; set; }
public EnumerableIList<ResolvedParameter> SubMethodParameters { get; set; }
public override string ToString() => Append(new StringBuilder()).ToString();
internal StringBuilder Append(StringBuilder builder)
{
if (IsAsync)
{
builder
.Append("async ");
}
if (ReturnParameter != null)
{
ReturnParameter.Append(builder);
builder.Append(" ");
}
if (!string.IsNullOrEmpty(DeclaringTypeName))
{
if (Name == ".ctor")
{
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
builder.Append("new ");
builder.Append(DeclaringTypeName);
}
else if (Name == ".cctor")
{
builder.Append("static ");
builder.Append(DeclaringTypeName);
}
else
{
builder
.Append(DeclaringTypeName)
.Append(".")
.Append(Name);
}
}
else
{
builder.Append(Name);
}
builder.Append(GenericArguments);
builder.Append("(");
if (MethodBase != null)
{
var isFirst = true;
foreach(var param in Parameters)
{
if (isFirst)
{
isFirst = false;
}
else
{
builder.Append(", ");
}
param.Append(builder);
}
}
else
{
builder.Append("?");
}
builder.Append(")");
if (!string.IsNullOrEmpty(SubMethod) || IsLambda)
{
builder.Append("+");
builder.Append(SubMethod);
builder.Append("(");
if (SubMethodBase != null)
{
var isFirst = true;
foreach (var param in SubMethodParameters)
{
if (isFirst)
{
isFirst = false;
}
else
{
builder.Append(", ");
}
param.Append(builder);
}
}
else
{
builder.Append("?");
}
builder.Append(")");
if (IsLambda)
{
builder.Append("=>{}");
if (Ordinal.HasValue)
{
builder.Append(" [");
builder.Append(Ordinal);
builder.Append("]");
}
}
}
return builder;
}
}
}

View File

@ -0,0 +1,36 @@
// 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.Text;
namespace System.Diagnostics
{
public class ResolvedParameter
{
public string Name { get; set; }
public string Type { get; set; }
public string Prefix { get; set; }
public override string ToString() => Append(new StringBuilder()).ToString();
internal StringBuilder Append(StringBuilder sb)
{
if (!string.IsNullOrEmpty(Prefix))
{
sb.Append(Prefix)
.Append(" ");
}
sb.Append(Type);
if (!string.IsNullOrEmpty(Name))
{
sb.Append(" ")
.Append(Name);
}
return sb;
}
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ben.Demystifier\Ben.Demystifier.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
namespace Demystify
{
public class MixedStack
{
[Fact]
public void ProducesReadableFrames()
{
// Arrange
Exception exception = GetMixedStackException();
// Act
var methodNames = new EnhancedStackTrace(exception)
.Select(
stackFrame => stackFrame.MethodInfo.ToString()
)
// Remove Framework method that can be optimized out (inlined)
.Where(methodName => methodName != "System.Collections.Generic.List<T>+Enumerator.MoveNext()")
// Don't include this method as call stack shared between multiple tests
.SkipLast(1);
foreach (var method in methodNames)
{
Console.WriteLine(method.ToString());
}
// Assert
Assert.Equal (ExpectedCallStack, methodNames.ToList());
}
Exception GetMixedStackException()
{
Exception exception = null;
try
{
Start((val:"", true));
}
catch (Exception ex)
{
exception = ex;
}
return exception;
}
static List<string> ExpectedCallStack = new List<string>()
{
"bool System.Collections.Generic.List<T>+Enumerator.MoveNextRare()",
"IEnumerable<string> Demystify.MixedStack.Iterator()+MoveNext()",
"string string.Join(string separator, IEnumerable<string> values)",
"string Demystify.MixedStack+GenericClass<T>.GenericMethod<V>(ref V value)",
"async Task<string> Demystify.MixedStack.MethodAsync(int value)",
"async Task<string> Demystify.MixedStack.MethodAsync<TValue>(TValue value)",
"(string val, bool) Demystify.MixedStack.Method(string value)",
"ref string Demystify.MixedStack.RefMethod(string value)",
"(string val, bool) Demystify.MixedStack.s_func(string s, bool b)",
"void Demystify.MixedStack.s_action(string s, bool b)",
"void Demystify.MixedStack.Start((string val, bool) param)"
};
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static IEnumerable<string> Iterator()
{
var list = new List<int>() { 1, 2, 3, 4 };
foreach (var item in list)
{
// Throws the exception
list.Add(item);
yield return item.ToString();
}
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static async Task<string> MethodAsync(int value)
{
await Task.Delay(0);
return GenericClass<byte>.GenericMethod(ref value);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static async Task<string> MethodAsync<TValue>(TValue value)
{
return await MethodAsync(1);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static (string val, bool) Method(string value)
{
return (MethodAsync(value).GetAwaiter().GetResult(), true);
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static ref string RefMethod(string value)
{
Method(value);
return ref s;
}
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void Start((string val, bool) param)
{
s_action.Invoke(param.val, param.Item2);
}
static Action<string, bool> s_action = (string s, bool b) => s_func(s, b);
static Func<string, bool, (string val, bool)> s_func = (string s, bool b) => (RefMethod(s), b);
static string s = "";
class GenericClass<T>
{
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static string GenericMethod<V>(ref V value)
{
var returnVal = "";
for (var i = 0; i < 10; i++)
{
returnVal += string.Join(", ", Iterator());
}
return returnVal;
}
}
}
}