[libcxx-commits] [libcxx] [libc++] Avoid non-trivial assignment in `__uninitialized_allocator_copy_impl` (PR #196648)
Yuxuan Chen via libcxx-commits
libcxx-commits at lists.llvm.org
Sat May 9 13:25:34 PDT 2026
https://github.com/yuxuanchen1997 updated https://github.com/llvm/llvm-project/pull/196648
>From d11847434af874dd85bff9b8a81045912199101b Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <ych at meta.com>
Date: Fri, 8 May 2026 14:18:06 -0700
Subject: [PATCH] [libc++] Avoid non-trivial assignment in
`__uninitialized_allocator_copy_impl`
__uninitialized_allocator_copy_impl has an optimization that replaces allocator_traits::construct with std::copy for raw pointer ranges when the element type is trivially copy constructible and trivially copy assignable.
The copy-assignment trait only checks whether assignment from const T& is trivial. That is weaker than the expression used by std::copy, which evaluates *out = *in. If overload resolution selects a different non-trivial assignment operator for that expression, std::copy can call that operator on uninitialized storage.
Const-qualify the input pointers in the optimized overload instead. This makes the std::copy expression assign from const T&, matching the existing is_trivially_copy_assignable check, preserving the optimized path when that assignment is trivial, and falling back to placement construction otherwise.
Add a regression test with a type whose defaulted copy assignment is trivial but whose templated assignment operator is selected for non-const lvalue sources.
Tested with:
build/bin/llvm-lit -q build/runtimes/runtimes-bins/libcxx/test --filter='uninitialized_allocator_copy(\\.pass|_template_op_assign)'
---
.../__memory/uninitialized_algorithms.h | 2 +-
...allocator_copy_template_op_assign.pass.cpp | 77 +++++++++++++++++++
2 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 9182db4b412e3..2170e8a6fc924 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -491,7 +491,7 @@ template <class _Alloc,
__allocator_has_trivial_copy_construct_v<_Alloc, _In>,
int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Out*
-__uninitialized_allocator_copy_impl(_Alloc&, _In* __first1, _In* __last1, _Out* __first2) {
+__uninitialized_allocator_copy_impl(_Alloc&, const _In* __first1, const _In* __last1, _Out* __first2) {
if (__libcpp_is_constant_evaluated()) {
while (__first1 != __last1) {
std::__construct_at(std::__to_address(__first2), *__first1);
diff --git a/libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp b/libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp
new file mode 100644
index 0000000000000..9e9fb4b24fc23
--- /dev/null
+++ b/libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+class Optional {
+public:
+ Optional() = default;
+ Optional(const Optional&) = default;
+ Optional& operator=(const Optional&) = default;
+ Optional(Optional&&) {}
+
+ template <class Arg>
+ Optional& operator=(Arg&& rhs) {
+ assert(value_ != Poison);
+ value_ = rhs.value_;
+ return *this;
+ }
+
+ bool hasValue() const { return value_ == HasValue; }
+
+private:
+ static constexpr std::uint32_t Poison = 0xBEBEBEBE;
+ static constexpr std::uint32_t NoValue = 0;
+ static constexpr std::uint32_t HasValue = 1;
+
+ std::uint32_t value_ = NoValue;
+};
+
+static_assert(std::is_trivially_copy_constructible<Optional>::value, "");
+static_assert(std::is_trivially_copy_assignable<Optional>::value, "");
+static_assert(!std::is_trivially_assignable<Optional&, Optional&>::value, "");
+
+template <class T>
+struct PoisoningAllocator {
+ using value_type = T;
+ PoisoningAllocator() = default;
+ template <class U>
+ PoisoningAllocator(const PoisoningAllocator<U>&) noexcept {}
+ T* allocate(std::size_t n) {
+ T* p = alloc_.allocate(n);
+ std::memset(static_cast<void*>(p), 0xBE, n * sizeof(T));
+ return p;
+ }
+ void deallocate(T* p, std::size_t n) noexcept { alloc_.deallocate(p, n); }
+
+ template <class U>
+ friend bool operator==(const PoisoningAllocator&, const PoisoningAllocator<U>&) noexcept {
+ return true;
+ }
+ template <class U>
+ friend bool operator!=(const PoisoningAllocator&, const PoisoningAllocator<U>&) noexcept {
+ return false;
+ }
+
+ std::allocator<T> alloc_;
+};
+
+int main(int, char**) {
+ std::vector<Optional, PoisoningAllocator<Optional>> src(1);
+ auto dst = src;
+ assert(!dst[0].hasValue());
+ return 0;
+}
More information about the libcxx-commits
mailing list