From f3cb662b2f842e08ce22e021ab26b5381b6457ef Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Wed, 13 May 2026 13:07:41 -0700 Subject: [PATCH 1/9] Modify vector and string annotations to extend 'last valid' point to 'end' point in situations where the end has been extended to the end of the allocation block. --- stl/inc/vector | 24 ++- stl/inc/xstring | 35 ++-- tests/std/test.lst | 1 + .../env.lst | 34 ++++ .../test.cpp | 177 ++++++++++++++++++ 5 files changed, 255 insertions(+), 16 deletions(-) create mode 100644 tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst create mode 100644 tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp diff --git a/stl/inc/vector b/stl/inc/vector index 4a38d29e03..5d86103862 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -529,15 +529,29 @@ private: const void* const _Old_last = _STD _Unfancy(_Old_last_); const void* const _New_last = _STD _Unfancy(_New_last_); if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { + // If we realign the _End forward to maximize coverage, we need to keep other significant points + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // cleaning up. + // old state: - // [_First, _Old_last) valid - // [_Old_last, _End) poison + // [_First, _Old_last_aligned) valid + // [_Old_last_aligned, asan_aligned_after(_End)) poison // new state: - // [_First, _New_last) valid - // [_New_last, asan_aligned_after(_End)) poison + // [_First, _New_last_aligned) valid + // [_New_last_aligned, asan_aligned_after(_End)) poison + + const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); + const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; + const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; + _CSTD __sanitizer_annotate_contiguous_container( - _First, _STD _Get_asan_aligned_after(_End), _Old_last, _New_last); + _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); } else { + + // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be + // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, + // but may miss some coverage near end of buffer. + const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); if (_Aligned._First == _Aligned._End) { // The buffer does not end at least one shadow memory section; nothing to do. diff --git a/stl/inc/xstring b/stl/inc/xstring index b371e31d6b..a742488f86 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -716,21 +716,34 @@ private: _Asan_aligned_pointers _Aligned; if constexpr (_Large_string_always_asan_aligned) { - _Aligned = {_First, _STD _Get_asan_aligned_after(_End)}; - } else { - _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); + // If we realign the _End forward to maximize coverage, we need to keep other significant points + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // cleaning up. + + // --- always aligned case --- + // old state: + // [_First, _Old_last_aligned) valid + // [_Old_last_aligned, asan_aligned_after(_End)) poison + // new state: + // [_First, _New_last_aligned) valid + // [_New_last_aligned, asan_aligned_after(_End)) poison + + const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); + const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; + const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; + + _CSTD __sanitizer_annotate_contiguous_container( + _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); + return; } + + // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be + // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, + // but may miss some coverage near end of buffer. + const _Asan_aligned_pointers _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); - // --- always aligned case --- - // old state: - // [_First, _Old_last) valid - // [_Old_last, asan_aligned_after(_End)) poison - // new state: - // [_First, _New_last) valid - // [_New_last, asan_aligned_after(_End)) poison - // --- sometimes non-aligned case --- // old state: // [_Aligned._First, _Old_fixed) valid diff --git a/tests/std/test.lst b/tests/std/test.lst index e034874360..8c4f26db24 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -281,6 +281,7 @@ tests\GH_005800_stable_sort_large_alignment tests\GH_005816_numeric_limits_traps tests\GH_005968_headers_provide_begin_end tests\GH_005974_asan_annotate_optional +tests\GH_006276_annotation_poison_cleanup tests\LWG2381_num_get_floating_point tests\LWG2510_tag_classes tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst b/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst new file mode 100644 index 0000000000..87996b3e1f --- /dev/null +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# This test matrix is the usual test matrix, with all currently unsupported options removed, crossed with the ASan flags. + +# TRANSITION, google/sanitizers#328: clang-cl does not support /MDd or /MTd with ASan +RUNALL_INCLUDE ..\prefix.lst +RUNALL_CROSSLIST +PM_CL="/Zi /wd4611 /w14640 /Zc:threadSafeInit-" PM_LINK="/debug" +RUNALL_CROSSLIST +PM_CL="-fsanitize=address /BE /c /EHsc /MD /std:c++14" +PM_CL="-fsanitize=address /BE /c /EHsc /MDd /std:c++17 /permissive-" +PM_CL="-fsanitize=address /BE /c /EHsc /MT /std:c++20 /permissive-" +PM_CL="-fsanitize=address /BE /c /EHsc /MTd /std:c++latest /permissive-" +PM_CL="-fsanitize=address /EHsc /MD /std:c++14" +PM_CL="-fsanitize=address /EHsc /MD /std:c++17" +PM_CL="-fsanitize=address /EHsc /MD /std:c++20" +PM_CL="-fsanitize=address /EHsc /MD /std:c++latest /permissive- /Zc:char8_t- /Zc:preprocessor" +PM_CL="-fsanitize=address /EHsc /MD /std:c++latest /permissive- /Zc:noexceptTypes-" +PM_CL="-fsanitize=address /EHsc /MDd /std:c++14 /fp:except /Zc:preprocessor" +PM_CL="-fsanitize=address /EHsc /MDd /std:c++17 /permissive-" +PM_CL="-fsanitize=address /EHsc /MDd /std:c++20 /permissive-" +PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive- /Zc:wchar_t-" +PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive-" +PM_CL="-fsanitize=address /EHsc /MT /std:c++latest /permissive- /analyze:only /analyze:autolog-" +PM_CL="-fsanitize=address /EHsc /MT /std:c++latest /permissive-" +PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive" +PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive- /analyze:only /analyze:autolog-" +PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive- /fp:strict" +PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive-" +PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /std:c++14" +PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /std:c++17" +PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MT /std:c++20 /permissive-" +PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MT /std:c++latest /permissive- /fp:strict" diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp new file mode 100644 index 0000000000..8e3689978b --- /dev/null +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// REQUIRES: x64 || x86 || arm64 + +#if defined(__clang__) && defined(_M_ARM64) // TRANSITION, LLVM-184902, fixed in Clang 23 +#pragma comment(linker, "/INFERASANLIBS") +int main() {} +#else // ^^^ workaround / no workaround vvv + +#pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension +#pragma warning(disable : 4324) // '%s': structure was padded due to alignment specifier +#pragma warning(disable : 4365) // '%s': conversion from '%s' to '%s', signed/unsigned mismatch + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wc++17-extensions" // constexpr if is a C++17 extension +#define NO_SANITIZER_ADDRESS __attribute__((no_sanitize_address)) +#else // ^^^ clang / msvc vvv +#define NO_SANITIZER_ADDRESS __declspec(no_sanitize_address) +#endif // __clang__ + +#include +#include +#include +#include + +using namespace std; + +extern "C" uintptr_t __asan_shadow_memory_dynamic_address; + +NO_SANITIZER_ADDRESS unsigned char* shadow_addr_of(const void* addr) { + return (unsigned char*)(((uintptr_t)addr >> 3) + __asan_shadow_memory_dynamic_address); +} + +NO_SANITIZER_ADDRESS unsigned char shadow_byte_of(const void* addr) { + return *shadow_addr_of(addr); +} + +NO_SANITIZER_ADDRESS void print_shadow_bytes(const void* addr, const size_t string_size) { + for (size_t i = 0; i < string_size; i += 8) { + printf("%02x ", shadow_byte_of(reinterpret_cast(addr) + i)); + } + printf("\n"); +} + +template +class asan_unaware_arena { + // In practice, custom memory pools should also be annotated for ASan. + // For simplicity, we aren't doing that so we can directly see the + // effects from only the container poisoning. +public: + asan_unaware_arena() noexcept { + fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, shadow_addr_of(_alloc_buffer)); + } + + ~asan_unaware_arena() noexcept { + fprintf(stderr, "Shadow during destruction (should be all 00):\t"); + print_shadow(); + + // Shadow should be cleared. If it isn't, this memset will trigger an ASan container-overflow error. + // This will likely appear as unknown-error since partial poisoning relies on nearby + // shadow bytes to determine error type. + memset(_alloc_buffer, 0, ArenaSize); + } + + asan_unaware_arena(const asan_unaware_arena&) = delete; + asan_unaware_arena& operator=(const asan_unaware_arena&) = delete; + + asan_unaware_arena(asan_unaware_arena&&) = delete; + asan_unaware_arena& operator=(asan_unaware_arena&&) = delete; + + char* allocate(size_t num_bytes) { + if (_next_alloc + num_bytes > _alloc_buffer + ArenaSize) { + throw bad_alloc(); + } + + char* result = _next_alloc; + _next_alloc += num_bytes; + _next_alloc = reinterpret_cast((reinterpret_cast(_next_alloc) + (AllocationAlignment - 1)) & ~(AllocationAlignment - 1)); // align the next allocation pointer. + + fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n", + result, _next_alloc, num_bytes, + shadow_addr_of(result), shadow_addr_of(_next_alloc), shadow_addr_of(_next_alloc) - shadow_addr_of(result)); + + return result; + } + + void print_shadow() const noexcept { + print_shadow_bytes(_alloc_buffer, ArenaSize); + } + +private: + alignas(AllocationAlignment) char _alloc_buffer [ArenaSize]{}; + char* _next_alloc = _alloc_buffer; +}; + +template +struct arena_reuse_allocator { + using value_type = T; + static constexpr size_t Size = AllocSize; + static constexpr size_t _Minimum_asan_allocation_alignment = Alignment; + + template + struct rebind { + using other = arena_reuse_allocator; + }; + + arena_reuse_allocator(asan_unaware_arena* a) noexcept : _arena(a) {} + + template + arena_reuse_allocator(const arena_reuse_allocator& rhs) noexcept : _arena(rhs._arena) {} + + T* allocate(size_t n) { + return reinterpret_cast(_arena->allocate(n * sizeof(T))); + } + + void deallocate(T*, size_t) noexcept {} + + asan_unaware_arena* _arena; +}; + +const size_t arena_size = 256; + +template +void test_string() { + fprintf(stderr, "\nTesting string with allocator alignment of %zu\n", Alignment); + + asan_unaware_arena test_arena; + arena_reuse_allocator alloc(&test_arena); + + basic_string, decltype(alloc)> test_string(L"a 24 length string repr", alloc); + fprintf(stderr, "Shadow after string constructor:\t\t"); + test_arena.print_shadow(); + + test_string.append(L"o"); // add any character to trigger resize + fprintf(stderr, "Shadow after string resize:\t\t\t"); + test_arena.print_shadow(); +} + +template +void test_vector() { + fprintf(stderr, "\nTesting vector with allocator alignment of %zu\n", Alignment); + + asan_unaware_arena test_arena; + arena_reuse_allocator alloc(&test_arena); + + vector test_vector(23, L'a', alloc); + fprintf(stderr, "Shadow after vector constructor:\t\t"); + test_arena.print_shadow(); + + test_vector.push_back(L'o'); // trigger resize + fprintf(stderr, "Shadow after vector resize:\t\t\t"); + test_arena.print_shadow(); +} + +int main() { + // Annotations for std::string and std::vector follow different + // paths based off allocation alignment, so test with both. + + // Alignment >= 8 is aligned with shadow bytes' 8-byte granularity, so string/vector + // annotations try to maximize coverage by poisoning past the allocation, since the allocator + // should not hand out that memory anyway. + + // Alignment < 8 is not aligned with shadow bytes, so string/vector annotations + // are more conservative and only poison the memory that was handed out by the + // allocator, leaving some at the end unpoisoned. + + test_string<2>(); // under poisoned code path + test_string<8>(); // over poisoned code path + + test_vector<2>(); // under poisoned code path + test_vector<8>(); // over poisoned code path + + return 0; +} + +#endif // ^^^ no workaround ^^^ From 17f94e2c1193afcedf741e6f0beda9b862374016 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Thu, 14 May 2026 09:38:53 -0700 Subject: [PATCH 2/9] Properly mirror xstring/vector changes. --- stl/inc/vector | 2 +- stl/inc/xstring | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/stl/inc/vector b/stl/inc/vector index 5d86103862..b9575019f8 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -530,7 +530,7 @@ private: const void* const _New_last = _STD _Unfancy(_New_last_); if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when // cleaning up. // old state: diff --git a/stl/inc/xstring b/stl/inc/xstring index a742488f86..bfc29163b9 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -714,10 +714,9 @@ private: // for the non-aligned buffer options, the buffer must always have size >= 9 bytes, // so it will always end at least one shadow memory section. - _Asan_aligned_pointers _Aligned; if constexpr (_Large_string_always_asan_aligned) { // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when // cleaning up. // --- always aligned case --- @@ -736,7 +735,7 @@ private: _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); return; } - + // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, // but may miss some coverage near end of buffer. From 4b35e625bd9493635be840bd0f7b58b60e847d0e Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Thu, 14 May 2026 10:13:53 -0700 Subject: [PATCH 3/9] Fix formatting --- stl/inc/vector | 4 +-- stl/inc/xstring | 10 +++---- .../test.cpp | 28 ++++++++++--------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/stl/inc/vector b/stl/inc/vector index b9575019f8..ecc8c430bb 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -530,7 +530,7 @@ private: const void* const _New_last = _STD _Unfancy(_New_last_); if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when // cleaning up. // old state: @@ -540,7 +540,7 @@ private: // [_First, _New_last_aligned) valid // [_New_last_aligned, asan_aligned_after(_End)) poison - const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); + const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; diff --git a/stl/inc/xstring b/stl/inc/xstring index bfc29163b9..83a590d3c7 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -716,7 +716,7 @@ private: if constexpr (_Large_string_always_asan_aligned) { // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when + // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when // cleaning up. // --- always aligned case --- @@ -727,7 +727,7 @@ private: // [_First, _New_last_aligned) valid // [_New_last_aligned, asan_aligned_after(_End)) poison - const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); + const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; @@ -735,13 +735,13 @@ private: _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); return; } - + // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, // but may miss some coverage near end of buffer. const _Asan_aligned_pointers _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); - const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); - const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); + const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); + const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); // --- sometimes non-aligned case --- // old state: diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp index 8e3689978b..d0283248c0 100644 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp @@ -19,9 +19,9 @@ int main() {} #define NO_SANITIZER_ADDRESS __declspec(no_sanitize_address) #endif // __clang__ +#include #include #include -#include #include using namespace std; @@ -29,7 +29,7 @@ using namespace std; extern "C" uintptr_t __asan_shadow_memory_dynamic_address; NO_SANITIZER_ADDRESS unsigned char* shadow_addr_of(const void* addr) { - return (unsigned char*)(((uintptr_t)addr >> 3) + __asan_shadow_memory_dynamic_address); + return (unsigned char*) (((uintptr_t) addr >> 3) + __asan_shadow_memory_dynamic_address); } NO_SANITIZER_ADDRESS unsigned char shadow_byte_of(const void* addr) { @@ -53,7 +53,7 @@ class asan_unaware_arena { fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, shadow_addr_of(_alloc_buffer)); } - ~asan_unaware_arena() noexcept { + ~asan_unaware_arena() noexcept { fprintf(stderr, "Shadow during destruction (should be all 00):\t"); print_shadow(); @@ -63,10 +63,10 @@ class asan_unaware_arena { memset(_alloc_buffer, 0, ArenaSize); } - asan_unaware_arena(const asan_unaware_arena&) = delete; + asan_unaware_arena(const asan_unaware_arena&) = delete; asan_unaware_arena& operator=(const asan_unaware_arena&) = delete; - asan_unaware_arena(asan_unaware_arena&&) = delete; + asan_unaware_arena(asan_unaware_arena&&) = delete; asan_unaware_arena& operator=(asan_unaware_arena&&) = delete; char* allocate(size_t num_bytes) { @@ -76,11 +76,12 @@ class asan_unaware_arena { char* result = _next_alloc; _next_alloc += num_bytes; - _next_alloc = reinterpret_cast((reinterpret_cast(_next_alloc) + (AllocationAlignment - 1)) & ~(AllocationAlignment - 1)); // align the next allocation pointer. + _next_alloc = reinterpret_cast((reinterpret_cast(_next_alloc) + (AllocationAlignment - 1)) + & ~(AllocationAlignment - 1)); // align the next allocation pointer. - fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n", - result, _next_alloc, num_bytes, - shadow_addr_of(result), shadow_addr_of(_next_alloc), shadow_addr_of(_next_alloc) - shadow_addr_of(result)); + fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n", result, + _next_alloc, num_bytes, shadow_addr_of(result), shadow_addr_of(_next_alloc), + shadow_addr_of(_next_alloc) - shadow_addr_of(result)); return result; } @@ -90,14 +91,14 @@ class asan_unaware_arena { } private: - alignas(AllocationAlignment) char _alloc_buffer [ArenaSize]{}; + alignas(AllocationAlignment) char _alloc_buffer[ArenaSize]{}; char* _next_alloc = _alloc_buffer; }; template struct arena_reuse_allocator { - using value_type = T; - static constexpr size_t Size = AllocSize; + using value_type = T; + static constexpr size_t Size = AllocSize; static constexpr size_t _Minimum_asan_allocation_alignment = Alignment; template @@ -108,7 +109,8 @@ struct arena_reuse_allocator { arena_reuse_allocator(asan_unaware_arena* a) noexcept : _arena(a) {} template - arena_reuse_allocator(const arena_reuse_allocator& rhs) noexcept : _arena(rhs._arena) {} + arena_reuse_allocator(const arena_reuse_allocator& rhs) noexcept + : _arena(rhs._arena) {} T* allocate(size_t n) { return reinterpret_cast(_arena->allocate(n * sizeof(T))); From 7b5cd6303cbb71139d7adc8073e5c15970ba36e8 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Fri, 15 May 2026 12:49:45 -0700 Subject: [PATCH 4/9] Fix shadowing --- .../GH_006276_annotation_poison_cleanup/test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp index d0283248c0..4227f5104c 100644 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp @@ -124,7 +124,7 @@ struct arena_reuse_allocator { const size_t arena_size = 256; template -void test_string() { +void test_string_poisoning() { fprintf(stderr, "\nTesting string with allocator alignment of %zu\n", Alignment); asan_unaware_arena test_arena; @@ -140,7 +140,7 @@ void test_string() { } template -void test_vector() { +void test_vector_poisoning() { fprintf(stderr, "\nTesting vector with allocator alignment of %zu\n", Alignment); asan_unaware_arena test_arena; @@ -167,11 +167,11 @@ int main() { // are more conservative and only poison the memory that was handed out by the // allocator, leaving some at the end unpoisoned. - test_string<2>(); // under poisoned code path - test_string<8>(); // over poisoned code path + test_string_poisoning<2>(); // under poisoned code path + test_string_poisoning<8>(); // over poisoned code path - test_vector<2>(); // under poisoned code path - test_vector<8>(); // over poisoned code path + test_vector_poisoning<2>(); // under poisoned code path + test_vector_poisoning<8>(); // over poisoned code path return 0; } From 47fd8eade2cabbd0eb16e8d4b37d5115c13cf12d Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Wed, 3 Jun 2026 15:48:59 -0700 Subject: [PATCH 5/9] Lightly refactor STL annotation logic for vector/string to attempt to imbue a sense of intent at the call site (i.e. 'end' pointers should be realigned when overpoisoning, but all other pointers should not - other attempts to capture this were convoluted). Moved testing into pre-existing vector/string annotation tests, with an added allocator that will be able to detect 'left-poison-bytes-behind' issues as well as a gh-6276 specific test case and extra testing for finding similar cases (insert/remove triggering a realloc). --- stl/inc/vector | 172 +++++++++-------- stl/inc/xstring | 127 ++++++------- tests/std/include/test_asan_support.hpp | 110 +++++++++++ .../GH_002030_asan_annotate_string/test.cpp | 164 +++++++++++++--- .../GH_002030_asan_annotate_vector/test.cpp | 176 +++++++++++++----- .../env.lst | 6 - .../test.cpp | 138 +++++++++----- 7 files changed, 621 insertions(+), 272 deletions(-) create mode 100644 tests/std/include/test_asan_support.hpp diff --git a/stl/inc/vector b/stl/inc/vector index ecc8c430bb..9a7ff27134 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -482,95 +482,102 @@ public: private: #ifdef _INSERT_VECTOR_ANNOTATION - _CONSTEXPR20 void _Create_annotation() const noexcept { - // Annotates the shadow memory of the valid range - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Myend, _My_data._Mylast); + + _CONSTEXPR20 bool _Should_annotate() const noexcept { +#if _HAS_CXX20 + if (_STD is_constant_evaluated()) { + return false; + } +#endif // _HAS_CXX20 + + return _Asan_vector_should_annotate; } - _CONSTEXPR20 void _Remove_annotation() const noexcept { - // Removes the shadow memory annotation of the range - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Myend); + _CONSTEXPR20 const void* _Annotation_begin() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myfirst != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Myfirst); } - _CONSTEXPR20 void _Modify_annotation(const difference_type _Count) const noexcept { - // Extends/shrinks the annotated range by _Count - if (_Count == 0) { // nothing to do - // This also avoids calling _Apply_annotation() with null pointers - // when the vector has zero capacity, see GH-2464. - return; - } + _CONSTEXPR20 const void* _Annotation_at(const size_type _Index) const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myfirst != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Myfirst + _Index); + } - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Mylast + _Count); + _CONSTEXPR20 const void* _Annotation_last() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Mylast != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Mylast); } - static _CONSTEXPR20 void _Apply_annotation( - pointer _First_, pointer _End_, pointer _Old_last_, pointer _New_last_) noexcept { - _STL_INTERNAL_CHECK(_First_ != nullptr); - _STL_INTERNAL_CHECK(_End_ != nullptr); - _STL_INTERNAL_CHECK(_Old_last_ != nullptr); - _STL_INTERNAL_CHECK(_New_last_ != nullptr); + _CONSTEXPR20 const void* _Annotation_last_with_difference(const difference_type _Count) const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Mylast != nullptr); + return _STD _Unfancy(_Mypair._Myval2._Mylast + _Count); + } + + _CONSTEXPR20 const void* _Annotation_end() const noexcept { + _STL_INTERNAL_CHECK(_Mypair._Myval2._Myend != nullptr); + const void* const _End = _STD _Unfancy(_Mypair._Myval2._Myend); + + if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { + // If the allocator alignment is less than or equal to the ASan granularity, then we can + // realign the end forward to maximize the annotation coverage. + return _STD _Get_asan_aligned_after(_End); + } else { + // The allocation may not end on an ASan granularity boundary. In that case, annotating up to _End + // could affect shadow bytes for memory beyond this allocation, so restrict annotations to the largest + // aligned subrange inside the buffer. This may leave the tail of the buffer unannotated. + return _End; // Using unaligned end will snap it to previous ASan granularity boundary. + } + } + + _CONSTEXPR20 void _Create_annotation() const noexcept { if constexpr (!_Disable_ASan_container_annotations_for_allocator) { -#if _HAS_CXX20 - if (_STD is_constant_evaluated()) { + if (!_Should_annotate()) { return; } -#endif // _HAS_CXX20 - if (!_Asan_vector_should_annotate) { + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last()); + } + } + + _CONSTEXPR20 void _Remove_annotation() const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { return; } - const void* const _First = _STD _Unfancy(_First_); - const void* const _End = _STD _Unfancy(_End_); - const void* const _Old_last = _STD _Unfancy(_Old_last_); - const void* const _New_last = _STD _Unfancy(_New_last_); - if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { - // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when - // cleaning up. - - // old state: - // [_First, _Old_last_aligned) valid - // [_Old_last_aligned, asan_aligned_after(_End)) poison - // new state: - // [_First, _New_last_aligned) valid - // [_New_last_aligned, asan_aligned_after(_End)) poison - - const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); - const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; - const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end()); + } + } - _CSTD __sanitizer_annotate_contiguous_container( - _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); - } else { + _CONSTEXPR20 void _Modify_annotation(const difference_type _Count) const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; + } - // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be - // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, - // but may miss some coverage near end of buffer. + if (_Count == 0) { // nothing to do + // This also avoids calling _Apply_annotation() with null pointers + // when the vector has zero capacity, see GH-2464. + return; + } - const auto _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); - if (_Aligned._First == _Aligned._End) { - // The buffer does not end at least one shadow memory section; nothing to do. - return; - } + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_last_with_difference(_Count)); + } + } - const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); - const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); - - // old state: - // [_Aligned._First, _Old_fixed) valid - // [_Old_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - // new state: - // [_Aligned._First, _New_fixed) valid - // [_New_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + _CONSTEXPR20 void _Set_annotation(const size_type _Target_size) const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; } + + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_at(_Target_size) + ); } } @@ -582,8 +589,17 @@ private: constexpr explicit _Asan_extend_guard(vector* _Myvec_, size_type _Target_size_) noexcept : _Myvec(_Myvec_), _Target_size(_Target_size_) { _STL_INTERNAL_CHECK(_Myvec != nullptr); - auto& _My_data = _Myvec->_Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Mylast, _My_data._Myfirst + _Target_size); + + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Myvec->_Should_annotate()) { + return; + } + + _CSTD __sanitizer_annotate_contiguous_container( + _Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), _Myvec->_Annotation_last(), + _Myvec->_Annotation_at(_Target_size) + ); + } } _CONSTEXPR20 ~_Asan_extend_guard() { @@ -591,9 +607,17 @@ private: return; } - // Shrinks the shadow memory to the current size of the vector. - auto& _My_data = _Myvec->_Mypair._Myval2; - _Apply_annotation(_My_data._Myfirst, _My_data._Myend, _My_data._Myfirst + _Target_size, _My_data._Mylast); + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Myvec->_Should_annotate()) { + return; + } + + // Shrinks the shadow memory to the current size of the vector. + _CSTD __sanitizer_annotate_contiguous_container( + _Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), _Myvec->_Annotation_at(_Target_size), + _Myvec->_Annotation_last() + ); + } } _CONSTEXPR20 void _Release() noexcept { diff --git a/stl/inc/xstring b/stl/inc/xstring index 83a590d3c7..31ea4478d4 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -668,91 +668,86 @@ private: #endif // _HAS_CXX17 #ifdef _INSERT_STRING_ANNOTATION - _CONSTEXPR20 void _Create_annotation() const noexcept { - // Annotates the valid range with shadow memory - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _My_data._Myres, _My_data._Mysize); + + _CONSTEXPR20 bool _Should_annotate() const noexcept { +#if _HAS_CXX20 + if (_STD is_constant_evaluated()) { + return false; + } +#endif // _HAS_CXX20 + // Only annotate if the string is large enough to be worth it, and if the user hasn't opted out via _Asan_string_should_annotate. + return _Mypair._Myval2._Myres > _Small_string_capacity && _Asan_string_should_annotate; } - _CONSTEXPR20 void _Remove_annotation() const noexcept { - // Removes annotation of the range with shadow memory - auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _My_data._Mysize, _My_data._Myres); + _CONSTEXPR20 const void* _Annotation_begin() const noexcept { + return _Mypair._Myval2._Myptr(); } - _CONSTEXPR20 void _Modify_annotation(const size_type _Old_size, const size_type _New_size) const noexcept { - if (_Old_size == _New_size) { - return; - } + _CONSTEXPR20 const void* _Annotation_at(const size_type _Size) const noexcept { + return _Mypair._Myval2._Myptr() + _Size + 1; // +1 to include null terminator in annotation + } + + _CONSTEXPR20 const void* _Annotation_last() const noexcept { + return _Mypair._Myval2._Myptr() + _Mypair._Myval2._Mysize + 1; // +1 to include null terminator in annotation + } + _CONSTEXPR20 const void* _Annotation_end() const noexcept { auto& _My_data = _Mypair._Myval2; - _Apply_annotation(_My_data._Myptr(), _My_data._Myres, _Old_size, _New_size); + const void* const _End = _My_data._Myptr() + _My_data._Myres + 1; // +1 to include null terminator in annotation + + constexpr bool _Large_string_always_asan_aligned = + (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; + + if constexpr (_Large_string_always_asan_aligned) { + // If the allocator alignment is less than or equal to the ASan granularity, then we can + // realign the end forward to maximize the annotation coverage. + return _STD _Get_asan_aligned_after(_End); + } else { + // The allocation may not end on an ASan granularity boundary. In that case, annotating up to _End + // could affect shadow bytes for memory beyond this allocation, so restrict annotations to the largest + // aligned subrange inside the buffer. This may leave the tail of the buffer unannotated. + + return _End; // Using unaligned end will snap it to previous ASan granularity boundary. + } } - static _CONSTEXPR20 void _Apply_annotation(const value_type* const _First, const size_type _Capacity, - const size_type _Old_size, const size_type _New_size) noexcept { + _CONSTEXPR20 void _Create_annotation() const noexcept { if constexpr (!_Disable_ASan_container_annotations_for_allocator) { -#if _HAS_CXX20 - if (_STD is_constant_evaluated()) { + if (!_Should_annotate()) { return; } -#endif // _HAS_CXX20 - // Don't annotate small strings; only annotate on the heap. - if (_Capacity <= _Small_string_capacity || !_Asan_string_should_annotate) { + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last() + ); + } + } + + _CONSTEXPR20 void _Remove_annotation() const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { return; } - // Note that `_Capacity`, `_Old_size`, and `_New_size` do not include the null terminator - const void* const _End = _First + _Capacity + 1; - const void* const _Old_last = _First + _Old_size + 1; - const void* const _New_last = _First + _New_size + 1; - - constexpr bool _Large_string_always_asan_aligned = - (_Container_allocation_minimum_asan_alignment) >= _Asan_granularity; - - // for the non-aligned buffer options, the buffer must always have size >= 9 bytes, - // so it will always end at least one shadow memory section. - - if constexpr (_Large_string_always_asan_aligned) { - // If we realign the _End forward to maximize coverage, we need to keep other significant points - // aligned with our concept of the end of the buffer, otherwise we may leave shadow bytes behind when - // cleaning up. - - // --- always aligned case --- - // old state: - // [_First, _Old_last_aligned) valid - // [_Old_last_aligned, asan_aligned_after(_End)) poison - // new state: - // [_First, _New_last_aligned) valid - // [_New_last_aligned, asan_aligned_after(_End)) poison - - const void* const _New_end_aligned = _STD _Get_asan_aligned_after(_End); - const void* const _Old_last_aligned = (_Old_last == _End) ? _New_end_aligned : _Old_last; - const void* const _New_last_aligned = (_New_last == _End) ? _New_end_aligned : _New_last; + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end() + ); + } + } - _CSTD __sanitizer_annotate_contiguous_container( - _First, _New_end_aligned, _Old_last_aligned, _New_last_aligned); + _CONSTEXPR20 void _Modify_annotation(const size_type _Old_size, const size_type _New_size) const noexcept { + if constexpr (!_Disable_ASan_container_annotations_for_allocator) { + if (!_Should_annotate()) { + return; + } + + if (_Old_size == _New_size) { return; } - // Allocation is potentially unaligned, so we cannot annotate whole buffer since we might be - // entering memory owned by someone else. Therefore, pull back where the annotations end on the buffer, - // but may miss some coverage near end of buffer. - const _Asan_aligned_pointers _Aligned = _STD _Get_asan_aligned_first_end(_First, _End); - const void* const _Old_fixed = _Aligned._Clamp_to_end(_Old_last); - const void* const _New_fixed = _Aligned._Clamp_to_end(_New_last); - - // --- sometimes non-aligned case --- - // old state: - // [_Aligned._First, _Old_fixed) valid - // [_Old_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - // new state: - // [_Aligned._First, _New_fixed) valid - // [_New_fixed, _Aligned._End) poison - // [_Aligned._End, _End) valid - _CSTD __sanitizer_annotate_contiguous_container(_Aligned._First, _Aligned._End, _Old_fixed, _New_fixed); + _CSTD __sanitizer_annotate_contiguous_container( + _Annotation_begin(), _Annotation_end(), _Annotation_at(_Old_size), _Annotation_at(_New_size) + ); } } diff --git a/tests/std/include/test_asan_support.hpp b/tests/std/include/test_asan_support.hpp new file mode 100644 index 0000000000..93f6e530d4 --- /dev/null +++ b/tests/std/include/test_asan_support.hpp @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wc++17-extensions" // constexpr if is a C++17 extension +#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else // ^^^ clang / msvc vvv +#define NO_SANITIZE_ADDRESS __declspec(no_sanitize_address) +#endif // __clang__ + +#ifdef __SANITIZE_ADDRESS__ +#include +#include + +extern "C" uintptr_t __asan_shadow_memory_dynamic_address; +extern "C" void* __sanitizer_contiguous_container_find_bad_address(const void* beg, const void* mid, const void* end) noexcept; +extern "C" void __asan_describe_address(void*) noexcept; + +namespace std_testing { + namespace asan { + constexpr uintptr_t shadow_granularity = 8; + + const char* round_down_to_shadow_granularity(const char* const addr) { + return reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); + } + + const char* round_up_to_shadow_granularity(const char* const addr) { + return reinterpret_cast((reinterpret_cast(addr) + shadow_granularity - 1) & ~(shadow_granularity - 1)); + } + + NO_SANITIZE_ADDRESS unsigned char* shadow_addr_of(const void* const addr) { + return reinterpret_cast((reinterpret_cast(addr) >> 3) + __asan_shadow_memory_dynamic_address); + } + + NO_SANITIZE_ADDRESS unsigned char shadow_byte_of(const void* addr) { + return *shadow_addr_of(addr); + } + + void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, unsigned char expected_shadow_byte = 0xff /*unused shadow byte*/) { + constexpr size_t shadow_bytes_per_line = 16; + constexpr uintptr_t bytes_per_line_mask = (shadow_bytes_per_line * shadow_granularity) - 1; + + const char* begin = reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); // align down to shadow boundary + const char* end = reinterpret_cast(addr) + num_bytes; + + if (error_addr) { + assert(error_addr >= begin && error_addr <= end); + fprintf(stderr, "ERROR: Expected %02x shadow byte at %p, but got %02x.\n", expected_shadow_byte, error_addr, shadow_byte_of(error_addr)); + } + + fprintf(stderr, "Shadow for addresses %p -> %p (%p -> %p):", begin, end, shadow_addr_of(begin), shadow_addr_of(end)); + + const char* const print_begin = reinterpret_cast(reinterpret_cast(begin) - bytes_per_line_mask & ~bytes_per_line_mask); + const char* const print_end = reinterpret_cast((reinterpret_cast(end) + bytes_per_line_mask) & ~bytes_per_line_mask); + for (const char* p = print_begin; p < print_end; p += 8) { + if ((reinterpret_cast(p) & bytes_per_line_mask) == 0) { + fprintf(stderr, "\n %p: ", p); + } + if (reinterpret_cast(p) == (reinterpret_cast(error_addr) & ~(shadow_granularity - 1))) { + fprintf(stderr, "\b[%02x]", shadow_byte_of(p)); + } else { + fprintf(stderr, "%02x ", shadow_byte_of(p)); + } + } + fprintf(stderr, "\n"); + } + + bool verify_poisoning_cleared(void* ptr, size_t num_bytes) { + const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(ptr)); + const char* end = reinterpret_cast(ptr) + num_bytes; + + void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, end, end); + if (bad_addr) { + print_shadow_bytes(ptr, num_bytes, bad_addr, 0); + __asan_describe_address(bad_addr); + return false; + } + return true; + } + + bool verify_container_poisoning(const void* const addr, const size_t valid_size, const size_t total_capacity, const bool is_overpoisoned) { + const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(addr)); + const char* const mid = reinterpret_cast(addr) + valid_size; + const char* end = reinterpret_cast(addr) + total_capacity; + + if (is_overpoisoned) { + end = round_up_to_shadow_granularity(end); + } + + void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, mid, end); + if (bad_addr) { + const char* const b1 = round_down_to_shadow_granularity(mid); + const char* const b2 = round_up_to_shadow_granularity(mid); + + const unsigned char expected_byte = static_cast( + (bad_addr >= b1 && bad_addr < b2) ? static_cast(mid - b1) : (bad_addr < mid) ? 0 : 0xfc + ); + + print_shadow_bytes(addr, total_capacity, bad_addr, expected_byte); + __asan_describe_address(bad_addr); + return false; + } + return true; + } + } // namespace asan +} // namespace std_testing +#endif // __SANITIZER_ADDRESS__ + diff --git a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp index d91b5774b3..3f9e617f6a 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -32,16 +32,13 @@ int main() {} #include #endif // _HAS_CXX17 +#include #include using namespace std; #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) -#ifdef __SANITIZE_ADDRESS__ -extern "C" int __sanitizer_verify_contiguous_container(const void* beg, const void* mid, const void* end) noexcept; -#endif // ASan instrumentation enabled - constexpr auto literal_input = "Hello fluffy kittens"; #ifdef __cpp_char8_t constexpr auto literal_input_u8 = u8"Hello fluffy kittens"; @@ -178,6 +175,17 @@ class input_iterator_tester { } }; +template +bool verify_poisoning_cleared(CharType *ptr, size_t capacity) { +#ifdef __SANITIZE_ADDRESS__ + return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); +#else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv + (void) ptr; + (void) capacity; + return true; +#endif // ASan instrumentation disabled +} + template bool verify_string(const basic_string, Alloc>& str) { #ifdef __SANITIZE_ADDRESS__ @@ -185,24 +193,12 @@ bool verify_string(const basic_string, Alloc>& s return true; } - const void* const buffer = str.data(); - const void* const buf_end = str.data() + str.capacity() + 1; - - constexpr bool _Large_string_always_aligned = - (_Container_allocation_minimum_asan_alignment>) >= 8; - - _Asan_aligned_pointers aligned; - if constexpr (_Large_string_always_aligned) { - aligned = {buffer, _Get_asan_aligned_after(buf_end)}; - } else { - aligned = _Get_asan_aligned_first_end(buffer, buf_end); - } - assert(aligned._First != aligned._End); - - const void* const mid = str.data() + str.size() + 1; - const void* const fixed_mid = aligned._Clamp_to_end(mid); - - return __sanitizer_verify_contiguous_container(aligned._First, fixed_mid, aligned._End) != 0; + return std_testing::asan::verify_container_poisoning( + str.data(), + (str.size() + 1) * sizeof(CharType), + (str.capacity() + 1) * sizeof(CharType), + (_Container_allocation_minimum_asan_alignment>) >= 8 + ); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) str; return true; @@ -241,7 +237,8 @@ struct aligned_allocator : public custom_test_allocator, aligned_allocator>> == 8); +template +struct extra_space_aligned_allocator : public custom_test_allocator { + static constexpr size_t _Minimum_asan_allocation_alignment = 8; + static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); + + extra_space_aligned_allocator() = default; + template + constexpr extra_space_aligned_allocator(const extra_space_aligned_allocator&) noexcept {} + + CharType* allocate(size_t n) { + CharType* mem = new CharType[n + 2 * extra_space_per_side]; + return mem + extra_space_per_side; + } + + void deallocate(CharType* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - extra_space_per_side, n + 2 * extra_space_per_side)); + delete[] (p - extra_space_per_side); + } +}; +STATIC_ASSERT( + _Container_allocation_minimum_asan_alignment, extra_space_aligned_allocator>> == 8); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_aligned_allocator>> + == 8); + template struct explicit_allocator : public custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = alignof(CharType); @@ -264,7 +286,8 @@ struct explicit_allocator : public custom_test_allocator, implicit_allocator>> == 2); +template +struct extra_space_unaligned_allocator : public custom_test_allocator { + static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); + + extra_space_unaligned_allocator() = default; + template + constexpr extra_space_unaligned_allocator(const extra_space_unaligned_allocator&) noexcept {} + + CharType* allocate(size_t n) { + CharType* mem = new CharType[n + 1 + 2*extra_space_per_side]; + return mem + extra_space_per_side + 1; + } + + void deallocate(CharType* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2*extra_space_per_side)); + delete[] (p - 1 - extra_space_per_side); + } +}; +STATIC_ASSERT( + _Container_allocation_minimum_asan_alignment, extra_space_unaligned_allocator>> == 1); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_unaligned_allocator>> + == 2); + // Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`) template struct implicit_allocator_no_asan_annotations : implicit_allocator { @@ -367,12 +415,12 @@ void test_construction() { assert(verify_string(literal_constructed)); assert(verify_string(copy_assigned_large_to_sso)); - str copy_assigned_sso_to_large(get_large_input()); + str copy_assigned_sso_to_large(get_large_input()); copy_assigned_sso_to_large = literal_constructed_sso; assert(verify_string(literal_constructed_sso)); assert(verify_string(copy_assigned_sso_to_large)); - str copy_assigned_large_to_large(get_large_input()); + str copy_assigned_large_to_large(get_large_input()); // creating allocator 28 with arena 8 copy_assigned_large_to_large = literal_constructed; assert(verify_string(literal_constructed)); assert(verify_string(copy_assigned_large_to_large)); @@ -392,7 +440,7 @@ void test_construction() { assert(verify_string(copy_assigned_sso_to_large)); assert(verify_string(move_assigned_sso_to_large)); - str move_assigned_large_to_large(get_large_input()); + str move_assigned_large_to_large(get_large_input()); // creating allocator 42 with arena 12 move_assigned_large_to_large = move(copy_assigned_large_to_large); assert(verify_string(copy_assigned_large_to_large)); assert(verify_string(move_assigned_large_to_large)); @@ -910,6 +958,15 @@ void test_append() { str op_char_rstr_sso_growing = CharType{'!'} + str(max_sso_size, CharType{'b'}); assert(verify_string(op_char_rstr_sso_growing)); } + + { // stress test append, testing for correctness after triggering resize (gh-6276) + for (size_t i = 1; i < 1024; ++i) { + str stress_append{}; + stress_append.assign(i, CharType{'a'}); + stress_append.append({CharType{'a'}}); + assert(verify_string(stress_append)); + } + } } template @@ -1542,6 +1599,15 @@ void test_removal() { assert(verify_string(pop_back_shrinking)); } + { // stress test pop_back - testing for correctness after triggering resize (gh-6276) + for (size_t i = 1; i < 1024; ++i) { + str stress_pop_back{}; + stress_pop_back.assign(i, CharType{'a'}); + stress_pop_back.pop_back(); + assert(verify_string(stress_pop_back)); + } + } + { // shrink_to_fit str shrink_to_fit(32, CharType{'a'}); shrink_to_fit.resize(min_large_size); @@ -1558,6 +1624,8 @@ void test_removal() { shrink_to_fit_shrinking.shrink_to_fit(); assert(verify_string(shrink_to_fit_shrinking)); } + + } template @@ -1918,8 +1986,10 @@ template void run_allocator_matrix() { run_tests>(); run_custom_allocator_matrix(); + run_custom_allocator_matrix(); run_custom_allocator_matrix(); run_custom_allocator_matrix(); + run_custom_allocator_matrix(); // To test ASan annotation disablement, we use an ad-hoc allocator type to avoid disrupting other // tests that depend on annotations being enabled. Therefore, unlike the prior tests, @@ -1975,6 +2045,41 @@ void test_gh_3955() { assert(s == t); } + +void test_gh_6276() { + { + basic_string, extra_space_unaligned_allocator> unaligned{L"1234567890123456789012"}; + assert(verify_string(unaligned)); + + unaligned.append(L"3"); + assert(verify_string(unaligned)); + } + + { + basic_string, extra_space_unaligned_allocator> unaligned{L"1234567890123456789012"}; + assert(verify_string(unaligned)); + + unaligned.pop_back(); + assert(verify_string(unaligned)); + } + + { + basic_string, extra_space_aligned_allocator> aligned{L"12345678901234567890123"}; + assert(verify_string(aligned)); + + aligned.append(L"4"); + assert(verify_string(aligned)); + } + + { + basic_string, extra_space_aligned_allocator> aligned{L"12345678901234567890123"}; + assert(verify_string(aligned)); + + aligned.pop_back(); + assert(verify_string(aligned)); + } +} + int main(int argc, char* argv[]) { std_testing::death_test_executive exec([] { run_allocator_matrix(); @@ -1989,7 +2094,9 @@ int main(int argc, char* argv[]) { test_DevCom_10109507(); test_gh_3883(); test_gh_3955(); + test_gh_6276(); }); + #ifdef __SANITIZE_ADDRESS__ exec.add_death_tests({ run_asan_container_overflow_death_test, @@ -2001,6 +2108,7 @@ int main(int argc, char* argv[]) { run_asan_container_overflow_death_test, }); #endif // ASan instrumentation enabled + return exec.run(argc, argv); } diff --git a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp index 589fa132cf..a4a56dfbe1 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -18,6 +18,7 @@ int main() {} #include #include +#include #pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension @@ -37,13 +38,6 @@ using namespace std; #endif #endif -#ifdef __SANITIZE_ADDRESS__ -extern "C" { -void* __sanitizer_contiguous_container_find_bad_address(const void* beg, const void* mid, const void* end) noexcept; -void __asan_describe_address(void*) noexcept; -} -#endif // ASan instrumentation enabled - struct non_trivial_can_throw { non_trivial_can_throw() { ++i; @@ -164,45 +158,26 @@ class input_iterator_tester { } }; +template +bool verify_poisoning_cleared(CharType *ptr, size_t capacity) { +#ifdef __SANITIZE_ADDRESS__ + return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); +#else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv + (void) ptr; + (void) capacity; + return true; +#endif // ASan instrumentation disabled +} + template bool verify_vector(vector& vec) { #ifdef __SANITIZE_ADDRESS__ - const void* buffer = vec.data(); - const void* buf_end = vec.data() + vec.capacity(); - _Asan_aligned_pointers aligned; - - if constexpr ((_Container_allocation_minimum_asan_alignment>) >= 8) { - aligned = {buffer, buf_end}; - } else { - aligned = _Get_asan_aligned_first_end(buffer, buf_end); - if (aligned._First == aligned._End) { - return true; - } - } - - const void* const mid = vec.data() + vec.size(); - const void* const fixed_mid = aligned._Clamp_to_end(mid); - - void* bad_address = __sanitizer_contiguous_container_find_bad_address(aligned._First, fixed_mid, aligned._End); - if (bad_address == nullptr) { - return true; - } - - if (bad_address < mid) { - cout << bad_address << " was marked as poisoned when it should not be." << endl; - } else { - cout << bad_address << " was not marked as poisoned when it should be." << endl; - } - cout << "Vector State:" << endl; - cout << " begin: " << buffer << endl; - cout << " aligned begin: " << aligned._First << endl; - cout << " last: " << mid << endl; - cout << " aligned_last: " << fixed_mid << endl; - cout << " end: " << buf_end << endl; - cout << " aligned_end: " << aligned._End << endl; - __asan_describe_address(bad_address); - - return false; + return std_testing::asan::verify_container_poisoning( + vec.data(), + vec.size() * sizeof(T), + vec.capacity() * sizeof(T), + _Container_allocation_minimum_asan_alignment> >= 8 + ); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) vec; return true; @@ -241,12 +216,35 @@ struct aligned_allocator : custom_test_allocator { return new T[n]; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p, n)); delete[] p; } }; STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 8); +template +struct extra_space_aligned_allocator : public custom_test_allocator { + static constexpr size_t _Minimum_asan_allocation_alignment = 8; + static constexpr size_t extra_space_per_side = 64 / sizeof(T); + + extra_space_aligned_allocator() = default; + template + constexpr extra_space_aligned_allocator(const extra_space_aligned_allocator&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 2 * extra_space_per_side]; + return mem + extra_space_per_side; + } + + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - extra_space_per_side, n + 2 * extra_space_per_side)); + delete[] (p - extra_space_per_side); + } +}; +STATIC_ASSERT( + _Container_allocation_minimum_asan_alignment>> == 8); + template struct explicit_allocator : custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = alignof(T); @@ -260,7 +258,8 @@ struct explicit_allocator : custom_test_allocator { return mem + 1; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1, n + 1)); delete[] (p - 1); } }; @@ -278,13 +277,38 @@ struct implicit_allocator : custom_test_allocator { return mem + 1; } - void deallocate(T* p, size_t) noexcept { + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1, n + 1)); delete[] (p - 1); } }; STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 1); STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 2); +template +struct extra_space_unaligned_allocator : public custom_test_allocator { + static constexpr size_t extra_space_per_side = 64 / sizeof(T); + + extra_space_unaligned_allocator() = default; + template + constexpr extra_space_unaligned_allocator(const extra_space_unaligned_allocator&) noexcept {} + + T* allocate(size_t n) { + T* mem = new T[n + 1 + 2*extra_space_per_side]; + return mem + extra_space_per_side + 1; + } + + void deallocate(T* p, size_t n) noexcept { + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2*extra_space_per_side)); + delete[] (p - 1 - extra_space_per_side); + } +}; +STATIC_ASSERT( + _Container_allocation_minimum_asan_alignment>> == 1); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + vector>> + == 2); + // Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`) template struct implicit_allocator_no_asan_annotations : implicit_allocator { @@ -322,6 +346,25 @@ void test_push_pop() { assert(verify_vector(v)); } +template +void test_push_pop_resize() { + using T = typename Alloc::value_type; + + // Try push/pop at various sizes to cover resize code path (gh-6276) + for (size_t i = 1; i < Size; ++i) { + vector v(Size, T()); + v.push_back(T()); + assert(verify_vector(v)); + } + + for (size_t i = 1; i < Size; ++i) { + vector v(Size, T()); + v.pop_back(); + assert(verify_vector(v)); + } + +} + template void test_reserve_shrink() { using T = typename Alloc::value_type; @@ -345,7 +388,7 @@ void test_reserve_shrink() { for (int i = 0; i < Size; i += Stride) { for (int j = 0; j < Stride && j + i < Size; ++j) { - v.pop_back(); + v.pop_back(); } v.shrink_to_fit(); @@ -1015,6 +1058,7 @@ void test_empty() { template void run_tests() { test_push_pop(); + test_push_pop_resize(); test_reserve_shrink(); test_emplace_pop(); test_move_assign(); @@ -1060,8 +1104,10 @@ template void run_allocator_matrix() { run_tests>(); run_custom_allocator_matrix(); + run_custom_allocator_matrix(); run_custom_allocator_matrix(); run_custom_allocator_matrix(); + run_custom_allocator_matrix(); // To test ASan annotation disablement, we use an ad-hoc allocator type to avoid disrupting other // tests that depend on annotations being enabled. Therefore, unlike the prior tests, @@ -1069,6 +1115,40 @@ void run_allocator_matrix() { run_asan_annotations_disablement_test(); } +void test_gh_6276() { + { + vector> unaligned{22, L'a'}; + assert(verify_vector(unaligned)); + + unaligned.push_back(L'b'); + assert(verify_vector(unaligned)); + } + + { + vector> unaligned{22, L'a'}; + assert(verify_vector(unaligned)); + + unaligned.pop_back(); + assert(verify_vector(unaligned)); + } + + { + vector> aligned{23, L'a'}; + assert(verify_vector(aligned)); + + aligned.push_back(L'a'); + assert(verify_vector(aligned)); + } + + { + vector> aligned{23, L'a'}; + assert(verify_vector(aligned)); + + aligned.pop_back(); + assert(verify_vector(aligned)); + } +} + int main(int argc, char* argv[]) { std_testing::death_test_executive exec([] { // Do some work even when we aren't instrumented @@ -1089,6 +1169,8 @@ int main(int argc, char* argv[]) { test_insert_n_throw(); #endif // ^^^ no workaround ^^^ #endif // ASan instrumentation enabled + + test_gh_6276(); }); #ifdef __SANITIZE_ADDRESS__ diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst b/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst index 87996b3e1f..af34718183 100644 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst @@ -8,10 +8,6 @@ RUNALL_INCLUDE ..\prefix.lst RUNALL_CROSSLIST PM_CL="/Zi /wd4611 /w14640 /Zc:threadSafeInit-" PM_LINK="/debug" RUNALL_CROSSLIST -PM_CL="-fsanitize=address /BE /c /EHsc /MD /std:c++14" -PM_CL="-fsanitize=address /BE /c /EHsc /MDd /std:c++17 /permissive-" -PM_CL="-fsanitize=address /BE /c /EHsc /MT /std:c++20 /permissive-" -PM_CL="-fsanitize=address /BE /c /EHsc /MTd /std:c++latest /permissive-" PM_CL="-fsanitize=address /EHsc /MD /std:c++14" PM_CL="-fsanitize=address /EHsc /MD /std:c++17" PM_CL="-fsanitize=address /EHsc /MD /std:c++20" @@ -22,10 +18,8 @@ PM_CL="-fsanitize=address /EHsc /MDd /std:c++17 /permissive-" PM_CL="-fsanitize=address /EHsc /MDd /std:c++20 /permissive-" PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive- /Zc:wchar_t-" PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive-" -PM_CL="-fsanitize=address /EHsc /MT /std:c++latest /permissive- /analyze:only /analyze:autolog-" PM_CL="-fsanitize=address /EHsc /MT /std:c++latest /permissive-" PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive" -PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive- /analyze:only /analyze:autolog-" PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive- /fp:strict" PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive-" PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /std:c++14" diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp index 4227f5104c..e36b18c1b2 100644 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp +++ b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp @@ -19,29 +19,16 @@ int main() {} #define NO_SANITIZER_ADDRESS __declspec(no_sanitize_address) #endif // __clang__ -#include +#include +#include +#include #include #include #include -using namespace std; - -extern "C" uintptr_t __asan_shadow_memory_dynamic_address; - -NO_SANITIZER_ADDRESS unsigned char* shadow_addr_of(const void* addr) { - return (unsigned char*) (((uintptr_t) addr >> 3) + __asan_shadow_memory_dynamic_address); -} - -NO_SANITIZER_ADDRESS unsigned char shadow_byte_of(const void* addr) { - return *shadow_addr_of(addr); -} +#include -NO_SANITIZER_ADDRESS void print_shadow_bytes(const void* addr, const size_t string_size) { - for (size_t i = 0; i < string_size; i += 8) { - printf("%02x ", shadow_byte_of(reinterpret_cast(addr) + i)); - } - printf("\n"); -} +using namespace std; template class asan_unaware_arena { @@ -50,17 +37,13 @@ class asan_unaware_arena { // effects from only the container poisoning. public: asan_unaware_arena() noexcept { - fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, shadow_addr_of(_alloc_buffer)); + fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, std_testing::asan::shadow_addr_of(_alloc_buffer)); } ~asan_unaware_arena() noexcept { - fprintf(stderr, "Shadow during destruction (should be all 00):\t"); - print_shadow(); - - // Shadow should be cleared. If it isn't, this memset will trigger an ASan container-overflow error. - // This will likely appear as unknown-error since partial poisoning relies on nearby - // shadow bytes to determine error type. - memset(_alloc_buffer, 0, ArenaSize); + fputs("Shadow during destruction (should be all 00):\n", stderr); + // Shadow should be cleared, otherwise the container has left scope leaving behind poisoned shadow bytes. + std_testing::asan::verify_container_poisoning(_alloc_buffer, ArenaSize, ArenaSize, AllocationAlignment >= 8); } asan_unaware_arena(const asan_unaware_arena&) = delete; @@ -78,18 +61,13 @@ class asan_unaware_arena { _next_alloc += num_bytes; _next_alloc = reinterpret_cast((reinterpret_cast(_next_alloc) + (AllocationAlignment - 1)) & ~(AllocationAlignment - 1)); // align the next allocation pointer. - fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n", result, - _next_alloc, num_bytes, shadow_addr_of(result), shadow_addr_of(_next_alloc), - shadow_addr_of(_next_alloc) - shadow_addr_of(result)); + _next_alloc, num_bytes, std_testing::asan::shadow_addr_of(result), std_testing::asan::shadow_addr_of(_next_alloc), + std_testing::asan::shadow_addr_of(_next_alloc) - std_testing::asan::shadow_addr_of(result)); return result; } - void print_shadow() const noexcept { - print_shadow_bytes(_alloc_buffer, ArenaSize); - } - private: alignas(AllocationAlignment) char _alloc_buffer[ArenaSize]{}; char* _next_alloc = _alloc_buffer; @@ -106,7 +84,7 @@ struct arena_reuse_allocator { using other = arena_reuse_allocator; }; - arena_reuse_allocator(asan_unaware_arena* a) noexcept : _arena(a) {} + arena_reuse_allocator() noexcept : _arena(std::make_shared>()) {} template arena_reuse_allocator(const arena_reuse_allocator& rhs) noexcept @@ -118,7 +96,7 @@ struct arena_reuse_allocator { void deallocate(T*, size_t) noexcept {} - asan_unaware_arena* _arena; + std::shared_ptr> _arena; }; const size_t arena_size = 256; @@ -127,32 +105,84 @@ template void test_string_poisoning() { fprintf(stderr, "\nTesting string with allocator alignment of %zu\n", Alignment); - asan_unaware_arena test_arena; - arena_reuse_allocator alloc(&test_arena); - - basic_string, decltype(alloc)> test_string(L"a 24 length string repr", alloc); - fprintf(stderr, "Shadow after string constructor:\t\t"); - test_arena.print_shadow(); + basic_string, arena_reuse_allocator> test_string(L"a 24 length string repr"); + fputs("Shadow after string constructor:\n", stderr); + // Should not see any poisoning, since allocation size == string size. + assert(std_testing::asan::verify_container_poisoning(test_string.data(), + (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator + (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator + Alignment >= 8)); test_string.append(L"o"); // add any character to trigger resize - fprintf(stderr, "Shadow after string resize:\t\t\t"); - test_arena.print_shadow(); + fputs("Shadow after string resize:\n", stderr); + // Should see poisoning, since the allocation should have resized more than +1. + assert(std_testing::asan::verify_container_poisoning(test_string.data(), + (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator + (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator + Alignment >= 8)); } template void test_vector_poisoning() { fprintf(stderr, "\nTesting vector with allocator alignment of %zu\n", Alignment); - asan_unaware_arena test_arena; - arena_reuse_allocator alloc(&test_arena); - - vector test_vector(23, L'a', alloc); - fprintf(stderr, "Shadow after vector constructor:\t\t"); - test_arena.print_shadow(); + vector> test_vector(23, L'a'); + fputs("Shadow after vector constructor:\n", stderr); + // Should not see any poisoning, since allocation size == vector size. + assert(std_testing::asan::verify_container_poisoning(test_vector.data(), + test_vector.size() * sizeof(wchar_t), + test_vector.capacity() * sizeof(wchar_t), + Alignment >= 8)); test_vector.push_back(L'o'); // trigger resize - fprintf(stderr, "Shadow after vector resize:\t\t\t"); - test_arena.print_shadow(); + fputs("Shadow after vector resize:\n", stderr); + // Should see poisoning, since allocation should have resized more than +1. + assert(std_testing::asan::verify_container_poisoning(test_vector.data(), + test_vector.size() * sizeof(wchar_t), + test_vector.capacity() * sizeof(wchar_t), + Alignment >= 8)); +} + +template +void test_string_poisoning_shrink() { + fprintf(stderr, "\nTesting string shrink with allocator alignment of %zu\n", Alignment); + + basic_string, arena_reuse_allocator> test_string(L"a 24 length string repr"); + fputs("Shadow after string constructor:\n", stderr); + // Should not see any poisoning, since allocation size == string size. + assert(std_testing::asan::verify_container_poisoning(test_string.data(), + (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator + (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator + Alignment >= 8)); + + test_string.pop_back(); // trigger size reduction + fputs("Shadow after string shrink:\n", stderr); + // Should see poisoning, since the allocation should have shrunk more than +1. + assert(std_testing::asan::verify_container_poisoning(test_string.data(), + (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator + (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator + Alignment >= 8)); +} + +template +void test_vector_poisoning_shrink() { + fprintf(stderr, "\nTesting vector shrink with allocator alignment of %zu\n", Alignment); + + vector> test_vector(16, L'a'); + fputs("Shadow after vector constructor:\n", stderr); + // Should not see any poisoning, since allocation size == vector size. + assert(std_testing::asan::verify_container_poisoning(test_vector.data(), + test_vector.size() * sizeof(wchar_t), + test_vector.capacity() * sizeof(wchar_t), + Alignment >= 8)); + + test_vector.pop_back(); // trigger size reduction + fputs("Shadow after vector shrink:\n", stderr); + // Should see poisoning, since allocation should have shrunk more than +1. + assert(std_testing::asan::verify_container_poisoning(test_vector.data(), + test_vector.size() * sizeof(wchar_t), + test_vector.capacity() * sizeof(wchar_t), + Alignment >= 8)); } int main() { @@ -170,9 +200,15 @@ int main() { test_string_poisoning<2>(); // under poisoned code path test_string_poisoning<8>(); // over poisoned code path + test_string_poisoning_shrink<2>(); // under poisoned code path + test_string_poisoning_shrink<8>(); // over poisoned code path + test_vector_poisoning<2>(); // under poisoned code path test_vector_poisoning<8>(); // over poisoned code path + test_vector_poisoning_shrink<2>(); // under poisoned code path + test_vector_poisoning_shrink<8>(); // over poisoned code path + return 0; } From c395821d6e0f05256437256cb34ebb285a103a39 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Wed, 3 Jun 2026 15:50:22 -0700 Subject: [PATCH 6/9] Remove gh6276 specific test since it's handled by other tests now. --- tests/std/test.lst | 1 - .../env.lst | 28 --- .../test.cpp | 215 ------------------ 3 files changed, 244 deletions(-) delete mode 100644 tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst delete mode 100644 tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp diff --git a/tests/std/test.lst b/tests/std/test.lst index 8c4f26db24..e034874360 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -281,7 +281,6 @@ tests\GH_005800_stable_sort_large_alignment tests\GH_005816_numeric_limits_traps tests\GH_005968_headers_provide_begin_end tests\GH_005974_asan_annotate_optional -tests\GH_006276_annotation_poison_cleanup tests\LWG2381_num_get_floating_point tests\LWG2510_tag_classes tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst b/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst deleted file mode 100644 index af34718183..0000000000 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/env.lst +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -# This test matrix is the usual test matrix, with all currently unsupported options removed, crossed with the ASan flags. - -# TRANSITION, google/sanitizers#328: clang-cl does not support /MDd or /MTd with ASan -RUNALL_INCLUDE ..\prefix.lst -RUNALL_CROSSLIST -PM_CL="/Zi /wd4611 /w14640 /Zc:threadSafeInit-" PM_LINK="/debug" -RUNALL_CROSSLIST -PM_CL="-fsanitize=address /EHsc /MD /std:c++14" -PM_CL="-fsanitize=address /EHsc /MD /std:c++17" -PM_CL="-fsanitize=address /EHsc /MD /std:c++20" -PM_CL="-fsanitize=address /EHsc /MD /std:c++latest /permissive- /Zc:char8_t- /Zc:preprocessor" -PM_CL="-fsanitize=address /EHsc /MD /std:c++latest /permissive- /Zc:noexceptTypes-" -PM_CL="-fsanitize=address /EHsc /MDd /std:c++14 /fp:except /Zc:preprocessor" -PM_CL="-fsanitize=address /EHsc /MDd /std:c++17 /permissive-" -PM_CL="-fsanitize=address /EHsc /MDd /std:c++20 /permissive-" -PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive- /Zc:wchar_t-" -PM_CL="-fsanitize=address /EHsc /MDd /std:c++latest /permissive-" -PM_CL="-fsanitize=address /EHsc /MT /std:c++latest /permissive-" -PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive" -PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive- /fp:strict" -PM_CL="-fsanitize=address /EHsc /MTd /std:c++latest /permissive-" -PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /std:c++14" -PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /std:c++17" -PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MT /std:c++20 /permissive-" -PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MT /std:c++latest /permissive- /fp:strict" diff --git a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp b/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp deleted file mode 100644 index e36b18c1b2..0000000000 --- a/tests/std/tests/GH_006276_annotation_poison_cleanup/test.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -// REQUIRES: x64 || x86 || arm64 - -#if defined(__clang__) && defined(_M_ARM64) // TRANSITION, LLVM-184902, fixed in Clang 23 -#pragma comment(linker, "/INFERASANLIBS") -int main() {} -#else // ^^^ workaround / no workaround vvv - -#pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension -#pragma warning(disable : 4324) // '%s': structure was padded due to alignment specifier -#pragma warning(disable : 4365) // '%s': conversion from '%s' to '%s', signed/unsigned mismatch - -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wc++17-extensions" // constexpr if is a C++17 extension -#define NO_SANITIZER_ADDRESS __attribute__((no_sanitize_address)) -#else // ^^^ clang / msvc vvv -#define NO_SANITIZER_ADDRESS __declspec(no_sanitize_address) -#endif // __clang__ - -#include -#include -#include -#include -#include -#include - -#include - -using namespace std; - -template -class asan_unaware_arena { - // In practice, custom memory pools should also be annotated for ASan. - // For simplicity, we aren't doing that so we can directly see the - // effects from only the container poisoning. -public: - asan_unaware_arena() noexcept { - fprintf(stderr, "Creating arena at %p with shadow at %p\n", _alloc_buffer, std_testing::asan::shadow_addr_of(_alloc_buffer)); - } - - ~asan_unaware_arena() noexcept { - fputs("Shadow during destruction (should be all 00):\n", stderr); - // Shadow should be cleared, otherwise the container has left scope leaving behind poisoned shadow bytes. - std_testing::asan::verify_container_poisoning(_alloc_buffer, ArenaSize, ArenaSize, AllocationAlignment >= 8); - } - - asan_unaware_arena(const asan_unaware_arena&) = delete; - asan_unaware_arena& operator=(const asan_unaware_arena&) = delete; - - asan_unaware_arena(asan_unaware_arena&&) = delete; - asan_unaware_arena& operator=(asan_unaware_arena&&) = delete; - - char* allocate(size_t num_bytes) { - if (_next_alloc + num_bytes > _alloc_buffer + ArenaSize) { - throw bad_alloc(); - } - - char* result = _next_alloc; - _next_alloc += num_bytes; - _next_alloc = reinterpret_cast((reinterpret_cast(_next_alloc) + (AllocationAlignment - 1)) - & ~(AllocationAlignment - 1)); // align the next allocation pointer. - fprintf(stderr, "Allocated %p -> %p (%zu bytes) from arena, %p -> %p (%zu bytes) in shadow\n", result, - _next_alloc, num_bytes, std_testing::asan::shadow_addr_of(result), std_testing::asan::shadow_addr_of(_next_alloc), - std_testing::asan::shadow_addr_of(_next_alloc) - std_testing::asan::shadow_addr_of(result)); - - return result; - } - -private: - alignas(AllocationAlignment) char _alloc_buffer[ArenaSize]{}; - char* _next_alloc = _alloc_buffer; -}; - -template -struct arena_reuse_allocator { - using value_type = T; - static constexpr size_t Size = AllocSize; - static constexpr size_t _Minimum_asan_allocation_alignment = Alignment; - - template - struct rebind { - using other = arena_reuse_allocator; - }; - - arena_reuse_allocator() noexcept : _arena(std::make_shared>()) {} - - template - arena_reuse_allocator(const arena_reuse_allocator& rhs) noexcept - : _arena(rhs._arena) {} - - T* allocate(size_t n) { - return reinterpret_cast(_arena->allocate(n * sizeof(T))); - } - - void deallocate(T*, size_t) noexcept {} - - std::shared_ptr> _arena; -}; - -const size_t arena_size = 256; - -template -void test_string_poisoning() { - fprintf(stderr, "\nTesting string with allocator alignment of %zu\n", Alignment); - - basic_string, arena_reuse_allocator> test_string(L"a 24 length string repr"); - fputs("Shadow after string constructor:\n", stderr); - // Should not see any poisoning, since allocation size == string size. - assert(std_testing::asan::verify_container_poisoning(test_string.data(), - (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator - (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator - Alignment >= 8)); - - test_string.append(L"o"); // add any character to trigger resize - fputs("Shadow after string resize:\n", stderr); - // Should see poisoning, since the allocation should have resized more than +1. - assert(std_testing::asan::verify_container_poisoning(test_string.data(), - (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator - (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator - Alignment >= 8)); -} - -template -void test_vector_poisoning() { - fprintf(stderr, "\nTesting vector with allocator alignment of %zu\n", Alignment); - - vector> test_vector(23, L'a'); - fputs("Shadow after vector constructor:\n", stderr); - // Should not see any poisoning, since allocation size == vector size. - assert(std_testing::asan::verify_container_poisoning(test_vector.data(), - test_vector.size() * sizeof(wchar_t), - test_vector.capacity() * sizeof(wchar_t), - Alignment >= 8)); - - test_vector.push_back(L'o'); // trigger resize - fputs("Shadow after vector resize:\n", stderr); - // Should see poisoning, since allocation should have resized more than +1. - assert(std_testing::asan::verify_container_poisoning(test_vector.data(), - test_vector.size() * sizeof(wchar_t), - test_vector.capacity() * sizeof(wchar_t), - Alignment >= 8)); -} - -template -void test_string_poisoning_shrink() { - fprintf(stderr, "\nTesting string shrink with allocator alignment of %zu\n", Alignment); - - basic_string, arena_reuse_allocator> test_string(L"a 24 length string repr"); - fputs("Shadow after string constructor:\n", stderr); - // Should not see any poisoning, since allocation size == string size. - assert(std_testing::asan::verify_container_poisoning(test_string.data(), - (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator - (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator - Alignment >= 8)); - - test_string.pop_back(); // trigger size reduction - fputs("Shadow after string shrink:\n", stderr); - // Should see poisoning, since the allocation should have shrunk more than +1. - assert(std_testing::asan::verify_container_poisoning(test_string.data(), - (test_string.size() + 1) * sizeof(wchar_t), // +1 for null terminator - (test_string.capacity() + 1) * sizeof(wchar_t), // +1 for null terminator - Alignment >= 8)); -} - -template -void test_vector_poisoning_shrink() { - fprintf(stderr, "\nTesting vector shrink with allocator alignment of %zu\n", Alignment); - - vector> test_vector(16, L'a'); - fputs("Shadow after vector constructor:\n", stderr); - // Should not see any poisoning, since allocation size == vector size. - assert(std_testing::asan::verify_container_poisoning(test_vector.data(), - test_vector.size() * sizeof(wchar_t), - test_vector.capacity() * sizeof(wchar_t), - Alignment >= 8)); - - test_vector.pop_back(); // trigger size reduction - fputs("Shadow after vector shrink:\n", stderr); - // Should see poisoning, since allocation should have shrunk more than +1. - assert(std_testing::asan::verify_container_poisoning(test_vector.data(), - test_vector.size() * sizeof(wchar_t), - test_vector.capacity() * sizeof(wchar_t), - Alignment >= 8)); -} - -int main() { - // Annotations for std::string and std::vector follow different - // paths based off allocation alignment, so test with both. - - // Alignment >= 8 is aligned with shadow bytes' 8-byte granularity, so string/vector - // annotations try to maximize coverage by poisoning past the allocation, since the allocator - // should not hand out that memory anyway. - - // Alignment < 8 is not aligned with shadow bytes, so string/vector annotations - // are more conservative and only poison the memory that was handed out by the - // allocator, leaving some at the end unpoisoned. - - test_string_poisoning<2>(); // under poisoned code path - test_string_poisoning<8>(); // over poisoned code path - - test_string_poisoning_shrink<2>(); // under poisoned code path - test_string_poisoning_shrink<8>(); // over poisoned code path - - test_vector_poisoning<2>(); // under poisoned code path - test_vector_poisoning<8>(); // over poisoned code path - - test_vector_poisoning_shrink<2>(); // under poisoned code path - test_vector_poisoning_shrink<8>(); // over poisoned code path - - return 0; -} - -#endif // ^^^ no workaround ^^^ From db3ced930a12c83778363e0c3125dc688cf1ceb5 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Thu, 4 Jun 2026 10:08:21 -0700 Subject: [PATCH 7/9] Remove extra spaces, reorder headers, etc. --- tests/std/tests/GH_002030_asan_annotate_string/test.cpp | 6 +----- tests/std/tests/GH_002030_asan_annotate_vector/test.cpp | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp index 3f9e617f6a..3d0ff26fef 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -1623,9 +1623,7 @@ void test_removal() { shrink_to_fit_shrinking.pop_back(); shrink_to_fit_shrinking.shrink_to_fit(); assert(verify_string(shrink_to_fit_shrinking)); - } - - + } } template @@ -2096,7 +2094,6 @@ int main(int argc, char* argv[]) { test_gh_3955(); test_gh_6276(); }); - #ifdef __SANITIZE_ADDRESS__ exec.add_death_tests({ run_asan_container_overflow_death_test, @@ -2108,7 +2105,6 @@ int main(int argc, char* argv[]) { run_asan_container_overflow_death_test, }); #endif // ASan instrumentation enabled - return exec.run(argc, argv); } diff --git a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp index a4a56dfbe1..87398f655a 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -17,8 +17,8 @@ int main() {} #include #include -#include #include +#include #pragma warning(disable : 4984) // 'if constexpr' is a C++17 language extension From 4663fc276fe312b482d210091c621d81aab293d4 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Thu, 4 Jun 2026 10:17:34 -0700 Subject: [PATCH 8/9] Apply clang-format --- stl/inc/vector | 17 ++---- stl/inc/xstring | 16 +++-- tests/std/include/test_asan_support.hpp | 59 +++++++++++-------- .../GH_002030_asan_annotate_string/test.cpp | 49 +++++++-------- .../GH_002030_asan_annotate_vector/test.cpp | 34 ++++------- 5 files changed, 87 insertions(+), 88 deletions(-) diff --git a/stl/inc/vector b/stl/inc/vector index 9a7ff27134..c616a02a10 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -516,7 +516,7 @@ private: _CONSTEXPR20 const void* _Annotation_end() const noexcept { _STL_INTERNAL_CHECK(_Mypair._Myval2._Myend != nullptr); const void* const _End = _STD _Unfancy(_Mypair._Myval2._Myend); - + if constexpr ((_Container_allocation_minimum_asan_alignment) >= _Asan_granularity) { // If the allocator alignment is less than or equal to the ASan granularity, then we can // realign the end forward to maximize the annotation coverage. @@ -576,8 +576,7 @@ private: } _CSTD __sanitizer_annotate_contiguous_container( - _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_at(_Target_size) - ); + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_at(_Target_size)); } } @@ -595,10 +594,8 @@ private: return; } - _CSTD __sanitizer_annotate_contiguous_container( - _Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), _Myvec->_Annotation_last(), - _Myvec->_Annotation_at(_Target_size) - ); + _CSTD __sanitizer_annotate_contiguous_container(_Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), + _Myvec->_Annotation_last(), _Myvec->_Annotation_at(_Target_size)); } } @@ -613,10 +610,8 @@ private: } // Shrinks the shadow memory to the current size of the vector. - _CSTD __sanitizer_annotate_contiguous_container( - _Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), _Myvec->_Annotation_at(_Target_size), - _Myvec->_Annotation_last() - ); + _CSTD __sanitizer_annotate_contiguous_container(_Myvec->_Annotation_begin(), _Myvec->_Annotation_end(), + _Myvec->_Annotation_at(_Target_size), _Myvec->_Annotation_last()); } } diff --git a/stl/inc/xstring b/stl/inc/xstring index 31ea4478d4..14e32c2b44 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -675,7 +675,8 @@ private: return false; } #endif // _HAS_CXX20 - // Only annotate if the string is large enough to be worth it, and if the user hasn't opted out via _Asan_string_should_annotate. + // Only annotate if the string is large enough to be worth it, and if the user hasn't opted out via + // _Asan_string_should_annotate. return _Mypair._Myval2._Myres > _Small_string_capacity && _Asan_string_should_annotate; } @@ -692,7 +693,7 @@ private: } _CONSTEXPR20 const void* _Annotation_end() const noexcept { - auto& _My_data = _Mypair._Myval2; + auto& _My_data = _Mypair._Myval2; const void* const _End = _My_data._Myptr() + _My_data._Myres + 1; // +1 to include null terminator in annotation constexpr bool _Large_string_always_asan_aligned = @@ -718,8 +719,7 @@ private: } _CSTD __sanitizer_annotate_contiguous_container( - _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last() - ); + _Annotation_begin(), _Annotation_end(), _Annotation_end(), _Annotation_last()); } } @@ -730,8 +730,7 @@ private: } _CSTD __sanitizer_annotate_contiguous_container( - _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end() - ); + _Annotation_begin(), _Annotation_end(), _Annotation_last(), _Annotation_end()); } } @@ -740,14 +739,13 @@ private: if (!_Should_annotate()) { return; } - + if (_Old_size == _New_size) { return; } _CSTD __sanitizer_annotate_contiguous_container( - _Annotation_begin(), _Annotation_end(), _Annotation_at(_Old_size), _Annotation_at(_New_size) - ); + _Annotation_begin(), _Annotation_end(), _Annotation_at(_Old_size), _Annotation_at(_New_size)); } } diff --git a/tests/std/include/test_asan_support.hpp b/tests/std/include/test_asan_support.hpp index 93f6e530d4..5881e57990 100644 --- a/tests/std/include/test_asan_support.hpp +++ b/tests/std/include/test_asan_support.hpp @@ -15,7 +15,8 @@ #include extern "C" uintptr_t __asan_shadow_memory_dynamic_address; -extern "C" void* __sanitizer_contiguous_container_find_bad_address(const void* beg, const void* mid, const void* end) noexcept; +extern "C" void* __sanitizer_contiguous_container_find_bad_address( + const void* beg, const void* mid, const void* end) noexcept; extern "C" void __asan_describe_address(void*) noexcept; namespace std_testing { @@ -23,42 +24,51 @@ namespace std_testing { constexpr uintptr_t shadow_granularity = 8; const char* round_down_to_shadow_granularity(const char* const addr) { - return reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); + return reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); } const char* round_up_to_shadow_granularity(const char* const addr) { - return reinterpret_cast((reinterpret_cast(addr) + shadow_granularity - 1) & ~(shadow_granularity - 1)); + return reinterpret_cast( + (reinterpret_cast(addr) + shadow_granularity - 1) & ~(shadow_granularity - 1)); } NO_SANITIZE_ADDRESS unsigned char* shadow_addr_of(const void* const addr) { - return reinterpret_cast((reinterpret_cast(addr) >> 3) + __asan_shadow_memory_dynamic_address); + return reinterpret_cast( + (reinterpret_cast(addr) >> 3) + __asan_shadow_memory_dynamic_address); } NO_SANITIZE_ADDRESS unsigned char shadow_byte_of(const void* addr) { - return *shadow_addr_of(addr); + return *shadow_addr_of(addr); } - void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, unsigned char expected_shadow_byte = 0xff /*unused shadow byte*/) { - constexpr size_t shadow_bytes_per_line = 16; + void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, + unsigned char expected_shadow_byte = 0xff /*unused shadow byte*/) { + constexpr size_t shadow_bytes_per_line = 16; constexpr uintptr_t bytes_per_line_mask = (shadow_bytes_per_line * shadow_granularity) - 1; - const char* begin = reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); // align down to shadow boundary + const char* begin = reinterpret_cast( + reinterpret_cast(addr) & ~(shadow_granularity - 1)); // align down to shadow boundary const char* end = reinterpret_cast(addr) + num_bytes; - + if (error_addr) { assert(error_addr >= begin && error_addr <= end); - fprintf(stderr, "ERROR: Expected %02x shadow byte at %p, but got %02x.\n", expected_shadow_byte, error_addr, shadow_byte_of(error_addr)); + fprintf(stderr, "ERROR: Expected %02x shadow byte at %p, but got %02x.\n", expected_shadow_byte, + error_addr, shadow_byte_of(error_addr)); } - fprintf(stderr, "Shadow for addresses %p -> %p (%p -> %p):", begin, end, shadow_addr_of(begin), shadow_addr_of(end)); - - const char* const print_begin = reinterpret_cast(reinterpret_cast(begin) - bytes_per_line_mask & ~bytes_per_line_mask); - const char* const print_end = reinterpret_cast((reinterpret_cast(end) + bytes_per_line_mask) & ~bytes_per_line_mask); + fprintf(stderr, "Shadow for addresses %p -> %p (%p -> %p):", begin, end, shadow_addr_of(begin), + shadow_addr_of(end)); + + const char* const print_begin = reinterpret_cast( + reinterpret_cast(begin) - bytes_per_line_mask & ~bytes_per_line_mask); + const char* const print_end = reinterpret_cast( + (reinterpret_cast(end) + bytes_per_line_mask) & ~bytes_per_line_mask); for (const char* p = print_begin; p < print_end; p += 8) { if ((reinterpret_cast(p) & bytes_per_line_mask) == 0) { fprintf(stderr, "\n %p: ", p); } - if (reinterpret_cast(p) == (reinterpret_cast(error_addr) & ~(shadow_granularity - 1))) { + if (reinterpret_cast(p) + == (reinterpret_cast(error_addr) & ~(shadow_granularity - 1))) { fprintf(stderr, "\b[%02x]", shadow_byte_of(p)); } else { fprintf(stderr, "%02x ", shadow_byte_of(p)); @@ -69,9 +79,9 @@ namespace std_testing { bool verify_poisoning_cleared(void* ptr, size_t num_bytes) { const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(ptr)); - const char* end = reinterpret_cast(ptr) + num_bytes; - - void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, end, end); + const char* end = reinterpret_cast(ptr) + num_bytes; + + void* bad_addr = __sanitizer_contiguous_container_find_bad_address(begin, end, end); if (bad_addr) { print_shadow_bytes(ptr, num_bytes, bad_addr, 0); __asan_describe_address(bad_addr); @@ -80,10 +90,11 @@ namespace std_testing { return true; } - bool verify_container_poisoning(const void* const addr, const size_t valid_size, const size_t total_capacity, const bool is_overpoisoned) { + bool verify_container_poisoning( + const void* const addr, const size_t valid_size, const size_t total_capacity, const bool is_overpoisoned) { const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(addr)); const char* const mid = reinterpret_cast(addr) + valid_size; - const char* end = reinterpret_cast(addr) + total_capacity; + const char* end = reinterpret_cast(addr) + total_capacity; if (is_overpoisoned) { end = round_up_to_shadow_granularity(end); @@ -94,9 +105,10 @@ namespace std_testing { const char* const b1 = round_down_to_shadow_granularity(mid); const char* const b2 = round_up_to_shadow_granularity(mid); - const unsigned char expected_byte = static_cast( - (bad_addr >= b1 && bad_addr < b2) ? static_cast(mid - b1) : (bad_addr < mid) ? 0 : 0xfc - ); + const unsigned char expected_byte = + static_cast((bad_addr >= b1 && bad_addr < b2) ? static_cast(mid - b1) + : (bad_addr < mid) ? 0 + : 0xfc); print_shadow_bytes(addr, total_capacity, bad_addr, expected_byte); __asan_describe_address(bad_addr); @@ -107,4 +119,3 @@ namespace std_testing { } // namespace asan } // namespace std_testing #endif // __SANITIZER_ADDRESS__ - diff --git a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp index 3d0ff26fef..0bd0bf37c6 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -176,7 +176,7 @@ class input_iterator_tester { }; template -bool verify_poisoning_cleared(CharType *ptr, size_t capacity) { +bool verify_poisoning_cleared(CharType* ptr, size_t capacity) { #ifdef __SANITIZE_ADDRESS__ return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv @@ -193,12 +193,9 @@ bool verify_string(const basic_string, Alloc>& s return true; } - return std_testing::asan::verify_container_poisoning( - str.data(), - (str.size() + 1) * sizeof(CharType), - (str.capacity() + 1) * sizeof(CharType), - (_Container_allocation_minimum_asan_alignment>) >= 8 - ); + return std_testing::asan::verify_container_poisoning(str.data(), (str.size() + 1) * sizeof(CharType), + (str.capacity() + 1) * sizeof(CharType), + (_Container_allocation_minimum_asan_alignment>) >= 8); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) str; return true; @@ -251,7 +248,7 @@ STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< template struct extra_space_aligned_allocator : public custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = 8; - static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); + static constexpr size_t extra_space_per_side = 64 / sizeof(CharType); extra_space_aligned_allocator() = default; template @@ -267,8 +264,9 @@ struct extra_space_aligned_allocator : public custom_test_allocator, extra_space_aligned_allocator>> == 8); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_aligned_allocator>> + == 8); STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< basic_string, extra_space_aligned_allocator>> == 8); @@ -328,17 +326,18 @@ struct extra_space_unaligned_allocator : public custom_test_allocator&) noexcept {} CharType* allocate(size_t n) { - CharType* mem = new CharType[n + 1 + 2*extra_space_per_side]; + CharType* mem = new CharType[n + 1 + 2 * extra_space_per_side]; return mem + extra_space_per_side + 1; } void deallocate(CharType* p, size_t n) noexcept { - assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2*extra_space_per_side)); + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2 * extra_space_per_side)); delete[] (p - 1 - extra_space_per_side); } }; -STATIC_ASSERT( - _Container_allocation_minimum_asan_alignment, extra_space_unaligned_allocator>> == 1); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< + basic_string, extra_space_unaligned_allocator>> + == 1); STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< basic_string, extra_space_unaligned_allocator>> == 2); @@ -415,7 +414,7 @@ void test_construction() { assert(verify_string(literal_constructed)); assert(verify_string(copy_assigned_large_to_sso)); - str copy_assigned_sso_to_large(get_large_input()); + str copy_assigned_sso_to_large(get_large_input()); copy_assigned_sso_to_large = literal_constructed_sso; assert(verify_string(literal_constructed_sso)); assert(verify_string(copy_assigned_sso_to_large)); @@ -1623,7 +1622,7 @@ void test_removal() { shrink_to_fit_shrinking.pop_back(); shrink_to_fit_shrinking.shrink_to_fit(); assert(verify_string(shrink_to_fit_shrinking)); - } + } } template @@ -2045,16 +2044,18 @@ void test_gh_3955() { void test_gh_6276() { - { - basic_string, extra_space_unaligned_allocator> unaligned{L"1234567890123456789012"}; - assert(verify_string(unaligned)); + { + basic_string, extra_space_unaligned_allocator> unaligned{ + L"1234567890123456789012"}; + assert(verify_string(unaligned)); unaligned.append(L"3"); assert(verify_string(unaligned)); } - + { - basic_string, extra_space_unaligned_allocator> unaligned{L"1234567890123456789012"}; + basic_string, extra_space_unaligned_allocator> unaligned{ + L"1234567890123456789012"}; assert(verify_string(unaligned)); unaligned.pop_back(); @@ -2062,7 +2063,8 @@ void test_gh_6276() { } { - basic_string, extra_space_aligned_allocator> aligned{L"12345678901234567890123"}; + basic_string, extra_space_aligned_allocator> aligned{ + L"12345678901234567890123"}; assert(verify_string(aligned)); aligned.append(L"4"); @@ -2070,7 +2072,8 @@ void test_gh_6276() { } { - basic_string, extra_space_aligned_allocator> aligned{L"12345678901234567890123"}; + basic_string, extra_space_aligned_allocator> aligned{ + L"12345678901234567890123"}; assert(verify_string(aligned)); aligned.pop_back(); diff --git a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp index 87398f655a..5ae8080eb0 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -159,7 +159,7 @@ class input_iterator_tester { }; template -bool verify_poisoning_cleared(CharType *ptr, size_t capacity) { +bool verify_poisoning_cleared(CharType* ptr, size_t capacity) { #ifdef __SANITIZE_ADDRESS__ return std_testing::asan::verify_poisoning_cleared(ptr, capacity * sizeof(CharType)); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv @@ -172,12 +172,8 @@ bool verify_poisoning_cleared(CharType *ptr, size_t capacity) { template bool verify_vector(vector& vec) { #ifdef __SANITIZE_ADDRESS__ - return std_testing::asan::verify_container_poisoning( - vec.data(), - vec.size() * sizeof(T), - vec.capacity() * sizeof(T), - _Container_allocation_minimum_asan_alignment> >= 8 - ); + return std_testing::asan::verify_container_poisoning(vec.data(), vec.size() * sizeof(T), vec.capacity() * sizeof(T), + _Container_allocation_minimum_asan_alignment> >= 8); #else // ^^^ ASan instrumentation enabled / ASan instrumentation disabled vvv (void) vec; return true; @@ -226,7 +222,7 @@ STATIC_ASSERT(_Container_allocation_minimum_asan_alignment struct extra_space_aligned_allocator : public custom_test_allocator { static constexpr size_t _Minimum_asan_allocation_alignment = 8; - static constexpr size_t extra_space_per_side = 64 / sizeof(T); + static constexpr size_t extra_space_per_side = 64 / sizeof(T); extra_space_aligned_allocator() = default; template @@ -242,8 +238,7 @@ struct extra_space_aligned_allocator : public custom_test_allocator>> == 8); +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 8); template struct explicit_allocator : custom_test_allocator { @@ -294,20 +289,18 @@ struct extra_space_unaligned_allocator : public custom_test_allocator&) noexcept {} T* allocate(size_t n) { - T* mem = new T[n + 1 + 2*extra_space_per_side]; + T* mem = new T[n + 1 + 2 * extra_space_per_side]; return mem + extra_space_per_side + 1; } void deallocate(T* p, size_t n) noexcept { - assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2*extra_space_per_side)); + assert(verify_poisoning_cleared(p - 1 - extra_space_per_side, n + 1 + 2 * extra_space_per_side)); delete[] (p - 1 - extra_space_per_side); } }; +STATIC_ASSERT(_Container_allocation_minimum_asan_alignment>> == 1); STATIC_ASSERT( - _Container_allocation_minimum_asan_alignment>> == 1); -STATIC_ASSERT(_Container_allocation_minimum_asan_alignment< - vector>> - == 2); + _Container_allocation_minimum_asan_alignment>> == 2); // Simple allocator that opts out of ASan annotations (via `_Disable_ASan_container_annotations_for_allocator`) template @@ -362,7 +355,6 @@ void test_push_pop_resize() { v.pop_back(); assert(verify_vector(v)); } - } template @@ -388,7 +380,7 @@ void test_reserve_shrink() { for (int i = 0; i < Size; i += Stride) { for (int j = 0; j < Stride && j + i < Size; ++j) { - v.pop_back(); + v.pop_back(); } v.shrink_to_fit(); @@ -1116,14 +1108,14 @@ void run_allocator_matrix() { } void test_gh_6276() { - { + { vector> unaligned{22, L'a'}; - assert(verify_vector(unaligned)); + assert(verify_vector(unaligned)); unaligned.push_back(L'b'); assert(verify_vector(unaligned)); } - + { vector> unaligned{22, L'a'}; assert(verify_vector(unaligned)); From d5bd1f8c92f878b95836cc60242b30b33dfe31d8 Mon Sep 17 00:00:00 2001 From: Amy Wishnousky Date: Tue, 9 Jun 2026 12:43:26 -0700 Subject: [PATCH 9/9] Remove unused re-alignment code, rewrite comments describing how to use __sanitizer_annotate_contiguous_container and its alignment requirements/behavior in those cases, as well as an example of the over-poisoning technique we use. And update tests - remove extra comments, add headers, fix test behavior caught by copilot. --- stl/inc/xmemory | 134 ++++++++++-------- tests/std/include/test_asan_support.hpp | 14 +- .../GH_002030_asan_annotate_string/test.cpp | 4 +- .../GH_002030_asan_annotate_vector/test.cpp | 4 +- 4 files changed, 90 insertions(+), 66 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 1640e5dc61..9971b685c5 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -813,56 +813,29 @@ _INLINE_VAR constexpr size_t _Asan_granularity_mask = _Asan_granularity - 1; template constexpr bool _Disable_ASan_container_annotations_for_allocator = false; -struct _Asan_aligned_pointers { - const void* _First; - const void* _End; - - _NODISCARD constexpr const void* _Clamp_to_end(const void* _Mid) const noexcept { - _STL_INTERNAL_CHECK(_Mid >= _First); - if (_Mid > _End) { - return _End; - } else { - return _Mid; - } - } -}; - // The way that ASan shadow memory works, each eight byte block of memory ("shadow memory section") // has a single byte to mark it as either poison or valid. // Each section has 0 to 8 "valid" bytes followed by poison bytes, so: // ``` -// [ v v v p p p p p ] +// [ v v v p p p p p ] (encoded as 03 for 'first 3 bytes are valid') // ``` // or // ``` -// [ v v v v v v v v ] +// [ v v v v v v v v ] (encoded as 00 for 'all bytes valid') // ``` // are okay, but // ``` // [ p p p p v v v v ] // ``` -// is not. -// -// This function exists to fix up `first` and `end` pointers so that one can call -// `__sanitizer_annotate_contiguous_container`: -// -// - `__sanitizer_annotate_contiguous_container` checks that `first` is aligned to an 8-byte boundary -// - if `end` is not aligned to an 8-byte boundary, `__sanitizer_annotate_contiguous_container` still poisons the -// remaining bytes in the shadow memory section. -// -// Because of the second property, we can only mark poison up to the final aligned address before the true `last`. -// Otherwise, we'd poison the memory _after_ `last` as well. -// For the first property, we can assume that everything before `first` in the shadow memory section is valid -// (since otherwise we couldn't mark `first` valid), and so we just return back the first address in -// `first`'s shadow memory section. +// is not. There are no semantics for describing the first X bytes as being poisoned. // // ### Example // // ```cpp // struct alignas(8) cat { -// int meow; // bytes [0, 4) +// int meow; // bytes [0, 4) // char buffer[16]; // bytes [4, 20) -// int purr; // bytes [20, 24) +// int purr; // bytes [20, 24) // }; // ``` // @@ -883,41 +856,90 @@ struct _Asan_aligned_pointers { // | meow | buffer | purr | // [ v v v v p p p p ][ p p p p p p p p ][ v v v v v v v v ] // sm1 sm2 sm3 +// Shadow: 04 fc 00 // ``` // -// We call `aligned = _Get_asan_aligned_first_end(cat.buffer, cat.buffer + 16);`, and we get back +// For this, we can call `__sanitizer_annotate_contiguous_container`. +// Since container poisoning is a set of valid bytes followed by a set of poisoned bytes, we describe this fully to ASan +// by passing a pointer to the first valid byte, the middle pointer (pointing to the first poisoned byte), and the end +// pointer. For the unaligned beginning pointer, ASan will handle the unalignment assuming the bytes to the left are +// valid. For the unaligned end pointer, since there are no 'first X bytes are poisoned' semantics, ASan will just mark +// the entire shadow memory section as valid. +// +// To generate the above shadow memory state, we would call like this: // // ```cpp -// aligned = { -// ._First = &cat.meow, -// ._End = cat.buffer + 12, -// }; +// __sanitizer_annotate_contiguous_container( +// cat.buffer, // First +// cat.buffer + 16 // End +// cat.buffer + 16, // Old middle pointer +// cat.buffer); // New middle pointer // ``` // -// Then, we poison as much of buffer as we can via +// Correctly describing the old middle pointer is important since ASan will not check state before the existing shadow +// memory state performing the transformation. +// +// In cases where unalignment must be supported, we can miss coverage due to ASan's 8-byte alignment requirements. +// However, most allocators on Windows will only return 8-byte aligned pointers. This gives us an opportunity to provide +// extra coverage, because if everything is 8-byte aligned, we can extend, instead of reduce, the concept of the end of +// the buffer to the next 8-byte aligned boundary. _Get_asan_aligned_after is a helper function provided for this. +// +// ### Example // +// Let's say we took the above struct, aligned the members, and shortened the buffer to 12, and left our annotation code +// the same. // ```cpp +// struct alignas(8) cat { +// int alignas(8) meow; // bytes [0, 4) +// char alignas(8) buffer[12]; // bytes [8, 20) +// int alignas(8) purr; // bytes [24, 28) +// }; +// +// ... +// // __sanitizer_annotate_contiguous_container( -// aligned._First, -// aligned._End, -// cat.buffer, -// aligned._Clamp_to_end(cat.buffer + 16)); +// cat.buffer, // First +// cat.buffer + 12 // End +// cat.buffer + 12, // Old middle pointer +// cat.buffer // New middle pointer // ``` // -// We are allowed to assume that `&cat.meow` is valid, since otherwise `cat.buffer + [0, 4)` could not be valid. -// We cannot poison up to `cat.buffer + 16`, since then `&purr` could not be valid. -// Thus, this results in the shadow memory state from the second example. -_NODISCARD inline _Asan_aligned_pointers _Get_asan_aligned_first_end( - const void* const _First, const void* const _End) noexcept { - return { - reinterpret_cast(reinterpret_cast(_First) & ~_Asan_granularity_mask), - reinterpret_cast(reinterpret_cast(_End) & ~_Asan_granularity_mask), - }; -} - -// When we can assume that the allocator we are using will always align allocations to the 8-byte, -// we can simply push the `_End` pointer to the end of the shadow memory section. -// This is _not_ safe in general (see _Get_asan_aligned_first_end's comment for why). +// Under this, the shadow buffer would look like this: +// ``` +// | meow | pad | buffer | pad | purr | pad | +// [ v v v v v v v v ][ p p p p p p p p ][ v v v v v v v v ][ v v v v v v v v ] +// sm1 sm2 sm3 sm4 +// Shadow: 00 fc 00 00 +// ``` +// +// Currently, there is no coverage on the last 4 bytes of the buffer due to the previous rules, +// but when everything is 8-byte aligned, we can use the fact there is padding to extend the poisoning. +// +// ``` +// char *end = _Get_asan_aligned_after(cat.buffer + 12); +// __sanitizer_annotate_contiguous_container( +// cat.buffer, // First +// end, // End +// end, // Old middle pointer +// cat.buffer); // New middle pointer +// ``` +// +// With this adjustment, the shadow has full coverage of the buffer, and we are not poisoning any memory that will be +// accessed, just padding. +// ``` +// | meow | pad | buffer | pad | purr | pad | +// [ v v v v v v v v ][ p p p p p p p p ][ p p p p p p p p ][ v v v v v v v v ] +// sm1 sm2 sm3 sm4 +// Shadow: 00 fc fc 00 +// ``` +// +// This example is conceptually the same for why we can apply this technique in general to any allocator guaranteed to +// only return 8-byte aligned pointers. +// +// Note that caution is warranted with this technique, particularly when differentiating between clearing the +// annotation (e.g. during destruction when we want everything valid) and setting the annotation to be fully valid +// (which would leave poisoning bytes in the padding). +// _NODISCARD inline const void* _Get_asan_aligned_after(const void* const _End) noexcept { return reinterpret_cast( (reinterpret_cast(_End) + _Asan_granularity_mask) & ~_Asan_granularity_mask); diff --git a/tests/std/include/test_asan_support.hpp b/tests/std/include/test_asan_support.hpp index 5881e57990..b71d20c1d9 100644 --- a/tests/std/include/test_asan_support.hpp +++ b/tests/std/include/test_asan_support.hpp @@ -12,6 +12,8 @@ #ifdef __SANITIZE_ADDRESS__ #include +#include +#include #include extern "C" uintptr_t __asan_shadow_memory_dynamic_address; @@ -23,25 +25,25 @@ namespace std_testing { namespace asan { constexpr uintptr_t shadow_granularity = 8; - const char* round_down_to_shadow_granularity(const char* const addr) { + inline const char* round_down_to_shadow_granularity(const char* const addr) { return reinterpret_cast(reinterpret_cast(addr) & ~(shadow_granularity - 1)); } - const char* round_up_to_shadow_granularity(const char* const addr) { + inline const char* round_up_to_shadow_granularity(const char* const addr) { return reinterpret_cast( (reinterpret_cast(addr) + shadow_granularity - 1) & ~(shadow_granularity - 1)); } - NO_SANITIZE_ADDRESS unsigned char* shadow_addr_of(const void* const addr) { + NO_SANITIZE_ADDRESS inline unsigned char* shadow_addr_of(const void* const addr) { return reinterpret_cast( (reinterpret_cast(addr) >> 3) + __asan_shadow_memory_dynamic_address); } - NO_SANITIZE_ADDRESS unsigned char shadow_byte_of(const void* addr) { + NO_SANITIZE_ADDRESS inline unsigned char shadow_byte_of(const void* addr) { return *shadow_addr_of(addr); } - void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, + inline void print_shadow_bytes(const void* addr, size_t num_bytes, const void* error_addr = nullptr, unsigned char expected_shadow_byte = 0xff /*unused shadow byte*/) { constexpr size_t shadow_bytes_per_line = 16; constexpr uintptr_t bytes_per_line_mask = (shadow_bytes_per_line * shadow_granularity) - 1; @@ -77,7 +79,7 @@ namespace std_testing { fprintf(stderr, "\n"); } - bool verify_poisoning_cleared(void* ptr, size_t num_bytes) { + inline bool verify_poisoning_cleared(void* ptr, size_t num_bytes) { const char* const begin = round_down_to_shadow_granularity(reinterpret_cast(ptr)); const char* end = reinterpret_cast(ptr) + num_bytes; diff --git a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp index 0bd0bf37c6..617ea23d59 100644 --- a/tests/std/tests/GH_002030_asan_annotate_string/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_string/test.cpp @@ -419,7 +419,7 @@ void test_construction() { assert(verify_string(literal_constructed_sso)); assert(verify_string(copy_assigned_sso_to_large)); - str copy_assigned_large_to_large(get_large_input()); // creating allocator 28 with arena 8 + str copy_assigned_large_to_large(get_large_input()); copy_assigned_large_to_large = literal_constructed; assert(verify_string(literal_constructed)); assert(verify_string(copy_assigned_large_to_large)); @@ -439,7 +439,7 @@ void test_construction() { assert(verify_string(copy_assigned_sso_to_large)); assert(verify_string(move_assigned_sso_to_large)); - str move_assigned_large_to_large(get_large_input()); // creating allocator 42 with arena 12 + str move_assigned_large_to_large(get_large_input()); move_assigned_large_to_large = move(copy_assigned_large_to_large); assert(verify_string(copy_assigned_large_to_large)); assert(verify_string(move_assigned_large_to_large)); diff --git a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp index 5ae8080eb0..675964a7c5 100644 --- a/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp +++ b/tests/std/tests/GH_002030_asan_annotate_vector/test.cpp @@ -345,13 +345,13 @@ void test_push_pop_resize() { // Try push/pop at various sizes to cover resize code path (gh-6276) for (size_t i = 1; i < Size; ++i) { - vector v(Size, T()); + vector v(i, T()); v.push_back(T()); assert(verify_vector(v)); } for (size_t i = 1; i < Size; ++i) { - vector v(Size, T()); + vector v(i, T()); v.pop_back(); assert(verify_vector(v)); }