`<fstream>`: `filebuf` seeks drop writes in update mode due to buffering
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 11.1k
- Forks
- 1.7k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 22
Description
Describe the bug
The C++ standard definition for filebuf is mostly written in terms of FILE*/C-IO and the VS documentation also follows that in that for update filestreams a seek is required to switch between read and write:
To switch between inserting and extracting, you must call either pubseekoff or pubseekpos.
However that doesn't hold true: In various cases data written to the put area but not yet "committed" to the file via overflow (which is not user-accessible) is discarded which as far as I read it contradicts the C++ standard for filebuf::seekpos/seekoff
if the last operation was output, then update the output sequence
Example code reproducing the bug. Compile as C++ code
#include <fstream>
#include <cassert>
int main(){
std::filebuf buf;
buf.open("test.txt", std::ios_base::in | std::ios_base::out | std::ios_base::trunc);
assert(buf.sputn("123456", 6) == 6);
buf.pubseekpos(0);
assert(buf.sbumpc() == '1');
// All above is just setup for the following:
const auto pos = buf.pubseekoff(0, std::ios_base::cur);
//buf.sputc('a');
buf.sputn("a", 1); // This or the sputc above
assert(buf.pubseekpos(pos) == pos);
const auto a = char(buf.sbumpc());
assert(a == '2'); // Supposed to be overwritten, so this should fail but does not.
assert(a == 'a'); // FAILS!
}
Expected behavior
The above code initializes a file with the string "123456" and verifies that at least the '1' got written to setup the actual test case:
- Record the current position, i.e.
tellginfstream - write the character 'a' (either via
sputcorsputn, only important thing is that it is not enough data to trigger anoverflowcall) - Seek back to the position where the 'a' is supposed to be.
- Read back the just written character which should be 'a' but is instead the original '2'
STL version
Microsoft Visual Studio Community 2022 (64-Bit)
Version 17.0.4
Additional context
The reason for this class of behavior (I assume similar issues can be produced by variations of the code having in common that writes to the intermediate buffer may be dropped by seeking) seems to be that the basic_streambuf functions write directly into the buffer underneath the FILE* which filebuf uses as its put/get area pointers: https://github.com/microsoft/STL/blob/f099e9c9f91187376e85385b135ac62ef933e54f/stl/inc/fstream#L720-L721
But without any call to fwrite the FILE* has no way to know its buffer is "dirty" and has to be flushed to file.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the provided C++ example, then inspect stl/inc/fstream around the referenced filebuf implementation at lines 720-721 and the seek handling for update streams. Trace how pending put-area data is treated before pubseekpos or pubseekoff. Done means the pending write survives the seek and the reproducer reads 'a' instead of the original '2', with a regression test covering sputc and sputn.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100