diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5c919e1 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ + +github: [benaadams] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..03ec545 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml new file mode 100644 index 0000000..0d5b3ea --- /dev/null +++ b/.github/workflows/pull-request.yaml @@ -0,0 +1,41 @@ +name: Demystifier PR Build +on: pull_request + +jobs: + build: + name: "Build for PR" + runs-on: ${{ matrix.os }} + env: + DOTNET_NOLOGO: true + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-18.04, macOS-latest] + config: [Debug, Release] + steps: + - name: Clone source + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install .NET Core SDK 2.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '2.1.x' + + - name: Install .NET Core SDK 3.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '3.1.x' + + - name: Install .NET SDK 5.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '5.0.x' + + - name: Get .NET information + run: dotnet --info + + - name: "Test" + run: dotnet test -c ${{ matrix.config }} + diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 9f08f22..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -image: Visual Studio 2017 - -branches: - only: - - master - -skip_branch_with_pr: true - -skip_tags: true - -skip_commits: - files: - - BUILDING.md - - CONTRIBUTING.md - - ISSUE_TEMPLATE.md - - LICENCE - - README.md - -nuget: - disable_publish_on_pr: true - -build_script: -- ps: .\build.ps1 -target appveyor - -test: off - -deploy: off - -artifacts: -- path: artifacts/build -- path: artifacts/packages -- path: artifacts/test diff --git a/sample/StackTrace/StackTrace.csproj b/sample/StackTrace/StackTrace.csproj index 3e72966..8699af6 100644 --- a/sample/StackTrace/StackTrace.csproj +++ b/sample/StackTrace/StackTrace.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp2.1 diff --git a/src/Ben.Demystifier/Ben.Demystifier.csproj b/src/Ben.Demystifier/Ben.Demystifier.csproj index de26b84..22997fd 100644 --- a/src/Ben.Demystifier/Ben.Demystifier.csproj +++ b/src/Ben.Demystifier/Ben.Demystifier.csproj @@ -11,6 +11,7 @@ git true embedded + true @@ -21,12 +22,13 @@ - + - 1.6.0 + 5.0.0 - + + diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index 17cf67f..03fd069 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -53,7 +53,7 @@ namespace System.Diagnostics var method = frame.GetMethod(); // Always show last stackFrame - if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) + if (method != null && !ShowInStackTrace(method) && i < stackFrames.Length - 1) { continue; } @@ -62,7 +62,7 @@ namespace System.Diagnostics var row = frame.GetFileLineNumber(); var column = frame.GetFileColumnNumber(); var ilOffset = frame.GetILOffset(); - if (string.IsNullOrEmpty(fileName) && ilOffset >= 0) + if (method != 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 @@ -636,6 +636,18 @@ namespace System.Diagnostics { Debug.Assert(method != null); + // Since .NET 5: + // https://github.com/dotnet/runtime/blob/7c18d4d6488dab82124d475d1199def01d1d252c/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L348-L361 + if ((method.MethodImplementationFlags & MethodImplAttributes.AggressiveInlining) != 0) + { + // Aggressive Inlines won't normally show in the StackTrace; however for Tier0 Jit and + // cross-assembly AoT/R2R these inlines will be blocked until Tier1 Jit re-Jits + // them when they will inline. We don't show them in the StackTrace to bring consistency + // between this first-pass asm and fully optimized asm. + return false; + } + + // Since .NET Core 2: if (StackTraceHiddenAttributeType != null) { // Don't show any methods marked with the StackTraceHiddenAttribute @@ -653,6 +665,17 @@ namespace System.Diagnostics return true; } + // Since .NET Core 2: + if (StackTraceHiddenAttributeType != null) + { + // Don't show any methods marked with the StackTraceHiddenAttribute + // https://github.com/dotnet/coreclr/pull/14652 + if (IsStackTraceHidden(type)) + { + return false; + } + } + if (type == typeof(Task<>) && method.Name == "InnerInvoke") { return false; @@ -661,8 +684,13 @@ namespace System.Diagnostics { return false; } - if (type == typeof(Task)) + if (type == typeof(Task) || type.DeclaringType == typeof(Task)) { + if (method.Name.Contains(".cctor")) + { + return false; + } + switch (method.Name) { case "ExecuteWithThreadLocal": @@ -670,15 +698,24 @@ namespace System.Diagnostics case "ExecutionContextCallback": case "ExecuteEntry": case "InnerInvoke": + case "ExecuteEntryUnsafe": + case "ExecuteFromThreadPool": + case "s_ecCallback": return false; } } if (type == typeof(ExecutionContext)) { + if (method.Name.Contains(".cctor")) + { + return false; + } + switch (method.Name) { case "RunInternal": case "Run": + case "RunFromThreadPoolDispatchLoop": return false; } } @@ -706,44 +743,33 @@ namespace System.Diagnostics } } - if (StackTraceHiddenAttributeType != null) + // Fallbacks for runtime pre-StackTraceHiddenAttribute + if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") { - // Don't show any types marked with the StackTraceHiddenAttribute - // https://github.com/dotnet/coreclr/pull/14652 - if (IsStackTraceHidden(type)) + return false; + } + + if (type == typeof(TaskAwaiter) || + type == typeof(TaskAwaiter<>) || + type == typeof(ValueTaskAwaiter) || + type == typeof(ValueTaskAwaiter<>) || + type == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter) || + type == typeof(ConfiguredValueTaskAwaitable<>.ConfiguredValueTaskAwaiter) || + type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || + type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) + { + switch (method.Name) { - return false; + case "HandleNonSuccessAndDebuggerNotification": + case "ThrowForNonSuccess": + case "ValidateEnd": + case "GetResult": + return false; } } - else + else if (type.FullName == "System.ThrowHelper") { - // Fallbacks for runtime pre-StackTraceHiddenAttribute - if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") - { - return false; - } - else if (type == typeof(TaskAwaiter) || - type == typeof(TaskAwaiter<>) || - type == typeof(ValueTaskAwaiter) || - type == typeof(ValueTaskAwaiter<>) || - type == typeof(ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter) || - type == typeof(ConfiguredValueTaskAwaitable<>.ConfiguredValueTaskAwaiter) || - type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || - type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) - { - switch (method.Name) - { - case "HandleNonSuccessAndDebuggerNotification": - case "ThrowForNonSuccess": - case "ValidateEnd": - case "GetResult": - return false; - } - } - else if (type.FullName == "System.ThrowHelper") - { - return false; - } + return false; } return true; diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs index fe4bc8b..3450b63 100644 --- a/src/Ben.Demystifier/TypeNameHelper.cs +++ b/src/Ben.Demystifier/TypeNameHelper.cs @@ -69,9 +69,8 @@ namespace System.Diagnostics } var genericPartIndex = type.Name.IndexOf('`'); - Debug.Assert(genericPartIndex >= 0); - return type.Name.Substring(0, genericPartIndex); + return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name; } private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options) diff --git a/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj b/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj index b208351..6c761d2 100644 --- a/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj +++ b/test/Ben.Demystifier.Benchmarks/Ben.Demystifier.Benchmarks.csproj @@ -1,11 +1,11 @@ - + - netcoreapp2.1;netcoreapp2.0;net462 + netcoreapp2.1;netcoreapp3.1;net462;net5.0 Release Exe - + \ No newline at end of file diff --git a/test/Ben.Demystifier.Benchmarks/Exceptions.cs b/test/Ben.Demystifier.Benchmarks/Exceptions.cs index 12417a9..c41396e 100644 --- a/test/Ben.Demystifier.Benchmarks/Exceptions.cs +++ b/test/Ben.Demystifier.Benchmarks/Exceptions.cs @@ -2,11 +2,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Jobs; namespace Ben.Demystifier.Benchmarks { - [ClrJob, CoreJob] + [SimpleJob(RuntimeMoniker.Net48)] + [SimpleJob(RuntimeMoniker.NetCoreApp21)] + [SimpleJob(RuntimeMoniker.NetCoreApp31)] + [SimpleJob(RuntimeMoniker.NetCoreApp50)] [Config(typeof(Config))] public class ExceptionTests { diff --git a/test/Ben.Demystifier.Benchmarks/Program.cs b/test/Ben.Demystifier.Benchmarks/Program.cs index 4b8a7e4..ac893e7 100644 --- a/test/Ben.Demystifier.Benchmarks/Program.cs +++ b/test/Ben.Demystifier.Benchmarks/Program.cs @@ -43,6 +43,6 @@ namespace Ben.Demystifier.Benchmarks internal class Config : ManualConfig { - public Config() => Add(new MemoryDiagnoser()); + public Config() => AddDiagnoser(MemoryDiagnoser.Default); } } diff --git a/test/Ben.Demystifier.Test/AggregateException.cs b/test/Ben.Demystifier.Test/AggregateException.cs index 3c0034a..59fe292 100644 --- a/test/Ben.Demystifier.Test/AggregateException.cs +++ b/test/Ben.Demystifier.Test/AggregateException.cs @@ -37,14 +37,28 @@ namespace Ben.Demystifier.Test var trace = string.Join("", stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) // Remove items that vary between test runners .Where(s => - s != " at Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()+() => { }" && - !s.Contains("System.Threading.Tasks.Task.WaitAll") + (s != " at Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()+() => { }" && + !s.Contains("System.Threading.Tasks.Task.WaitAll")) ) .Skip(1) .ToArray()) // Remove Full framework back arrow .Replace("<---", ""); - +#if NET5_0 || NETCOREAPP3_1 + var expected = string.Join("", new[] { + " ---> System.ArgumentException: Value does not fall within the expected range.", + " at async Task Ben.Demystifier.Test.AggregateException.Throw1()", + " at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }", + " --- End of inner exception stack trace ---", + " at void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()", + " ---> (Inner Exception #1) System.NullReferenceException: Object reference not set to an instance of an object.", + " at async Task Ben.Demystifier.Test.AggregateException.Throw2()", + " at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }", + " ---> (Inner Exception #2) System.InvalidOperationException: Operation is not valid due to the current state of the object.", + " at async Task Ben.Demystifier.Test.AggregateException.Throw3()", + " at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }" + }); +#else var expected = string.Join("", new[] { " at async Task Ben.Demystifier.Test.AggregateException.Throw1()", " at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }", @@ -59,7 +73,7 @@ namespace Ben.Demystifier.Test "---> (Inner Exception #2) System.InvalidOperationException: Operation is not valid due to the current state of the object.", " at async Task Ben.Demystifier.Test.AggregateException.Throw3()", " at async void Ben.Demystifier.Test.AggregateException.DemystifiesAggregateExceptions()+(?) => { }"}); - +#endif Assert.Equal(expected, trace); } diff --git a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj index bf3aa90..66837aa 100644 --- a/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj +++ b/test/Ben.Demystifier.Test/Ben.Demystifier.Test.csproj @@ -1,16 +1,18 @@ - netcoreapp2.1;netcoreapp2.0;net46 + netcoreapp2.1;netcoreapp3.1;net46;net5.0 + netcoreapp2.1;netcoreapp3.1;net5.0 true ..\..\src\Ben.Demystifier\key.snk false - - - + + + + diff --git a/test/Ben.Demystifier.Test/DynamicCompilation.cs b/test/Ben.Demystifier.Test/DynamicCompilation.cs index 3731011..e9a7364 100644 --- a/test/Ben.Demystifier.Test/DynamicCompilation.cs +++ b/test/Ben.Demystifier.Test/DynamicCompilation.cs @@ -44,6 +44,7 @@ namespace Ben.Demystifier.Test s != " at void System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state)" && s != " at Task Ben.Demystifier.Test.DynamicCompilation.DoesNotPreventStackTrace()+() => { }" ) + .Select(s => Regex.Replace(s, "lambda_method[0-9]+\\(", "lambda_method(")) .ToArray(); Assert.Equal( diff --git a/test/Ben.Demystifier.Test/NonThrownException.cs b/test/Ben.Demystifier.Test/NonThrownException.cs index ef5abf2..ef99cf2 100644 --- a/test/Ben.Demystifier.Test/NonThrownException.cs +++ b/test/Ben.Demystifier.Test/NonThrownException.cs @@ -31,13 +31,24 @@ namespace Ben.Demystifier.Test stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace); var trace = stackTrace.Split(new[]{Environment.NewLine}, StringSplitOptions.None); +#if NETCOREAPP3_1 || NET5_0 Assert.Equal( new[] { - "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", + "System.Exception: Exception of type 'System.Exception' was thrown.", + " ---> System.Exception: Exception of type 'System.Exception' was thrown.", " at Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }", " at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()", " --- End of inner exception stack trace ---"}, trace); +#else + Assert.Equal( + new[] { + "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", + " at Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }", + " at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()", + " --- End of inner exception stack trace ---"}, + trace); +#endif // Act try @@ -54,6 +65,18 @@ namespace Ben.Demystifier.Test stackTrace = LineEndingsHelper.RemoveLineEndings(stackTrace); trace = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None); +#if NETCOREAPP3_1 || NET5_0 + Assert.Equal( + new[] { + "System.Exception: Exception of type 'System.Exception' was thrown.", + " ---> System.Exception: Exception of type 'System.Exception' was thrown.", + " at Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()+() => { }", + " at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()", + " --- End of inner exception stack trace ---", + " at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()" + }, + trace); +#else Assert.Equal( new[] { "System.Exception: Exception of type 'System.Exception' was thrown. ---> System.Exception: Exception of type 'System.Exception' was thrown.", @@ -63,6 +86,7 @@ namespace Ben.Demystifier.Test " at async Task Ben.Demystifier.Test.NonThrownException.DoesNotPreventThrowStackTrace()" }, trace); +#endif } [Fact] diff --git a/test/Ben.Demystifier.Test/TypeNameTests.cs b/test/Ben.Demystifier.Test/TypeNameTests.cs new file mode 100644 index 0000000..837e8a1 --- /dev/null +++ b/test/Ben.Demystifier.Test/TypeNameTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics; +using Xunit; + +namespace Ben.Demystifier.Test +{ + public class TypeNameTests + { + [Fact] + public void NestedGenericTypes() + { + try + { + Throw(new Generic<(int, string)>.Nested()); + } + catch (Exception ex) + { + var text = ex.ToStringDemystified(); + } + } + + public void Throw(Generic<(int a, string b)>.Nested nested) + { + throw null; + } + } + + public class Generic { public struct Nested { } } +} \ No newline at end of file diff --git a/version.json b/version.json index 19a4255..a50dbf4 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "0.1.4", + "version": "0.2.0", "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master "^refs/heads/dev$", // we release out of develop