On platforms that fail the ptrtomemfn-cast-to-pfn hack, such as arm-*-vxworks*, time_get fails with %I and %p because the state is not preserved across do_get calls.
This patch introduces an alternate hack, that encodes the state in unused bits of struct tm before calling do_get, extracts them in do_get, does the processing, and encodes it back, so that get extracts it. The finalizer is adjusted for idempotence, because both do_get and get may call it. Regstrapped on x86_64-linux-gnu. Tested on arm-vxworks7 (gcc-12) and arm-eabi (trunk). Ok to install? for libstdc++-v3/ChangeLog PR libstdc++/77760 * include/bits/locale_facets_nonio.h (__time_get_state): Add _M_state_tm, _M_save_to and _M_restore_from. * include/bits/locale_facets_nonio.tcc (time_get::get): Drop do_get-overriding hack. Use state unconditionally, and encode it in tm around do_get. (time_get::do_get): Extract state from tm, and encode it back, around parsing and finalizing. * src/c++98/locale_facets.cc (__time_get_state::_M_finalize_state): Make tm_hour and tm_year idempotent. --- libstdc++-v3/include/bits/locale_facets_nonio.h | 80 +++++++++++++++++++++ libstdc++-v3/include/bits/locale_facets_nonio.tcc | 43 ++--------- libstdc++-v3/src/c++98/locale_facets.cc | 8 ++ 3 files changed, 93 insertions(+), 38 deletions(-) diff --git a/libstdc++-v3/include/bits/locale_facets_nonio.h b/libstdc++-v3/include/bits/locale_facets_nonio.h index 372cf0429501d..711bede158427 100644 --- a/libstdc++-v3/include/bits/locale_facets_nonio.h +++ b/libstdc++-v3/include/bits/locale_facets_nonio.h @@ -361,6 +361,86 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION void _M_finalize_state(tm* __tm); + private: + void + _M_state_tm(tm* __tm, bool __totm) + { + // Check we don't invade the in-range tm bits, even if int is + // 16-bits wide. +#define _M_min_shift_tm_sec 6 +#define _M_min_shift_tm_min 6 +#define _M_min_shift_tm_hour 5 +#define _M_min_shift_tm_mday 5 +#define _M_min_shift_tm_mon 4 +#define _M_min_shift_tm_year 16 // 14, but signed, so avoid it. +#define _M_min_shift_tm_wday 3 +#define _M_min_shift_tm_yday 9 +#define _M_min_shift_tm_isdst 1 + // Represent __STF in __WDT bits of __TMF up to the __MSB bit. + // In __MSB, 0 stands for the most significant bit of __TMF, + // 1 the bit next to it, and so on. +#define _M_time_get_state_bitfield_inout(__tmf, __msb, __wdt, __stf) \ + do \ + { \ + const unsigned __shift = (sizeof (__tm->__tmf) * __CHAR_BIT__ \ + - (__msb) - (__wdt)); \ + static char __attribute__ ((__unused__)) \ + __check_parms_##__tmf[(__msb) >= 0 && (__wdt) > 0 \ + && __shift >= (_M_min_shift_##__tmf \ + + (sizeof (__tm->__tmf) \ + * __CHAR_BIT__) - 16) \ + ? 1 : -1]; \ + const unsigned __mask = ((1 << (__wdt)) - 1) << __shift; \ + if (!__totm) \ + this->__stf = (__tm->__tmf & __mask) >> __shift; \ + __tm->__tmf &= ~__mask; \ + if (__totm) \ + __tm->__tmf |= ((unsigned)this->__stf << __shift) & __mask; \ + } \ + while (0) + + _M_time_get_state_bitfield_inout (tm_hour, 0, 1, _M_have_I); + _M_time_get_state_bitfield_inout (tm_wday, 0, 1, _M_have_wday); + _M_time_get_state_bitfield_inout (tm_yday, 0, 1, _M_have_yday); + _M_time_get_state_bitfield_inout (tm_mon, 0, 1, _M_have_mon); + _M_time_get_state_bitfield_inout (tm_mday, 0, 1, _M_have_mday); + _M_time_get_state_bitfield_inout (tm_yday, 1, 1, _M_have_uweek); + _M_time_get_state_bitfield_inout (tm_yday, 2, 1, _M_have_wweek); + _M_time_get_state_bitfield_inout (tm_isdst, 0, 1, _M_have_century); + _M_time_get_state_bitfield_inout (tm_hour, 1, 1, _M_is_pm); + _M_time_get_state_bitfield_inout (tm_isdst, 1, 1, _M_want_century); + _M_time_get_state_bitfield_inout (tm_yday, 3, 1, _M_want_xday); + // _M_pad1 + _M_time_get_state_bitfield_inout (tm_wday, 1, 6, _M_week_no); + // _M_pad2 + _M_time_get_state_bitfield_inout (tm_mon, 1, 8, _M_century); + // _M_pad3 + +#undef _M_min_shift_tm_hour +#undef _M_min_shift_tm_sec +#undef _M_min_shift_tm_min +#undef _M_min_shift_tm_hour +#undef _M_min_shift_tm_mday +#undef _M_min_shift_tm_mon +#undef _M_min_shift_tm_year +#undef _M_min_shift_tm_wday +#undef _M_min_shift_tm_yday +#undef _M_min_shift_tm_isdst +#undef _M_time_get_state_bitfield_inout + } + public: + // Encode *THIS into scratch bits of __TM. + void + _M_save_to(tm* __tm) { + _M_state_tm (__tm, true); + } + + // Decode and zero out scratch bits of __TM back into *THIS. + void + _M_restore_from(tm* __tm) { + _M_state_tm (__tm, false); + } + unsigned int _M_have_I : 1; unsigned int _M_have_wday : 1; unsigned int _M_have_yday : 1; diff --git a/libstdc++-v3/include/bits/locale_facets_nonio.tcc b/libstdc++-v3/include/bits/locale_facets_nonio.tcc index b19f442530011..47d243ce84dd3 100644 --- a/libstdc++-v3/include/bits/locale_facets_nonio.tcc +++ b/libstdc++-v3/include/bits/locale_facets_nonio.tcc @@ -1464,21 +1464,8 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11 const locale& __loc = __io._M_getloc(); ctype<_CharT> const& __ctype = use_facet<ctype<_CharT> >(__loc); __err = ios_base::goodbit; - bool __use_state = false; -#if __GNUC__ >= 5 && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpmf-conversions" - // Nasty hack. The C++ standard mandates that get invokes the do_get - // virtual method, but unfortunately at least without an ABI change - // for the facets we can't keep state across the different do_get - // calls. So e.g. if __fmt is "%p %I:%M:%S", we can't handle it - // properly, because we first handle the %p am/pm specifier and only - // later the 12-hour format specifier. - if ((void*)(this->*(&time_get::do_get)) == (void*)(&time_get::do_get)) - __use_state = true; -#pragma GCC diagnostic pop -#endif __time_get_state __state = __time_get_state(); + __state._M_save_to (__tm); while (__fmt != __fmtend && __err == ios_base::goodbit) { @@ -1510,26 +1497,8 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11 __err = ios_base::failbit; break; } - if (__use_state) - { - char_type __new_fmt[4]; - __new_fmt[0] = __fmt_start[0]; - __new_fmt[1] = __fmt_start[1]; - if (__mod) - { - __new_fmt[2] = __fmt_start[2]; - __new_fmt[3] = char_type(); - } - else - __new_fmt[2] = char_type(); - __s = _M_extract_via_format(__s, __end, __io, __err, __tm, - __new_fmt, __state); - if (__s == __end) - __err |= ios_base::eofbit; - } - else - __s = this->do_get(__s, __end, __io, __err, __tm, __format, - __mod); + __s = this->do_get(__s, __end, __io, __err, __tm, + __format, __mod); ++__fmt; } else if (__ctype.is(ctype_base::space, *__fmt)) @@ -1556,8 +1525,8 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11 break; } } - if (__use_state) - __state._M_finalize_state(__tm); + __state._M_restore_from (__tm); + __state._M_finalize_state(__tm); return __s; } @@ -1588,9 +1557,11 @@ _GLIBCXX_END_NAMESPACE_LDBL_OR_CXX11 } __time_get_state __state = __time_get_state(); + __state._M_restore_from (__tm); __beg = _M_extract_via_format(__beg, __end, __io, __err, __tm, __fmt, __state); __state._M_finalize_state(__tm); + __state._M_save_to (__tm); if (__beg == __end) __err |= ios_base::eofbit; return __beg; diff --git a/libstdc++-v3/src/c++98/locale_facets.cc b/libstdc++-v3/src/c++98/locale_facets.cc index c0bb7fd181d7f..28c34b5f3794a 100644 --- a/libstdc++-v3/src/c++98/locale_facets.cc +++ b/libstdc++-v3/src/c++98/locale_facets.cc @@ -181,12 +181,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __time_get_state:: _M_finalize_state(tm* tm) { - if (_M_have_I && _M_is_pm) + if (_M_have_I && _M_is_pm && (unsigned) tm->tm_hour < 12) tm->tm_hour += 12; if (_M_have_century) { if (_M_want_century) - tm->tm_year = tm->tm_year % 100; + { + tm->tm_year = tm->tm_year % 100; + if (tm->tm_year < 0) + tm->tm_year += 100; + } else tm->tm_year = 0; tm->tm_year += (_M_century - 19) * 100; -- Alexandre Oliva, happy hacker https://FSFLA.org/blogs/lxo/ Free Software Activist GNU Toolchain Engineer Disinformation flourishes because many people care deeply about injustice but very few check the facts. Ask me about <https://stallmansupport.org>