Skip to content

fix(#1028): LenientJsonParser infinite-loop / OOM on non-JSON content#40

Merged
Skobeltsyn merged 1 commit intomainfrom
fix/1028-lenient-parser-oom
May 5, 2026
Merged

fix(#1028): LenientJsonParser infinite-loop / OOM on non-JSON content#40
Skobeltsyn merged 1 commit intomainfrom
fix/1028-lenient-parser-oom

Conversation

@Skobeltsyn
Copy link
Copy Markdown
Contributor

Two-layer fix for the parser bug that turned LLM-emitted brackets around non-JSON content (e.g. [abc], {"k": foo}, [<html>]) into infinite loops and OOM during agent invocation.

Layer 1: parseValue() now strict on the else branch — throws on chars that aren't a valid JSON-value prefix instead of falling through to parseNumber(). The throw is caught by parse(input)'s try/catch and the function returns null, preserving the lenient contract.

Layer 2: parseArray() and parseObject() carry zero-progress guards as defense-in-depth. Each loop iteration captures pos before parseValue() and throws if pos didn't advance afterward — catches any future regression where some other path also fails to advance.

Root cause: the old else -> parseNumber() fallthrough returned 0 without advancing pos when the input had no digits (the digit-while-loop ran zero times, substring start..pos was empty, the if (n.isEmpty()) return 0 branch fired). Back in parseArray, the loop condition was unchanged, list.add(0) repeated forever.

Bumps version 0.2.2 → 0.2.3 (patch). Same fix will roll into 0.3.0.

Two-layer fix for the parser bug that turned LLM-emitted brackets around
non-JSON content (e.g. `[abc]`, `{"k": foo}`, `[<html>]`) into infinite
loops and OOM during agent invocation.

Layer 1: parseValue() now strict on the else branch — throws on chars
that aren't a valid JSON-value prefix instead of falling through to
parseNumber(). The throw is caught by parse(input)'s try/catch and the
function returns null, preserving the lenient contract.

Layer 2: parseArray() and parseObject() carry zero-progress guards as
defense-in-depth. Each loop iteration captures pos before parseValue()
and throws if pos didn't advance afterward — catches any future
regression where some other path also fails to advance.

Root cause: the old `else -> parseNumber()` fallthrough returned 0
without advancing pos when the input had no digits (the digit-while-loop
ran zero times, substring start..pos was empty, the `if (n.isEmpty())
return 0` branch fired). Back in parseArray, the loop condition was
unchanged, list.add(0) repeated forever.

Bumps version 0.2.2 → 0.2.3 (patch). Same fix will roll into 0.3.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Skobeltsyn Skobeltsyn merged commit 506aa8b into main May 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant