Skip to content

Fix TypeLoadException when async Task<T> overrides Task#124062

Merged
agocke merged 7 commits intomainfrom
copilot/fix-devbetterweb-startup-crash
Feb 7, 2026
Merged

Fix TypeLoadException when async Task<T> overrides Task#124062
agocke merged 7 commits intomainfrom
copilot/fix-devbetterweb-startup-crash

Conversation

Copy link
Contributor

Copilot AI commented Feb 6, 2026

Description

Runtime throws TypeLoadException for valid C# code where async Task<T> overrides Task. This breaks applications like DevBetterWeb on SDK 11.0.100-preview.1.26066.111.

public abstract class Base {
    public abstract Task HandleAsync();
}

public class Derived : Base {
    // Valid C# - compiles but runtime rejects
    public override async Task<string> HandleAsync() {
        await Task.Yield();
        return string.Empty;
    }
}

Async methods generate variant methods with unwrapped return types (string vs Task<string>) for the async calling convention. The covariant return type validator treats these synthetic methods as invalid overrides.

Changes

  • src/coreclr/vm/class.cpp: Skip async variant methods in ValidateMethodsWithCovariantReturnTypes. Check is placed early (right after resolving the derived class method) to avoid unnecessary work resolving the parent method when validation will be skipped. Only checks the derived class method since async variant methods should only override other async variant methods.
  • src/tests/.../AsyncCovariantReturn.cs: Regression test that validates type loading succeeds without throwing TypeLoadException. Test uses void return type following standard test pattern for tests without conditional logic.

Pattern matches existing async variant handling in comdelegate.cpp:1046 and dispatchinfo.cpp:2585.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 6, 2026 00:30
…tion

Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix startup crash in DevBetterWeb with dotnet-sdk-11.0.100-preview.1 Fix TypeLoadException when async Task<T> overrides Task Feb 6, 2026
Copilot AI requested a review from elinor-fung February 6, 2026 01:07
- Only check pMD (derived class method) for async variant
- Move check before resolving pParentMD to save unnecessary work
- AsyncVariantMethods should only override AsyncVariantMethods

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Let test harness handle exceptions rather than catching them explicitly.

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copilot AI requested a review from jkotas February 6, 2026 17:57
@jkotas
Copy link
Member

jkotas commented Feb 6, 2026

[error]src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs(13,23): error XUW1002: Tests should not unconditionally return 100. Convert to a void return. [/__w/1/s/src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.csproj]

@copilot

Change TestEntryPoint from int to void since it unconditionally returns 100.
Test only validates type loading succeeds, no conditional logic needed.

Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 6, 2026

[error]src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs(13,23): error XUW1002: Tests should not unconditionally return 100. Convert to a void return. [/__w/1...

Done in e719797. Converted TestEntryPoint to void return since it only validates type loading and doesn't need conditional exit codes.

@elinor-fung elinor-fung marked this pull request as ready for review February 6, 2026 19:07
Copilot AI review requested due to automatic review settings February 6, 2026 19:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a CoreCLR type-loading failure where runtime-generated async-variant methods (with unwrapped return types) were incorrectly included in covariant return type validation, causing TypeLoadException for otherwise valid overrides.

Changes:

  • Skip async-variant methods during ValidateMethodsWithCovariantReturnTypes to avoid validating synthetic signatures.
  • Add a regression test project covering an async Task<T> override of a Task virtual method.
  • Add test source that forces creation/loading of the relevant override shape.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/coreclr/vm/class.cpp Skips covariant return validation for async-variant methods to prevent false TypeLoadException.
src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.csproj Adds a new test project for the regression scenario.
src/tests/Loader/classloader/MethodImpl/CovariantReturns/UnitTest/AsyncCovariantReturn.cs Adds a regression test ensuring type loading succeeds for the async/covariant-return scenario.

@agocke agocke merged commit 7179aaa into main Feb 7, 2026
108 of 112 checks passed
@github-project-automation github-project-automation bot moved this to Done in AppModel Feb 7, 2026
@jkotas jkotas deleted the copilot/fix-devbetterweb-startup-crash branch February 8, 2026 19:09
lewing pushed a commit to lewing/runtime that referenced this pull request Feb 9, 2026
## Description

Runtime throws `TypeLoadException` for valid C# code where `async
Task<T>` overrides `Task`. This breaks applications like DevBetterWeb on
SDK 11.0.100-preview.1.26066.111.

```csharp
public abstract class Base {
    public abstract Task HandleAsync();
}

public class Derived : Base {
    // Valid C# - compiles but runtime rejects
    public override async Task<string> HandleAsync() {
        await Task.Yield();
        return string.Empty;
    }
}
```

Async methods generate variant methods with unwrapped return types
(`string` vs `Task<string>`) for the async calling convention. The
covariant return type validator treats these synthetic methods as
invalid overrides.

## Changes

- **src/coreclr/vm/class.cpp**: Skip async variant methods in
`ValidateMethodsWithCovariantReturnTypes`. Check is placed early (right
after resolving the derived class method) to avoid unnecessary work
resolving the parent method when validation will be skipped. Only checks
the derived class method since async variant methods should only
override other async variant methods.
- **src/tests/.../AsyncCovariantReturn.cs**: Regression test that
validates type loading succeeds without throwing TypeLoadException. Test
uses void return type following standard test pattern for tests without
conditional logic.

Pattern matches existing async variant handling in
`comdelegate.cpp:1046` and `dispatchinfo.cpp:2585`.

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for
you](https://github.com/dotnet/runtime/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Comment on lines +2 to +4
<PropertyGroup>
<RequiresProcessIsolation>true</RequiresProcessIsolation>
</PropertyGroup>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this require process isolation?

//
// This test ensures that such valid code can be loaded without throwing.

GC.KeepAlive(new Derived());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a plan to add actual test coverage for covariant returns? The test here inherits whatever is the global runtime-async setting, but I believe it might be useful to have real coverage under tests/async that tests: actually calling the method, pairs of "base is runtime-async, override is not" and inverted.

This test has a minimal value because it surgically only checks type load and nothing else. We don't get much bang for our RequiresProcessIsolation buck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants