From 3be5fb29b97df7a9f1699899952693c9493a5ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Wed, 14 Jan 2026 02:41:05 +0300 Subject: [PATCH 01/14] add asan annotation to optional --- .../__msvc_sanitizer_annotate_container.hpp | 35 ++++++++++++++ stl/inc/optional | 30 +++++++++++- tests/std/test.lst | 1 + .../std/tests/GH_005974_optional_asan/env.lst | 4 ++ .../tests/GH_005974_optional_asan/test.cpp | 48 +++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 tests/std/tests/GH_005974_optional_asan/env.lst create mode 100644 tests/std/tests/GH_005974_optional_asan/test.cpp diff --git a/stl/inc/__msvc_sanitizer_annotate_container.hpp b/stl/inc/__msvc_sanitizer_annotate_container.hpp index 057d68e7c0f..887d5d7f941 100644 --- a/stl/inc/__msvc_sanitizer_annotate_container.hpp +++ b/stl/inc/__msvc_sanitizer_annotate_container.hpp @@ -20,11 +20,13 @@ _STL_DISABLE_CLANG_WARNINGS // (this will be auto-defined on unsupported platforms) // + _DISABLE_STRING_ANNOTATION: same, but for only `basic_string` // + _DISABLE_VECTOR_ANNOTATION: same, but for only `vector` +// + _DISABLE_OPTIONAL_ANNOTATION: same, but for only `optional` // - _ENABLE_STL_ANNOTATION_ON_UNSUPPORTED_PLATFORMS: Don't auto-disable ASan annotations // - _ANNOTATE_STL: Even when ASan annotations are disabled, insert the code for annotating into the STL anyways; // this is useful when building static libraries which may be linked against both ASan and non-ASan binaries. // + _ANNOTATE_STRING: same, but only for `basic_string` // + _ANNOTATE_VECTOR: same, but only for `vector` +// + _ANNOTATE_OPTIONAL: same, but only for `optional` #if !defined(_DISABLE_STL_ANNOTATION) && !defined(_ENABLE_STL_ANNOTATION_ON_UNSUPPORTED_PLATFORMS) @@ -46,6 +48,9 @@ _STL_DISABLE_CLANG_WARNINGS #ifndef _DISABLE_VECTOR_ANNOTATION #define _DISABLE_VECTOR_ANNOTATION #endif // ^^^ !defined(_DISABLE_VECTOR_ANNOTATION) ^^^ +#ifndef _DISABLE_OPTIONAL_ANNOTATION +#define _DISABLE_OPTIONAL_ANNOTATION +#endif // ^^^ !defined(_DISABLE_OPTIONAL_ANNOTATION) ^^^ #endif // ^^^ defined(_DISABLE_STL_ANNOTATION) ^^^ @@ -59,6 +64,10 @@ _STL_DISABLE_CLANG_WARNINGS #define _ANNOTATE_VECTOR #endif // ^^^ !defined(_ANNOTATE_VECTOR) ^^^ +#ifndef _ANNOTATE_OPTIONAL +#define _ANNOTATE_OPTIONAL +#endif // ^^^ !defined(_ANNOTATE_OPTIONAL) ^^^ + #endif // ^^^ defined(_ANNOTATE_STL) ^^^ #ifdef __SANITIZE_ADDRESS__ @@ -67,6 +76,8 @@ _STL_DISABLE_CLANG_WARNINGS #define _INSERT_STRING_ANNOTATION #define _ACTIVATE_VECTOR_ANNOTATION #define _INSERT_VECTOR_ANNOTATION +#define _ACTIVATE_OPTIONAL_ANNOTATION +#define _INSERT_OPTIONAL_ANNOTATION #elif defined(__clang__) // ^^^ defined(__SANITIZE_ADDRESS__) / defined(__clang__) vvv @@ -75,6 +86,8 @@ _STL_DISABLE_CLANG_WARNINGS #define _INSERT_STRING_ANNOTATION #define _ACTIVATE_VECTOR_ANNOTATION #define _INSERT_VECTOR_ANNOTATION +#define _ACTIVATE_OPTIONAL_ANNOTATION +#define _INSERT_OPTIONAL_ANNOTATION #pragma comment(linker, "/INFERASANLIBS") #endif // __has_feature(address_sanitizer) @@ -89,6 +102,10 @@ _STL_DISABLE_CLANG_WARNINGS #undef _ACTIVATE_VECTOR_ANNOTATION #undef _INSERT_VECTOR_ANNOTATION #endif // ^^^ defined(_DISABLE_VECTOR_ANNOTATION) ^^^ +#ifdef _DISABLE_OPTIONAL_ANNOTATION +#undef _ACTIVATE_OPTIONAL_ANNOTATION +#undef _INSERT_OPTIONAL_ANNOTATION +#endif // ^^^ defined(_DISABLE_OPTIONAL_ANNOTATION) ^^^ #ifdef _ANNOTATE_STRING #define _INSERT_STRING_ANNOTATION @@ -96,6 +113,9 @@ _STL_DISABLE_CLANG_WARNINGS #ifdef _ANNOTATE_VECTOR #define _INSERT_VECTOR_ANNOTATION #endif // ^^^ defined(_ANNOTATE_VECTOR) ^^^ +#ifdef _ANNOTATE_OPTIONAL +#define _INSERT_OPTIONAL_ANNOTATION +#endif // ^^^ defined(_ANNOTATE_OPTIONAL) ^^^ #ifndef _INSERT_STRING_ANNOTATION @@ -104,6 +124,9 @@ _STL_DISABLE_CLANG_WARNINGS #ifndef _INSERT_VECTOR_ANNOTATION #pragma detect_mismatch("annotate_vector", "0") #endif // ^^^ !defined(_INSERT_VECTOR_ANNOTATION) ^^^ +#ifndef _INSERT_OPTIONAL_ANNOTATION +#pragma detect_mismatch("annotate_optional", "0") +#endif // ^^^ !defined(_INSERT_OPTIONAL_ANNOTATION) ^^^ #ifdef _ACTIVATE_STRING_ANNOTATION #pragma comment(lib, "stl_asan") @@ -113,9 +136,14 @@ _STL_DISABLE_CLANG_WARNINGS #pragma comment(lib, "stl_asan") #pragma detect_mismatch("annotate_vector", "1") #endif // ^^^ defined(_ACTIVATE_VECTOR_ANNOTATION) ^^^ +#ifdef _ACTIVATE_OPTIONAL_ANNOTATION +#pragma comment(lib, "stl_asan") +#pragma detect_mismatch("annotate_optional", "1") +#endif // ^^^ defined(_ACTIVATE_OPTIONAL_ANNOTATION) ^^^ #undef _ACTIVATE_STRING_ANNOTATION #undef _ACTIVATE_VECTOR_ANNOTATION +#undef _ACTIVATE_OPTIONAL_ANNOTATION extern "C" { #ifdef _INSERT_VECTOR_ANNOTATION @@ -125,10 +153,17 @@ extern const bool _Asan_vector_should_annotate; #ifdef _INSERT_STRING_ANNOTATION extern const bool _Asan_string_should_annotate; #endif + +#ifdef _INSERT_OPTIONAL_ANNOTATION +extern const bool _Asan_optional_should_annotate; +#endif } // extern "C" #if defined(_INSERT_VECTOR_ANNOTATION) || defined(_INSERT_STRING_ANNOTATION) extern "C" { +void __cdecl __asan_poison_memory_region(void const volatile* addr, size_t size); +void __cdecl __asan_unpoison_memory_region(void const volatile* addr, size_t size); + // This must match ASan's primary declaration, which isn't marked `noexcept`. void __cdecl __sanitizer_annotate_contiguous_container( const void* _First, const void* _End, const void* _Old_last, const void* _New_last); diff --git a/stl/inc/optional b/stl/inc/optional index 8454922206e..d312fbdd8ce 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -13,6 +13,7 @@ _EMIT_STL_WARNING(STL4038, "The contents of are available only with C #if _HAS_CXX20 #include #endif // _HAS_CXX20 +#include <__msvc_sanitizer_annotate_container.hpp> #include #include #include @@ -74,7 +75,11 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( }; bool _Has_value; - constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} {} // initialize an empty optional + constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); +#endif + } // initialize an empty optional template constexpr explicit _Optional_destruct_base(in_place_t, _Types&&... _Args) @@ -93,6 +98,9 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( _CONSTEXPR20 void reset() noexcept { _Has_value = false; +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); +#endif } }; @@ -108,6 +116,10 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o if (_Has_value) { _Value.~_Ty(); +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); +#endif + #if _MSVC_STL_DESTRUCTOR_TOMBSTONES // For the non-trivially destructible case, we can set the optional to be empty. // We don't attempt to scribble over the bytes of the object's storage because that could be expensive @@ -117,7 +129,11 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o } } - constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} {} // initialize an empty optional + constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); +#endif + } // initialize an empty optional template constexpr explicit _Optional_destruct_base(in_place_t, _Types&&... _Args) @@ -139,6 +155,11 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _CONSTEXPR20 void reset() noexcept { if (_Has_value) { _Value.~_Ty(); + +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); +#endif + _Has_value = false; } } @@ -153,6 +174,11 @@ struct _Optional_construct_base : _Optional_destruct_base<_Ty> { _CONSTEXPR20 _Ty& _Construct(_Types&&... _Args) noexcept(is_nothrow_constructible_v<_Ty, _Types...>) { // transition from the empty to the value-containing state _STL_INTERNAL_CHECK(!this->_Has_value); + +#ifdef _INSERT_OPTIONAL_ANNOTATION + __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(this->_Value)); +#endif + _STD _Construct_in_place(this->_Value, _STD forward<_Types>(_Args)...); this->_Has_value = true; return this->_Value; diff --git a/tests/std/test.lst b/tests/std/test.lst index 5bb68adcb1d..80f40cb5822 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -278,6 +278,7 @@ tests\GH_005553_regex_character_translation tests\GH_005768_pow_accuracy tests\GH_005800_stable_sort_large_alignment tests\GH_005968_headers_provide_begin_end +tests\GH_005974_optional_asan tests\LWG2381_num_get_floating_point tests\LWG2510_tag_classes tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_005974_optional_asan/env.lst b/tests/std/tests/GH_005974_optional_asan/env.lst new file mode 100644 index 00000000000..2de7aab2959 --- /dev/null +++ b/tests/std/tests/GH_005974_optional_asan/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_17_matrix.lst diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp new file mode 100644 index 00000000000..c4150005dd5 --- /dev/null +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include + +struct LargePayload { + int data[4]; +}; + +void test_activation_unpoisoning() { + std::optional opt; + opt.emplace(LargePayload{1, 2, 3, 4}); + assert(opt->data[0] == 1); +} + +void test_assignment_unpoisoning() { + std::optional opt; + LargePayload val{5, 6, 7, 8}; + opt = val; + assert(opt->data[0] == 5); +} + +void test_poison_on_empty_access() { + std::optional opt; + auto* p = reinterpret_cast(&opt); + volatile int crash_val = p->data[0]; + (void) crash_val; +} + +void test_repoison_after_reset() { + std::optional opt; + opt.emplace(LargePayload{1, 1, 1, 1}); + opt.reset(); + + auto* p = reinterpret_cast(&opt); + volatile int crash_val = p->data[0]; + (void) crash_val; +} + +int main() { + test_activation_unpoisoning(); + test_assignment_unpoisoning(); + test_poison_on_empty_access(); + test_repoison_after_reset(); + + return 0; +} From 37bcf487cbd1fed1f33b544a25ce92b458461c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Wed, 14 Jan 2026 03:06:45 +0300 Subject: [PATCH 02/14] fix poison tests --- tests/std/tests/GH_005974_optional_asan/test.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp index c4150005dd5..fe6d3ca665a 100644 --- a/tests/std/tests/GH_005974_optional_asan/test.cpp +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -23,9 +23,8 @@ void test_assignment_unpoisoning() { void test_poison_on_empty_access() { std::optional opt; - auto* p = reinterpret_cast(&opt); - volatile int crash_val = p->data[0]; - (void) crash_val; + volatile int x = opt->data[0]; + (void) x; } void test_repoison_after_reset() { @@ -33,9 +32,8 @@ void test_repoison_after_reset() { opt.emplace(LargePayload{1, 1, 1, 1}); opt.reset(); - auto* p = reinterpret_cast(&opt); - volatile int crash_val = p->data[0]; - (void) crash_val; + volatile int x = opt->data[0]; + (void) x; } int main() { From c9f424c560561fd1d0150fc528d31df9cf938194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Wed, 14 Jan 2026 15:15:25 +0300 Subject: [PATCH 03/14] use __asan_address_is_poisoned in tests --- .../tests/GH_005974_optional_asan/test.cpp | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp index fe6d3ca665a..ed6dba5f08b 100644 --- a/tests/std/tests/GH_005974_optional_asan/test.cpp +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -1,45 +1,55 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include <__msvc_sanitizer_annotate_container.hpp> #include #include -struct LargePayload { - int data[4]; +#ifdef __SANITIZE_ADDRESS__ +extern "C" int __cdecl __asan_address_is_poisoned(void const volatile* addr); +#define ASAN_VERIFY_POISONED(addr) assert(__asan_address_is_poisoned((addr)) != 0) +#define ASAN_VERIFY_UNPOISONED(addr) assert(__asan_address_is_poisoned((addr)) == 0) +#else +#define ASAN_VERIFY_POISONED(addr) +#define ASAN_VERIFY_UNPOISONED(addr) +#endif + +struct Payload { + long long x; + long long y; + long long z; + long long w; }; -void test_activation_unpoisoning() { - std::optional opt; - opt.emplace(LargePayload{1, 2, 3, 4}); - assert(opt->data[0] == 1); +void test_poison_on_empty_access() { + std::optional opt; + ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); } -void test_assignment_unpoisoning() { - std::optional opt; - LargePayload val{5, 6, 7, 8}; - opt = val; - assert(opt->data[0] == 5); +void test_emplace_unpoisoning() { + std::optional opt; + opt.emplace(Payload{1, 2, 3, 4}); + ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } -void test_poison_on_empty_access() { - std::optional opt; - volatile int x = opt->data[0]; - (void) x; +void test_assignment_unpoisoning() { + std::optional opt; + Payload val{1, 2, 3, 4}; + opt = val; + ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } void test_repoison_after_reset() { - std::optional opt; - opt.emplace(LargePayload{1, 1, 1, 1}); + std::optional opt = Payload{1, 2, 3, 4}; + ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); opt.reset(); - - volatile int x = opt->data[0]; - (void) x; + ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); } int main() { - test_activation_unpoisoning(); - test_assignment_unpoisoning(); test_poison_on_empty_access(); + test_emplace_unpoisoning(); + test_assignment_unpoisoning(); test_repoison_after_reset(); return 0; From b4fd22b333004fedeee604dba0e7d6163d226076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Wed, 14 Jan 2026 16:08:48 +0300 Subject: [PATCH 04/14] fix unused var error in tests --- tests/std/tests/GH_005974_optional_asan/test.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp index ed6dba5f08b..7403dc6c7f0 100644 --- a/tests/std/tests/GH_005974_optional_asan/test.cpp +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -10,8 +10,8 @@ extern "C" int __cdecl __asan_address_is_poisoned(void const volatile* addr); #define ASAN_VERIFY_POISONED(addr) assert(__asan_address_is_poisoned((addr)) != 0) #define ASAN_VERIFY_UNPOISONED(addr) assert(__asan_address_is_poisoned((addr)) == 0) #else -#define ASAN_VERIFY_POISONED(addr) -#define ASAN_VERIFY_UNPOISONED(addr) +#define ASAN_VERIFY_POISONED(addr) ((void) (addr)) +#define ASAN_VERIFY_UNPOISONED(addr) ((void) (addr)) #endif struct Payload { @@ -22,25 +22,24 @@ struct Payload { }; void test_poison_on_empty_access() { - std::optional opt; + [[maybe_unused]] std::optional opt; ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); } void test_emplace_unpoisoning() { std::optional opt; - opt.emplace(Payload{1, 2, 3, 4}); + opt.emplace(Payload()); ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } void test_assignment_unpoisoning() { - std::optional opt; - Payload val{1, 2, 3, 4}; - opt = val; + std::optional opt = std::nullopt; + opt = Payload(); ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } void test_repoison_after_reset() { - std::optional opt = Payload{1, 2, 3, 4}; + std::optional opt = Payload(); ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); opt.reset(); ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); From 93879fe78ab5e5316b4b26fa884ee8b9242e9321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Thu, 15 Jan 2026 20:18:07 +0300 Subject: [PATCH 05/14] add _is_constant_evaluated and should_annotate checks before annotations --- .../__msvc_sanitizer_annotate_container.hpp | 8 ++++- stl/inc/optional | 32 +++++++++++++------ stl/src/asan.cpp | 5 +-- stl/src/asan_noop.cpp | 5 +-- .../tests/GH_005974_optional_asan/test.cpp | 7 ++-- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/stl/inc/__msvc_sanitizer_annotate_container.hpp b/stl/inc/__msvc_sanitizer_annotate_container.hpp index 887d5d7f941..9b03edf07a2 100644 --- a/stl/inc/__msvc_sanitizer_annotate_container.hpp +++ b/stl/inc/__msvc_sanitizer_annotate_container.hpp @@ -159,7 +159,7 @@ extern const bool _Asan_optional_should_annotate; #endif } // extern "C" -#if defined(_INSERT_VECTOR_ANNOTATION) || defined(_INSERT_STRING_ANNOTATION) +#if defined(_INSERT_VECTOR_ANNOTATION) || defined(_INSERT_STRING_ANNOTATION) || defined(_INSERT_OPTIONAL_ANNOTATION) extern "C" { void __cdecl __asan_poison_memory_region(void const volatile* addr, size_t size); void __cdecl __asan_unpoison_memory_region(void const volatile* addr, size_t size); @@ -178,6 +178,8 @@ void __cdecl __sanitizer_annotate_contiguous_container( #pragma comment(linker, "/alternatename:_Asan_vector_should_annotate=_Asan_vector_should_annotate_default") #pragma comment(linker, "/alternatename:#_Asan_string_should_annotate=#_Asan_string_should_annotate_default") #pragma comment(linker, "/alternatename:_Asan_string_should_annotate=_Asan_string_should_annotate_default") +#pragma comment(linker, "/alternatename:#_Asan_optional_should_annotate=#_Asan_optional_should_annotate_default") +#pragma comment(linker, "/alternatename:_Asan_optional_should_annotate=_Asan_optional_should_annotate_default") #elif defined(_M_HYBRID) #pragma comment(linker, \ "/alternatename:#__sanitizer_annotate_contiguous_container=#__sanitizer_annotate_contiguous_container_default") @@ -187,16 +189,20 @@ void __cdecl __sanitizer_annotate_contiguous_container( #pragma comment(linker, "/alternatename:__Asan_vector_should_annotate=__Asan_vector_should_annotate_default") #pragma comment(linker, "/alternatename:#_Asan_string_should_annotate=#_Asan_string_should_annotate_default") #pragma comment(linker, "/alternatename:__Asan_string_should_annotate=__Asan_string_should_annotate_default") +#pragma comment(linker, "/alternatename:#_Asan_optional_should_annotate=#_Asan_optional_should_annotate_default") +#pragma comment(linker, "/alternatename:__Asan_optional_should_annotate=__Asan_optional_should_annotate_default") #elif defined(_M_IX86) #pragma comment(linker, \ "/alternatename:___sanitizer_annotate_contiguous_container=___sanitizer_annotate_contiguous_container_default") #pragma comment(linker, "/alternatename:__Asan_vector_should_annotate=__Asan_vector_should_annotate_default") #pragma comment(linker, "/alternatename:__Asan_string_should_annotate=__Asan_string_should_annotate_default") +#pragma comment(linker, "/alternatename:__Asan_optional_should_annotate=__Asan_optional_should_annotate_default") #elif defined(_M_X64) || defined(_M_ARM64) #pragma comment(linker, \ "/alternatename:__sanitizer_annotate_contiguous_container=__sanitizer_annotate_contiguous_container_default") #pragma comment(linker, "/alternatename:_Asan_vector_should_annotate=_Asan_vector_should_annotate_default") #pragma comment(linker, "/alternatename:_Asan_string_should_annotate=_Asan_string_should_annotate_default") +#pragma comment(linker, "/alternatename:_Asan_optional_should_annotate=_Asan_optional_should_annotate_default") #else // ^^^ known architecture / unknown architecture vvv #error Unknown architecture #endif // ^^^ unknown architecture ^^^ diff --git a/stl/inc/optional b/stl/inc/optional index d312fbdd8ce..6c742c26e4c 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -75,11 +75,13 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( }; bool _Has_value; - constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { + constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } #endif - } // initialize an empty optional + } template constexpr explicit _Optional_destruct_base(in_place_t, _Types&&... _Args) @@ -99,7 +101,9 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( _CONSTEXPR20 void reset() noexcept { _Has_value = false; #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } #endif } }; @@ -117,7 +121,9 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _Value.~_Ty(); #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } #endif #if _MSVC_STL_DESTRUCTOR_TOMBSTONES @@ -129,11 +135,13 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o } } - constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { + constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } #endif - } // initialize an empty optional + } template constexpr explicit _Optional_destruct_base(in_place_t, _Types&&... _Args) @@ -157,7 +165,9 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _Value.~_Ty(); #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } #endif _Has_value = false; @@ -176,7 +186,9 @@ struct _Optional_construct_base : _Optional_destruct_base<_Ty> { _STL_INTERNAL_CHECK(!this->_Has_value); #ifdef _INSERT_OPTIONAL_ANNOTATION - __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(this->_Value)); + if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(_Ty)); + } #endif _STD _Construct_in_place(this->_Value, _STD forward<_Types>(_Args)...); diff --git a/stl/src/asan.cpp b/stl/src/asan.cpp index da75de1afcd..e49b05f27f6 100644 --- a/stl/src/asan.cpp +++ b/stl/src/asan.cpp @@ -3,7 +3,8 @@ namespace std { extern "C" { - extern const bool _Asan_string_should_annotate = true; - extern const bool _Asan_vector_should_annotate = true; + extern const bool _Asan_string_should_annotate = true; + extern const bool _Asan_vector_should_annotate = true; + extern const bool _Asan_optional_should_annotate = true; } // extern "C" } // namespace std diff --git a/stl/src/asan_noop.cpp b/stl/src/asan_noop.cpp index fda89488c80..773ca3880fa 100644 --- a/stl/src/asan_noop.cpp +++ b/stl/src/asan_noop.cpp @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception extern "C" { -extern const bool _Asan_string_should_annotate_default = false; -extern const bool _Asan_vector_should_annotate_default = false; +extern const bool _Asan_string_should_annotate_default = false; +extern const bool _Asan_vector_should_annotate_default = false; +extern const bool _Asan_optional_should_annotate_default = false; void __cdecl __sanitizer_annotate_contiguous_container_default( const void*, const void*, const void*, const void*) noexcept {} diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp index 7403dc6c7f0..42208e9bfa5 100644 --- a/tests/std/tests/GH_005974_optional_asan/test.cpp +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#include <__msvc_sanitizer_annotate_container.hpp> #include #include @@ -28,18 +27,18 @@ void test_poison_on_empty_access() { void test_emplace_unpoisoning() { std::optional opt; - opt.emplace(Payload()); + opt.emplace(); ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } void test_assignment_unpoisoning() { std::optional opt = std::nullopt; - opt = Payload(); + opt = Payload{}; ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); } void test_repoison_after_reset() { - std::optional opt = Payload(); + std::optional opt = Payload{}; ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); opt.reset(); ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); From 1be47723f34dda3bda3c4ad16378196f84afdf76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Thu, 15 Jan 2026 20:56:50 +0300 Subject: [PATCH 06/14] add constexpr test --- stl/inc/optional | 12 ++++++------ .../std/tests/GH_005974_optional_asan/test.cpp | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/stl/inc/optional b/stl/inc/optional index 6c742c26e4c..8a90c37b518 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -77,7 +77,7 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif @@ -101,7 +101,7 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( _CONSTEXPR20 void reset() noexcept { _Has_value = false; #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif @@ -121,7 +121,7 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _Value.~_Ty(); #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif @@ -137,7 +137,7 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif @@ -165,7 +165,7 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _Value.~_Ty(); #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif @@ -186,7 +186,7 @@ struct _Optional_construct_base : _Optional_destruct_base<_Ty> { _STL_INTERNAL_CHECK(!this->_Has_value); #ifdef _INSERT_OPTIONAL_ANNOTATION - if (_Asan_optional_should_annotate && !_STD _Is_constant_evaluated()) { + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(_Ty)); } #endif diff --git a/tests/std/tests/GH_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_optional_asan/test.cpp index 42208e9bfa5..541ecc07285 100644 --- a/tests/std/tests/GH_005974_optional_asan/test.cpp +++ b/tests/std/tests/GH_005974_optional_asan/test.cpp @@ -44,11 +44,29 @@ void test_repoison_after_reset() { ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); } +constexpr bool test_constexpr() { +#if _HAS_CXX20 + bool res = true; + std::optional opt = std::nullopt; + opt = Payload{}; + opt.reset(); + opt = Payload{86, 0, 0, 0}; + res = opt->x == 86; + opt.emplace(42, 0, 0, 0); + res = res && (opt->x == 42); + return res; +#else + std::optional opt{Payload{86, 0, 0, 0}}; + return opt->x == 86; +#endif +} + int main() { test_poison_on_empty_access(); test_emplace_unpoisoning(); test_assignment_unpoisoning(); test_repoison_after_reset(); + static_assert(test_constexpr(), "constexpr test failed"); return 0; } From eaa64c4f50bf9585167bb8fdc1402b133fd8e4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Thu, 26 Feb 2026 16:36:57 +0300 Subject: [PATCH 07/14] address review suggestions --- .../__msvc_sanitizer_annotate_container.hpp | 16 +++++- stl/src/asan_noop.cpp | 3 ++ tests/std/test.lst | 2 +- .../GH_005974_asan_annotate_optional/env.lst | 52 +++++++++++++++++++ .../test.cpp | 0 .../std/tests/GH_005974_optional_asan/env.lst | 4 -- 6 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 tests/std/tests/GH_005974_asan_annotate_optional/env.lst rename tests/std/tests/{GH_005974_optional_asan => GH_005974_asan_annotate_optional}/test.cpp (100%) delete mode 100644 tests/std/tests/GH_005974_optional_asan/env.lst diff --git a/stl/inc/__msvc_sanitizer_annotate_container.hpp b/stl/inc/__msvc_sanitizer_annotate_container.hpp index 9b03edf07a2..6fd50ccb3c4 100644 --- a/stl/inc/__msvc_sanitizer_annotate_container.hpp +++ b/stl/inc/__msvc_sanitizer_annotate_container.hpp @@ -161,8 +161,8 @@ extern const bool _Asan_optional_should_annotate; #if defined(_INSERT_VECTOR_ANNOTATION) || defined(_INSERT_STRING_ANNOTATION) || defined(_INSERT_OPTIONAL_ANNOTATION) extern "C" { -void __cdecl __asan_poison_memory_region(void const volatile* addr, size_t size); -void __cdecl __asan_unpoison_memory_region(void const volatile* addr, size_t size); +void __cdecl __asan_poison_memory_region(void const volatile* _Addr, size_t _Size); +void __cdecl __asan_unpoison_memory_region(void const volatile* _Addr, size_t _Size); // This must match ASan's primary declaration, which isn't marked `noexcept`. void __cdecl __sanitizer_annotate_contiguous_container( @@ -170,6 +170,10 @@ void __cdecl __sanitizer_annotate_contiguous_container( } // extern "C" #ifdef _M_ARM64EC +#pragma comment(linker, "/alternatename:#__asan_poison_memory_region=#__asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:__asan_poison_memory_region=__asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:#__asan_unpoison_memory_region=#__asan_unpoison_memory_region_default") +#pragma comment(linker, "/alternatename:__asan_unpoison_memory_region=__asan_unpoison_memory_region_default") #pragma comment(linker, \ "/alternatename:#__sanitizer_annotate_contiguous_container=#__sanitizer_annotate_contiguous_container_default") #pragma comment(linker, \ @@ -181,6 +185,10 @@ void __cdecl __sanitizer_annotate_contiguous_container( #pragma comment(linker, "/alternatename:#_Asan_optional_should_annotate=#_Asan_optional_should_annotate_default") #pragma comment(linker, "/alternatename:_Asan_optional_should_annotate=_Asan_optional_should_annotate_default") #elif defined(_M_HYBRID) +#pragma comment(linker, "/alternatename:#__asan_poison_memory_region=#__asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:___asan_poison_memory_region=___asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:#__asan_unpoison_memory_region=#__asan_unpoison_memory_region_default") +#pragma comment(linker, "/alternatename:___asan_unpoison_memory_region=___asan_unpoison_memory_region_default") #pragma comment(linker, \ "/alternatename:#__sanitizer_annotate_contiguous_container=#__sanitizer_annotate_contiguous_container_default") #pragma comment(linker, \ @@ -192,12 +200,16 @@ void __cdecl __sanitizer_annotate_contiguous_container( #pragma comment(linker, "/alternatename:#_Asan_optional_should_annotate=#_Asan_optional_should_annotate_default") #pragma comment(linker, "/alternatename:__Asan_optional_should_annotate=__Asan_optional_should_annotate_default") #elif defined(_M_IX86) +#pragma comment(linker, "/alternatename:___asan_poison_memory_region=___asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:___asan_unpoison_memory_region=___asan_unpoison_memory_region_default") #pragma comment(linker, \ "/alternatename:___sanitizer_annotate_contiguous_container=___sanitizer_annotate_contiguous_container_default") #pragma comment(linker, "/alternatename:__Asan_vector_should_annotate=__Asan_vector_should_annotate_default") #pragma comment(linker, "/alternatename:__Asan_string_should_annotate=__Asan_string_should_annotate_default") #pragma comment(linker, "/alternatename:__Asan_optional_should_annotate=__Asan_optional_should_annotate_default") #elif defined(_M_X64) || defined(_M_ARM64) +#pragma comment(linker, "/alternatename:__asan_poison_memory_region=__asan_poison_memory_region_default") +#pragma comment(linker, "/alternatename:__asan_unpoison_memory_region=__asan_unpoison_memory_region_default") #pragma comment(linker, \ "/alternatename:__sanitizer_annotate_contiguous_container=__sanitizer_annotate_contiguous_container_default") #pragma comment(linker, "/alternatename:_Asan_vector_should_annotate=_Asan_vector_should_annotate_default") diff --git a/stl/src/asan_noop.cpp b/stl/src/asan_noop.cpp index 773ca3880fa..1a32e86ec4b 100644 --- a/stl/src/asan_noop.cpp +++ b/stl/src/asan_noop.cpp @@ -6,6 +6,9 @@ extern const bool _Asan_string_should_annotate_default = false; extern const bool _Asan_vector_should_annotate_default = false; extern const bool _Asan_optional_should_annotate_default = false; +void __cdecl __asan_poison_memory_region_default(void const volatile*, size_t) {} +void __cdecl __asan_unpoison_memory_region_default(void const volatile*, size_t) {} + void __cdecl __sanitizer_annotate_contiguous_container_default( const void*, const void*, const void*, const void*) noexcept {} } // extern "C" diff --git a/tests/std/test.lst b/tests/std/test.lst index 80f40cb5822..b1ba196aeec 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -278,7 +278,7 @@ tests\GH_005553_regex_character_translation tests\GH_005768_pow_accuracy tests\GH_005800_stable_sort_large_alignment tests\GH_005968_headers_provide_begin_end -tests\GH_005974_optional_asan +tests\GH_005974_asan_annotate_optional tests\LWG2381_num_get_floating_point tests\LWG2510_tag_classes tests\LWG2597_complex_branch_cut diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/env.lst b/tests/std/tests/GH_005974_asan_annotate_optional/env.lst new file mode 100644 index 00000000000..f1477bf3294 --- /dev/null +++ b/tests/std/tests/GH_005974_asan_annotate_optional/env.lst @@ -0,0 +1,52 @@ +# 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++17" +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++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++17 /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_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MD /std:c++17" +PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MDd /std:c++17 /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MT /std:c++20 /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MTd /std:c++latest /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++17" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++20" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++latest /permissive- /Zc:char8_t- /Zc:preprocessor" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++latest /permissive- /Zc:noexceptTypes-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++17 /fp:except /Zc:preprocessor" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++17 /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++20 /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++latest /permissive- /Zc:wchar_t-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++latest /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MT /std:c++latest /permissive- /analyze:only /analyze:autolog-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MT /std:c++latest /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive- /analyze:only /analyze:autolog-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive- /fp:strict" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive-" +# TRANSITION, we don't use /ALTERNATENAME for Clang (see GH-5224) so we cannot test /D_ANNOTATE_OPTIONAL without -fsanitize=address +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_005974_optional_asan/test.cpp b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp similarity index 100% rename from tests/std/tests/GH_005974_optional_asan/test.cpp rename to tests/std/tests/GH_005974_asan_annotate_optional/test.cpp diff --git a/tests/std/tests/GH_005974_optional_asan/env.lst b/tests/std/tests/GH_005974_optional_asan/env.lst deleted file mode 100644 index 2de7aab2959..00000000000 --- a/tests/std/tests/GH_005974_optional_asan/env.lst +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -RUNALL_INCLUDE ..\usual_17_matrix.lst From 51b64660ea9faaf6f88fbce0d26cb70d5fc9f5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr?= Date: Thu, 26 Feb 2026 20:15:28 +0300 Subject: [PATCH 08/14] disable arm tests --- .../std/tests/GH_005974_asan_annotate_optional/notarget.lst | 5 +++++ tests/std/tests/GH_005974_asan_annotate_optional/test.cpp | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst b/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst new file mode 100644 index 00000000000..0027fd085c1 --- /dev/null +++ b/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +## TRANSITION, VSO-1938218 ASAN is not yet supported on arm64 +arm64 diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp index 541ecc07285..24d4e2405da 100644 --- a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp +++ b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// REQUIRES: x64 || x86 + #include #include From c0c5a78da5c143fe65bf49c1cc5316034e6ad15d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Mon, 16 Mar 2026 14:49:53 -0700 Subject: [PATCH 09/14] Enable ARM64 test coverage. --- .../tests/GH_005974_asan_annotate_optional/notarget.lst | 5 ----- .../std/tests/GH_005974_asan_annotate_optional/test.cpp | 9 ++++++++- 2 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst b/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst deleted file mode 100644 index 0027fd085c1..00000000000 --- a/tests/std/tests/GH_005974_asan_annotate_optional/notarget.lst +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -## TRANSITION, VSO-1938218 ASAN is not yet supported on arm64 -arm64 diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp index 24d4e2405da..c86ee081af3 100644 --- a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp +++ b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp @@ -1,7 +1,12 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// REQUIRES: x64 || x86 +// 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 #include #include @@ -72,3 +77,5 @@ int main() { return 0; } + +#endif // ^^^ no workaround ^^^ From ad97b4b7a829f48b5f1df55f8cad5f4b251846a4 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 17 Mar 2026 04:42:37 -0700 Subject: [PATCH 10/14] `void const volatile*` => `const volatile void*` --- stl/inc/__msvc_sanitizer_annotate_container.hpp | 4 ++-- stl/src/asan_noop.cpp | 4 ++-- tests/std/tests/GH_005974_asan_annotate_optional/test.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stl/inc/__msvc_sanitizer_annotate_container.hpp b/stl/inc/__msvc_sanitizer_annotate_container.hpp index 243c47051d6..dbfd83b311d 100644 --- a/stl/inc/__msvc_sanitizer_annotate_container.hpp +++ b/stl/inc/__msvc_sanitizer_annotate_container.hpp @@ -161,8 +161,8 @@ extern const bool _Asan_optional_should_annotate; #if defined(_INSERT_VECTOR_ANNOTATION) || defined(_INSERT_STRING_ANNOTATION) || defined(_INSERT_OPTIONAL_ANNOTATION) extern "C" { -void __cdecl __asan_poison_memory_region(void const volatile* _Addr, size_t _Size); -void __cdecl __asan_unpoison_memory_region(void const volatile* _Addr, size_t _Size); +void __cdecl __asan_poison_memory_region(const volatile void* _Addr, size_t _Size); +void __cdecl __asan_unpoison_memory_region(const volatile void* _Addr, size_t _Size); // This must match ASan's primary declaration, which isn't marked `noexcept`. void __cdecl __sanitizer_annotate_contiguous_container( diff --git a/stl/src/asan_noop.cpp b/stl/src/asan_noop.cpp index 1a32e86ec4b..88eca405d5a 100644 --- a/stl/src/asan_noop.cpp +++ b/stl/src/asan_noop.cpp @@ -6,8 +6,8 @@ extern const bool _Asan_string_should_annotate_default = false; extern const bool _Asan_vector_should_annotate_default = false; extern const bool _Asan_optional_should_annotate_default = false; -void __cdecl __asan_poison_memory_region_default(void const volatile*, size_t) {} -void __cdecl __asan_unpoison_memory_region_default(void const volatile*, size_t) {} +void __cdecl __asan_poison_memory_region_default(const volatile void*, size_t) {} +void __cdecl __asan_unpoison_memory_region_default(const volatile void*, size_t) {} void __cdecl __sanitizer_annotate_contiguous_container_default( const void*, const void*, const void*, const void*) noexcept {} diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp index c86ee081af3..a340013244f 100644 --- a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp +++ b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp @@ -12,7 +12,7 @@ int main() {} #include #ifdef __SANITIZE_ADDRESS__ -extern "C" int __cdecl __asan_address_is_poisoned(void const volatile* addr); +extern "C" int __cdecl __asan_address_is_poisoned(const volatile void* addr); #define ASAN_VERIFY_POISONED(addr) assert(__asan_address_is_poisoned((addr)) != 0) #define ASAN_VERIFY_UNPOISONED(addr) assert(__asan_address_is_poisoned((addr)) == 0) #else From 39a9624023902b2131a8b2bb30d6cbff9703ba63 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 17 Mar 2026 05:15:05 -0700 Subject: [PATCH 11/14] Consistently poison before assigning `_Has_value = false;`. --- stl/inc/optional | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/optional b/stl/inc/optional index 0b4efbdc6ae..47d2d429fbe 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -99,12 +99,12 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( // N5001 [optional.dtor]/2: "Remarks: If is_trivially_destructible_v is true, then this destructor is trivial." _CONSTEXPR20 void reset() noexcept { - _Has_value = false; #ifdef _INSERT_OPTIONAL_ANNOTATION if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } #endif + _Has_value = false; } }; From b36dc3db9ab8ca722d8b6eabdc6bca61b291cb5d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 17 Mar 2026 08:06:18 -0700 Subject: [PATCH 12/14] Fix product code, add lots of comments. --- stl/inc/optional | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/stl/inc/optional b/stl/inc/optional index 47d2d429fbe..6b55ea08c4e 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -75,13 +75,7 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( }; bool _Has_value; - constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional -#ifdef _INSERT_OPTIONAL_ANNOTATION - if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); - } -#endif - } + constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} {} // initialize an empty optional template constexpr explicit _Optional_destruct_base(in_place_t, _Types&&... _Args) @@ -97,13 +91,9 @@ struct _Optional_destruct_base { // either contains a value of _Ty or is empty ( // For the trivially destructible case, we can't add a destructor for _MSVC_STL_DESTRUCTOR_TOMBSTONES, due to // N5001 [optional.dtor]/2: "Remarks: If is_trivially_destructible_v is true, then this destructor is trivial." + // This also prevents ASan annotations from being used. _CONSTEXPR20 void reset() noexcept { -#ifdef _INSERT_OPTIONAL_ANNOTATION - if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); - } -#endif _Has_value = false; } }; @@ -120,27 +110,30 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o if (_Has_value) { _Value.~_Ty(); -#ifdef _INSERT_OPTIONAL_ANNOTATION - if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { - __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); - } -#endif - #if _MSVC_STL_DESTRUCTOR_TOMBSTONES // For the non-trivially destructible case, we can set the optional to be empty. // We don't attempt to scribble over the bytes of the object's storage because that could be expensive // and we don't know whether the object has an invalid representation, much less what it could be. _Has_value = false; #endif + } else { +#ifdef _INSERT_OPTIONAL_ANNOTATION + // Unpoison our storage when the std::optional is destroyed. + // We're giving up control of these bytes, which can be validly reused for something else. + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { + __asan_unpoison_memory_region(_STD addressof(_Value), sizeof(_Ty)); + } +#endif // ^^^ defined(_INSERT_OPTIONAL_ANNOTATION) ^^^ } } constexpr _Optional_destruct_base() noexcept : _Dummy{}, _Has_value{false} { // initialize an empty optional #ifdef _INSERT_OPTIONAL_ANNOTATION + // Poison our storage when starting empty, until that changes or the std::optional is destroyed. if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } -#endif +#endif // ^^^ defined(_INSERT_OPTIONAL_ANNOTATION) ^^^ } template @@ -163,14 +156,14 @@ struct _Optional_destruct_base<_Ty, false> { // either contains a value of _Ty o _CONSTEXPR20 void reset() noexcept { if (_Has_value) { _Value.~_Ty(); + _Has_value = false; #ifdef _INSERT_OPTIONAL_ANNOTATION + // Poison our storage when becoming empty, until that changes or the std::optional is destroyed. if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { __asan_poison_memory_region(_STD addressof(_Value), sizeof(_Ty)); } -#endif - - _Has_value = false; +#endif // ^^^ defined(_INSERT_OPTIONAL_ANNOTATION) ^^^ } } }; @@ -186,10 +179,14 @@ struct _Optional_construct_base : _Optional_destruct_base<_Ty> { _STL_INTERNAL_CHECK(!this->_Has_value); #ifdef _INSERT_OPTIONAL_ANNOTATION - if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { - __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(_Ty)); + // Only the non-trivially destructible case can use ASan annotations, as the destructor is needed to undo them. + if constexpr (!is_trivially_destructible_v<_Ty>) { + // Unpoison our storage when becoming non-empty. + if (!_STD _Is_constant_evaluated() && _Asan_optional_should_annotate) { + __asan_unpoison_memory_region(_STD addressof(this->_Value), sizeof(_Ty)); + } } -#endif +#endif // ^^^ defined(_INSERT_OPTIONAL_ANNOTATION) ^^^ _STD _Construct_in_place(this->_Value, _STD forward<_Types>(_Args)...); this->_Has_value = true; From 4b4347c54247a138d90637c17154ccab3e9ee6a7 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 17 Mar 2026 11:08:26 -0700 Subject: [PATCH 13/14] Re-derive the test matrix from usual_17_matrix.lst. --- .../tests/GH_005974_asan_annotate_optional/env.lst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/env.lst b/tests/std/tests/GH_005974_asan_annotate_optional/env.lst index f1477bf3294..d2131f19577 100644 --- a/tests/std/tests/GH_005974_asan_annotate_optional/env.lst +++ b/tests/std/tests/GH_005974_asan_annotate_optional/env.lst @@ -1,14 +1,15 @@ # 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. +# This test matrix was derived from usual_17_matrix.lst with the same elaborate transformations +# that were used to derive GH_002030_asan_annotate_vector/env.lst from usual_matrix.lst. # 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++17" +PM_CL="-fsanitize=address /BE /c /EHsc /MD /std:c++latest /permissive-" 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-" @@ -16,9 +17,10 @@ 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++17 /fp:except /Zc:preprocessor" +PM_CL="-fsanitize=address /EHsc /MD /std:c++latest /permissive-" 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- /fp:except /Zc:preprocessor" 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-" @@ -27,7 +29,7 @@ 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_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MD /std:c++17" +PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MD /std:c++latest /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MDd /std:c++17 /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MT /std:c++20 /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /BE /c /EHsc /MTd /std:c++latest /permissive-" @@ -35,9 +37,10 @@ PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++17" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++20" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++latest /permissive- /Zc:char8_t- /Zc:preprocessor" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++latest /permissive- /Zc:noexceptTypes-" -PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++17 /fp:except /Zc:preprocessor" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MD /std:c++latest /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++17 /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++20 /permissive-" +PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++latest /permissive- /fp:except /Zc:preprocessor" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++latest /permissive- /Zc:wchar_t-" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MDd /std:c++latest /permissive-" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MT /std:c++latest /permissive- /analyze:only /analyze:autolog-" @@ -47,6 +50,7 @@ PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive- /analyze:only PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive- /fp:strict" PM_CL="/D_ANNOTATE_OPTIONAL /EHsc /MTd /std:c++latest /permissive-" # TRANSITION, we don't use /ALTERNATENAME for Clang (see GH-5224) so we cannot test /D_ANNOTATE_OPTIONAL without -fsanitize=address +PM_COMPILER="clang-cl" PM_CL="-fsanitize=address -fno-ms-compatibility -fno-delayed-template-parsing -Wno-unqualified-std-cast-call /EHsc /MD /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++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" From 4a5db0e97e84bd5c644e466d4c5d0ea2cae000bd Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Tue, 17 Mar 2026 13:30:01 -0700 Subject: [PATCH 14/14] Overhaul the test. --- .../GH_005974_asan_annotate_optional/test.cpp | 144 ++++++++++++------ 1 file changed, 96 insertions(+), 48 deletions(-) diff --git a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp index a340013244f..95d0ed99b2c 100644 --- a/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp +++ b/tests/std/tests/GH_005974_asan_annotate_optional/test.cpp @@ -10,72 +10,120 @@ int main() {} #include #include +#include +#include +#include +#include +using namespace std; + +#if _HAS_CXX20 +#define CONSTEXPR20 constexpr +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv +#define CONSTEXPR20 inline +#endif // ^^^ !_HAS_CXX20 ^^^ #ifdef __SANITIZE_ADDRESS__ extern "C" int __cdecl __asan_address_is_poisoned(const volatile void* addr); #define ASAN_VERIFY_POISONED(addr) assert(__asan_address_is_poisoned((addr)) != 0) #define ASAN_VERIFY_UNPOISONED(addr) assert(__asan_address_is_poisoned((addr)) == 0) -#else +#else // ^^^ defined(__SANITIZE_ADDRESS__) / !defined(__SANITIZE_ADDRESS__) vvv #define ASAN_VERIFY_POISONED(addr) ((void) (addr)) #define ASAN_VERIFY_UNPOISONED(addr) ((void) (addr)) -#endif +#endif // ^^^ !defined(__SANITIZE_ADDRESS__) ^^^ -struct Payload { - long long x; - long long y; - long long z; - long long w; -}; +template +void test_poisoning() { + // std::optional is ASan-annotated only for non-trivially destructible T. + static_assert(!is_trivially_destructible_v); + { + // Verify layout assumption, specific to our implementation but not a guarantee provided to users. + // This test assumes that a std::optional's contained value is stored at offset 0, so it has the same address. + const optional opt{"cats"}; + assert(static_cast(&opt) == static_cast(&opt.value())); + } + { + // Same layout assumption for std::variant. + const variant, int> var{"cats"}; + assert(static_cast(&var) == static_cast(&get>(var).value())); + } + { + optional opt; + ASAN_VERIFY_POISONED(&opt); + assert(!opt.has_value()); -void test_poison_on_empty_access() { - [[maybe_unused]] std::optional opt; - ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); -} + opt.emplace("cats"); + ASAN_VERIFY_UNPOISONED(&opt); + assert(opt.value() == "cats"); + } + { + optional opt{"cats"}; + ASAN_VERIFY_UNPOISONED(&opt); + assert(opt.value() == "cats"); -void test_emplace_unpoisoning() { - std::optional opt; - opt.emplace(); - ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); -} + opt.reset(); + ASAN_VERIFY_POISONED(&opt); + assert(!opt.has_value()); + } + { + // Verify that std::optional's destructor unpoisons its storage, so the bytes can be reused for another object. + variant, int> var{"cats"}; + ASAN_VERIFY_UNPOISONED(&var); + assert(get>(var).value() == "cats"); -void test_assignment_unpoisoning() { - std::optional opt = std::nullopt; - opt = Payload{}; - ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); -} + get>(var).reset(); + ASAN_VERIFY_POISONED(&var); + assert(!get>(var).has_value()); -void test_repoison_after_reset() { - std::optional opt = Payload{}; - ASAN_VERIFY_UNPOISONED(reinterpret_cast(&opt)); - opt.reset(); - ASAN_VERIFY_POISONED(reinterpret_cast(&opt)); + var = 1729; + ASAN_VERIFY_UNPOISONED(&var); + assert(get(var) == 1729); + } } -constexpr bool test_constexpr() { #if _HAS_CXX20 - bool res = true; - std::optional opt = std::nullopt; - opt = Payload{}; - opt.reset(); - opt = Payload{86, 0, 0, 0}; - res = opt->x == 86; - opt.emplace(42, 0, 0, 0); - res = res && (opt->x == 42); - return res; -#else - std::optional opt{Payload{86, 0, 0, 0}}; - return opt->x == 86; -#endif +template +constexpr bool test_constexpr() { + { + optional opt; + assert(!opt.has_value()); + opt.emplace("cats"); + assert(opt.value() == "cats"); + } + { + optional opt{"cats"}; + assert(opt.value() == "cats"); + opt.reset(); + assert(!opt.has_value()); + } + return true; } +#endif // _HAS_CXX20 -int main() { - test_poison_on_empty_access(); - test_emplace_unpoisoning(); - test_assignment_unpoisoning(); - test_repoison_after_reset(); - static_assert(test_constexpr(), "constexpr test failed"); +class MyString { +public: + constexpr MyString(const char* const ptr) : m_sv{ptr} {} + + CONSTEXPR20 ~MyString() {} // non-trivially destructible - return 0; + constexpr bool operator==(const char* const ptr) const { + return m_sv == ptr; + } + +private: + string_view m_sv{}; +}; +static_assert(!is_trivially_destructible_v); + +int main() { + // Test std::optional with both std::string and MyString as value types. + // std::string is the realistic scenario. + // MyString ensures that we're exercising std::optional's ASan annotations instead of std::string's. + test_poisoning(); + test_poisoning(); +#if _HAS_CXX20 + static_assert(test_constexpr()); + static_assert(test_constexpr()); +#endif // _HAS_CXX20 } #endif // ^^^ no workaround ^^^