[gcc r16-9570] libstdc++: Fix SIGSEGV in std::print with a setvbuf'd FILE

Tomasz Kaminski via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:a8ce20256d4ffac873c81bae1a339e4eac9472f5

commit r16-9570-ga8ce20256d4ffac873c81bae1a339e4eac9472f5
Author: Anlai Lu <[email protected]>
Date:   Sun Aug 16 04:01:03 2026 +0000

    libstdc++: Fix SIGSEGV in std::print with a setvbuf'd FILE
    
    This is a regression introduced by the P3107R5 implementation in
    r16-4350-g8bd872f1ea7414. Before that formatting to a FILE* wrote to
    _Str_sink and used fwrite, which handles this legal stream state.
    
    This fixes it by calling __overflow from the _File_sink constructor
    when _IO_write_ptr is null.
    
    libstdc++-v3/ChangeLog:
    
            * include/bits/print.h (_File_sink::_File::_File)
            [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
            Move __overflow call to separate member function.
            (_File_sink::_File::_M_init_write_buf)
            [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
            Extracted from constructor, call __overflow for null
            _M_file->_IO_write_ptr.
            (_File_sink::_File_sink)
            [_GLIBCXX_USE_STDIO_LOCKING && _GLIBCXX_USE_GLIBC_STDIO_EXT]:
            Call _M_init_write_buf instead of _M_write_buf.
            * testsuite/27_io/print/1.cc: Add test_print_setvbuf.
    
    Reviewed-by: Jonathan Wakely <[email protected]>
    Co-authored-by: Tomasz Kamiński <[email protected]>
    Signed-off-by: Anlai Lu <[email protected]>
    Signed-off-by: Tomasz Kamiński <[email protected]>
            (cherry picked from commits 4cb7258da9bd637ccce023693ae6a167d179784d)
            (74b3180bb616fbc35d1b655da75d8f6d8b39d368)

Diff:
---
 libstdc++-v3/include/bits/print.h       | 25 +++++++++++++++----------
 libstdc++-v3/testsuite/27_io/print/1.cc | 17 +++++++++++++++++
 2 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/libstdc++-v3/include/bits/print.h b/libstdc++-v3/include/bits/print.h
index 207248447264..722d82146ac8 100644
--- a/libstdc++-v3/include/bits/print.h
+++ b/libstdc++-v3/include/bits/print.h
@@ -80,20 +80,25 @@ namespace __format
 	    ::funlockfile(__f);
 	    __throw_system_error(EACCES);
 	  }
-	// Allocate buffer if needed:
-	if (_M_write_buf().empty())
-	  if (::__overflow(__f, EOF) == EOF)
-	    {
-	      const int __err = errno;
-	      ::funlockfile(__f);
-	      __throw_system_error(__err);
-	    }
       }
 
       ~_File() { ::funlockfile(_M_file); }
 
       _File(_File&&) = delete;
 
+      // Allocate FILE's output buffer if needed, and return a span
+      // viewing unused portion of it.
+      std::span<char>
+      _M_init_write_buf()
+      {
+	// 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(_M_file, EOF) == EOF)
+	    __throw_system_error(errno);
+	return _M_write_buf();
+      }
+
       // A span viewing the unused portion of the stream's output buffer.
       std::span<char>
       _M_write_buf() noexcept
@@ -158,8 +163,8 @@ namespace __format
     : _M_file(__f), _M_add_newline(__add_newline)
     {
       if (!_M_file._M_unbuffered())
-	// Write directly to the FILE's output buffer.
-	this->_M_reset(_M_file._M_write_buf());
+	// Allocate FILE's output buffer if needed, and write directly to it.
+	this->_M_reset(_M_file._M_init_write_buf());
     }
 
     // This calls I/O functions which are cancellation points, so they
diff --git a/libstdc++-v3/testsuite/27_io/print/1.cc b/libstdc++-v3/testsuite/27_io/print/1.cc
index 58f1eb163dfa..41314adb8203 100644
--- a/libstdc++-v3/testsuite/27_io/print/1.cc
+++ b/libstdc++-v3/testsuite/27_io/print/1.cc
@@ -65,6 +65,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()
 {
@@ -140,6 +156,7 @@ int main()
   test_print_file();
   test_println_file();
   test_print_raw();
+  test_print_setvbuf();
   test_vprint_nonunicode();
 #ifdef __cpp_exceptions
   test_errors();
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.