Skip to content

[wasm][coreCLR] R2R reflection-invoke of a shared-generic (reference-type) method instantiation crashes (corrupt MethodTable) #129861

Description

@pavelsavara

Summary

On wasm (CoreCLR interpreter + R2R, FEATURE_PORTABLE_ENTRYPOINTS), reflection-invoking a generic method instantiated over a reference type (a shared __Canon instantiation) crashes. The same generic method instantiated over a value type (an exact instantiation) works.

This is the root cause of several of the "silent" browser-lane crashes seen when enabling R2R for browser/wasm CoreCLR (#129634) — assemblies that abort during xUnit test execution with WASM EXIT 1 and no [FAIL] (e.g. System.IO.Compression.Tests, System.IO.Compression.ZipFile.Tests, System.Text.RegularExpressions.Tests, Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests). xUnit invokes every test method via reflection, so a shared-generic test method (or shared-generic xUnit machinery) trips this.

Repro

Minimal repro in src/mono/sample/wasm/console-node (the sample assembly is R2R-compiled via <WasmReadyToRunAssembly Include="$(AssemblyName)"/>):

using System;

public class Base
{
    public string NonVirtGen<T>() => "n:" + typeof(T).Name;
}

public class Test
{
    public static int Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        // value-type (exact) instantiation -> OK
        Console.WriteLine("VALUE:");
        Console.WriteLine("  " + typeof(Base).GetMethod("NonVirtGen")!
            .MakeGenericMethod(typeof(int)).Invoke(new Base(), null));

        // reference-type (shared __Canon) instantiation -> CRASH
        Console.WriteLine("REF:");
        Console.WriteLine("  " + typeof(Base).GetMethod("NonVirtGen")!
            .MakeGenericMethod(typeof(string)).Invoke(new Base(), null));

        Console.WriteLine("done");
        return 0;
    }
}

Build & run:

.\dotnet.cmd build /p:TargetOS=browser /p:TargetArchitecture=wasm /p:Configuration=Debug /p:RuntimeFlavor=CoreClr /t:RunSample .\src\mono\sample\wasm\console-node\

Output (the process aborts at the reference-type instantiation; run node main.mjs directly on the bundle for per-line output, since the abort drops buffered stdout):

Hello World!
VALUE:
  n:Int32
REF:

The discriminator is purely the generic argument: value type (exact) passes, reference type (shared __Canon) crashes — same method, same call site.

It is not virtual-specific: a generic virtual method (GVM) shows the same pattern (VirtGen<int> ok, VirtGen<string> / VirtGen<Tuple<decimal,string>> crash).

Observed failure

Two manifestations of the same corruption were observed (Debug runtime):

  1. Compression test (Chrome console) — a MethodTable SanityCheck assert during generic-virtual dispatch reached from reflection invoke:

    ASSERT FAILED  Expression: SanityCheck()
    Location: src/coreclr/vm/methodtable.cpp:8627   Function: Validate
      0) System.Runtime.CompilerServices.VirtualDispatchHelpers::ResolveVirtualFunctionPointer
      1) VirtualDispatchHelpers::VirtualFunctionPointerSlow
      2) VirtualDispatchHelpers::VirtualFunctionPointer
      3) System.RuntimeMethodHandle::InvokeMethod
      5) System.Reflection.MethodBaseInvoker::InterpretedInvoke_Method
      9) Xunit.Sdk.TestInvoker`1[__Canon]::CallTestMethod
         (instantiations: AsyncTaskMethodBuilder`1[Decimal], Tuple`2[Decimal,String])
    
  2. Sample probe (Node) — a NullReferenceException inside the invoke machinery:

    Unhandled exception. System.NullReferenceException
       at System.Runtime.CompilerServices.RuntimeHelpers.GetMethodTable(Object obj)
       at <reflection invoke>
    

Both point at a corrupt / null MethodTable (or generic context) being produced when reflection resolves a shared-generic (__Canon) method instantiation under R2R.

In Release (CI) there is no SanityCheck assert, so the corrupt MethodTable proceeds and eventually causes a hard wasm abort — surfacing as the opaque WASM EXIT 1 with no [FAIL]. (The dotnet console redirect / helix wasm-console.log does not capture the wasm error; only the browser's JS error console does.)

Why this is R2R-related

On main (CoreCLR + browser + interpreter, no R2R) the full library test suite is green (CI build 1478862: totalTests=78361, failedTests=0), and shared-generic reflection invoke is pervasive in xUnit. R2R is the delta in #129634. Likely tied to the generic-dictionary / generic-context R2R area on wasm (see #129821).

Environment

Related: #129766, #129808 (reflection invoke of R2R methods), #129821 (generic-lookup null function), #129857 (UnmanagedCallersOnly function pointers). Discovered while bringing up R2R for browser/wasm CoreCLR (#129634).

Note

This issue was authored with the assistance of GitHub Copilot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions