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

Tomasz Kaminski tkaminsk@redhat.com
Mon Aug 17 06:52:39 GMT 2026


On Sun, Aug 16, 2026 at 6:04 AM Anlai Lu <agicy@qq.com> 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 <agicy@qq.com>
> ---
> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260817/e2941c3e/attachment-0001.htm>


More information about the Libstdc++ mailing list