You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NativeAOT: MethodInfo.Invoke on a ref-returning method can return a wrong object (ref return escapes invoke helper's argument storage) #135170
DynamicInvokeInfo.InvokeWithManyArguments and InvokeWithFewArguments copy reference-type arguments into stack storage (GC-registered in the many-args case) and pass byrefs to those slots. If the target returns ref to one of its ref arguments, the returned byref points into that storage.
The helpers return that byref, and only the outer Invoke dereferences it in ReturnTransform. By then the helper's frame is gone and its GC registrations are removed. The result can be stale or wrong, especially under a compacting GC.
Reproduction Steps
usingSystem;usingSystem.Reflection;usingSystem.Runtime.CompilerServices;usingSystem.Runtime.InteropServices;usingSystem.Threading;publicstaticclassProgram{privatestaticbools_stop;[MethodImpl(MethodImplOptions.NoInlining)]publicstaticrefobjectEcho(refobjectvalue,objecta,objectb,objectc,objectd){returnrefvalue;}publicstaticintMain(){Console.WriteLine(RuntimeInformation.FrameworkDescription);MethodInfomethod=typeof(Program).GetMethod(nameof(Echo))!;object[]arguments={newobject(),newobject(),newobject(),newobject(),newobject()};Threadcollector=newThread(static()=>{while(!Volatile.Read(refs_stop)){GC.Collect(2,GCCollectionMode.Forced,blocking:true,compacting:true);Thread.Yield();}});collector.Start();try{for(inti=0;i<100_000;i++){objectexpected=newobject();arguments[0]=expected;object?actual=method.Invoke(null,arguments);if(!ReferenceEquals(expected,actual)){Console.WriteLine($"Incorrect reference at iteration {i}");return1;}}Console.WriteLine("All references matched.");return0;}finally{Volatile.Write(refs_stop,true);collector.Join();}}}
Publish with PublishAot=true (dotnet publish -c Release -r win-x64) and run. It is timing-dependent, so rerun if needed.
Expected behavior
Every call returns the exact object passed as arguments[0]; exit code 0.
Actual behavior
Invoke returns a different object (first observed at iteration 5523 in my run); exit code 1.
Regression?
Unknown.
Known Workarounds
Avoid reflection invocation of methods that return ref to a reference-type ref parameter.
Configuration
NativeAOT, Windows x64, .NET 11.0.0-rc.1.26425.128
Likely affects all NativeAOT targets, since the code is shared.
Other information
Affected code is in src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs: InvokeWithManyArguments, InvokeWithFewArguments, InvokeDirectWithFewArguments, then ReturnTransform. The array, Span and MethodInvoker paths are all affected.
The generated invoke thunk deliberately doesn't copy byref returns (DynamicInvokeMethodThunk.cs, "Store the return value unless it is a byref").
Suggested fix: call ReturnTransform inside each helper, before the temporary storage goes out of scope and the GC registrations are removed, and return the resulting object?.
Activity
dotnet-policy-service commentedon Oct 3, 2026
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.
Fix NativeAOT reflection ref-return storage lifetime (#135172)