From 1eb591e85c699b44bfda5de510e67b21bb3ce6d9 Mon Sep 17 00:00:00 2001 From: Roland Dreier Date: Sat, 23 Feb 2019 14:12:10 -0800 Subject: [PATCH] Fix deallocating never-allocated storage in vector.merge() If merge() is called on an empty vector, then priv_merge_in_new_buffer() will call deallocate() with size 0 on the old (not-yet-allocated) vector storage. This violates the Allocator requirement that the pointer passed to deallocate() must have come from a call to allocate() with the same size. Fix this by checking old_cap against 0, and also add a unit test for this bug. --- include/boost/container/vector.hpp | 4 ++- test/vector_test.cpp | 47 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/boost/container/vector.hpp b/include/boost/container/vector.hpp index 6260168..df42df5 100644 --- a/include/boost/container/vector.hpp +++ b/include/boost/container/vector.hpp @@ -2373,7 +2373,9 @@ private: pointer const old_p = this->m_holder.start(); size_type const old_cap = this->m_holder.capacity(); boost::container::destroy_alloc_n(a, boost::movelib::to_raw_pointer(old_p), old_size); - this->m_holder.deallocate(old_p, old_cap); + if (old_cap > 0) { + this->m_holder.deallocate(old_p, old_cap); + } this->m_holder.m_size = old_size + added; this->m_holder.start(new_storage); this->m_holder.capacity(new_cap); diff --git a/test/vector_test.cpp b/test/vector_test.cpp index 84a7569..2420563 100644 --- a/test/vector_test.cpp +++ b/test/vector_test.cpp @@ -152,6 +152,47 @@ struct alloc_propagate_base }}} //namespace boost::container::test +template +class check_dealloc_allocator : public std::allocator +{ + public: + bool allocate_zero_called_; + bool deallocate_called_without_allocate_; + + check_dealloc_allocator() + : std::allocator() + , allocate_zero_called_(false) + , deallocate_called_without_allocate_(false) + {} + + T* allocate(std::size_t n) + { + if (n == 0) { + allocate_zero_called_ = true; + } + return std::allocator::allocate(n); + } + + void deallocate(T* p, std::size_t n) + { + if (n == 0 && !allocate_zero_called_) { + deallocate_called_without_allocate_ = true; + } + return std::allocator::deallocate(p, n); + } +}; + +bool test_merge_empty_free() +{ + vector source; + source.emplace_back(1); + + vector< int, check_dealloc_allocator > empty; + empty.merge(source.begin(), source.end()); + + return empty.get_stored_allocator().deallocate_called_without_allocate_; +} + int main() { { @@ -285,5 +326,11 @@ int main() } } #endif + + if (test_merge_empty_free()) { + std::cerr << "Merge into empty vector test failed" << std::endl; + return 1; + } + return 0; }