Skip to content

[browser][coreCLR] Assembly resolution has no VFS fallback — private/copy-local assemblies fail to load (FileNotFoundException) where Mono succeeds #132308

Open

Description

@pavelsavara

Description

On browser-wasm CoreCLR (browserhost), loading a managed assembly that is present on the emscripten VFS but not eagerly registered from the boot config fails with FileNotFoundException. The same code works on Mono WASM, because Mono's assembly loader can probe the VFS filesystem while the CoreCLR browserhost probe is an in-memory-only lookup.

Surfaced by System.Runtime.Serialization.Formatters in System.Runtime.Tests (and others) on CoreCLR:

System.IO.FileNotFoundException : Could not load file or assembly
'System.Runtime.Serialization.Formatters, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
The system cannot find the file specified.
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

This assembly is a private, copy-local reference (a NetCoreAppMinimum build with a functional BinaryFormatter) added by the test project, not part of the shared framework:

<!-- src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj -->
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.Serialization.Formatters\src\System.Runtime.Serialization.Formatters.csproj"
                  Private="true"
                  SetTargetFramework="TargetFramework=$(NetCoreAppMinimum)" />

Affected tests (browser CoreCLR, Release)

Observed on the LibraryTestsCoreCLR legs of the R2R bring-up PR #129634 (build 1551759, head d3f76ed6):

  • System.Tests.DateTimeTests.GetObjectData_Invoke_ReturnsExpected
  • System.Formats.Nrbf.Tests.EdgeCaseTests.* (fails during xUnit discovery — CustomAttributeFormatException -> FileNotFoundException)
  • System.Collections.Tests.InternalHashCodeTests_*.ComparerImplementations_Dictionary_WithWellKnownStringComparers (×16 fixtures)
  • System.Collections.Tests.HashSet_NonGeneric_Tests.HashSet_CopyConstructor_ShouldWorkWithRandomizedEffectiveComparer

Root cause

CoreCLR browserhost resolves every managed assembly through external_assembly_probe, which is an in-memory Map with no filesystem/VFS fallback.

The host wires BrowserHost_ExternalAssemblyProbe as host_contract.external_assembly_probe (src/native/corehost/browserhost/browserhost.cpp). Its implementation (src/native/libs/Common/JavaScript/host/assets.ts) only consults loadedAssemblies, which is populated exclusively from boot-config assets (finishWebcilInstance — "this loader receives them from boot config"):

export function BrowserHost_ExternalAssemblyProbe(pathPtr, outDataStartPtr, outSize): boolean {
    const path = _ems_.UTF8ArrayToString(_ems_.dotnetApi.localHeapViewU8(), pathPtr);
    const assembly = loadedAssemblies.get(path);   // in-memory map only
    if (assembly) { /* return ptr + length */ return true; }
    _ems_.dotnetLogger.debug(`Assembly not found: '${path}'`);
    return false;                                   // -> FileNotFoundException
}

There is no FS/VFS read anywhere in the CoreCLR probe path. If an assembly is not in the eagerly-registered boot-config set, CoreCLR cannot load it even when the file physically exists on the emscripten VFS.

Why Mono succeeds (comparison)

Mono's asset loader (src/mono/browser/runtime/assets.ts) writes assets to the emscripten VFS as real files in addition to any in-memory registration:

case "vfs": {
    ...
    Module.FS_createPath("/", parentDirectory, true, true);
    Module.FS_createDataFile(parentDirectory, fileName, bytes, /*canRead*/ true, /*canWrite*/ true, /*canOwn*/ true);
    break;
}
// and, for behavior === "assembly":
cwraps.mono_wasm_add_assembly(virtualName, offset, bytes.length);

The app base is a VFS directory (FS.chdir(virtualWorkingDirectory) in src/mono/browser/runtime/startup.ts), so Mono's native assembly loader resolves an assembly that isn't in the eager in-memory set by probing the VFS filesystem under the app base. That is what makes System.Runtime.Serialization.Formatters load on Mono but not on CoreCLR.

Candidate fixes

  1. Bundling parity — ensure the CoreCLR WASM publish registers private/copy-local assemblies as eager "assembly" boot-config assets (so they land in loadedAssemblies), matching what Mono's in-memory path gets. This keeps the runtime probe in-memory-only.
  2. Runtime fallback — on a loadedAssemblies miss, have BrowserHost_ExternalAssemblyProbe read the file from the emscripten FS at the probed path, mirroring Mono's filesystem probing.

Open question to confirm before picking a fix: whether the private copy is present on the VFS-but-not-in-boot-config (favor fix 1 or 2), or missing from the publish bundle entirely (bundling gap). This can be checked from the _framework boot manifest of a CoreCLR test bundle.

Configuration

  • -os browser -a wasm, CoreCLR (browserhost), Release; reproduces in V8, Firefox, and Chrome.
  • Not R2R-specific — it's an assembly-resolution/probe limitation of the CoreCLR browser host.

Regression?

No — this is a gap in the new CoreCLR-on-browser host rather than a regression from a previously working CoreCLR configuration. Tracking so the CoreCLR wasm library test legs can match Mono coverage.

Note

This issue was researched and drafted with the assistance of GitHub Copilot.

Activity

  1. self-assigned this
    on Aug 14, 2026
  2. dotnet-policy-service commented on Aug 14, 2026

    @dotnet-policy-service
    Contributor

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

  3. dotnet-policy-service commented on Aug 14, 2026

    @dotnet-policy-service
    Contributor

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

  4. added this to the 12.0.0 milestone on Aug 14, 2026
  5. added and removed
    untriagedNew issue has not been triaged by the area owner
    on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions