Skip to content

[wasm][R2R] GC hole: custom attribute instances get a dangling string field under GCStress #134777

Description

@lewing

Description

On browser-wasm CoreCLR with R2R code, custom attribute objects built by Assembly.GetCustomAttributes can end up with a dangling reference field when a GC happens during their construction. Under DOTNET_GCStress=0x1, a later GC or validation of the attribute's culture string hits SanityCheck().

No exception handling is involved, so this is separate from the aborted-funclet hole fixed in #134769.

It shows up in the Methodical_d1 runtime tests as an intermittent RawGetMethodTable() assert in SetMarked during JitTest_throw_SEH_cs.Test.TestEntryPoint. There, Exception.ToString() loads a resource string for the first time in the process. That goes through ResourceManager, which reads the NeutralResourcesLanguageAttribute via RuntimeCustomAttribute.GetCustomAttributes(QCustomAttributeList, int, RuntimeType). The failing root is attributes._item, a live ListBuilder<object> slot in that frame. The attribute object itself validates, and the assert fires while marking an object reachable from it.

Reproduction Steps

using System;
using System.Reflection;
using System.Resources;

static class Program
{
    static int Main()
    {
        Assembly[] asms = { typeof(object).Assembly, typeof(System.Reflection.Metadata.MetadataReader).Assembly, typeof(Console).Assembly };
        int bad = 0, n = 0;
        for (int i = 0; i < 60; i++)
        {
            foreach (Assembly a in asms)
            {
                object[] attrs = a.GetCustomAttributes(typeof(NeutralResourcesLanguageAttribute), false);
                foreach (NeutralResourcesLanguageAttribute attr in attrs)
                {
                    n++;
                    if (attr.CultureName is not { Length: > 0 }) bad++;
                }
            }
        }
        Console.WriteLine($"attrs={n} bad={bad}");
        return bad == 0 && n > 0 ? 100 : 1;
    }
}
  1. Build the program for net11.0 and R2R-compile it with crossgen2 using --targetarch:wasm --targetos:browser -f wasm -O --codegenopt:JitWasmNyiToR2RUnsupported=1.
  2. Run it with a checked browser-wasm corerun under node, using the Core_Root from a runtime-tests Helix payload, with DOTNET_TieredCompilation=0 and DOTNET_GCStress=0x1.

Expected behavior

Prints attrs=180 bad=0 and exits with 100, as it does without GC stress.

Actual behavior

Fails every time (4/4 runs) with:

ASSERT FAILED
	Expression: SanityCheck()
	Location:   src/coreclr/vm/methodtable.cpp:8691
	Function:   Validate
Frame (InterpreterFrame)
   0) Program::Main, IR_00e5

It fails identically with and without the fix in #134769.

Regression?

Unknown. On CI it was hidden, because the test that reaches it (throw_SEH) crashed earlier on the EH hole fixed in #134769.

Known Workarounds

None.

Configuration

  • browser-wasm CoreCLR, Checked runtime (main as of 2026-09-28), CoreLib and Core_Root from the Methodical_d1 Helix payload of build 1613981.
  • Test assembly R2R-compiled to Wasm. Main itself runs in the interpreter (IR_ offsets).
  • node 26.4.0, macOS arm64 host.

Other information

In Methodical_d1, the failure is intermittent. It depends on the first-time resource load happening at a bad GC point, and randomized string hashing probably varies where that happens. The standalone repro above is deterministic.

Not yet root-caused. Likely suspects are where attribute constructor arguments are created and passed: the string argument parsed from the CA blob as it goes through reflection invoke and the R2R/interpreter thunks.

Note

This issue was drafted with GitHub Copilot.

Activity

  1. dotnet-policy-service commented on Sep 28, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  2. dotnet-policy-service commented on Sep 28, 2026

    @dotnet-policy-service
    Contributor

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

  3. added this to the 12.0.0 milestone on Sep 28, 2026
  4. removed
    untriagedNew issue has not been triaged by the area owner
    on Sep 28, 2026
  5. added a commit that references this issue on Sep 30, 2026
    cd42bb5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions