Skip to content

fix(encoding): tolerate legacy RLE chunks that overflow the declared value count#7708

Open
jackye1995 wants to merge 3 commits into
lance-format:mainfrom
jackye1995:jack/fix-rle-decode-overflow
Open

fix(encoding): tolerate legacy RLE chunks that overflow the declared value count#7708
jackye1995 wants to merge 3 commits into
lance-format:mainfrom
jackye1995:jack/fix-rle-decode-overflow

Conversation

@jackye1995

@jackye1995 jackye1995 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

#7376 made the RLE miniblock decoder reject chunks whose run lengths sum past the declared value count. The legacy (pre run-length-width) miniblock encoder rolled back to a power-of-2 checkpoint after a run had already crossed it, so existing files contain chunks that declare 2048 values but hold runs summing slightly more; the excess values are re-encoded at the start of the next chunk. The legacy decoder truncated the excess, so those files were valid when written — after #7376 they fail to read with:

Invalid user input: RLE decoding overflowed expected value count: produced at least 2080, expected 2048

This restores the truncating behavior for miniblock decoding: clamp the final crossing run at the declared count and ignore trailing legacy runs, while still rejecting underflow and zero run lengths. Block payloads have no chunk boundaries, so an overflow there can only be corruption and remains a hard error. Current encoders emit exact chunks, so this only changes how legacy files are read. Includes regression tests for the legacy chunk-boundary overflow and the strict block path.

Summary by CodeRabbit

  • Bug Fixes
    • Improved run-length (RLE) decoding to cap results to the expected number of values, avoiding failures when runs exceed the requested range.
    • Enhanced compatibility with legacy data by clamping miniblock overflow at chunk boundaries and ensuring decoded output matches the expected length and values.
    • Updated miniblock test coverage to reflect the new overflow semantics, and added a regression test for boundary-crossing truncation.
    • Preserved strict overflow handling for full block payloads, which still reports an error.

…value count

The legacy miniblock RLE encoder rolled back to a power-of-2 checkpoint
after a run had already crossed it, producing chunks whose run lengths
sum past the declared value count; the legacy decoder truncated the
excess. Restore that truncating behavior so those files stay readable,
while still rejecting underflow and zero run lengths.
@github-actions github-actions Bot added A-encoding Encoding, IO, file reader/writer bug Something isn't working labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The RLE decoder now clamps miniblock overflow to the expected value count, while block decoding still errors on overflow. The miniblock tests were updated for successful clamped decoding, and a regression test was added for legacy chunk-boundary overflow.

Changes

RLE decoder clamping

Layer / File(s) Summary
Clamp decode logic
rust/lance-encoding/src/encodings/physical/rle.rs
decode_data and decode_generic now accept overflow-clamping control, the decode loop truncates overflowing miniblock runs to the remaining budget, and miniblock/block callers pass different overflow modes.
Regression tests
rust/lance-encoding/src/encodings/physical/rle.rs
The miniblock overflow test now expects clamped success, and additional tests cover strict block overflow rejection plus legacy chunk-boundary truncation.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: legacy RLE chunk overflow is now tolerated during decoding.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance-encoding/src/encodings/physical/rle.rs (1)

1459-1478: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject zero-length runs before ignoring trailing legacy overflow.

Line 1460 exits before reading remaining run lengths, so [5, 0] with expected_value_count = 5 now succeeds and silently ignores the zero run. That conflicts with the PR contract that zero run lengths are still rejected.

Proposed fix
         let mut decoded: Vec<T> = Vec::with_capacity(expected_value_count);
         for (value, length_bytes) in values.iter().zip(lengths.chunks_exact(length_size)) {
-            if decoded.len() == expected_value_count {
-                break;
-            }
             let length = self.run_length_width.read_length(length_bytes);
             if length == 0 {
                 return Err(Error::invalid_input_source(
                     "RLE decoding encountered a zero run length".into(),
                 ));
             }
+            if decoded.len() == expected_value_count {
+                continue;
+            }
             let length = usize::try_from(length).map_err(|_| {
                 Error::invalid_input_source(
                     format!("RLE run length does not fit in usize: {length}").into(),
                 )
             })?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-encoding/src/encodings/physical/rle.rs` around lines 1459 - 1478,
The RLE decoder in the `decode_runs` logic is returning early once
`decoded.len()` reaches `expected_value_count`, which allows a trailing zero run
length to be skipped instead of rejected. Update the loop to keep validating
remaining run lengths even after the expected output has been filled, while
still ignoring only legacy overflow beyond the expected count; ensure the
zero-length check in `read_length` handling still triggers for any run
encountered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@rust/lance-encoding/src/encodings/physical/rle.rs`:
- Around line 1459-1478: The RLE decoder in the `decode_runs` logic is returning
early once `decoded.len()` reaches `expected_value_count`, which allows a
trailing zero run length to be skipped instead of rejected. Update the loop to
keep validating remaining run lengths even after the expected output has been
filled, while still ignoring only legacy overflow beyond the expected count;
ensure the zero-length check in `read_length` handling still triggers for any
run encountered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 76833c0c-7928-4807-af17-4390cefceabe

📥 Commits

Reviewing files that changed from the base of the PR and between 5dbd140 and 49cb363.

📒 Files selected for processing (1)
  • rust/lance-encoding/src/encodings/physical/rle.rs

Only miniblock chunks can legitimately overflow their declared value
count (legacy chunk-boundary crossing); block payloads have no next
chunk, so an overflow there is corruption and stays a hard error.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-encoding/src/encodings/physical/rle.rs`:
- Around line 2184-2190: The test in rle.rs only checks the error message from
BlockDecompressor::decompress and should also verify the specific overflow error
variant. Update the assertion to match on the strict error type returned by
decompress, then separately assert that its message contains the expected
“overflowed expected value count” text, using the BlockDecompressor::decompress
call site as the reference point.
- Around line 1510-1511: The trailing-run handling in the RLE decode path is
stopping too early by breaking after clamping, which lets zero-length runs slip
through. Update the logic in the decode loop around the `decoded.resize(...)`
branch in `rle.rs` so it keeps validating remaining run lengths after
`expected_value_count` is reached, rejecting any zero-length runs while only
suppressing extra output beyond the expected count. Use the existing
decode/run-length scanning logic in that function to continue iterating instead
of exiting immediately.
- Line 1485: The output buffer reservation in RLE decoding is unguarded and can
abort on malformed metadata. In the decoder path that builds the `decoded` Vec
in rle.rs, replace the direct capacity allocation with a fallible reservation
using try_reserve_exact(expected_value_count), and convert any allocation
failure into Error::invalid_input_source before proceeding. Keep the change
scoped to the decode logic that uses expected_value_count so oversized counts
are handled safely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3defb8bb-91be-4f7d-9b83-5b92ede74d87

📥 Commits

Reviewing files that changed from the base of the PR and between 49cb363 and 08b5292.

📒 Files selected for processing (1)
  • rust/lance-encoding/src/encodings/physical/rle.rs

Comment thread rust/lance-encoding/src/encodings/physical/rle.rs Outdated
Comment thread rust/lance-encoding/src/encodings/physical/rle.rs
Comment thread rust/lance-encoding/src/encodings/physical/rle.rs Outdated
Guard the decoded-vector allocation with try_reserve_exact so corrupt
metadata surfaces as InvalidInput instead of aborting, and assert the
error variant in the block overflow test.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance-encoding/src/encodings/physical/rle.rs (1)

1510-1512: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use checked addition in the overflow error path decoded.len() + length can overflow for large RLE counts and turn malformed input into a panic while building invalid_input_source. Use checked_add here and report the safe total.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-encoding/src/encodings/physical/rle.rs` around lines 1510 - 1512,
In the RLE overflow error path inside the decoding logic in rle.rs, replace the
direct decoded.len() + length calculation with a checked addition so malformed
large counts cannot panic while constructing invalid_input_source. Update the
overflow reporting in the RLE decode handling to use the safe total from
checked_add, and keep the existing error context around the expected_value_count
message.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@rust/lance-encoding/src/encodings/physical/rle.rs`:
- Around line 1510-1512: In the RLE overflow error path inside the decoding
logic in rle.rs, replace the direct decoded.len() + length calculation with a
checked addition so malformed large counts cannot panic while constructing
invalid_input_source. Update the overflow reporting in the RLE decode handling
to use the safe total from checked_add, and keep the existing error context
around the expected_value_count message.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a9d961f1-27a8-498e-9b12-f4a59d9a5ad6

📥 Commits

Reviewing files that changed from the base of the PR and between 08b5292 and ac1c5f8.

📒 Files selected for processing (1)
  • rust/lance-encoding/src/encodings/physical/rle.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-encoding Encoding, IO, file reader/writer bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants