fixed warnings
This commit is contained in:
parent
5f593123de
commit
ffddfa21ec
@ -1,71 +0,0 @@
|
||||
|
||||
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackTrace", "sample\StackTrace\StackTrace.csproj", "{E161FC12-53C2-47CD-A5FC-3684B86723A9}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5937ACDF-0059-488E-9604-D84689C72933}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
appveyor.yml = appveyor.yml
|
||||
build.ps1 = build.ps1
|
||||
Directory.Build.props = Directory.Build.props
|
||||
README.md = README.md
|
||||
version.json = version.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ben.Demystifier.Benchmarks", "test\Ben.Demystifier.Benchmarks\Ben.Demystifier.Benchmarks.csproj", "{EF5557DF-C48E-4999-846C-D99A92E86373}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpStackTrace", "sample\FSharpStackTrace\FSharpStackTrace.fsproj", "{D6B779D2-A678-47CC-A2F9-A312292EA7A2}"
|
||||
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
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D6B779D2-A678-47CC-A2F9-A312292EA7A2}.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}
|
||||
{EF5557DF-C48E-4999-846C-D99A92E86373} = {59CA6310-4AA5-4093-95D4-472B94DC0CD4}
|
||||
{D6B779D2-A678-47CC-A2F9-A312292EA7A2} = {455921D3-DD54-4355-85CF-F4009DF2AB70}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {841B7D5F-E810-4F94-A529-002C7E075216}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -1,16 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<Nullable>enable</Nullable>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net6.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<Nullable>enable</Nullable>
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" Condition="'$(TargetFramework)' != 'net6.0'"/>
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' != 'netstandard2.1'" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" Condition="'$(TargetFramework)' != 'net6.0'"/>
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' != 'netstandard2.1'"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,93 +1,84 @@
|
||||
// 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 Ben.Demystifier;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
public class EnhancedStackFrame : StackFrame
|
||||
{
|
||||
public class EnhancedStackFrame : StackFrame
|
||||
private readonly int _colNumber;
|
||||
private readonly string? _fileName;
|
||||
private readonly int _lineNumber;
|
||||
|
||||
internal EnhancedStackFrame(StackFrame stackFrameBase, ResolvedMethod methodInfo, string? fileName, int lineNumber,
|
||||
int colNumber)
|
||||
: base(fileName, lineNumber, colNumber)
|
||||
{
|
||||
private readonly string? _fileName;
|
||||
private readonly int _lineNumber;
|
||||
private readonly int _colNumber;
|
||||
|
||||
public StackFrame StackFrame { get; }
|
||||
|
||||
public bool IsRecursive
|
||||
{
|
||||
get => MethodInfo.RecurseCount > 0;
|
||||
internal set => MethodInfo.RecurseCount++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber)
|
||||
{
|
||||
return _lineNumber == lineNumber &&
|
||||
_colNumber == colNumber &&
|
||||
_fileName == fileName &&
|
||||
MethodInfo.IsSequentialEquivalent(methodInfo);
|
||||
}
|
||||
|
||||
/// <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();
|
||||
StackFrameBase = stackFrameBase;
|
||||
MethodInfo = methodInfo;
|
||||
_fileName = fileName;
|
||||
_lineNumber = lineNumber;
|
||||
_colNumber = colNumber;
|
||||
}
|
||||
|
||||
private StackFrame StackFrameBase { get; }
|
||||
|
||||
public bool IsRecursive
|
||||
{
|
||||
get => MethodInfo.RecurseCount > 0;
|
||||
internal set => MethodInfo.RecurseCount++;
|
||||
}
|
||||
|
||||
public ResolvedMethod MethodInfo { get; }
|
||||
|
||||
internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) =>
|
||||
_lineNumber == lineNumber &&
|
||||
_colNumber == colNumber &&
|
||||
_fileName == fileName &&
|
||||
MethodInfo.IsSequentialEquivalent(methodInfo);
|
||||
|
||||
/// <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() => StackFrameBase.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() => StackFrameBase.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() => StackFrameBase.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();
|
||||
}
|
||||
@ -3,28 +3,27 @@
|
||||
// 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.
|
||||
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using Ben.Demystifier.Internal;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using Ben.Demystifier.Internal;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
{
|
||||
public partial class EnhancedStackTrace
|
||||
{
|
||||
private static readonly Type? StackTraceHiddenAttributeType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false);
|
||||
private static readonly Type? AsyncIteratorStateMachineAttributeType = Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
||||
private static readonly Type? StackTraceHiddenAttributeType =
|
||||
Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false);
|
||||
|
||||
private static readonly Type? AsyncIteratorStateMachineAttributeType =
|
||||
Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
||||
|
||||
static EnhancedStackTrace()
|
||||
{
|
||||
if (AsyncIteratorStateMachineAttributeType != null) return;
|
||||
if (AsyncIteratorStateMachineAttributeType is not null) return;
|
||||
|
||||
Assembly mba;
|
||||
try
|
||||
@ -36,32 +35,15 @@ namespace Ben.Demystifier
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncIteratorStateMachineAttributeType = mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
||||
}
|
||||
|
||||
private static List<EnhancedStackFrame> GetFrames(Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return new List<EnhancedStackFrame>();
|
||||
}
|
||||
|
||||
var needFileInfo = true;
|
||||
var stackTrace = new StackTrace(exception, needFileInfo);
|
||||
|
||||
return GetFrames(stackTrace);
|
||||
AsyncIteratorStateMachineAttributeType =
|
||||
mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
||||
}
|
||||
|
||||
public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace)
|
||||
{
|
||||
var frames = new List<EnhancedStackFrame>();
|
||||
var enhancedFrames = new List<EnhancedStackFrame>();
|
||||
var stackFrames = stackTrace.GetFrames();
|
||||
|
||||
if (stackFrames == null)
|
||||
{
|
||||
return frames;
|
||||
}
|
||||
|
||||
EnhancedStackFrame? lastFrame = null;
|
||||
PortablePdbReader? portablePdbReader = null;
|
||||
try
|
||||
@ -69,27 +51,22 @@ namespace Ben.Demystifier
|
||||
for (var i = 0; i < stackFrames.Length; i++)
|
||||
{
|
||||
var frame = stackFrames[i];
|
||||
if (frame is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var method = frame.GetMethod();
|
||||
|
||||
// Always show last stackFrame
|
||||
if (method != null && !ShowInStackTrace(method) && i < stackFrames.Length - 1)
|
||||
{
|
||||
if (method is not null && !ShowInStackTrace(method) && i < stackFrames.Length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = frame.GetFileName();
|
||||
var row = frame.GetFileLineNumber();
|
||||
var column = frame.GetFileColumnNumber();
|
||||
var ilOffset = frame.GetILOffset();
|
||||
if (method != null && string.IsNullOrEmpty(fileName) && ilOffset >= 0)
|
||||
if (method is not null && string.IsNullOrEmpty(fileName) && ilOffset >= 0)
|
||||
{
|
||||
// .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 ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column);
|
||||
(portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method,
|
||||
frame.GetILOffset(), out fileName, out row, out column);
|
||||
}
|
||||
|
||||
if (method is null)
|
||||
@ -106,7 +83,7 @@ namespace Ben.Demystifier
|
||||
else
|
||||
{
|
||||
var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column);
|
||||
frames.Add(stackFrame);
|
||||
enhancedFrames.Add(stackFrame);
|
||||
lastFrame = stackFrame;
|
||||
}
|
||||
}
|
||||
@ -116,7 +93,7 @@ namespace Ben.Demystifier
|
||||
portablePdbReader?.Dispose();
|
||||
}
|
||||
|
||||
return frames;
|
||||
return enhancedFrames;
|
||||
}
|
||||
|
||||
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
|
||||
@ -161,7 +138,8 @@ namespace Ben.Demystifier
|
||||
methodDisplayInfo.Name = methodName;
|
||||
if (method.Name.IndexOf("<") >= 0)
|
||||
{
|
||||
if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal))
|
||||
if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind,
|
||||
out var ordinal))
|
||||
{
|
||||
methodName = method.Name;
|
||||
methodDisplayInfo.MethodBase = method;
|
||||
@ -175,7 +153,7 @@ namespace Ben.Demystifier
|
||||
|
||||
methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod);
|
||||
|
||||
if (methodDisplayInfo.IsLambda && type != null)
|
||||
if (methodDisplayInfo.IsLambda && type is not null)
|
||||
{
|
||||
if (methodName == ".cctor")
|
||||
{
|
||||
@ -185,7 +163,8 @@ namespace Ben.Demystifier
|
||||
}
|
||||
else
|
||||
{
|
||||
var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var fields =
|
||||
type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var value = field.GetValue(field);
|
||||
@ -212,33 +191,29 @@ namespace Ben.Demystifier
|
||||
}
|
||||
|
||||
// ResolveStateMachineMethod may have set declaringType to null
|
||||
if (type != null)
|
||||
if (type is not null)
|
||||
{
|
||||
methodDisplayInfo.DeclaringType = type;
|
||||
}
|
||||
|
||||
if (method is MethodInfo mi)
|
||||
{
|
||||
var returnParameter = mi.ReturnParameter;
|
||||
if (returnParameter != null)
|
||||
{
|
||||
if (mi.ReturnParameter is not null)
|
||||
methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter);
|
||||
}
|
||||
else if (mi.ReturnType != null)
|
||||
{
|
||||
else if (mi.ReturnType is not null)
|
||||
methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType)
|
||||
{
|
||||
Prefix = "",
|
||||
Name = "",
|
||||
Name = ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (method.IsGenericMethod)
|
||||
{
|
||||
var genericArguments = method.GetGenericArguments();
|
||||
var genericArgumentsString = string.Join(", ", genericArguments
|
||||
.Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
|
||||
.Select(arg =>
|
||||
TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true)));
|
||||
methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">";
|
||||
methodDisplayInfo.ResolvedGenericArguments = genericArguments;
|
||||
}
|
||||
@ -260,7 +235,7 @@ namespace Ben.Demystifier
|
||||
{
|
||||
methodDisplayInfo.SubMethodBase = null;
|
||||
}
|
||||
else if (methodDisplayInfo.SubMethodBase != null)
|
||||
else if (methodDisplayInfo.SubMethodBase is not null)
|
||||
{
|
||||
parameters = methodDisplayInfo.SubMethodBase.GetParameters();
|
||||
if (parameters.Length > 0)
|
||||
@ -295,7 +270,8 @@ namespace Ben.Demystifier
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName, out string? subMethodName, out GeneratedNameKind kind, out int? ordinal)
|
||||
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;
|
||||
@ -315,28 +291,29 @@ namespace Ben.Demystifier
|
||||
switch (kind)
|
||||
{
|
||||
case GeneratedNameKind.LocalFunction:
|
||||
{
|
||||
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
|
||||
if (localNameStart < 0) break;
|
||||
localNameStart += 3;
|
||||
{
|
||||
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
|
||||
if (localNameStart < 0) break;
|
||||
localNameStart += 3;
|
||||
|
||||
if (localNameStart < generatedName.Length)
|
||||
if (localNameStart < generatedName.Length)
|
||||
{
|
||||
var localNameEnd = generatedName.IndexOf("|", localNameStart);
|
||||
if (localNameEnd > 0)
|
||||
{
|
||||
var localNameEnd = generatedName.IndexOf("|", localNameStart);
|
||||
if (localNameEnd > 0)
|
||||
{
|
||||
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
|
||||
}
|
||||
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GeneratedNameKind.LambdaMethod:
|
||||
subMethodName = "";
|
||||
break;
|
||||
}
|
||||
|
||||
var dt = method.DeclaringType;
|
||||
if (dt == null)
|
||||
if (dt is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -345,30 +322,42 @@ namespace Ben.Demystifier
|
||||
|
||||
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 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;
|
||||
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;
|
||||
|
||||
const int MaxResolveDepth = 10;
|
||||
for (var i = 0; i < MaxResolveDepth; i++)
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
dt = dt.DeclaringType;
|
||||
if (dt == null)
|
||||
{
|
||||
if (dt is 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;
|
||||
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;
|
||||
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;
|
||||
|
||||
if (methodName == ".cctor")
|
||||
{
|
||||
candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
|
||||
candidateConstructors =
|
||||
dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
|
||||
BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
|
||||
foreach (var cctor in candidateConstructors)
|
||||
{
|
||||
method = cctor;
|
||||
@ -381,7 +370,8 @@ namespace Ben.Demystifier
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryResolveSourceMethod(IEnumerable<MethodBase> candidateMethods, GeneratedNameKind kind, string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal)
|
||||
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)
|
||||
@ -390,6 +380,7 @@ namespace Ben.Demystifier
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (kind == GeneratedNameKind.LambdaMethod)
|
||||
{
|
||||
foreach (var v in EnumerableIList.Create(methodBody.LocalVariables))
|
||||
@ -397,8 +388,8 @@ namespace Ben.Demystifier
|
||||
if (v.LocalType == type)
|
||||
{
|
||||
GetOrdinal(method, ref ordinal);
|
||||
|
||||
}
|
||||
|
||||
method = candidateMethod;
|
||||
type = method.DeclaringType;
|
||||
return true;
|
||||
@ -412,12 +403,13 @@ namespace Ben.Demystifier
|
||||
{
|
||||
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 (method == mb || matchHint is not null && method.Name.Contains(matchHint))
|
||||
{
|
||||
if (kind == GeneratedNameKind.LambdaMethod)
|
||||
{
|
||||
@ -460,10 +452,12 @@ namespace Ben.Demystifier
|
||||
|
||||
ordinal = foundOrdinal;
|
||||
|
||||
var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.Static | BindingFlags.Instance |
|
||||
BindingFlags.DeclaredOnly);
|
||||
|
||||
var count = 0;
|
||||
if (methods != null)
|
||||
if (methods is not null)
|
||||
{
|
||||
var startName = method.Name.Substring(0, lamdaStart);
|
||||
foreach (var m in methods)
|
||||
@ -501,6 +495,7 @@ namespace Ben.Demystifier
|
||||
|
||||
return methodName.Substring(start, end - start);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -517,13 +512,8 @@ namespace Ben.Demystifier
|
||||
{
|
||||
openBracketOffset = -1;
|
||||
if (name.StartsWith("CS$<", StringComparison.Ordinal))
|
||||
{
|
||||
openBracketOffset = 3;
|
||||
}
|
||||
else if (name.StartsWith("<", StringComparison.Ordinal))
|
||||
{
|
||||
openBracketOffset = 0;
|
||||
}
|
||||
else if (name.StartsWith("<", StringComparison.Ordinal)) openBracketOffset = 0;
|
||||
|
||||
if (openBracketOffset >= 0)
|
||||
{
|
||||
@ -549,50 +539,32 @@ namespace Ben.Demystifier
|
||||
private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing)
|
||||
{
|
||||
var opening = str[openingOffset];
|
||||
|
||||
var depth = 1;
|
||||
for (var 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)
|
||||
{
|
||||
if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false))
|
||||
{
|
||||
return "params";
|
||||
}
|
||||
|
||||
if (parameter.IsOut)
|
||||
{
|
||||
return "out";
|
||||
}
|
||||
|
||||
if (parameter.IsIn)
|
||||
{
|
||||
return "in";
|
||||
}
|
||||
|
||||
if (parameter.ParameterType.IsByRef)
|
||||
{
|
||||
return "ref";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
@ -605,20 +577,16 @@ namespace Ben.Demystifier
|
||||
{
|
||||
var customAttribs = parameter.GetCustomAttributes(inherit: false);
|
||||
|
||||
var tupleNameAttribute = customAttribs.OfType<Attribute>().FirstOrDefault(a => a.IsTupleElementNameAttribute());
|
||||
var tupleNameAttribute = customAttribs.OfType<Attribute>()
|
||||
.FirstOrDefault(a => a.IsTupleElementNameAttribute());
|
||||
|
||||
var tupleNames = tupleNameAttribute?.GetTransformerNames();
|
||||
|
||||
if (tupleNames?.Count > 0)
|
||||
{
|
||||
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameterType.IsByRef && parameterType.GetElementType() is {} elementType)
|
||||
{
|
||||
parameterType = elementType;
|
||||
}
|
||||
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType) parameterType = elementType;
|
||||
|
||||
return new ResolvedParameter(parameterType)
|
||||
{
|
||||
@ -628,48 +596,16 @@ namespace Ben.Demystifier
|
||||
};
|
||||
}
|
||||
|
||||
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string? name, Type parameterType)
|
||||
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string? name,
|
||||
Type parameterType)
|
||||
{
|
||||
return new ValueTupleResolvedParameter(parameterType, tupleNames)
|
||||
{
|
||||
Prefix = prefix,
|
||||
Name = name,
|
||||
Name = name
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetValueTupleParameterName(IList<string> tupleNames, Type parameterType)
|
||||
{
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append(" ");
|
||||
sb.Append(argName);
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool ShowInStackTrace(MethodBase method)
|
||||
{
|
||||
// Since .NET 5:
|
||||
@ -684,7 +620,7 @@ namespace Ben.Demystifier
|
||||
}
|
||||
|
||||
// Since .NET Core 2:
|
||||
if (StackTraceHiddenAttributeType != null)
|
||||
if (StackTraceHiddenAttributeType is not null)
|
||||
{
|
||||
// Don't show any methods marked with the StackTraceHiddenAttribute
|
||||
// https://github.com/dotnet/coreclr/pull/14652
|
||||
@ -696,13 +632,13 @@ namespace Ben.Demystifier
|
||||
|
||||
var type = method.DeclaringType;
|
||||
|
||||
if (type == null)
|
||||
if (type is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Since .NET Core 2:
|
||||
if (StackTraceHiddenAttributeType != null)
|
||||
if (StackTraceHiddenAttributeType is not null)
|
||||
{
|
||||
// Don't show any methods marked with the StackTraceHiddenAttribute
|
||||
// https://github.com/dotnet/coreclr/pull/14652
|
||||
@ -716,18 +652,24 @@ namespace Ben.Demystifier
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == typeof(ValueTask<>) && method.Name == "get_Result")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult"))
|
||||
|
||||
if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") &&
|
||||
method.Name.EndsWith(".GetResult"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (method.Name == "GetResult" && method.DeclaringType?.FullName == "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1")
|
||||
|
||||
if (method.Name == "GetResult" && method.DeclaringType?.FullName ==
|
||||
"System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == typeof(Task) || type.DeclaringType == typeof(Task))
|
||||
{
|
||||
if (method.Name.Contains(".cctor"))
|
||||
@ -747,12 +689,11 @@ namespace Ben.Demystifier
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == typeof(ExecutionContext))
|
||||
{
|
||||
if (method.Name.Contains(".cctor"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (method.Name)
|
||||
{
|
||||
@ -821,9 +762,7 @@ namespace Ben.Demystifier
|
||||
private static bool IsStackTraceHidden(MemberInfo memberInfo)
|
||||
{
|
||||
if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly)
|
||||
{
|
||||
return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0;
|
||||
}
|
||||
|
||||
EnumerableIList<CustomAttributeData> attributes;
|
||||
try
|
||||
@ -835,8 +774,9 @@ namespace Ben.Demystifier
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var attribute in attributes)
|
||||
for (var i = 0; i < attributes.Count; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
// reflection-only attribute, match on name
|
||||
if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName)
|
||||
{
|
||||
@ -855,6 +795,7 @@ namespace Ben.Demystifier
|
||||
declaringType = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
declaringType = method.DeclaringType;
|
||||
|
||||
var parentType = declaringType.DeclaringType;
|
||||
@ -863,11 +804,9 @@ namespace Ben.Demystifier
|
||||
return false;
|
||||
}
|
||||
|
||||
static MethodInfo[] GetDeclaredMethods(Type type) =>
|
||||
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
|
||||
var methods = GetDeclaredMethods(parentType);
|
||||
if (methods == null)
|
||||
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
|
||||
BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
if (methods is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -888,8 +827,8 @@ namespace Ben.Demystifier
|
||||
{
|
||||
foundAttribute = true;
|
||||
foundIteratorAttribute |= asma is IteratorStateMachineAttribute
|
||||
|| AsyncIteratorStateMachineAttributeType != null
|
||||
&& AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma);
|
||||
|| AsyncIteratorStateMachineAttributeType is not null
|
||||
&& AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma);
|
||||
}
|
||||
}
|
||||
|
||||
@ -903,6 +842,7 @@ namespace Ben.Demystifier
|
||||
return foundIteratorAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -917,7 +857,9 @@ namespace Ben.Demystifier
|
||||
LambdaMethod = 'b',
|
||||
LambdaDisplayClass = 'c',
|
||||
StateMachineType = 'd',
|
||||
LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names
|
||||
|
||||
LocalFunction =
|
||||
'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names
|
||||
|
||||
// Used by EnC:
|
||||
AwaiterField = 'u',
|
||||
|
||||
@ -1,141 +1,89 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
namespace Ben.Demystifier;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
public partial class EnhancedStackTrace : StackTrace, IEnumerable<EnhancedStackFrame>
|
||||
{
|
||||
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
|
||||
/// </summary>
|
||||
public EnhancedStackTrace(Exception e)
|
||||
{
|
||||
public static EnhancedStackTrace Current() => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true));
|
||||
_frames = GetFrames(new StackTrace(e, true));
|
||||
}
|
||||
|
||||
private readonly List<EnhancedStackFrame> _frames;
|
||||
public EnhancedStackTrace(StackTrace stackTrace)
|
||||
{
|
||||
_frames = GetFrames(stackTrace);
|
||||
}
|
||||
|
||||
// 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)
|
||||
/// <returns>The number of frames in the stack trace.</returns>
|
||||
public override int FrameCount => _frames.Count;
|
||||
|
||||
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
|
||||
|
||||
public static EnhancedStackTrace Current()
|
||||
=> new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true));
|
||||
|
||||
/// <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];
|
||||
|
||||
/// <returns>a copy of all stack frames in the current stack trace. </returns>
|
||||
public override StackFrame[] GetFrames() => _frames.ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Builds a readable representation of the stack trace.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
if (_frames.Count == 0) return "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
AppendTo(sb);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void AppendTo(StringBuilder sb)
|
||||
{
|
||||
var count = _frames.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (e == null)
|
||||
if (i > 0) sb.Append(Environment.NewLine);
|
||||
var frame = _frames[i];
|
||||
sb.Append(" at ");
|
||||
frame.MethodInfo.AppendTo(sb);
|
||||
|
||||
var fileName = frame.GetFileName();
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(e));
|
||||
sb.Append(" in ");
|
||||
sb.Append(TryGetFullPath(fileName));
|
||||
}
|
||||
|
||||
_frames = GetFrames(e);
|
||||
}
|
||||
|
||||
|
||||
public EnhancedStackTrace(StackTrace stackTrace)
|
||||
{
|
||||
if (stackTrace == null)
|
||||
var lineNo = frame.GetFileLineNumber();
|
||||
if (lineNo != 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stackTrace));
|
||||
sb.Append(":line ");
|
||||
sb.Append(lineNo);
|
||||
}
|
||||
|
||||
_frames = GetFrames(stackTrace);
|
||||
}
|
||||
|
||||
/// <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 || _frames.Count == 0) 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.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
var frame = frames[i];
|
||||
|
||||
sb.Append(" at ");
|
||||
frame.MethodInfo.Append(sb);
|
||||
|
||||
if (frame.GetFileName() is {} fileName
|
||||
// IsNullOrEmpty alone wasn't enough to disable the null warning
|
||||
&& !string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
sb.Append(" in ");
|
||||
sb.Append(TryGetFullPath(fileName));
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert a given <paramref name="filePath"/> to a full path.
|
||||
/// Returns original value if the conversion isn't possible or a given path is relative.
|
||||
/// </summary>
|
||||
public static string TryGetFullPath(string filePath)
|
||||
{
|
||||
if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile)
|
||||
{
|
||||
return Uri.UnescapeDataString(uri.AbsolutePath);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to convert a given <paramref name="filePath" /> to a full path.
|
||||
/// Returns original value if the conversion isn't possible or a given path is relative.
|
||||
/// </summary>
|
||||
public static string TryGetFullPath(string filePath)
|
||||
{
|
||||
if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile)
|
||||
return Uri.UnescapeDataString(uri.AbsolutePath);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
@ -1,65 +1,77 @@
|
||||
// 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 Ben.Demystifier.Enumerable
|
||||
namespace Ben.Demystifier.Enumerable;
|
||||
|
||||
public static class EnumerableIList
|
||||
{
|
||||
public static class EnumerableIList
|
||||
public static EnumerableIList<T> Create<T>(IList<T> list)
|
||||
{
|
||||
public static EnumerableIList<T> Create<T>(IList<T> list) => new EnumerableIList<T>(list);
|
||||
}
|
||||
|
||||
public 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();
|
||||
return new(list);
|
||||
}
|
||||
}
|
||||
|
||||
public struct EnumerableIList<T> : IEnumerableIList<T>, IList<T>
|
||||
{
|
||||
private readonly IList<T> _list;
|
||||
|
||||
public EnumerableIList(IList<T> list) => _list = list;
|
||||
|
||||
public EnumeratorIList<T> GetEnumerator() => new(_list);
|
||||
|
||||
public static implicit operator EnumerableIList<T>(List<T> list) => new(list);
|
||||
|
||||
public static implicit operator EnumerableIList<T>(T[] array) => new(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()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,35 @@
|
||||
// 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 Ben.Demystifier.Enumerable
|
||||
namespace Ben.Demystifier.Enumerable;
|
||||
|
||||
public struct EnumeratorIList<T> : IEnumerator<T>
|
||||
{
|
||||
public struct EnumeratorIList<T> : IEnumerator<T>
|
||||
private readonly IList<T> _list;
|
||||
private int _index;
|
||||
|
||||
public EnumeratorIList(IList<T> list)
|
||||
{
|
||||
private readonly IList<T> _list;
|
||||
private int _index;
|
||||
_index = -1;
|
||||
_list = list;
|
||||
}
|
||||
|
||||
public EnumeratorIList(IList<T> list)
|
||||
{
|
||||
_index = -1;
|
||||
_list = list;
|
||||
}
|
||||
public T Current => _list[_index];
|
||||
|
||||
public T Current => _list[_index];
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
return _index < (_list?.Count ?? 0);
|
||||
}
|
||||
|
||||
return _index < (_list?.Count ?? 0);
|
||||
}
|
||||
public void Dispose()
|
||||
{ }
|
||||
|
||||
public void Dispose() { }
|
||||
object? IEnumerator.Current => Current;
|
||||
public void Reset() => _index = -1;
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = -1;
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
// 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 Ben.Demystifier.Enumerable
|
||||
namespace Ben.Demystifier.Enumerable;
|
||||
|
||||
internal interface IEnumerableIList<T> : IEnumerable<T>
|
||||
{
|
||||
interface IEnumerableIList<T> : IEnumerable<T>
|
||||
{
|
||||
new EnumeratorIList<T> GetEnumerator();
|
||||
}
|
||||
new EnumeratorIList<T> GetEnumerator();
|
||||
}
|
||||
@ -1,66 +1,66 @@
|
||||
// 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.
|
||||
|
||||
global using System.Diagnostics;
|
||||
global using System;
|
||||
global using System.Text;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Diagnostics;
|
||||
global using System.Reflection;
|
||||
global using System.Linq;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Collections.Generic;
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
namespace Ben.Demystifier;
|
||||
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
public static class ExceptionExtensions
|
||||
private static readonly FieldInfo? StackTraceString =
|
||||
typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
private static void SetStackTracesString(this Exception exception, string value)
|
||||
{
|
||||
private static readonly FieldInfo? stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
StackTraceString?.SetValue(exception, value);
|
||||
}
|
||||
|
||||
private static void SetStackTracesString(this Exception exception, string value)
|
||||
=> stackTraceString?.SetValue(exception, value);
|
||||
|
||||
/// <summary>
|
||||
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
|
||||
/// </summary>
|
||||
public static T Demystify<T>(this T exception) where T : Exception
|
||||
/// <summary>
|
||||
/// Demystifies the given <paramref name="exception" /> and tracks the original stack traces for the whole exception
|
||||
/// tree.
|
||||
/// </summary>
|
||||
public static T Demystify<T>(this T exception) where T : Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var stackTrace = new EnhancedStackTrace(exception);
|
||||
var stackTrace = new EnhancedStackTrace(exception);
|
||||
|
||||
if (stackTrace.FrameCount > 0)
|
||||
{
|
||||
exception.SetStackTracesString(stackTrace.ToString());
|
||||
}
|
||||
if (stackTrace.FrameCount > 0) exception.SetStackTracesString(stackTrace.ToString());
|
||||
|
||||
if (exception is AggregateException aggEx)
|
||||
{
|
||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||
{
|
||||
ex.Demystify();
|
||||
}
|
||||
}
|
||||
if (exception is AggregateException aggEx)
|
||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||
ex.Demystify();
|
||||
|
||||
exception.InnerException?.Demystify();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||
}
|
||||
|
||||
return exception;
|
||||
exception.InnerException?.Demystify();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets demystified string representation of the <paramref name="exception"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
|
||||
/// issues if a system relies on the stack trace be in the specific form.
|
||||
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
|
||||
/// computes a demystified string representation and then restores the original state of the exception back.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static string ToStringDemystified(this Exception exception)
|
||||
=> new StringBuilder().AppendDemystified(exception).ToString();
|
||||
return exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets demystified string representation of the <paramref name="exception" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Demystify{T}" /> method mutates the exception instance that can cause
|
||||
/// issues if a system relies on the stack trace be in the specific form.
|
||||
/// Unlike <see cref="Demystify{T}" /> this method is pure. It calls <see cref="Demystify{T}" /> first,
|
||||
/// computes a demystified string representation and then restores the original state of the exception back.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static string ToStringDemystified(this Exception exception)
|
||||
{
|
||||
return new StringBuilder().AppendDemystified(exception).ToString();
|
||||
}
|
||||
}
|
||||
@ -1,145 +1,145 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Ben.Demystifier.Internal
|
||||
namespace Ben.Demystifier.Internal;
|
||||
|
||||
internal class ILReader
|
||||
{
|
||||
internal class ILReader
|
||||
private static readonly OpCode[] singleByteOpCode;
|
||||
private static readonly OpCode[] doubleByteOpCode;
|
||||
|
||||
private readonly byte[] _cil;
|
||||
private int ptr;
|
||||
|
||||
static ILReader()
|
||||
{
|
||||
private static OpCode[] singleByteOpCode;
|
||||
private static OpCode[] doubleByteOpCode;
|
||||
singleByteOpCode = new OpCode[225];
|
||||
doubleByteOpCode = new OpCode[31];
|
||||
|
||||
private readonly byte[] _cil;
|
||||
private int ptr;
|
||||
var fields = GetOpCodeFields();
|
||||
|
||||
|
||||
public ILReader(byte[] cil) => _cil = cil;
|
||||
|
||||
public OpCode OpCode { get; private set; }
|
||||
public int MetadataToken { get; private set; }
|
||||
public MemberInfo? Operand { get; private set; }
|
||||
|
||||
public bool Read(MethodBase methodInfo)
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
if (ptr < _cil.Length)
|
||||
{
|
||||
OpCode = ReadOpCode();
|
||||
Operand = ReadOperand(OpCode, methodInfo);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var code = (OpCode)fields[i].GetValue(null)!;
|
||||
if (code.OpCodeType == OpCodeType.Nternal)
|
||||
continue;
|
||||
|
||||
OpCode ReadOpCode()
|
||||
{
|
||||
var instruction = ReadByte();
|
||||
if (instruction < 254)
|
||||
return singleByteOpCode[instruction];
|
||||
if (code.Size == 1)
|
||||
singleByteOpCode[code.Value] = code;
|
||||
else
|
||||
return doubleByteOpCode[ReadByte()];
|
||||
doubleByteOpCode[code.Value & 0xff] = code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ILReader(byte[] cil)
|
||||
{
|
||||
_cil = cil;
|
||||
}
|
||||
|
||||
public OpCode OpCode { get; private set; }
|
||||
public int MetadataToken { get; private set; }
|
||||
public MemberInfo? Operand { get; private set; }
|
||||
|
||||
public bool Read(MethodBase methodInfo)
|
||||
{
|
||||
if (ptr < _cil.Length)
|
||||
{
|
||||
OpCode = ReadOpCode();
|
||||
Operand = ReadOperand(OpCode, methodInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo)
|
||||
return false;
|
||||
}
|
||||
|
||||
private OpCode ReadOpCode()
|
||||
{
|
||||
var instruction = ReadByte();
|
||||
if (instruction < 254)
|
||||
return singleByteOpCode[instruction];
|
||||
return doubleByteOpCode[ReadByte()];
|
||||
}
|
||||
|
||||
private MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo)
|
||||
{
|
||||
MetadataToken = 0;
|
||||
int inlineLength;
|
||||
switch (code.OperandType)
|
||||
{
|
||||
MetadataToken = 0;
|
||||
int inlineLength;
|
||||
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();
|
||||
}
|
||||
try
|
||||
{
|
||||
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
|
||||
return null;
|
||||
}
|
||||
|
||||
case OperandType.InlineNone:
|
||||
inlineLength = 0;
|
||||
break;
|
||||
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.ShortInlineVar:
|
||||
case OperandType.ShortInlineI:
|
||||
inlineLength = 1;
|
||||
break;
|
||||
|
||||
case OperandType.InlineVar:
|
||||
inlineLength = 2;
|
||||
break;
|
||||
|
||||
case OperandType.InlineBrTarget:
|
||||
case OperandType.InlineField:
|
||||
case OperandType.InlineI:
|
||||
case OperandType.InlineString:
|
||||
case OperandType.InlineSig:
|
||||
case OperandType.InlineSwitch:
|
||||
case OperandType.InlineTok:
|
||||
case OperandType.InlineType:
|
||||
case OperandType.ShortInlineR:
|
||||
inlineLength = 4;
|
||||
break;
|
||||
|
||||
case OperandType.InlineI8:
|
||||
case OperandType.InlineR:
|
||||
inlineLength = 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
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 is not null) typeArgs = methodInfo.DeclaringType.GetGenericArguments();
|
||||
try
|
||||
{
|
||||
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < inlineLength; i++)
|
||||
{
|
||||
ReadByte();
|
||||
}
|
||||
case OperandType.InlineNone:
|
||||
inlineLength = 0;
|
||||
break;
|
||||
|
||||
return null;
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.ShortInlineVar:
|
||||
case OperandType.ShortInlineI:
|
||||
inlineLength = 1;
|
||||
break;
|
||||
|
||||
case OperandType.InlineVar:
|
||||
inlineLength = 2;
|
||||
break;
|
||||
|
||||
case OperandType.InlineBrTarget:
|
||||
case OperandType.InlineField:
|
||||
case OperandType.InlineI:
|
||||
case OperandType.InlineString:
|
||||
case OperandType.InlineSig:
|
||||
case OperandType.InlineSwitch:
|
||||
case OperandType.InlineTok:
|
||||
case OperandType.InlineType:
|
||||
case OperandType.ShortInlineR:
|
||||
inlineLength = 4;
|
||||
break;
|
||||
|
||||
case OperandType.InlineI8:
|
||||
case OperandType.InlineR:
|
||||
inlineLength = 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
byte ReadByte() => _cil[ptr++];
|
||||
for (var i = 0; i < inlineLength; i++) ReadByte();
|
||||
|
||||
int ReadInt()
|
||||
{
|
||||
var b1 = ReadByte();
|
||||
var b2 = ReadByte();
|
||||
var b3 = ReadByte();
|
||||
var b4 = ReadByte();
|
||||
return b1 | b2 << 8 | b3 << 16 | b4 << 24;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static ILReader()
|
||||
{
|
||||
singleByteOpCode = new OpCode[225];
|
||||
doubleByteOpCode = new OpCode[31];
|
||||
private byte ReadByte()
|
||||
{
|
||||
return _cil[ptr++];
|
||||
}
|
||||
|
||||
var fields = GetOpCodeFields();
|
||||
private int ReadInt()
|
||||
{
|
||||
var b1 = ReadByte();
|
||||
var b2 = ReadByte();
|
||||
var b3 = ReadByte();
|
||||
var b4 = ReadByte();
|
||||
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
||||
}
|
||||
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var 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() => typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
private static FieldInfo[] GetOpCodeFields()
|
||||
{
|
||||
return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
}
|
||||
}
|
||||
@ -1,136 +1,113 @@
|
||||
// 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 Ben.Demystifier.Internal
|
||||
namespace Ben.Demystifier.Internal;
|
||||
|
||||
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs
|
||||
internal class PortablePdbReader : IDisposable
|
||||
{
|
||||
// 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(StringComparer.Ordinal);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
private readonly Dictionary<string, MetadataReaderProvider> _cache =
|
||||
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
|
||||
foreach (var entry in _cache) entry.Value.Dispose();
|
||||
|
||||
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, out int row, out int column)
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
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 is null) return;
|
||||
|
||||
var methodToken = MetadataTokens.Handle(method.MetadataToken);
|
||||
|
||||
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
|
||||
|
||||
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
|
||||
|
||||
if (!handle.IsNil)
|
||||
{
|
||||
fileName = "";
|
||||
row = 0;
|
||||
column = 0;
|
||||
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
|
||||
var sequencePoints = methodDebugInfo.GetSequencePoints();
|
||||
SequencePoint? bestPointSoFar = null;
|
||||
|
||||
if (method.Module.Assembly.IsDynamic)
|
||||
foreach (var point in sequencePoints)
|
||||
{
|
||||
return;
|
||||
if (point.Offset > IlOffset) break;
|
||||
|
||||
if (point.StartLine != SequencePoint.HiddenLine) bestPointSoFar = point;
|
||||
}
|
||||
|
||||
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
|
||||
|
||||
if (metadataReader == null)
|
||||
if (bestPointSoFar.HasValue)
|
||||
{
|
||||
return;
|
||||
row = bestPointSoFar.Value.StartLine;
|
||||
column = bestPointSoFar.Value.StartColumn;
|
||||
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
|
||||
{
|
||||
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 peDirectory is null
|
||||
? null
|
||||
: 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();
|
||||
}
|
||||
}
|
||||
|
||||
private MetadataReader? GetMetadataReader(string assemblyPath)
|
||||
{
|
||||
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
|
||||
{
|
||||
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 peDirectory is null
|
||||
? null
|
||||
: 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';
|
||||
}
|
||||
}
|
||||
@ -1,62 +1,60 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ben.Demystifier.Internal
|
||||
namespace Ben.Demystifier.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class that contains utilities methods for dealing with reflection.
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
private static PropertyInfo? transformerNamesLazyPropertyInfo;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class that contains utilities methods for dealing with reflection.
|
||||
/// Returns true if the <paramref name="type" /> is a value tuple type.
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
public static bool IsValueTuple(this Type type)
|
||||
{
|
||||
private static PropertyInfo? transformerNamesLazyPropertyInfo;
|
||||
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the <paramref name="type"/> is a value tuple type.
|
||||
/// </summary>
|
||||
public static bool IsValueTuple(this Type type)
|
||||
{
|
||||
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns true if the given <paramref name="attribute" /> is of type <code>TupleElementNameAttribute</code>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically
|
||||
/// that
|
||||
/// the given <paramref name="attribute" /> is <code>TupleElementNameAttribute</code>.
|
||||
/// </remarks>
|
||||
public static bool IsTupleElementNameAttribute(this Attribute attribute)
|
||||
{
|
||||
var attributeType = attribute.GetType();
|
||||
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
|
||||
attributeType.Name == "TupleElementNamesAttribute";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given <paramref name="attribute"/> is of type <code>TupleElementNameAttribute</code>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically that
|
||||
/// the given <paramref name="attribute"/> is <code>TupleElementNameAttribute</code>.
|
||||
/// </remarks>
|
||||
public static bool IsTupleElementNameAttribute(this Attribute attribute)
|
||||
{
|
||||
var attributeType = attribute.GetType();
|
||||
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
|
||||
attributeType.Name == "TupleElementNamesAttribute";
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns 'TransformNames' property value from a given <paramref name="attribute" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection
|
||||
/// instead of casting the attribute to a specific type.
|
||||
/// </remarks>
|
||||
public static IList<string>? GetTransformerNames(this Attribute attribute)
|
||||
{
|
||||
Debug.Assert(attribute.IsTupleElementNameAttribute());
|
||||
|
||||
/// <summary>
|
||||
/// Returns 'TransformNames' property value from a given <paramref name="attribute"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection
|
||||
/// instead of casting the attribute to a specific type.
|
||||
/// </remarks>
|
||||
public static IList<string>? GetTransformerNames(this Attribute attribute)
|
||||
{
|
||||
Debug.Assert(attribute.IsTupleElementNameAttribute());
|
||||
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType());
|
||||
return propertyInfo.GetValue(attribute) as IList<string>;
|
||||
}
|
||||
|
||||
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType());
|
||||
return propertyInfo?.GetValue(attribute) as IList<string>;
|
||||
}
|
||||
|
||||
private static PropertyInfo? GetTransformNamesPropertyInfo(Type attributeType)
|
||||
{
|
||||
private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType)
|
||||
{
|
||||
#pragma warning disable 8634
|
||||
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
|
||||
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
|
||||
#pragma warning restore 8634
|
||||
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!);
|
||||
}
|
||||
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!);
|
||||
}
|
||||
}
|
||||
@ -2,171 +2,142 @@
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
namespace Ben.Demystifier;
|
||||
|
||||
public class ResolvedMethod
|
||||
{
|
||||
public class ResolvedMethod
|
||||
public MethodBase? MethodBase { get; set; }
|
||||
|
||||
public Type? DeclaringType { 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 Type[]? ResolvedGenericArguments { 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 int RecurseCount { get; internal set; }
|
||||
|
||||
internal bool IsSequentialEquivalent(ResolvedMethod obj)
|
||||
{
|
||||
public MethodBase? MethodBase { get; set; }
|
||||
return
|
||||
IsAsync == obj.IsAsync &&
|
||||
DeclaringType == obj.DeclaringType &&
|
||||
Name == obj.Name &&
|
||||
IsLambda == obj.IsLambda &&
|
||||
Ordinal == obj.Ordinal &&
|
||||
GenericArguments == obj.GenericArguments &&
|
||||
SubMethod == obj.SubMethod;
|
||||
}
|
||||
|
||||
public Type? DeclaringType { get; set; }
|
||||
public override string ToString() => AppendTo(new StringBuilder()).ToString();
|
||||
|
||||
public bool IsAsync { get; set; }
|
||||
public StringBuilder AppendTo(StringBuilder builder, bool fullName = true)
|
||||
{
|
||||
if (IsAsync) builder.Append("async ");
|
||||
|
||||
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 Type[]? ResolvedGenericArguments { 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 int RecurseCount { get; internal set; }
|
||||
|
||||
internal bool IsSequentialEquivalent(ResolvedMethod obj)
|
||||
if (ReturnParameter is not null)
|
||||
{
|
||||
return
|
||||
IsAsync == obj.IsAsync &&
|
||||
DeclaringType == obj.DeclaringType &&
|
||||
Name == obj.Name &&
|
||||
IsLambda == obj.IsLambda &&
|
||||
Ordinal == obj.Ordinal &&
|
||||
GenericArguments == obj.GenericArguments &&
|
||||
SubMethod == obj.SubMethod;
|
||||
ReturnParameter.Append(builder);
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
public override string ToString() => Append(new StringBuilder()).ToString();
|
||||
|
||||
public StringBuilder Append(StringBuilder builder)
|
||||
=> Append(builder, true);
|
||||
|
||||
public StringBuilder Append(StringBuilder builder, bool fullName)
|
||||
if (DeclaringType is not null)
|
||||
{
|
||||
if (IsAsync)
|
||||
if (Name == ".ctor")
|
||||
{
|
||||
builder.Append("async ");
|
||||
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
|
||||
builder.Append("new ");
|
||||
|
||||
AppendDeclaringTypeName(builder, fullName);
|
||||
}
|
||||
|
||||
if (ReturnParameter != null)
|
||||
else if (Name == ".cctor")
|
||||
{
|
||||
ReturnParameter.Append(builder);
|
||||
builder.Append(" ");
|
||||
}
|
||||
|
||||
if (DeclaringType != null)
|
||||
{
|
||||
|
||||
if (Name == ".ctor")
|
||||
{
|
||||
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
|
||||
builder.Append("new ");
|
||||
|
||||
AppendDeclaringTypeName(builder, fullName);
|
||||
}
|
||||
else if (Name == ".cctor")
|
||||
{
|
||||
builder.Append("static ");
|
||||
AppendDeclaringTypeName(builder, fullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendDeclaringTypeName(builder, fullName)
|
||||
.Append(".")
|
||||
.Append(Name);
|
||||
}
|
||||
builder.Append("static ");
|
||||
AppendDeclaringTypeName(builder, fullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(Name);
|
||||
AppendDeclaringTypeName(builder, fullName)
|
||||
.Append('.')
|
||||
.Append(Name);
|
||||
}
|
||||
builder.Append(GenericArguments);
|
||||
}
|
||||
else builder.Append(Name);
|
||||
|
||||
builder.Append("(");
|
||||
if (MethodBase != null)
|
||||
builder.Append(GenericArguments);
|
||||
|
||||
builder.Append('(');
|
||||
if (MethodBase is not 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 is not null)
|
||||
{
|
||||
var isFirst = true;
|
||||
foreach(var param in Parameters)
|
||||
foreach (var param in SubMethodParameters)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
else builder.Append(", ");
|
||||
param.Append(builder);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("?");
|
||||
}
|
||||
builder.Append(")");
|
||||
else builder.Append('?');
|
||||
|
||||
if (!string.IsNullOrEmpty(SubMethod) || IsLambda)
|
||||
builder.Append(')');
|
||||
if (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(" => { }");
|
||||
builder.Append(" => { }");
|
||||
|
||||
if (Ordinal.HasValue)
|
||||
{
|
||||
builder.Append(" [");
|
||||
builder.Append(Ordinal);
|
||||
builder.Append("]");
|
||||
}
|
||||
if (Ordinal.HasValue)
|
||||
{
|
||||
builder.Append(" [");
|
||||
builder.Append(Ordinal);
|
||||
builder.Append(']');
|
||||
}
|
||||
}
|
||||
|
||||
if (RecurseCount > 0)
|
||||
{
|
||||
builder.Append($" x {RecurseCount + 1:0}");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true)
|
||||
{
|
||||
return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: fullName, includeGenericParameterNames: true) : builder;
|
||||
}
|
||||
if (RecurseCount > 0) builder.Append($" x {RecurseCount + 1:0}");
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true)
|
||||
{
|
||||
return DeclaringType is not null ? builder.AppendTypeDisplayName(DeclaringType, fullName, true) : builder;
|
||||
}
|
||||
}
|
||||
@ -1,59 +1,49 @@
|
||||
// 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 Ben.Demystifier;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
public class ResolvedParameter
|
||||
{
|
||||
public class ResolvedParameter
|
||||
public ResolvedParameter(Type resolvedType)
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
ResolvedType = resolvedType;
|
||||
}
|
||||
|
||||
public Type ResolvedType { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Prefix { get; set; }
|
||||
public bool IsDynamicType { get; set; }
|
||||
public Type ResolvedType { get; set; }
|
||||
|
||||
public ResolvedParameter(Type resolvedType) => ResolvedType = resolvedType;
|
||||
public string? Prefix { get; set; }
|
||||
public bool IsDynamicType { get; set; }
|
||||
|
||||
public override string ToString() => Append(new StringBuilder()).ToString();
|
||||
|
||||
public StringBuilder Append(StringBuilder sb)
|
||||
{
|
||||
if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit")
|
||||
return sb;
|
||||
|
||||
if (!string.IsNullOrEmpty(Prefix))
|
||||
{
|
||||
sb.Append(Prefix)
|
||||
.Append(" ");
|
||||
}
|
||||
|
||||
if (IsDynamicType)
|
||||
{
|
||||
sb.Append("dynamic");
|
||||
}
|
||||
else if (ResolvedType != null)
|
||||
{
|
||||
AppendTypeName(sb);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("?");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
sb.Append(" ")
|
||||
.Append(Name);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return Append(new StringBuilder()).ToString();
|
||||
}
|
||||
|
||||
public StringBuilder Append(StringBuilder sb)
|
||||
{
|
||||
if (ResolvedType.Assembly.ManifestModule.Name == "FSharp.Core.dll" && ResolvedType.Name == "Unit")
|
||||
return sb;
|
||||
}
|
||||
|
||||
protected virtual void AppendTypeName(StringBuilder sb)
|
||||
{
|
||||
sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Prefix))
|
||||
sb.Append(Prefix)
|
||||
.Append(' ');
|
||||
|
||||
if (IsDynamicType)
|
||||
sb.Append("dynamic");
|
||||
else AppendTypeName(sb);
|
||||
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
sb.Append(' ')
|
||||
.Append(Name);
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
protected virtual void AppendTypeName(StringBuilder sb)
|
||||
{
|
||||
sb.AppendTypeDisplayName(ResolvedType, false, true);
|
||||
}
|
||||
}
|
||||
@ -2,55 +2,42 @@
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Ben.Demystifier.Enumerable;
|
||||
using System.Text;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
namespace Ben.Demystifier;
|
||||
|
||||
public static class StringBuilderExtentions
|
||||
{
|
||||
public static class StringBuilderExtentions
|
||||
public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception)
|
||||
{
|
||||
public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var stackTrace = new EnhancedStackTrace(exception);
|
||||
var stackTrace = new EnhancedStackTrace(exception);
|
||||
|
||||
builder.Append(exception.GetType());
|
||||
if (!string.IsNullOrEmpty(exception.Message))
|
||||
{
|
||||
builder.Append(": ").Append(exception.Message);
|
||||
}
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(exception.GetType());
|
||||
if (!string.IsNullOrEmpty(exception.Message)) builder.Append(": ").Append(exception.Message);
|
||||
builder.Append(Environment.NewLine);
|
||||
|
||||
if (stackTrace.FrameCount > 0)
|
||||
{
|
||||
stackTrace.Append(builder);
|
||||
}
|
||||
if (stackTrace.FrameCount > 0) stackTrace.AppendTo(builder);
|
||||
|
||||
if (exception is AggregateException aggEx)
|
||||
{
|
||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||
{
|
||||
builder.AppendInnerException(ex);
|
||||
}
|
||||
}
|
||||
if (exception is AggregateException aggEx)
|
||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||
builder.AppendInnerException(ex);
|
||||
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
builder.AppendInnerException(exception.InnerException);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||
}
|
||||
|
||||
return builder;
|
||||
if (exception.InnerException is not null) builder.AppendInnerException(exception.InnerException);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||
}
|
||||
|
||||
private static void AppendInnerException(this StringBuilder builder, Exception exception)
|
||||
=> builder.Append(" ---> ")
|
||||
.AppendDemystified(exception)
|
||||
.AppendLine()
|
||||
.Append(" --- End of inner exception stack trace ---");
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void AppendInnerException(this StringBuilder builder, Exception exception)
|
||||
{
|
||||
builder.Append(" ---> ")
|
||||
.AppendDemystified(exception)
|
||||
.AppendLine()
|
||||
.Append(" --- End of inner exception stack trace ---");
|
||||
}
|
||||
}
|
||||
@ -1,218 +1,192 @@
|
||||
// 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 Ben.Demystifier;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
|
||||
public static class TypeNameHelper
|
||||
{
|
||||
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
|
||||
public static class TypeNameHelper
|
||||
public static readonly Dictionary<Type, string> BuiltInTypeNames = new()
|
||||
{
|
||||
public 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" }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> FSharpTypeNames = new()
|
||||
{
|
||||
{ "Unit", "void" },
|
||||
{ "FSharpOption", "Option" },
|
||||
{ "FSharpAsync", "Async" },
|
||||
{ "FSharpOption`1", "Option" },
|
||||
{ "FSharpAsync`1", "Async" }
|
||||
};
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true,
|
||||
bool includeGenericParameterNames = false)
|
||||
{
|
||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name of given generic type without '`'.
|
||||
/// </summary>
|
||||
public static string GetTypeNameForGenericType(Type type)
|
||||
{
|
||||
if (!type.IsGenericType) throw new ArgumentException("The given type should be generic", nameof(type));
|
||||
|
||||
var genericPartIndex = type.Name.IndexOf('`');
|
||||
|
||||
return genericPartIndex >= 0 ? type.Name.Substring(0, genericPartIndex) : type.Name;
|
||||
}
|
||||
|
||||
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
{ 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" }
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> FSharpTypeNames = new Dictionary<string, string>
|
||||
{
|
||||
{ "Unit", "void" },
|
||||
{ "FSharpOption", "Option" },
|
||||
{ "FSharpAsync", "Async" },
|
||||
{ "FSharpOption`1", "Option" },
|
||||
{ "FSharpAsync`1", "Async" }
|
||||
};
|
||||
|
||||
/// <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();
|
||||
}
|
||||
|
||||
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true, bool includeGenericParameterNames = false)
|
||||
{
|
||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a name of given generic type without '`'.
|
||||
/// </summary>
|
||||
public static string GetTypeNameForGenericType(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType is not null)
|
||||
{
|
||||
throw new ArgumentException("The given type should be generic", nameof(type));
|
||||
}
|
||||
|
||||
var genericPartIndex = type.Name.IndexOf('`');
|
||||
|
||||
return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name;
|
||||
}
|
||||
|
||||
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||
if (underlyingType != null)
|
||||
{
|
||||
ProcessType(builder, underlyingType, options);
|
||||
builder.Append('?');
|
||||
}
|
||||
else
|
||||
{
|
||||
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.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
else if (type.IsGenericParameter)
|
||||
{
|
||||
if (options.IncludeGenericParameterNames)
|
||||
{
|
||||
builder.Append(type.Name);
|
||||
}
|
||||
ProcessType(builder, underlyingType, options);
|
||||
builder.Append('?');
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||
else if (type.IsArray)
|
||||
{
|
||||
var innerType = type;
|
||||
while (innerType.IsArray)
|
||||
{
|
||||
if (innerType.GetElementType() is { } inner)
|
||||
{
|
||||
innerType = inner;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessType(builder, innerType, options);
|
||||
|
||||
while (type.IsArray)
|
||||
{
|
||||
builder.Append('[');
|
||||
builder.Append(',', type.GetArrayRank() - 1);
|
||||
builder.Append(']');
|
||||
if (type.GetElementType() is not { } elementType)
|
||||
{
|
||||
break;
|
||||
}
|
||||
type = elementType;
|
||||
}
|
||||
ProcessArrayType(builder, type, options);
|
||||
}
|
||||
|
||||
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options)
|
||||
else if (BuiltInTypeNames.TryGetValue(type, out var builtInName))
|
||||
{
|
||||
var offset = 0;
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
offset = type.DeclaringType.GetGenericArguments().Length;
|
||||
}
|
||||
|
||||
if (options.FullName)
|
||||
{
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out var builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
else
|
||||
{
|
||||
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('>');
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
|
||||
private struct DisplayNameOptions
|
||||
else if (type.Namespace == nameof(System))
|
||||
{
|
||||
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
|
||||
{
|
||||
FullName = fullName;
|
||||
IncludeGenericParameterNames = includeGenericParameterNames;
|
||||
}
|
||||
|
||||
public bool FullName { get; }
|
||||
|
||||
public bool IncludeGenericParameterNames { get; }
|
||||
builder.Append(type.Name);
|
||||
}
|
||||
else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out builtInName))
|
||||
{
|
||||
builder.Append(builtInName);
|
||||
}
|
||||
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)
|
||||
if (innerType.GetElementType() is { } inner)
|
||||
innerType = inner;
|
||||
|
||||
ProcessType(builder, innerType, options);
|
||||
|
||||
while (type.IsArray)
|
||||
{
|
||||
builder.Append('[');
|
||||
builder.Append(',', type.GetArrayRank() - 1);
|
||||
builder.Append(']');
|
||||
if (type.GetElementType() is not { } elementType) break;
|
||||
type = elementType;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length,
|
||||
DisplayNameOptions options)
|
||||
{
|
||||
var offset = 0;
|
||||
if (type.IsNested && type.DeclaringType is not null) offset = type.DeclaringType.GetGenericArguments().Length;
|
||||
|
||||
if (options.FullName)
|
||||
{
|
||||
if (type.IsNested && type.DeclaringType is not null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||
&& FSharpTypeNames.TryGetValue(type.Name, out var builtInName))
|
||||
builder.Append(builtInName);
|
||||
else
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -1,68 +1,58 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Ben.Demystifier.Internal;
|
||||
using System.Text;
|
||||
|
||||
namespace Ben.Demystifier
|
||||
namespace Ben.Demystifier;
|
||||
|
||||
public class ValueTupleResolvedParameter : ResolvedParameter
|
||||
{
|
||||
public class ValueTupleResolvedParameter : ResolvedParameter
|
||||
public ValueTupleResolvedParameter(Type resolvedType, IList<string> tupleNames)
|
||||
: base(resolvedType)
|
||||
{
|
||||
public IList<string> TupleNames { get; }
|
||||
TupleNames = tupleNames;
|
||||
}
|
||||
|
||||
public ValueTupleResolvedParameter(Type resolvedType, IList<string> tupleNames)
|
||||
: base(resolvedType)
|
||||
=> TupleNames = tupleNames;
|
||||
public IList<string> TupleNames { get; }
|
||||
|
||||
protected override void AppendTypeName(StringBuilder sb)
|
||||
protected override void AppendTypeName(StringBuilder sb)
|
||||
{
|
||||
if (ResolvedType is not null)
|
||||
{
|
||||
if (ResolvedType is not null)
|
||||
if (ResolvedType.IsValueTuple())
|
||||
{
|
||||
if (ResolvedType.IsValueTuple())
|
||||
{
|
||||
AppendValueTupleParameterName(sb, ResolvedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to unwrap the first generic argument first.
|
||||
sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
|
||||
sb.Append("<");
|
||||
AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]);
|
||||
sb.Append(">");
|
||||
}
|
||||
AppendValueTupleParameterName(sb, ResolvedType);
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
|
||||
{
|
||||
sb.Append("(");
|
||||
var args = parameterType.GetGenericArguments();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
else
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true);
|
||||
|
||||
if (i >= TupleNames.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var argName = TupleNames[i];
|
||||
if (argName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append(" ");
|
||||
sb.Append(argName);
|
||||
// Need to unwrap the first generic argument first.
|
||||
sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
|
||||
sb.Append("<");
|
||||
AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]);
|
||||
sb.Append(">");
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
|
||||
{
|
||||
sb.Append('(');
|
||||
var args = parameterType.GetGenericArguments();
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0) sb.Append(", ");
|
||||
|
||||
sb.AppendTypeDisplayName(args[i], false, true);
|
||||
|
||||
if (i >= TupleNames.Count) continue;
|
||||
|
||||
var argName = TupleNames[i];
|
||||
if (argName is null) continue;
|
||||
|
||||
sb.Append(' ');
|
||||
sb.Append(argName);
|
||||
}
|
||||
|
||||
sb.Append(')');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user