Skip to content

Commit 6291b63

Browse files
authored
[libc++] Ensure that we restore invariants in basic_filebuf::overflow (#147389)
In rare circumstances, the invariants could fail to be restored.
1 parent 88ee17c commit 6291b63

File tree

2 files changed

+96
-11
lines changed

2 files changed

+96
-11
lines changed

libcxx/include/fstream

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,14 @@ typename basic_filebuf<_CharT, _Traits>::int_type basic_filebuf<_CharT, _Traits>
821821

822822
template <class _CharT, class _Traits>
823823
typename basic_filebuf<_CharT, _Traits>::int_type basic_filebuf<_CharT, _Traits>::overflow(int_type __c) {
824+
auto __failed = [this]() {
825+
if (this->pptr() == this->epptr() + 1) {
826+
this->pbump(-1); // lose the character we overflowed above -- we don't really have a
827+
// choice since we couldn't commit the contents of the put area
828+
}
829+
return traits_type::eof();
830+
};
831+
824832
if (__file_ == nullptr)
825833
return traits_type::eof();
826834
__write_mode();
@@ -841,8 +849,9 @@ typename basic_filebuf<_CharT, _Traits>::int_type basic_filebuf<_CharT, _Traits>
841849

842850
if (__always_noconv_) {
843851
size_t __n = static_cast<size_t>(this->pptr() - this->pbase());
844-
if (std::fwrite(this->pbase(), sizeof(char_type), __n, __file_) != __n)
845-
return traits_type::eof();
852+
if (std::fwrite(this->pbase(), sizeof(char_type), __n, __file_) != __n) {
853+
return __failed();
854+
}
846855
} else {
847856
if (!__cv_)
848857
std::__throw_bad_cast();
@@ -854,34 +863,38 @@ typename basic_filebuf<_CharT, _Traits>::int_type basic_filebuf<_CharT, _Traits>
854863
char* __extbuf_end = __extbuf_;
855864
do {
856865
codecvt_base::result __r = __cv_->out(__st_, __b, __p, __end, __extbuf_, __extbuf_ + __ebs_, __extbuf_end);
857-
if (__end == __b)
858-
return traits_type::eof();
866+
if (__end == __b) {
867+
return __failed();
868+
}
859869

860870
// No conversion needed: output characters directly to the file, done.
861871
if (__r == codecvt_base::noconv) {
862872
size_t __n = static_cast<size_t>(__p - __b);
863-
if (std::fwrite(__b, 1, __n, __file_) != __n)
864-
return traits_type::eof();
873+
if (std::fwrite(__b, 1, __n, __file_) != __n) {
874+
return __failed();
875+
}
865876
break;
866877

867878
// Conversion successful: output the converted characters to the file, done.
868879
} else if (__r == codecvt_base::ok) {
869880
size_t __n = static_cast<size_t>(__extbuf_end - __extbuf_);
870-
if (std::fwrite(__extbuf_, 1, __n, __file_) != __n)
871-
return traits_type::eof();
881+
if (std::fwrite(__extbuf_, 1, __n, __file_) != __n) {
882+
return __failed();
883+
}
872884
break;
873885

874886
// Conversion partially successful: output converted characters to the file and repeat with the
875887
// remaining characters.
876888
} else if (__r == codecvt_base::partial) {
877889
size_t __n = static_cast<size_t>(__extbuf_end - __extbuf_);
878-
if (std::fwrite(__extbuf_, 1, __n, __file_) != __n)
879-
return traits_type::eof();
890+
if (std::fwrite(__extbuf_, 1, __n, __file_) != __n) {
891+
return __failed();
892+
}
880893
__b = const_cast<char_type*>(__end);
881894
continue;
882895

883896
} else {
884-
return traits_type::eof();
897+
return __failed();
885898
}
886899
} while (true);
887900
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: no-filesystem
10+
11+
// setrlimit(RLIMIT_FSIZE) seems to only work as intended on Apple platforms
12+
// REQUIRES: target={{.+}}-apple-{{.+}}
13+
14+
// <fstream>
15+
16+
// Make sure that we properly handle the case where we try to write content to a file
17+
// but we fail to do so because std::fwrite fails.
18+
19+
#include <cassert>
20+
#include <csignal>
21+
#include <cstddef>
22+
#include <fstream>
23+
#include <string>
24+
25+
#include "platform_support.h"
26+
#include "test_macros.h"
27+
28+
#if __has_include(<sys/resource.h>)
29+
# include <sys/resource.h>
30+
void limit_file_size_to(std::size_t bytes) {
31+
rlimit lim = {bytes, bytes};
32+
assert(setrlimit(RLIMIT_FSIZE, &lim) == 0);
33+
34+
std::signal(SIGXFSZ, [](int) {}); // ignore SIGXFSZ to ensure std::fwrite fails
35+
}
36+
#else
37+
# error No known way to limit the amount of filesystem space available
38+
#endif
39+
40+
template <class CharT>
41+
void test() {
42+
std::string temp = get_temp_file_name();
43+
std::basic_filebuf<CharT> fbuf;
44+
assert(fbuf.open(temp, std::ios::out | std::ios::trunc));
45+
46+
std::size_t const limit = 100000;
47+
limit_file_size_to(limit);
48+
49+
std::basic_string<CharT> large_block(limit / 10, CharT(42));
50+
51+
std::streamsize ret;
52+
std::size_t bytes_written = 0;
53+
while ((ret = fbuf.sputn(large_block.data(), large_block.size())) != 0) {
54+
bytes_written += ret;
55+
56+
// In theory, it's possible for an implementation to allow writing arbitrarily more bytes than
57+
// set by setrlimit, but in practice if we bust 100x our limit, something else is wrong with the
58+
// test and we'd end up looping forever.
59+
assert(bytes_written < 100 * limit);
60+
}
61+
62+
fbuf.close();
63+
}
64+
65+
int main(int, char**) {
66+
test<char>();
67+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
68+
test<wchar_t>();
69+
#endif
70+
71+
return 0;
72+
}

0 commit comments

Comments
 (0)