VM: (bug) Delegate.Method on an open delegate to an inherited private generic-base method pollutes the derived type's reflection cache
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,FinalizeActual
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,SecretAnalysis
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).
Source: dotnet/runtime