Re: [PATCH v2 1/1] libstdc++: Fix SIGSEGV in std::print with a setvbuf'd FILE

Tomasz Kaminski <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel
Message-ID <CAKvuMXBCWayCF70=YGCLvV3GoQda_wV_n6rKVyAMbfJ8R=s0gA@mail.gmail.com>
On Sun, Aug 16, 2026 at 6:04 AM Anlai Lu <[email protected]> wrote:

> _File_sink::_M_write_buf() returns a span with a null data pointer when
> the FILE buffer was pre-allocated by setvbuf but never written (glibc
> keeps _IO_write_ptr as nullptr until the first write).  The empty()
> check in the constructor then fails to force buffer initialization and
> the format engine memcpys into nullptr.
>
> Return an empty span when the write pointer is null.
>
> libstdc++-v3/ChangeLog:
>
>         * include/bits/print.h (_File_sink::_File::_M_write_buf): Return
>         an empty span when _IO_write_ptr is null.
>         * testsuite/27_io/print/1.cc: Add test_print_setvbuf.
>
> Signed-off-by: Anlai Lu <[email protected]>
> ---
> Changes in v2: removed [[unlikely]]; VERIFY the setvbuf return value in
> the test.
>
>  libstdc++-v3/include/bits/print.h       |  5 +++++
>  libstdc++-v3/testsuite/27_io/print/1.cc | 17 +++++++++++++++++
>  2 files changed, 22 insertions(+)
>
> diff --git a/libstdc++-v3/include/bits/print.h
> b/libstdc++-v3/include/bits/print.h
> index 20724844726..7e023c79c55 100644
> --- a/libstdc++-v3/include/bits/print.h
> +++ b/libstdc++-v3/include/bits/print.h
> @@ -95,9 +95,14 @@ namespace __format
>        _File(_File&&) = delete;
>
>        // A span viewing the unused portion of the stream's output buffer.
> +      // Empty when the buffer is not yet initialized: after setvbuf
> +      // glibc pre-allocates the buffer but keeps _IO_write_ptr as
> +      // nullptr until the first write.
>        std::span<char>
>        _M_write_buf() noexcept
>
This function is called in the constructor, and then only after flush, but
the above
check is only needed for the constructor case.

I would preffer if we extract the overflow call, to separate
_M_init_write_buf function,
something like:
      std::span<char>
      _M_init_write_but()
      {
        // After setvbuf glibc pre-allocates the buffer but _IO_write_ptr
        // remains null until the first write.
        if (!_M_file->_IO_write_ptr || _M_write_buf().empty())
          if (::__overflow(__f, EOF) == EOF)
            {
              const int __err = errno;
              __throw_system_error(__err);
            }
        return _M_write_buf();
      }
And then call int in File_sinki cosntructor:
      if (!_M_file._M_unbuffered())
        // Allocate FILE's output buffer if needed, and write directly to
it.
        this->_M_reset(_M_file._M_init_write_buf());

This has another benefits:
* it clearly calls overflow only for buffered streams output, and
* we do not need to call funlock file, as File was already constructed, and
  destructort will call it.

>        {
> +       if (_M_file->_IO_write_ptr == nullptr)
> +         return {};
>         return {_M_file->_IO_write_ptr,
>                 size_t(_M_file->_IO_buf_end - _M_file->_IO_write_ptr)};
>        }
> diff --git a/libstdc++-v3/testsuite/27_io/print/1.cc
> b/libstdc++-v3/testsuite/27_io/print/1.cc
> index 4fd7f5dc925..641a97c1f9c 100644
> --- a/libstdc++-v3/testsuite/27_io/print/1.cc
> +++ b/libstdc++-v3/testsuite/27_io/print/1.cc
> @@ -67,6 +67,22 @@ test_print_raw()
>    VERIFY( txt == "\xa3" );
>  }
>
> +void
> +test_print_setvbuf()
> +{
> +  __gnu_test::scoped_file f;
> +  FILE* strm = std::fopen(f.path.string().c_str(), "w");
> +  VERIFY( strm );
> +  VERIFY( std::setvbuf(strm, nullptr, _IOFBF, 4096) == 0 );
> +  std::string str{"Hello, World!"};
> +  std::print(strm, "{}", str);
> +  std::fclose(strm);
> +
> +  std::ifstream in(f.path);
> +  std::string txt(std::istreambuf_iterator<char>(in), {});
> +  VERIFY( txt == "Hello, World!" );
> +}
> +
>  void
>  test_vprint_nonunicode()
>  {
> @@ -142,6 +158,7 @@ int main()
>    test_print_file();
>    test_println_file();
>    test_print_raw();
> +  test_print_setvbuf();
>    test_vprint_nonunicode();
>  #ifdef __cpp_exceptions
>    test_errors();
> --
> 2.34.1
>
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.