Add upper bound length checks to `InlinedVector` capacity, size, and insertion methods * Add unconditional `ThrowStdLengthError` checks across `InlinedVector` methods (`insert`, `emplace`, `emplace_back`, `push_back`, `resize`, `reserve`, `assign`, and constructors) when the requested size or element count exceeds `max_size()`. * This prevents integer overflow when computing `size() + n` in `insert` (which could wrap around to `0` and cause `Storage::Insert` to bypass allocation and write out of bounds) across all build modes, and ensures full compliance with C++ standard container exception behavior (`std::length_error`) for exceeding `max_size()`. * Update unit tests in `inlined_vector_test.cc` (`TEST(IntVec, LengthThrows)` and `TEST(IntVec, EmplaceLengthThrows)`) verifying that exceeding `max_size()` throws `std::length_error` (or cleanly terminates when exceptions are disabled). PiperOrigin-RevId: 945154401 Change-Id: I8501776d3be95e85bfe5e89ce58429543b7392ff
diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 8132564..744d3d9 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h
@@ -72,7 +72,7 @@ // designed to cover the same API footprint as covered by `std::vector`. template <typename T, size_t N, typename A = std::allocator<T>> class ABSL_ATTRIBUTE_WARN_UNUSED InlinedVector { - static_assert(N > 0, "`absl::InlinedVector` requires an inlined capacity."); + static_assert(N > 0, "absl::InlinedVector requires an inlined capacity."); using Storage = inlined_vector_internal::Storage<T, N, A>; @@ -135,6 +135,9 @@ explicit InlinedVector(size_type n, const allocator_type& allocator = allocator_type()) : storage_(allocator) { + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::InlinedVector failed length check"); + } storage_.Initialize(DefaultValueAdapter<A>(), n); } @@ -142,6 +145,9 @@ InlinedVector(size_type n, const_reference v, const allocator_type& allocator = allocator_type()) : storage_(allocator) { + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::InlinedVector failed length check"); + } storage_.Initialize(CopyValueAdapter<A>(std::addressof(v)), n); } @@ -161,8 +167,11 @@ InlinedVector(ForwardIterator first, ForwardIterator last, const allocator_type& allocator = allocator_type()) : storage_(allocator) { - storage_.Initialize(IteratorValueAdapter<A, ForwardIterator>(first), - static_cast<size_t>(std::distance(first, last))); + const size_type s = static_cast<size_type>(std::distance(first, last)); + if (ABSL_PREDICT_FALSE(s > max_size())) { + ThrowStdLengthError("InlinedVector::InlinedVector failed length check"); + } + storage_.Initialize(IteratorValueAdapter<A, ForwardIterator>(first), s); } // Creates an inlined vector with elements constructed from the provided input @@ -383,7 +392,7 @@ // in both debug and non-debug builds, `std::out_of_range` will be thrown. reference at(size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { - ThrowStdOutOfRange("`InlinedVector::at(size_type)` failed bounds check"); + ThrowStdOutOfRange("InlinedVector::at(size_type) failed bounds check"); } return data()[i]; } @@ -395,8 +404,7 @@ // in both debug and non-debug builds, `std::out_of_range` will be thrown. const_reference at(size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { - ThrowStdOutOfRange( - "`InlinedVector::at(size_type) const` failed bounds check"); + ThrowStdOutOfRange("InlinedVector::at(size_type) failed bounds check"); } return data()[i]; } @@ -558,6 +566,9 @@ // // Replaces the contents of the inlined vector with `n` copies of `v`. void assign(size_type n, const_reference v) { + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::assign failed length check"); + } storage_.Assign(CopyValueAdapter<A>(std::addressof(v)), n); } @@ -574,8 +585,11 @@ template <typename ForwardIterator, EnableIfAtLeastForwardIterator<ForwardIterator> = 0> void assign(ForwardIterator first, ForwardIterator last) { - storage_.Assign(IteratorValueAdapter<A, ForwardIterator>(first), - static_cast<size_t>(std::distance(first, last))); + const size_type s = static_cast<size_type>(std::distance(first, last)); + if (ABSL_PREDICT_FALSE(s > max_size())) { + ThrowStdLengthError("InlinedVector::assign failed length check"); + } + storage_.Assign(IteratorValueAdapter<A, ForwardIterator>(first), s); } // Overload of `InlinedVector::assign(...)` to replace the contents of the @@ -601,7 +615,9 @@ // NOTE: If `n` is smaller than `size()`, extra elements are destroyed. If `n` // is larger than `size()`, new elements are value-initialized. void resize(size_type n) { - absl::base_internal::HardeningAssertLE(n, max_size()); + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::resize failed length check"); + } storage_.Resize(DefaultValueAdapter<A>(), n); } @@ -611,7 +627,9 @@ // NOTE: if `n` is smaller than `size()`, extra elements are destroyed. If `n` // is larger than `size()`, new elements are copied-constructed from `v`. void resize(size_type n, const_reference v) { - absl::base_internal::HardeningAssertLE(n, max_size()); + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::resize failed length check"); + } storage_.Resize(CopyValueAdapter<A>(std::addressof(v)), n); } @@ -638,6 +656,9 @@ const_reference v) ABSL_ATTRIBUTE_LIFETIME_BOUND { absl::base_internal::HardeningAssertGE(pos, cbegin()); absl::base_internal::HardeningAssertLE(pos, cend()); + if (ABSL_PREDICT_FALSE(n > max_size() - size())) { + ThrowStdLengthError("InlinedVector::insert failed length check"); + } if (ABSL_PREDICT_TRUE(n != 0)) { value_type dealias = v; @@ -679,11 +700,14 @@ ForwardIterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND { absl::base_internal::HardeningAssertGE(pos, cbegin()); absl::base_internal::HardeningAssertLE(pos, cend()); + const size_type s = static_cast<size_type>(std::distance(first, last)); + if (ABSL_PREDICT_FALSE(s > max_size() - size())) { + ThrowStdLengthError("InlinedVector::insert failed length check"); + } if (ABSL_PREDICT_TRUE(first != last)) { return storage_.Insert( - pos, IteratorValueAdapter<A, ForwardIterator>(first), - static_cast<size_type>(std::distance(first, last))); + pos, IteratorValueAdapter<A, ForwardIterator>(first), s); } else { return const_cast<iterator>(pos); } @@ -718,6 +742,9 @@ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { absl::base_internal::HardeningAssertGE(pos, cbegin()); absl::base_internal::HardeningAssertLE(pos, cend()); + if (ABSL_PREDICT_FALSE(size() == max_size())) { + ThrowStdLengthError("InlinedVector::emplace failed length check"); + } value_type dealias(std::forward<Args>(args)...); // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2 @@ -744,6 +771,9 @@ // `end()`, returning a `reference` to the newly emplaced element. template <typename... Args> reference emplace_back(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { + if (ABSL_PREDICT_FALSE(size() == max_size())) { + ThrowStdLengthError("InlinedVector::emplace_back failed length check"); + } return storage_.EmplaceBack(std::forward<Args>(args)...); } @@ -825,7 +855,12 @@ // `InlinedVector::reserve(...)` // // Ensures that there is enough room for at least `n` elements. - void reserve(size_type n) { storage_.Reserve(n); } + void reserve(size_type n) { + if (ABSL_PREDICT_FALSE(n > max_size())) { + ThrowStdLengthError("InlinedVector::reserve failed length check"); + } + storage_.Reserve(n); + } // `InlinedVector::shrink_to_fit()` //
diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index d6cc175..67b4368 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc
@@ -207,6 +207,42 @@ "failed bounds check"); } +TEST(IntVec, LengthThrows) { + IntVec v = {1, 2, 3}; + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.insert(v.begin(), v.max_size() + 1, 0), + std::length_error, "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL( + v.insert(v.begin(), static_cast<size_t>(-1), 0), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.resize(v.max_size() + 1), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.reserve(v.max_size() + 1), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(static_cast<void>(IntVec(v.max_size() + 1, 0)), + std::length_error, "failed length check"); +} + +template <typename T> +struct SmallMaxAllocator : std::allocator<T> { + template <typename U> + struct rebind { + using other = SmallMaxAllocator<U>; + }; + size_t max_size() const noexcept { return 2; } +}; + +TEST(IntVec, EmplaceLengthThrows) { + absl::InlinedVector<int, 1, SmallMaxAllocator<int>> v = {1, 2}; + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.push_back(3), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.emplace_back(3), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.insert(v.begin(), 3), std::length_error, + "failed length check"); + ABSL_BASE_INTERNAL_EXPECT_FAIL(v.emplace(v.begin(), 3), std::length_error, + "failed length check"); +} + TEST(IntVec, ReverseIterator) { for (size_t len = 0; len < 20; len++) { IntVec v; @@ -262,7 +298,6 @@ absl::base_internal::ScopedSetAbslHardeningForTesting hardener(true); EXPECT_DEATH_IF_SUPPORTED(v[10], ""); EXPECT_DEATH_IF_SUPPORTED(v[static_cast<size_t>(-1)], ""); - EXPECT_DEATH_IF_SUPPORTED(v.resize(v.max_size() + 1), ""); #endif }