[v2] libstdc++: Gracefully handle unrecognized _Arg_t values in basic_format_arg.
Checks
Commit Message
Implementing features from later revision of the standard, may require
extending the set of types stored directly in _Arg_value (without
changing in size and aliment), and thus expanding the values in _Arg_t.
However, any such value would be unrecognized by the TUs compiled with
older release, and would lead to UB (call to __builtin_unreachable).
This patch addresses above by introducing _M_handle_unrecognized method,
that is called instead. As specializations of this method for all
context supported at runtime (format_context and wformat_context) are
exported from libstdc++.so, the newest version (supporting all _Arg_t
values introduced later) will be picked (this is ensured by not providing
template definition in header in such case).
In consequence, the implementation may return handle object referencing
new alternatives (such wrapping is already required to provide standard
compliant behavior of visit_format_arg).
As no new _Arg_t were introduced since GCC16, this method simply throws
format_error now, and contains appropriate comment.
Note that the above is not required for formatting when __do_vformat_to
is exported (Unicode literal encoding and C++20). It remains necessary
in remaining cases or when visit on basic_format_arg is called by user.
In consequence addition of new argument types stored directly does not
have negative impact on performance in most common case.
This patch also removes the __type parameter from the _M_visit and
_M_visit_user, and uses _M_type member instead. This prevents misuse,
as the provided type was always required to match _M_type.
libstdc++-v3/ChangeLog:
* config/abi/pre/gnu.ver (GLIBCXX_3.4.36): Export basic_format_arg
_M_handle_unrecognized method of specializations of
format_context and wformat_context.
* include/std/format (basic_format_arg::_M_handle_unrecognized):
Declare with explicit specializations for (w)format_context
if _GLIBCXX_EXTERN_TEMPLATE, and define otherwise.
(basic_format_arg::_M_visit): Remove __type parameter, and use
_M_type instead. Call _M_handle_unrecognized for unrecognized
_Arg_t values.
(basic_format_arg::_M_visit_user): Remove __type parameter,
and use _M_type instead. Adjust calls to _M_visit.
(basic_format_arg::visit, std::visit_format_arg)
(__format::__visit_format_arg): Remove _M_type argument from
_M_visit(_user) calls.
* src/c++20/format-inst.cc
(basic_format_arg::_M_handle_unrecognized): Define and export
explicit specializations for (w)format_context.
Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
---
v2 removes the defintion of _M_handle_unrecognized template
from the header, and moves it to source file if _GLIBCXX_EXTERN_TEMPLATE,
is defined. Add revelant comment to the source file and commit description.
Also addresses the other review comments.
Tested on x86_64-linux locally. OK for trunk?
libstdc++-v3/config/abi/pre/gnu.ver | 4 +++
libstdc++-v3/include/std/format | 52 +++++++++++++++++++--------
libstdc++-v3/src/c++20/format-inst.cc | 19 ++++++++++
3 files changed, 61 insertions(+), 14 deletions(-)
Comments
On Tue, 7 Jul 2026 at 12:43, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
>
> Implementing features from later revision of the standard, may require
> extending the set of types stored directly in _Arg_value (without
> changing in size and aliment), and thus expanding the values in _Arg_t.
> However, any such value would be unrecognized by the TUs compiled with
> older release, and would lead to UB (call to __builtin_unreachable).
>
> This patch addresses above by introducing _M_handle_unrecognized method,
> that is called instead. As specializations of this method for all
> context supported at runtime (format_context and wformat_context) are
> exported from libstdc++.so, the newest version (supporting all _Arg_t
> values introduced later) will be picked (this is ensured by not providing
> template definition in header in such case).
> In consequence, the implementation may return handle object referencing
> new alternatives (such wrapping is already required to provide standard
> compliant behavior of visit_format_arg).
> As no new _Arg_t were introduced since GCC16, this method simply throws
> format_error now, and contains appropriate comment.
>
> Note that the above is not required for formatting when __do_vformat_to
> is exported (Unicode literal encoding and C++20). It remains necessary
> in remaining cases or when visit on basic_format_arg is called by user.
> In consequence addition of new argument types stored directly does not
> have negative impact on performance in most common case.
>
> This patch also removes the __type parameter from the _M_visit and
> _M_visit_user, and uses _M_type member instead. This prevents misuse,
> as the provided type was always required to match _M_type.
>
> libstdc++-v3/ChangeLog:
>
> * config/abi/pre/gnu.ver (GLIBCXX_3.4.36): Export basic_format_arg
> _M_handle_unrecognized method of specializations of
> format_context and wformat_context.
> * include/std/format (basic_format_arg::_M_handle_unrecognized):
> Declare with explicit specializations for (w)format_context
> if _GLIBCXX_EXTERN_TEMPLATE, and define otherwise.
> (basic_format_arg::_M_visit): Remove __type parameter, and use
> _M_type instead. Call _M_handle_unrecognized for unrecognized
> _Arg_t values.
> (basic_format_arg::_M_visit_user): Remove __type parameter,
> and use _M_type instead. Adjust calls to _M_visit.
> (basic_format_arg::visit, std::visit_format_arg)
> (__format::__visit_format_arg): Remove _M_type argument from
> _M_visit(_user) calls.
> * src/c++20/format-inst.cc
> (basic_format_arg::_M_handle_unrecognized): Define and export
> explicit specializations for (w)format_context.
>
> Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> ---
> v2 removes the defintion of _M_handle_unrecognized template
> from the header, and moves it to source file if _GLIBCXX_EXTERN_TEMPLATE,
> is defined. Add revelant comment to the source file and commit description.
> Also addresses the other review comments.
>
> Tested on x86_64-linux locally. OK for trunk?
OK thanks
>
> libstdc++-v3/config/abi/pre/gnu.ver | 4 +++
> libstdc++-v3/include/std/format | 52 +++++++++++++++++++--------
> libstdc++-v3/src/c++20/format-inst.cc | 19 ++++++++++
> 3 files changed, 61 insertions(+), 14 deletions(-)
>
> diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver
> index 3a6afac8308..9e1bebb4f50 100644
> --- a/libstdc++-v3/config/abi/pre/gnu.ver
> +++ b/libstdc++-v3/config/abi/pre/gnu.ver
> @@ -2628,6 +2628,10 @@ GLIBCXX_3.4.36 {
>
> _ZNSt6chrono8__detail25__recent_leap_second_infoERNS_16leap_second_infoEj;
>
> + # basic_format_arg<format_context>::handle basic_format_arg<format_context>::_M_handle_unrecognized() const;
> + # basic_format_arg<wformat_context>::handle basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> + _ZNKSt16basic_format_argISt20basic_format_contextINSt8__format10_Sink_iterI[cw]EE[cw]EE22_M_handle_unrecognizedEv;
> +
> # basic_string::allocate_at_least
> _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_S_allocate_*;
> _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_M_create_*;
> diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
> index dd275b6b817..516af6f14c8 100644
> --- a/libstdc++-v3/include/std/format
> +++ b/libstdc++-v3/include/std/format
> @@ -4404,12 +4404,12 @@ namespace __format
> template<typename _Visitor>
> _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> visit(this basic_format_arg __arg, _Visitor&& __vis)
> - { return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type); }
> + { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
>
> template<typename _Res, typename _Visitor>
> _GLIBCXX_CONSTEXPR_FORMAT _Res
> visit(this basic_format_arg __arg, _Visitor&& __vis)
> - { return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type); }
> + { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
> #endif
>
> private:
> @@ -4622,13 +4622,17 @@ namespace __format
> friend consteval __format::_Arg_t
> __format::__to_arg_t_enum() noexcept;
>
> + [[__gnu__::__noinline__]]
> + handle
> + _M_handle_unrecognized() const;
> +
> template<typename _Visitor>
> _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> - _M_visit(_Visitor&& __vis, __format::_Arg_t __type)
> + _M_visit(_Visitor&& __vis)
> {
> - using namespace __format;
> - switch (__type)
> + switch (_M_type)
> {
> + using enum __format::_Arg_t;
> case _Arg_none:
> return std::forward<_Visitor>(__vis)(_M_val._M_none);
> case _Arg_bool:
> @@ -4693,13 +4697,17 @@ namespace __format
> return std::forward<_Visitor>(__vis)(_M_val._M_u128);
> #endif
> default:
> - __builtin_unreachable();
> + // Call exported definition of _M_handle_unrecognized from
> + // libstdc++.so, that should recognize new _Arg_t values and
> + // return basic_format_arg, containing a handle to that value.
> + handle __h = _M_handle_unrecognized();
> + return std::forward<_Visitor>(__vis)(__h);
> }
> }
>
> template<typename _Visitor>
> _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> - _M_visit_user(_Visitor&& __vis, __format::_Arg_t __type)
> + _M_visit_user(_Visitor&& __vis)
> {
> return _M_visit([&__vis]<typename _Tp>(_Tp& __val) -> decltype(auto)
> {
> @@ -4716,7 +4724,7 @@ namespace __format
> handle __h(__val);
> return std::forward<_Visitor>(__vis)(__h);
> }
> - }, __type);
> + });
> }
> };
>
> @@ -4724,9 +4732,7 @@ namespace __format
> _GLIBCXX26_DEPRECATED_SUGGEST("std::basic_format_arg::visit")
> inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg)
> - {
> - return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type);
> - }
> + { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
>
> /// @cond undocumented
> namespace __format
> @@ -4734,9 +4740,7 @@ namespace __format
> template<typename _Visitor, typename _Ctx>
> inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx> __arg)
> - {
> - return __arg._M_visit(std::forward<_Visitor>(__vis), __arg._M_type);
> - }
> + { return __arg._M_visit(std::forward<_Visitor>(__vis)); }
>
> struct _WidthPrecVisitor
> {
> @@ -5069,6 +5073,26 @@ namespace __format
> advance_to(iterator __it) { _M_out = std::move(__it); }
> };
>
> +#if _GLIBCXX_EXTERN_TEMPLATE
> + // The defintion _M_handle_unrecognized is placed in format-inst.cc
> + // source file, to ensure that it will not be inlined by compiler.
> + extern template basic_format_arg<format_context>::handle
> + basic_format_arg<format_context>::_M_handle_unrecognized() const;
> +# ifdef _GLIBCXX_USE_WCHAR_T
> + extern template basic_format_arg<wformat_context>::handle
> + basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> +# endif
> +#else
> + template<typename _Context>
> + typename basic_format_arg<_Context>::handle
> + basic_format_arg<_Context>::_M_handle_unrecognized() const
> + {
> + // If _M_type corresponds to a new value of _Arg_t introduced after
> + // GCC 16, this function should return a handle that refers to the
> + // union member of _M_val corresponding to that _Arg_t value.
> + __throw_format_error("format error: unrecognized argument type");
> + }
> +#endif
>
> /// @cond undocumented
> namespace __format
> diff --git a/libstdc++-v3/src/c++20/format-inst.cc b/libstdc++-v3/src/c++20/format-inst.cc
> index 65a3f282732..fe15992485e 100644
> --- a/libstdc++-v3/src/c++20/format-inst.cc
> +++ b/libstdc++-v3/src/c++20/format-inst.cc
> @@ -27,6 +27,25 @@
> namespace std
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
> +
> + template<typename _Context>
> + typename basic_format_arg<_Context>::handle
> + basic_format_arg<_Context>::_M_handle_unrecognized() const
> + {
> + // If _M_type corresponds to a new value of _Arg_t introduced after GCC 16,
> + // this function should return a handle that refers to the union member of
> + // _M_val corresponding to that _Arg_t value.
> + __throw_format_error("format error: unrecognized argument type");
> + }
> +
> + template basic_format_arg<format_context>::handle
> + basic_format_arg<format_context>::_M_handle_unrecognized() const;
> +
> +# ifdef _GLIBCXX_USE_WCHAR_T
> + template basic_format_arg<wformat_context>::handle
> + basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> +# endif
> +
> namespace __format
> {
>
> --
> 2.54.0
>
@@ -2628,6 +2628,10 @@ GLIBCXX_3.4.36 {
_ZNSt6chrono8__detail25__recent_leap_second_infoERNS_16leap_second_infoEj;
+ # basic_format_arg<format_context>::handle basic_format_arg<format_context>::_M_handle_unrecognized() const;
+ # basic_format_arg<wformat_context>::handle basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
+ _ZNKSt16basic_format_argISt20basic_format_contextINSt8__format10_Sink_iterI[cw]EE[cw]EE22_M_handle_unrecognizedEv;
+
# basic_string::allocate_at_least
_ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_S_allocate_*;
_ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_M_create_*;
@@ -4404,12 +4404,12 @@ namespace __format
template<typename _Visitor>
_GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
visit(this basic_format_arg __arg, _Visitor&& __vis)
- { return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type); }
+ { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
template<typename _Res, typename _Visitor>
_GLIBCXX_CONSTEXPR_FORMAT _Res
visit(this basic_format_arg __arg, _Visitor&& __vis)
- { return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type); }
+ { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
#endif
private:
@@ -4622,13 +4622,17 @@ namespace __format
friend consteval __format::_Arg_t
__format::__to_arg_t_enum() noexcept;
+ [[__gnu__::__noinline__]]
+ handle
+ _M_handle_unrecognized() const;
+
template<typename _Visitor>
_GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
- _M_visit(_Visitor&& __vis, __format::_Arg_t __type)
+ _M_visit(_Visitor&& __vis)
{
- using namespace __format;
- switch (__type)
+ switch (_M_type)
{
+ using enum __format::_Arg_t;
case _Arg_none:
return std::forward<_Visitor>(__vis)(_M_val._M_none);
case _Arg_bool:
@@ -4693,13 +4697,17 @@ namespace __format
return std::forward<_Visitor>(__vis)(_M_val._M_u128);
#endif
default:
- __builtin_unreachable();
+ // Call exported definition of _M_handle_unrecognized from
+ // libstdc++.so, that should recognize new _Arg_t values and
+ // return basic_format_arg, containing a handle to that value.
+ handle __h = _M_handle_unrecognized();
+ return std::forward<_Visitor>(__vis)(__h);
}
}
template<typename _Visitor>
_GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
- _M_visit_user(_Visitor&& __vis, __format::_Arg_t __type)
+ _M_visit_user(_Visitor&& __vis)
{
return _M_visit([&__vis]<typename _Tp>(_Tp& __val) -> decltype(auto)
{
@@ -4716,7 +4724,7 @@ namespace __format
handle __h(__val);
return std::forward<_Visitor>(__vis)(__h);
}
- }, __type);
+ });
}
};
@@ -4724,9 +4732,7 @@ namespace __format
_GLIBCXX26_DEPRECATED_SUGGEST("std::basic_format_arg::visit")
inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg)
- {
- return __arg._M_visit_user(std::forward<_Visitor>(__vis), __arg._M_type);
- }
+ { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
/// @cond undocumented
namespace __format
@@ -4734,9 +4740,7 @@ namespace __format
template<typename _Visitor, typename _Ctx>
inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
__visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx> __arg)
- {
- return __arg._M_visit(std::forward<_Visitor>(__vis), __arg._M_type);
- }
+ { return __arg._M_visit(std::forward<_Visitor>(__vis)); }
struct _WidthPrecVisitor
{
@@ -5069,6 +5073,26 @@ namespace __format
advance_to(iterator __it) { _M_out = std::move(__it); }
};
+#if _GLIBCXX_EXTERN_TEMPLATE
+ // The defintion _M_handle_unrecognized is placed in format-inst.cc
+ // source file, to ensure that it will not be inlined by compiler.
+ extern template basic_format_arg<format_context>::handle
+ basic_format_arg<format_context>::_M_handle_unrecognized() const;
+# ifdef _GLIBCXX_USE_WCHAR_T
+ extern template basic_format_arg<wformat_context>::handle
+ basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
+# endif
+#else
+ template<typename _Context>
+ typename basic_format_arg<_Context>::handle
+ basic_format_arg<_Context>::_M_handle_unrecognized() const
+ {
+ // If _M_type corresponds to a new value of _Arg_t introduced after
+ // GCC 16, this function should return a handle that refers to the
+ // union member of _M_val corresponding to that _Arg_t value.
+ __throw_format_error("format error: unrecognized argument type");
+ }
+#endif
/// @cond undocumented
namespace __format
@@ -27,6 +27,25 @@
namespace std
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
+
+ template<typename _Context>
+ typename basic_format_arg<_Context>::handle
+ basic_format_arg<_Context>::_M_handle_unrecognized() const
+ {
+ // If _M_type corresponds to a new value of _Arg_t introduced after GCC 16,
+ // this function should return a handle that refers to the union member of
+ // _M_val corresponding to that _Arg_t value.
+ __throw_format_error("format error: unrecognized argument type");
+ }
+
+ template basic_format_arg<format_context>::handle
+ basic_format_arg<format_context>::_M_handle_unrecognized() const;
+
+# ifdef _GLIBCXX_USE_WCHAR_T
+ template basic_format_arg<wformat_context>::handle
+ basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
+# endif
+
namespace __format
{