Describe the bug
g_json_parse_err (src/builtins.c:979) is only ever reset in builtin_json_decode
(src/builtins.c:1199). Every other consumer of the JSON parser leaves it set, so one malformed
parse silently poisons the next parse in the same thread.
The comment above the flag (src/builtins.c:971-978) says lenient callers "ignore it" — and they do,
in the sense that they don't raise. But the parser itself checks it mid-parse, so ignoring it is
not enough: it has to be cleared.
builtin_json_path (src/builtins.c:2764) calls eigs_json_parse_value at :2784 with no reset —
that is the place that should clear it and doesn't. Same for src/ext_http.c:411, :623, :682 and
:1298.
The poison lands at two checks inside the parser:
- object key check,
src/builtins.c:1131 — if (!key || g_json_parse_err) fires on the first key of
the next parse, so a perfectly good object decodes to an empty dict;
- array element check,
:1112 — fires after the first element, so an array decodes to a
one-element list.
That object/array asymmetry is worth knowing: the same poisoned state truncates arrays but empties
objects.
To reproduce
No strict decode is needed — the lenient path poisons itself:
x is json_path of ["{\"a\": \"unterminated", "a"]
print of x
y is json_path of ["{\"user\": {\"name\": \"bob\"}}", "user.name"]
print of y
Expected behavior
The second json_path is a fresh, well-formed document and should print bob. A previous malformed
parse should not affect it.
Actual behavior
The second line is empty. len of it is 0.
The same happens after a strict decode that raises: try: json_decode of "\"abc" catch … followed by
the well-formed json_path also prints empty.
Environment
- OS: Debian 13 (x86_64)
- GCC version: 14
- EigenScript version:
0.34.0, main @ abf8294
Found while working #724. Context that may be useful when you fix it: the #724 branch deliberately
keeps its recoverable substitutions (unpaired surrogate, escaped NUL, malformed \u) off this flag
on a separate channel, precisely so they don't widen the poison surface. Structural errors —
unterminated string, truncation, bad number — behave exactly as they do here on main.
Happy to send the reset as a PR if you'd like it, though where it belongs (each caller, or a helper
that owns the flag's lifetime) is your architectural call rather than mine to guess.
Describe the bug
g_json_parse_err(src/builtins.c:979) is only ever reset inbuiltin_json_decode(
src/builtins.c:1199). Every other consumer of the JSON parser leaves it set, so one malformedparse silently poisons the next parse in the same thread.
The comment above the flag (
src/builtins.c:971-978) says lenient callers "ignore it" — and they do,in the sense that they don't raise. But the parser itself checks it mid-parse, so ignoring it is
not enough: it has to be cleared.
builtin_json_path(src/builtins.c:2764) callseigs_json_parse_valueat:2784with no reset —that is the place that should clear it and doesn't. Same for
src/ext_http.c:411,:623,:682and:1298.The poison lands at two checks inside the parser:
src/builtins.c:1131—if (!key || g_json_parse_err)fires on the first key ofthe next parse, so a perfectly good object decodes to an empty dict;
:1112— fires after the first element, so an array decodes to aone-element list.
That object/array asymmetry is worth knowing: the same poisoned state truncates arrays but empties
objects.
To reproduce
No strict decode is needed — the lenient path poisons itself:
Expected behavior
The second
json_pathis a fresh, well-formed document and should printbob. A previous malformedparse should not affect it.
Actual behavior
The second line is empty.
len ofit is 0.The same happens after a strict decode that raises:
try: json_decode of "\"abc" catch …followed bythe well-formed
json_pathalso prints empty.Environment
0.34.0,main@abf8294Found while working #724. Context that may be useful when you fix it: the #724 branch deliberately
keeps its recoverable substitutions (unpaired surrogate, escaped NUL, malformed
\u) off this flagon a separate channel, precisely so they don't widen the poison surface. Structural errors —
unterminated string, truncation, bad number — behave exactly as they do here on
main.Happy to send the reset as a PR if you'd like it, though where it belongs (each caller, or a helper
that owns the flag's lifetime) is your architectural call rather than mine to guess.