GH-50840: [C++] Fix dead overflow guard in Take on binary-like arrays - #50841
Merged
zanmato1984 merged 1 commit intoAug 10, 2026
Merged
Conversation
…arrays
A misplaced closing parenthesis made the binary capacity check in
VarBinarySelectionImpl::GenerateOutput a no-op on GCC and clang:
ARROW_PREDICT_FALSE(a + b) > kOffsetLimit
expands to (__builtin_expect(!!(a + b), 0)) > kOffsetLimit, and the !!
collapses the sum to 0 or 1, which is never greater than kOffsetLimit.
Take() therefore overflowed the int32 offsets buffer silently and
returned a corrupt array instead of an error, producing garbage values
and segfaults downstream -- for example when decoding a dictionary whose
dense form exceeds 2 GiB.
Move the closing parenthesis so the comparison happens inside the macro.
MSVC and the fallback definitions expand ARROW_PREDICT_FALSE(x) to (x),
so those builds were unaffected.
Add a LARGE_MEMORY_TEST covering the overflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
|
|
uros-b
approved these changes
Aug 10, 2026
Member
|
Nice minimal fix for a critical bug, thank you @pearu! |
zanmato1984
approved these changes
Aug 10, 2026
zanmato1984
left a comment
Contributor
There was a problem hiding this comment.
+1
Thanks for locating and fix this tricky issue!
Contributor
|
Merging. |
|
After merging your PR, Conbench analyzed the 4 benchmarking runs that have been run so far on merge-commit a7d0bfa. There were no benchmark performance regressions. 🎉 The full Conbench report has more details. It also includes information about 11 possible false positives for unstable benchmarks that are known to sometimes produce them. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rationale for this change
compute::Takeonstring/binaryarrays silently overflows the int32 offsets buffer when the selected data exceedsINT32_MAXbytes, returningStatus::OK()with a corrupt array. Downstream this produces garbage values and segfaults — most visibly in pyarrow, wherenp.asarray()on a dictionary array whose dense form exceeds 2 GiB crashes the interpreter.A guard for this already exists, but a misplaced closing parenthesis makes it dead code on GCC and clang:
expands to
(__builtin_expect(!!(offset + val_size), 0)) > kOffsetLimit. The!!collapses the sum to 0 or 1, which is never greater thankOffsetLimit(2147483646), so the branch is never taken. MSVC and the fallback definitions expandARROW_PREDICT_FALSE(x)to(x), so those builds were unaffected.Present since
c07486c29f(ARROW-5760, 2020-06-11). See #50840 for full analysis.What changes are included in this PR?
ARROW_PREDICT_FALSE, inVarBinarySelectionImpl::GenerateOutput.TestTakeKernel.TakeBinaryOffsetOverflow, aLARGE_MEMORY_TESTcovering the overflow.Deliberately minimal: it does not attempt to make the oversized dictionary-decode case succeed. A 32-bit
stringcannot represent >2 GiB, soTakerefusing is the correct behaviour; making the pyarrow conversion work is a separate enhancement.Are these changes tested?
Yes, and the test was verified to distinguish both states:
Expected: has substring "...overflowed binary array capacity" / Actual: "OK"arrow-compute-vector-selection-test,ARROW_LARGE_MEMORY_TESTS=ONThe test uses 2048 × 1 MiB = 2 GiB, one value past the limit — ~2 GiB peak and ~1 s, rather than the multi-GB/multi-minute shape of the original reproducer.
Separately, I confirmed the end-to-end path on
main@42694575d0: before the fixCast(dictionary<int16,string> -> string)on a 2.5 GB decode returns OK with 7,050,328 negative offsets and a final offset of −1794967296 (= 2500000000 − 2³²); after the fix it returnsInvalid: Take operation overflowed binary array capacity.Note that
LARGE_MEMORY_TESTcompiles toDISABLED_*unlessARROW_LARGE_MEMORY_TESTS=ON, which in CI only happens in the "AMD64 Ubuntu Large Memory Tests" job ofcpp_extra.yml— nightly, or on PRs labelledCI: Extra: C++. I don't have permission to add that label; a committer may want to, so the new test is exercised before merge.Are there any user-facing changes?
Yes.
Take(and anything built on it, including dictionary decoding andDictionaryArray→ numpy/pandas conversion) now raisesInvalid: Take operation overflowed binary array capacitywhere it previously returned corrupt data or crashed. Code that unknowingly relied on the corrupt result will now see an error — which is the intent.This PR contains a "Critical Fix". It fixes both a bug that caused incorrect or invalid data to be produced — silently corrupt offset buffers, returned as a valid array with
Status::OK()— and a bug that causes a crash even when the API contract is upheld, since those offsets lead to out-of-bounds reads and segfaults on ordinaryTakeusage.AI usage
Per the AI-generated code guidance: the diagnosis, the one-line fix, and the test were produced with Claude Code, and reviewed and verified by me. Correctness was checked by (1) compiling the macro expansion standalone to confirm the guard never fires as written, (2) running the new test against both the fixed and unfixed kernel to confirm it distinguishes them, and (3) reproducing the corrupt offsets and the post-fix clean error end-to-end through
compute::Cast.🤖 Drafted by Claude Code (an AI agent) and reviewed & approved by pearu.