diff --git a/libcxx/include/string b/libcxx/include/string --- a/libcxx/include/string +++ b/libcxx/include/string @@ -616,6 +616,12 @@ #else # define _LIBCPP_STRING_INTERNAL_MEMORY_ACCESS #endif +#if (_LIBCPP_CLANG_VER >= 1600) + // TODO LLVM18: Remove special casing or macro +# define _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED true +#else +# define _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED false +#endif _LIBCPP_BEGIN_NAMESPACE_STD @@ -1842,7 +1848,7 @@ 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) + if (!__libcpp_is_constant_evaluated() && __begin != nullptr && __asan_annotate_container_with_allocator<_Allocator>::value) __sanitizer_annotate_contiguous_container(__begin, __end, __old_mid, __new_mid); } #else @@ -1852,11 +1858,7 @@ // ASan: short string is poisoned if and only if this function returns true. _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __annotate_short_string_check() const _NOEXCEPT { -#if !defined(_LIBCPP_HAS_NO_ASAN) && (_LIBCPP_CLANG_VER >= 1600) - // TODO LLVM18: remove special case value - return !__libcpp_is_constant_evaluated(); -#else - return false; + return _LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED && !__libcpp_is_constant_evaluated(); #endif } diff --git a/libcxx/test/libcxx/containers/strings/no_asan.pass.cpp b/libcxx/test/libcxx/containers/strings/no_asan.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/containers/strings/no_asan.pass.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 + +// + +// Test based on: https://bugs.chromium.org/p/chromium/issues/detail?id=1419798#c5 +// Some allocators during deallocation may not call destructors and just reuse memory. +// In those situations, one may want to deactivate annotations for a specific allocator. +// It's possible with __asan_annotate_container_with_allocator template class. +// This test confirms that those allocators work after turning off annotations. + +#include +#include +#include +#include + +struct reuse_allocator { + static size_t const N = 100; + reuse_allocator() { + for (size_t i = 0; i < N; ++i) + __buffers[i] = malloc(8*1024); + } + ~reuse_allocator() { + for (size_t i = 0; i < N; ++i) + free(__buffers[i]); + } + void* alloc() { + assert(__next_id < N); + return __buffers[__next_id++]; + } + void reset() { __next_id = 0; } + void* __buffers[N]; + size_t __next_id = 0; +} reuse_buffers; + +template +struct user_allocator { + using value_type = T; + user_allocator() = default; + template + user_allocator(user_allocator) {} + friend bool operator==(user_allocator, user_allocator) {return true;} + friend bool operator!=(user_allocator x, user_allocator y) {return !(x == y);} + + T* allocate(size_t) { return (T*)reuse_buffers.alloc(); } + void deallocate(T*, size_t) noexcept {} +}; + +template +struct std::__asan_annotate_container_with_allocator> { + static bool const value = false; +}; + +int main() { + using S = std::basic_string, user_allocator>; + + { + S* s = new (reuse_buffers.alloc()) S(); + for (int i = 0; i < 100; i++) + s->push_back('a'); + } + reuse_buffers.reset(); + { + S s; + for (int i = 0; i < 1000; i++) + s.push_back('b'); + } + + return 0; +} diff --git a/libcxx/test/libcxx/strings/basic.string/asan.pass.cpp b/libcxx/test/libcxx/strings/basic.string/asan.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/strings/basic.string/asan.pass.cpp @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: asan + +// + +// reference operator[](size_type n); + +#include +#include +#include + +#include "asan_testing.h" +#include "min_allocator.h" +#include "test_iterators.h" +#include "test_macros.h" + +extern "C" void __sanitizer_set_death_callback(void (*callback)(void)); + +void do_exit() { + exit(0); +} + +int main(int, char**) +{ + { + typedef cpp17_input_iterator MyInputIter; + // Should not trigger ASan. + std::basic_string v; + char i[] = {'a'}; + v.insert(v.begin(), MyInputIter(i), MyInputIter(i + 1)); + assert(v[0] == 'a'); + assert(is_string_asan_correct(v)); + } + + __sanitizer_set_death_callback(do_exit); + { + typedef char T; + typedef std::string C; + const T t[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + C c(std::begin(t), std::end(t)); + assert(is_string_asan_correct(c)); + assert(!__sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1)); + volatile T foo = c[c.size()]; // should trigger ASAN. Use volatile to prevent being optimized away. + assert(false); // if we got here, ASAN didn't trigger + ((void)foo); + } +} diff --git a/libcxx/test/support/asan_testing.h b/libcxx/test/support/asan_testing.h --- a/libcxx/test/support/asan_testing.h +++ b/libcxx/test/support/asan_testing.h @@ -51,20 +51,14 @@ if (TEST_IS_CONSTANT_EVALUATED) return true; if (c.data() != NULL) { -#if TEST_CLANG_VER < 16000 - // TODO LLVM18: remove special case - if(!is_string_short(c)) { -# endif - if (std::is_same>::value) - return __sanitizer_verify_contiguous_container( - c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) != 0; - else - return __sanitizer_verify_contiguous_container( - c.data(), c.data() + c.capacity() + 1, c.data() + c.capacity() + 1) != 0; -# if TEST_CLANG_VER < 16000 - // TODO LLVM18: remove special case - } -#endif + if(_LIBCPP_SHORT_STRING_ANNOTATIONS_ALLOWED || !is_string_short(c)) + return __sanitizer_verify_contiguous_container(c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) != 0; + if (__asan_annotate_container_with_allocator::value) + return __sanitizer_verify_contiguous_container( + c.data(), c.data() + c.size() + 1, c.data() + c.capacity() + 1) != 0; + else + return __sanitizer_verify_contiguous_container( + c.data(), c.data() + c.capacity() + 1, c.data() + c.capacity() + 1) != 0; } return true; }