The warning was produced by following seqeunce, given an istream_iterator<char>
it, such that *it will result in hitting EoF in it->_M_get(), and thus clearing
_M_sbuf, the subsequent call to ++it, will result in _M_sbuf->sbumpc() call
on null pointer dereference. This is however an false-positive, as in such 
sitution
it == istream_iteator() returns true, and the iterator should not be 
dereferenced
in first place.

This patch addresses the above by clearing the _M_sbuf in operator++, instead of
_M_get(). This removes the need for making _M_sbuf mutable, and thus make the
implementation conforming with regards to C++11 [res.on.data.races] p3.

This change should not or positive have performance impact on the usual 
iteration
patterns, in form:
 while (it != end) { process(*it); ++it; }
In case when it is end-of-stream iterator, the it != end returns in one call of
_M_sbuf->sgetc() both before and after the change. However we do not modify 
_M_sbuf
in this case. For non-empty range, we replace call to _M_sbuf->sbumpc() with
_M_sbuf->snextc() in pre-increment, and extract the check again from *it to
++it. However, as _M_sbuf is now cleared during increment, so last it != end
check avoids _M_sbuf->sgetc() call to check against EoF.

However, this change impact the behavior of the post-increment (*it++), as
we now load both current character (for return value) and next character (to
check against EoF). In consequence we call both sgetc() and snextc(), in 
contrast
to previous single sbumpc() call.

        PR libstdc++/105580

libstdc++-v3/ChangeLog:

        * include/bits/streambuf_iterator.h (istreambuf_iterator::_M_sbuf):
        Remove mutable and adjust whitespace.
        (istreambuf_iterator::_M_c): Adjust whitespace.
        (istreambuf_iterator::operator++()): Clear _M_sbuf if next character
        is EoF.
        (istreambuf_iterator::operator++(int)): Use _M_sbuf->sgetc() to
        load current character, and define in terms of __*this.
        (istreambuf_iterator::_M_get()): Do not clear _M_sbuf in case of EoF.
        * testsuite/24_iterators/istreambuf_iterator/2.cc: Test for using
        multiple iterators to same rdbuf.
---
This warning turned out to be false-positive (as explained in the commit
decription), so we can just add add pragrams to address that. But this
change seem to be beneficial anyway, as we are no longer causing
data-races.

Tested on x86_64-linux. Locally tested with -Wnull-dereference,
and the istream_iterator warning in 24_iterators/istreambuf_iterator/2.cc
disappears (there are some other warning present I haven't looked into
that yet).

 .../include/bits/streambuf_iterator.h         | 20 +++----
 .../24_iterators/istreambuf_iterator/2.cc     | 58 +++++++++++++++++++
 2 files changed, 66 insertions(+), 12 deletions(-)

diff --git a/libstdc++-v3/include/bits/streambuf_iterator.h 
b/libstdc++-v3/include/bits/streambuf_iterator.h
index 93d3dd24f93..095928ca4d8 100644
--- a/libstdc++-v3/include/bits/streambuf_iterator.h
+++ b/libstdc++-v3/include/bits/streambuf_iterator.h
@@ -112,8 +112,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       // the "end of stream" iterator value.
       // NB: This implementation assumes the "end of stream" value
       // is EOF, or -1.
-      mutable streambuf_type*  _M_sbuf;
-      int_type                 _M_c;
+      streambuf_type* _M_sbuf;
+      int_type        _M_c;
 
     public:
       ///  Construct end of input stream iterator.
@@ -172,7 +172,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                                _M_message(__gnu_debug::__msg_inc_istreambuf)
                                ._M_iterator(*this));
 
-       _M_sbuf->sbumpc();
+       if (_S_is_eof(_M_sbuf->snextc()))
+         _M_sbuf = 0;
        _M_c = traits_type::eof();
        return *this;
       }
@@ -181,14 +182,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       istreambuf_iterator
       operator++(int)
       {
-       __glibcxx_requires_cond(_M_sbuf &&
-                               (!_S_is_eof(_M_c) || 
!_S_is_eof(_M_sbuf->sgetc())),
-                               _M_message(__gnu_debug::__msg_inc_istreambuf)
-                               ._M_iterator(*this));
-
        istreambuf_iterator __old = *this;
-       __old._M_c = _M_sbuf->sbumpc();
-       _M_c = traits_type::eof();
+       __old._M_c = _M_sbuf->sgetc();
+       ++*this;
        return __old;
       }
 
@@ -206,8 +202,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _M_get() const
       {
        int_type __ret = _M_c;
-       if (_M_sbuf && _S_is_eof(__ret) && _S_is_eof(__ret = _M_sbuf->sgetc()))
-         _M_sbuf = 0;
+       if (_M_sbuf && _S_is_eof(__ret))
+         __ret = _M_sbuf->sgetc();
        return __ret;
       }
 
diff --git a/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/2.cc 
b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/2.cc
index 78701d71cee..0318a677f35 100644
--- a/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/2.cc
+++ b/libstdc++-v3/testsuite/24_iterators/istreambuf_iterator/2.cc
@@ -109,8 +109,66 @@ void test02(void)
     }
 }
 
+void
+test_empty()
+{
+  std::istreambuf_iterator<char> null(0), end;
+  VERIFY( null == end );
+
+  std::istringstream ess;
+  // Thie specification hear seem to indicate, that such iterators
+  // are not end-of-stream iterators, as rdbuf pointer is nullptr,
+  // however we treat them as such, as otherwise we would produce
+  // a range containing single eof character.
+  std::istreambuf_iterator<char> it1(ess), it2(ess);
+  VERIFY( it1 == end );
+  VERIFY( it2 == end );
+}
+
+void
+test_multi()
+{
+  // C++98 (and later) define operator* in [istreambuf.iterator.ops] as:
+  // Returns: The character obtained via the streambuf member sbuf_->sgetc().
+  // This nails down behavior of mulptiple iterators to same sequence.
+  std::istringstream iss("abcd");
+  std::istreambuf_iterator<char> it1(iss), it2(iss), end;
+  
+  VERIFY( it1 != end );
+  VERIFY( it2 != end );
+  VERIFY( *it1 == 'a' );
+  VERIFY( *it2 == 'a' );
+  ++it1;
+
+  VERIFY( it1 != end );
+  VERIFY( it2 != end );
+  VERIFY( *it1 == 'b' );
+  VERIFY( *it2 == 'b' );
+  ++it2;
+
+  VERIFY( it1 != end );
+  VERIFY( it2 != end );
+  VERIFY( *it1 == 'c' );
+  VERIFY( *it2 == 'c' );
+  ++it2;
+
+  VERIFY( it1 != end );
+  VERIFY( it2 != end );
+  VERIFY( *it1 == 'd' );
+  VERIFY( *it2 == 'd' );
+  // second dereference
+  VERIFY( *it1 == 'd' );
+  VERIFY( *it2 == 'd' );
+  ++it1;
+
+  VERIFY( it1 == end );
+  VERIFY( it2 == end );
+}
+
 int main()
 {
   test02();
+  test_empty();
+  test_multi();
   return 0;
 }
-- 
2.52.0

Reply via email to