fix(arrow/array): clamp run ends when concatenating a sliced RunEndEncoded array - #1219
fix(arrow/array): clamp run ends when concatenating a sliced RunEndEncoded array#1219winklemad wants to merge 3 commits into
Conversation
…coded array updateRuns normalizes each input array's run ends by subtracting its logical offset, but never clamps the final run end to the array's logical length. When a RunEndEncoded array is sliced in the middle of a run, the slice keeps that run's original physical end, so after normalization the last run end overshoots the slice length. The overshoot then shifts every following array's run ends, so array.Concatenate silently returns wrong values while the result still passes ValidateFull. Clamp each input's final run end to the running logical length. Signed-off-by: Madan Kumar <winklemad@outlook.com>
zeroshade
left a comment
There was a problem hiding this comment.
The logical run-end clamp fixes the ordinary sliced-array overshoot, but the overflow check still runs against the unclamped physical run end. As a result, valid sliced inputs near the run-end type limit are rejected before the clamp can take effect.
I reproduced this with valid int16 RunEndEncoded arrays: a 32,760-element prefix followed by a one-element slice of a physical 32,767-element run returns an overflow error instead of a valid 32,761-element result. Details are inline.
The package, race, randomized slicing, vet, and CI checks otherwise pass.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Arrow Go maintainer. After you've
addressed the points above and pushed an update, an Apache Arrow Go
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.More on how Apache Arrow Go handles maintainer review:
CONTRIBUTING.md.
| // we can check the last runEnd in the src and add it to the | ||
| // last value that we're adjusting them all by to see if we | ||
| // are going to overflow | ||
| if uint64(lastEnd)+uint64(int(src[len(src)-1])-inputData[i].Offset()) > uint64(maxOf[T]()) { |
There was a problem hiding this comment.
The overflow check still uses the slice’s physical final run end before the new logical-length clamp. This rejects valid near-limit inputs. A valid int16 REE prefix of length 32,760 followed by a one-element slice of a physical 32,767-element run should produce length 32,761, but currently returns invalid: overflow in run-length-encoded run ends concat. Please clamp the normalized final source end to the sliced input’s logical length before both the overflow check and output write, and add this boundary regression test.
…erflow check The logical-length clamp fixed the sliced-array overshoot but the overflow check still used the input's unclamped physical final run end, so a valid slice whose physical run end is near the run-end type limit was rejected before the clamp applied. Clamp the normalized final run end to the input's logical length up front and use it for both the overflow check and the written final run end. Adds a near-int16-limit boundary regression test. Signed-off-by: Madan Kumar <winklemad@outlook.com>
|
Thanks @zeroshade — you're exactly right, the overflow check was still measuring the unclamped physical run end. Fixed in 300dbe3: I compute the clamped final run end once up front ( Your repro now passes — an |
zeroshade
left a comment
There was a problem hiding this comment.
The previous overflow-check finding is fixed at the current head: clamping now precedes the checked addition, with near-boundary regression coverage.
A separate 32-bit overflow remains when the final int64 run end is converted through int. This can turn valid run ends into negative output values; details and the fix direction are inline.
Array tests, race tests, vet, randomized differential tests, and Linux/386 cross-compilation otherwise pass. No CI checks are currently reported for this head.
This review was drafted by an AI-assisted tool and
confirmed by an Apache Arrow Go maintainer. After you've
addressed the points above and pushed an update, an Apache Arrow Go
maintainer — a real person — will take the next look
at the PR. The findings cite the project's review criteria;
if you think one of them is mis-applied, please reply on the
PR and a maintainer will weigh in.More on how Apache Arrow Go handles maintainer review:
CONTRIBUTING.md.
| // input's logical length before both the overflow check and the output write: | ||
| // otherwise a valid near-limit slice trips a false overflow, and the written | ||
| // run end overshoots (shifting every following array's run ends). | ||
| finalEnd := int(src[len(src)-1]) - offset |
There was a problem hiding this comment.
Converting the final int64 run end to int overflows on 32-bit targets. A valid run-end encoded array with final run end math.MaxInt64 and logical length 1 becomes -1 during concatenation, producing invalid output. Please keep this calculation in type T, clamp against T(inputData[i].Len()), and add a 32-bit int64 regression test.
…-end type Converting the final run end through int overflowed on 32-bit targets: a valid run-end-encoded array with final run end math.MaxInt64 and logical length 1 became -1 during concatenation. Compute and clamp the final run end (and the per-run normalization) in the run-end type T, clamping against T(inputData[i].Len()) rather than going through int. Adds a MaxInt64 regression test. Signed-off-by: Madan Kumar <winklemad@outlook.com>
|
Fixed in fe3b0ab. The final run end and the per-run normalization in the write loop now stay in the run-end type |
Rationale for this change
array.Concatenatesilently corrupts values when one of the inputs is aRunEndEncodedarray that was sliced in the middle of a run.updateRunsnormalizes each input's run ends by subtracting its logical offset, but never clamps the final run end to the array's logical length. A slice keeps the original physical end of the run it cuts through, so after normalization the last run end overshoots the slice length. That overshoot then shifts every following array's run ends, and the result still passesValidateFull, so nothing flags the corruption.Reproduction (values are wrong, no error):
slicedon its own decodes correctly ([100,100,200]); only the concatenated result is wrong, so the defect is entirely in the run-end merge.What changes are included in this PR?
Clamp each input array's final run end to the running logical length in
updateRuns(arrow/array/concat.go). This is the single place run ends are merged forRunEndEncodedconcatenation (the generic function covers int16/int32/int64 run-end types). No change to any array that ends on a run boundary — only a slice that cuts through a run is affected, and it now stays within its logical length.Are these changes tested?
Yes — added
TestConcatRunEndEncodedMidRunSliceinarrow/array/concat_test.go, which reproduces the corruption (it fails without the fix) and uses a checked allocator to confirm no leaks. The existingTestConcatRunEndEncoded/TestConcatAlmostOverflowRunEndEncodingand the fullarrow/arraypackage tests still pass;gofmtandgo vetare clean.Are there any user-facing changes?
Yes —
array.Concatenatenow returns correct values when an input is a mid-run slice of aRunEndEncodedarray, instead of silently wrong ones. No API change.