From e7db4825bec6f5c981eeebdcc271e4136f8566a1 Mon Sep 17 00:00:00 2001 From: Tacet Date: Wed, 13 Dec 2023 06:05:34 +0100 Subject: [PATCH] [ASan][libc++] std::basic_string annotations (#72677) This commit introduces basic annotations for `std::basic_string`, mirroring the approach used in `std::vector` and `std::deque`. Initially, only long strings with the default allocator will be annotated. Short strings (_SSO - short string optimization_) and strings with non-default allocators will be annotated in the near future, with separate commits dedicated to enabling them. The process will be similar to the workflow employed for enabling annotations in `std::deque`. **Please note**: these annotations function effectively only when libc++ and libc++abi dylibs are instrumented (with ASan). This aligns with the prevailing behavior of Memory Sanitizer. To avoid breaking everything, this commit also appends `_LIBCPP_INSTRUMENTED_WITH_ASAN` to `__config_site` whenever libc++ is compiled with ASan. If this macro is not defined, string annotations are not enabled. However, linking a binary that does **not** annotate strings with a dynamic library that annotates strings, is not permitted. Originally proposed here: https://reviews.llvm.org/D132769 Related patches on Phabricator: - Turning on annotations for short strings: https://reviews.llvm.org/D147680 - Turning on annotations for all allocators: https://reviews.llvm.org/D146214 This PR 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` and `std::deque` collections. These enhancements empower ASan to effectively detect instances where the instrumented program attempts to access memory within a collection's internal allocation that remains unused. This includes cases where access occurs before or after the stored elements in `std::deque`, or between the `std::basic_string`'s size (including the null terminator) and capacity bounds. The introduction of these annotations was spurred by a real-world software bug discovered by Trail of Bits, involving an out-of-bounds memory access during the comparison of two strings using the `std::equals` function. This function was taking iterators (`iter1_begin`, `iter1_end`, `iter2_begin`) to perform the comparison, using a custom comparison function. When the `iter1` object exceeded the length of `iter2`, an out-of-bounds read could occur on the `iter2` object. Container sanitization, upon enabling these annotations, would effectively identify and flag this potential vulnerability. This Pull Request introduces basic annotations for `std::basic_string`. Long strings exhibit structural similarities to `std::vector` and will be annotated accordingly. Short strings are already implemented, but will be turned on separately in a forthcoming commit. Look at [a comment](https://github.com/llvm/llvm-project/pull/72677#issuecomment-1850554465) below to read about SSO issues at current moment. Due to the functionality introduced in [D132522](https://github.com/llvm/llvm-project/commit/dd1b7b797a116eed588fd752fbe61d34deeb24e4), the `__sanitizer_annotate_contiguous_container` function now offers compatibility with all allocators. However, enabling this support will be done in a subsequent commit. For the time being, only strings with the default allocator will be annotated. If you have any questions, please email: - advenam.tacet@trailofbits.com - disconnect3d@trailofbits.com NOKEYCHECK=True GitOrigin-RevId: 9ed20568e7de53dce85f1631d7d8c1415e7930ae --- CMakeLists.txt | 13 + include/__config_site.in | 1 + include/string | 282 ++++++++++++++---- .../string.capacity/capacity.pass.cpp | 9 + .../string.capacity/clear.pass.cpp | 8 + .../string.capacity/reserve.pass.cpp | 3 + .../reserve_size.asan.pass.cpp | 63 ++++ .../string.capacity/reserve_size.pass.cpp | 3 + .../resize_and_overwrite.pass.cpp | 6 + .../string.capacity/resize_size.pass.cpp | 3 + .../string.capacity/resize_size_char.pass.cpp | 13 + .../string.capacity/shrink_to_fit.pass.cpp | 9 + .../string.cons/T_size_size.pass.cpp | 3 + .../basic.string/string.cons/alloc.pass.cpp | 6 + .../string.cons/brace_assignment.pass.cpp | 27 ++ .../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/from_range.pass.cpp | 3 + .../string.cons/from_range_deduction.pass.cpp | 3 + .../string.cons/initializer_list.pass.cpp | 14 + .../initializer_list_assignment.pass.cpp | 23 ++ .../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.cons/substr_rvalue.pass.cpp | 9 + .../string_append/append_range.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/assign_range.pass.cpp | 5 + .../string_assign/initializer_list.pass.cpp | 13 + .../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.pass.cpp | 5 + .../string_erase/size_size.pass.cpp | 3 + .../string_insert/iter_char.pass.cpp | 4 + .../iter_initializer_list.pass.cpp | 8 + ...iter_iter_iter.infinite_recursion.pass.cpp | 2 + .../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 | 17 ++ .../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 | 91 ++++++ .../string_swap/swap.pass.cpp | 9 + .../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 + .../string.ops/string_substr/substr.pass.cpp | 4 + .../string_substr/substr_rvalue.pass.cpp | 3 + test/support/asan_testing.h | 61 +++- 92 files changed, 862 insertions(+), 64 deletions(-) create mode 100644 test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp create mode 100644 test/std/strings/basic.string/string.modifiers/string_swap/swap.asan.pass.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7751bf1efc..5970322505 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -651,6 +651,19 @@ get_sanitizer_flags(SANITIZER_FLAGS "${LLVM_USE_SANITIZER}") add_library(cxx-sanitizer-flags INTERFACE) target_compile_options(cxx-sanitizer-flags INTERFACE ${SANITIZER_FLAGS}) +# _LIBCPP_INSTRUMENTED_WITH_ASAN informs that library was built with ASan. +# Defining _LIBCPP_INSTRUMENTED_WITH_ASAN while building the library with ASan is required. +# Normally, the _LIBCPP_INSTRUMENTED_WITH_ASAN flag is used to keep information whether +# dylibs are built with AddressSanitizer. However, when building libc++, +# this flag needs to be defined so that the resulting dylib has all ASan functionalities guarded by this flag. +# If the _LIBCPP_INSTRUMENTED_WITH_ASAN flag is not defined, then parts of the ASan instrumentation code in libc++ +# will not be compiled into it, resulting in false positives. +# For context, read: https://github.com/llvm/llvm-project/pull/72677#pullrequestreview-1765402800 +string(FIND "${LLVM_USE_SANITIZER}" "Address" building_with_asan) +if (NOT "${building_with_asan}" STREQUAL "-1") + config_define(ON _LIBCPP_INSTRUMENTED_WITH_ASAN) +endif() + # Link system libraries ======================================================= function(cxx_link_system_libraries target) if (NOT MSVC) diff --git a/include/__config_site.in b/include/__config_site.in index 6cade6f10d..7c002c5bfc 100644 --- a/include/__config_site.in +++ b/include/__config_site.in @@ -29,6 +29,7 @@ #cmakedefine _LIBCPP_HAS_NO_WIDE_CHARACTERS #cmakedefine _LIBCPP_HAS_NO_STD_MODULES #cmakedefine _LIBCPP_HAS_NO_TIME_ZONE_DATABASE +#cmakedefine _LIBCPP_INSTRUMENTED_WITH_ASAN // PSTL backends #cmakedefine _LIBCPP_PSTL_CPU_BACKEND_SERIAL diff --git a/include/string b/include/string index 9c97abefcb..80ccf442ce 100644 --- a/include/string +++ b/include/string @@ -649,6 +649,17 @@ basic_string operator""s( const char32_t *str, size_t len ); _LIBCPP_PUSH_MACROS #include <__undef_macros> +#if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_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; @@ -860,6 +874,7 @@ private: __set_long_pointer(__allocation); __set_long_size(__size); } + __annotate_new(__size); } template @@ -882,7 +897,9 @@ public: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string() _NOEXCEPT_(is_nothrow_default_constructible::value) - : __r_(__value_init_tag(), __default_init_tag()) {} + : __r_(__value_init_tag(), __default_init_tag()) { + __annotate_new(0); + } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit basic_string(const allocator_type& __a) #if _LIBCPP_STD_VER <= 14 @@ -890,44 +907,65 @@ public: #else _NOEXCEPT #endif - : __r_(__value_init_tag(), __a) {} + : __r_(__value_init_tag(), __a) { + __annotate_new(0); + } - _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 + // does not work consistently during initialization of __r_, so we instead unpoison __str's memory manually first. + // __str's memory needs to be unpoisoned only in the case where it's a short string. + : __r_( ( (__str.__is_long() ? 0 : (__str.__annotate_delete(), 0)), std::move(__str.__r_)) ) { __str.__r_.first() = __rep(); + __str.__annotate_new(0); + if(!__is_long()) + __annotate_new(size()); } - _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 + 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()); else { if (__libcpp_is_constant_evaluated()) __r_.first() = __rep(); + if (!__str.__is_long()) + __str.__annotate_delete(); __r_.first() = __str.__r_.first(); __str.__r_.first() = __rep(); + __str.__annotate_new(0); + if(!__is_long() && this != &__str) + __annotate_new(size()); } } #endif // _LIBCPP_CXX03_LANG @@ -1085,6 +1123,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()); } @@ -1092,7 +1131,7 @@ public: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 operator __self_view() const _NOEXCEPT { return __self_view(data(), size()); } - _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& operator=(const basic_string& __str); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS basic_string& operator=(const basic_string& __str); template ::value && !__is_same_uncvref<_Tp, basic_string>::value, int> = 0> @@ -1102,8 +1141,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 basic_string& + operator=(basic_string&& __str) _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) { __move_assign(__str, integral_constant()); return *this; } @@ -1116,7 +1155,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 @@ -1339,12 +1378,22 @@ public: 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"); + size_type __old_sz = __str.size(); + if (!__str.__is_long()) + __str.__annotate_delete(); __r_.first() = __str.__r_.first(); __str.__r_.first() = __rep(); + __str.__annotate_new(0); _Traits::move(data(), data() + __pos, __len); __set_size(__len); _Traits::assign(data()[__len], value_type()); + + if (!__is_long()) { + __annotate_new(__len); + } else if(__old_sz > __len) { + __annotate_shrink(__old_sz); + } } #endif @@ -1742,7 +1791,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 +1831,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 +1858,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 +1866,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 +1916,42 @@ 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. + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __annotate_contiguous_container( + [[__maybe_unused__]] const void* __old_mid, [[__maybe_unused__]] const void* __new_mid) const { +#if !defined(_LIBCPP_HAS_NO_ASAN) && defined(_LIBCPP_INSTRUMENTED_WITH_ASAN) + 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); +#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 +2054,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 +2064,7 @@ private: __set_long_pointer(__allocation.ptr); __set_long_cap(__allocation.count); __set_long_size(__str.size()); + __annotate_new(__get_long_size()); } } } @@ -1989,7 +2077,7 @@ private: _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __move_assign(basic_string& __str, false_type) _NOEXCEPT_(__alloc_traits::is_always_equal::value); - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS void __move_assign(basic_string& __str, true_type) #if _LIBCPP_STD_VER >= 17 _NOEXCEPT; @@ -2024,18 +2112,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 +2240,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 +2269,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 +2294,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 +2324,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 @@ -2238,6 +2340,7 @@ template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void basic_string<_CharT, _Traits, _Allocator>::__init_with_sentinel(_InputIterator __first, _Sentinel __last) { __r_.first() = __rep(); + __annotate_new(0); #ifndef _LIBCPP_HAS_NO_EXCEPTIONS try @@ -2249,6 +2352,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; @@ -2309,6 +2413,7 @@ void basic_string<_CharT, _Traits, _Allocator>::__init_with_size( throw; } #endif // _LIBCPP_HAS_NO_EXCEPTIONS + __annotate_new(__sz); } template @@ -2325,6 +2430,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 +2450,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 +2473,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 +2504,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 +2517,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 +2540,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 +2570,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 +2589,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 +2616,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 +2647,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 +2656,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()); @@ -2535,13 +2664,35 @@ basic_string<_CharT, _Traits, _Allocator>::__move_assign(basic_string& __str, tr if (!is_nothrow_move_assignable::value) { __set_short_size(0); traits_type::assign(__get_short_pointer()[0], value_type()); + __annotate_new(0); } #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 on original __str's buffer. + __annotate_new(__get_short_size()); } #endif @@ -2587,6 +2738,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 +2749,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 +2820,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 +2844,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 +2864,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 +2893,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 +2928,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 +2994,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 +3028,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 +3137,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 +3168,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 +3214,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 +3353,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 +3364,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 +3427,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 +3484,7 @@ basic_string<_CharT, _Traits, _Allocator>::__shrink_or_extend(size_type __target } else __set_short_size(__sz); + __annotate_new(__sz); } template @@ -3365,8 +3535,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 +4032,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== diff --git a/test/std/strings/basic.string/string.capacity/capacity.pass.cpp b/test/std/strings/basic.string/string.capacity/capacity.pass.cpp index e1d20662e4..61867cfb08 100644 --- a/test/std/strings/basic.string/string.capacity/capacity.pass.cpp +++ b/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/test/std/strings/basic.string/string.capacity/clear.pass.cpp b/test/std/strings/basic.string/string.capacity/clear.pass.cpp index 3a308de9b7..643ea4a3bd 100644 --- a/test/std/strings/basic.string/string.capacity/clear.pass.cpp +++ b/test/std/strings/basic.string/string.capacity/clear.pass.cpp @@ -15,31 +15,39 @@ #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); + LIBCPP_ASSERT(is_string_asan_correct(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/test/std/strings/basic.string/string.capacity/reserve.pass.cpp b/test/std/strings/basic.string/string.capacity/reserve.pass.cpp index b740901be1..43414da379 100644 --- a/test/std/strings/basic.string/string.capacity/reserve.pass.cpp +++ b/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/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp b/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp new file mode 100644 index 0000000000..d35a5bcefc --- /dev/null +++ b/test/std/strings/basic.string/string.capacity/reserve_size.asan.pass.cpp @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// This test verifies that the ASan annotations for basic_string objects remain accurate +// after invoking basic_string::reserve(size_type __requested_capacity). +// Different types are used to confirm that ASan works correctly with types of different sizes. +#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)); + + short_s1.clear(); + long_s1.clear(); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); + + short_s1.reserve(0x1); + long_s1.reserve(0x1); + + LIBCPP_ASSERT(is_string_asan_correct(short_s1)); + LIBCPP_ASSERT(is_string_asan_correct(long_s1)); + + 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/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp b/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp index dfb3b270f7..30c171680a 100644 --- a/test/std/strings/basic.string/string.capacity/reserve_size.pass.cpp +++ b/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/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp b/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp index bbe6551a0f..edc8b67808 100644 --- a/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp +++ b/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) { @@ -37,6 +38,7 @@ constexpr void test_appending(std::size_t k, size_t N, size_t new_capacity) { const S expected = S(k, 'a') + S(N - k, 'b'); assert(s == expected); assert(s.c_str()[N] == '\0'); + LIBCPP_ASSERT(is_string_asan_correct(s)); } template @@ -55,6 +57,7 @@ constexpr void test_truncating(std::size_t o, size_t N) { const S expected = S(N - 1, 'a') + S(1, 'b'); assert(s == expected); assert(s.c_str()[N] == '\0'); + LIBCPP_ASSERT(is_string_asan_correct(s)); } template @@ -76,11 +79,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/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp b/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp index 487b12d9df..7cf4b7ca3b 100644 --- a/test/std/strings/basic.string/string.capacity/resize_size.pass.cpp +++ b/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/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp b/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp index 3b6adc0b0a..e3b925ca8b 100644 --- a/test/std/strings/basic.string/string.capacity/resize_size_char.pass.cpp +++ b/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/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp b/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp index 66eefdd383..057050cdcf 100644 --- a/test/std/strings/basic.string/string.capacity/shrink_to_fit.pass.cpp +++ b/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,19 @@ TEST_CONSTEXPR_CXX20 void test_string() { s.assign(100, 'a'); s.erase(50); test(s); + + s.assign(100, 'a'); + for (int i = 0; i <= 9; ++i) { + s.erase(90 - 10 * i); + 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/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp b/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp index a6b625b7b0..dcf697bed7 100644 --- a/test/std/strings/basic.string/string.cons/T_size_size.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/alloc.pass.cpp b/test/std/strings/basic.string/string.cons/alloc.pass.cpp index 97a0566ba0..91beac3776 100644 --- a/test/std/strings/basic.string/string.cons/alloc.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp b/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp index e7d18b4ca8..49a90872c5 100644 --- a/test/std/strings/basic.string/string.cons/brace_assignment.pass.cpp +++ b/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,37 @@ 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)); + } + { + std::string s = "hello world"; + s = {"It'sALongString!NoSSO!qwertyuiop", 30}; + assert(s == "It'sALongString!NoSSO!qwertyui"); + LIBCPP_ASSERT(is_string_asan_correct(s)); + } + { + std::string s = "Hello world! Hello world! Hello world! Hello world! Hello world!"; + s = {"It'sALongString!NoSSO!qwertyuiop", 30}; + assert(s == "It'sALongString!NoSSO!qwertyui"); + LIBCPP_ASSERT(is_string_asan_correct(s)); + } + { + std::string s = "Hello world! Hello world! Hello world! Hello world! Hello world!"; + s = {"abc", 2}; + assert(s == "ab"); + LIBCPP_ASSERT(is_string_asan_correct(s)); + } + { + std::string s = "Hello world! Hello world! Hello world! Hello world! Hello world!"; + s = {"abc", 0}; + assert(s == ""); + LIBCPP_ASSERT(is_string_asan_correct(s)); } return true; diff --git a/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp b/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp index 3cffc82e94..1019dc8bca 100644 --- a/test/std/strings/basic.string/string.cons/char_assignment.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/copy.pass.cpp b/test/std/strings/basic.string/string.cons/copy.pass.cpp index 3afe76e883..f65f8e97c9 100644 --- a/test/std/strings/basic.string/string.cons/copy.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp b/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp index 6b0040376a..b0045cb4af 100644 --- a/test/std/strings/basic.string/string.cons/copy_alloc.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp b/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp index eb522aafa2..2e98fccb53 100644 --- a/test/std/strings/basic.string/string.cons/copy_assignment.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/default.pass.cpp b/test/std/strings/basic.string/string.cons/default.pass.cpp index 3993a40dd5..fc263f9820 100644 --- a/test/std/strings/basic.string/string.cons/default.pass.cpp +++ b/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/test/std/strings/basic.string/string.cons/from_range.pass.cpp b/test/std/strings/basic.string/string.cons/from_range.pass.cpp index 3ae5b74a35..7f33237de4 100644 --- a/test/std/strings/basic.string/string.cons/from_range.pass.cpp +++ b/test/std/strings/basic.string/string.cons/from_range.pass.cpp @@ -19,6 +19,7 @@ #include "../../../containers/from_range_helpers.h" #include "../../../containers/sequences/from_range_sequence_containers.h" #include "test_macros.h" +#include "asan_testing.h" template concept StringHasFromRangeAllocCtr = @@ -70,6 +71,7 @@ constexpr void test_with_input(std::vector input) { LIBCPP_ASSERT(c.__invariants()); assert(c.size() == static_cast(std::distance(c.begin(), c.end()))); assert(std::ranges::equal(in, c)); + LIBCPP_ASSERT(is_string_asan_correct(c)); } { // (range, allocator) @@ -80,6 +82,7 @@ constexpr void test_with_input(std::vector input) { assert(c.get_allocator() == alloc); assert(c.size() == static_cast(std::distance(c.begin(), c.end()))); assert(std::ranges::equal(in, c)); + LIBCPP_ASSERT(is_string_asan_correct(c)); } } diff --git a/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp b/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp index b2dab03506..83c3dfdfa7 100644 --- a/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp +++ b/test/std/strings/basic.string/string.cons/from_range_deduction.pass.cpp @@ -26,6 +26,7 @@ #include "deduction_guides_sfinae_checks.h" #include "test_allocator.h" +#include "asan_testing.h" int main(int, char**) { using Char = char16_t; @@ -33,12 +34,14 @@ int main(int, char**) { { std::basic_string c(std::from_range, std::array()); static_assert(std::is_same_v>); + LIBCPP_ASSERT(is_string_asan_correct(c)); } { using Alloc = test_allocator; std::basic_string c(std::from_range, std::array(), Alloc()); static_assert(std::is_same_v, Alloc>>); + LIBCPP_ASSERT(is_string_asan_correct(c)); } // Note: defining `value_type` is a workaround because one of the deduction guides will end up instantiating diff --git a/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp b/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp index 5b7e8bde2e..ebdcc523f0 100644 --- a/test/std/strings/basic.string/string.cons/initializer_list.pass.cpp +++ b/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