44#pragma once
55
66#include < cassert>
7+ #include < cstddef>
78#include < functional>
89#include < memory>
910#include < optional>
1011#include < type_traits>
1112#include < utility>
1213
14+ #if ( defined( __has_feature ) && __has_feature( address_sanitizer ) )
15+ # define NOVA_HAVE_ASAN 1
16+ #endif
17+ #if defined( __SANITIZE_ADDRESS__ )
18+ # define NOVA_HAVE_ASAN 1
19+ #endif
20+
21+ #ifdef NOVA_HAVE_ASAN
22+ # include < sanitizer/asan_interface.h>
23+ #endif
24+
25+
1326#if defined( __has_cpp_attribute )
1427# if __has_cpp_attribute( assume )
1528# define NOVA_ASSUME ( expr ) [[assume( expr )]]
4659# define NOVA_NONNULL_NONTRIVIAL
4760#endif
4861
62+
4963namespace nova {
5064
5165namespace detail {
5266
67+ #if defined( NOVA_HAVE_ASAN )
68+ inline void nova_asan_poison ( void const * NOVA_NONNULL p, std::size_t s ) noexcept
69+ {
70+ __asan_poison_memory_region ( p, s );
71+ }
72+ inline void nova_asan_unpoison ( void const * NOVA_NONNULL p, std::size_t s ) noexcept
73+ {
74+ __asan_unpoison_memory_region ( p, s );
75+ }
76+ /* In ASAN builds we cannot keep take() constexpr because it calls runtime
77+ instrumentation functions. Control the constexpr-ness via this macro. */
78+ # define NOVA_ASAN_CONSTEXPR /* empty */
79+ #else
80+ inline void nova_asan_poison ( void const * NOVA_NONNULL, std::size_t ) noexcept
81+ {}
82+ inline void nova_asan_unpoison ( void const * NOVA_NONNULL, std::size_t ) noexcept
83+ {}
84+ # define NOVA_ASAN_CONSTEXPR constexpr
85+ #endif
86+
87+
5388template < typename T, typename = void >
5489struct element_type_trait
5590{
@@ -144,8 +179,15 @@ class non_null
144179 // - For move-only pointers (unique_ptr): move deleted; use take() instead
145180 // This prevents accidental moves of move-only types while enabling efficient
146181 // moves of copyable types.
147- non_null ( const non_null& ) = default ;
148- non_null& operator =( const non_null& ) = default ;
182+ non_null ( const non_null& ) = default ;
183+ non_null& operator =( const non_null& other ) noexcept
184+ {
185+ // If this object was previously poisoned by take(), ensure we can write
186+ // into ptr_ without ASAN reporting a write to poisoned memory.
187+ detail::nova_asan_unpoison ( &ptr_, sizeof ( ptr_ ) );
188+ ptr_ = other.ptr_ ;
189+ return *this ;
190+ }
149191
150192 /* *
151193 * @brief Move constructor, enabled only for copyable pointer types.
@@ -157,13 +199,19 @@ class non_null
157199 ptr_( std::move( other.ptr_ ) )
158200 {}
159201
202+ ~non_null ()
203+ {
204+ detail::nova_asan_unpoison ( &ptr_, sizeof ( ptr_ ) );
205+ }
206+
160207 /* *
161208 * @brief Move assignment, enabled only for copyable pointer types.
162209 * Raw pointers and std::shared_ptr support moves; std::unique_ptr does not.
163210 */
164211 constexpr non_null& operator =( non_null&& other ) noexcept
165212 requires detail::copyable_pointer< T >
166213 {
214+ detail::nova_asan_unpoison ( &ptr_, sizeof ( ptr_ ) );
167215 ptr_ = std::move ( other.ptr_ );
168216 return *this ;
169217 }
@@ -186,12 +234,16 @@ class non_null
186234 * For copyable pointer types the result can be re-wrapped immediately:
187235 * auto nn2 = non_null( take( std::move(nn1) ) );
188236 */
189- friend constexpr T NOVA_NONNULL_NONTRIVIAL take ( non_null&& nn ) noexcept
237+ friend NOVA_ASAN_CONSTEXPR T NOVA_NONNULL_NONTRIVIAL take ( non_null&& nn ) noexcept
190238#if defined( __clang__ ) && ( __clang_major__ >= 20 )
191239 NOVA_RETURNS_NONNULL
192240#endif
193241 {
194- return std::move ( nn.ptr_ );
242+ T tmp = std::move ( nn.ptr_ );
243+ // Poison the source wrapper storage so accidental use-after-take
244+ // triggers ASAN in instrumented builds.
245+ detail::nova_asan_poison ( &nn.ptr_ , sizeof ( nn.ptr_ ) );
246+ return tmp;
195247 }
196248
197249 /* *
@@ -200,6 +252,10 @@ class non_null
200252 */
201253 constexpr void swap ( non_null& other ) noexcept
202254 {
255+ // Unpoison both sides before swapping so the swap operation can write
256+ // into the underlying storage safely under ASAN.
257+ detail::nova_asan_unpoison ( &ptr_, sizeof ( ptr_ ) );
258+ detail::nova_asan_unpoison ( &other.ptr_ , sizeof ( other.ptr_ ) );
203259 using std::swap;
204260 swap ( ptr_, other.ptr_ );
205261 }
@@ -515,9 +571,20 @@ class non_null_function< R( Args... ) >
515571 detail::assume_not_empty ( fn_ );
516572 }
517573
574+ ~non_null_function ()
575+ {
576+ detail::nova_asan_unpoison ( &fn_, sizeof ( fn_ ) );
577+ }
578+
518579 // Copy ctor and copy assignment are defaulted (std::function is copyable)
519- non_null_function ( const non_null_function& ) = default ;
520- non_null_function& operator =( const non_null_function& ) = default ;
580+ non_null_function ( const non_null_function& ) = default ;
581+ non_null_function& operator =( const non_null_function& other )
582+ {
583+ // Unpoison target storage in case it was poisoned by a previous take().
584+ detail::nova_asan_unpoison ( &fn_, sizeof ( fn_ ) );
585+ fn_ = other.fn_ ;
586+ return *this ;
587+ }
521588
522589 // Implicit move: deleted to prevent accidental moves that leave the
523590 // wrapper in an unusable (empty) state. Use take() to transfer ownership explicitly.
@@ -563,6 +630,8 @@ class non_null_function< R( Args... ) >
563630 */
564631 constexpr void swap ( non_null_function& other ) noexcept
565632 {
633+ detail::nova_asan_unpoison ( &fn_, sizeof ( fn_ ) );
634+ detail::nova_asan_unpoison ( &other.fn_ , sizeof ( other.fn_ ) );
566635 fn_.swap ( other.fn_ );
567636 }
568637
@@ -575,7 +644,9 @@ class non_null_function< R( Args... ) >
575644 */
576645 friend function_type take ( non_null_function&& nn ) noexcept
577646 {
578- return std::move ( nn.fn_ );
647+ function_type tmp = std::move ( nn.fn_ );
648+ detail::nova_asan_poison ( &nn.fn_ , sizeof ( nn.fn_ ) );
649+ return tmp;
579650 }
580651
581652private:
@@ -688,6 +759,8 @@ class non_null_move_only_function< R( Args... ) >
688759 */
689760 constexpr void swap ( non_null_move_only_function& other ) noexcept
690761 {
762+ NOVA_ASAN_UNPOISON ( &fn_, sizeof ( fn_ ) );
763+ NOVA_ASAN_UNPOISON ( &other.fn_ , sizeof ( other.fn_ ) );
691764 fn_.swap ( other.fn_ );
692765 }
693766
@@ -701,7 +774,9 @@ class non_null_move_only_function< R( Args... ) >
701774 */
702775 friend function_type take ( non_null_move_only_function&& nn ) noexcept
703776 {
704- return std::move ( nn.fn_ );
777+ function_type tmp = std::move ( nn.fn_ );
778+ NOVA_ASAN_POISON ( &nn.fn_ , sizeof ( nn.fn_ ) );
779+ return tmp;
705780 }
706781
707782private:
0 commit comments