validate utf-16 surrogate halves in decodeUnicodeCodePoint - #1698
validate utf-16 surrogate halves in decodeUnicodeCodePoint#1698SABITHSAHEB wants to merge 4 commits into
Conversation
|
any update? |
baylesj
left a comment
There was a problem hiding this comment.
Thanks for the fix — the validation logic itself looks correct. One design concern before merging, though: this rejection is unconditional in both readers, with no way to opt out.
Unlike #1663 (unescaped control characters, which are grammar-invalid per RFC 8259), lone/mismatched surrogate escapes are syntactically valid JSON under both the RFC 8259 ABNF and ECMA-404 — the spec only calls the resulting value "unpredictable".
Real-world producers emit them legally: ES2019+ JSON.stringify('\uD83D') yields "\ud83d", and Python's json.dumps does the same. After this change, documents that parsed on every prior release hard-fail in every configuration — including CharReaderBuilder::ecma404Mode, which would now reject text that ECMA-404 defines as conforming, and the legacy Json::Reader, whose Features struct has no knob at all.
Comparable strictness decisions (failIfExtra, rejectDupKeys, allowSpecialFloats) are all routed through CharReaderBuilder settings. Could this be gated behind a setting (e.g. rejectInvalidSurrogates, arguably default-on) so downstream users with lenient-ingestion pipelines have an escape hatch? Alternatively, WHATWG-style U+FFFD replacement in the lenient path would avoid the hard break while still fixing the invalid-UTF-8 output.
Route the escape validation through a CharReaderBuilder setting instead of failing unconditionally, matching failIfExtra/rejectDupKeys. Default on, on in strictMode, off in ecma404Mode so ECMA-404-conforming lone surrogates still parse. Revert the deprecated Json::Reader change so the legacy path keeps its prior behaviour.
|
Makes sense on the spec point: these are well-formed per RFC 8259 and ECMA-404, so an unconditional hard-fail in every config was too aggressive. Routed it through a |
|
gentle ping |
|
| Filename | Overview |
|---|---|
| src/lib_json/json_reader.cpp | Adds correct configurable checks to OurReader and builder modes, but omits the equivalent validation from the separate public Reader decoder. |
| include/json/reader.h | Documents the new CharReaderBuilder setting, its validation behavior, and its ECMA-404 interaction. |
| src/test_lib_json/main.cpp | Covers both new rejection branches and lenient mode, but does not test the legacy Json::Reader path where the bug remains. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[JSON input with surrogate escape] --> B{Parser API}
B -->|CharReader| C[OurReader decoder]
C --> D{rejectInvalidSurrogates}
D -->|true| E[Validate pair or reject lone low surrogate]
D -->|false| F[Preserve lenient behavior]
B -->|Json::Reader| G[Reader decoder]
G --> H[No new range validation]
H --> I[Malformed surrogate remains accepted]
Reviews (1): Last reviewed commit: "Merge branch 'master' into validate-surr..." | Re-trigger Greptile
| if (features_.rejectInvalidSurrogates_ && | ||
| (surrogatePair < 0xDC00 || surrogatePair > 0xDFFF)) | ||
| return addError("expecting a low surrogate (DC00-DFFF) to complete " | ||
| "the unicode surrogate pair", | ||
| token, current); |
There was a problem hiding this comment.
Legacy Reader skips surrogate validation
When callers parse a mismatched pair such as "\uD801\u0041" or a lone low surrogate through the public Json::Reader API, its separate unchanged decoder still accepts the malformed escape, causing the second escape's value to be lost or invalid UTF-8 to be emitted. Apply equivalent validation to Reader::decodeUnicodeCodePoint so both public parser paths enforce the behavior described by this change.
There was a problem hiding this comment.
That's intentional, see the thread above: the legacy Json::Reader change was dropped during review because its Features struct has no setting to gate this on, and an unconditional hard-fail there would break input that parsed on every prior release. The fix is scoped to CharReader behind rejectInvalidSurrogates. I've updated the PR description so it no longer says both readers.
Coverage Report for CI Build 32424372791Coverage increased (+0.01%) to 89.917%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats💛 - Coveralls |
OurReader (CharReader) now validates the low-surrogate range when completing a pair and rejects an unpaired low surrogate, gated behind a new
rejectInvalidSurrogatesCharReaderBuilder setting: on by default and in strictMode, off in ecma404Mode (lone surrogate escapes are conforming there). The legacy Json::Reader is intentionally left unchanged since its Features struct has no knob to gate this on. Added CharReader tests for the two rejection paths and the opt-out; existing valid-pair cases are unaffected.