Skip to content

Wrap unexpected RuntimeException from parsers as StreamReadException in _readTreeAndClose#5882

Draft
Copilot wants to merge 2 commits into3.xfrom
copilot/fix-index-out-of-bounds-test
Draft

Wrap unexpected RuntimeException from parsers as StreamReadException in _readTreeAndClose#5882
Copilot wants to merge 2 commits into3.xfrom
copilot/fix-index-out-of-bounds-test

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 5, 2026

Some format backends (e.g., jackson-dataformats-text CSV) can throw unchecked RuntimeException on corrupt input rather than JacksonException, violating the API contract of readTree. The specific trigger: CsvDecoder passes total spaceCount (instead of per-segment _currentSize) as outPtr to _nextUnquotedString after TextBuffer.resetWithCopy expands into a new segment — causing finishAndReturn to be called with lastSegmentEnd > _currentSegment.length, which propagates as IndexOutOfBoundsException out of TextBuffer.contentsAsString.

Changes

  • ObjectMapper._readTreeAndClose: Wrap the readRootValue call in a try/catch (RuntimeException) that re-throws as StreamReadException("Unexpected character (corrupt input): ..."), preserving the original exception as cause. This keeps the throws JacksonException contract and surfaces a meaningful error instead of a raw buffer exception.
// Before: IndexOutOfBoundsException from corrupt CSV input escaped readTree()
mapper.readTree(corruptCsvBytes); // throws IndexOutOfBoundsException: start 0, end 646, length 500

// After: wrapped as StreamReadException
mapper.readTree(corruptCsvBytes); // throws StreamReadException: Unexpected character (corrupt input): start 0, end 646, length 500

Note: The true root-cause fix belongs upstream — either in TextBuffer.finishAndReturn (jackson-core, guard lastSegmentEnd against exceeding _currentSegment.length) or in CsvDecoder.nextString (jackson-dataformats-text, use _textBuffer.getCurrentSegmentSize() instead of spaceCount). This change is a defensive safety net at the databind layer.

Catch JacksonException separately to handle it directly.
@pjfanning
Copy link
Copy Markdown
Member

Relates to FasterXML/jackson-dataformats-text#648. The problem in 648 is that we could update the code in any of 3 libs to avoid the ArrayIndexOutOfBoundsException. jackson-core, jackson-databind, jackson-dataformat-csv.

Our docs suggest that we only throw JacksonExceptions, so there is an argument for catching other RuntimeExceptions in key places and wrapping them in JacksonExceptions.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 81.48% 📈 +0.000%
Branches branches 74.74% 📈 +0.010%

Coverage data generated from JaCoCo test results

@cowtowncoder
Copy link
Copy Markdown
Member

I don't think this is a good idea. FasterXML/jackson-dataformats-text#648 was a clear simple regression caught by Fuzzer, and if we had caught, re-thrown exception, Fuzzer would not have noticed anything.
Content Fuzzer passed may or may not have been invalid -- likely it was not, actually -- but it is up to format codec to catch such cases and throw JacksonException.

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.

3 participants