microsoft / microsoft/STL

`<fstream>`: `filebuf` seeks drop writes in update mode due to buffering

Open
#2,614 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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. tellg in fstream
  • write the character 'a' (either via sputc or sputn, only important thing is that it is not enough data to trigger an overflow call)
  • 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.