Index: libcxx/include/vector =================================================================== --- libcxx/include/vector +++ libcxx/include/vector @@ -858,13 +858,49 @@ const void *__new_mid) const { - if (__beg && is_same::value) + if (__beg && is_same::value) { + _LIBCPP_ASSERT( + (!(reinterpret_cast(__beg) & static_cast(7))), + "Beginning of range isn't aligned appropriately for " + "AddressSanitizer"); + _LIBCPP_ASSERT( + (!(reinterpret_cast(__end) & static_cast(7))), + "End of range isn't aligned appropriately for AddressSanitizer"); __sanitizer_annotate_contiguous_container(__beg, __end, __old_mid, __new_mid); + } + } + + // Even with constraints on the alignment of the default allocator it's still + // possible for the end of the range to end up on a non-8B boundary, ie in the + // case that _Tp is an int and a single element is allocated. To support this + // we round up the size of any allocations to the boundary alignment. + size_type __align_size(size_type __size) const _NOEXCEPT { + // This is what it should be, but we can't get to gcd from here (and it requires + // C++11 to use). Also note that we can't do these enum tricks at class scope + // because `value_type` might be incomplete. + //enum { __asan_size_align = 8 / gcd(8, sizeof(value_type)) }; + enum {__size_mod_8 = sizeof(value_type) % 8}; + enum {__alignment = (__size_mod_8 & 1) ? 8 + : (__size_mod_8 & 2) ? 4 + : (__size_mod_8 & 4) ? 2 + : 1}; + static_assert(((__alignment * sizeof(value_type)) % 8) == 0, + "End of allocation won't be aligned correctly"); + + const size_type __new_size = ((__size - 1) | (__alignment - 1)) + 1; + const size_type __ms = max_size(); + if (__new_size < __size || __new_size > __ms) + this->__throw_length_error(); + return __new_size; } #else _LIBCPP_INLINE_VISIBILITY void __annotate_contiguous_container(const void*, const void*, const void*, const void*) const _NOEXCEPT {} + _LIBCPP_INLINE_VISIBILITY + size_type __align_size(size_type __new_size) const _NOEXCEPT { + return __new_size; + } #endif _LIBCPP_INLINE_VISIBILITY void __annotate_new(size_type __current_size) const _NOEXCEPT { @@ -989,6 +1025,7 @@ void vector<_Tp, _Allocator>::__vallocate(size_type __n) { + __n = __align_size(__n); if (__n > max_size()) this->__throw_length_error(); this->__begin_ = this->__end_ = __alloc_traits::allocate(this->__alloc(), __n); @@ -1089,7 +1126,8 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + __n), size(), __a); + __split_buffer __v( + __align_size(__recommend(size() + __n)), size(), __a); __v.__construct_at_end(__n); __swap_out_circular_buffer(__v); } @@ -1108,7 +1146,8 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + __n), size(), __a); + __split_buffer __v( + __align_size(__recommend(size() + __n)), size(), __a); __v.__construct_at_end(__n, __x); __swap_out_circular_buffer(__v); } @@ -1583,7 +1622,8 @@ if (__n > capacity()) { allocator_type& __a = this->__alloc(); - __split_buffer __v(__n, size(), __a); + __split_buffer __v(__align_size(__n), + size(), __a); __swap_out_circular_buffer(__v); } } @@ -1599,7 +1639,8 @@ { #endif // _LIBCPP_NO_EXCEPTIONS allocator_type& __a = this->__alloc(); - __split_buffer __v(size(), size(), __a); + __split_buffer __v( + __align_size(size()), size(), __a); __swap_out_circular_buffer(__v); #ifndef _LIBCPP_NO_EXCEPTIONS } @@ -1620,7 +1661,8 @@ #endif { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + 1), size(), __a); + __split_buffer __v( + __align_size(__recommend(size() + 1)), size(), __a); // __v.push_back(_VSTD::forward<_Up>(__x)); __alloc_traits::construct(__a, _VSTD::__to_address(__v.__end_), _VSTD::forward<_Up>(__x)); __v.__end_++; @@ -1661,7 +1703,8 @@ vector<_Tp, _Allocator>::__emplace_back_slow_path(_Args&&... __args) { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + 1), size(), __a); + __split_buffer __v( + __align_size(__recommend(size() + 1)), size(), __a); // __v.emplace_back(_VSTD::forward<_Args>(__args)...); __alloc_traits::construct(__a, _VSTD::__to_address(__v.__end_), _VSTD::forward<_Args>(__args)...); __v.__end_++; @@ -1788,7 +1831,8 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __split_buffer __v( + __align_size(__recommend(size() + 1)), __p - this->__begin_, __a); __v.push_back(__x); __p = __swap_out_circular_buffer(__v, __p); } @@ -1822,7 +1866,8 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __split_buffer __v( + __align_size(__recommend(size() + 1)), __p - this->__begin_, __a); __v.push_back(_VSTD::move(__x)); __p = __swap_out_circular_buffer(__v, __p); } @@ -1856,7 +1901,8 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __split_buffer __v( + __align_size(__recommend(size() + 1)), __p - this->__begin_, __a); __v.emplace_back(_VSTD::forward<_Args>(__args)...); __p = __swap_out_circular_buffer(__v, __p); } @@ -1899,7 +1945,9 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + __n), __p - this->__begin_, __a); + __split_buffer __v( + __align_size(__recommend(size() + __n)), __p - this->__begin_, + __a); __v.__construct_at_end(__n, __x); __p = __swap_out_circular_buffer(__v, __p); } @@ -2005,7 +2053,9 @@ else { allocator_type& __a = this->__alloc(); - __split_buffer __v(__recommend(size() + __n), __p - this->__begin_, __a); + __split_buffer __v( + __align_size(__recommend(size() + __n)), __p - this->__begin_, + __a); __v.__construct_at_end(__first, __last); __p = __swap_out_circular_buffer(__v, __p); } Index: libcxx/test/libcxx/containers/sequences/vector/asan_operator_overloads.pass.cpp =================================================================== --- /dev/null +++ libcxx/test/libcxx/containers/sequences/vector/asan_operator_overloads.pass.cpp @@ -0,0 +1,210 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: c++98, c++03 + +#include +#include +#include +#include + +#include "asan_testing.h" + +constexpr std::size_t kAlignment = 16; +alignas(kAlignment) uint8_t sBuffer[10 * 1024]; +std::size_t sBufferOffset; + +void* BufferNew(std::size_t size) { + // Adjust size to alignment and add space to save the size to + size = ((size - 1) | (kAlignment - 1)) + 1; + size += kAlignment; + + // "Allocate" + uint8_t* ptr = sBuffer + sBufferOffset; + assert(ptr <= std::end(sBuffer)); + sBufferOffset += size; + + // Save the size + *reinterpret_cast(ptr) = size; + ptr += kAlignment; + + return ptr; +} + +void BufferDelete(void* p) noexcept { + volatile uint8_t* ptr = reinterpret_cast(p); + + // Read off the size + ptr -= kAlignment; + std::size_t size = *reinterpret_cast(ptr); + + // Zero out the contents + while (size > 0) { + *ptr++ = 0; + size--; + } +} + +void BufferReset() { sBufferOffset = 0; } + +// Rather than overloading operator new/delete (since it's giving me linker +// errors in the test suite) we'll instead trick libc++ into thinking that +// the allocator FakeAllocator is the default allocator. +#if 0 +void* operator new(std::size_t size) { return BufferNew(size); } + +void operator delete(void* ptr) noexcept { BufferDelete(ptr); } + +template +using FakeAllocator = std::allocator; +#else +template +struct FakeAllocator { + using value_type = T; + + FakeAllocator() = default; + template + FakeAllocator(const FakeAllocator

&) noexcept {} + + T* allocate(std::size_t n) { + return static_cast(BufferNew(n * sizeof(T))); + } + void deallocate(void* p, std::size_t) noexcept { BufferDelete(p); } +}; + +namespace std { +template +struct is_same, allocator > + : std::integral_constant {}; +template +struct is_same, FakeAllocator > + : is_same, allocator > {}; +} // namespace std +#endif + +template +void test_vector() { + using V = std::vector >; + constexpr std::size_t kLoopCount = 32; + + // resize + { + BufferReset(); + V v; + for (std::size_t i = 0; i < kLoopCount; ++i) { + v.resize(i); + assert(is_contiguous_container_asan_correct(v)); + assert(v.size() == i); + } + // Make sure this isn't optimised away + volatile T t = v[0]; + (void)t; + } + + // constructor + { + BufferReset(); + for (std::size_t i = 1; i < kLoopCount; ++i) { + V v(i); + assert(is_contiguous_container_asan_correct(v)); + assert(v.size() == i); + // Make sure this isn't optimised away + volatile T t = v[0]; + (void)t; + } + } + + // back + { + BufferReset(); + V v; + for (std::size_t i = 0; i < kLoopCount; ++i) { + v.emplace_back(); + assert(is_contiguous_container_asan_correct(v)); + assert(v.size() == i + 1); + } + // Make sure this isn't optimised away + volatile T t = v[0]; + (void)t; + } + + // front + { + BufferReset(); + V v; + for (std::size_t i = 0; i < kLoopCount; ++i) { + v.insert(v.begin(), T{}); + assert(is_contiguous_container_asan_correct(v)); + assert(v.size() == i + 1); + } + // Make sure this isn't optimised away + volatile T t = v[0]; + (void)t; + } + + // middle + { + BufferReset(); + V v; + for (std::size_t i = 0; i < kLoopCount; ++i) { + v.insert(std::next(v.begin(), i / 2), T{}); + assert(is_contiguous_container_asan_correct(v)); + assert(v.size() == i + 1); + } + // Make sure this isn't optimised away + volatile T t = v[0]; + (void)t; + } +} + +template +struct SizedElement { + uint8_t data[N]; +}; + +template +void test_sized_element() { + using T = SizedElement; + test_vector(); +} + +int main(int, char**) { + test_sized_element<1>(); + test_sized_element<2>(); + test_sized_element<3>(); + test_sized_element<4>(); + test_sized_element<5>(); + test_sized_element<6>(); + test_sized_element<7>(); + test_sized_element<8>(); + + test_sized_element<9>(); + test_sized_element<10>(); + test_sized_element<11>(); + test_sized_element<12>(); + test_sized_element<13>(); + test_sized_element<14>(); + test_sized_element<15>(); + test_sized_element<16>(); + + //test_vector(); + + test_vector(); + test_vector(); + test_vector(); + test_vector(); + test_vector(); + + test_vector(); + test_vector(); + test_vector(); + test_vector(); + test_vector(); + + return 0; +} Index: libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit.pass.cpp =================================================================== --- libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit.pass.cpp +++ libcxx/test/std/containers/sequences/vector/vector.capacity/shrink_to_fit.pass.cpp @@ -24,19 +24,35 @@ v.push_back(1); assert(is_contiguous_container_asan_correct(v)); v.shrink_to_fit(); +#ifndef _LIBCPP_HAS_NO_ASAN + assert(v.capacity() == 102); +#else assert(v.capacity() == 101); +#endif assert(v.size() == 101); assert(is_contiguous_container_asan_correct(v)); } + // When ASAN is enabled the capacity grows to 102 rather than 101 after + // the call to push_back(). This leaves us with not enough space in the + // allocator to shrink it correctly so it throws internally, so disable + // this test when ASAN is enabled with no exceptions. +#if defined(_LIBCPP_HAS_NO_ASAN) || !defined(_LIBCPP_NO_EXCEPTIONS) { std::vector > v(100); v.push_back(1); assert(is_contiguous_container_asan_correct(v)); v.shrink_to_fit(); +#ifndef _LIBCPP_HAS_NO_ASAN + // As above, shrink_to_fit() will throw internally, leaving us with + // a capacity of 200 from libc++'s 2x growth factor + assert(v.capacity() == 200); +#else assert(v.capacity() == 101); +#endif assert(v.size() == 101); assert(is_contiguous_container_asan_correct(v)); } +#endif #ifndef _LIBCPP_NO_EXCEPTIONS { std::vector > v(100); @@ -54,7 +70,11 @@ v.push_back(1); assert(is_contiguous_container_asan_correct(v)); v.shrink_to_fit(); +#ifndef _LIBCPP_HAS_NO_ASAN + assert(v.capacity() == 102); +#else assert(v.capacity() == 101); +#endif assert(v.size() == 101); assert(is_contiguous_container_asan_correct(v)); }