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
[wasm] R2R: JsonDocument.Parse throws spurious NullReferenceException in Utf8JsonReader.ReadSingleSegment #131640
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
usingSystem;usingSystem.Text.Json;classProgram{staticvoidMain(){// works under R2R and interpretervarreader=newUtf8JsonReader("[1,2,3]"u8);intn=0;while(reader.Read())n++;Console.WriteLine($"Utf8JsonReader.Read: {n} tokens");// throws NullReferenceException under R2R; returns 3 under the interpreterusingvardoc=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.
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];
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
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.
Activity
dotnet-policy-service commentedon Jul 31, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Jul 31, 2026
Tagging subscribers to this area: @dotnet/area-system-text-json
See info in area-owners.md if you want to be subscribed.
pavelsavara commentedon Jul 31, 2026
Root cause narrowed: a torn
ReadOnlySpan<byte>constructor argumentI bisected this by editing
System.Text.Jsonsource, rebuilding, and re-running the wasm R2R publish. Each row below is one build → re-crossgen → run cycle:JsonDocument.Parse/Utf8JsonReaderwhile (reader.Read()) { }[MethodImpl(NoInlining)]onUtf8JsonReader.Read()ReadinliningNoInliningon the 3-arg ctor as wellbyte first = _buffer[_consumed];_buffer.Length == 7(correct for[1,2,3]),_consumed == 0, yet_buffer[0]NREs_buffer = jsonData;jsonData[0]NREs whilejsonData.Length == 7Conclusion
The
ReadOnlySpan<byte>arrives torn as the constructor argument: its_length(7) is preserved but the managedref byte(_reference) is null. Passing the span by value intodrops the ref half while keeping the length.
HasMoreData()reads the (correct) length and passes, then_buffer[_consumed]dereferences the null ref → the spuriousNullReferenceExceptioninReadSingleSegment.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:
static int Probe(ReadOnlySpan<byte> span, bool b, BigStruct state)→ returnsspan[0] == 91(correct);ref structthat stores aReadOnlySpan<byte>field (with individualstatefield 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 realUtf8JsonReaderctor as crossgen2 compiles it insideSystem.Text.Json.dll, soJsonDocument.Parseremains the reliable repro.Note
This analysis was written with the assistance of GitHub Copilot.
dotnet-policy-service commentedon Aug 11, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
AndyAyersMS commentedon Aug 12, 2026
This seems different from #131639 and #132137. Not sure where the problem lies just yet.
Wasm: don't zero-pad the caller's return buffer in the interp-to-R2R …