diff --git a/Ben.Demystifier.sln b/Ben.Demystifier.sln deleted file mode 100644 index 0b7c702..0000000 --- a/Ben.Demystifier.sln +++ /dev/null @@ -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 diff --git a/src/Ben.Demystifier/Ben.Demystifier.csproj b/src/Ben.Demystifier/Ben.Demystifier.csproj index ebc33c9..9a09d92 100644 --- a/src/Ben.Demystifier/Ben.Demystifier.csproj +++ b/src/Ben.Demystifier/Ben.Demystifier.csproj @@ -1,16 +1,16 @@ - - embedded - true - enable - true - netstandard2.1;netstandard2.0;net45;net6.0 - + + embedded + true + enable + true + net6.0 + - - - - + + + + diff --git a/src/Ben.Demystifier/EnhancedStackFrame.cs b/src/Ben.Demystifier/EnhancedStackFrame.cs index f54ffdb..fa8609a 100644 --- a/src/Ben.Demystifier/EnhancedStackFrame.cs +++ b/src/Ben.Demystifier/EnhancedStackFrame.cs @@ -1,93 +1,84 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Reflection; +namespace Ben.Demystifier; -namespace Ben.Demystifier +public class EnhancedStackFrame : StackFrame { - public class EnhancedStackFrame : StackFrame + private readonly int _colNumber; + private readonly string? _fileName; + private readonly int _lineNumber; + + internal EnhancedStackFrame(StackFrame stackFrameBase, ResolvedMethod methodInfo, string? fileName, int lineNumber, + int colNumber) + : base(fileName, lineNumber, colNumber) { - private readonly string? _fileName; - private readonly int _lineNumber; - private readonly int _colNumber; - - public StackFrame StackFrame { get; } - - public bool IsRecursive - { - get => MethodInfo.RecurseCount > 0; - internal set => MethodInfo.RecurseCount++; - } - - public ResolvedMethod MethodInfo { get; } - - internal EnhancedStackFrame(StackFrame stackFrame, ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) - : base(fileName, lineNumber, colNumber) - { - StackFrame = stackFrame; - MethodInfo = methodInfo; - - _fileName = fileName; - _lineNumber = lineNumber; - _colNumber = colNumber; - } - - internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) - { - return _lineNumber == lineNumber && - _colNumber == colNumber && - _fileName == fileName && - MethodInfo.IsSequentialEquivalent(methodInfo); - } - - /// - /// 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. - /// - /// The file column number, or 0 (zero) if the file column number cannot be determined. - public override int GetFileColumnNumber() => _colNumber; - - /// - /// 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. - /// - /// The file line number, or 0 (zero) if the file line number cannot be determined. - public override int GetFileLineNumber() => _lineNumber; - - /// - /// Gets the file name that contains the code that is executing. - /// This information is typically extracted from the debugging symbols for the executable. - /// - /// The file name, or null if the file name cannot be determined. - public override string? GetFileName() => _fileName; - - /// - /// 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. - /// - /// The offset from the start of the MSIL code for the method that is executing. - public override int GetILOffset() => StackFrame.GetILOffset(); - - /// - /// Gets the method in which the frame is executing. - /// - /// The method in which the frame is executing. - public override MethodBase? GetMethod() => StackFrame.GetMethod(); - - /// - /// 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. - /// - /// The offset from the start of the JIT-compiled code for the method that is being executed. - public override int GetNativeOffset() => StackFrame.GetNativeOffset(); - - /// - /// Builds a readable representation of the stack trace. - /// - /// A readable representation of the stack trace. - public override string ToString() => MethodInfo.ToString(); + StackFrameBase = stackFrameBase; + MethodInfo = methodInfo; + _fileName = fileName; + _lineNumber = lineNumber; + _colNumber = colNumber; } -} + + private StackFrame StackFrameBase { get; } + + public bool IsRecursive + { + get => MethodInfo.RecurseCount > 0; + internal set => MethodInfo.RecurseCount++; + } + + public ResolvedMethod MethodInfo { get; } + + internal bool IsEquivalent(ResolvedMethod methodInfo, string? fileName, int lineNumber, int colNumber) => + _lineNumber == lineNumber && + _colNumber == colNumber && + _fileName == fileName && + MethodInfo.IsSequentialEquivalent(methodInfo); + + /// + /// 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. + /// + /// The file column number, or 0 (zero) if the file column number cannot be determined. + public override int GetFileColumnNumber() => _colNumber; + + /// + /// 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. + /// + /// The file line number, or 0 (zero) if the file line number cannot be determined. + public override int GetFileLineNumber() => _lineNumber; + + /// + /// Gets the file name that contains the code that is executing. + /// This information is typically extracted from the debugging symbols for the executable. + /// + /// The file name, or null if the file name cannot be determined. + public override string? GetFileName() => _fileName; + + /// + /// 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. + /// + /// The offset from the start of the MSIL code for the method that is executing. + public override int GetILOffset() => StackFrameBase.GetILOffset(); + + /// Gets the method in which the frame is executing. + /// The method in which the frame is executing. + public override MethodBase? GetMethod() => StackFrameBase.GetMethod(); + + /// + /// 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. + /// + /// The offset from the start of the JIT-compiled code for the method that is being executed. + public override int GetNativeOffset() => StackFrameBase.GetNativeOffset(); + + /// Builds a readable representation of the stack trace. + /// A readable representation of the stack trace. + public override string ToString() => MethodInfo.ToString(); +} \ No newline at end of file diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index b224152..54efcc4 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -3,28 +3,27 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -global using System.Collections; -global using System.Collections.Generic; -using Ben.Demystifier.Enumerable; -using Ben.Demystifier.Internal; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Ben.Demystifier.Enumerable; +using Ben.Demystifier.Internal; namespace Ben.Demystifier { public partial class EnhancedStackTrace { - private static readonly Type? StackTraceHiddenAttributeType = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false); - private static readonly Type? AsyncIteratorStateMachineAttributeType = Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); + private static readonly Type? StackTraceHiddenAttributeType = + Type.GetType("System.Diagnostics.StackTraceHiddenAttribute", false); + + private static readonly Type? AsyncIteratorStateMachineAttributeType = + Type.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); static EnhancedStackTrace() { - if (AsyncIteratorStateMachineAttributeType != null) return; + if (AsyncIteratorStateMachineAttributeType is not null) return; Assembly mba; try @@ -36,32 +35,15 @@ namespace Ben.Demystifier return; } - AsyncIteratorStateMachineAttributeType = mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); - } - - private static List GetFrames(Exception exception) - { - if (exception == null) - { - return new List(); - } - - var needFileInfo = true; - var stackTrace = new StackTrace(exception, needFileInfo); - - return GetFrames(stackTrace); + AsyncIteratorStateMachineAttributeType = + mba.GetType("System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute", false); } public static List GetFrames(StackTrace stackTrace) { - var frames = new List(); + var enhancedFrames = new List(); var stackFrames = stackTrace.GetFrames(); - if (stackFrames == null) - { - return frames; - } - EnhancedStackFrame? lastFrame = null; PortablePdbReader? portablePdbReader = null; try @@ -69,27 +51,22 @@ namespace Ben.Demystifier for (var i = 0; i < stackFrames.Length; i++) { var frame = stackFrames[i]; - if (frame is null) - { - continue; - } var method = frame.GetMethod(); // Always show last stackFrame - if (method != null && !ShowInStackTrace(method) && i < stackFrames.Length - 1) - { + if (method is not null && !ShowInStackTrace(method) && i < stackFrames.Length - 1) continue; - } var fileName = frame.GetFileName(); var row = frame.GetFileLineNumber(); var column = frame.GetFileColumnNumber(); var ilOffset = frame.GetILOffset(); - if (method != null && string.IsNullOrEmpty(fileName) && ilOffset >= 0) + if (method is not null && string.IsNullOrEmpty(fileName) && ilOffset >= 0) { // .NET Framework and older versions of mono don't support portable PDBs // so we read it manually to get file name and line information - (portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, frame.GetILOffset(), out fileName, out row, out column); + (portablePdbReader ??= new PortablePdbReader()).PopulateStackFrame(frame, method, + frame.GetILOffset(), out fileName, out row, out column); } if (method is null) @@ -106,7 +83,7 @@ namespace Ben.Demystifier else { var stackFrame = new EnhancedStackFrame(frame, resolvedMethod, fileName, row, column); - frames.Add(stackFrame); + enhancedFrames.Add(stackFrame); lastFrame = stackFrame; } } @@ -116,7 +93,7 @@ namespace Ben.Demystifier portablePdbReader?.Dispose(); } - return frames; + return enhancedFrames; } public static ResolvedMethod GetMethodDisplayString(MethodBase originMethod) @@ -161,7 +138,8 @@ namespace Ben.Demystifier methodDisplayInfo.Name = methodName; if (method.Name.IndexOf("<") >= 0) { - if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, out var ordinal)) + if (TryResolveGeneratedName(ref method, out type, out methodName, out subMethodName, out var kind, + out var ordinal)) { methodName = method.Name; methodDisplayInfo.MethodBase = method; @@ -175,7 +153,7 @@ namespace Ben.Demystifier methodDisplayInfo.IsLambda = (kind == GeneratedNameKind.LambdaMethod); - if (methodDisplayInfo.IsLambda && type != null) + if (methodDisplayInfo.IsLambda && type is not null) { if (methodName == ".cctor") { @@ -185,7 +163,8 @@ namespace Ben.Demystifier } else { - var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + var fields = + type.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (var field in fields) { var value = field.GetValue(field); @@ -212,33 +191,29 @@ namespace Ben.Demystifier } // ResolveStateMachineMethod may have set declaringType to null - if (type != null) + if (type is not null) { methodDisplayInfo.DeclaringType = type; } if (method is MethodInfo mi) { - var returnParameter = mi.ReturnParameter; - if (returnParameter != null) - { + if (mi.ReturnParameter is not null) methodDisplayInfo.ReturnParameter = GetParameter(mi.ReturnParameter); - } - else if (mi.ReturnType != null) - { + else if (mi.ReturnType is not null) methodDisplayInfo.ReturnParameter = new ResolvedParameter(mi.ReturnType) { Prefix = "", - Name = "", + Name = "" }; - } } if (method.IsGenericMethod) { var genericArguments = method.GetGenericArguments(); var genericArgumentsString = string.Join(", ", genericArguments - .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); + .Select(arg => + TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); methodDisplayInfo.GenericArguments += "<" + genericArgumentsString + ">"; methodDisplayInfo.ResolvedGenericArguments = genericArguments; } @@ -260,7 +235,7 @@ namespace Ben.Demystifier { methodDisplayInfo.SubMethodBase = null; } - else if (methodDisplayInfo.SubMethodBase != null) + else if (methodDisplayInfo.SubMethodBase is not null) { parameters = methodDisplayInfo.SubMethodBase.GetParameters(); if (parameters.Length > 0) @@ -295,7 +270,8 @@ namespace Ben.Demystifier return false; } - private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName, out string? subMethodName, out GeneratedNameKind kind, out int? ordinal) + private static bool TryResolveGeneratedName(ref MethodBase method, out Type? type, out string methodName, + out string? subMethodName, out GeneratedNameKind kind, out int? ordinal) { kind = GeneratedNameKind.None; type = method.DeclaringType; @@ -315,28 +291,29 @@ namespace Ben.Demystifier switch (kind) { case GeneratedNameKind.LocalFunction: - { - var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); - if (localNameStart < 0) break; - localNameStart += 3; + { + var localNameStart = generatedName.IndexOf((char)kind, closeBracketOffset + 1); + if (localNameStart < 0) break; + localNameStart += 3; - if (localNameStart < generatedName.Length) + if (localNameStart < generatedName.Length) + { + var localNameEnd = generatedName.IndexOf("|", localNameStart); + if (localNameEnd > 0) { - var localNameEnd = generatedName.IndexOf("|", localNameStart); - if (localNameEnd > 0) - { - subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart); - } + subMethodName = generatedName.Substring(localNameStart, localNameEnd - localNameStart); } - break; } + + break; + } case GeneratedNameKind.LambdaMethod: subMethodName = ""; break; } var dt = method.DeclaringType; - if (dt == null) + if (dt is null) { return false; } @@ -345,30 +322,42 @@ namespace Ben.Demystifier var matchName = methodName; - var candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; + var candidateMethods = + dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); + if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) + return true; - var candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; + var candidateConstructors = + dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); + if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) + return true; - const int MaxResolveDepth = 10; - for (var i = 0; i < MaxResolveDepth; i++) + for (var i = 0; i < 10; i++) { dt = dt.DeclaringType; - if (dt == null) - { + if (dt is null) return false; - } - candidateMethods = dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) return true; + candidateMethods = + dt.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); + if (TryResolveSourceMethod(candidateMethods, kind, matchHint, ref method, ref type, out ordinal)) + return true; - candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); - if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) return true; + candidateConstructors = + dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name == matchName); + if (TryResolveSourceMethod(candidateConstructors, kind, matchHint, ref method, ref type, out ordinal)) + return true; if (methodName == ".cctor") { - candidateConstructors = dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); + candidateConstructors = + dt.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.DeclaredOnly).Where(m => m.Name == matchName); foreach (var cctor in candidateConstructors) { method = cctor; @@ -381,7 +370,8 @@ namespace Ben.Demystifier return false; } - private static bool TryResolveSourceMethod(IEnumerable candidateMethods, GeneratedNameKind kind, string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal) + private static bool TryResolveSourceMethod(IEnumerable candidateMethods, GeneratedNameKind kind, + string? matchHint, ref MethodBase method, ref Type? type, out int? ordinal) { ordinal = null; foreach (var candidateMethod in candidateMethods) @@ -390,6 +380,7 @@ namespace Ben.Demystifier { continue; } + if (kind == GeneratedNameKind.LambdaMethod) { foreach (var v in EnumerableIList.Create(methodBody.LocalVariables)) @@ -397,8 +388,8 @@ namespace Ben.Demystifier if (v.LocalType == type) { GetOrdinal(method, ref ordinal); - } + method = candidateMethod; type = method.DeclaringType; return true; @@ -412,12 +403,13 @@ namespace Ben.Demystifier { continue; } + var reader = new ILReader(rawIl); while (reader.Read(candidateMethod)) { if (reader.Operand is MethodBase mb) { - if (method == mb || matchHint != null && method.Name.Contains(matchHint)) + if (method == mb || matchHint is not null && method.Name.Contains(matchHint)) { if (kind == GeneratedNameKind.LambdaMethod) { @@ -460,10 +452,12 @@ namespace Ben.Demystifier ordinal = foundOrdinal; - var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); + var methods = method.DeclaringType?.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Static | BindingFlags.Instance | + BindingFlags.DeclaredOnly); var count = 0; - if (methods != null) + if (methods is not null) { var startName = method.Name.Substring(0, lamdaStart); foreach (var m in methods) @@ -501,6 +495,7 @@ namespace Ben.Demystifier return methodName.Substring(start, end - start); } + return null; } @@ -517,13 +512,8 @@ namespace Ben.Demystifier { openBracketOffset = -1; if (name.StartsWith("CS$<", StringComparison.Ordinal)) - { openBracketOffset = 3; - } - else if (name.StartsWith("<", StringComparison.Ordinal)) - { - openBracketOffset = 0; - } + else if (name.StartsWith("<", StringComparison.Ordinal)) openBracketOffset = 0; if (openBracketOffset >= 0) { @@ -549,50 +539,32 @@ namespace Ben.Demystifier private static int IndexOfBalancedParenthesis(string str, int openingOffset, char closing) { var opening = str[openingOffset]; - var depth = 1; for (var i = openingOffset + 1; i < str.Length; i++) { var c = str[i]; if (c == opening) - { depth++; - } else if (c == closing) { depth--; if (depth == 0) - { return i; - } } } - return -1; } private static string GetPrefix(ParameterInfo parameter) { if (Attribute.IsDefined(parameter, typeof(ParamArrayAttribute), false)) - { return "params"; - } - if (parameter.IsOut) - { return "out"; - } - if (parameter.IsIn) - { return "in"; - } - if (parameter.ParameterType.IsByRef) - { return "ref"; - } - return string.Empty; } @@ -605,20 +577,16 @@ namespace Ben.Demystifier { var customAttribs = parameter.GetCustomAttributes(inherit: false); - var tupleNameAttribute = customAttribs.OfType().FirstOrDefault(a => a.IsTupleElementNameAttribute()); + var tupleNameAttribute = customAttribs.OfType() + .FirstOrDefault(a => a.IsTupleElementNameAttribute()); var tupleNames = tupleNameAttribute?.GetTransformerNames(); if (tupleNames?.Count > 0) - { return GetValueTupleParameter(tupleNames, prefix, parameter.Name, parameterType); - } } - if (parameterType.IsByRef && parameterType.GetElementType() is {} elementType) - { - parameterType = elementType; - } + if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType) parameterType = elementType; return new ResolvedParameter(parameterType) { @@ -628,48 +596,16 @@ namespace Ben.Demystifier }; } - private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string? name, Type parameterType) + private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string? name, + Type parameterType) { return new ValueTupleResolvedParameter(parameterType, tupleNames) { Prefix = prefix, - Name = name, + Name = name }; } - private static string GetValueTupleParameterName(IList tupleNames, Type parameterType) - { - var sb = new StringBuilder(); - sb.Append("("); - var args = parameterType.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) - { - if (i > 0) - { - sb.Append(", "); - } - - sb.Append(TypeNameHelper.GetTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true)); - - if (i >= tupleNames.Count) - { - continue; - } - - var argName = tupleNames[i]; - if (argName == null) - { - continue; - } - - sb.Append(" "); - sb.Append(argName); - } - - sb.Append(")"); - return sb.ToString(); - } - private static bool ShowInStackTrace(MethodBase method) { // Since .NET 5: @@ -684,7 +620,7 @@ namespace Ben.Demystifier } // Since .NET Core 2: - if (StackTraceHiddenAttributeType != null) + if (StackTraceHiddenAttributeType is not null) { // Don't show any methods marked with the StackTraceHiddenAttribute // https://github.com/dotnet/coreclr/pull/14652 @@ -696,13 +632,13 @@ namespace Ben.Demystifier var type = method.DeclaringType; - if (type == null) + if (type is null) { return true; } // Since .NET Core 2: - if (StackTraceHiddenAttributeType != null) + if (StackTraceHiddenAttributeType is not null) { // Don't show any methods marked with the StackTraceHiddenAttribute // https://github.com/dotnet/coreclr/pull/14652 @@ -716,18 +652,24 @@ namespace Ben.Demystifier { return false; } + if (type == typeof(ValueTask<>) && method.Name == "get_Result") { return false; } - if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && method.Name.EndsWith(".GetResult")) + + if (method.Name.StartsWith("System.Threading.Tasks.Sources.IValueTaskSource") && + method.Name.EndsWith(".GetResult")) { return false; } - if (method.Name == "GetResult" && method.DeclaringType?.FullName == "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1") + + if (method.Name == "GetResult" && method.DeclaringType?.FullName == + "System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1") { return false; } + if (type == typeof(Task) || type.DeclaringType == typeof(Task)) { if (method.Name.Contains(".cctor")) @@ -747,12 +689,11 @@ namespace Ben.Demystifier return false; } } + if (type == typeof(ExecutionContext)) { if (method.Name.Contains(".cctor")) - { return false; - } switch (method.Name) { @@ -821,9 +762,7 @@ namespace Ben.Demystifier private static bool IsStackTraceHidden(MemberInfo memberInfo) { if (StackTraceHiddenAttributeType is not null && !memberInfo.Module.Assembly.ReflectionOnly) - { return memberInfo.GetCustomAttributes(StackTraceHiddenAttributeType, false).Length != 0; - } EnumerableIList attributes; try @@ -835,8 +774,9 @@ namespace Ben.Demystifier return false; } - foreach (var attribute in attributes) + for (var i = 0; i < attributes.Count; i++) { + var attribute = attributes[i]; // reflection-only attribute, match on name if (attribute.AttributeType.FullName == StackTraceHiddenAttributeType?.FullName) { @@ -855,6 +795,7 @@ namespace Ben.Demystifier declaringType = null!; return false; } + declaringType = method.DeclaringType; var parentType = declaringType.DeclaringType; @@ -863,11 +804,9 @@ namespace Ben.Demystifier return false; } - static MethodInfo[] GetDeclaredMethods(Type type) => - type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); - - var methods = GetDeclaredMethods(parentType); - if (methods == null) + var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | + BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (methods is null) { return false; } @@ -888,8 +827,8 @@ namespace Ben.Demystifier { foundAttribute = true; foundIteratorAttribute |= asma is IteratorStateMachineAttribute - || AsyncIteratorStateMachineAttributeType != null - && AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma); + || AsyncIteratorStateMachineAttributeType is not null + && AsyncIteratorStateMachineAttributeType.IsInstanceOfType(asma); } } @@ -903,6 +842,7 @@ namespace Ben.Demystifier return foundIteratorAttribute; } } + return false; } @@ -917,7 +857,9 @@ namespace Ben.Demystifier LambdaMethod = 'b', LambdaDisplayClass = 'c', StateMachineType = 'd', - LocalFunction = 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names + + LocalFunction = + 'g', // note collision with Deprecated_InitializerLocal, however this one is only used for method names // Used by EnC: AwaiterField = 'u', @@ -942,4 +884,4 @@ namespace Ben.Demystifier DynamicCallSiteField = 'p' } } -} +} \ No newline at end of file diff --git a/src/Ben.Demystifier/EnhancedStackTrace.cs b/src/Ben.Demystifier/EnhancedStackTrace.cs index 933e60d..c4e13f9 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.cs @@ -1,141 +1,89 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections; -using System.Collections.Generic; -using Ben.Demystifier.Enumerable; -using System.IO; -using System.Text; +namespace Ben.Demystifier; -namespace Ben.Demystifier +public partial class EnhancedStackTrace : StackTrace, IEnumerable { - public partial class EnhancedStackTrace : StackTrace, IEnumerable + private readonly List _frames; + + /// + /// Initializes a new instance of the System.Diagnostics.StackTrace class using the + /// provided exception object + /// + 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 _frames; + public EnhancedStackTrace(StackTrace stackTrace) + { + _frames = GetFrames(stackTrace); + } - // Summary: - // Initializes a new instance of the System.Diagnostics.StackTrace class using the - // provided exception object. - // - // Parameters: - // e: - // The exception object from which to construct the stack trace. - // - // Exceptions: - // T:System.ArgumentNullException: - // The parameter e is null. - public EnhancedStackTrace(Exception e) + /// The number of frames in the stack trace. + public override int FrameCount => _frames.Count; + + IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); + + public static EnhancedStackTrace Current() + => new EnhancedStackTrace(new StackTrace(1 /* skip this one frame */, true)); + + /// The index of the stack frame requested. + /// The specified stack frame. + public override StackFrame GetFrame(int index) => _frames[index]; + + /// a copy of all stack frames in the current stack trace. + public override StackFrame[] GetFrames() => _frames.ToArray(); + + /// + /// Builds a readable representation of the stack trace. + /// + public override string ToString() + { + if (_frames.Count == 0) return ""; + + var sb = new StringBuilder(); + AppendTo(sb); + return sb.ToString(); + } + + public void AppendTo(StringBuilder sb) + { + var count = _frames.Count; + for (var i = 0; i < count; i++) { - if (e == null) + if (i > 0) sb.Append(Environment.NewLine); + var frame = _frames[i]; + sb.Append(" at "); + frame.MethodInfo.AppendTo(sb); + + var fileName = frame.GetFileName(); + if (!string.IsNullOrEmpty(fileName)) { - throw new ArgumentNullException(nameof(e)); + sb.Append(" in "); + sb.Append(TryGetFullPath(fileName)); } - _frames = GetFrames(e); - } - - - public EnhancedStackTrace(StackTrace stackTrace) - { - if (stackTrace == null) + var lineNo = frame.GetFileLineNumber(); + if (lineNo != 0) { - throw new ArgumentNullException(nameof(stackTrace)); + sb.Append(":line "); + sb.Append(lineNo); } - - _frames = GetFrames(stackTrace); - } - - /// - /// Gets the number of frames in the stack trace. - /// - /// The number of frames in the stack trace. - public override int FrameCount => _frames.Count; - - /// - /// Gets the specified stack frame. - /// - /// The index of the stack frame requested. - /// The specified stack frame. - public override StackFrame GetFrame(int index) => _frames[index]; - - /// - /// Returns a copy of all stack frames in the current stack trace. - /// - /// - /// An array of type System.Diagnostics.StackFrame representing the function calls - /// in the stack trace. - /// - public override StackFrame[] GetFrames() => _frames.ToArray(); - - /// - /// Builds a readable representation of the stack trace. - /// - /// A readable representation of the stack trace. - 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 GetEnumerator() => EnumerableIList.Create(_frames); - IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _frames.GetEnumerator(); - - /// - /// Tries to convert a given to a full path. - /// Returns original value if the conversion isn't possible or a given path is relative. - /// - public static string TryGetFullPath(string filePath) - { - if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile) - { - return Uri.UnescapeDataString(uri.AbsolutePath); - } - - return filePath; } } -} + + /// + /// Tries to convert a given to a full path. + /// Returns original value if the conversion isn't possible or a given path is relative. + /// + public static string TryGetFullPath(string filePath) + { + if (Uri.TryCreate(filePath, UriKind.Absolute, out var uri) && uri.IsFile) + return Uri.UnescapeDataString(uri.AbsolutePath); + return filePath; + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Enumerable/EnumerableIList.cs b/src/Ben.Demystifier/Enumerable/EnumerableIList.cs index 478219a..9eb9502 100644 --- a/src/Ben.Demystifier/Enumerable/EnumerableIList.cs +++ b/src/Ben.Demystifier/Enumerable/EnumerableIList.cs @@ -1,65 +1,77 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Ben.Demystifier.Enumerable +namespace Ben.Demystifier.Enumerable; + +public static class EnumerableIList { - public static class EnumerableIList + public static EnumerableIList Create(IList list) { - public static EnumerableIList Create(IList list) => new EnumerableIList(list); - } - - public struct EnumerableIList : IEnumerableIList, IList - { - private readonly IList _list; - - public EnumerableIList(IList list) => _list = list; - - public EnumeratorIList GetEnumerator() => new EnumeratorIList(_list); - - public static implicit operator EnumerableIList(List list) => new EnumerableIList(list); - - public static implicit operator EnumerableIList(T[] array) => new EnumerableIList(array); - - public static EnumerableIList Empty = default; - - - // IList pass through - - /// - public T this[int index] { get => _list[index]; set => _list[index] = value; } - - /// - public int Count => _list.Count; - - /// - public bool IsReadOnly => _list.IsReadOnly; - - /// - public void Add(T item) => _list.Add(item); - - /// - public void Clear() => _list.Clear(); - - /// - public bool Contains(T item) => _list.Contains(item); - - /// - public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); - - /// - public int IndexOf(T item) => _list.IndexOf(item); - - /// - public void Insert(int index, T item) => _list.Insert(index, item); - - /// - public bool Remove(T item) => _list.Remove(item); - - /// - public void RemoveAt(int index) => _list.RemoveAt(index); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return new(list); } } + +public struct EnumerableIList : IEnumerableIList, IList +{ + private readonly IList _list; + + public EnumerableIList(IList list) => _list = list; + + public EnumeratorIList GetEnumerator() => new(_list); + + public static implicit operator EnumerableIList(List list) => new(list); + + public static implicit operator EnumerableIList(T[] array) => new(array); + + public static EnumerableIList Empty = default; + + + // IList pass through + + /// + public T this[int index] + { + get => _list[index]; + set => _list[index] = value; + } + + /// + public int Count => _list.Count; + + /// + public bool IsReadOnly => _list.IsReadOnly; + + /// + public void Add(T item) => _list.Add(item); + + /// + public void Clear() => _list.Clear(); + + /// + public bool Contains(T item) => _list.Contains(item); + + /// + public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + + /// + public int IndexOf(T item) => _list.IndexOf(item); + + /// + public void Insert(int index, T item) => _list.Insert(index, item); + + /// + public bool Remove(T item) => _list.Remove(item); + + /// + public void RemoveAt(int index) => _list.RemoveAt(index); + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Enumerable/EnumeratorIList.cs b/src/Ben.Demystifier/Enumerable/EnumeratorIList.cs index 3629166..31c0ace 100644 --- a/src/Ben.Demystifier/Enumerable/EnumeratorIList.cs +++ b/src/Ben.Demystifier/Enumerable/EnumeratorIList.cs @@ -1,30 +1,35 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Ben.Demystifier.Enumerable +namespace Ben.Demystifier.Enumerable; + +public struct EnumeratorIList : IEnumerator { - public struct EnumeratorIList : IEnumerator + private readonly IList _list; + private int _index; + + public EnumeratorIList(IList list) { - private readonly IList _list; - private int _index; - - public EnumeratorIList(IList 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; + _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; + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Enumerable/IEnumerableIList.cs b/src/Ben.Demystifier/Enumerable/IEnumerableIList.cs index 13e848a..fcee75a 100644 --- a/src/Ben.Demystifier/Enumerable/IEnumerableIList.cs +++ b/src/Ben.Demystifier/Enumerable/IEnumerableIList.cs @@ -1,10 +1,9 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Ben.Demystifier.Enumerable +namespace Ben.Demystifier.Enumerable; + +internal interface IEnumerableIList : IEnumerable { - interface IEnumerableIList : IEnumerable - { - new EnumeratorIList GetEnumerator(); - } -} + new EnumeratorIList GetEnumerator(); +} \ No newline at end of file diff --git a/src/Ben.Demystifier/ExceptionExtensions.cs b/src/Ben.Demystifier/ExceptionExtensions.cs index ce88c02..36b2c44 100644 --- a/src/Ben.Demystifier/ExceptionExtensions.cs +++ b/src/Ben.Demystifier/ExceptionExtensions.cs @@ -1,66 +1,66 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -global using System.Diagnostics; global using System; +global using System.Text; +global using System.Collections; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.Reflection; +global using System.Linq; using System.Diagnostics.Contracts; -using System.Collections.Generic; using Ben.Demystifier.Enumerable; -using System.Reflection; -using System.Text; -namespace Ben.Demystifier +namespace Ben.Demystifier; + +public static class ExceptionExtensions { - public static class ExceptionExtensions + private static readonly FieldInfo? StackTraceString = + typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); + + private static void SetStackTracesString(this Exception exception, string value) { - private static readonly FieldInfo? stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); + StackTraceString?.SetValue(exception, value); + } - private static void SetStackTracesString(this Exception exception, string value) - => stackTraceString?.SetValue(exception, value); - - /// - /// Demystifies the given and tracks the original stack traces for the whole exception tree. - /// - public static T Demystify(this T exception) where T : Exception + /// + /// Demystifies the given and tracks the original stack traces for the whole exception + /// tree. + /// + public static T Demystify(this T exception) where T : Exception + { + try { - try - { - var stackTrace = new EnhancedStackTrace(exception); + var stackTrace = new EnhancedStackTrace(exception); - if (stackTrace.FrameCount > 0) - { - exception.SetStackTracesString(stackTrace.ToString()); - } + if (stackTrace.FrameCount > 0) exception.SetStackTracesString(stackTrace.ToString()); - if (exception is AggregateException aggEx) - { - foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) - { - ex.Demystify(); - } - } + if (exception is AggregateException aggEx) + foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) + ex.Demystify(); - exception.InnerException?.Demystify(); - } - catch - { - // Processing exceptions shouldn't throw exceptions; if it fails - } - - return exception; + exception.InnerException?.Demystify(); + } + catch + { + // Processing exceptions shouldn't throw exceptions; if it fails } - /// - /// Gets demystified string representation of the . - /// - /// - /// method mutates the exception instance that can cause - /// issues if a system relies on the stack trace be in the specific form. - /// Unlike this method is pure. It calls first, - /// computes a demystified string representation and then restores the original state of the exception back. - /// - [Pure] - public static string ToStringDemystified(this Exception exception) - => new StringBuilder().AppendDemystified(exception).ToString(); + return exception; } -} + + /// + /// Gets demystified string representation of the . + /// + /// + /// method mutates the exception instance that can cause + /// issues if a system relies on the stack trace be in the specific form. + /// Unlike this method is pure. It calls first, + /// computes a demystified string representation and then restores the original state of the exception back. + /// + [Pure] + public static string ToStringDemystified(this Exception exception) + { + return new StringBuilder().AppendDemystified(exception).ToString(); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Internal/ILReader.cs b/src/Ben.Demystifier/Internal/ILReader.cs index 29bc436..eb236af 100644 --- a/src/Ben.Demystifier/Internal/ILReader.cs +++ b/src/Ben.Demystifier/Internal/ILReader.cs @@ -1,145 +1,145 @@ -using System.Reflection; using System.Reflection.Emit; -namespace Ben.Demystifier.Internal +namespace Ben.Demystifier.Internal; + +internal class ILReader { - internal class ILReader + private static readonly OpCode[] singleByteOpCode; + private static readonly OpCode[] doubleByteOpCode; + + private readonly byte[] _cil; + private int ptr; + + static ILReader() { - private static OpCode[] singleByteOpCode; - private static OpCode[] doubleByteOpCode; + singleByteOpCode = new OpCode[225]; + doubleByteOpCode = new OpCode[31]; - private readonly byte[] _cil; - private int ptr; + var fields = GetOpCodeFields(); - - public ILReader(byte[] cil) => _cil = cil; - - public OpCode OpCode { get; private set; } - public int MetadataToken { get; private set; } - public MemberInfo? Operand { get; private set; } - - public bool Read(MethodBase methodInfo) + for (var i = 0; i < fields.Length; i++) { - if (ptr < _cil.Length) - { - OpCode = ReadOpCode(); - Operand = ReadOperand(OpCode, methodInfo); - return true; - } - return false; - } + var code = (OpCode)fields[i].GetValue(null)!; + if (code.OpCodeType == OpCodeType.Nternal) + continue; - OpCode ReadOpCode() - { - var instruction = ReadByte(); - if (instruction < 254) - return singleByteOpCode[instruction]; + if (code.Size == 1) + singleByteOpCode[code.Value] = code; else - return doubleByteOpCode[ReadByte()]; + doubleByteOpCode[code.Value & 0xff] = code; } - - 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); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Internal/PortablePdbReader.cs b/src/Ben.Demystifier/Internal/PortablePdbReader.cs index 5eab488..af7b861 100644 --- a/src/Ben.Demystifier/Internal/PortablePdbReader.cs +++ b/src/Ben.Demystifier/Internal/PortablePdbReader.cs @@ -1,136 +1,113 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; -namespace Ben.Demystifier.Internal +namespace Ben.Demystifier.Internal; + +// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs +internal class PortablePdbReader : IDisposable { - // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs - internal class PortablePdbReader : IDisposable + private readonly Dictionary _cache = new(StringComparer.Ordinal); + + public void Dispose() { - private readonly Dictionary _cache = - new Dictionary(StringComparer.Ordinal); + foreach (var entry in _cache) entry.Value.Dispose(); - public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, out int row, out int column) + _cache.Clear(); + } + + public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName, + out int row, out int column) + { + fileName = ""; + row = 0; + column = 0; + + if (method.Module.Assembly.IsDynamic) return; + + var metadataReader = GetMetadataReader(method.Module.Assembly.Location); + + if (metadataReader is null) return; + + var methodToken = MetadataTokens.Handle(method.MetadataToken); + + Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); + + var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); + + if (!handle.IsNil) { - fileName = ""; - row = 0; - column = 0; + var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); + var sequencePoints = methodDebugInfo.GetSequencePoints(); + SequencePoint? bestPointSoFar = null; - if (method.Module.Assembly.IsDynamic) + foreach (var point in sequencePoints) { - return; + if (point.Offset > IlOffset) break; + + if (point.StartLine != SequencePoint.HiddenLine) bestPointSoFar = point; } - var metadataReader = GetMetadataReader(method.Module.Assembly.Location); - - if (metadataReader == null) + if (bestPointSoFar.HasValue) { - return; + row = bestPointSoFar.Value.StartLine; + column = bestPointSoFar.Value.StartColumn; + fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); } - - var methodToken = MetadataTokens.Handle(method.MetadataToken); - - Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); - - var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); - - if (!handle.IsNil) - { - var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); - var sequencePoints = methodDebugInfo.GetSequencePoints(); - SequencePoint? bestPointSoFar = null; - - foreach (var point in sequencePoints) - { - if (point.Offset > IlOffset) - { - break; - } - - if (point.StartLine != SequencePoint.HiddenLine) - { - bestPointSoFar = point; - } - } - - if (bestPointSoFar.HasValue) - { - row = bestPointSoFar.Value.StartLine; - column = bestPointSoFar.Value.StartColumn; - fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); - } - } - } - - private MetadataReader? GetMetadataReader(string assemblyPath) - { - if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null) - { - var pdbPath = GetPdbPath(assemblyPath); - - if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!)) - { - var pdbStream = File.OpenRead(pdbPath); - provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); - } - - _cache[assemblyPath] = provider; - } - - return provider?.GetMetadataReader(); - } - - private static string? GetPdbPath(string assemblyPath) - { - if (string.IsNullOrEmpty(assemblyPath)) - { - return null; - } - - if (File.Exists(assemblyPath)) - { - var peStream = File.OpenRead(assemblyPath); - - using var peReader = new PEReader(peStream); - foreach (var entry in peReader.ReadDebugDirectory()) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); - var peDirectory = Path.GetDirectoryName(assemblyPath); - return peDirectory is null - ? null - : Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); - } - } - } - - return null; - } - - private static bool IsPortable(string pdbPath) - { - using var pdbStream = File.OpenRead(pdbPath); - return pdbStream.ReadByte() == 'B' && - pdbStream.ReadByte() == 'S' && - pdbStream.ReadByte() == 'J' && - pdbStream.ReadByte() == 'B'; - } - - public void Dispose() - { - foreach (var entry in _cache) - { - entry.Value?.Dispose(); - } - - _cache.Clear(); } } -} + + private MetadataReader? GetMetadataReader(string assemblyPath) + { + if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null) + { + var pdbPath = GetPdbPath(assemblyPath); + + if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!)) + { + var pdbStream = File.OpenRead(pdbPath); + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + } + + _cache[assemblyPath] = provider; + } + + return provider?.GetMetadataReader(); + } + + private static string? GetPdbPath(string assemblyPath) + { + if (string.IsNullOrEmpty(assemblyPath)) return null; + + if (File.Exists(assemblyPath)) + { + var peStream = File.OpenRead(assemblyPath); + + using var peReader = new PEReader(peStream); + foreach (var entry in peReader.ReadDebugDirectory()) + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + var peDirectory = Path.GetDirectoryName(assemblyPath); + return peDirectory is null + ? null + : Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); + } + } + + return null; + } + + private static bool IsPortable(string pdbPath) + { + using var pdbStream = File.OpenRead(pdbPath); + return pdbStream.ReadByte() == 'B' && + pdbStream.ReadByte() == 'S' && + pdbStream.ReadByte() == 'J' && + pdbStream.ReadByte() == 'B'; + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/Internal/ReflectionHelper.cs b/src/Ben.Demystifier/Internal/ReflectionHelper.cs index 7f484d2..6e11b5a 100644 --- a/src/Ben.Demystifier/Internal/ReflectionHelper.cs +++ b/src/Ben.Demystifier/Internal/ReflectionHelper.cs @@ -1,62 +1,60 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; -using System.Reflection; using System.Threading; -namespace Ben.Demystifier.Internal +namespace Ben.Demystifier.Internal; + +/// +/// A helper class that contains utilities methods for dealing with reflection. +/// +public static class ReflectionHelper { + private static PropertyInfo? transformerNamesLazyPropertyInfo; + /// - /// A helper class that contains utilities methods for dealing with reflection. + /// Returns true if the is a value tuple type. /// - public static class ReflectionHelper + public static bool IsValueTuple(this Type type) { - private static PropertyInfo? transformerNamesLazyPropertyInfo; - - /// - /// Returns true if the is a value tuple type. - /// - public static bool IsValueTuple(this Type type) - { - return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); - } - - /// - /// Returns true if the given is of type TupleElementNameAttribute. - /// - /// - /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically that - /// the given is TupleElementNameAttribute. - /// - public static bool IsTupleElementNameAttribute(this Attribute attribute) - { - var attributeType = attribute.GetType(); - return attributeType.Namespace == "System.Runtime.CompilerServices" && - attributeType.Name == "TupleElementNamesAttribute"; - } - - /// - /// Returns 'TransformNames' property value from a given . - /// - /// - /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection - /// instead of casting the attribute to a specific type. - /// - public static IList? GetTransformerNames(this Attribute attribute) - { - Debug.Assert(attribute.IsTupleElementNameAttribute()); - - var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); - return propertyInfo?.GetValue(attribute) as IList; - } - - 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)!); - } + return type.Namespace == "System" && type.Name.Contains("ValueTuple`"); } -} + + /// + /// Returns true if the given is of type TupleElementNameAttribute. + /// + /// + /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically + /// that + /// the given is TupleElementNameAttribute. + /// + public static bool IsTupleElementNameAttribute(this Attribute attribute) + { + var attributeType = attribute.GetType(); + return attributeType.Namespace == "System.Runtime.CompilerServices" && + attributeType.Name == "TupleElementNamesAttribute"; + } + + /// + /// Returns 'TransformNames' property value from a given . + /// + /// + /// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection + /// instead of casting the attribute to a specific type. + /// + public static IList? GetTransformerNames(this Attribute attribute) + { + Debug.Assert(attribute.IsTupleElementNameAttribute()); + + var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType()); + return propertyInfo.GetValue(attribute) as IList; + } + + 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)!); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/ResolvedMethod.cs b/src/Ben.Demystifier/ResolvedMethod.cs index 1fb4fb8..31108da 100644 --- a/src/Ben.Demystifier/ResolvedMethod.cs +++ b/src/Ben.Demystifier/ResolvedMethod.cs @@ -2,171 +2,142 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Ben.Demystifier.Enumerable; -using System.Reflection; -using System.Text; -namespace Ben.Demystifier +namespace Ben.Demystifier; + +public class ResolvedMethod { - public class ResolvedMethod + public MethodBase? MethodBase { get; set; } + + public Type? DeclaringType { get; set; } + + public bool IsAsync { get; set; } + + public bool IsLambda { get; set; } + + public ResolvedParameter? ReturnParameter { get; set; } + + public string? Name { get; set; } + + public int? Ordinal { get; set; } + + public string? GenericArguments { get; set; } + + public Type[]? ResolvedGenericArguments { get; set; } + + public MethodBase? SubMethodBase { get; set; } + + public string? SubMethod { get; set; } + + public EnumerableIList Parameters { get; set; } + + public EnumerableIList 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 bool IsAsync { get; set; } + public override string ToString() => AppendTo(new StringBuilder()).ToString(); - public bool IsLambda { get; set; } + public StringBuilder AppendTo(StringBuilder builder, bool fullName = true) + { + if (IsAsync) builder.Append("async "); - 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 Parameters { get; set; } - - public EnumerableIList SubMethodParameters { get; set; } - public int RecurseCount { get; internal set; } - - internal bool IsSequentialEquivalent(ResolvedMethod obj) + if (ReturnParameter is not null) { - return - IsAsync == obj.IsAsync && - DeclaringType == obj.DeclaringType && - Name == obj.Name && - IsLambda == obj.IsLambda && - Ordinal == obj.Ordinal && - GenericArguments == obj.GenericArguments && - SubMethod == obj.SubMethod; + ReturnParameter.Append(builder); + builder.Append(' '); } - public override string ToString() => Append(new StringBuilder()).ToString(); - - public StringBuilder Append(StringBuilder builder) - => Append(builder, true); - - public StringBuilder Append(StringBuilder builder, bool fullName) + if (DeclaringType is not null) { - if (IsAsync) + if (Name == ".ctor") { - builder.Append("async "); + if (string.IsNullOrEmpty(SubMethod) && !IsLambda) + builder.Append("new "); + + AppendDeclaringTypeName(builder, fullName); } - - if (ReturnParameter != null) + else if (Name == ".cctor") { - ReturnParameter.Append(builder); - builder.Append(" "); - } - - if (DeclaringType != null) - { - - if (Name == ".ctor") - { - if (string.IsNullOrEmpty(SubMethod) && !IsLambda) - builder.Append("new "); - - AppendDeclaringTypeName(builder, fullName); - } - else if (Name == ".cctor") - { - builder.Append("static "); - AppendDeclaringTypeName(builder, fullName); - } - else - { - AppendDeclaringTypeName(builder, fullName) - .Append(".") - .Append(Name); - } + builder.Append("static "); + AppendDeclaringTypeName(builder, fullName); } else { - builder.Append(Name); + AppendDeclaringTypeName(builder, fullName) + .Append('.') + .Append(Name); } - builder.Append(GenericArguments); + } + else builder.Append(Name); - builder.Append("("); - if (MethodBase != null) + builder.Append(GenericArguments); + + builder.Append('('); + if (MethodBase is not null) + { + var isFirst = true; + foreach (var param in Parameters) + { + if (isFirst) + isFirst = false; + else builder.Append(", "); + param.Append(builder); + } + } + else builder.Append('?'); + + builder.Append(')'); + + if (!string.IsNullOrEmpty(SubMethod) || IsLambda) + { + builder.Append('+'); + builder.Append(SubMethod); + builder.Append('('); + if (SubMethodBase is not null) { var isFirst = true; - foreach(var param in Parameters) + foreach (var param in SubMethodParameters) { if (isFirst) - { isFirst = false; - } - else - { - builder.Append(", "); - } + else builder.Append(", "); param.Append(builder); } } - else - { - builder.Append("?"); - } - builder.Append(")"); + else builder.Append('?'); - if (!string.IsNullOrEmpty(SubMethod) || IsLambda) + builder.Append(')'); + if (IsLambda) { - builder.Append("+"); - builder.Append(SubMethod); - builder.Append("("); - if (SubMethodBase != null) - { - var isFirst = true; - foreach (var param in SubMethodParameters) - { - if (isFirst) - { - isFirst = false; - } - else - { - builder.Append(", "); - } - param.Append(builder); - } - } - else - { - builder.Append("?"); - } - builder.Append(")"); - if (IsLambda) - { - builder.Append(" => { }"); + builder.Append(" => { }"); - if (Ordinal.HasValue) - { - builder.Append(" ["); - builder.Append(Ordinal); - builder.Append("]"); - } + if (Ordinal.HasValue) + { + builder.Append(" ["); + builder.Append(Ordinal); + builder.Append(']'); } } - - if (RecurseCount > 0) - { - builder.Append($" x {RecurseCount + 1:0}"); - } - - return builder; } - private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true) - { - return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: fullName, includeGenericParameterNames: true) : builder; - } + if (RecurseCount > 0) builder.Append($" x {RecurseCount + 1:0}"); + + return builder; } -} + + private StringBuilder AppendDeclaringTypeName(StringBuilder builder, bool fullName = true) + { + return DeclaringType is not null ? builder.AppendTypeDisplayName(DeclaringType, fullName, true) : builder; + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/ResolvedParameter.cs b/src/Ben.Demystifier/ResolvedParameter.cs index 9433c85..7208dfc 100644 --- a/src/Ben.Demystifier/ResolvedParameter.cs +++ b/src/Ben.Demystifier/ResolvedParameter.cs @@ -1,59 +1,49 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Text; +namespace Ben.Demystifier; -namespace Ben.Demystifier +public class ResolvedParameter { - public class ResolvedParameter + public ResolvedParameter(Type resolvedType) { - public string? Name { get; set; } - - 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); - } + ResolvedType = resolvedType; } -} + + 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); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/StringBuilderExtentions.cs b/src/Ben.Demystifier/StringBuilderExtentions.cs index 55d01e2..196dd8c 100644 --- a/src/Ben.Demystifier/StringBuilderExtentions.cs +++ b/src/Ben.Demystifier/StringBuilderExtentions.cs @@ -2,55 +2,42 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Ben.Demystifier.Enumerable; -using System.Text; -namespace Ben.Demystifier +namespace Ben.Demystifier; + +public static class StringBuilderExtentions { - public static class StringBuilderExtentions + public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) { - public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception) + try { - try - { - var stackTrace = new EnhancedStackTrace(exception); + var stackTrace = new EnhancedStackTrace(exception); - builder.Append(exception.GetType()); - if (!string.IsNullOrEmpty(exception.Message)) - { - builder.Append(": ").Append(exception.Message); - } - builder.Append(Environment.NewLine); + builder.Append(exception.GetType()); + if (!string.IsNullOrEmpty(exception.Message)) builder.Append(": ").Append(exception.Message); + builder.Append(Environment.NewLine); - if (stackTrace.FrameCount > 0) - { - stackTrace.Append(builder); - } + if (stackTrace.FrameCount > 0) stackTrace.AppendTo(builder); - if (exception is AggregateException aggEx) - { - foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) - { - builder.AppendInnerException(ex); - } - } + if (exception is AggregateException aggEx) + foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) + builder.AppendInnerException(ex); - if (exception.InnerException != null) - { - builder.AppendInnerException(exception.InnerException); - } - } - catch - { - // Processing exceptions shouldn't throw exceptions; if it fails - } - - return builder; + if (exception.InnerException is not null) builder.AppendInnerException(exception.InnerException); + } + catch + { + // Processing exceptions shouldn't throw exceptions; if it fails } - private static void AppendInnerException(this StringBuilder builder, Exception exception) - => builder.Append(" ---> ") - .AppendDemystified(exception) - .AppendLine() - .Append(" --- End of inner exception stack trace ---"); + return builder; } -} + + private static void AppendInnerException(this StringBuilder builder, Exception exception) + { + builder.Append(" ---> ") + .AppendDemystified(exception) + .AppendLine() + .Append(" --- End of inner exception stack trace ---"); + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs index 8e36e35..2c44768 100644 --- a/src/Ben.Demystifier/TypeNameHelper.cs +++ b/src/Ben.Demystifier/TypeNameHelper.cs @@ -1,218 +1,192 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; -using System.Text; +namespace Ben.Demystifier; -namespace Ben.Demystifier +// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs +public static class TypeNameHelper { - // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs - public static class TypeNameHelper + public static readonly Dictionary BuiltInTypeNames = new() { - public static readonly Dictionary BuiltInTypeNames = new Dictionary - { - { typeof(void), "void" }, - { typeof(bool), "bool" }, - { typeof(byte), "byte" }, - { typeof(char), "char" }, - { typeof(decimal), "decimal" }, - { typeof(double), "double" }, - { typeof(float), "float" }, - { typeof(int), "int" }, - { typeof(long), "long" }, - { typeof(object), "object" }, - { typeof(sbyte), "sbyte" }, - { typeof(short), "short" }, - { typeof(string), "string" }, - { typeof(uint), "uint" }, - { typeof(ulong), "ulong" }, - { typeof(ushort), "ushort" } - }; - - public static readonly Dictionary FSharpTypeNames = new Dictionary - { - { "Unit", "void" }, - { "FSharpOption", "Option" }, - { "FSharpAsync", "Async" }, - { "FSharpOption`1", "Option" }, - { "FSharpAsync`1", "Async" } - }; + { typeof(void), "void" }, + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(long), "long" }, + { typeof(object), "object" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(string), "string" }, + { typeof(uint), "uint" }, + { typeof(ulong), "ulong" }, + { typeof(ushort), "ushort" } + }; - /// - /// Pretty print a type name. - /// - /// The . - /// true to print a fully qualified name. - /// true to include generic parameter names. - /// The pretty printed type name. - public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false) + public static readonly Dictionary FSharpTypeNames = new() + { + { "Unit", "void" }, + { "FSharpOption", "Option" }, + { "FSharpAsync", "Async" }, + { "FSharpOption`1", "Option" }, + { "FSharpAsync`1", "Async" } + }; + + /// + /// Pretty print a type name. + /// + /// The . + /// true to print a fully qualified name. + /// true to include generic parameter names. + /// The pretty printed type name. + 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; + } + + /// + /// Returns a name of given generic type without '`'. + /// + 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(); - 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; - } - - /// - /// Returns a name of given generic type without '`'. - /// - public static string GetTypeNameForGenericType(Type type) - { - if (!type.IsGenericType) + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType is not null) { - throw new ArgumentException("The given type should be generic", nameof(type)); - } - - var genericPartIndex = type.Name.IndexOf('`'); - - return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name; - } - - private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) - { - if (type.IsGenericType) - { - var underlyingType = Nullable.GetUnderlyingType(type); - if (underlyingType != null) - { - ProcessType(builder, underlyingType, options); - builder.Append('?'); - } - else - { - var genericArguments = type.GetGenericArguments(); - ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); - } - } - else if (type.IsArray) - { - ProcessArrayType(builder, type, options); - } - else if (BuiltInTypeNames.TryGetValue(type, out var builtInName)) - { - builder.Append(builtInName); - } - else if (type.Namespace == nameof(System)) - { - builder.Append(type.Name); - } - else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" - && FSharpTypeNames.TryGetValue(type.Name, out builtInName)) - { - builder.Append(builtInName); - } - else if (type.IsGenericParameter) - { - if (options.IncludeGenericParameterNames) - { - builder.Append(type.Name); - } + ProcessType(builder, underlyingType, options); + builder.Append('?'); } else { - builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); + var genericArguments = type.GetGenericArguments(); + ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); } } - - private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) + else if (type.IsArray) { - var innerType = type; - while (innerType.IsArray) - { - if (innerType.GetElementType() is { } inner) - { - innerType = inner; - } - } - - ProcessType(builder, innerType, options); - - while (type.IsArray) - { - builder.Append('['); - builder.Append(',', type.GetArrayRank() - 1); - builder.Append(']'); - if (type.GetElementType() is not { } elementType) - { - break; - } - type = elementType; - } + ProcessArrayType(builder, type, options); } - - private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, DisplayNameOptions options) + else if (BuiltInTypeNames.TryGetValue(type, out var builtInName)) { - var offset = 0; - if (type.IsNested && type.DeclaringType is not null) - { - offset = type.DeclaringType.GetGenericArguments().Length; - } - - if (options.FullName) - { - if (type.IsNested && type.DeclaringType is not null) - { - ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); - builder.Append('+'); - } - else if (!string.IsNullOrEmpty(type.Namespace)) - { - builder.Append(type.Namespace); - builder.Append('.'); - } - } - - var genericPartIndex = type.Name.IndexOf('`'); - if (genericPartIndex <= 0) - { - builder.Append(type.Name); - return; - } - - if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" - && FSharpTypeNames.TryGetValue(type.Name, out var builtInName)) - { - builder.Append(builtInName); - } - else - { - builder.Append(type.Name, 0, genericPartIndex); - } - - builder.Append('<'); - for (var i = offset; i < length; i++) - { - ProcessType(builder, genericArguments[i], options); - if (i + 1 == length) - { - continue; - } - - builder.Append(','); - if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) - { - builder.Append(' '); - } - } - builder.Append('>'); + builder.Append(builtInName); } - - private struct DisplayNameOptions + else if (type.Namespace == nameof(System)) { - public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) - { - FullName = fullName; - IncludeGenericParameterNames = includeGenericParameterNames; - } - - public bool FullName { get; } - - public bool IncludeGenericParameterNames { get; } + builder.Append(type.Name); + } + else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" + && FSharpTypeNames.TryGetValue(type.Name, out builtInName)) + { + builder.Append(builtInName); + } + else if (type.IsGenericParameter) + { + if (options.IncludeGenericParameterNames) builder.Append(type.Name); + } + else + { + builder.Append(options.FullName ? type.FullName ?? type.Name : type.Name); } } -} + + private static void ProcessArrayType(StringBuilder builder, Type type, DisplayNameOptions options) + { + var innerType = type; + while (innerType.IsArray) + if (innerType.GetElementType() is { } inner) + innerType = inner; + + ProcessType(builder, innerType, options); + + while (type.IsArray) + { + builder.Append('['); + builder.Append(',', type.GetArrayRank() - 1); + builder.Append(']'); + if (type.GetElementType() is not { } elementType) break; + type = elementType; + } + } + + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, + DisplayNameOptions options) + { + var offset = 0; + if (type.IsNested && type.DeclaringType is not null) offset = type.DeclaringType.GetGenericArguments().Length; + + if (options.FullName) + { + if (type.IsNested && type.DeclaringType is not null) + { + ProcessGenericType(builder, type.DeclaringType, genericArguments, offset, options); + builder.Append('+'); + } + else if (!string.IsNullOrEmpty(type.Namespace)) + { + builder.Append(type.Namespace); + builder.Append('.'); + } + } + + var genericPartIndex = type.Name.IndexOf('`'); + if (genericPartIndex <= 0) + { + builder.Append(type.Name); + return; + } + + if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" + && FSharpTypeNames.TryGetValue(type.Name, out var builtInName)) + builder.Append(builtInName); + else + builder.Append(type.Name, 0, genericPartIndex); + + builder.Append('<'); + for (var i = offset; i < length; i++) + { + ProcessType(builder, genericArguments[i], options); + if (i + 1 == length) continue; + + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + builder.Append(' '); + } + + builder.Append('>'); + } + + private struct DisplayNameOptions + { + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) + { + FullName = fullName; + IncludeGenericParameterNames = includeGenericParameterNames; + } + + public bool FullName { get; } + + public bool IncludeGenericParameterNames { get; } + } +} \ No newline at end of file diff --git a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs index 26e0daf..5037e26 100644 --- a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs +++ b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs @@ -1,68 +1,58 @@ // Copyright (c) Ben A Adams. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Ben.Demystifier.Internal; -using System.Text; -namespace Ben.Demystifier +namespace Ben.Demystifier; + +public class ValueTupleResolvedParameter : ResolvedParameter { - public class ValueTupleResolvedParameter : ResolvedParameter + public ValueTupleResolvedParameter(Type resolvedType, IList tupleNames) + : base(resolvedType) { - public IList TupleNames { get; } + TupleNames = tupleNames; + } - public ValueTupleResolvedParameter(Type resolvedType, IList tupleNames) - : base(resolvedType) - => TupleNames = tupleNames; + public IList TupleNames { get; } - protected override void AppendTypeName(StringBuilder sb) + protected override void AppendTypeName(StringBuilder sb) + { + if (ResolvedType is not null) { - if (ResolvedType is not null) + if (ResolvedType.IsValueTuple()) { - if (ResolvedType.IsValueTuple()) - { - AppendValueTupleParameterName(sb, ResolvedType); - } - else - { - // Need to unwrap the first generic argument first. - sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType)); - sb.Append("<"); - AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]); - sb.Append(">"); - } + AppendValueTupleParameterName(sb, ResolvedType); } - } - - private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType) - { - sb.Append("("); - var args = parameterType.GetGenericArguments(); - for (var i = 0; i < args.Length; i++) + else { - if (i > 0) - { - sb.Append(", "); - } - - sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true); - - if (i >= TupleNames.Count) - { - continue; - } - - var argName = TupleNames[i]; - if (argName == null) - { - continue; - } - - sb.Append(" "); - sb.Append(argName); + // Need to unwrap the first generic argument first. + sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType)); + sb.Append("<"); + AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]); + sb.Append(">"); } - - sb.Append(")"); } } -} + + private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType) + { + sb.Append('('); + var args = parameterType.GetGenericArguments(); + for (var i = 0; i < args.Length; i++) + { + if (i > 0) sb.Append(", "); + + sb.AppendTypeDisplayName(args[i], false, true); + + if (i >= TupleNames.Count) continue; + + var argName = TupleNames[i]; + if (argName is null) continue; + + sb.Append(' '); + sb.Append(argName); + } + + sb.Append(')'); + } +} \ No newline at end of file