In [stringbuf.virtuals]/1, basic_stringbuf::underflow() is specified to unconditionally return traits::eof() when a read position is not available.
The semantics of basic_stringbuf require, and existing libraries implement it so that this function makes a read position available if possible to do so, e.g. if some characters were inserted into the stream since the last call to overflow(), resulting in pptr() > egptr(). Compare to the conceptually similar [depr.strstreambuf.virtuals]/15.