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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net6.0</TargetFrameworks>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Reflection.Metadata" Version="5.0.0" Condition="'$(TargetFramework)' != 'net6.0'"/>
|
<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'" />
|
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" Condition="'$(TargetFramework)' != 'netstandard2.1'"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,93 +1,84 @@
|
|||||||
// Copyright (c) Ben A Adams. All rights reserved.
|
// Copyright (c) Ben A Adams. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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;
|
StackFrameBase = stackFrameBase;
|
||||||
private readonly int _lineNumber;
|
MethodInfo = methodInfo;
|
||||||
private readonly int _colNumber;
|
_fileName = fileName;
|
||||||
|
_lineNumber = lineNumber;
|
||||||
public StackFrame StackFrame { get; }
|
_colNumber = colNumber;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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.
|
// 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.
|
// 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.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ben.Demystifier.Enumerable;
|
||||||
|
using Ben.Demystifier.Internal;
|
||||||
|
|
||||||
namespace Ben.Demystifier
|
namespace Ben.Demystifier
|
||||||
{
|
{
|
||||||
public partial class EnhancedStackTrace
|
public partial class EnhancedStackTrace
|
||||||
{
|
{
|
||||||
private static readonly Type? StackTraceHiddenAttributeType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false);
|
private static readonly Type? StackTraceHiddenAttributeType =
|
||||||
private static readonly Type? AsyncIteratorStateMachineAttributeType = Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false);
|
||||||
|
|
||||||
|
private static readonly Type? AsyncIteratorStateMachineAttributeType =
|
||||||
|
Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
||||||
|
|
||||||
static EnhancedStackTrace()
|
static EnhancedStackTrace()
|
||||||
{
|
{
|
||||||
if (AsyncIteratorStateMachineAttributeType != null) return;
|
if (AsyncIteratorStateMachineAttributeType is not null) return;
|
||||||
|
|
||||||
Assembly mba;
|
Assembly mba;
|
||||||
try
|
try
|
||||||
@ -36,32 +35,15 @@ namespace Ben.Demystifier
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncIteratorStateMachineAttributeType = mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace)
|
public static List<EnhancedStackFrame> GetFrames(StackTrace stackTrace)
|
||||||
{
|
{
|
||||||
var frames = new List<EnhancedStackFrame>();
|
var enhancedFrames = new List<EnhancedStackFrame>();
|
||||||
var stackFrames = stackTrace.GetFrames();
|
var stackFrames = stackTrace.GetFrames();
|
||||||
|
|
||||||
if (stackFrames == null)
|
|
||||||
{
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnhancedStackFrame? lastFrame = null;
|
EnhancedStackFrame? lastFrame = null;
|
||||||
PortablePdbReader? portablePdbReader = null;
|
PortablePdbReader? portablePdbReader = null;
|
||||||
try
|
try
|
||||||
@ -69,27 +51,22 @@ namespace Ben.Demystifier
|
|||||||
for (var i = 0; i < stackFrames.Length; i++)
|
for (var i = 0; i < stackFrames.Length; i++)
|
||||||
{
|
{
|
||||||
var frame = stackFrames[i];
|
var frame = stackFrames[i];
|
||||||
if (frame is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var method = frame.GetMethod();
|
var method = frame.GetMethod();
|
||||||
|
|
||||||
// Always show last stackFrame
|
// 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;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
var fileName = frame.GetFileName();
|
var fileName = frame.GetFileName();
|
||||||
var row = frame.GetFileLineNumber();
|
var row = frame.GetFileLineNumber();
|
||||||
var column = frame.GetFileColumnNumber();
|
var column = frame.GetFileColumnNumber();
|
||||||
var ilOffset = frame.GetILOffset();
|
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
|
// .NET Framework and older versions of mono don't support portable PDBs
|
||||||
// so we read it manually to get file name and line information
|
// 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)
|
if (method is null)
|
||||||
@ -106,7 +83,7 @@ namespace Ben.Demystifier
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column);
|
var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column);
|
||||||
frames.Add(stackFrame);
|
enhancedFrames.Add(stackFrame);
|
||||||
lastFrame = stackFrame;
|
lastFrame = stackFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +93,7 @@ namespace Ben.Demystifier
|
|||||||
portablePdbReader?.Dispose();
|
portablePdbReader?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames;
|
return enhancedFrames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
|
public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod)
|
||||||
@ -161,7 +138,8 @@ namespace Ben.Demystifier
|
|||||||
methodDisplayInfo.Name = methodName;
|
methodDisplayInfo.Name = methodName;
|
||||||
if (method.Name.IndexOf("<") >= 0)
|
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;
|
methodName = method.Name;
|
||||||
methodDisplayInfo.MethodBase = method;
|
methodDisplayInfo.MethodBase = method;
|
||||||
@ -175,7 +153,7 @@ namespace Ben.Demystifier
|
|||||||
|
|
||||||
methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod);
|
methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod);
|
||||||
|
|
||||||
if (methodDisplayInfo.IsLambda && type != null)
|
if (methodDisplayInfo.IsLambda && type is not null)
|
||||||
{
|
{
|
||||||
if (methodName == ".cctor")
|
if (methodName == ".cctor")
|
||||||
{
|
{
|
||||||
@ -185,7 +163,8 @@ namespace Ben.Demystifier
|
|||||||
}
|
}
|
||||||
else
|
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)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
var value = field.GetValue(field);
|
var value = field.GetValue(field);
|
||||||
@ -212,33 +191,29 @@ namespace Ben.Demystifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolveStateMachineMethod may have set declaringType to null
|
// ResolveStateMachineMethod may have set declaringType to null
|
||||||
if (type != null)
|
if (type is not null)
|
||||||
{
|
{
|
||||||
methodDisplayInfo.DeclaringType = type;
|
methodDisplayInfo.DeclaringType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method is MethodInfo mi)
|
if (method is MethodInfo mi)
|
||||||
{
|
{
|
||||||
var returnParameter = mi.ReturnParameter;
|
if (mi.ReturnParameter is not null)
|
||||||
if (returnParameter != null)
|
|
||||||
{
|
|
||||||
methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter);
|
methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter);
|
||||||
}
|
else if (mi.ReturnType is not null)
|
||||||
else if (mi.ReturnType != null)
|
|
||||||
{
|
|
||||||
methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType)
|
methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType)
|
||||||
{
|
{
|
||||||
Prefix = "",
|
Prefix = "",
|
||||||
Name = "",
|
Name = ""
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method.IsGenericMethod)
|
if (method.IsGenericMethod)
|
||||||
{
|
{
|
||||||
var genericArguments = method.GetGenericArguments();
|
var genericArguments = method.GetGenericArguments();
|
||||||
var genericArgumentsString = string.Join(", ", genericArguments
|
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.GenericArguments += "<" + genericArgumentsString + ">";
|
||||||
methodDisplayInfo.ResolvedGenericArguments = genericArguments;
|
methodDisplayInfo.ResolvedGenericArguments = genericArguments;
|
||||||
}
|
}
|
||||||
@ -260,7 +235,7 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
methodDisplayInfo.SubMethodBase = null;
|
methodDisplayInfo.SubMethodBase = null;
|
||||||
}
|
}
|
||||||
else if (methodDisplayInfo.SubMethodBase != null)
|
else if (methodDisplayInfo.SubMethodBase is not null)
|
||||||
{
|
{
|
||||||
parameters = methodDisplayInfo.SubMethodBase.GetParameters();
|
parameters = methodDisplayInfo.SubMethodBase.GetParameters();
|
||||||
if (parameters.Length > 0)
|
if (parameters.Length > 0)
|
||||||
@ -295,7 +270,8 @@ namespace Ben.Demystifier
|
|||||||
return false;
|
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;
|
kind = GeneratedNameKind.None;
|
||||||
type = method.DeclaringType;
|
type = method.DeclaringType;
|
||||||
@ -315,28 +291,29 @@ namespace Ben.Demystifier
|
|||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case GeneratedNameKind.LocalFunction:
|
case GeneratedNameKind.LocalFunction:
|
||||||
{
|
{
|
||||||
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
|
var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1);
|
||||||
if (localNameStart < 0) break;
|
if (localNameStart < 0) break;
|
||||||
localNameStart += 3;
|
localNameStart += 3;
|
||||||
|
|
||||||
if (localNameStart < generatedName.Length)
|
if (localNameStart < generatedName.Length)
|
||||||
|
{
|
||||||
|
var localNameEnd = generatedName.IndexOf("|", localNameStart);
|
||||||
|
if (localNameEnd > 0)
|
||||||
{
|
{
|
||||||
var localNameEnd = generatedName.IndexOf("|", localNameStart);
|
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
|
||||||
if (localNameEnd > 0)
|
|
||||||
{
|
|
||||||
subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case GeneratedNameKind.LambdaMethod:
|
case GeneratedNameKind.LambdaMethod:
|
||||||
subMethodName = "";
|
subMethodName = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dt = method.DeclaringType;
|
var dt = method.DeclaringType;
|
||||||
if (dt == null)
|
if (dt is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -345,30 +322,42 @@ namespace Ben.Demystifier
|
|||||||
|
|
||||||
var matchName = methodName;
|
var matchName = methodName;
|
||||||
|
|
||||||
var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
|
var candidateMethods =
|
||||||
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true;
|
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);
|
var candidateConstructors =
|
||||||
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true;
|
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 < 10; i++)
|
||||||
for (var i = 0; i < MaxResolveDepth; i++)
|
|
||||||
{
|
{
|
||||||
dt = dt.DeclaringType;
|
dt = dt.DeclaringType;
|
||||||
if (dt == null)
|
if (dt is null)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName);
|
candidateMethods =
|
||||||
if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true;
|
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);
|
candidateConstructors =
|
||||||
if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true;
|
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")
|
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)
|
foreach (var cctor in candidateConstructors)
|
||||||
{
|
{
|
||||||
method = cctor;
|
method = cctor;
|
||||||
@ -381,7 +370,8 @@ namespace Ben.Demystifier
|
|||||||
return false;
|
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;
|
ordinal = null;
|
||||||
foreach (var candidateMethod in candidateMethods)
|
foreach (var candidateMethod in candidateMethods)
|
||||||
@ -390,6 +380,7 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind == GeneratedNameKind.LambdaMethod)
|
if (kind == GeneratedNameKind.LambdaMethod)
|
||||||
{
|
{
|
||||||
foreach (var v in EnumerableIList.Create(methodBody.LocalVariables))
|
foreach (var v in EnumerableIList.Create(methodBody.LocalVariables))
|
||||||
@ -397,8 +388,8 @@ namespace Ben.Demystifier
|
|||||||
if (v.LocalType == type)
|
if (v.LocalType == type)
|
||||||
{
|
{
|
||||||
GetOrdinal(method, ref ordinal);
|
GetOrdinal(method, ref ordinal);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
method = candidateMethod;
|
method = candidateMethod;
|
||||||
type = method.DeclaringType;
|
type = method.DeclaringType;
|
||||||
return true;
|
return true;
|
||||||
@ -412,12 +403,13 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader = new ILReader(rawIl);
|
var reader = new ILReader(rawIl);
|
||||||
while (reader.Read(candidateMethod))
|
while (reader.Read(candidateMethod))
|
||||||
{
|
{
|
||||||
if (reader.Operand is MethodBase mb)
|
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)
|
if (kind == GeneratedNameKind.LambdaMethod)
|
||||||
{
|
{
|
||||||
@ -460,10 +452,12 @@ namespace Ben.Demystifier
|
|||||||
|
|
||||||
ordinal = foundOrdinal;
|
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;
|
var count = 0;
|
||||||
if (methods != null)
|
if (methods is not null)
|
||||||
{
|
{
|
||||||
var startName = method.Name.Substring(0, lamdaStart);
|
var startName = method.Name.Substring(0, lamdaStart);
|
||||||
foreach (var m in methods)
|
foreach (var m in methods)
|
||||||
@ -501,6 +495,7 @@ namespace Ben.Demystifier
|
|||||||
|
|
||||||
return methodName.Substring(start, end - start);
|
return methodName.Substring(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,13 +512,8 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
openBracketOffset = -1;
|
openBracketOffset = -1;
|
||||||
if (name.StartsWith("CS$<", StringComparison.Ordinal))
|
if (name.StartsWith("CS$<", StringComparison.Ordinal))
|
||||||
{
|
|
||||||
openBracketOffset = 3;
|
openBracketOffset = 3;
|
||||||
}
|
else if (name.StartsWith("<", StringComparison.Ordinal)) openBracketOffset = 0;
|
||||||
else if (name.StartsWith("<", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
openBracketOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openBracketOffset >= 0)
|
if (openBracketOffset >= 0)
|
||||||
{
|
{
|
||||||
@ -549,50 +539,32 @@ namespace Ben.Demystifier
|
|||||||
private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing)
|
private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing)
|
||||||
{
|
{
|
||||||
var opening = str[openingOffset];
|
var opening = str[openingOffset];
|
||||||
|
|
||||||
var depth = 1;
|
var depth = 1;
|
||||||
for (var i = openingOffset + 1; i < str.Length; i++)
|
for (var i = openingOffset + 1; i < str.Length; i++)
|
||||||
{
|
{
|
||||||
var c = str[i];
|
var c = str[i];
|
||||||
if (c == opening)
|
if (c == opening)
|
||||||
{
|
|
||||||
depth++;
|
depth++;
|
||||||
}
|
|
||||||
else if (c == closing)
|
else if (c == closing)
|
||||||
{
|
{
|
||||||
depth--;
|
depth--;
|
||||||
if (depth == 0)
|
if (depth == 0)
|
||||||
{
|
|
||||||
return i;
|
return i;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetPrefix(ParameterInfo parameter)
|
private static string GetPrefix(ParameterInfo parameter)
|
||||||
{
|
{
|
||||||
if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false))
|
if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false))
|
||||||
{
|
|
||||||
return "params";
|
return "params";
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.IsOut)
|
if (parameter.IsOut)
|
||||||
{
|
|
||||||
return "out";
|
return "out";
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.IsIn)
|
if (parameter.IsIn)
|
||||||
{
|
|
||||||
return "in";
|
return "in";
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.ParameterType.IsByRef)
|
if (parameter.ParameterType.IsByRef)
|
||||||
{
|
|
||||||
return "ref";
|
return "ref";
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,20 +577,16 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
var customAttribs = parameter.GetCustomAttributes(inherit: false);
|
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();
|
var tupleNames = tupleNameAttribute?.GetTransformerNames();
|
||||||
|
|
||||||
if (tupleNames?.Count > 0)
|
if (tupleNames?.Count > 0)
|
||||||
{
|
|
||||||
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType);
|
return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameterType.IsByRef && parameterType.GetElementType() is {} elementType)
|
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType) parameterType = elementType;
|
||||||
{
|
|
||||||
parameterType = elementType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ResolvedParameter(parameterType)
|
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)
|
return new ValueTupleResolvedParameter(parameterType, tupleNames)
|
||||||
{
|
{
|
||||||
Prefix = prefix,
|
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)
|
private static bool ShowInStackTrace(MethodBase method)
|
||||||
{
|
{
|
||||||
// Since .NET 5:
|
// Since .NET 5:
|
||||||
@ -684,7 +620,7 @@ namespace Ben.Demystifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Since .NET Core 2:
|
// Since .NET Core 2:
|
||||||
if (StackTraceHiddenAttributeType != null)
|
if (StackTraceHiddenAttributeType is not null)
|
||||||
{
|
{
|
||||||
// Don't show any methods marked with the StackTraceHiddenAttribute
|
// Don't show any methods marked with the StackTraceHiddenAttribute
|
||||||
// https://github.com/dotnet/coreclr/pull/14652
|
// https://github.com/dotnet/coreclr/pull/14652
|
||||||
@ -696,13 +632,13 @@ namespace Ben.Demystifier
|
|||||||
|
|
||||||
var type = method.DeclaringType;
|
var type = method.DeclaringType;
|
||||||
|
|
||||||
if (type == null)
|
if (type is null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since .NET Core 2:
|
// Since .NET Core 2:
|
||||||
if (StackTraceHiddenAttributeType != null)
|
if (StackTraceHiddenAttributeType is not null)
|
||||||
{
|
{
|
||||||
// Don't show any methods marked with the StackTraceHiddenAttribute
|
// Don't show any methods marked with the StackTraceHiddenAttribute
|
||||||
// https://github.com/dotnet/coreclr/pull/14652
|
// https://github.com/dotnet/coreclr/pull/14652
|
||||||
@ -716,18 +652,24 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == typeof(ValueTask<>) && method.Name == "get_Result")
|
if (type == typeof(ValueTask<>) && method.Name == "get_Result")
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == typeof(Task) || type.DeclaringType == typeof(Task))
|
if (type == typeof(Task) || type.DeclaringType == typeof(Task))
|
||||||
{
|
{
|
||||||
if (method.Name.Contains(".cctor"))
|
if (method.Name.Contains(".cctor"))
|
||||||
@ -747,12 +689,11 @@ namespace Ben.Demystifier
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == typeof(ExecutionContext))
|
if (type == typeof(ExecutionContext))
|
||||||
{
|
{
|
||||||
if (method.Name.Contains(".cctor"))
|
if (method.Name.Contains(".cctor"))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
switch (method.Name)
|
switch (method.Name)
|
||||||
{
|
{
|
||||||
@ -821,9 +762,7 @@ namespace Ben.Demystifier
|
|||||||
private static bool IsStackTraceHidden(MemberInfo memberInfo)
|
private static bool IsStackTraceHidden(MemberInfo memberInfo)
|
||||||
{
|
{
|
||||||
if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly)
|
if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly)
|
||||||
{
|
|
||||||
return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0;
|
return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0;
|
||||||
}
|
|
||||||
|
|
||||||
EnumerableIList<CustomAttributeData> attributes;
|
EnumerableIList<CustomAttributeData> attributes;
|
||||||
try
|
try
|
||||||
@ -835,8 +774,9 @@ namespace Ben.Demystifier
|
|||||||
return false;
|
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
|
// reflection-only attribute, match on name
|
||||||
if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName)
|
if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName)
|
||||||
{
|
{
|
||||||
@ -855,6 +795,7 @@ namespace Ben.Demystifier
|
|||||||
declaringType = null!;
|
declaringType = null!;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
declaringType = method.DeclaringType;
|
declaringType = method.DeclaringType;
|
||||||
|
|
||||||
var parentType = declaringType.DeclaringType;
|
var parentType = declaringType.DeclaringType;
|
||||||
@ -863,11 +804,9 @@ namespace Ben.Demystifier
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static MethodInfo[] GetDeclaredMethods(Type type) =>
|
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static |
|
||||||
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||||
|
if (methods is null)
|
||||||
var methods = GetDeclaredMethods(parentType);
|
|
||||||
if (methods == null)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -888,8 +827,8 @@ namespace Ben.Demystifier
|
|||||||
{
|
{
|
||||||
foundAttribute = true;
|
foundAttribute = true;
|
||||||
foundIteratorAttribute |= asma is IteratorStateMachineAttribute
|
foundIteratorAttribute |= asma is IteratorStateMachineAttribute
|
||||||
|| AsyncIteratorStateMachineAttributeType != null
|
|| AsyncIteratorStateMachineAttributeType is not null
|
||||||
&& AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma);
|
&& AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -903,6 +842,7 @@ namespace Ben.Demystifier
|
|||||||
return foundIteratorAttribute;
|
return foundIteratorAttribute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,7 +857,9 @@ namespace Ben.Demystifier
|
|||||||
LambdaMethod = 'b',
|
LambdaMethod = 'b',
|
||||||
LambdaDisplayClass = 'c',
|
LambdaDisplayClass = 'c',
|
||||||
StateMachineType = 'd',
|
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:
|
// Used by EnC:
|
||||||
AwaiterField = 'u',
|
AwaiterField = 'u',
|
||||||
@ -942,4 +884,4 @@ namespace Ben.Demystifier
|
|||||||
DynamicCallSiteField = 'p'
|
DynamicCallSiteField = 'p'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,141 +1,89 @@
|
|||||||
// Copyright (c) Ben A Adams. All rights reserved.
|
// Copyright (c) Ben A Adams. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections;
|
namespace Ben.Demystifier;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Ben.Demystifier.Enumerable;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
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:
|
/// <returns>The number of frames in the stack trace.</returns>
|
||||||
// Initializes a new instance of the System.Diagnostics.StackTrace class using the
|
public override int FrameCount => _frames.Count;
|
||||||
// provided exception object.
|
|
||||||
//
|
IEnumerator<EnhancedStackFrame> IEnumerable<EnhancedStackFrame>.GetEnumerator() => _frames.GetEnumerator();
|
||||||
// Parameters:
|
|
||||||
// e:
|
IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator();
|
||||||
// The exception object from which to construct the stack trace.
|
|
||||||
//
|
public static EnhancedStackTrace Current()
|
||||||
// Exceptions:
|
=> new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true));
|
||||||
// T:System.ArgumentNullException:
|
|
||||||
// The parameter e is null.
|
/// <param name="index">The index of the stack frame requested.</param>
|
||||||
public EnhancedStackTrace(Exception e)
|
/// <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);
|
var lineNo = frame.GetFileLineNumber();
|
||||||
}
|
if (lineNo != 0)
|
||||||
|
|
||||||
|
|
||||||
public EnhancedStackTrace(StackTrace stackTrace)
|
|
||||||
{
|
|
||||||
if (stackTrace == null)
|
|
||||||
{
|
{
|
||||||
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.
|
// 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.
|
// 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);
|
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 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// 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.
|
// 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;
|
_index = -1;
|
||||||
private int _index;
|
_list = list;
|
||||||
|
|
||||||
public EnumeratorIList(IList<T> list)
|
|
||||||
{
|
|
||||||
_index = -1;
|
|
||||||
_list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T Current => _list[_index];
|
|
||||||
|
|
||||||
public bool MoveNext()
|
|
||||||
{
|
|
||||||
_index++;
|
|
||||||
|
|
||||||
return _index < (_list?.Count ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
object? IEnumerator.Current => Current;
|
|
||||||
public void Reset() => _index = -1;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public T Current => _list[_index];
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_index++;
|
||||||
|
|
||||||
|
return _index < (_list?.Count ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
object? IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_index = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
// Copyright (c) Ben A Adams. All rights reserved.
|
// Copyright (c) Ben A Adams. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.
|
// 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.
|
// 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;
|
||||||
|
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.Diagnostics.Contracts;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Ben.Demystifier.Enumerable;
|
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)
|
/// <summary>
|
||||||
=> stackTraceString?.SetValue(exception, value);
|
/// Demystifies the given <paramref name="exception" /> and tracks the original stack traces for the whole exception
|
||||||
|
/// tree.
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
|
public static T Demystify<T>(this T exception) where T : Exception
|
||||||
/// </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)
|
if (stackTrace.FrameCount > 0) exception.SetStackTracesString(stackTrace.ToString());
|
||||||
{
|
|
||||||
exception.SetStackTracesString(stackTrace.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is AggregateException aggEx)
|
if (exception is AggregateException aggEx)
|
||||||
{
|
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
ex.Demystify();
|
||||||
{
|
|
||||||
ex.Demystify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exception.InnerException?.Demystify();
|
exception.InnerException?.Demystify();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||||
}
|
|
||||||
|
|
||||||
return exception;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return exception;
|
||||||
/// 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();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <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;
|
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;
|
singleByteOpCode = new OpCode[225];
|
||||||
private static OpCode[] doubleByteOpCode;
|
doubleByteOpCode = new OpCode[31];
|
||||||
|
|
||||||
private readonly byte[] _cil;
|
var fields = GetOpCodeFields();
|
||||||
private int ptr;
|
|
||||||
|
|
||||||
|
for (var i = 0; i < fields.Length; i++)
|
||||||
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)
|
var code = (OpCode)fields[i].GetValue(null)!;
|
||||||
{
|
if (code.OpCodeType == OpCodeType.Nternal)
|
||||||
OpCode = ReadOpCode();
|
continue;
|
||||||
Operand = ReadOperand(OpCode, methodInfo);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpCode ReadOpCode()
|
if (code.Size == 1)
|
||||||
{
|
singleByteOpCode[code.Value] = code;
|
||||||
var instruction = ReadByte();
|
|
||||||
if (instruction < 254)
|
|
||||||
return singleByteOpCode[instruction];
|
|
||||||
else
|
else
|
||||||
return doubleByteOpCode[ReadByte()];
|
doubleByteOpCode[code.Value & 0xff] = code;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo)
|
|
||||||
{
|
|
||||||
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:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < inlineLength; i++)
|
|
||||||
{
|
|
||||||
ReadByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte ReadByte() => _cil[ptr++];
|
|
||||||
|
|
||||||
int ReadInt()
|
|
||||||
{
|
|
||||||
var b1 = ReadByte();
|
|
||||||
var b2 = ReadByte();
|
|
||||||
var b3 = ReadByte();
|
|
||||||
var b4 = ReadByte();
|
|
||||||
return b1 | b2 << 8 | b3 << 16 | b4 << 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ILReader()
|
|
||||||
{
|
|
||||||
singleByteOpCode = new OpCode[225];
|
|
||||||
doubleByteOpCode = new OpCode[31];
|
|
||||||
|
|
||||||
var fields = GetOpCodeFields();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < inlineLength; i++) ReadByte();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte ReadByte()
|
||||||
|
{
|
||||||
|
return _cil[ptr++];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ReadInt()
|
||||||
|
{
|
||||||
|
var b1 = ReadByte();
|
||||||
|
var b2 = ReadByte();
|
||||||
|
var b3 = ReadByte();
|
||||||
|
var b4 = ReadByte();
|
||||||
|
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FieldInfo[] GetOpCodeFields()
|
||||||
|
{
|
||||||
|
return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,136 +1,113 @@
|
|||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Reflection.Metadata;
|
using System.Reflection.Metadata;
|
||||||
using System.Reflection.Metadata.Ecma335;
|
using System.Reflection.Metadata.Ecma335;
|
||||||
using System.Reflection.PortableExecutable;
|
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
|
private readonly Dictionary<string, MetadataReaderProvider> _cache = new(StringComparer.Ordinal);
|
||||||
internal class PortablePdbReader : IDisposable
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, MetadataReaderProvider> _cache =
|
foreach (var entry in _cache) entry.Value.Dispose();
|
||||||
new Dictionary<string, MetadataReaderProvider>(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
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 = "";
|
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
|
||||||
row = 0;
|
var sequencePoints = methodDebugInfo.GetSequencePoints();
|
||||||
column = 0;
|
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 (bestPointSoFar.HasValue)
|
||||||
|
|
||||||
if (metadataReader == null)
|
|
||||||
{
|
{
|
||||||
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.
|
// 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.
|
// 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;
|
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>
|
/// <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>
|
/// </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 '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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PropertyInfo? GetTransformNamesPropertyInfo(Type attributeType)
|
|
||||||
{
|
|
||||||
#pragma warning disable 8634
|
|
||||||
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
|
|
||||||
#pragma warning restore 8634
|
|
||||||
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <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());
|
||||||
|
|
||||||
|
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType());
|
||||||
|
return propertyInfo.GetValue(attribute) as IList<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType)
|
||||||
|
{
|
||||||
|
#pragma warning disable 8634
|
||||||
|
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
|
||||||
|
#pragma warning restore 8634
|
||||||
|
() => 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Ben.Demystifier.Enumerable;
|
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 bool IsLambda { get; set; }
|
public StringBuilder AppendTo(StringBuilder builder, bool fullName = true)
|
||||||
|
{
|
||||||
|
if (IsAsync) builder.Append("async ");
|
||||||
|
|
||||||
public ResolvedParameter? ReturnParameter { get; set; }
|
if (ReturnParameter is not null)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
return
|
ReturnParameter.Append(builder);
|
||||||
IsAsync == obj.IsAsync &&
|
builder.Append(' ');
|
||||||
DeclaringType == obj.DeclaringType &&
|
|
||||||
Name == obj.Name &&
|
|
||||||
IsLambda == obj.IsLambda &&
|
|
||||||
Ordinal == obj.Ordinal &&
|
|
||||||
GenericArguments == obj.GenericArguments &&
|
|
||||||
SubMethod == obj.SubMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Append(new StringBuilder()).ToString();
|
if (DeclaringType is not null)
|
||||||
|
|
||||||
public StringBuilder Append(StringBuilder builder)
|
|
||||||
=> Append(builder, true);
|
|
||||||
|
|
||||||
public StringBuilder Append(StringBuilder builder, bool fullName)
|
|
||||||
{
|
{
|
||||||
if (IsAsync)
|
if (Name == ".ctor")
|
||||||
{
|
{
|
||||||
builder.Append("async ");
|
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
|
||||||
|
builder.Append("new ");
|
||||||
|
|
||||||
|
AppendDeclaringTypeName(builder, fullName);
|
||||||
}
|
}
|
||||||
|
else if (Name == ".cctor")
|
||||||
if (ReturnParameter != null)
|
|
||||||
{
|
{
|
||||||
ReturnParameter.Append(builder);
|
builder.Append("static ");
|
||||||
builder.Append(" ");
|
AppendDeclaringTypeName(builder, fullName);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append(Name);
|
AppendDeclaringTypeName(builder, fullName)
|
||||||
|
.Append('.')
|
||||||
|
.Append(Name);
|
||||||
}
|
}
|
||||||
builder.Append(GenericArguments);
|
}
|
||||||
|
else builder.Append(Name);
|
||||||
|
|
||||||
builder.Append("(");
|
builder.Append(GenericArguments);
|
||||||
if (MethodBase != null)
|
|
||||||
|
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;
|
var isFirst = true;
|
||||||
foreach(var param in Parameters)
|
foreach (var param in SubMethodParameters)
|
||||||
{
|
{
|
||||||
if (isFirst)
|
if (isFirst)
|
||||||
{
|
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
}
|
else builder.Append(", ");
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Append(", ");
|
|
||||||
}
|
|
||||||
param.Append(builder);
|
param.Append(builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else builder.Append('?');
|
||||||
{
|
|
||||||
builder.Append("?");
|
|
||||||
}
|
|
||||||
builder.Append(")");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(SubMethod) || IsLambda)
|
builder.Append(')');
|
||||||
|
if (IsLambda)
|
||||||
{
|
{
|
||||||
builder.Append("+");
|
builder.Append(" => { }");
|
||||||
builder.Append(SubMethod);
|
|
||||||
builder.Append("(");
|
|
||||||
if (SubMethodBase != null)
|
|
||||||
{
|
|
||||||
var isFirst = true;
|
|
||||||
foreach (var param in SubMethodParameters)
|
|
||||||
{
|
|
||||||
if (isFirst)
|
|
||||||
{
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Append(", ");
|
|
||||||
}
|
|
||||||
param.Append(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Append("?");
|
|
||||||
}
|
|
||||||
builder.Append(")");
|
|
||||||
if (IsLambda)
|
|
||||||
{
|
|
||||||
builder.Append(" => { }");
|
|
||||||
|
|
||||||
if (Ordinal.HasValue)
|
if (Ordinal.HasValue)
|
||||||
{
|
{
|
||||||
builder.Append(" [");
|
builder.Append(" [");
|
||||||
builder.Append(Ordinal);
|
builder.Append(Ordinal);
|
||||||
builder.Append("]");
|
builder.Append(']');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RecurseCount > 0)
|
|
||||||
{
|
|
||||||
builder.Append($" x {RecurseCount + 1:0}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true)
|
if (RecurseCount > 0) builder.Append($" x {RecurseCount + 1:0}");
|
||||||
{
|
|
||||||
return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: fullName, includeGenericParameterNames: true) : builder;
|
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.
|
// 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.
|
// 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? Prefix { get; set; }
|
|
||||||
public bool IsDynamicType { get; set; }
|
|
||||||
|
|
||||||
public ResolvedParameter(Type resolvedType) => ResolvedType = resolvedType;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void AppendTypeName(StringBuilder sb)
|
|
||||||
{
|
|
||||||
sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public Type ResolvedType { get; set; }
|
||||||
|
|
||||||
|
public string? Prefix { get; set; }
|
||||||
|
public bool IsDynamicType { get; set; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Ben.Demystifier.Enumerable;
|
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());
|
builder.Append(exception.GetType());
|
||||||
if (!string.IsNullOrEmpty(exception.Message))
|
if (!string.IsNullOrEmpty(exception.Message)) builder.Append(": ").Append(exception.Message);
|
||||||
{
|
builder.Append(Environment.NewLine);
|
||||||
builder.Append(": ").Append(exception.Message);
|
|
||||||
}
|
|
||||||
builder.Append(Environment.NewLine);
|
|
||||||
|
|
||||||
if (stackTrace.FrameCount > 0)
|
if (stackTrace.FrameCount > 0) stackTrace.AppendTo(builder);
|
||||||
{
|
|
||||||
stackTrace.Append(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is AggregateException aggEx)
|
if (exception is AggregateException aggEx)
|
||||||
{
|
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
builder.AppendInnerException(ex);
|
||||||
{
|
|
||||||
builder.AppendInnerException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception.InnerException != null)
|
if (exception.InnerException is not null) builder.AppendInnerException(exception.InnerException);
|
||||||
{
|
}
|
||||||
builder.AppendInnerException(exception.InnerException);
|
catch
|
||||||
}
|
{
|
||||||
}
|
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendInnerException(this StringBuilder builder, Exception exception)
|
return builder;
|
||||||
=> builder.Append(" ---> ")
|
|
||||||
.AppendDemystified(exception)
|
|
||||||
.AppendLine()
|
|
||||||
.Append(" --- End of inner exception stack trace ---");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
namespace Ben.Demystifier;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
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 readonly Dictionary<Type, string> BuiltInTypeNames = new()
|
||||||
public static class TypeNameHelper
|
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string>
|
{ typeof(void), "void" },
|
||||||
{
|
{ typeof(bool), "bool" },
|
||||||
{ typeof(void), "void" },
|
{ typeof(byte), "byte" },
|
||||||
{ typeof(bool), "bool" },
|
{ typeof(char), "char" },
|
||||||
{ typeof(byte), "byte" },
|
{ typeof(decimal), "decimal" },
|
||||||
{ typeof(char), "char" },
|
{ typeof(double), "double" },
|
||||||
{ typeof(decimal), "decimal" },
|
{ typeof(float), "float" },
|
||||||
{ typeof(double), "double" },
|
{ typeof(int), "int" },
|
||||||
{ typeof(float), "float" },
|
{ typeof(long), "long" },
|
||||||
{ typeof(int), "int" },
|
{ typeof(object), "object" },
|
||||||
{ typeof(long), "long" },
|
{ typeof(sbyte), "sbyte" },
|
||||||
{ typeof(object), "object" },
|
{ typeof(short), "short" },
|
||||||
{ typeof(sbyte), "sbyte" },
|
{ typeof(string), "string" },
|
||||||
{ typeof(short), "short" },
|
{ typeof(uint), "uint" },
|
||||||
{ typeof(string), "string" },
|
{ typeof(ulong), "ulong" },
|
||||||
{ typeof(uint), "uint" },
|
{ typeof(ushort), "ushort" }
|
||||||
{ 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>
|
public static readonly Dictionary<string, string> FSharpTypeNames = new()
|
||||||
/// Pretty print a type name.
|
{
|
||||||
/// </summary>
|
{ "Unit", "void" },
|
||||||
/// <param name="type">The <see cref="Type"/>.</param>
|
{ "FSharpOption", "Option" },
|
||||||
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
|
{ "FSharpAsync", "Async" },
|
||||||
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
|
{ "FSharpOption`1", "Option" },
|
||||||
/// <returns>The pretty printed type name.</returns>
|
{ "FSharpAsync`1", "Async" }
|
||||||
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
|
};
|
||||||
|
|
||||||
|
/// <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)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var underlyingType = Nullable.GetUnderlyingType(type);
|
||||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
if (underlyingType is not null)
|
||||||
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));
|
ProcessType(builder, underlyingType, options);
|
||||||
}
|
builder.Append('?');
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name);
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (type.IsArray)
|
||||||
private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options)
|
|
||||||
{
|
{
|
||||||
var innerType = type;
|
ProcessArrayType(builder, type, options);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (BuiltInTypeNames.TryGetValue(type, out var builtInName))
|
||||||
private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options)
|
|
||||||
{
|
{
|
||||||
var offset = 0;
|
builder.Append(builtInName);
|
||||||
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('>');
|
|
||||||
}
|
}
|
||||||
|
else if (type.Namespace == nameof(System))
|
||||||
private struct DisplayNameOptions
|
|
||||||
{
|
{
|
||||||
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames)
|
builder.Append(type.Name);
|
||||||
{
|
}
|
||||||
FullName = fullName;
|
else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll"
|
||||||
IncludeGenericParameterNames = includeGenericParameterNames;
|
&& FSharpTypeNames.TryGetValue(type.Name, out builtInName))
|
||||||
}
|
{
|
||||||
|
builder.Append(builtInName);
|
||||||
public bool FullName { get; }
|
}
|
||||||
|
else if (type.IsGenericParameter)
|
||||||
public bool IncludeGenericParameterNames { get; }
|
{
|
||||||
|
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.
|
// 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.
|
// 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 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)
|
public IList<string> TupleNames { get; }
|
||||||
: base(resolvedType)
|
|
||||||
=> TupleNames = tupleNames;
|
|
||||||
|
|
||||||
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);
|
||||||
{
|
|
||||||
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(">");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
|
|
||||||
{
|
|
||||||
sb.Append("(");
|
|
||||||
var args = parameterType.GetGenericArguments();
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (i > 0)
|
// Need to unwrap the first generic argument first.
|
||||||
{
|
sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
|
||||||
sb.Append(", ");
|
sb.Append("<");
|
||||||
}
|
AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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