Skip to content

Commit ca1af94

Browse files
Properly null-terminate output buffer in basic_istream::get[line] (#5073)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent e87ae37 commit ca1af94

File tree

3 files changed

+272
-6
lines changed

3 files changed

+272
-6
lines changed

stl/inc/istream

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ _STL_DISABLE_CLANG_WARNINGS
1717
#undef new
1818

1919
_STD_BEGIN
20+
template <class _Elem>
21+
struct _NODISCARD _Null_terminator_guard {
22+
_Elem** _Str_ref;
23+
~_Null_terminator_guard() {
24+
if (_Str_ref) {
25+
**_Str_ref = _Elem(); // add terminating null character
26+
}
27+
}
28+
};
29+
2030
#pragma vtordisp(push, 2) // compiler bug workaround
2131

2232
_EXPORT_STD extern "C++" template <class _Elem, class _Traits>
@@ -367,6 +377,10 @@ public:
367377
// get up to _Count characters into NTCS, stop before _Delim
368378
ios_base::iostate _State = ios_base::goodbit;
369379
_Chcount = 0;
380+
381+
// ensure null termination of buffer with nonzero length
382+
const _Null_terminator_guard<_Elem> _Guard{0 < _Count ? &_Str : nullptr};
383+
370384
const sentry _Ok(*this, true);
371385

372386
if (_Ok && 0 < _Count) { // state okay, extract characters
@@ -388,7 +402,6 @@ public:
388402
}
389403

390404
_Myios::setstate(_Chcount == 0 ? _State | ios_base::failbit : _State);
391-
*_Str = _Elem(); // add terminating null character
392405
return *this;
393406
}
394407

@@ -450,6 +463,10 @@ public:
450463
// get up to _Count characters into NTCS, discard _Delim
451464
ios_base::iostate _State = ios_base::goodbit;
452465
_Chcount = 0;
466+
467+
// ensure null termination of buffer with nonzero length
468+
const _Null_terminator_guard<_Elem> _Guard{0 < _Count ? &_Str : nullptr};
469+
453470
const sentry _Ok(*this, true);
454471

455472
if (_Ok && 0 < _Count) { // state okay, use facet to extract
@@ -477,7 +494,6 @@ public:
477494
_CATCH_IO_END
478495
}
479496

480-
*_Str = _Elem(); // add terminating null character
481497
_Myios::setstate(_Chcount == 0 ? _State | ios_base::failbit : _State);
482498
return *this;
483499
}

tests/libcxx/expected_results.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,10 +488,6 @@ std/diagnostics/syserr/syserr.syserr/syserr.syserr.members/ctor_int_error_catego
488488
std/diagnostics/syserr/syserr.syserr/syserr.syserr.members/ctor_int_error_category_string.pass.cpp FAIL
489489
std/diagnostics/syserr/syserr.syserr/syserr.syserr.members/ctor_int_error_category.pass.cpp FAIL
490490

491-
# libc++ disagrees with libstdc++ and MSVC on whether setstate calls during I/O that throw set failbit; see open issue LWG-2349
492-
std/input.output/iostream.format/input.streams/istream.unformatted/get_pointer_size_chart.pass.cpp FAIL
493-
std/input.output/iostream.format/input.streams/istream.unformatted/get_pointer_size.pass.cpp FAIL
494-
495491
# Sensitive to implementation details. Assertion failed: test_alloc_base::count == expected_num_allocs
496492
std/containers/container.requirements/container.requirements.general/allocator_move.pass.cpp FAIL
497493

tests/std/tests/GH_001858_iostream_exception/test.cpp

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#define _SILENCE_CXX17_STRSTREAM_DEPRECATION_WARNING
55

6+
#include <array>
67
#include <cassert>
78
#include <fstream>
89
#include <ios>
@@ -11,6 +12,7 @@
1112
#include <ostream>
1213
#include <sstream>
1314
#include <streambuf>
15+
#include <string>
1416
#include <strstream>
1517
#include <type_traits>
1618
#include <utility>
@@ -148,6 +150,255 @@ void test_istream_exceptions() {
148150
}
149151
}
150152

153+
template <class CharT>
154+
CharT meow_array;
155+
template <>
156+
constexpr array<char, 5> meow_array<char> = {"meow"};
157+
template <>
158+
constexpr array<wchar_t, 5> meow_array<wchar_t> = {L"meow"};
159+
160+
// testing GH-5070: basic_istream::get[line](char_type* s, std::streamsize n, char_type delim)
161+
// do not null-terminate the output buffer correctly
162+
template <class CharT>
163+
void test_gh5070_istream_get_null_termination_under_exceptions() {
164+
throwing_buffer<CharT> buffer;
165+
const basic_string<CharT> stream_content(1U, meow_array<CharT>[2]);
166+
167+
{ // get, exception during input extraction, no exception rethrow
168+
basic_istream<CharT> is(buffer.to_buf());
169+
auto buf = meow_array<CharT>;
170+
assert(!is.bad());
171+
is.get(buf.data(), static_cast<streamsize>(buf.size()));
172+
assert(is.bad());
173+
assert(buf[0] == CharT());
174+
}
175+
176+
{ // get, exception during input extraction, exception rethrow enabled
177+
basic_istream<CharT> is(buffer.to_buf());
178+
auto buf = meow_array<CharT>;
179+
is.exceptions(ios_base::badbit);
180+
assert(!is.bad());
181+
try {
182+
is.get(buf.data(), static_cast<streamsize>(buf.size()));
183+
assert(false);
184+
} catch (const ios_base::failure&) {
185+
assert(false);
186+
} catch (const test_exception&) {
187+
// Expected case
188+
}
189+
assert(is.bad());
190+
assert(buf[0] == CharT());
191+
}
192+
193+
{ // get, empty output buffer, no exception raised
194+
basic_istream<CharT> is(buffer.to_buf());
195+
auto buf = meow_array<CharT>;
196+
assert(!is.bad());
197+
is.get(buf.data(), 0);
198+
assert(is.fail());
199+
assert(buf[0] == meow_array<CharT>[0]);
200+
}
201+
202+
{ // get, empty output buffer, exception raised on failbit
203+
basic_istream<CharT> is(buffer.to_buf());
204+
auto buf = meow_array<CharT>;
205+
is.exceptions(ios_base::failbit);
206+
assert(!is.bad());
207+
try {
208+
is.get(buf.data(), 0);
209+
assert(false);
210+
} catch (const ios_base::failure&) {
211+
// Expected case
212+
}
213+
assert(is.fail());
214+
assert(buf[0] == meow_array<CharT>[0]);
215+
}
216+
217+
{ // get, sentry construction fails, no exception raised
218+
basic_stringbuf<CharT> strbuf{stream_content};
219+
basic_istream<CharT> is(&strbuf);
220+
assert(!is.bad());
221+
222+
// tests null termination on eof and
223+
// sets eofbit, preparing sentry failure
224+
auto buf1 = meow_array<CharT>;
225+
is.get(buf1.data(), static_cast<streamsize>(buf1.size()));
226+
assert(is.eof());
227+
assert(!is.fail());
228+
assert(buf1[0] == meow_array<CharT>[2]);
229+
assert(buf1[1] == CharT());
230+
231+
// actually tests sentry construction failure
232+
auto buf2 = meow_array<CharT>;
233+
is.get(buf2.data(), static_cast<streamsize>(buf2.size()));
234+
assert(is.fail());
235+
assert(buf2[0] == CharT());
236+
}
237+
238+
{ // get, sentry construction fails, exception raised on failbit
239+
basic_stringbuf<CharT> strbuf{stream_content};
240+
basic_istream<CharT> is(&strbuf);
241+
is.exceptions(ios_base::failbit);
242+
assert(!is.bad());
243+
244+
// tests null termination on eof and
245+
// sets eofbit, preparing sentry failure
246+
auto buf1 = meow_array<CharT>;
247+
is.get(buf1.data(), static_cast<streamsize>(buf1.size()));
248+
assert(is.eof());
249+
assert(!is.fail());
250+
assert(buf1[0] == meow_array<CharT>[2]);
251+
assert(buf1[1] == CharT());
252+
253+
// actually tests sentry construction failure
254+
auto buf2 = meow_array<CharT>;
255+
try {
256+
is.get(buf2.data(), static_cast<streamsize>(buf2.size()));
257+
assert(false);
258+
} catch (const ios_base::failure&) {
259+
// Expected case
260+
}
261+
assert(is.fail());
262+
assert(buf2[0] == CharT());
263+
}
264+
265+
{ // get, exception raised on eofbit
266+
basic_stringbuf<CharT> strbuf{stream_content};
267+
basic_istream<CharT> is(&strbuf);
268+
is.exceptions(ios_base::eofbit);
269+
assert(!is.bad());
270+
271+
auto buf = meow_array<CharT>;
272+
try {
273+
is.get(buf.data(), static_cast<streamsize>(buf.size()));
274+
assert(false);
275+
} catch (const ios_base::failure&) {
276+
// Expected case
277+
}
278+
assert(is.eof());
279+
assert(!is.fail());
280+
assert(buf[0] == meow_array<CharT>[2]);
281+
assert(buf[1] == CharT());
282+
}
283+
284+
{ // getline, exception during input extraction, no exception rethrow
285+
basic_istream<CharT> is(buffer.to_buf());
286+
auto buf = meow_array<CharT>;
287+
assert(!is.bad());
288+
is.getline(buf.data(), static_cast<streamsize>(buf.size()));
289+
assert(is.bad());
290+
assert(buf[0] == CharT());
291+
}
292+
293+
{ // getline, exception during input extraction, exception rethrow enabled
294+
basic_istream<CharT> is(buffer.to_buf());
295+
auto buf = meow_array<CharT>;
296+
is.exceptions(ios_base::badbit);
297+
assert(!is.bad());
298+
try {
299+
is.getline(buf.data(), static_cast<streamsize>(buf.size()));
300+
assert(false);
301+
} catch (const ios_base::failure&) {
302+
assert(false);
303+
} catch (const test_exception&) {
304+
// Expected case
305+
}
306+
assert(is.bad());
307+
assert(buf[0] == CharT());
308+
}
309+
310+
{ // getline, empty output buffer, no exception raised
311+
basic_istream<CharT> is(buffer.to_buf());
312+
auto buf = meow_array<CharT>;
313+
assert(!is.bad());
314+
is.getline(buf.data(), 0);
315+
assert(is.fail());
316+
assert(buf[0] == meow_array<CharT>[0]);
317+
}
318+
319+
{ // getline, empty output buffer, exception raised on failbit
320+
basic_istream<CharT> is(buffer.to_buf());
321+
auto buf = meow_array<CharT>;
322+
is.exceptions(ios_base::failbit);
323+
assert(!is.bad());
324+
try {
325+
is.getline(buf.data(), 0);
326+
assert(false);
327+
} catch (const ios_base::failure&) {
328+
// Expected case
329+
}
330+
assert(is.fail());
331+
assert(buf[0] == meow_array<CharT>[0]);
332+
}
333+
334+
{ // getline, sentry construction fails, no exception raised
335+
basic_stringbuf<CharT> strbuf{stream_content};
336+
basic_istream<CharT> is(&strbuf);
337+
assert(!is.bad());
338+
339+
// tests null termination on eof and
340+
// sets eofbit, preparing sentry failure
341+
auto buf1 = meow_array<CharT>;
342+
is.getline(buf1.data(), static_cast<streamsize>(buf1.size()));
343+
assert(is.eof());
344+
assert(!is.fail());
345+
assert(buf1[0] == meow_array<CharT>[2]);
346+
assert(buf1[1] == CharT());
347+
348+
// actually tests sentry construction failure
349+
auto buf2 = meow_array<CharT>;
350+
is.getline(buf2.data(), static_cast<streamsize>(buf2.size()));
351+
assert(is.fail());
352+
assert(buf2[0] == CharT());
353+
}
354+
355+
{ // getline, sentry construction fails, exception raised on failbit
356+
basic_stringbuf<CharT> strbuf{stream_content};
357+
basic_istream<CharT> is(&strbuf);
358+
is.exceptions(ios_base::failbit);
359+
assert(!is.bad());
360+
361+
// tests null termination on eof and
362+
// sets eofbit, preparing sentry failure
363+
auto buf1 = meow_array<CharT>;
364+
is.getline(buf1.data(), static_cast<streamsize>(buf1.size()));
365+
assert(is.eof());
366+
assert(!is.fail());
367+
assert(buf1[0] == meow_array<CharT>[2]);
368+
assert(buf1[1] == CharT());
369+
370+
// actually tests sentry construction failure
371+
auto buf2 = meow_array<CharT>;
372+
try {
373+
is.getline(buf2.data(), static_cast<streamsize>(buf2.size()));
374+
assert(false);
375+
} catch (const ios_base::failure&) {
376+
// Expected case
377+
}
378+
assert(is.fail());
379+
assert(buf2[0] == CharT());
380+
}
381+
382+
{ // getline, exception raised on eofbit
383+
basic_stringbuf<CharT> strbuf{stream_content};
384+
basic_istream<CharT> is(&strbuf);
385+
is.exceptions(ios_base::eofbit);
386+
assert(!is.bad());
387+
388+
auto buf = meow_array<CharT>;
389+
try {
390+
is.getline(buf.data(), static_cast<streamsize>(buf.size()));
391+
assert(false);
392+
} catch (const ios_base::failure&) {
393+
// Expected case
394+
}
395+
assert(is.eof());
396+
assert(!is.fail());
397+
assert(buf[0] == meow_array<CharT>[2]);
398+
assert(buf[1] == CharT());
399+
}
400+
}
401+
151402
template <class CharT>
152403
void test_ostream_exceptions() {
153404
throwing_buffer<CharT> buffer;
@@ -481,6 +732,9 @@ int main() {
481732
test_istream_exceptions<char>();
482733
test_istream_exceptions<wchar_t>();
483734

735+
test_gh5070_istream_get_null_termination_under_exceptions<char>();
736+
test_gh5070_istream_get_null_termination_under_exceptions<wchar_t>();
737+
484738
test_ostream_exceptions<char>();
485739
test_ostream_exceptions<wchar_t>();
486740
}

0 commit comments

Comments
 (0)