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