Skip to content

Fix insert(pos, count, value) overflowing instead of throwing - #76

Merged
martinus merged 1 commit into
mainfrom
fix-69-insert-overflow
Jul 27, 2026
Merged

Fix insert(pos, count, value) overflowing instead of throwing#76
martinus merged 1 commit into
mainfrom
fix-69-insert-overflow

Conversation

@martinus

Copy link
Copy Markdown
Owner

Fixes #69.

auto sv = ankerl::svector<int, 4>{1, 2, 3};
sv.insert(sv.begin(), SIZE_MAX - 1, 5);
std::vector: threw length_error
svector: size=3 capacity=5, inserting SIZE_MAX-1...
ERROR: AddressSanitizer: stack-buffer-overflow
  #4 svector<int, 4ul>::shift_right(int*, int*, int*)
  #7 svector<int, 4ul>::insert(int const*, unsigned long, int const&)

s + count wrapped, the wrapped sum looked small enough to fit, so the in-place shift ran past the end of the buffer.

Fix

Both comparisons are now subtractions:

if (count > max_size() - s) { throw std::bad_alloc(); }
if (count > capacity<D>() - s) { return make_uninitialized_space_new<D>(s, p, count); }

Neither can wrap, since s is never larger than capacity() or max_size(). The review pointed out that leaving the second one as s + count > capacity() and relying on the guard above would be a cross-line invariant a later edit could quietly break — each check is now correct on its own.

The existing overflow checks could not have caught this: calculate_new_capacity() only ever receives an already-summed size, and storage<T>::alloc() guards capacity * sizeof(T). Both sit behind the wrapped comparison that decided not to reallocate at all.

Audit of the other arithmetic

Checked every other size computation in the header. reserve, resize, resize(count, value), svector(count), svector(count, value), assign(count, value) and resize_and_overwrite all pass a single bounded value into calculate_new_capacity, which already checks it against max_size() — no sum of two unbounded values, so nothing to wrap. emplace_back_grow's s + 1 is bounded by the size invariant. Every s + count in make_uninitialized_space_new is now downstream of the new guard. This was the only one.

Exception type

std::bad_alloc, matching calculate_new_capacity() and reserve(). std::vector throws std::length_error for this specific case, which is arguably the better contract for a drop-in replacement, but test/unit/bad_alloc.cpp already pins reserve(max_size()) to bad_alloc — introducing length_error for this one check would be a worse inconsistency than the existing divergence. Changing it library-wide is a separate decision.

Cost

Reviewed the codegen: max_size() folds to an immediate, and the check is movabs/sub/cmp+jb. Benchmarked single-element insert, emplace, and count inserts — all within noise.

Tests live in test/unit/insert.cpp next to the other insert coverage; reverting the guard reproduces the stack-buffer-overflow.

make_uninitialized_space() decided whether the elements still fit with
"s + count > capacity()". A huge count wrapped that sum around to something
small, the check said it fits, and the in place shift wrote past the end of the
buffer. std::vector rejects the same call.

Both comparisons are now subtractions, which cannot wrap because s is never
larger than capacity() or max_size(), so each one is correct on its own rather
than relying on a guard above it.

The existing overflow checks did not help: calculate_new_capacity() only ever
sees an already summed size, and storage<T>::alloc() guards capacity * sizeof(T)
instead. Both sit behind the wrapped comparison that decided not to reallocate
at all.

Throws std::bad_alloc, which is what this library uses everywhere for a request
that is too large. std::vector would throw std::length_error here, but
calculate_new_capacity() and reserve() already made the other choice and being
inconsistent within svector seems worse than the existing difference.

Closes #69

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@martinus
martinus merged commit c32ad12 into main Jul 27, 2026
6 checks passed
@martinus
martinus deleted the fix-69-insert-overflow branch July 27, 2026 19:30
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.

insert(pos, count, value) overflows instead of throwing length_error

1 participant