-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Add runtime async tests for catching non-Exception throws #130506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
90839e1
Add runtime async tests for catching non-Exception throws
Copilot cbc221a
Apply remaining changes
Copilot 58878e3
Block async1 divergent case with a comment instead of ActiveIssue
Copilot 050c0e2
Block runtime-wrapped-exception async tests on NativeAOT
Copilot 457430a
Block a test on NativeAOT
Copilot f2a1ccb
Fix Directory.Build.props to only include C# helper for C# projects
Copilot 174a8a2
Use issue #69919 for NativeAOT blocking instead of #131111
Copilot 8d99f6e
Remove accidentally committed reflect_ai.csx
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,5 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.IL"> | ||
| <ItemGroup> | ||
| <!-- The runtime-async methods are written directly in IL, so the C# helper | ||
| attribute injected by the directory props is not applicable here. --> | ||
| <Compile Remove="..\RuntimeAsyncMethodGenerationAttribute.cs" /> | ||
| <Compile Include="$(MSBuildProjectName).il" /> | ||
| </ItemGroup> | ||
| </Project> |
46 changes: 46 additions & 0 deletions
46
src/tests/async/runtime-wrapped-exception/NoWrapThrowers.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Runtime.CompilerServices; | ||
| using System.Threading.Tasks; | ||
|
|
||
| // This assembly opts OUT of wrapping non-Exception throws, in contrast to the | ||
| // C# default (WrapNonExceptionThrows = true) used by the main test assembly and | ||
| // by CoreLib. The async methods below are otherwise identical to the ones in the | ||
| // main test assembly; keeping them in a separate assembly is the only way to | ||
| // exercise the WrapNonExceptionThrows = false configuration, since the setting is | ||
| // assembly-scoped and decided by the frame that catches the exception. | ||
| [assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)] | ||
|
|
||
| public static class NoWrapThrowers | ||
| { | ||
| // Runtime async (async2) throwing a non-Exception after suspending. | ||
| public static async Task ThrowAfterYieldAsync2() | ||
| { | ||
| await Task.Yield(); | ||
| NonExceptionThrower.ThrowNonException(); | ||
| } | ||
|
|
||
| // Compiler state machine (async1) throwing a non-Exception after suspending. | ||
| [RuntimeAsyncMethodGeneration(false)] | ||
| public static async Task ThrowAfterYieldAsync1() | ||
| { | ||
| await Task.Yield(); | ||
| NonExceptionThrower.ThrowNonException(); | ||
| } | ||
|
|
||
| // Runtime async (async2) throwing a non-Exception before suspending. | ||
| public static async Task ThrowBeforeYieldAsync2() | ||
| { | ||
| NonExceptionThrower.ThrowNonException(); | ||
| await Task.Yield(); | ||
| } | ||
|
|
||
| // Compiler state machine (async1) throwing a non-Exception before suspending. | ||
| [RuntimeAsyncMethodGeneration(false)] | ||
| public static async Task ThrowBeforeYieldAsync1() | ||
| { | ||
| NonExceptionThrower.ThrowNonException(); | ||
| await Task.Yield(); | ||
| } | ||
| } |
6 changes: 6 additions & 0 deletions
6
src/tests/async/runtime-wrapped-exception/NoWrapThrowers.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <ItemGroup> | ||
| <Compile Include="NoWrapThrowers.cs" /> | ||
| <ProjectReference Include="NonExceptionThrower.ilproj" /> | ||
| </ItemGroup> | ||
| </Project> |
24 changes: 24 additions & 0 deletions
24
src/tests/async/runtime-wrapped-exception/NonExceptionThrower.il
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| // Helper that throws an object which does not derive from System.Exception. | ||
| // This is only expressible in IL, so it lives in its own IL assembly. The thrown | ||
| // object (a string) is what a catch handler in a WrapNonExceptionThrows=true | ||
| // assembly observes as RuntimeWrappedException.WrappedException. | ||
|
|
||
| .assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A) .ver 4:0:0:0 } | ||
|
|
||
| .assembly NonExceptionThrower | ||
| { | ||
| } | ||
|
|
||
| .class public auto ansi abstract sealed beforefieldinit NonExceptionThrower | ||
| extends [System.Runtime]System.Object | ||
| { | ||
| .method public hidebysig static void ThrowNonException() cil managed noinlining | ||
| { | ||
| .maxstack 8 | ||
| ldstr "A non-Exception object thrown from IL" | ||
| throw | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
src/tests/async/runtime-wrapped-exception/NonExceptionThrower.ilproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.IL"> | ||
| <PropertyGroup> | ||
| <OutputType>Library</OutputType> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="NonExceptionThrower.il" /> | ||
| </ItemGroup> | ||
| </Project> |
125 changes: 125 additions & 0 deletions
125
src/tests/async/runtime-wrapped-exception/runtime-wrapped-exception.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| // Regression tests for https://github.com/dotnet/runtime/issues/123194. | ||
| // | ||
| // IL allows throwing an object that does not derive from System.Exception. When | ||
| // such an object propagates into a catch handler, it is (or is not) wrapped in a | ||
| // RuntimeWrappedException depending on the RuntimeCompatibilityAttribute | ||
| // WrapNonExceptionThrows setting of the assembly that owns the catch handler. | ||
| // | ||
| // The goal of runtime async (async2) is to match the traditional compiler | ||
| // generated state machine (async1) behavior. These tests exercise both async | ||
| // forms (via [RuntimeAsyncMethodGeneration]) throwing a non-Exception object, | ||
| // once from an assembly with WrapNonExceptionThrows = true (this assembly, which | ||
| // matches the C# default and CoreLib) and once from an assembly with | ||
| // WrapNonExceptionThrows = false (NoWrapThrowers), for throws both before and | ||
| // after a suspension point. | ||
| public class RuntimeAsyncNonExceptionThrows | ||
| { | ||
| private const string ThrownObject = "A non-Exception object thrown from IL"; | ||
|
|
||
| // WrapNonExceptionThrows = true (this assembly): the non-Exception is observed | ||
| // as a RuntimeWrappedException by the awaiting caller for both async forms. | ||
|
|
||
| [Fact] | ||
| public static void CatchAfterYield_Async2() | ||
| => AssertNonExceptionWrapped(ThrowAfterYieldAsync2); | ||
|
|
||
| [Fact] | ||
| public static void CatchAfterYield_Async1() | ||
| => AssertNonExceptionWrapped(ThrowAfterYieldAsync1); | ||
|
|
||
| [Fact] | ||
| public static void CatchBeforeYield_Async2() | ||
| => AssertNonExceptionWrapped(ThrowBeforeYieldAsync2); | ||
|
|
||
| [Fact] | ||
| public static void CatchBeforeYield_Async1() | ||
| => AssertNonExceptionWrapped(ThrowBeforeYieldAsync1); | ||
|
|
||
| // WrapNonExceptionThrows = false (NoWrapThrowers assembly). | ||
|
|
||
| [Fact] | ||
| public static void CatchAfterYield_NoWrap_Async2() | ||
| => AssertNonExceptionWrapped(NoWrapThrowers.ThrowAfterYieldAsync2); | ||
|
|
||
| // async2 and async1 behave differently for a non-Exception thrown after a | ||
| // suspension point in a WrapNonExceptionThrows = false assembly: | ||
| // * async2 faults the returned Task with a RuntimeWrappedException, which the | ||
| // caller observes just like the wrap=true case above. | ||
| // * async1 lets the raw non-Exception escape the resumed state machine onto | ||
| // the thread pool where it becomes an unhandled exception and crashes the | ||
| // process, so it can never be observed by the caller. | ||
| // async1 behavior here is terminal to the process, thus we will not test it: | ||
| // | ||
| // [Fact] | ||
| // public static void CatchAfterYield_NoWrap_Async1() | ||
| // => AssertNonExceptionWrapped(NoWrapThrowers.ThrowAfterYieldAsync1); | ||
|
VSadov marked this conversation as resolved.
|
||
|
|
||
|
VSadov marked this conversation as resolved.
|
||
| [Fact] | ||
| public static void CatchBeforeYield_NoWrap_Async2() | ||
| => AssertNonExceptionWrapped(NoWrapThrowers.ThrowBeforeYieldAsync2); | ||
|
|
||
| [Fact] | ||
| public static void CatchBeforeYield_NoWrap_Async1() | ||
| => AssertNonExceptionWrapped(NoWrapThrowers.ThrowBeforeYieldAsync1); | ||
|
|
||
| private static void AssertNonExceptionWrapped(Func<Task> thrower) | ||
| { | ||
| object wrapped = ObserveNonException(thrower).GetAwaiter().GetResult(); | ||
| Assert.Equal(ThrownObject, wrapped); | ||
| } | ||
|
|
||
| // Awaits the throwing method and returns the wrapped non-Exception object. | ||
| // The catch handler lives in this WrapNonExceptionThrows = true assembly, so a | ||
| // propagating non-Exception is caught as a RuntimeWrappedException. Returns null | ||
| // if nothing was thrown so the assertion above fails with a clear diff. | ||
| private static async Task<object> ObserveNonException(Func<Task> thrower) | ||
| { | ||
| try | ||
| { | ||
| await thrower(); | ||
| return null; | ||
| } | ||
| catch (RuntimeWrappedException e) | ||
| { | ||
| return e.WrappedException; | ||
| } | ||
| } | ||
|
|
||
| // Runtime async (async2) throwing a non-Exception after suspending. | ||
| public static async Task ThrowAfterYieldAsync2() | ||
| { | ||
| await Task.Yield(); | ||
| NonExceptionThrower.ThrowNonException(); | ||
| } | ||
|
|
||
| // Compiler state machine (async1) throwing a non-Exception after suspending. | ||
| [RuntimeAsyncMethodGeneration(false)] | ||
| public static async Task ThrowAfterYieldAsync1() | ||
| { | ||
| await Task.Yield(); | ||
| NonExceptionThrower.ThrowNonException(); | ||
| } | ||
|
|
||
| // Runtime async (async2) throwing a non-Exception before suspending. | ||
| public static async Task ThrowBeforeYieldAsync2() | ||
| { | ||
| NonExceptionThrower.ThrowNonException(); | ||
| await Task.Yield(); | ||
| } | ||
|
|
||
| // Compiler state machine (async1) throwing a non-Exception before suspending. | ||
| [RuntimeAsyncMethodGeneration(false)] | ||
| public static async Task ThrowBeforeYieldAsync1() | ||
| { | ||
| NonExceptionThrower.ThrowNonException(); | ||
| await Task.Yield(); | ||
| } | ||
| } | ||
13 changes: 13 additions & 0 deletions
13
src/tests/async/runtime-wrapped-exception/runtime-wrapped-exception.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <!-- On NativeAOT a non-Exception thrown after a suspension point is not surfaced | ||
| as a RuntimeWrappedException; it terminates the process instead of faulting the | ||
| Task. Blocked on NativeAOT pending https://github.com/dotnet/runtime/issues/69919. --> | ||
| <DisableProjectBuild Condition="'$(TestBuildMode)' == 'nativeaot'">true</DisableProjectBuild> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="$(MSBuildProjectName).cs" /> | ||
| <ProjectReference Include="NonExceptionThrower.ilproj" /> | ||
| <ProjectReference Include="NoWrapThrowers.csproj" /> | ||
| </ItemGroup> | ||
| </Project> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.