From 3d48196e23545aab26b9b172bf632d7d047ab1f1 Mon Sep 17 00:00:00 2001 From: Advenam Tacet Date: Fri, 17 Nov 2023 01:51:21 +0100 Subject: [PATCH 01/10] [ASan][libc++] std::basic_string annotations This commit turns on basic annotations for std::basic_string, similar to those in std::vector and std::deque. Originally proposed here: https://reviews.llvm.org/D132769 Related patches: Turning on annotations for short strings: https://reviews.llvm.org/D147680 Turning on annotations for all allocators: https://reviews.llvm.org/D146214 This revision is a part of a series of patches extending AddressSanitizer C++ container overflow detection capabilities by adding annotations, similar to those existing in std::vector, to std::string and std::deque collections. These changes allow ASan to detect cases when the instrumented program accesses memory which is internally allocated by the collection but is still not in-use (accesses before or after the stored elements for std::deque, or between the size and capacity bounds for std::string). The motivation for the research and those changes was a bug, found by Trail of Bits, in a real code where an out-of-bounds read could happen as two strings were compared via a std::equals function that took iter1_begin, iter1_end, iter2_begin iterators (with a custom comparison function). When object iter1 was longer than iter2, read out-of-bounds on iter2 could happen. Container sanitization would detect it. This Pull Request adds annotations for `std::basic_string`. Long string is very similar to std::vector, and therefore works well with `__sanitizer_annotate_contiguous_container` from LLVM 15 and therefore annotations works if that implementation is compiled with previous LLVM. However, only the standard allocator is supported. As D132522 extended possibilities of `__sanitizer_annotate_contiguous_container`, now annotations may be supported with all allocators, but that support will be added in a next PR. Only strings with default allocator are annotated with that commit. If you have any questions, please email: - advenam.tacet@trailofbits.com - disconnect3d@trailofbits.com --- libcxx/include/string | 274 ++++++++++++++---- .../string.capacity/capacity.pass.cpp | 9 + .../string.capacity/clear.pass.cpp | 7 + .../string.capacity/reserve.pass.cpp | 3 + .../reserve_size.asan.pass.cpp | 66 +++++ .../string.capacity/reserve_size.pass.cpp | 3 + .../resize_and_overwrite.pass.cpp | 4 + .../string.capacity/resize_size.pass.cpp | 3 + .../string.capacity/resize_size_char.pass.cpp | 13 + .../string.capacity/shrink_to_fit.pass.cpp | 8 + .../string.cons/T_size_size.pass.cpp | 3 + .../basic.string/string.cons/alloc.pass.cpp | 6 + .../string.cons/brace_assignment.pass.cpp | 3 + .../string.cons/char_assignment.pass.cpp | 3 + .../basic.string/string.cons/copy.pass.cpp | 4 + .../string.cons/copy_alloc.pass.cpp | 4 + .../string.cons/copy_assignment.pass.cpp | 4 + .../basic.string/string.cons/default.pass.cpp | 2 + .../string.cons/initializer_list.pass.cpp | 10 + .../initializer_list_assignment.pass.cpp | 14 + .../string.cons/iter_alloc.pass.cpp | 3 + .../string.cons/iter_alloc_deduction.pass.cpp | 6 + .../basic.string/string.cons/move.pass.cpp | 4 + .../string.cons/move_alloc.pass.cpp | 4 + .../string.cons/move_assignment.pass.cpp | 5 + .../string.cons/pointer_alloc.pass.cpp | 4 + .../string.cons/pointer_assignment.pass.cpp | 3 + .../string.cons/pointer_size_alloc.pass.cpp | 4 + .../string.cons/size_char_alloc.pass.cpp | 4 + .../string.cons/string_view.pass.cpp | 6 + .../string_view_assignment.pass.cpp | 3 + .../basic.string/string.cons/substr.pass.cpp | 5 + .../string_append/initializer_list.pass.cpp | 3 + .../string_append/iterator.pass.cpp | 3 + .../string_append/pointer.pass.cpp | 3 + .../string_append/pointer_size.pass.cpp | 3 + .../string_append/push_back.pass.cpp | 3 + .../string_append/size_char.pass.cpp | 3 + .../string_append/string.pass.cpp | 3 + .../string_append/string_size_size.pass.cpp | 3 + .../string_assign/T_size_size.pass.cpp | 3 + .../string_assign/initializer_list.pass.cpp | 3 + .../string_assign/iterator.pass.cpp | 3 + .../string_assign/pointer.pass.cpp | 3 + .../string_assign/pointer_size.pass.cpp | 3 + .../string_assign/size_char.pass.cpp | 3 + .../string_assign/string.pass.cpp | 3 + .../string_assign/string_size_size.pass.cpp | 3 + .../string_copy/copy.pass.cpp | 5 + .../string_erase/iter.pass.cpp | 3 + .../string_erase/iter_iter.pass.cpp | 3 + .../string_erase/pop_back.asan.pass.cpp | 50 ++++ .../string_erase/pop_back.pass.cpp | 3 + .../string_erase/size_size.pass.cpp | 3 + .../string_insert/iter_char.pass.cpp | 4 + .../iter_initializer_list.pass.cpp | 3 + .../string_insert/iter_iter_iter.pass.cpp | 3 + .../string_insert/iter_size_char.pass.cpp | 3 + .../string_insert/size_pointer.pass.cpp | 3 + .../string_insert/size_pointer_size.pass.cpp | 3 + .../string_insert/size_size_char.pass.cpp | 3 + .../string_insert/size_string.pass.cpp | 3 + .../size_string_size_size.pass.cpp | 3 + .../string_op_plus_equal/char.pass.cpp | 3 + .../initializer_list.pass.cpp | 3 + .../string_op_plus_equal/pointer.pass.cpp | 3 + .../string_op_plus_equal/string.pass.cpp | 4 + .../string_replace/iter_iter_string.pass.cpp | 3 + .../size_size_T_size_size.pass.cpp | 3 + .../string_replace/size_size_pointer.pass.cpp | 3 + .../size_size_pointer_size.pass.cpp | 3 + .../size_size_size_char.pass.cpp | 3 + .../string_replace/size_size_string.pass.cpp | 3 + .../size_size_string_size_size.pass.cpp | 3 + .../size_size_string_view.pass.cpp | 3 + .../string_swap/swap.asan.pass.cpp | 87 ++++++ .../string_swap/swap.pass.cpp | 4 + .../string.special/swap.pass.cpp | 4 + .../string_op+/char_string.pass.cpp | 3 + .../string_op+/string_char.pass.cpp | 3 + .../string_op+/string_pointer.pass.cpp | 3 + .../string_op+/string_string.pass.cpp | 3 + libcxx/test/support/asan_testing.h | 63 +++- 83 files changed, 780 insertions(+), 64 deletions(-) create mode 100644 libcxx/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp create mode 100644 libcxx/test/std/strings/basic.string/string.modifiers/string_erase/pop_back.asan.pass.cpp create mode 100644 libcxx/test/std/strings/basic.string/string.modifiers/string_swap/swap.asan.pass.cpp diff --git a/libcxx/include/string b/libcxx/include/string index 9c97abefcb8d0..93725c2acc619 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -649,6 +649,17 @@ basic_string operator""s( const char32_t *str, size_t len ); _LIBCPP_PUSH_MACROS #include <__undef_macros> +#ifndef _LIBCPP_HAS_NO_ASAN +# define _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS __attribute__((__no_sanitize__("address"))) +// This macro disables AddressSanitizer (ASan) instrumentation for a specific function, +// allowing memory accesses that would normally trigger ASan errors to proceed without crashing. +// This is useful for accessing parts of objects memory, which should not be accessed, +// such as unused bytes in short strings, that should never be accessed +// by other parts of the program. +#else +# define _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS +#endif +#define _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED false _LIBCPP_BEGIN_NAMESPACE_STD @@ -706,6 +717,9 @@ struct __init_with_sentinel_tag {}; template class basic_string { +private: + using __default_allocator_type = allocator<_CharT>; + public: typedef basic_string __self; typedef basic_string_view<_CharT, _Traits> __self_view; @@ -892,34 +906,46 @@ public: #endif : __r_(__value_init_tag(), __a) {} - _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const basic_string& __str) + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string(const basic_string& __str) : __r_(__default_init_tag(), __alloc_traits::select_on_container_copy_construction(__str.__alloc())) { if (!__str.__is_long()) + { __r_.first() = __str.__r_.first(); + __annotate_new(__get_short_size()); + } else __init_copy_ctor_external(std::__to_address(__str.__get_long_pointer()), __str.__get_long_size()); } - _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(const basic_string& __str, const allocator_type& __a) + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string(const basic_string& __str, const allocator_type& __a) : __r_(__default_init_tag(), __a) { if (!__str.__is_long()) + { __r_.first() = __str.__r_.first(); + __annotate_new(__get_short_size()); + } else __init_copy_ctor_external(std::__to_address(__str.__get_long_pointer()), __str.__get_long_size()); } #ifndef _LIBCPP_CXX03_LANG - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(basic_string&& __str) + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + basic_string(basic_string&& __str) # if _LIBCPP_STD_VER <= 14 _NOEXCEPT_(is_nothrow_move_constructible::value) # else _NOEXCEPT # endif - : __r_(std::move(__str.__r_)) { + // Turning off ASan instrumentation for variable initialization with _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS + // is inconsistent and that initialization may be annotated. + // Therefore, to copy __str memory, we have to unpoison it first (if object is poisoned and not external buffer, + // so only the short string case). + : __r_( ( (__str.__is_long() ? 0 : (__str.__annotate_delete(), 0)), std::move(__str.__r_)) ) { __str.__r_.first() = __rep(); } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string(basic_string&& __str, const allocator_type& __a) + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS + basic_string(basic_string&& __str, const allocator_type& __a) : __r_(__default_init_tag(), __a) { if (__str.__is_long() && __a != __str.__alloc()) // copy, not move __init(std::__to_address(__str.__get_long_pointer()), __str.__get_long_size()); @@ -927,6 +953,9 @@ public: if (__libcpp_is_constant_evaluated()) __r_.first() = __rep(); __r_.first() = __str.__r_.first(); + if (!__str.__is_long()) { + __str.__annotate_delete(); + } __str.__r_.first() = __rep(); } } @@ -969,11 +998,11 @@ public: } #if _LIBCPP_STD_VER >= 23 - _LIBCPP_HIDE_FROM_ABI constexpr + _LIBCPP_HIDE_FROM_ABI _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS constexpr basic_string(basic_string&& __str, size_type __pos, const _Allocator& __alloc = _Allocator()) : basic_string(std::move(__str), __pos, npos, __alloc) {} - _LIBCPP_HIDE_FROM_ABI constexpr + _LIBCPP_HIDE_FROM_ABI _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS constexpr basic_string(basic_string&& __str, size_type __pos, size_type __n, const _Allocator& __alloc = _Allocator()) : __r_(__default_init_tag(), __alloc) { if (__pos > __str.size()) @@ -1085,6 +1114,7 @@ public: #endif // _LIBCPP_CXX03_LANG inline _LIBCPP_CONSTEXPR_SINCE_CXX20 ~basic_string() { + __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); } @@ -1102,8 +1132,8 @@ public: } #ifndef _LIBCPP_CXX03_LANG - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(basic_string&& __str) - _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) { + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string& + operator=(basic_string&& __str) _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) { __move_assign(__str, integral_constant()); return *this; } @@ -1116,7 +1146,7 @@ public: #if _LIBCPP_STD_VER >= 23 basic_string& operator=(nullptr_t) = delete; #endif - _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(value_type __c); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string& operator=(value_type __c); _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator begin() _NOEXCEPT @@ -1335,7 +1365,7 @@ public: } #if _LIBCPP_STD_VER >= 20 - _LIBCPP_HIDE_FROM_ABI constexpr + _LIBCPP_HIDE_FROM_ABI constexpr _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS void __move_assign(basic_string&& __str, size_type __pos, size_type __len) { // Pilfer the allocation from __str. _LIBCPP_ASSERT_INTERNAL(__alloc() == __str.__alloc(), "__move_assign called with wrong allocator"); @@ -1351,7 +1381,7 @@ public: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& assign(const basic_string& __str) { return *this = __str; } #ifndef _LIBCPP_CXX03_LANG - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string& assign(basic_string&& __str) _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) {*this = std::move(__str); return *this;} @@ -1742,7 +1772,7 @@ private: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __shrink_or_extend(size_type __target_capacity); - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS bool __is_long() const _NOEXCEPT { if (__libcpp_is_constant_evaluated() && __builtin_constant_p(__r_.first().__l.__is_long_)) { return __r_.first().__l.__is_long_; @@ -1782,6 +1812,7 @@ private: value_type* __p; if (__cap - __sz >= __n) { + __annotate_increase(__n); __p = std::__to_address(__get_pointer()); size_type __n_move = __sz - __ip; if (__n_move != 0) @@ -1808,7 +1839,7 @@ private: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 allocator_type& __alloc() _NOEXCEPT { return __r_.second(); } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const allocator_type& __alloc() const _NOEXCEPT { return __r_.second(); } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS void __set_short_size(size_type __s) _NOEXCEPT { _LIBCPP_ASSERT_INTERNAL( __s < __min_cap, "__s should never be greater than or equal to the short string capacity"); @@ -1816,7 +1847,7 @@ private: __r_.first().__s.__is_long_ = false; } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS size_type __get_short_size() const _NOEXCEPT { _LIBCPP_ASSERT_INTERNAL( !__r_.first().__s.__is_long_, "String has to be short when trying to get the short size"); @@ -1866,6 +1897,45 @@ private: const_pointer __get_pointer() const _NOEXCEPT {return __is_long() ? __get_long_pointer() : __get_short_pointer();} + // The following functions are no-ops outside of AddressSanitizer mode. +#ifndef _LIBCPP_HAS_NO_ASAN + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_contiguous_container( + const void* __old_mid, const void* __new_mid) const { + const void* __begin = data(); + const void* __end = data() + capacity() + 1; + if (!__libcpp_is_constant_evaluated() && __begin != nullptr && is_same::value) + __sanitizer_annotate_contiguous_container(__begin, __end, __old_mid, __new_mid); + } +#else + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void + __annotate_contiguous_container(const void*, const void*) const {} +#endif + + // ASan: short string is poisoned if and only if this function returns true. + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __asan_short_string_is_annotated() const _NOEXCEPT { + return _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED && !__libcpp_is_constant_evaluated(); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_new(size_type __current_size) const _NOEXCEPT { + if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long())) + __annotate_contiguous_container(data() + capacity() + 1, data() + __current_size + 1); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_delete() const _NOEXCEPT { + if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long())) + __annotate_contiguous_container(data() + size() + 1, data() + capacity() + 1); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_increase(size_type __n) const _NOEXCEPT { + if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long())) + __annotate_contiguous_container(data() + size() + 1, data() + size() + 1 + __n); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_shrink(size_type __old_size) const _NOEXCEPT { + if (!__libcpp_is_constant_evaluated() && (__asan_short_string_is_annotated() || __is_long())) + __annotate_contiguous_container(data() + __old_size + 1, data() + size() + 1); + } + template static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type __align_it(size_type __s) _NOEXCEPT @@ -1968,6 +2038,7 @@ private: } else { + __annotate_delete(); allocator_type __a = __str.__alloc(); auto __allocation = std::__allocate_at_least(__a, __str.__get_long_cap()); __begin_lifetime(__allocation.ptr, __allocation.count); @@ -1977,6 +2048,7 @@ private: __set_long_pointer(__allocation.ptr); __set_long_cap(__allocation.count); __set_long_size(__str.size()); + __annotate_new(__get_long_size()); } } } @@ -2024,18 +2096,28 @@ private: // Assigns the value in __s, guaranteed to be __n < __min_cap in length. inline _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& __assign_short(const value_type* __s, size_type __n) { + size_type __old_size = size(); + if (__n > __old_size) + __annotate_increase(__n - __old_size); pointer __p = __is_long() ? (__set_long_size(__n), __get_long_pointer()) : (__set_short_size(__n), __get_short_pointer()); traits_type::move(std::__to_address(__p), __s, __n); traits_type::assign(__p[__n], value_type()); + if (__old_size > __n) + __annotate_shrink(__old_size); return *this; } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& __null_terminate_at(value_type* __p, size_type __newsz) { + size_type __old_size = size(); + if (__newsz > __old_size) + __annotate_increase(__newsz - __old_size); __set_size(__newsz); traits_type::assign(__p[__newsz], value_type()); + if (__old_size > __newsz) + __annotate_shrink(__old_size); return *this; } @@ -2142,6 +2224,7 @@ void basic_string<_CharT, _Traits, _Allocator>::__init(const value_type* __s, } traits_type::copy(std::__to_address(__p), __s, __sz); traits_type::assign(__p[__sz], value_type()); + __annotate_new(__sz); } template @@ -2170,6 +2253,7 @@ basic_string<_CharT, _Traits, _Allocator>::__init(const value_type* __s, size_ty } traits_type::copy(std::__to_address(__p), __s, __sz); traits_type::assign(__p[__sz], value_type()); + __annotate_new(__sz); } template @@ -2194,6 +2278,7 @@ void basic_string<_CharT, _Traits, _Allocator>::__init_copy_ctor_external( __set_long_size(__sz); } traits_type::copy(std::__to_address(__p), __s, __sz + 1); + __annotate_new(__sz); } template @@ -2223,6 +2308,7 @@ basic_string<_CharT, _Traits, _Allocator>::__init(size_type __n, value_type __c) } traits_type::assign(std::__to_address(__p), __n, __c); traits_type::assign(__p[__n], value_type()); + __annotate_new(__n); } template @@ -2249,6 +2335,7 @@ void basic_string<_CharT, _Traits, _Allocator>::__init_with_sentinel(_InputItera } catch (...) { + __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); throw; @@ -2304,11 +2391,13 @@ void basic_string<_CharT, _Traits, _Allocator>::__init_with_size( } catch (...) { + __annotate_delete(); if (__is_long()) __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); throw; } #endif // _LIBCPP_HAS_NO_EXCEPTIONS + __annotate_new(__sz); } template @@ -2325,6 +2414,7 @@ basic_string<_CharT, _Traits, _Allocator>::__grow_by_and_replace size_type __cap = __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1; + __annotate_delete(); auto __allocation = std::__allocate_at_least(__alloc(), __cap + 1); pointer __p = __allocation.ptr; __begin_lifetime(__p, __allocation.count); @@ -2344,6 +2434,7 @@ basic_string<_CharT, _Traits, _Allocator>::__grow_by_and_replace __old_sz = __n_copy + __n_add + __sec_cp_sz; __set_long_size(__old_sz); traits_type::assign(__p[__old_sz], value_type()); + __annotate_new(__old_cap + __delta_cap); } // __grow_by is deprecated because it does not set the size. It may not update the size when the size is changed, and it @@ -2366,6 +2457,7 @@ basic_string<_CharT, _Traits, _Allocator>::__grow_by(size_type __old_cap, size_t size_type __cap = __old_cap < __ms / 2 - __alignment ? __recommend(std::max(__old_cap + __delta_cap, 2 * __old_cap)) : __ms - 1; + __annotate_delete(); auto __allocation = std::__allocate_at_least(__alloc(), __cap + 1); pointer __p = __allocation.ptr; __begin_lifetime(__p, __allocation.count); @@ -2396,6 +2488,7 @@ basic_string<_CharT, _Traits, _Allocator>::__grow_by_without_replace( __grow_by(__old_cap, __delta_cap, __old_sz, __n_copy, __n_del, __n_add); _LIBCPP_SUPPRESS_DEPRECATED_POP __set_long_size(__old_sz - __n_del + __n_add); + __annotate_new(__old_sz - __n_del + __n_add); } // assign @@ -2408,10 +2501,15 @@ basic_string<_CharT, _Traits, _Allocator>::__assign_no_alias( const value_type* __s, size_type __n) { size_type __cap = __is_short ? static_cast(__min_cap) : __get_long_cap(); if (__n < __cap) { + size_type __old_size = __is_short ? __get_short_size() : __get_long_size(); + if (__n > __old_size) + __annotate_increase(__n - __old_size); pointer __p = __is_short ? __get_short_pointer() : __get_long_pointer(); __is_short ? __set_short_size(__n) : __set_long_size(__n); traits_type::copy(std::__to_address(__p), __s, __n); traits_type::assign(__p[__n], value_type()); + if (__old_size > __n) + __annotate_shrink(__old_size); } else { size_type __sz = __is_short ? __get_short_size() : __get_long_size(); __grow_by_and_replace(__cap - 1, __n - __cap + 1, __sz, 0, __sz, __n, __s); @@ -2426,6 +2524,9 @@ basic_string<_CharT, _Traits, _Allocator>::__assign_external( const value_type* __s, size_type __n) { size_type __cap = capacity(); if (__cap >= __n) { + size_type __old_size = size(); + if (__n > __old_size) + __annotate_increase(__n - __old_size); value_type* __p = std::__to_address(__get_pointer()); traits_type::move(__p, __s, __n); return __null_terminate_at(__p, __n); @@ -2453,11 +2554,15 @@ basic_string<_CharT, _Traits, _Allocator>& basic_string<_CharT, _Traits, _Allocator>::assign(size_type __n, value_type __c) { size_type __cap = capacity(); + size_type __old_size = size(); if (__cap < __n) { size_type __sz = size(); __grow_by_without_replace(__cap, __n - __cap, __sz, 0, __sz); + __annotate_increase(__n); } + else if(__n > __old_size) + __annotate_increase(__n - __old_size); value_type* __p = std::__to_address(__get_pointer()); traits_type::assign(__p, __n, __c); return __null_terminate_at(__p, __n); @@ -2468,24 +2573,26 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string<_CharT, _Traits, _Allocator>& basic_string<_CharT, _Traits, _Allocator>::operator=(value_type __c) { - pointer __p; - if (__is_long()) - { - __p = __get_long_pointer(); - __set_long_size(1); - } - else - { - __p = __get_short_pointer(); - __set_short_size(1); - } - traits_type::assign(*__p, __c); - traits_type::assign(*++__p, value_type()); - return *this; + pointer __p; + size_type __old_size = size(); + if (__old_size == 0) + __annotate_increase(1); + if (__is_long()) { + __p = __get_long_pointer(); + __set_long_size(1); + } else { + __p = __get_short_pointer(); + __set_short_size(1); + } + traits_type::assign(*__p, __c); + traits_type::assign(*++__p, value_type()); + if (__old_size > 1) + __annotate_shrink(__old_size); + return *this; } template -_LIBCPP_CONSTEXPR_SINCE_CXX20 +_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string<_CharT, _Traits, _Allocator>& basic_string<_CharT, _Traits, _Allocator>::operator=(const basic_string& __str) { @@ -2493,7 +2600,12 @@ basic_string<_CharT, _Traits, _Allocator>::operator=(const basic_string& __str) __copy_assign_alloc(__str); if (!__is_long()) { if (!__str.__is_long()) { + size_type __old_size = __get_short_size(); + if (__get_short_size() < __str.__get_short_size()) + __annotate_increase(__str.__get_short_size() - __get_short_size()); __r_.first() = __str.__r_.first(); + if (__old_size > __get_short_size()) + __annotate_shrink(__old_size); } else { return __assign_no_alias(__str.data(), __str.size()); } @@ -2519,7 +2631,7 @@ basic_string<_CharT, _Traits, _Allocator>::__move_assign(basic_string& __str, fa } template -inline _LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS void basic_string<_CharT, _Traits, _Allocator>::__move_assign(basic_string& __str, true_type) #if _LIBCPP_STD_VER >= 17 @@ -2528,6 +2640,7 @@ basic_string<_CharT, _Traits, _Allocator>::__move_assign(basic_string& __str, tr _NOEXCEPT_(is_nothrow_move_assignable::value) #endif { + __annotate_delete(); if (__is_long()) { __alloc_traits::deallocate(__alloc(), __get_long_pointer(), __get_long_cap()); @@ -2538,10 +2651,31 @@ basic_string<_CharT, _Traits, _Allocator>::__move_assign(basic_string& __str, tr } #endif } + size_type __str_old_size = __str.size(); + bool __str_was_short = !__str.__is_long(); + __move_assign_alloc(__str); __r_.first() = __str.__r_.first(); __str.__set_short_size(0); traits_type::assign(__str.__get_short_pointer()[0], value_type()); + + if (__str_was_short && this != &__str) + __str.__annotate_shrink(__str_old_size); + else + // ASan annotations: was long, so object memory is unpoisoned as new. + // Or is same as *this, and __annotate_delete() was called. + __str.__annotate_new(0); + + // ASan annotations: Guard against `std::string s; s = std::move(s);` + // You can find more here: https://en.cppreference.com/w/cpp/utility/move + // Quote: "Unless otherwise specified, all standard library objects that have been moved + // from are placed in a "valid but unspecified state", meaning the object's class + // invariants hold (so functions without preconditions, such as the assignment operator, + // can be safely used on the object after it was moved from):" + // Quote: "v = std::move(v); // the value of v is unspecified" + if (!__is_long() && &__str != this) + // If it is long string, delete was never called. + __annotate_new(__get_short_size()); } #endif @@ -2587,6 +2721,7 @@ basic_string<_CharT, _Traits, _Allocator>::__assign_trivial(_Iterator __first, _ _LIBCPP_ASSERT_INTERNAL( __string_is_trivial_iterator<_Iterator>::value, "The iterator type given to `__assign_trivial` must be trivial"); + size_type __old_size = size(); size_type __cap = capacity(); if (__cap < __n) { // Unlike `append` functions, if the input range points into the string itself, there is no case that the input @@ -2597,12 +2732,17 @@ basic_string<_CharT, _Traits, _Allocator>::__assign_trivial(_Iterator __first, _ // object itself stays valid even if reallocation happens. size_type __sz = size(); __grow_by_without_replace(__cap, __n - __cap, __sz, 0, __sz); + __annotate_increase(__n); } + else if (__n > __old_size) + __annotate_increase(__n - __old_size); pointer __p = __get_pointer(); for (; __first != __last; ++__p, (void) ++__first) traits_type::assign(*__p, *__first); traits_type::assign(*__p, value_type()); __set_size(__n); + if (__n < __old_size) + __annotate_shrink(__old_size); } template @@ -2663,6 +2803,7 @@ basic_string<_CharT, _Traits, _Allocator>::append(const value_type* __s, size_ty { if (__n) { + __annotate_increase(__n); value_type* __p = std::__to_address(__get_pointer()); traits_type::copy(__p + __sz, __s, __n); __sz += __n; @@ -2686,6 +2827,7 @@ basic_string<_CharT, _Traits, _Allocator>::append(size_type __n, value_type __c) size_type __sz = size(); if (__cap - __sz < __n) __grow_by_without_replace(__cap, __sz + __n - __cap, __sz, __sz, 0); + __annotate_increase(__n); pointer __p = __get_pointer(); traits_type::assign(std::__to_address(__p) + __sz, __n, __c); __sz += __n; @@ -2705,6 +2847,7 @@ basic_string<_CharT, _Traits, _Allocator>::__append_default_init(size_type __n) size_type __sz = size(); if (__cap - __sz < __n) __grow_by_without_replace(__cap, __sz + __n - __cap, __sz, __sz, 0); + __annotate_increase(__n); pointer __p = __get_pointer(); __sz += __n; __set_size(__sz); @@ -2733,8 +2876,10 @@ basic_string<_CharT, _Traits, _Allocator>::push_back(value_type __c) if (__sz == __cap) { __grow_by_without_replace(__cap, 1, __sz, __sz, 0); + __annotate_increase(1); __is_short = false; // the string is always long after __grow_by - } + } else + __annotate_increase(1); pointer __p = __get_pointer(); if (__is_short) { @@ -2766,6 +2911,7 @@ basic_string<_CharT, _Traits, _Allocator>::append( { if (__cap - __sz < __n) __grow_by_without_replace(__cap, __sz + __n - __cap, __sz, __sz, 0); + __annotate_increase(__n); pointer __p = __get_pointer() + __sz; for (; __first != __last; ++__p, (void) ++__first) traits_type::assign(*__p, *__first); @@ -2831,6 +2977,7 @@ basic_string<_CharT, _Traits, _Allocator>::insert(size_type __pos, const value_t { if (__n) { + __annotate_increase(__n); value_type* __p = std::__to_address(__get_pointer()); size_type __n_move = __sz - __pos; if (__n_move != 0) @@ -2864,6 +3011,7 @@ basic_string<_CharT, _Traits, _Allocator>::insert(size_type __pos, size_type __n value_type* __p; if (__cap - __sz >= __n) { + __annotate_increase(__n); __p = std::__to_address(__get_pointer()); size_type __n_move = __sz - __pos; if (__n_move != 0) @@ -2972,6 +3120,7 @@ basic_string<_CharT, _Traits, _Allocator>::insert(const_iterator __pos, value_ty } else { + __annotate_increase(1); __p = std::__to_address(__get_pointer()); size_type __n_move = __sz - __ip; if (__n_move != 0) @@ -3002,6 +3151,8 @@ basic_string<_CharT, _Traits, _Allocator>::replace(size_type __pos, size_type __ value_type* __p = std::__to_address(__get_pointer()); if (__n1 != __n2) { + if (__n2 > __n1) + __annotate_increase(__n2 - __n1); size_type __n_move = __sz - __pos - __n1; if (__n_move != 0) { @@ -3046,20 +3197,18 @@ basic_string<_CharT, _Traits, _Allocator>::replace(size_type __pos, size_type __ __n1 = std::min(__n1, __sz - __pos); size_type __cap = capacity(); value_type* __p; - if (__cap - __sz + __n1 >= __n2) - { - __p = std::__to_address(__get_pointer()); - if (__n1 != __n2) - { - size_type __n_move = __sz - __pos - __n1; - if (__n_move != 0) - traits_type::move(__p + __pos + __n2, __p + __pos + __n1, __n_move); - } - } - else - { - __grow_by_without_replace(__cap, __sz - __n1 + __n2 - __cap, __sz, __pos, __n1, __n2); - __p = std::__to_address(__get_long_pointer()); + if (__cap - __sz + __n1 >= __n2) { + __p = std::__to_address(__get_pointer()); + if (__n1 != __n2) { + if (__n2 > __n1) + __annotate_increase(__n2 - __n1); + size_type __n_move = __sz - __pos - __n1; + if (__n_move != 0) + traits_type::move(__p + __pos + __n2, __p + __pos + __n1, __n_move); + } + } else { + __grow_by_without_replace(__cap, __sz - __n1 + __n2 - __cap, __sz, __pos, __n1, __n2); + __p = std::__to_address(__get_long_pointer()); } traits_type::assign(__p + __pos, __n2, __c); return __null_terminate_at(__p, __sz - (__n1 - __n2)); @@ -3187,6 +3336,7 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::clear() _NOEXCEPT { + size_type __old_size = size(); if (__is_long()) { traits_type::assign(*__get_long_pointer(), value_type()); @@ -3197,6 +3347,7 @@ basic_string<_CharT, _Traits, _Allocator>::clear() _NOEXCEPT traits_type::assign(*__get_short_pointer(), value_type()); __set_short_size(0); } + __annotate_shrink(__old_size); } template @@ -3259,6 +3410,7 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::__shrink_or_extend(size_type __target_capacity) { + __annotate_delete(); size_type __cap = capacity(); size_type __sz = size(); @@ -3315,6 +3467,7 @@ basic_string<_CharT, _Traits, _Allocator>::__shrink_or_extend(size_type __target } else __set_short_size(__sz); + __annotate_new(__sz); } template @@ -3365,8 +3518,16 @@ basic_string<_CharT, _Traits, _Allocator>::swap(basic_string& __str) __alloc_traits::propagate_on_container_swap::value || __alloc_traits::is_always_equal::value || __alloc() == __str.__alloc(), "swapping non-equal allocators"); + if (!__is_long()) + __annotate_delete(); + if (this != &__str && !__str.__is_long()) + __str.__annotate_delete(); std::swap(__r_.first(), __str.__r_.first()); std::__swap_allocator(__alloc(), __str.__alloc()); + if (!__is_long()) + __annotate_new(__get_short_size()); + if (this != &__str && !__str.__is_long()) + __str.__annotate_new(__str.__get_short_size()); } // find @@ -3854,12 +4015,12 @@ inline _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::__clear_and_shrink() _NOEXCEPT { - clear(); - if(__is_long()) - { - __alloc_traits::deallocate(__alloc(), __get_long_pointer(), capacity() + 1); - __r_.first() = __rep(); - } + clear(); + if (__is_long()) { + __annotate_delete(); + __alloc_traits::deallocate(__alloc(), __get_long_pointer(), capacity() + 1); + __r_.first() = __rep(); + } } // operator== @@ -4110,6 +4271,7 @@ operator+(const basic_string<_CharT, _Traits, _Allocator>& __lhs, _Traits::copy(__ptr, __lhs.data(), __lhs_sz); _Traits::copy(__ptr + __lhs_sz, __rhs.data(), __rhs_sz); _Traits::assign(__ptr + __lhs_sz + __rhs_sz, 1, _CharT()); + __r.__annotate_new(__lhs_sz + __rhs_sz); return __r; } @@ -4124,10 +4286,12 @@ operator+(const _CharT* __lhs , const basic_string<_CharT,_Traits,_Allocator>& _ _String __r(__uninitialized_size_tag(), __lhs_sz + __rhs_sz, _String::__alloc_traits::select_on_container_copy_construction(__rhs.get_allocator())); + __r.__annotate_delete(); auto __ptr = std::__to_address(__r.__get_pointer()); _Traits::copy(__ptr, __lhs, __lhs_sz); _Traits::copy(__ptr + __lhs_sz, __rhs.data(), __rhs_sz); _Traits::assign(__ptr + __lhs_sz + __rhs_sz, 1, _CharT()); + __r.__annotate_new(__lhs_sz + __rhs_sz); return __r; } @@ -4141,10 +4305,12 @@ operator+(_CharT __lhs, const basic_string<_CharT,_Traits,_Allocator>& __rhs) _String __r(__uninitialized_size_tag(), __rhs_sz + 1, _String::__alloc_traits::select_on_container_copy_construction(__rhs.get_allocator())); + __r.__annotate_delete(); auto __ptr = std::__to_address(__r.__get_pointer()); _Traits::assign(__ptr, 1, __lhs); _Traits::copy(__ptr + 1, __rhs.data(), __rhs_sz); _Traits::assign(__ptr + 1 + __rhs_sz, 1, _CharT()); + __r.__annotate_new(1 + __rhs_sz); return __r; } @@ -4159,10 +4325,12 @@ operator+(const basic_string<_CharT, _Traits, _Allocator>& __lhs, const _CharT* _String __r(__uninitialized_size_tag(), __lhs_sz + __rhs_sz, _String::__alloc_traits::select_on_container_copy_construction(__lhs.get_allocator())); + __r.__annotate_delete(); auto __ptr = std::__to_address(__r.__get_pointer()); _Traits::copy(__ptr, __lhs.data(), __lhs_sz); _Traits::copy(__ptr + __lhs_sz, __rhs, __rhs_sz); _Traits::assign(__ptr + __lhs_sz + __rhs_sz, 1, _CharT()); + __r.__annotate_new(__lhs_sz + __rhs_sz); return __r; } @@ -4176,10 +4344,12 @@ operator+(const basic_string<_CharT, _Traits, _Allocator>& __lhs, _CharT __rhs) _String __r(__uninitialized_size_tag(), __lhs_sz + 1, _String::__alloc_traits::select_on_container_copy_construction(__lhs.get_allocator())); + __r.__annotate_delete(); auto __ptr = std::__to_address(__r.__get_pointer()); _Traits::copy(__ptr, __lhs.data(), __lhs_sz); _Traits::assign(__ptr + __lhs_sz, 1, __rhs); _Traits::assign(__ptr + 1 + __lhs_sz, 1, _CharT()); + __r.__annotate_new(__lhs_sz + 1); return __r; } diff --git a/libcxx/test/std/strings/basic.string/string.capacity/capacity.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/capacity.pass.cpp index e1d20662e41de..61867cfb087b9 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/capacity.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/capacity.pass.cpp @@ -15,6 +15,7 @@ #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" #include "test_macros.h" @@ -28,6 +29,7 @@ TEST_CONSTEXPR_CXX20 void test_invariant(S s, test_allocator_statistics& alloc_s while (s.size() < s.capacity()) s.push_back(typename S::value_type()); assert(s.size() == s.capacity()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } #ifndef TEST_HAS_NO_EXCEPTIONS catch (...) { @@ -43,10 +45,12 @@ TEST_CONSTEXPR_CXX20 void test_string(const Alloc& a) { { S const s((Alloc(a))); assert(s.capacity() >= 0); + LIBCPP_ASSERT(is_string_asan_correct(s)); } { S const s(3, 'x', Alloc(a)); assert(s.capacity() >= 3); + LIBCPP_ASSERT(is_string_asan_correct(s)); } #if TEST_STD_VER >= 11 // Check that we perform SSO @@ -54,6 +58,7 @@ TEST_CONSTEXPR_CXX20 void test_string(const Alloc& a) { S const s; assert(s.capacity() > 0); ASSERT_NOEXCEPT(s.capacity()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } #endif } @@ -63,18 +68,22 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(test_allocator()); test_string(test_allocator(3)); test_string(min_allocator()); + test_string(safe_allocator()); { test_allocator_statistics alloc_stats; typedef std::basic_string, test_allocator > S; S s((test_allocator(&alloc_stats))); test_invariant(s, alloc_stats); + LIBCPP_ASSERT(is_string_asan_correct(s)); s.assign(10, 'a'); s.erase(5); test_invariant(s, alloc_stats); + LIBCPP_ASSERT(is_string_asan_correct(s)); s.assign(100, 'a'); s.erase(50); test_invariant(s, alloc_stats); + LIBCPP_ASSERT(is_string_asan_correct(s)); } return true; diff --git a/libcxx/test/std/strings/basic.string/string.capacity/clear.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/clear.pass.cpp index 3a308de9b7569..7fa5cd43b80af 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/clear.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/clear.pass.cpp @@ -15,31 +15,38 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s) { s.clear(); assert(s.size() == 0); + LIBCPP_ASSERT(is_string_asan_correct(s)); } template TEST_CONSTEXPR_CXX20 void test_string() { S s; test(s); + LIBCPP_ASSERT(is_string_asan_correct(s)); s.assign(10, 'a'); s.erase(5); + LIBCPP_ASSERT(is_string_asan_correct(s)); test(s); s.assign(100, 'a'); s.erase(50); + LIBCPP_ASSERT(is_string_asan_correct(s)); test(s); + LIBCPP_ASSERT(is_string_asan_correct(s)); } TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.capacity/reserve.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/reserve.pass.cpp index b740901be1c4d..43414da3794a5 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/reserve.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/reserve.pass.cpp @@ -18,6 +18,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template void test(typename S::size_type min_cap, typename S::size_type erased_index) { @@ -33,6 +34,7 @@ void test(typename S::size_type min_cap, typename S::size_type erased_index) { assert(s == s0); assert(s.capacity() <= old_cap); assert(s.capacity() >= s.size()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } template @@ -47,6 +49,7 @@ bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp new file mode 100644 index 0000000000000..7b9f7442ff4e2 --- /dev/null +++ b/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +#include +#include + +#include "test_macros.h" +#include "asan_testing.h" + +template +void test() { + S short_s1(3, 'a'), long_s1(100, 'c'); + short_s1.reserve(0x1337); + long_s1.reserve(0x1337); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); +#if TEST_STD_VER >= 11 + short_s1.shrink_to_fit(); + long_s1.shrink_to_fit(); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); +#endif + short_s1.clear(); + long_s1.clear(); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); +#if TEST_STD_VER >= 11 + short_s1.shrink_to_fit(); + long_s1.shrink_to_fit(); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); +#endif + S short_s2(3, 'a'), long_s2(100, 'c'); + short_s2.reserve(0x1); + long_s2.reserve(0x1); + + LIBCPP_ASSERT(is_string_asan_correct(short_s2)); + LIBCPP_ASSERT(is_string_asan_correct(long_s2)); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif +#if TEST_STD_VER >= 11 + test(); + test(); +#endif +#if TEST_STD_VER >= 20 + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp index dfb3b270f750e..30c171680a23c 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp @@ -20,6 +20,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void @@ -28,6 +29,7 @@ test(typename S::size_type min_cap, typename S::size_type erased_index, typename s.erase(erased_index); assert(s.size() == erased_index); assert(s.capacity() >= min_cap); // Check that we really have at least this capacity. + LIBCPP_ASSERT(is_string_asan_correct(s)); #if TEST_STD_VER > 17 typename S::size_type old_cap = s.capacity(); @@ -39,6 +41,7 @@ test(typename S::size_type min_cap, typename S::size_type erased_index, typename assert(s == s0); assert(s.capacity() >= res_arg); assert(s.capacity() >= s.size()); + LIBCPP_ASSERT(is_string_asan_correct(s)); #if TEST_STD_VER > 17 assert(s.capacity() >= old_cap); // reserve never shrinks as of P0966 (C++20) #endif diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp index bbe6551a0ff11..00f5cccd30d6b 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp @@ -19,6 +19,7 @@ #include "make_string.h" #include "test_macros.h" +#include "asan_testing.h" template constexpr void test_appending(std::size_t k, size_t N, size_t new_capacity) { @@ -76,11 +77,14 @@ constexpr bool test() { void test_value_categories() { std::string s; s.resize_and_overwrite(10, [](char*&&, std::size_t&&) { return 0; }); + LIBCPP_ASSERT(is_string_asan_correct(s)); s.resize_and_overwrite(10, [](char* const&, const std::size_t&) { return 0; }); + LIBCPP_ASSERT(is_string_asan_correct(s)); struct RefQualified { int operator()(char*, std::size_t) && { return 0; } }; s.resize_and_overwrite(10, RefQualified{}); + LIBCPP_ASSERT(is_string_asan_correct(s)); } int main(int, char**) { diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp index 487b12d9df87f..7cf4b7ca3b6ef 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, S expected) { @@ -23,6 +24,7 @@ TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, S expected) { s.resize(n); LIBCPP_ASSERT(s.__invariants()); assert(s == expected); + LIBCPP_ASSERT(is_string_asan_correct(s)); } #ifndef TEST_HAS_NO_EXCEPTIONS else if (!TEST_IS_CONSTANT_EVALUATED) { @@ -61,6 +63,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp index 3b6adc0b0afeb..e3b925ca8bcdb 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, typename S::value_type c, S expected) { @@ -23,6 +24,7 @@ TEST_CONSTEXPR_CXX20 void test(S s, typename S::size_type n, typename S::value_t s.resize(n, c); LIBCPP_ASSERT(s.__invariants()); assert(s == expected); + LIBCPP_ASSERT(is_string_asan_correct(s)); } #ifndef TEST_HAS_NO_EXCEPTIONS else if (!TEST_IS_CONSTANT_EVALUATED) { @@ -57,12 +59,23 @@ TEST_CONSTEXPR_CXX20 void test_string() { 'a', S("12345678901234567890123456789012345678901234567890aaaaaaaaaa")); test(S(), S::npos, 'a', S("not going to happen")); + //ASan: + test(S(), 21, 'a', S("aaaaaaaaaaaaaaaaaaaaa")); + test(S(), 22, 'a', S("aaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 23, 'a', S("aaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 24, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 29, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 30, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 31, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 32, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + test(S(), 33, 'a', S("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); } TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp index 66eefdd383dc0..eb572de512e7d 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp @@ -15,6 +15,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s) { @@ -25,6 +26,7 @@ TEST_CONSTEXPR_CXX20 void test(S s) { assert(s == s0); assert(s.capacity() <= old_cap); assert(s.capacity() >= s.size()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } template @@ -43,12 +45,18 @@ TEST_CONSTEXPR_CXX20 void test_string() { s.assign(100, 'a'); s.erase(50); test(s); + + s.assign(100, 'a'); + for (int i = 0; i < 90; ++i) + s.erase(1); + test(s); } TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp index a6b625b7b0e81..dcf697bed752f 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp @@ -23,6 +23,7 @@ #include "test_macros.h" #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(SV sv, std::size_t pos, std::size_t n) { @@ -38,6 +39,7 @@ TEST_CONSTEXPR_CXX20 void test(SV sv, std::size_t pos, std::size_t n) { assert(T::compare(s2.data(), sv.data() + pos, rlen) == 0); assert(s2.get_allocator() == A()); assert(s2.capacity() >= s2.size()); + LIBCPP_ASSERT(is_string_asan_correct(s2)); } #ifndef TEST_HAS_NO_EXCEPTIONS else if (!TEST_IS_CONSTANT_EVALUATED) { @@ -113,6 +115,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(test_allocator(8)); #if TEST_STD_VER >= 11 test_string(min_allocator()); + test_string(safe_allocator()); #endif { diff --git a/libcxx/test/std/strings/basic.string/string.cons/alloc.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/alloc.pass.cpp index 97a0566ba031b..91beac37764db 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/alloc.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/alloc.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test() { @@ -31,6 +32,7 @@ TEST_CONSTEXPR_CXX20 void test() { assert(s.size() == 0); assert(s.capacity() >= s.size()); assert(s.get_allocator() == typename S::allocator_type()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } { #if TEST_STD_VER > 14 @@ -46,6 +48,7 @@ TEST_CONSTEXPR_CXX20 void test() { assert(s.size() == 0); assert(s.capacity() >= s.size()); assert(s.get_allocator() == typename S::allocator_type(5)); + LIBCPP_ASSERT(is_string_asan_correct(s)); } } @@ -65,6 +68,7 @@ TEST_CONSTEXPR_CXX20 void test2() { assert(s.size() == 0); assert(s.capacity() >= s.size()); assert(s.get_allocator() == typename S::allocator_type()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } { # if TEST_STD_VER > 14 @@ -80,6 +84,7 @@ TEST_CONSTEXPR_CXX20 void test2() { assert(s.size() == 0); assert(s.capacity() >= s.size()); assert(s.get_allocator() == typename S::allocator_type()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } } @@ -89,6 +94,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test, test_allocator > >(); #if TEST_STD_VER >= 11 test2, min_allocator > >(); + test2, safe_allocator > >(); test2, explicit_allocator > >(); #endif diff --git a/libcxx/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp index e7d18b4ca8717..6b3a665ced447 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp @@ -17,6 +17,7 @@ #include #include "test_macros.h" +#include "asan_testing.h" TEST_CONSTEXPR_CXX20 bool test() { // Test that assignment from {} and {ptr, len} are allowed and are not @@ -25,11 +26,13 @@ TEST_CONSTEXPR_CXX20 bool test() { std::string s = "hello world"; s = {}; assert(s.empty()); + LIBCPP_ASSERT(is_string_asan_correct(s)); } { std::string s = "hello world"; s = {"abc", 2}; assert(s == "ab"); + LIBCPP_ASSERT(is_string_asan_correct(s)); } return true; diff --git a/libcxx/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp index 3cffc82e94835..1019dc8bca5df 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp @@ -15,6 +15,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s1, typename S::value_type s2) { @@ -24,6 +25,7 @@ TEST_CONSTEXPR_CXX20 void test(S s1, typename S::value_type s2) { assert(s1.size() == 1); assert(T::eq(s1[0], s2)); assert(s1.capacity() >= s1.size()); + LIBCPP_ASSERT(is_string_asan_correct(s1)); } template @@ -38,6 +40,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.cons/copy.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/copy.pass.cpp index 3afe76e88316f..f65f8e97c9824 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/copy.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/copy.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s1) { @@ -24,6 +25,8 @@ TEST_CONSTEXPR_CXX20 void test(S s1) { assert(s2 == s1); assert(s2.capacity() >= s2.size()); assert(s2.get_allocator() == s1.get_allocator()); + LIBCPP_ASSERT(is_string_asan_correct(s1)); + LIBCPP_ASSERT(is_string_asan_correct(s2)); } template @@ -40,6 +43,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(test_allocator(3)); #if TEST_STD_VER >= 11 test_string(min_allocator()); + test_string(safe_allocator()); #endif return true; diff --git a/libcxx/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp index 6b0040376a424..b0045cb4afbba 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" #ifndef TEST_HAS_NO_EXCEPTIONS struct alloc_imp { @@ -83,6 +84,8 @@ TEST_CONSTEXPR_CXX20 void test(S s1, const typename S::allocator_type& a) { assert(s2 == s1); assert(s2.capacity() >= s2.size()); assert(s2.get_allocator() == a); + LIBCPP_ASSERT(is_string_asan_correct(s1)); + LIBCPP_ASSERT(is_string_asan_correct(s2)); } template @@ -99,6 +102,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(test_allocator(3)); #if TEST_STD_VER >= 11 test_string(min_allocator()); + test_string(safe_allocator()); #endif #if TEST_STD_VER >= 11 diff --git a/libcxx/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp index eb522aafa2430..2e98fccb5394b 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp @@ -16,6 +16,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "asan_testing.h" template TEST_CONSTEXPR_CXX20 void test(S s1, const S& s2) { @@ -23,6 +24,8 @@ TEST_CONSTEXPR_CXX20 void test(S s1, const S& s2) { LIBCPP_ASSERT(s1.__invariants()); assert(s1 == s2); assert(s1.capacity() >= s1.size()); + LIBCPP_ASSERT(is_string_asan_correct(s1)); + LIBCPP_ASSERT(is_string_asan_correct(s2)); } template @@ -47,6 +50,7 @@ TEST_CONSTEXPR_CXX20 bool test() { test_string(); #if TEST_STD_VER >= 11 test_string, min_allocator>>(); + test_string, safe_allocator>>(); #endif #if TEST_STD_VER >= 11 diff --git a/libcxx/test/std/strings/basic.string/string.cons/default.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/default.pass.cpp index 3993a40dd5a16..fc263f9820cb5 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/default.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/default.pass.cpp @@ -15,6 +15,7 @@ #include "test_macros.h" #include "test_allocator.h" +#include "asan_testing.h" #if TEST_STD_VER >= 11 // Test the noexcept specification, which is a conforming extension @@ -30,6 +31,7 @@ LIBCPP_STATIC_ASSERT(!std::is_nothrow_default_constructible< TEST_CONSTEXPR_CXX20 bool test() { std::string str; assert(str.empty()); + LIBCPP_ASSERT(is_string_asan_correct(str)); return true; } diff --git a/libcxx/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp b/libcxx/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp index 5b7e8bde2e6e8..e326a71dc7a46 100644 --- a/libcxx/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp @@ -18,6 +18,7 @@ #include "test_macros.h" #include "test_allocator.h" #include "min_allocator.h" +#include "asan_testing.h" // clang-format off template