https://gcc.gnu.org/g:7793e34adfa05a4a9868647e0fba8f088100c142
commit r16-7910-g7793e34adfa05a4a9868647e0fba8f088100c142 Author: Tomasz Kamiński <[email protected]> Date: Wed Feb 25 12:15:08 2026 +0100 libstdc++: Remove UB in _Arg_value union alternative assignment The _Arg_value::_M_set method, initialized the union member, by assigning to reference to that member produced by _M_get(*this). However, per language rules, such assignment has undefined behavior, if alternative was not already active, same as for any object not within its lifetime. To address above, we modify _M_set to use placement new for the class types, and invoke _S_access with two arguments for all other types. The _S_access (rename of _S_get) is modified to assign the value of the second parameter (if provided) to the union member. Such direct assignments are treated specially in the language (see N5032 [class.union.general] p5), and will start lifetime of trivially default constructible alternative. libstdc++-v3/ChangeLog: * include/std/format (_Arg_value::_M_get): Rename to... (_Arg_value::_M_access): Modified to accept optional second parameter that is assigned to value. (_Arg_value::_M_get): Handle rename. (_Arg_value::_M_set): Use construct_at for basic_string_view, handle, and two-argument _S_access for other types. Reviewed-by: Jonathan Wakely <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Signed-off-by: Ivan Lazaric <[email protected]> Co-authored-by: Ivan Lazaric <[email protected]> Diff: --- libstdc++-v3/include/std/format | 62 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index b014936a21ea..786edbe29b20 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -4212,67 +4212,70 @@ namespace __format { _S_get<_Tp>() = __val; } #endif - template<typename _Tp, typename _Self> + // Returns reference to the _Arg_value member with the type _Tp. + // Value of second argument (if provided), is assigned to that member. + template<typename _Tp, typename _Self, typename... _Value> [[__gnu__::__always_inline__]] static auto& - _S_get(_Self& __u) noexcept + _S_access(_Self& __u, _Value... __value) noexcept { + static_assert(sizeof...(_Value) <= 1); if constexpr (is_same_v<_Tp, bool>) - return __u._M_bool; + return (__u._M_bool = ... = __value); else if constexpr (is_same_v<_Tp, _CharT>) - return __u._M_c; + return (__u._M_c = ... = __value); else if constexpr (is_same_v<_Tp, int>) - return __u._M_i; + return (__u._M_i = ... = __value); else if constexpr (is_same_v<_Tp, unsigned>) - return __u._M_u; + return (__u._M_u = ... = __value); else if constexpr (is_same_v<_Tp, long long>) - return __u._M_ll; + return (__u._M_ll = ... = __value); else if constexpr (is_same_v<_Tp, unsigned long long>) - return __u._M_ull; + return (__u._M_ull = ... = __value); else if constexpr (is_same_v<_Tp, float>) - return __u._M_flt; + return (__u._M_flt = ... = __value); else if constexpr (is_same_v<_Tp, double>) - return __u._M_dbl; + return (__u._M_dbl = ... = __value); #ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT else if constexpr (is_same_v<_Tp, long double>) - return __u._M_ldbl; + return (__u._M_ldbl = ... = __value); #else else if constexpr (is_same_v<_Tp, __ibm128>) - return __u._M_ibm128; + return (__u._M_ibm128 = ... = __value); else if constexpr (is_same_v<_Tp, __ieee128>) - return __u._M_ieee128; + return (__u._M_ieee128 = ... = __value); #endif #ifdef __SIZEOF_FLOAT128__ else if constexpr (is_same_v<_Tp, __float128>) - return __u._M_float128; + return (__u._M_float128 = ... = __value); #endif else if constexpr (is_same_v<_Tp, const _CharT*>) - return __u._M_str; + return (__u._M_str = ... = __value); else if constexpr (is_same_v<_Tp, basic_string_view<_CharT>>) - return __u._M_sv; + return (__u._M_sv = ... = __value); else if constexpr (is_same_v<_Tp, const void*>) - return __u._M_ptr; + return (__u._M_ptr = ... = __value); #ifdef __SIZEOF_INT128__ else if constexpr (is_same_v<_Tp, __int128>) - return __u._M_i128; + return (__u._M_i128 = ... = __value); else if constexpr (is_same_v<_Tp, unsigned __int128>) - return __u._M_u128; + return (__u._M_u128 = ... = __value); #endif #ifdef __BFLT16_DIG__ else if constexpr (is_same_v<_Tp, __bflt16_t>) - return __u._M_bf16; + return (__u._M_bf16 = ... = __value); #endif #ifdef __FLT16_DIG__ else if constexpr (is_same_v<_Tp, _Float16>) - return __u._M_f16; + return (__u._M_f16 = ... = __value); #endif #ifdef __FLT32_DIG__ else if constexpr (is_same_v<_Tp, _Float32>) - return __u._M_f32; + return (__u._M_f32 = ... = __value); #endif #ifdef __FLT64_DIG__ else if constexpr (is_same_v<_Tp, _Float64>) - return __u._M_f64; + return (__u._M_f64 = ... = __value); #endif else if constexpr (is_same_v<_Tp, handle>) return __u._M_handle; @@ -4283,23 +4286,28 @@ namespace __format [[__gnu__::__always_inline__]] auto& _M_get() noexcept - { return _S_get<_Tp>(*this); } + { return _S_access<_Tp>(*this); } template<typename _Tp> [[__gnu__::__always_inline__]] const auto& _M_get() const noexcept - { return _S_get<_Tp>(*this); } + { return _S_access<_Tp>(*this); } template<typename _Tp> [[__gnu__::__always_inline__]] void _M_set(_Tp __v) noexcept { - if constexpr (is_same_v<_Tp, handle>) + // Explicitly construct types without trivial default constructor. + if constexpr (is_same_v<_Tp, basic_string_view<_CharT>>) + std::construct_at(&_M_sv, __v); + else if constexpr (is_same_v<_Tp, handle>) std::construct_at(&_M_handle, __v); else - _S_get<_Tp>(*this) = __v; + // Builtin types are trivially default constructible, and assignment + // changes active member per N5032 [class.union.general] p5. + _S_access<_Tp>(*this, __v); } };
