diff --git a/include/fast_io_core_impl/allocation/adapters.h b/include/fast_io_core_impl/allocation/adapters.h index 809ebe226..9adcba2f3 100644 --- a/include/fast_io_core_impl/allocation/adapters.h +++ b/include/fast_io_core_impl/allocation/adapters.h @@ -104,10 +104,13 @@ class generic_allocator_adapter ::fast_io::details::has_allocate_zero_impl || ::fast_io::details::has_allocate_aligned_zero_impl)}; +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline constexpr void * - allocate(::std::size_t n) noexcept + allocate(::std::size_t n) noexcept requires(!has_status) { #if __cpp_constexpr_dynamic_alloc >= 201907L @@ -149,6 +152,9 @@ class generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline void *allocate_zero(::std::size_t n) noexcept requires(!has_status) { @@ -185,6 +191,9 @@ class generic_allocator_adapter ::fast_io::details::has_reallocate_aligned_zero_impl || ::fast_io::details::has_reallocate_aligned_zero_at_least_impl); +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline void *reallocate(void *p, ::std::size_t n) noexcept requires(!has_status && has_reallocate) { @@ -228,6 +237,9 @@ class generic_allocator_adapter ::fast_io::details::has_reallocate_aligned_zero_at_least_impl); +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline void *reallocate_zero(void *p, ::std::size_t n) noexcept requires(!has_status && has_reallocate_zero) { @@ -249,9 +261,16 @@ class generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline void *reallocate_n(void *p, ::std::size_t oldn, ::std::size_t n) noexcept requires(!has_status) { + if (p != nullptr && oldn == n) + { + return p; + } if constexpr (::fast_io::details::has_reallocate_n_impl) { return allocator_type::reallocate_n(p, oldn, n); @@ -319,22 +338,32 @@ class generic_allocator_adapter else { auto newptr{generic_allocator_adapter::allocate(n)}; - if (p != nullptr && n) + if (p != nullptr) { - if (oldn < n) + if (n) { - n = oldn; + if (oldn < n) + { + n = oldn; + } + ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); } - ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); generic_allocator_adapter::deallocate_n(p, oldn); } return newptr; } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline void *reallocate_zero_n(void *p, ::std::size_t oldn, ::std::size_t n) noexcept requires(!has_status) { + if (p != nullptr && oldn == n) + { + return p; + } if constexpr (::fast_io::details::has_reallocate_zero_n_impl) { return allocator_type::reallocate_zero_n(p, oldn, n); @@ -437,6 +466,9 @@ class generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline constexpr void * @@ -455,6 +487,9 @@ class generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline constexpr void * allocate_aligned_zero(::std::size_t alignment, ::std::size_t n) noexcept @@ -620,9 +655,12 @@ class generic_allocator_adapter ::fast_io::details::has_reallocate_aligned_zero_impl || ::fast_io::details::has_reallocate_aligned_zero_at_least_impl); +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline - void * - reallocate_aligned(void *p, ::std::size_t alignment, ::std::size_t n) noexcept + void * + reallocate_aligned(void *p, ::std::size_t alignment, ::std::size_t n) noexcept requires(!has_status && has_reallocate_aligned) { if constexpr (::fast_io::details::has_reallocate_aligned_impl) @@ -645,9 +683,12 @@ class generic_allocator_adapter static inline constexpr bool has_reallocate_aligned_zero = (::fast_io::details::has_reallocate_aligned_zero_impl || ::fast_io::details::has_reallocate_aligned_zero_at_least_impl); +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline - void * - reallocate_aligned_zero(void *p, ::std::size_t alignment, ::std::size_t n) noexcept + void * + reallocate_aligned_zero(void *p, ::std::size_t alignment, ::std::size_t n) noexcept requires(!has_status && has_reallocate_aligned_zero) { if constexpr (::fast_io::details::has_reallocate_aligned_zero_impl) @@ -660,11 +701,18 @@ class generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline - void * - reallocate_aligned_n(void *p, ::std::size_t oldn, ::std::size_t alignment, ::std::size_t n) noexcept + void * + reallocate_aligned_n(void *p, ::std::size_t oldn, ::std::size_t alignment, ::std::size_t n) noexcept requires(!has_status) { + if (p != nullptr && oldn == n) + { + return p; + } if constexpr (::fast_io::details::has_reallocate_aligned_n_impl) { return allocator_type::reallocate_aligned_n(p, oldn, alignment, n); @@ -710,24 +758,34 @@ class generic_allocator_adapter } } auto newptr{::fast_io::details::allocator_pointer_aligned_impl(alignment, n)}; - if (p != nullptr && n) + if (p != nullptr) { - if (oldn < n) + if (n) { - n = oldn; + if (oldn < n) + { + n = oldn; + } + ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); } - ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); generic_allocator_adapter::deallocate_aligned_n(p, alignment, oldn); } return newptr; } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline - void * - reallocate_aligned_zero_n(void *p, ::std::size_t oldn, ::std::size_t alignment, ::std::size_t n) noexcept + void * + reallocate_aligned_zero_n(void *p, ::std::size_t oldn, ::std::size_t alignment, ::std::size_t n) noexcept requires(!has_status) { + if (p != nullptr && oldn == n) + { + return p; + } if constexpr (::fast_io::details::has_reallocate_aligned_zero_n_impl) { return allocator_type::reallocate_aligned_zero_n(p, oldn, alignment, n); @@ -753,7 +811,7 @@ class generic_allocator_adapter ::fast_io::details::has_reallocate_aligned_zero_at_least_impl)); static inline ::fast_io::allocation_least_result - reallocate_at_least(void *p, ::std::size_t n) noexcept + reallocate_at_least(void *p, ::std::size_t n) noexcept requires(!has_status && has_reallocate) { if constexpr (::fast_io::details::has_reallocate_at_least_impl) @@ -793,10 +851,10 @@ class generic_allocator_adapter static inline constexpr bool has_native_reallocate_zero_at_least = (has_reallocate_zero && (::fast_io::details::has_reallocate_zero_at_least_impl || ::fast_io::details::has_reallocate_aligned_zero_at_least_impl)); - + static inline ::fast_io::allocation_least_result - reallocate_zero_at_least(void *p, ::std::size_t n) noexcept + reallocate_zero_at_least(void *p, ::std::size_t n) noexcept requires(!has_status && has_reallocate) { if constexpr (::fast_io::details::has_reallocate_zero_at_least_impl) @@ -819,7 +877,7 @@ class generic_allocator_adapter static inline ::fast_io::allocation_least_result - reallocate_n_at_least(void *p, ::std::size_t oldn, ::std::size_t n) noexcept + reallocate_n_at_least(void *p, ::std::size_t oldn, ::std::size_t n) noexcept requires(!has_status) { if constexpr (::fast_io::details::has_reallocate_n_at_least_impl) @@ -887,25 +945,28 @@ class generic_allocator_adapter return {allocator_type::reallocate_aligned_zero(p, default_alignment, n), n}; } else - { - auto newres{generic_allocator_adapter::allocate_at_least(n)}; - auto newptr{newres.ptr}; - if (p != nullptr && n) { - if (oldn < n) + auto newres{generic_allocator_adapter::allocate_at_least(n)}; + auto newptr{newres.ptr}; + if (p != nullptr) { - n = oldn; + if (n) + { + if (oldn < n) + { + n = oldn; + } + ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); + } + generic_allocator_adapter::deallocate_n(p, oldn); } - ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); - generic_allocator_adapter::deallocate_n(p, oldn); + return newres; } - return newres; - } } static inline ::fast_io::allocation_least_result - reallocate_zero_n_at_least(void *p, ::std::size_t oldn, ::std::size_t n) noexcept + reallocate_zero_n_at_least(void *p, ::std::size_t oldn, ::std::size_t n) noexcept requires(!has_status) { if constexpr (::fast_io::details::has_reallocate_zero_n_at_least_impl) @@ -1035,13 +1096,16 @@ class generic_allocator_adapter } auto newres{::fast_io::details::allocator_pointer_aligned_at_least_impl(alignment, n)}; auto newptr{newres.ptr}; - if (p != nullptr && n) + if (p != nullptr) { - if (oldn < n) + if (n) { - n = oldn; + if (oldn < n) + { + n = oldn; + } + ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); } - ::fast_io::freestanding::nonoverlapped_bytes_copy_n(reinterpret_cast<::std::byte const *>(p), n, reinterpret_cast<::std::byte *>(newptr)); generic_allocator_adapter::deallocate_aligned_n(p, alignment, oldn); } return newres; @@ -1140,6 +1204,9 @@ class typed_generic_allocator_adapter using allocator_adaptor = alloc; static inline constexpr bool has_status{allocator_adaptor::has_status}; using handle_type = typename allocator_adaptor::handle_type; +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if __cpp_constexpr_dynamic_alloc >= 201907L constexpr @@ -1202,6 +1269,9 @@ class typed_generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if (__cpp_if_consteval >= 202106L || __cpp_lib_is_constant_evaluated >= 201811L) && \ __cpp_constexpr_dynamic_alloc >= 201907L @@ -1260,6 +1330,9 @@ class typed_generic_allocator_adapter static inline constexpr bool has_reallocate = allocator_adaptor::has_reallocate; +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if (__cpp_if_consteval >= 202106L || __cpp_lib_is_constant_evaluated >= 201811L) && \ __cpp_constexpr_dynamic_alloc >= 201907L @@ -1311,6 +1384,9 @@ class typed_generic_allocator_adapter } static inline constexpr bool has_reallocate_zero = allocator_adaptor::has_reallocate_zero; +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if (__cpp_if_consteval >= 202106L || __cpp_lib_is_constant_evaluated >= 201811L) && \ __cpp_constexpr_dynamic_alloc >= 201907L @@ -1361,6 +1437,9 @@ class typed_generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if (__cpp_if_consteval >= 202106L || __cpp_lib_is_constant_evaluated >= 201811L) && \ __cpp_constexpr_dynamic_alloc >= 201907L @@ -1411,6 +1490,9 @@ class typed_generic_allocator_adapter } } +#if __has_cpp_attribute(__gnu__::__returns_nonnull__) + [[__gnu__::__returns_nonnull__]] +#endif static inline #if (__cpp_if_consteval >= 202106L || __cpp_lib_is_constant_evaluated >= 201811L) && \ __cpp_constexpr_dynamic_alloc >= 201907L @@ -1755,7 +1837,7 @@ namespace details template inline constexpr void *allocator_pointer_aligned_impl(::std::size_t alignment, ::std::size_t n) noexcept -{ +{ static_assert(::fast_io::generic_allocator_adapter::has_native_allocate); constexpr ::std::size_t defaultalignment{::fast_io::details::calculate_default_alignment()}; @@ -1782,7 +1864,7 @@ inline constexpr void *allocator_pointer_aligned_impl(::std::size_t alignment, : template inline constexpr ::fast_io::allocation_least_result allocator_pointer_aligned_at_least_impl(::std::size_t alignment, ::std::size_t n) noexcept -{ +{ static_assert(::fast_io::generic_allocator_adapter::has_native_allocate); constexpr ::std::size_t defaultalignment{::fast_io::details::calculate_default_alignment()}; diff --git a/tests/0016.address_sanitizer/reallocate_n.cc b/tests/0016.address_sanitizer/reallocate_n.cc new file mode 100644 index 000000000..9320543c4 --- /dev/null +++ b/tests/0016.address_sanitizer/reallocate_n.cc @@ -0,0 +1,80 @@ +#include + +#include +#include + +#include +#include +#include + +namespace +{ + +struct allocate_only_allocator +{ + static inline void *allocate(::std::size_t n) noexcept + { + return ::fast_io::c_malloc_allocator::allocate(n); + } + + static inline void deallocate_n(void *p, ::std::size_t) noexcept + { + ::fast_io::c_malloc_allocator::deallocate(p); + } +}; + +} // namespace + +int main() +{ + using adapter = ::fast_io::generic_allocator_adapter; + + // Trigger fallback path for allocators that have allocate but no reallocate. + // Under ASan/LSan this should detect leaks if old blocks are not freed. + + // Case 1: grow and verify content is preserved. + constexpr ::std::size_t oldn1{16}; + constexpr ::std::size_t newn1{32}; + void *p1 = adapter::allocate(oldn1); + auto *b1 = static_cast(p1); + for (::std::size_t i{}; i != oldn1; ++i) + { + b1[i] = static_cast(i); + } + p1 = adapter::reallocate_n(p1, oldn1, newn1); + b1 = static_cast(p1); + for (::std::size_t i{}; i != oldn1; ++i) + { + assert(b1[i] == static_cast(i)); + } + adapter::deallocate_n(p1, newn1); + + // Case 1b: same-size reallocate returns original pointer. + constexpr ::std::size_t samen1{24}; + void *p1b = adapter::allocate(samen1); + auto *b1b = static_cast(p1b); + for (::std::size_t i{}; i != samen1; ++i) + { + b1b[i] = static_cast(0x5A ^ i); + } + void *p1b2 = adapter::reallocate_n(p1b, samen1, samen1); + assert(p1b2 == p1b); + b1b = static_cast(p1b2); + for (::std::size_t i{}; i != samen1; ++i) + { + assert(b1b[i] == static_cast(0x5A ^ i)); + } + adapter::deallocate_n(p1b2, samen1); + + // Case 2: shrink-to-zero request (fast_io allocators typically treat 0 as 1). + constexpr ::std::size_t oldn2{64}; + void *p2 = adapter::allocate(oldn2); + ::std::memset(p2, 0xAB, oldn2); + p2 = adapter::reallocate_n(p2, oldn2, 0); + assert(p2 != nullptr); + static_cast(p2)[0] = 0xCD; + adapter::deallocate_n(p2, 1); + + return 0; +} +