Skip to content

Return errors instead of panicking in fallible functions - #10755

Open
emilk wants to merge 8 commits into
apache:mainfrom
emilk:emilk/missing-panics-doc
Open

Return errors instead of panicking in fallible functions#10755
emilk wants to merge 8 commits into
apache:mainfrom
emilk:emilk/missing-panics-doc

Conversation

@emilk

@emilk emilk commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

While clearing clippy::missing_panics_doc, a pattern turned up: functions that
already return Result, but report some failures by panicking. Several are
reachable from untrusted input, so the panic is a denial of service rather than a
bug report.

Documenting those panics would normalise them, so this returns errors instead.

This is one of four PRs splitting up the work.

  1. this PR - return errors from fallible functions
  2. Remove some unwraps #10759 - remove unreachable panics
  3. Document the panics of public functions #10760 - document the panics that genuinely remain
  4. Mark unreachable panics with #[expect(clippy::missing_panics_doc)] #10761 - #[expect] the unreachable ones, so the lint can be turned on

What changes are included in this PR?

Functions that could report the failure and did not.

Are these changes tested?

Covered by the existing tests, plus new tests for the b64_decode and
ArrayData::validate_values error paths.

Are there any user-facing changes?

No API changes. Some calls that used to panic now return an Err, which is the
point of the PR.

Comment thread arrow-array/src/array/run_array.rs Outdated
@emilk
emilk marked this pull request as ready for review August 19, 2026 13:34
@emilk
emilk marked this pull request as draft August 19, 2026 13:37
These functions already return `Result`, but reported some failures by
panicking. Several are reachable from untrusted input, where a panic is a
denial of service rather than a bug report:

* `b64_decode` panicked on invalid base64 in the input array
* `read_record_batch` asserted on the variadic buffer counts declared by the
  IPC message, and `FileReaderBuilder::build` unwrapped footer metadata whose
  key or value may be absent
* `Decoder::flush` (arrow-json) unwrapped a malformed tape
* `FFI_ArrowSchema::metadata` unwrapped lengths supplied by the producer, and
  preallocated a `HashMap` for an entry count it had not checked
* `ArrayData::validate_values` had `unreachable!()` arms for dictionary key
  and run end types, in a function whose whole job is to report bad data
* `concat_elements_bytes` and `concat_elements_utf8_many` unwrapped the offset
  conversion, so a long enough concatenation panicked
* `ColumnReader::skip_records` asserted on a page's record count
* `get_row_group_column_bloom_filter` (sync and async) unwrapped the Bloom
  filter offsets, and subtracted them without checking
* `ArrowColumnWriter::close` and `SerializedFileWriter::next_row_group`
  unwrapped on shared state and on overflow
* the three `try_new_from_builder` dictionary builders unwrapped on a shared
  key buffer
* the Flight SQL client unwrapped a truncated or unexpected server response
* `garbage_collect_dictionary` unwrapped the new dictionary key
* `data_type_from_json`, `field_from_json`, `ArrowFile::read_batch(es)` and
  `open_json_file` unwrapped malformed JSON

Adds tests for the base64 and `validate_values` error paths.

The remaining panics are documented or removed separately; this covers only
the functions that could report the failure and did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@emilk emilk changed the title Document or remove panics in public functions Return errors instead of panicking in fallible functions Aug 19, 2026

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @emilk -- this is pretty cool

Comment thread arrow-array/src/builder/generic_bytes_dictionary_builder.rs
emilk and others added 7 commits August 20, 2026 10:04
`new_keys` comes from `try_unary` on a locally owned array, and the `drop` just
above releases the null buffer it shared with `source_keys`, so `into_builder`
holds the only reference and cannot fail. Word the error as an internal one, so
a reader does not go looking for the input that triggers it.

The old wording also blamed the source builder, when the buffer in question is
the freshly derived one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One variadic buffer count is consumed per `BinaryView` or `Utf8View` column in
the schema, so a leftover count means the message describes more view columns
than the schema has. Say that, rather than reporting the symptom.

This also matches how `create_array` reports the opposite case, too few counts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ArrayData::validate_values` is public and can be called without
`validate()` having checked the buffer and child counts first, so the
`buffers[i]` and `child_data[i]` indexing panicked on bad data instead
of reporting it.

Add `buffer_at` and `child_at` accessors that return an error, and use
them on every path `validate_values` can reach. `check_bounds` now goes
through `typed_buffer`, which performs the same size check it used to
assert on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The number of variadic buffers for a `BinaryView` or `Utf8View` column
was taken from the IPC message and used unchecked. A negative count made
`create_primitive_array` index a buffer slice that was too short, so a
crafted message panicked instead of being rejected:

    index out of bounds: the len is 0 but the index is 0

The same count is used by `skip_field` for columns a projection leaves
out, where `skip_buffer` unwrapped the exhausted buffer iterator.

Check the count against the buffers the message actually has, and let
`skip_buffer` report the mismatch instead of unwrapping.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`read_records` and `skip_records` guard the same invariant with the same
code, but only `skip_records` reported it. Share one check between them,
and say which invariant was broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`garbage_collect_dictionary` built a bespoke `ComputeError` for a key that
does not fit its type. `ArrowError::DictionaryKeyOverflowError` is what the
rest of the crate reports for exactly that, including the call a few lines
below, so use it.

`ArrowColumnWriter::close` documented an error the caller cannot cause and
did not mention the poisoned lock, which is the one that can happen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	arrow-string/src/concat_elements.rs
@emilk
emilk marked this pull request as ready for review August 20, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants