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 > >