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