libstdc++: Fix istream::ignore exit conditions (PR 94749, PR 96161)
authorJonathan Wakely <jwakely@redhat.com>
Mon, 13 Jul 2020 09:26:39 +0000 (10:26 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Mon, 13 Jul 2020 11:09:44 +0000 (12:09 +0100)
My previous fix for PR 94749 did fix the reported case, so that the next
character is not discarded if it happens to equal the delimiter when __n
characters have already been read. But it introduced a new bug, which is
that the delimiter character would *not* be discarded if the number of
characters discarded is numeric_limits<streamsize>::max() or more before
reaching the delimiter.

The new bug happens because I changed the code to check _M_gcount < __n.
But when __n == numeric_limits<streamsize>::max() that is false, and so
we don't discard the delimiter. It's not sufficient to check for the
delimiter when the __large_ignore condition is true, because there's an
edge case where the delimiter is reached when _M_gcount == __n and so
we break out of the loop without setting __large_ignore.

PR 96161 is a similar bug to the original PR 94749 report, where eofbit
is set after discarding __n characters if there happen to be no more
characters in the stream.

This patch fixes both cases (and the regression) by checking different
conditions for the __n == max case and the __n < max case. For the
former case, we know that we must have either reached the delimiter or
EOF, and the value of _M_gcount doesn't matter (except to avoid integer
overflow). For the latter case we need to check _M_gcount first and only
set eofbit or discard the delimiter if it didn't reach __n. For the
latter case overflow can't happen because _M_gcount <= __n < max.

libstdc++-v3/ChangeLog:

PR libstdc++/94749
PR libstdc++/96161
* include/bits/istream.tcc (basic_istream::ignore(streamsize))
[n == max]: Check overflow conditions on _M_gcount. Rely on
the fact that either EOF or the delimiter was reached.
[n < max]: Check _M_gcount < n before checking for EOF or
delimiter.
(basic_istream::ignore(streamsize, char_type): Likewise.
* src/c++98/compatibility.cc (istream::ignore(streamsize))
(wistream::ignore(streamsize)): Likewise.
* src/c++98/istream.cc (istream::ignore(streamsize, char_type))
(wistream::ignore(streamsize, char_type)): Likewise.
* testsuite/27_io/basic_istream/ignore/char/94749.cc: Check that
delimiter is discarded if the number of characters ignored
doesn't fit in streamsize.
* testsuite/27_io/basic_istream/ignore/wchar_t/94749.cc:
Likewise.
* testsuite/27_io/basic_istream/ignore/char/96161.cc: New test.
* testsuite/27_io/basic_istream/ignore/wchar_t/96161.cc: New test.

libstdc++-v3/include/bits/istream.tcc
libstdc++-v3/src/c++98/compatibility.cc
libstdc++-v3/src/c++98/istream.cc
libstdc++-v3/testsuite/27_io/basic_istream/ignore/char/94749.cc
libstdc++-v3/testsuite/27_io/basic_istream/ignore/char/96161.cc [new file with mode: 0644]
libstdc++-v3/testsuite/27_io/basic_istream/ignore/wchar_t/94749.cc
libstdc++-v3/testsuite/27_io/basic_istream/ignore/wchar_t/96161.cc [new file with mode: 0644]

index d36374c..5983e51 100644 (file)
@@ -538,11 +538,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-             if (traits_type::eq_int_type(__c, __eof))
-                __err |= ios_base::eofbit;
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
+             else if (_M_gcount < __n)
+               {
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
             }
          __catch(__cxxabiv1::__forced_unwind&)
            {
@@ -596,15 +604,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-              if (traits_type::eq_int_type(__c, __eof))
-                __err |= ios_base::eofbit;
-             else if (_M_gcount < __n) // implies __c == __delim
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     if (_M_gcount != __n)
+                       ++_M_gcount;
+                     __sb->sbumpc();
+                   }
+               }
+             else if (_M_gcount < __n) // implies __c == __delim or EOF
                {
-                 ++_M_gcount;
-                 __sb->sbumpc();
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     ++_M_gcount;
+                     __sb->sbumpc();
+                   }
                }
             }
          __catch(__cxxabiv1::__forced_unwind&)
index 4e95b7e..6024bb9 100644 (file)
@@ -107,11 +107,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-             if (traits_type::eq_int_type(__c, __eof))
-               __err |= ios_base::eofbit;
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
+             else if (_M_gcount < __n)
+               {
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
            }
          __catch(__cxxabiv1::__forced_unwind&)
            {
@@ -178,11 +186,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-             if (traits_type::eq_int_type(__c, __eof))
-               __err |= ios_base::eofbit;
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
+             else if (_M_gcount < __n)
+               {
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+               }
            }
          __catch(__cxxabiv1::__forced_unwind&)
            {
index d6fee1a..d2c6794 100644 (file)
@@ -166,15 +166,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-             if (traits_type::eq_int_type(__c, __eof))
-               __err |= ios_base::eofbit;
-             else if (_M_gcount < __n) // implies __c == __delim
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     if (_M_gcount != __n)
+                       ++_M_gcount;
+                     __sb->sbumpc();
+                   }
+               }
+             else if (_M_gcount < __n) // implies __c == __delim or EOF
                {
-                 ++_M_gcount;
-                 __sb->sbumpc();
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     ++_M_gcount;
+                     __sb->sbumpc();
+                   }
                }
            }
          __catch(__cxxabiv1::__forced_unwind&)
@@ -406,15 +420,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                    break;
                }
 
-             if (__large_ignore)
-               _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
+             if (__n == __gnu_cxx::__numeric_traits<streamsize>::__max)
+               {
+                 if (__large_ignore)
+                   _M_gcount = __gnu_cxx::__numeric_traits<streamsize>::__max;
 
-             if (traits_type::eq_int_type(__c, __eof))
-               __err |= ios_base::eofbit;
-             else if (_M_gcount < __n) // implies __c == __delim
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     if (_M_gcount != __n)
+                       ++_M_gcount;
+                     __sb->sbumpc();
+                   }
+               }
+             else if (_M_gcount < __n) // implies __c == __delim or EOF
                {
-                 ++_M_gcount;
-                 __sb->sbumpc();
+                 if (traits_type::eq_int_type(__c, __eof))
+                   __err |= ios_base::eofbit;
+                 else
+                   {
+                     ++_M_gcount;
+                     __sb->sbumpc();
+                   }
                }
            }
          __catch(__cxxabiv1::__forced_unwind&)
index 03b5286..32b604d 100644 (file)
 // <http://www.gnu.org/licenses/>.
 
 // { dg-do run }
+// { dg-options "-DSIMULATOR_TEST" { target simulator } }
 
 // PR libstdc++/94749
 // basic_istream::ignore(n, c) discards n+1 if next character is equal to c.
 
 #include <sstream>
+#include <limits>
 #include <testsuite_hooks.h>
 
 typedef char C;
@@ -30,8 +32,10 @@ test01()
 {
   std::basic_istringstream<C> s(" +   -");
   s.ignore(1, '+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == '+' );
   s.ignore(3, '-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == '-' );
 }
 
@@ -40,8 +44,10 @@ test02()
 {
   std::basic_istringstream<C> s(".+...-");
   s.ignore(1, '+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == '+' );
   s.ignore(3, '-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == '-' );
 }
 
@@ -50,8 +56,10 @@ test03()
 {
   std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(" +   -");
   s.ignore(1, '+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == '+' );
   s.ignore(3, '-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == '-' );
 }
 
@@ -60,11 +68,150 @@ test04()
 {
   std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(".+...-");
   s.ignore(1, '+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == '+' );
   s.ignore(3, '-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == '-' );
 }
 
+// The original fix for PR libstdc++/94749 failed to discard the delimiter
+// if it occurred after numeric_limits<streamsize>::max() had been seen.
+// This streambuf will keep filling the get area with zero bytes until
+// almost numeric_limits<streamsize>::max() characters have been read,
+// and then return one more buffer that has "123" at its end.
+template<typename T>
+struct buff : std::basic_streambuf<typename T::char_type, T>
+{
+  typedef typename T::char_type                  char_type;
+  typedef typename T::int_type           int_type;
+  typedef std::streamsize                streamsize;
+  typedef std::numeric_limits<streamsize> limits;
+
+  buff() : count(0), buf() { }
+
+  int_type underflow()
+  {
+    // Number of characters left until we overflow the counter
+    const streamsize headroom = limits::max() - count;
+
+    if (headroom == 0)
+      return T::eof();
+
+    if (bufsz < headroom)
+    {
+      this->setg(buf, buf, buf + bufsz);
+      count += bufsz;
+    }
+    else
+    {
+      // write "123" across the 2GB boundary
+      buf[headroom-1] = '1';
+      buf[headroom+0] = '2';
+      buf[headroom+1] = '3';
+      this->setg(buf, buf, buf + headroom + 2);
+      count = limits::max();
+    }
+
+    return buf[0];
+  }
+
+  streamsize count;
+
+  static const streamsize bufsz = 2048 << limits::digits10;
+  char_type buf[bufsz + 2];
+};
+
+void
+test05()
+{
+  // Not possible to overflow 64-bit streamsize in reasonable time.
+  if (std::numeric_limits<std::streamsize>::digits > 32)
+    return;
+
+  typedef std::char_traits<C> T;
+
+  std::basic_istream<C, T> in(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '1');
+  VERIFY(in.good());
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == '2');
+  VERIFY(in.get() == '3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '2');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == '3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '3');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '4');
+  VERIFY(in.eof());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(nullptr);
+}
+
+void
+test06()
+{
+  if (std::numeric_limits<std::streamsize>::digits > 32)
+    return;
+
+  typedef __gnu_cxx::char_traits<C> T;
+
+  std::basic_istream<C, T> in(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '1');
+  VERIFY(in.good());
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == '2');
+  VERIFY(in.get() == '3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '2');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == '3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '3');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), '4');
+  VERIFY(in.eof());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(nullptr);
+}
 
 int
 main()
@@ -73,4 +220,8 @@ main()
   test02();
   test03();
   test04();
+  test05();
+#ifndef SIMULATOR_TEST
+  test06();
+#endif
 }
diff --git a/libstdc++-v3/testsuite/27_io/basic_istream/ignore/char/96161.cc b/libstdc++-v3/testsuite/27_io/basic_istream/ignore/char/96161.cc
new file mode 100644 (file)
index 0000000..c19e5f9
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do run }
+
+// PR libstdc++/96161
+// basic_istream::ignore sets eofbit too soon
+
+#include <sstream>
+#include <limits>
+#include <testsuite_hooks.h>
+
+typedef char C;
+
+void
+test01()
+{
+  std::basic_istringstream<C> s("  ");
+  s.ignore(2, '+');
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == std::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test02()
+{
+  std::basic_istringstream<C> s("  ");
+  s.ignore(2);
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == std::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test03()
+{
+  std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s("  ");
+  s.ignore(2, '+');
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == __gnu_cxx::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test04()
+{
+  std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s("  ");
+  s.ignore(2);
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == __gnu_cxx::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+int main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+}
index e5ec4e7..23d3a0a 100644 (file)
 // <http://www.gnu.org/licenses/>.
 
 // { dg-do run }
+// { dg-options "-DSIMULATOR_TEST" { target simulator } }
 
 // PR libstdc++/94749
 // basic_istream::ignore(n, c) discards n+1 if next character is equal to c.
 
 #include <sstream>
+#include <limits>
 #include <testsuite_hooks.h>
 
 typedef wchar_t C;
@@ -30,8 +32,10 @@ test01()
 {
   std::basic_istringstream<C> s(L" +   -");
   s.ignore(1, L'+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == L'+' );
   s.ignore(3, L'-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == L'-' );
 }
 
@@ -40,8 +44,10 @@ test02()
 {
   std::basic_istringstream<C> s(L".+...-");
   s.ignore(1, L'+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == L'+' );
   s.ignore(3, L'-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == L'-' );
 }
 
@@ -50,8 +56,10 @@ test03()
 {
   std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(L" +   -");
   s.ignore(1, L'+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == L'+' );
   s.ignore(3, L'-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == L'-' );
 }
 
@@ -60,17 +68,160 @@ test04()
 {
   std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(L".+...-");
   s.ignore(1, L'+');
+  VERIFY( s.gcount() == 1 );
   VERIFY( s.get() == L'+' );
   s.ignore(3, L'-');
+  VERIFY( s.gcount() == 3 );
   VERIFY( s.get() == L'-' );
 }
 
+// The original fix for PR libstdc++/94749 failed to discard the delimiter
+// if it occurred after numeric_limits<streamsize>::max() had been seen.
+// This streambuf will keep filling the get area with zero bytes until
+// almost numeric_limits<streamsize>::max() characters have been read,
+// and then return one more buffer that has L"123" at its end.
+template<typename T>
+struct buff : std::basic_streambuf<typename T::char_type, T>
+{
+  typedef typename T::char_type                  char_type;
+  typedef typename T::int_type           int_type;
+  typedef std::streamsize                streamsize;
+  typedef std::numeric_limits<streamsize> limits;
+
+  buff() : count(0), buf() { }
+
+  int_type underflow()
+  {
+    // Number of characters left until we overflow the counter
+    const streamsize headroom = limits::max() - count;
+
+    if (headroom == 0)
+      return T::eof();
+
+    if (bufsz < headroom)
+    {
+      this->setg(buf, buf, buf + bufsz);
+      count += bufsz;
+    }
+    else
+    {
+      // write L"123" across the 2GB boundary
+      buf[headroom-1] = L'1';
+      buf[headroom+0] = L'2';
+      buf[headroom+1] = L'3';
+      this->setg(buf, buf, buf + headroom + 2);
+      count = limits::max();
+    }
+
+    return buf[0];
+  }
+
+  streamsize count;
+
+  static const streamsize bufsz = 2048 << limits::digits10;
+  char_type buf[bufsz + 2];
+};
+
+void
+test05()
+{
+  // Not possible to overflow 64-bit streamsize in reasonable time.
+  if (std::numeric_limits<std::streamsize>::digits > 32)
+    return;
+
+  typedef std::char_traits<C> T;
+
+  std::basic_istream<C, T> in(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'1');
+  VERIFY(in.good());
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == L'2');
+  VERIFY(in.get() == L'3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'2');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == L'3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'3');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'4');
+  VERIFY(in.eof());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(nullptr);
+}
+
+void
+test06()
+{
+  if (std::numeric_limits<std::streamsize>::digits > 32)
+    return;
+
+  typedef __gnu_cxx::char_traits<C> T;
+
+  std::basic_istream<C, T> in(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'1');
+  VERIFY(in.good());
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == L'2');
+  VERIFY(in.get() == L'3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'2');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == L'3');
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'3');
+  VERIFY(in.good());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(new buff<T>);
+
+  in.ignore(std::numeric_limits<std::streamsize>::max(), L'4');
+  VERIFY(in.eof());
+  // The standard doesn't say what gcount() should return in this case:
+  VERIFY(in.gcount() == std::numeric_limits<std::streamsize>::max());
+  VERIFY(in.get() == T::eof());
+
+  delete in.rdbuf(nullptr);
+}
 
 int
 main()
 {
-  // test01();
-  // test02();
+  test01();
+  test02();
   test03();
   test04();
+  test05();
+#ifndef SIMULATOR_TEST
+  test06();
+#endif
 }
diff --git a/libstdc++-v3/testsuite/27_io/basic_istream/ignore/wchar_t/96161.cc b/libstdc++-v3/testsuite/27_io/basic_istream/ignore/wchar_t/96161.cc
new file mode 100644 (file)
index 0000000..6423442
--- /dev/null
@@ -0,0 +1,79 @@
+// Copyright (C) 2020 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do run }
+
+// PR libstdc++/96161
+// basic_istream::ignore sets eofbit too soon
+
+#include <sstream>
+#include <limits>
+#include <testsuite_hooks.h>
+
+typedef wchar_t C;
+
+void
+test01()
+{
+  std::basic_istringstream<C> s(L"  ");
+  s.ignore(2, L'+');
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == std::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test02()
+{
+  std::basic_istringstream<C> s(L"  ");
+  s.ignore(2);
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == std::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test03()
+{
+  std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(L"  ");
+  s.ignore(2, L'+');
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == __gnu_cxx::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+void
+test04()
+{
+  std::basic_istringstream<C, __gnu_cxx::char_traits<C> > s(L"  ");
+  s.ignore(2);
+  VERIFY( s.gcount() == 2 );
+  VERIFY( s.good() );
+  VERIFY( s.get() == __gnu_cxx::char_traits<C>::eof() );
+  VERIFY( s.eof() );
+}
+
+int main()
+{
+  test01();
+  test02();
+  test03();
+  test04();
+}