diff --git a/src/coreclr/vm/commodule.cpp b/src/coreclr/vm/commodule.cpp index ad5fea4a2b81bc..933a4d15bb8d8e 100644 --- a/src/coreclr/vm/commodule.cpp +++ b/src/coreclr/vm/commodule.cpp @@ -295,19 +295,10 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH if (!pMeth) COMPlusThrow(kArgumentNullException); - // Otherwise, we want to return memberref token. - if (pMeth->IsArray()) - { - _ASSERTE(!"Should not have come here!"); - COMPlusThrow(kNotSupportedException); - } - - // TODO: (async) revisit and examine if this needs to be supported somehow - if (pMeth->IsAsyncVariantMethod()) - { - _ASSERTE(!"Async variants should be hidden from reflection."); - COMPlusThrow(kNotSupportedException); - } + // Should not have come here. + _ASSERTE(!pMeth->IsArray()); + // Async variants should be hidden from reflection. + _ASSERTE(!pMeth->IsAsyncVariantMethod()); if ((pMeth->GetMethodTable()->GetModule() == pModule)) { diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index cd76192d29db70..3cb83744e9e03b 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1863,8 +1863,7 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_StripMethodInstantiation(MethodDes // async variants for task-returning methods // // For {task-returning, async} variants Reflection hands out only the task-returning variant. -// the async varinat is an implementation detail that conceptually does not exist. -// TODO: (async) the filtering may not cover all scenarios. Review and add tests. +// the async variant is an implementation detail that conceptually does not exist. // // For generic methods we always hand out an instantiating stub except for a generic method definition // For non-generic methods on generic types we need an instantiating stub if it's one of the following diff --git a/src/tests/async/reflection/reflection-simple.cs b/src/tests/async/reflection/reflection-simple.cs deleted file mode 100644 index 853f1264946349..00000000000000 --- a/src/tests/async/reflection/reflection-simple.cs +++ /dev/null @@ -1,245 +0,0 @@ -// 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.Linq.Expressions; -using System.Reflection; -using System.Reflection.Emit; -using System.Threading.Tasks; -using Xunit; - -public class Async2Reflection -{ - [Fact] - public static void MethodInfo_Invoke_TaskReturning() - { - var mi = typeof(Async2Reflection).GetMethod("Foo", BindingFlags.Static | BindingFlags.NonPublic)!; - Task r = (Task)mi.Invoke(null, null)!; - - int barResult; - if (TestLibrary.Utilities.IsNativeAot) - { - mi = typeof(Async2Reflection).GetMethod("Bar", BindingFlags.Instance | BindingFlags.NonPublic)!; - barResult = ((Task)mi.Invoke(new Async2Reflection(), null)!).Result; - } - else - { - dynamic d = new Async2Reflection(); - barResult = d.Bar().Result; - } - - Assert.Equal(100, (int)(r.Result + barResult)); - } - - [Fact] - public static void MethodInfo_Invoke_AsyncHelper() - { - var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!; - Assert.NotNull(mi); - Assert.Throws(() => mi.Invoke(null, new object[] { FooTask() })); - - // Sadly the following does not throw and results in UB - // We cannot completely prevent putting a token of an Async method into IL stream. - // CONSIDER: perhaps JIT could throw? - // - // dynamic d = FooTask(); - // System.Runtime.CompilerServices.AsyncHelpers.Await(d); - } - - private static async Task Foo() - { - await Task.Yield(); - return 90; - } - - private static async Task FooTask() - { - await Task.Yield(); - } - - private async Task Bar() - { - await Task.Yield(); - return 10; - } - -[Fact] - public static void AwaitTaskReturningExpressionLambda() - { - var expr1 = (Expression>>)(() => Task.FromResult(42)); - var del = expr1.Compile(); - Assert.Equal(42, del().Result); - - AwaitF(42, del).GetAwaiter().GetResult(); - } - - static async Task AwaitF(T expected, Func> f) - { - var res = await f.Invoke(); - Assert.Equal(expected, res); - } - - public interface IExample - { - Task TaskReturning(); - T TReturning(); - } - - public class ExampleClass : IExample - { - public Task TaskReturning() - { - return null; - } - - public Task TReturning() - { - return null; - } - } - - public struct ExampleStruct : IExample - { - public Task TaskReturning() - { - return null; - } - - public Task TReturning() - { - return null; - } - } - - [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/89157", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] - public static void GetInterfaceMap() - { - Type interfaceType = typeof(IExample); - Type classType = typeof(ExampleClass); - - InterfaceMapping map = classType.GetInterfaceMap(interfaceType); - - Assert.Equal(2, map.InterfaceMethods.Length); - Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()", - $"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}"); - - Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()", - $"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}"); - - Type structType = typeof(ExampleStruct); - - map = structType.GetInterfaceMap(interfaceType); - Assert.Equal(2, map.InterfaceMethods.Length); - Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()", - $"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}"); - - Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()", - $"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}"); - } - - [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsReflectionEmitSupported))] - public static void TypeBuilder_DefineMethod() - { - // we will be compiling a dynamic vesion of this method - // - // public async static Task StaticMethod(Task arg) - // { - // await arg; - // } - - // Define a dynamic assembly and module - AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); - - // Define a type - TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public); - - // Define a method - MethodBuilder methodBuilder = typeBuilder.DefineMethod( - "DynamicMethod", - MethodAttributes.Public | MethodAttributes.Static, - typeof(Task), - new Type[] { typeof(Task) }); - - // Set `MethodImpl.Async` flag - methodBuilder.SetImplementationFlags(MethodImplAttributes.Async); - - // { - // Await(arg_0); - // ret; - // } - ILGenerator ilGenerator = methodBuilder.GetILGenerator(); - ilGenerator.Emit(OpCodes.Ldarg_0); - var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!; - ilGenerator.EmitCall(OpCodes.Call, mi, new Type[] { typeof(Task) }); - ilGenerator.Emit(OpCodes.Ret); - - // Create the type and invoke the method - Type dynamicType = typeBuilder.CreateType(); - MethodInfo dynamicMethod = dynamicType.GetMethod("DynamicMethod"); - var del = dynamicMethod.CreateDelegate>(); - - // the following should not crash - del(Task.CompletedTask); - del(FooTask()); - } - - public class PrivateAsync1 - { - public static int s; - private static async Task a_task1(int i) - { - s++; - if (i == 0) - { - await Task.Yield(); - return default; - } - - return await Accessors2.accessor(null, i - 1); - } - } - - public class PrivateAsync2 - { - public static int s; - private static async Task a_task2(int i) - { - s++; - if (i == 0) - { - await Task.Yield(); - return default; - } - - return await Accessors1.accessor(null, i - 1); - } - } - - public class Accessors1 - { - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task1")] - public extern static Task accessor(PrivateAsync1 o, int i); - } - - public class Accessors2 - { - [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task2")] - public extern static Task accessor(PrivateAsync2 o, int i); - } - - [Fact] - public static void UnsafeAccessors() - { - Accessors2.accessor(null, 7).GetAwaiter().GetResult(); - Assert.Equal(4, PrivateAsync1.s); - Assert.Equal(4, PrivateAsync2.s); - - Accessors1.accessor(null, 7).GetAwaiter().GetResult(); - Assert.Equal(8, PrivateAsync1.s); - Assert.Equal(8, PrivateAsync2.s); - } -} diff --git a/src/tests/async/reflection/reflection.cs b/src/tests/async/reflection/reflection.cs new file mode 100644 index 00000000000000..f4e0fd702f6535 --- /dev/null +++ b/src/tests/async/reflection/reflection.cs @@ -0,0 +1,452 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading.Tasks; +using Xunit; + +public class Async2Reflection +{ + [Fact] + public static void MethodInfo_Invoke_TaskReturning() + { + var mi = typeof(Async2Reflection).GetMethod("Foo", BindingFlags.Static | BindingFlags.NonPublic)!; + Task r = (Task)mi.Invoke(null, null)!; + + int barResult; + if (TestLibrary.Utilities.IsNativeAot) + { + mi = typeof(Async2Reflection).GetMethod("Bar", BindingFlags.Instance | BindingFlags.NonPublic)!; + barResult = ((Task)mi.Invoke(new Async2Reflection(), null)!).Result; + } + else + { + dynamic d = new Async2Reflection(); + barResult = d.Bar().Result; + } + + Assert.Equal(100, (int)(r.Result + barResult)); + } + + [Fact] + public static void MethodInfo_Invoke_AsyncHelper() + { + var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!; + Assert.NotNull(mi); + Assert.Throws(() => mi.Invoke(null, new object[] { FooTask() })); + + // Sadly the following does not throw and results in UB + // We cannot completely prevent putting a token of an Async method into IL stream. + // CONSIDER: perhaps JIT could throw? + // + // dynamic d = FooTask(); + // System.Runtime.CompilerServices.AsyncHelpers.Await(d); + } + + private static async Task Foo() + { + await Task.Yield(); + return 90; + } + + private static async Task FooTask() + { + await Task.Yield(); + } + + private async Task Bar() + { + await Task.Yield(); + return 10; + } + + [Fact] + public static void AwaitTaskReturningExpressionLambda() + { + var expr1 = (Expression>>)(() => Task.FromResult(42)); + var del = expr1.Compile(); + Assert.Equal(42, del().Result); + + AwaitF(42, del).GetAwaiter().GetResult(); + } + + static async Task AwaitF(T expected, Func> f) + { + var res = await f.Invoke(); + Assert.Equal(expected, res); + } + + public interface IExample + { + Task TaskReturning(); + T TReturning(); + } + + public class ExampleClass : IExample + { + public Task TaskReturning() + { + return null; + } + + public Task TReturning() + { + return null; + } + } + + public struct ExampleStruct : IExample + { + public Task TaskReturning() + { + return null; + } + + public Task TReturning() + { + return null; + } + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/89157", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] + public static void GetInterfaceMap() + { + Type interfaceType = typeof(IExample); + Type classType = typeof(ExampleClass); + + InterfaceMapping map = classType.GetInterfaceMap(interfaceType); + + Assert.Equal(2, map.InterfaceMethods.Length); + Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()", + $"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}"); + + Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()", + $"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}"); + + Type structType = typeof(ExampleStruct); + + map = structType.GetInterfaceMap(interfaceType); + Assert.Equal(2, map.InterfaceMethods.Length); + Assert.Equal("System.Threading.Tasks.Task TaskReturning() --> System.Threading.Tasks.Task TaskReturning()", + $"{map.InterfaceMethods[0]?.ToString()} --> {map.TargetMethods[0]?.ToString()}"); + + Assert.Equal("System.Threading.Tasks.Task TReturning() --> System.Threading.Tasks.Task TReturning()", + $"{map.InterfaceMethods[1]?.ToString()} --> {map.TargetMethods[1]?.ToString()}"); + } + + [ConditionalFact(typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsReflectionEmitSupported))] + public static void TypeBuilder_DefineMethod() + { + // we will be compiling a dynamic vesion of this method + // + // public async static Task StaticMethod(Task arg) + // { + // await arg; + // } + + // Define a dynamic assembly and module + AssemblyName assemblyName = new AssemblyName("DynamicAssembly"); + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); + + // Define a type + TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public); + + // Define a method + MethodBuilder methodBuilder = typeBuilder.DefineMethod( + "DynamicMethod", + MethodAttributes.Public | MethodAttributes.Static, + typeof(Task), + new Type[] { typeof(Task) }); + + // Set `MethodImpl.Async` flag + methodBuilder.SetImplementationFlags(MethodImplAttributes.Async); + + // { + // Await(arg_0); + // ret; + // } + ILGenerator ilGenerator = methodBuilder.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ldarg_0); + var mi = typeof(System.Runtime.CompilerServices.AsyncHelpers).GetMethod("Await", BindingFlags.Static | BindingFlags.Public, new Type[] { typeof(Task) })!; + ilGenerator.EmitCall(OpCodes.Call, mi, new Type[] { typeof(Task) }); + ilGenerator.Emit(OpCodes.Ret); + + // Create the type and invoke the method + Type dynamicType = typeBuilder.CreateType(); + MethodInfo dynamicMethod = dynamicType.GetMethod("DynamicMethod"); + var del = dynamicMethod.CreateDelegate>(); + + // the following should not crash + del(Task.CompletedTask); + del(FooTask()); + } + + public class PrivateAsync1 + { + public static int s; + private static async Task a_task1(int i) + { + s++; + if (i == 0) + { + await Task.Yield(); + return default; + } + + return await Accessors2.accessor(null, i - 1); + } + } + + public class PrivateAsync2 + { + public static int s; + private static async Task a_task2(int i) + { + s++; + if (i == 0) + { + await Task.Yield(); + return default; + } + + return await Accessors1.accessor(null, i - 1); + } + } + + public class Accessors1 + { + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task1")] + public extern static Task accessor(PrivateAsync1 o, int i); + } + + public class Accessors2 + { + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "a_task2")] + public extern static Task accessor(PrivateAsync2 o, int i); + } + + [Fact] + public static void UnsafeAccessors() + { + Accessors2.accessor(null, 7).GetAwaiter().GetResult(); + Assert.Equal(4, PrivateAsync1.s); + Assert.Equal(4, PrivateAsync2.s); + + Accessors1.accessor(null, 7).GetAwaiter().GetResult(); + Assert.Equal(8, PrivateAsync1.s); + Assert.Equal(8, PrivateAsync2.s); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/122517", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] + public static void CurrentMethod() + { + // Note: async1 leaks implementation details here and returns "Void MoveNext()" + Assert.Equal("System.Threading.Tasks.Task`1[System.String] GetCurrentMethodAsync()", GetCurrentMethodAsync().Result); + Assert.Equal("System.Threading.Tasks.Task`1[System.String] GetCurrentMethodAsync()", GetCurrentMethodAwait().Result); + + Assert.Equal("System.Threading.Tasks.Task`1[System.String] GetCurrentMethodTask()", GetCurrentMethodTask().Result); + Assert.Equal("System.Threading.Tasks.Task`1[System.String] GetCurrentMethodTask()", GetCurrentMethodAwaitTask().Result); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task GetCurrentMethodAsync() + { + await Task.Yield(); + MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod()!; + return mi.ToString()!; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task GetCurrentMethodAwait() + { + return await GetCurrentMethodAsync(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task GetCurrentMethodTask() + { + MethodInfo mi = (MethodInfo)MethodBase.GetCurrentMethod()!; + return Task.FromResult(mi.ToString()!); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task GetCurrentMethodAwaitTask() + { + return await GetCurrentMethodTask(); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + public static void FromStack(int level) + { + // StackFrame.GetMethod() is not supported on NativeAOT + if (TestLibrary.Utilities.IsNativeAot) + { + return; + } + + if (level == 0) + { + // Note: async1 leaks implementation details here and returns "Void MoveNext()" + Assert.Equal("System.Threading.Tasks.Task`1[System.String] FromStackAsync(Int32)", FromStackAsync(0).Result); + Assert.Equal("System.Threading.Tasks.Task`1[System.String] FromStackAsync(Int32)", FromStackAwait(0).Result); + + Assert.Equal("System.Threading.Tasks.Task`1[System.String] FromStackTask(Int32)", FromStackTask(0).Result); + Assert.Equal("System.Threading.Tasks.Task`1[System.String] FromStackTask(Int32)", FromStackAwaitTask(0).Result); + } + else + { + // Note: we go through suspend/resume, that is why we see dispatcher as the caller. + // we do not see the resume stub though. + Assert.Equal("Void DispatchContinuations()", FromStackAsync(1).Result); + Assert.Equal("Void DispatchContinuations()", FromStackAwait(1).Result); + + Assert.Equal("Void FromStack(Int32)", FromStackTask(1).Result); + // Note: we do not go through suspend/resume, that is why we see the actual caller. + // we do not see the async->Task thunk though. + Assert.Equal("System.Threading.Tasks.Task`1[System.String] FromStackAwaitTask(Int32)", FromStackAwaitTask(1).Result); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackAsync(int level) + { + await Task.Yield(); + StackFrame stackFrame = new StackFrame(level); + MethodInfo mi = (MethodInfo)stackFrame.GetMethod(); + return mi.ToString()!; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackAwait(int level) + { + return await FromStackAsync(level); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task FromStackTask(int level) + { + StackFrame stackFrame = new StackFrame(level); + MethodInfo mi = (MethodInfo)stackFrame.GetMethod(); + return Task.FromResult(mi.ToString()!); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackAwaitTask(int level) + { + return await FromStackTask(level); + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/122547", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] + [Theory] + [InlineData(0)] + [InlineData(1)] + public static void FromStackDMI(int level) + { + if (level == 0) + { + // Note: async1 leaks implementation details here and returns "Void MoveNext()" + Assert.Equal("FromStackDMIAsync", FromStackDMIAsync(0).Result); + Assert.Equal("FromStackDMIAsync", FromStackDMIAwait(0).Result); + + Assert.Equal("FromStackDMITask", FromStackDMITask(0).Result); + Assert.Equal("FromStackDMITask", FromStackDMIAwaitTask(0).Result); + } + else + { + // Note: we go through suspend/resume, that is why we see dispatcher as the caller. + // we do not see the resume stub though. + Assert.Equal("DispatchContinuations", FromStackDMIAsync(1).Result); + Assert.Equal("DispatchContinuations", FromStackDMIAwait(1).Result); + + Assert.Equal("FromStackDMI", FromStackDMITask(1).Result); + // Note: we do not go through suspend/resume, that is why we see the actual caller. + // we do not see the async->Task thunk though. + Assert.Equal("FromStackDMIAwaitTask", FromStackDMIAwaitTask(1).Result); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackDMIAsync(int level) + { + await Task.Yield(); + StackFrame stackFrame = new StackFrame(level); + DiagnosticMethodInfo mi = DiagnosticMethodInfo.Create(stackFrame); + return mi.Name; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackDMIAwait(int level) + { + return await FromStackDMIAsync(level); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task FromStackDMITask(int level) + { + StackFrame stackFrame = new StackFrame(level); + DiagnosticMethodInfo mi = DiagnosticMethodInfo.Create(stackFrame); + return Task.FromResult(mi.Name); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task FromStackDMIAwaitTask(int level) + { + return await FromStackDMITask(level); + } + + [Fact] + public static void EnumerateAll() + { + string[] actual = EnumAll.GetAll(); + string[] expected = + {"Boolean Equals(System.Object)", + "Void Finalize()", + "System.Threading.Tasks.Task`1[System.Int32] get_P1()", + "System.String[] GetAll()", + "Int32 GetHashCode()", + "System.Type GetType()", + "System.Threading.Tasks.Task`1[System.Int32] M1()", + "System.Threading.Tasks.Task`1[System.Int32] M2()", + "System.Object MemberwiseClone()", + "System.String ToString()" }; + + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < actual.Length; i++) + { + Assert.Equal(actual[i], expected[i]); + } + } + + class EnumAll + { + public static Task M1() => Task.FromResult(1); + + public async Task M2() => 1; + + public static Task P1 => Task.FromResult(1); + + public static string[] GetAll() + { + Type t = typeof(EnumAll); + List names = new(); + foreach (MethodInfo mi in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).OrderBy(it => it.Name)) + { + names.Add(mi.ToString()!); + } + + return names.ToArray(); + } + } +} diff --git a/src/tests/async/reflection/reflection-simple.csproj b/src/tests/async/reflection/reflection.csproj similarity index 100% rename from src/tests/async/reflection/reflection-simple.csproj rename to src/tests/async/reflection/reflection.csproj