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
Commit c3cb36c
Browse filesBrowse the repository at this point in the historyBrowse files
fix(zero-rust): take JSON nested as deep as zero-cache writes (#205)
The parser rejected JSON nested deeper than 1,500 levels, because cloning,
comparing, and writing a value recursed and a debug build overflowed a
2 MB thread stack by about 2,400 to 3,500 levels. zero-cache writes up to
about 4,100, and JS takes them, so a row with a JSON column nested deeper
than 1,500 made the client reject the poke, disconnect, and fail the same
way after every reconnect.
Clone, PartialEq, and json::write now recurse 128 levels and then go on
over a heap stack, and MAX_DEPTH is 5,000. Dropping fits in a 2 MB debug
stack at that depth without changes. The conformance runner no longer has
a nesting exception: harden-json's 1,502-level case matches JS exactly,
and a rejection for depth now counts as a failure. PARITY.md records why
the two remaining exceptions (lone surrogates, unwritable JSON paths)
stay.
Amp-Thread-ID: https://ampcode.com/threads/T-db04f816-97ed-4bc9-b19e-9ee68611a0ab
Co-authored-by: Amp <amp@ampcode.com>
Take JSON nested up to 5,000 levels deep, more than zero-cache can write, instead of rejecting it past 1,500: a row with a JSON column nested that deep no longer makes the client disconnect after every reconnect. Cloning, comparing, and writing such a value no longer recurse past a hundred or so levels, so it fits in a 2 MB thread stack even in a debug build.
| Lone surrogate | A Rust `String` can't hold a lone surrogate, which a JS string can. The parser reads an escaped one (`"\ud800"`) as U+FFFD, as the Swift client does; such a case matches when JS's output, with each such escape read as U+FFFD, is what Rust writes. Real servers send valid UTF-8, so this only shows in fuzzing. |
201
-
| Nesting | JSON nested deeper than `json::MAX_DEPTH` (1,500) is rejected. Dropping, comparing, and writing a value recurse, and a debug build overflows a 2 MB thread stack by 3,000 levels. JS has no limit short of its own stack (zero-cache writes about 4,100 levels). The Swift client has the same limit. |
202
-
| Unwritable path | A JSON path index is a `u32`, so a program with a negative or fractional index (`json('metadata', -1)`) can't be written in Rust. JS rejects it with "Invalid JSON path segment …"; such a case matches when JS rejects it that way. An index past `MAX_JSON_PATH_INDEX` can be written, and is rejected with JS's message. |
196
+
match. Both come from what Rust's types can hold, not from the client's
| Lone surrogate | A Rust `String` can't hold a lone surrogate, which a JS string can. The parser reads an escaped one (`"\ud800"`) as U+FFFD, as the Swift client does; such a case matches when JS's output, with each such escape read as U+FFFD, is what Rust writes. Holding one would take a string type of Rust's own (WTF-8) throughout the client and in every app's code. Postgres text and `jsonb` can't store one: only a `json` (not `jsonb`) column, which keeps its text as written, could bring one to the client, and it shows as U+FFFD, as a browser renders it. Nearly all such cases come from the JSON fuzz corpus. |
202
+
| Unwritable path | A JSON path index is a `u32`, so a program with a negative or fractional index (`json('metadata', -1)`) can't be written in Rust. JS rejects it with "Invalid JSON path segment …"; such a case matches when JS rejects it that way. Rust's types rule out at compile time what JS throws for at run time. An index past `MAX_JSON_PATH_INDEX` can be written, and is rejected with JS's message. |
203
+
204
+
JSON nesting is no longer a difference. JSON nested up to `json::MAX_DEPTH`
205
+
(5,000) levels deep, more than the about 4,100 zero-cache can write, is
206
+
parsed, cloned, compared, written, and dropped within a 2 MB thread stack,
207
+
even in a debug build, so the client takes any value the server sends.
208
+
Deeper text is rejected ("JSON nested too deeply"), where JS takes whatever
209
+
fits its stack. The Swift client rejects past 1,500 levels, so a row with a
210
+
JSON column nested deeper than that makes it disconnect after every
211
+
reconnect. Formatting a value with `Debug` still recurses.
0 commit comments