Pushed to trunk.

On Tue, 7 May 2024 at 15:04, Jonathan Wakely <jwak...@redhat.com> wrote:
>
> Tested x86_64-linux. This seems "obviously correct", and I'd like to
> push it. The current code definitely has a data race, i.e. undefined
> behaviour.
>
> -- >8 --
>
> The lazy caching in std::basic_ios::fill() updates a mutable member
> without synchronization, which can cause a data race if two threads both
> call fill() on the same stream object when _M_fill_init is false.
>
> To avoid this we can just cache the _M_fill member and set _M_fill_init
> early in std::basic_ios::init, instead of doing it lazily. As explained
> by the comment in init, there's a good reason for doing it lazily. When
> char_type is neither char nor wchar_t, the locale might not have a
> std::ctype<char_type>, so getting the fill character would throw an
> exception. The current lazy init allows using unformatted I/O with such
> a stream, because the fill character is never needed and so it doesn't
> matter if the locale doesn't have a ctype<char_type> facet. We can
> maintain this property by only setting the fill character in
> std::basic_ios::init if the ctype facet is present at that time. If
> fill() is called later and the fill character wasn't set by init, we can
> get it from the stream's current locale at the point when fill() is
> called (and not try to cache it without synchronization).
>
> This causes a change in behaviour for the following program:
>
>   std::ostringstream out;
>   out.imbue(loc);
>   auto fill = out.fill();
>
> Previously the fill character would have been set when fill() is called,
> and so would have used the new locale. This commit changes it so that
> the fill character is set on construction and isn't affected by the new
> locale being imbued later. This new behaviour seems to be what the
> standard requires, and matches MSVC.
>
> The new 27_io/basic_ios/fill/char/fill.cc test verifies that it's still
> possible to use a std::basic_ios without the ctype<char_type> facet
> being present at construction.
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/77704
>         * include/bits/basic_ios.h (basic_ios::fill()): Do not modify
>         _M_fill and _M_fill_init in a const member function.
>         (basic_ios::fill(char_type)): Use _M_fill directly instead of
>         calling fill(). Set _M_fill_init to true.
>         * include/bits/basic_ios.tcc (basic_ios::init): Set _M_fill and
>         _M_fill_init here instead.
>         * testsuite/27_io/basic_ios/fill/char/1.cc: New test.
>         * testsuite/27_io/basic_ios/fill/wchar_t/1.cc: New test.
> ---
>  libstdc++-v3/include/bits/basic_ios.h         | 10 +--
>  libstdc++-v3/include/bits/basic_ios.tcc       | 15 +++-
>  .../testsuite/27_io/basic_ios/fill/char/1.cc  | 78 +++++++++++++++++++
>  .../27_io/basic_ios/fill/wchar_t/1.cc         | 55 +++++++++++++
>  4 files changed, 148 insertions(+), 10 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/27_io/basic_ios/fill/char/1.cc
>  create mode 100644 libstdc++-v3/testsuite/27_io/basic_ios/fill/wchar_t/1.cc
>
> diff --git a/libstdc++-v3/include/bits/basic_ios.h 
> b/libstdc++-v3/include/bits/basic_ios.h
> index 258e6042b8f..bc3be4d2e37 100644
> --- a/libstdc++-v3/include/bits/basic_ios.h
> +++ b/libstdc++-v3/include/bits/basic_ios.h
> @@ -373,11 +373,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        char_type
>        fill() const
>        {
> -       if (!_M_fill_init)
> -         {
> -           _M_fill = this->widen(' ');
> -           _M_fill_init = true;
> -         }
> +       if (__builtin_expect(!_M_fill_init, false))
> +         return this->widen(' ');
>         return _M_fill;
>        }
>
> @@ -393,8 +390,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        char_type
>        fill(char_type __ch)
>        {
> -       char_type __old = this->fill();
> +       char_type __old = _M_fill;
>         _M_fill = __ch;
> +       _M_fill_init = true;
>         return __old;
>        }
>
> diff --git a/libstdc++-v3/include/bits/basic_ios.tcc 
> b/libstdc++-v3/include/bits/basic_ios.tcc
> index a9313736e32..0197bdf8f67 100644
> --- a/libstdc++-v3/include/bits/basic_ios.tcc
> +++ b/libstdc++-v3/include/bits/basic_ios.tcc
> @@ -138,13 +138,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        // return without throwing an exception. Unfortunately,
>        // ctype<char_type> is not necessarily a required facet, so
>        // streams with char_type != [char, wchar_t] will not have it by
> -      // default. Because of this, the correct value for _M_fill is
> -      // constructed on the first call of fill(). That way,
> +      // default. If the ctype<char_type> facet is available now,
> +      // _M_fill is set here, but otherwise no fill character will be
> +      // cached and a call to fill() will check for the facet again later
> +      // (and will throw if the facet is still not present). This way
>        // unformatted input and output with non-required basic_ios
>        // instantiations is possible even without imbuing the expected
>        // ctype<char_type> facet.
> -      _M_fill = _CharT();
> -      _M_fill_init = false;
> +      if (_M_ctype)
> +       {
> +         _M_fill = _M_ctype->widen(' ');
> +         _M_fill_init = true;
> +       }
> +      else
> +       _M_fill_init = false;
>
>        _M_tie = 0;
>        _M_exception = goodbit;
> diff --git a/libstdc++-v3/testsuite/27_io/basic_ios/fill/char/1.cc 
> b/libstdc++-v3/testsuite/27_io/basic_ios/fill/char/1.cc
> new file mode 100644
> index 00000000000..d5747c7507f
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/27_io/basic_ios/fill/char/1.cc
> @@ -0,0 +1,78 @@
> +// { dg-do run }
> +
> +#include <ios>
> +#include <locale>
> +#include <streambuf>
> +#include <testsuite_hooks.h>
> +
> +typedef char C;
> +
> +struct tabby_mctype : std::ctype<C>
> +{
> +  C do_widen(char c) const { return c == ' ' ? '\t' : c; }
> +
> +  const char*
> +  do_widen(const char* lo, const char* hi, C* to) const
> +  {
> +    while (lo != hi)
> +      *to++ = do_widen(*lo++);
> +    return hi;
> +  }
> +};
> +
> +void
> +test01()
> +{
> +  std::basic_ios<C> out(0);
> +  std::locale loc(std::locale(), new tabby_mctype);
> +  out.imbue(loc);
> +  VERIFY( out.fill() == ' ' ); // Imbuing a new locale doesn't affect fill().
> +  out.fill('*');
> +  VERIFY( out.fill() == '*' ); // This will be cached now.
> +  out.imbue(std::locale());
> +  VERIFY( out.fill() == '*' ); // Imbuing a new locale doesn't affect fill().
> +}
> +
> +void
> +test02()
> +{
> +  std::locale loc(std::locale(), new tabby_mctype);
> +  std::locale::global(loc);
> +  std::basic_ios<C> out(0);
> +  VERIFY( out.fill() == '\t' );
> +  out.imbue(std::locale::classic());
> +  VERIFY( out.fill() == '\t' ); // Imbuing a new locale doesn't affect 
> fill().
> +  out.fill('*');
> +  VERIFY( out.fill() == '*' );  // This will be cached now.
> +  out.imbue(std::locale());
> +  VERIFY( out.fill() == '*' );  // Imbuing a new locale doesn't affect 
> fill().
> +}
> +
> +void
> +test03()
> +{
> +  // This function tests a libstdc++ extension: if no ctype<char_type> facet
> +  // is present when the stream is initialized, a fill character will not be
> +  // cached. Calling fill() will obtain a fill character from the locale each
> +  // time it's called.
> +  typedef signed char C2;
> +  std::basic_ios<C2> out(0);
> +#if __cpp_exceptions
> +  try {
> +    (void) out.fill(); // No ctype<signed char> in the locale.
> +    VERIFY( false );
> +  } catch (...) {
> +  }
> +#endif
> +  out.fill('*');
> +  VERIFY( out.fill() == '*' ); // This will be cached now.
> +  out.imbue(std::locale());
> +  VERIFY( out.fill() == '*' ); // Imbuing a new locale doesn't affect fill().
> +}
> +
> +int main()
> +{
> +  test01();
> +  test02();
> +  test03();
> +}
> diff --git a/libstdc++-v3/testsuite/27_io/basic_ios/fill/wchar_t/1.cc 
> b/libstdc++-v3/testsuite/27_io/basic_ios/fill/wchar_t/1.cc
> new file mode 100644
> index 00000000000..2d639a0844d
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/27_io/basic_ios/fill/wchar_t/1.cc
> @@ -0,0 +1,55 @@
> +// { dg-do run }
> +
> +#include <ios>
> +#include <locale>
> +#include <streambuf>
> +#include <testsuite_hooks.h>
> +
> +typedef wchar_t C;
> +
> +struct tabby_mctype : std::ctype<C>
> +{
> +  C do_widen(char c) const { return c == ' ' ? L'\t' : c; }
> +
> +  const char*
> +  do_widen(const char* lo, const char* hi, C* to) const
> +  {
> +    while (lo != hi)
> +      *to++ = do_widen(*lo++);
> +    return hi;
> +  }
> +};
> +
> +void
> +test01()
> +{
> +  std::basic_ios<C> out(0);
> +  std::locale loc(std::locale(), new tabby_mctype);
> +  out.imbue(loc);
> +  VERIFY( out.fill() == L' ' ); // Imbuing a new locale doesn't affect 
> fill().
> +  out.fill(L'*');
> +  VERIFY( out.fill() == L'*' ); // This will be cached now.
> +  out.imbue(std::locale());
> +  VERIFY( out.fill() == L'*' ); // Imbuing a new locale doesn't affect 
> fill().
> +}
> +
> +void
> +test02()
> +{
> +  std::locale loc(std::locale(), new tabby_mctype);
> +  std::locale::global(loc);
> +  std::basic_ios<C> out(0);
> +  VERIFY( out.fill() == L'\t' );
> +  out.imbue(std::locale::classic());
> +  VERIFY( out.fill() == L'\t' ); // Imbuing a new locale doesn't affect 
> fill().
> +  out.fill(L'*');
> +  VERIFY( out.fill() == L'*' );  // This will be cached now.
> +  out.imbue(std::locale());
> +  VERIFY( out.fill() == L'*' );  // Imbuing a new locale doesn't affect 
> fill().
> +}
> +
> +int main()
> +{
> +  test01();
> +  test02();
> +}
> --
> 2.44.0
>

Reply via email to