Skip to content

[wasm] R2R: JsonDocument.Parse throws spurious NullReferenceException in Utf8JsonReader.ReadSingleSegment #131640

Closed

Description

@pavelsavara

Description

On browser-wasm (CoreCLR) with PublishReadyToRun, JsonDocument.Parse throws a spurious NullReferenceException inside Utf8JsonReader.ReadSingleSegment. The same parse works under the interpreter, and a plain Utf8JsonReader.Read() loop over the same input works even under R2R — only the JsonDocument.Parse path faults.

Distinct from the (fixed) SIMD issue #131299: this reproduces on a build where #131299's Base64Url.DecodeFromUtf8 repro passes, and the input here ([1,2,3]) is numeric (no SIMD string-scan path).

Repro

using System;
using System.Text.Json;

class Program
{
    static void Main()
    {
        // works under R2R and interpreter
        var reader = new Utf8JsonReader("[1,2,3]"u8);
        int n = 0; while (reader.Read()) n++;
        Console.WriteLine($"Utf8JsonReader.Read: {n} tokens");

        // throws NullReferenceException under R2R; returns 3 under the interpreter
        using var doc = JsonDocument.Parse("[1,2,3]");
        Console.WriteLine($"JsonDocument.Parse: {doc.RootElement.GetArrayLength()} items");
    }
}

System.Text.Json must be ReadyToRun-compiled.

Expected

JsonDocument.Parse: 3 items

Actual

System.NullReferenceException: Arg_NullReferenceException
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.JsonDocument.Parse(ReadOnlySpan`1, JsonReaderOptions, MetadataDb&, StackRowStack&)
   at System.Text.Json.JsonDocument.Parse(String, JsonDocumentOptions)

Notes

  • Reproduces only under R2R; the interpreter parses the same input correctly.
  • A direct Utf8JsonReader.Read() loop over the same input works under R2R — the fault is specific to the JsonDocument.Parse reader path.

Configuration

  • -os browser -a wasm, CoreCLR, PublishReadyToRun (crossgen2, --obj-format:wasm)
  • Reproduces in Node and in the browser.

Note

This issue was written with the assistance of GitHub Copilot.

Activity

  1. dotnet-policy-service commented on Jul 31, 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 Jul 31, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-text-json
    See info in area-owners.md if you want to be subscribed.

  3. added this to the 11.0.0 milestone on Jul 31, 2026
  4. removed
    untriagedNew issue has not been triaged by the area owner
    on Jul 31, 2026
  5. pavelsavara commented on Jul 31, 2026

    @pavelsavara
    MemberAuthor

    Root cause narrowed: a torn ReadOnlySpan<byte> constructor argument

    I bisected this by editing System.Text.Json source, rebuilding, and re-running the wasm R2R publish. Each row below is one build → re-crossgen → run cycle:

    # Change to JsonDocument.Parse / Utf8JsonReader Result
    1 Empty loop body while (reader.Read()) { } Still NREs → not the loop body
    2 [MethodImpl(NoInlining)] on Utf8JsonReader.Read() Still NREs → not Read inlining
    3 NoInlining on the 3-arg ctor as well Still NREs → not ctor inlining
    4 Probe just before byte first = _buffer[_consumed]; _buffer.Length == 7 (correct for [1,2,3]), _consumed == 0, yet _buffer[0] NREs
    5 Probe inside the ctor right after _buffer = jsonData; jsonData[0] NREs while jsonData.Length == 7

    Conclusion

    The ReadOnlySpan<byte> arrives torn as the constructor argument: its _length (7) is preserved but the managed ref byte (_reference) is null. Passing the span by value into

    public Utf8JsonReader(ReadOnlySpan<byte> jsonData, bool isFinalBlock, JsonReaderState state)

    drops the ref half while keeping the length. HasMoreData() reads the (correct) length and passes, then _buffer[_consumed] dereferences the null ref → the spurious NullReferenceException in ReadSingleSegment.

    This looks like the same family as #131639 (by-value struct/span argument corruption under the wasm crossgen2 R2R calling convention).

    What I could not reduce to a standalone repro

    App-side structural mimics do not reproduce:

    • a static int Probe(ReadOnlySpan<byte> span, bool b, BigStruct state) → returns span[0] == 91 (correct);
    • an instance ctor on a large ref struct that stores a ReadOnlySpan<byte> field (with individual state field reads plus a branch) → also correct.

    Cross-module (app → System.Text.Json) ctor calls also work. The tear only happens on the intra-module call to the real Utf8JsonReader ctor as crossgen2 compiles it inside System.Text.Json.dll, so JsonDocument.Parse remains the reliable repro.

    Note

    This analysis was written with the assistance of GitHub Copilot.

  6. modified the milestones: 11.0.0, 12.0.0 on Aug 3, 2026
  7. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    and removed on Aug 11, 2026
  8. dotnet-policy-service commented on Aug 11, 2026

    @dotnet-policy-service
    Contributor

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

  9. AndyAyersMS commented on Aug 12, 2026

    @AndyAyersMS
    Member

    This seems different from #131639 and #132137. Not sure where the problem lies just yet.

  10. added a commit that references this issue on Aug 13, 2026
    f6e73fc
  11. locked and limited conversation to collaborators on Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    arch-wasmWebAssembly architecturearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions