Skip to content

NativeAOT: reflection on static abstract interface methods crashes in .NET 11 (10.0 throws NotSupportedException) #135565

Description

@caraioniurie47

Description

On NativeAOT in .NET 11, reflection on a static abstract interface member calls through a null entry point. MethodInfo.Invoke, MethodInvoker.Invoke and PropertyInfo.GetValue crash the process with an access violation, a delegate from MethodInfo.CreateDelegate crashes when called, and RuntimeMethodHandle.GetFunctionPointer returns zero. On 10.0 all of these throw NotSupportedException. The change coincides with #125875, which made static virtual methods reflection-invokable on NativeAOT.

Reproduction Steps

using System;
using System.Reflection;

Run("MethodInfo.Invoke, ISortable.Sort", () => typeof(ISortable).GetMethod("Sort")!.Invoke(null, null));
Run("MethodInfo.Invoke, ISortable<int>.Sort(int)", () => typeof(ISortable<int>).GetMethod("Sort")!.Invoke(null, new object[] { 1 }));
Run("MethodInvoker.Invoke, ISortable.Sort", () => MethodInvoker.Create(typeof(ISortable).GetMethod("Sort")!).Invoke(null));
Run("PropertyInfo.GetValue, ICounted.Count", () => typeof(ICounted).GetProperty("Count")!.GetValue(null));
Run("CreateDelegate, ISortable.Sort, then call", () => typeof(ISortable).GetMethod("Sort")!.CreateDelegate<Action>()());
Run("GetFunctionPointer, ISortable.Sort", () => Console.WriteLine($"0x{typeof(ISortable).GetMethod("Sort")!.MethodHandle.GetFunctionPointer():X}"));
Run("MethodInfo.Invoke, IDefault.Sort (static virtual with a body)", () => typeof(IDefault).GetMethod("Sort")!.Invoke(null, null));

static void Run(string label, Action action)
{
    try
    {
        action();
        Console.WriteLine($"{label}: no exception");
    }
    catch (Exception e)
    {
        Console.WriteLine($"{label}: {e.GetType()}: {e.Message}");
    }
}

public interface ISortable { static abstract void Sort(); }
public interface ISortable<T> { static abstract void Sort(T value); }
public interface ICounted { static abstract int Count { get; } }
public interface IDefault { static virtual void Sort() { } }
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <Nullable>enable</Nullable>
    <AssemblyName>Repro</AssemblyName>
  </PropertyGroup>
</Project>
dotnet publish -c Release -r win-x64 -p:PublishAot=true
bin\Release\net11.0\win-x64\publish\Repro.exe

Expected behavior

The six calls on static abstract members throw a managed exception; the last call prints no exception.

Actual behavior

The process prints nothing and exits with 0xC0000005 (access violation). Run one at a time, the four invoke calls and the delegate call each exit with 0xC0000005, GetFunctionPointer prints 0x0, and the static virtual call with a body prints no exception.

Regression?

Yes, from 10.0. The same program published with SDK 10.0.401 (net10.0, ILCompiler 10.0.12) prints System.NotSupportedException: This object cannot be invoked because no code was generated for it: 'ISortable.Sort()'. for the first call, the same exception for every other call, the static virtual one included, and exits 0.

PR #125875 (ac11e33, in release/11.0, not in release/10.0) removed this exclusion from MetadataManager.IsMethodSupportedInReflectionInvoke:

MetadataManager.cs:427-429

            // TODO: Reflection invoking static virtual methods
            if (method.IsVirtual && method.Signature.IsStatic)
                return false;

Static abstract methods now get invoke map entries, but the compiler only sets HasEntrypoint for non-abstract methods:

ReflectionInvokeMapNode.cs:164-165

                if (!method.IsAbstract)
                    flags |= InvokeTableFlags.HasEntrypoint;

Known Workarounds

Not checked.

Configuration

.NET SDK 11.0.100-rc.1.26420.103 with ILCompiler 11.0.0-rc.1.26420.103, Windows 11 x64, win-x64. For the 10.0 comparison, SDK 10.0.401 with ILCompiler 10.0.12.

Other information

Proposed fix: leave static abstract methods out of the reflection invoke map (MetadataManager.ShouldMethodBeInInvokeMap), keeping static virtual methods with a body invokable. That restores the 10.0 behaviour for all six calls, is a small compiler-only change, and suits a backport to release/11.0.

Options not taken:

I have a fix with a test ready and will open a PR for it shortly. Could this be assigned to me?

Note

AI-generated, written at my direction and reviewed by me before posting.
Verified on Windows 11 x64: the program above published with dotnet publish -c Release -r win-x64 -p:PublishAot=true using SDK 11.0.100-rc.1.26420.103 (exit 0xC0000005, no output) and, with net10.0, SDK 10.0.401 (the NotSupportedException output); the one-at-a-time results are from a copy of the program that runs only the call named on its command line. The commit's effect is from git show ac11e33bf5d and git merge-base --is-ancestor against origin/release/11.0 and origin/release/10.0; the code excerpts are from dotnet/runtime 2eb7113245f7c536ab876dca5e3533fb96c81bbf and from the parent of ac11e33. The proposed fix's result is the program above published with a compiler built locally from 2eb7113245f plus the fix; the trimming test failures are ILCompiler.Trimming.Tests run on the same commit with the first option not taken.

Activity

  1. dotnet-policy-service commented on Oct 10, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
    See info in area-owners.md if you want to be subscribed.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions