diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index dbc9b2514eb188..c532eae900b5b4 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -1366,6 +1366,12 @@ void ClassLoader::ValidateMethodsWithCovariantReturnTypes(MethodTable* pMT) continue; } MethodDesc* pMD = pMT->GetMethodDescForSlot(i); + + // Skip validation for async variant methods, as they have different signatures by design + // to support the async calling convention + if (pMD->IsAsyncVariantMethod()) + continue; + MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i); if (pMD == pParentMD) diff --git a/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs new file mode 100644 index 00000000000000..c26e1183f5a100 --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs @@ -0,0 +1,50 @@ +// 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.Threading.Tasks; +using Xunit; + +namespace AsyncCovariantReturnTest; + +public static class Program +{ + [Fact] + public static void TestEntryPoint() + { + // This test validates that async methods with covariant-like return type signatures + // do not trigger TypeLoadException during type loading. + // + // Background: The C# compiler allows overriding a Task method with an async Task method + // because the async keyword changes the method's semantics. The compiler generates + // async variant methods with different signatures to support the async calling convention. + // These variant methods have the unwrapped return type (T instead of Task). + // + // The issue was that the runtime's covariant return type validator was incorrectly + // treating these synthetic async variant methods as invalid overrides, causing a + // TypeLoadException even though the code is valid and compiles successfully. + // + // This test ensures that such valid code can be loaded without throwing. + + GC.KeepAlive(new Derived()); + } +} + +public abstract class Base +{ + public abstract Task HandleAsync(); +} + +public class Derived : Base +{ + // This override is valid C# code that compiles successfully. + // The C# compiler allows overriding Task with async Task because the async keyword + // changes how the method is compiled. However, the runtime generates async variant methods + // with different signatures (returning T instead of Task) to support the async calling + // convention. The covariant return type validator should skip these synthetic methods. + public override async Task HandleAsync() + { + await Task.Yield(); + return string.Empty; + } +} diff --git a/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.csproj b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.csproj new file mode 100644 index 00000000000000..00dd1ee50adbfc --- /dev/null +++ b/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.csproj @@ -0,0 +1,11 @@ + + + true + + + + + + + +