Add 0.0.1 code
This commit is contained in:
parent
3164e5c775
commit
34930738f6
48
Ben.Demystifier.sln
Normal file
48
Ben.Demystifier.sln
Normal 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
|
||||
166
sample/StackTrace/Program.cs
Normal file
166
sample/StackTrace/Program.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
sample/StackTrace/StackTrace.csproj
Normal file
20
sample/StackTrace/StackTrace.csproj
Normal 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>
|
||||
35
src/Ben.Demystifier/Ben.Demystifier.csproj
Normal file
35
src/Ben.Demystifier/Ben.Demystifier.csproj
Normal 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>
|
||||
79
src/Ben.Demystifier/EnhancedStackFrame.cs
Normal file
79
src/Ben.Demystifier/EnhancedStackFrame.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
698
src/Ben.Demystifier/EnhancedStackTrace.Frames.cs
Normal file
698
src/Ben.Demystifier/EnhancedStackTrace.Frames.cs
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/Ben.Demystifier/EnhancedStackTrace.cs
Normal file
113
src/Ben.Demystifier/EnhancedStackTrace.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
68
src/Ben.Demystifier/Enumerable/EnumerableIList.cs
Normal file
68
src/Ben.Demystifier/Enumerable/EnumerableIList.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
30
src/Ben.Demystifier/Enumerable/EnumeratorIList.cs
Normal file
30
src/Ben.Demystifier/Enumerable/EnumeratorIList.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
10
src/Ben.Demystifier/Enumerable/IEnumerableIList.cs
Normal file
10
src/Ben.Demystifier/Enumerable/IEnumerableIList.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
29
src/Ben.Demystifier/ExceptionExtentions.cs
Normal file
29
src/Ben.Demystifier/ExceptionExtentions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/Ben.Demystifier/Internal/ILReader.cs
Normal file
101
src/Ben.Demystifier/Internal/ILReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/Ben.Demystifier/Internal/PortablePdbReader.cs
Normal file
139
src/Ben.Demystifier/Internal/PortablePdbReader.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/Ben.Demystifier/Internal/TypeNameHelper.cs
Normal file
159
src/Ben.Demystifier/Internal/TypeNameHelper.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/Ben.Demystifier/ResolvedMethod.cs
Normal file
147
src/Ben.Demystifier/ResolvedMethod.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/Ben.Demystifier/ResolvedParameter.cs
Normal file
36
src/Ben.Demystifier/ResolvedParameter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj
Normal file
19
test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj
Normal 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>
|
||||
132
test/Ben.Demystifier.Test/MixedStack.cs
Normal file
132
test/Ben.Demystifier.Test/MixedStack.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user