[libcxx-commits] [libcxx] [libc++] Require the exact assignment expression to be trivial in `__uninitialized_allocator_copy_impl` (PR #196648)

Yuxuan Chen via libcxx-commits libcxx-commits at lists.llvm.org
Sat May 9 08:30:22 PDT 2026


https://github.com/yuxuanchen1997 updated https://github.com/llvm/llvm-project/pull/196648

>From 3a7d4301e8b481f940c6eba85e5908bc9ce650c3 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++] Require the exact assignment expression to be
 trivial 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.

Check is_trivially_assignable<_Out&, _In&> instead. This matches the assignment expression used by std::copy, preserves the optimized path when that assignment is actually trivial, and falls 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:
~/llvm-project/build-libcxx-fresh/bin/llvm-lit ~/llvm-project/libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp ~/llvm-project/libcxx/test/libcxx/memory/uninitialized_allocator_copy.pass.cpp -q
---
 .../__memory/uninitialized_algorithms.h       |  2 +-
 ...allocator_copy_template_op_assign.pass.cpp | 75 +++++++++++++++++++
 2 files changed, 76 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..2c90a9901dea0 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -486,7 +486,7 @@ inline const bool __allocator_has_trivial_copy_construct_v<allocator<_Type>, _Ty
 template <class _Alloc,
           class _In,
           class _Out,
-          __enable_if_t<is_trivially_copy_constructible<_In>::value && is_trivially_copy_assignable<_In>::value &&
+          __enable_if_t<is_trivially_copy_constructible<_In>::value && is_trivially_assignable<_Out&, _In&>::value &&
                             is_same<__remove_const_t<_In>, __remove_const_t<_Out> >::value &&
                             __allocator_has_trivial_copy_construct_v<_Alloc, _In>,
                         int> = 0>
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..2be149410fac9
--- /dev/null
+++ b/libcxx/test/libcxx/memory/uninitialized_allocator_copy_template_op_assign.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <new>
+#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) {
+    void* p = ::operator new(n * sizeof(T));
+    std::memset(p, 0xBE, n * sizeof(T));
+    return static_cast<T*>(p);
+  }
+  void deallocate(T* p, std::size_t) noexcept { ::operator delete(p); }
+};
+
+template <class T, class U>
+bool operator==(const PoisoningAllocator<T>&, const PoisoningAllocator<U>&) noexcept {
+  return true;
+}
+template <class T, class U>
+bool operator!=(const PoisoningAllocator<T>&, const PoisoningAllocator<U>&) noexcept {
+  return false;
+}
+
+int main(int, char**) {
+  std::vector<Optional, PoisoningAllocator<Optional>> src(1);
+  auto dst = src;
+  (void)dst[0].hasValue();
+  return 0;
+}



More information about the libcxx-commits mailing list