[libcxx-commits] [libcxx] [libc++] Avoid type-punning locale in ios_base (PR #193507)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Wed Apr 22 07:05:15 PDT 2026


https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/193507

While there is currently a comment that all members of `ios_base` must
be scalars, I see no reason that is a correct statement. However, the
only place where this is relevant is `locale __loc_`. This patch
replaces the current `void*` implementation with `union { locale __loc_ }`
to simplify `ios.cpp`.


>From 870ae43baa20ef5d52fe0c3aa060386ebb822e54 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Wed, 22 Apr 2026 12:42:59 +0200
Subject: [PATCH] [libc++] Avoid type-punning locale in ios_base

While there is currently a comment that all members of `ios_base` must
be scalars, I see no reason that is a correct statement. However, the
only place where this is relevant is `locale __loc_`. This patch
replaces the current `void*` implementation with `union { locale __loc_ }`
to simplify `ios.cpp`.
---
 libcxx/include/__locale |  3 +++
 libcxx/include/ios      |  7 +++---
 libcxx/src/ios.cpp      | 47 ++++++++++++++++-------------------------
 3 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/libcxx/include/__locale b/libcxx/include/__locale
index 5b1787451fb25..8b6b48ccd6b59 100644
--- a/libcxx/include/__locale
+++ b/libcxx/include/__locale
@@ -118,6 +118,9 @@ private:
 
   template <class>
   friend struct __no_destroy;
+
+  friend ios_base;
+
   _LIBCPP_HIDE_FROM_ABI explicit locale(__private_constructor_tag, __imp* __loc) : __locale_(__loc) {}
 
   void __install_ctor(const locale&, facet*, long);
diff --git a/libcxx/include/ios b/libcxx/include/ios
index 9cf0aa8998ed1..3c26d92822367 100644
--- a/libcxx/include/ios
+++ b/libcxx/include/ios
@@ -361,7 +361,7 @@ public:
   }
 
 protected:
-  _LIBCPP_HIDE_FROM_ABI ios_base() : __loc_(nullptr) {
+  _LIBCPP_HIDE_FROM_ABI ios_base() : __loc_(__private_constructor_tag(), nullptr) {
     // Purposefully does no initialization
     //
     // Except for the locale, this is a sentinel to avoid destroying
@@ -386,14 +386,15 @@ protected:
   _LIBCPP_HIDE_FROM_ABI void set_rdbuf(void* __sb) { __rdbuf_ = __sb; }
 
 private:
-  // All data members must be scalars
   fmtflags __fmtflags_;
   streamsize __precision_;
   streamsize __width_;
   iostate __rdstate_;
   iostate __exceptions_;
   void* __rdbuf_;
-  void* __loc_;
+  union {
+    locale __loc_;
+  };
   event_callback* __fn_;
   int* __index_;
   size_t __event_size_;
diff --git a/libcxx/src/ios.cpp b/libcxx/src/ios.cpp
index 077389eafd61b..5baff89a86410 100644
--- a/libcxx/src/ios.cpp
+++ b/libcxx/src/ios.cpp
@@ -102,18 +102,13 @@ void ios_base::__call_callbacks(event ev) {
 // locale
 
 locale ios_base::imbue(const locale& newloc) {
-  static_assert(sizeof(locale) == sizeof(__loc_), "");
-  locale& loc_storage = *reinterpret_cast<locale*>(&__loc_);
-  locale oldloc       = loc_storage;
-  loc_storage         = newloc;
+  locale loc = newloc;
+  std::swap(__loc_, loc);
   __call_callbacks(imbue_event);
-  return oldloc;
+  return loc;
 }
 
-locale ios_base::getloc() const {
-  const locale& loc_storage = *reinterpret_cast<const locale*>(&__loc_);
-  return loc_storage;
-}
+locale ios_base::getloc() const { return __loc_; }
 
 // xalloc
 #if _LIBCPP_HAS_C_ATOMIC_IMP && _LIBCPP_HAS_THREADS
@@ -201,11 +196,10 @@ void ios_base::register_callback(event_callback fn, int index) {
 ios_base::~ios_base() {
   // Avoid UB when not properly initialized. See ios_base::ios_base for
   // more information.
-  if (!__loc_)
+  if (!__loc_.__locale_)
     return;
   __call_callbacks(erase_event);
-  locale& loc_storage = *reinterpret_cast<locale*>(&__loc_);
-  loc_storage.~locale();
+  __loc_.~locale();
   free(__fn_);
   free(__index_);
   free(__iarray_);
@@ -277,12 +271,10 @@ void ios_base::copyfmt(const ios_base& rhs) {
       std::__throw_bad_alloc();
   }
   // Got everything we need.  Copy everything but __rdstate_, __rdbuf_ and __exceptions_
-  __fmtflags_           = rhs.__fmtflags_;
-  __precision_          = rhs.__precision_;
-  __width_              = rhs.__width_;
-  locale& lhs_loc       = *reinterpret_cast<locale*>(&__loc_);
-  const locale& rhs_loc = *reinterpret_cast<const locale*>(&rhs.__loc_);
-  lhs_loc               = rhs_loc;
+  __fmtflags_  = rhs.__fmtflags_;
+  __precision_ = rhs.__precision_;
+  __width_     = rhs.__width_;
+  __loc_       = rhs.__loc_;
   if (__event_cap_ < rhs.__event_size_) {
     free(__fn_);
     __fn_ = new_callbacks.release();
@@ -312,14 +304,13 @@ void ios_base::copyfmt(const ios_base& rhs) {
 
 void ios_base::move(ios_base& rhs) {
   // *this is uninitialized
-  __fmtflags_     = rhs.__fmtflags_;
-  __precision_    = rhs.__precision_;
-  __width_        = rhs.__width_;
-  __rdstate_      = rhs.__rdstate_;
-  __exceptions_   = rhs.__exceptions_;
-  __rdbuf_        = 0;
-  locale& rhs_loc = *reinterpret_cast<locale*>(&rhs.__loc_);
-  ::new (&__loc_) locale(rhs_loc);
+  __fmtflags_   = rhs.__fmtflags_;
+  __precision_  = rhs.__precision_;
+  __width_      = rhs.__width_;
+  __rdstate_    = rhs.__rdstate_;
+  __exceptions_ = rhs.__exceptions_;
+  __rdbuf_      = 0;
+  ::new (&__loc_) locale(rhs.__loc_);
   __fn_              = rhs.__fn_;
   rhs.__fn_          = 0;
   __index_           = rhs.__index_;
@@ -348,9 +339,7 @@ void ios_base::swap(ios_base& rhs) noexcept {
   std::swap(__width_, rhs.__width_);
   std::swap(__rdstate_, rhs.__rdstate_);
   std::swap(__exceptions_, rhs.__exceptions_);
-  locale& lhs_loc = *reinterpret_cast<locale*>(&__loc_);
-  locale& rhs_loc = *reinterpret_cast<locale*>(&rhs.__loc_);
-  std::swap(lhs_loc, rhs_loc);
+  std::swap(__loc_, rhs.__loc_);
   std::swap(__fn_, rhs.__fn_);
   std::swap(__index_, rhs.__index_);
   std::swap(__event_size_, rhs.__event_size_);



More information about the libcxx-commits mailing list