On Fri, 23 Oct 2026 at 18:40, Jonathan Wakely <[email protected]> wrote:
>
> From: Peter Edwards <[email protected]>
>
> If we fail to write the content of the buffer after inserting the
> overflow character into the end of it, then remove that overflow
> character before propagating the error back to the caller. Without this,
> the caller is left with pptr set past epptr, and successive calls to
> overflow will write further past the end of the managed buffer with each
> failure.
>
> libstdc++-v3/ChangeLog:
>
> PR libstdc++/118099
> * include/bits/fstream.tcc (basic_filebuf::overflow): Rewind the
> put position if outputting the put area fails.
> * testsuite/27_io/basic_filebuf/overflow/char/118099.cc: New
> test.
> * testsuite/27_io/basic_filebuf/overflow/wchar_t/118099.cc:
> New test.
>
> Co-authored-by: Jonathan Wakely <[email protected]>
> ---
>
> Tested x86_64-linux.
I forgot to say:
[PATCH 1/3] should be backported to active branches.
[PATCH 2/3] is doesn't need to be backported since it only fixes a
hypothetical problem.
[PATCH 3/3] is not suitable for backporting, but the bug can be fixed
with a try-catch around the call to _M_convert_to_external.
>
> The new tests use /dev/full which (as far as I know) only exists on
> Linux and Hurd. We could use a *-*-*-gnu target selector, but we'd still
> need the runtime check to see if it's present because it's not
> guaranteed to be there for all Hurd systems. I don't think a target
> selector adds any benefit.
>
> libstdc++-v3/include/bits/fstream.tcc | 2 +
> .../basic_filebuf/overflow/char/118099.cc | 51 +++++++++++++++++++
> .../basic_filebuf/overflow/wchar_t/118099.cc | 51 +++++++++++++++++++
> 3 files changed, 104 insertions(+)
> create mode 100644
> libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/char/118099.cc
> create mode 100644
> libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/wchar_t/118099.cc
>
> diff --git a/libstdc++-v3/include/bits/fstream.tcc
> b/libstdc++-v3/include/bits/fstream.tcc
> index 99aa296b83c4..2a1257c3e341 100644
> --- a/libstdc++-v3/include/bits/fstream.tcc
> +++ b/libstdc++-v3/include/bits/fstream.tcc
> @@ -576,6 +576,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _M_set_buffer(0);
> __ret = traits_type::not_eof(__c);
> }
> + else if (!__testeof)
> + this->pbump(-1);
> }
> else if (_M_buf_size > 1)
> {
> diff --git
> a/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/char/118099.cc
> b/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/char/118099.cc
> new file mode 100644
> index 000000000000..cceb29aa6d34
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/char/118099.cc
> @@ -0,0 +1,51 @@
> +// { dg-do run }
> +// { dg-require-fileio "" }
> +
> +// PR libstdc++/118099
> +// basic_filebuf::overflow leaves streambuf in inconsistent state on I/O
> error
> +
> +#include <fstream>
> +#include <testsuite_hooks.h>
> +
> +static void
> +test_pr118099()
> +{
> + typedef char C;
> +
> + const C val = C('x');
> + const C canary = ~val;
> + const int N = 16;
> + C buf[N] = { };
> + for (int i = 0; i < N; ++i)
> + buf[i] = canary;
> +
> + // Only run if /dev/full already exists as a device: probe it with a
> + // read-only open first, so we never accidentally create a regular file
> + // named /dev/full on a hypothetical system with a writable /dev.
> + {
> + std::filebuf probe;
> + if (!probe.open("/dev/full", std::ios_base::in))
> + return; // no /dev/full here: nothing to test.
> + }
> +
> + std::basic_filebuf<C> fb;
> + fb.pubsetbuf(buf, N/2); // filebuf can use buf[0..7]
> + fb.open("/dev/full", std::ios_base::out);
> + VERIFY( fb.is_open() );
> +
> + bool got_eof = false;
> + for (int i = 0; i < N; ++i)
> + if (fb.sputc(val) == std::char_traits<C>::eof())
> + got_eof = true;
> + VERIFY( got_eof ); // the error path was actually taken
> +
> + for (int i = 0; i < N/2; ++i)
> + VERIFY( buf[i] == val );
> + for (int i = N/2; i < N; ++i)
> + VERIFY( buf[i] == canary );
> +}
> +
> +int main()
> +{
> + test_pr118099();
> +}
> diff --git
> a/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/wchar_t/118099.cc
> b/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/wchar_t/118099.cc
> new file mode 100644
> index 000000000000..be2318578b36
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/27_io/basic_filebuf/overflow/wchar_t/118099.cc
> @@ -0,0 +1,51 @@
> +// { dg-do run }
> +// { dg-require-fileio "" }
> +
> +// PR libstdc++/118099
> +// basic_filebuf::overflow leaves streambuf in inconsistent state on I/O
> error
> +
> +#include <fstream>
> +#include <testsuite_hooks.h>
> +
> +static void
> +test_pr118099()
> +{
> + typedef wchar_t C;
> +
> + const C val = C('x');
> + const C canary = ~val;
> + const int N = 16;
> + C buf[N] = { };
> + for (int i = 0; i < N; ++i)
> + buf[i] = canary;
> +
> + // Only run if /dev/full already exists as a device: probe it with a
> + // read-only open first, so we never accidentally create a regular file
> + // named /dev/full on a hypothetical system with a writable /dev.
> + {
> + std::filebuf probe;
> + if (!probe.open("/dev/full", std::ios_base::in))
> + return; // no /dev/full here: nothing to test.
> + }
> +
> + std::basic_filebuf<C> fb;
> + fb.pubsetbuf(buf, N/2); // filebuf can use buf[0..7]
> + fb.open("/dev/full", std::ios_base::out);
> + VERIFY( fb.is_open() );
> +
> + bool got_eof = false;
> + for (int i = 0; i < N; ++i)
> + if (fb.sputc(val) == std::char_traits<C>::eof())
> + got_eof = true;
> + VERIFY( got_eof ); // the error path was actually taken
> +
> + for (int i = 0; i < N/2; ++i)
> + VERIFY( buf[i] == val );
> + for (int i = N/2; i < N; ++i)
> + VERIFY( buf[i] == canary );
> +}
> +
> +int main()
> +{
> + test_pr118099();
> +}
> --
> 2.56.0
>