Skip to content

VM: (bug) Delegate.Method on an open delegate to an inherited private generic-base method pollutes the derived type's reflection cache #134219

Closed

Description

@EgorBo

Description

Reading Delegate.Method on an open-instance delegate to a private method of a generic base class exposes that private method through the derived type's reflection cache. Checked CoreCLR terminates on an assertion in the member cache merge; Release silently pollutes the cache so typeof(Derived).GetMethods(...) starts returning the base class's private method.

Repro

using System;
using System.Linq;
using System.Reflection;

public class Base<T>
{
    private int Secret() => 42;

    public static Delegate MakeOpen() =>
        Delegate.CreateDelegate(typeof(Func<Derived, int>),
            typeof(Base<string>).GetMethod("Secret", BindingFlags.Instance | BindingFlags.NonPublic)!);
}

public class Derived : Base<string> { }

public static class Program
{
    static string Methods() => string.Join(",",
        typeof(Derived).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Select(m => m.Name));

    public static void Main()
    {
        Delegate d = Base<string>.MakeOpen();
        Console.WriteLine(Methods());
        Console.WriteLine(d.Method + " reflected=" + d.Method.ReflectedType);
        Console.WriteLine(Methods());
    }
}

Expected

MemberwiseClone,Finalize
Int32 Secret() reflected=Base`1[System.String]
MemberwiseClone,Finalize

Actual

CoreCLR main (Checked) terminates on an assertion:

MemberwiseClone,Finalize
Process terminated.
Assertion failed.
false
   at System.RuntimeType.RuntimeTypeCache.MemberInfoCache`1.MergeWithGlobalList(T[] list)
   at System.RuntimeType.GetMethodBase(RuntimeType reflectedType, RuntimeMethodHandleInternal methodHandle)
   at System.Delegate.GetMethodImplUncached()

Release silently pollutes the derived type's cache:

MemberwiseClone,Finalize
Int32 Secret() reflected=Derived
MemberwiseClone,Finalize,Secret

Analysis

The open-delegate branch in Delegate.GetMethodImplUncached (src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs) substitutes Derived as the reflected type instead of normalizing back to the exact generic base type that declares the private method. A private method is not a member of the derived type, so it must not be entered into that type's member cache.

Regression?

No. Reproduces on .NET 10.0.12 (Release) and on current main.

Platform

Windows x64, CoreCLR main (Checked) and .NET 10.0.12 (Release).

Activity

  1. added this to the 12.0.0 milestone on Sep 18, 2026
  2. dotnet-policy-service commented on Sep 18, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
    See info in area-owners.md if you want to be subscribed.

  3. dotnet-policy-service commented on Sep 18, 2026

    @dotnet-policy-service
    Contributor

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

  4. dotnet-policy-service commented on Sep 18, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
    See info in area-owners.md if you want to be subscribed.

  5. dotnet-policy-service commented on Sep 18, 2026

    @dotnet-policy-service
    Contributor

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

  6. added a commit that references this issue on Oct 9, 2026
    f26cb64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

  • Status
    No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions