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 // ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS -D_LIBCPP_ENABLE_CXX26_REMOVED_CODECVT
10 // MSVC warning C4242: '+=': conversion from 'const _Ty' to 'size_t', possible loss of data
11 // MSVC warning C4244: 'argument': conversion from 'std::streamsize' to 'size_t', possible loss of data
12 // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4242 /wd4244
13 // UNSUPPORTED: c++03
14 
15 // <fstream>
16 
17 // This test checks the behavior of writing payloads of different sizes in different patterns.
18 // In particular, it was written to exercise code paths that deal with buffering inside the fstream
19 // implementation.
20 //
21 // For each test, we test various behaviors w.r.t. how the buffer is handled:
22 // - Provide a user-managed buffer to the library. In this case, we test the following corner-cases:
23 //    + A 0-sized buffer.
24 //    + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects.
25 //      Important values are +/- 1 byte from the payload size.
26 // - Let the library manage a buffer of a user-provided size 'n'. In this case, we test the following corner-cases:
27 //    + A 0-sized buffer.
28 //    + A buffer size greater than and smaller than the payload size, which causes multiple buffer effects.
29 //      Important values are +/- 1 or 2 bytes from the payload size.
30 //    + A buffer size smaller than 8 bytes. If pubsetbuf() is called with less than 8 bytes, the library will
31 //      use __extbuf_min_ with 8 bytes instead of allocating anything.
32 // - Let the library manage a buffer, without specifying any size. In this case, the library will use the default
33 //   buffer size of 4096 bytes.
34 
35 #include <cassert>
36 #include <codecvt>
37 #include <fstream>
38 #include <locale>
39 #include <numeric>
40 #include <string>
41 #include <vector>
42 
43 #include "../types.h"
44 #include "assert_macros.h"
45 #include "platform_support.h"
46 #include "test_macros.h"
47 
48 template <class BufferPolicy>
test_write(BufferPolicy policy,const std::vector<std::streamsize> & payload_sizes)49 void test_write(BufferPolicy policy, const std::vector<std::streamsize>& payload_sizes) {
50   std::size_t previously_written = 0;
51   std::streamsize total_size     = std::accumulate(payload_sizes.begin(), payload_sizes.end(), std::streamsize{0});
52   std::vector<char> data(total_size);
53   for (std::size_t i = 0; i < data.size(); ++i) {
54     data[i] = static_cast<char>(i % (1 << 8 * sizeof(char)));
55   }
56   std::string p = get_temp_file_name();
57   {
58     std::ofstream ofs;
59     policy(ofs);
60     ofs.open(p, std::ios::out | std::ios::binary);
61     assert(ofs.is_open());
62     for (const auto& payload_sz : payload_sizes) {
63       ofs.write(data.data() + previously_written, payload_sz);
64       assert(!ofs.fail());
65       // test that the user's out_buffer buffer was not modified by write()
66       for (std::streamsize j = 0; j < payload_sz; ++j) {
67         char exp = (previously_written + j) % (1 << 8 * sizeof(char));
68         TEST_REQUIRE(data[previously_written + j] == exp, [&] {
69           test_eprintf(
70               "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n",
71               previously_written + j,
72               j,
73               payload_sz,
74               data[previously_written + j],
75               exp);
76         });
77       }
78       previously_written += payload_sz;
79     }
80     ofs.close();
81   }
82   { // verify contents after reading the file back
83     std::ifstream ifs(p.c_str(), std::ios::ate | std::ios::binary);
84     const std::streamsize in_sz = ifs.tellg();
85     TEST_REQUIRE(in_sz == total_size, [&] { test_eprintf("out_sz = %zu, in_sz = %ld\n", total_size, in_sz); });
86     std::vector<char> in_buffer(total_size);
87     ifs.seekg(0, std::ios::beg);
88     assert(ifs.read(in_buffer.data(), total_size));
89     for (std::size_t i = 0; i < in_buffer.size(); ++i) {
90       char exp = i % (1 << 8 * sizeof(char));
91       TEST_REQUIRE(in_buffer[i] == exp, [&] {
92         test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n", i, in_buffer[i], exp);
93       });
94     }
95   }
96   std::remove(p.c_str());
97 }
98 
99 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
100 template <class BufferPolicy>
test_write_codecvt(BufferPolicy policy,const std::vector<std::streamsize> & payload_sizes)101 void test_write_codecvt(BufferPolicy policy, const std::vector<std::streamsize>& payload_sizes) {
102   std::size_t previously_written = 0;
103   std::streamsize total_size     = std::accumulate(payload_sizes.begin(), payload_sizes.end(), std::streamsize{0});
104   std::vector<wchar_t> data(total_size);
105   for (std::size_t i = 0; i < data.size(); ++i) {
106     data[i] = static_cast<wchar_t>(i);
107   }
108   std::string p = get_temp_file_name();
109   {
110     std::wofstream ofs;
111     ofs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8<wchar_t>));
112     policy(ofs);
113     ofs.open(p, std::ios::out | std::ios::binary);
114     assert(ofs.is_open());
115     for (const auto& payload_sz : payload_sizes) {
116       ofs.write(data.data() + previously_written, payload_sz);
117       assert(!ofs.fail());
118       // test that the user's out_buffer buffer was not modified by write()
119       for (std::streamsize j = 0; j < payload_sz; ++j) {
120         wchar_t exp = static_cast<wchar_t>(previously_written + j);
121         TEST_REQUIRE(data[previously_written + j] == exp, [&] {
122           test_eprintf(
123               "failed after write() at offset %zu (offset %zu in chunk size %zu): got=%x, expected=%x\n",
124               previously_written + j,
125               j,
126               payload_sz,
127               data[previously_written + j],
128               exp);
129         });
130       }
131       previously_written += payload_sz;
132     }
133     ofs.close();
134   }
135   { // verify contents after reading the file back
136     std::wifstream ifs(p.c_str(), std::ios::in | std::ios::binary);
137     ifs.imbue(std::locale(std::locale::classic(), new std::codecvt_utf8<wchar_t>));
138     std::vector<wchar_t> in_buffer(total_size);
139     assert(ifs.read(in_buffer.data(), total_size));
140     for (std::size_t i = 0; i < in_buffer.size(); ++i) {
141       wchar_t exp = static_cast<wchar_t>(i);
142       TEST_REQUIRE(in_buffer[i] == exp, [&] {
143         test_eprintf("failed after read() at offset %zu: got=%x, expected=%x\n", i, in_buffer[i], exp);
144       });
145     }
146   }
147   std::remove(p.c_str());
148 }
149 #endif
150 
151 const std::vector<std::streamsize> buffer_sizes{0L, 3L, 8L, 9L, 11L};
152 const std::vector<std::streamsize> io_sizes{0L, 1L, 2L, 3L, 4L, 9L, 10L, 11L, 12L, 13L, 21L, 22L, 23L};
153 const std::vector<std::streamsize> io_sizes_default{
154     0L, 1L, 2L, 3L, 4L, 4094L, 4095L, 4096L, 4097L, 4098L, 8190L, 8191L, 8192L, 8193L, 8194L};
155 
156 // Test single write operations
test_1_write()157 void test_1_write() {
158   // with default library buffer size: 4096b
159   for (std::streamsize x : io_sizes_default) {
160     test_write(LibraryDefaultBuffer(), {x});
161 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
162     test_write_codecvt(LibraryDefaultBuffer(), {x});
163 #endif
164   }
165 
166   // with the library-managed buffer of given size
167   for (std::streamsize b : buffer_sizes) {
168     for (std::streamsize x : io_sizes) {
169       test_write(LibraryManagedBuffer(b), {x});
170 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
171       test_write_codecvt(LibraryManagedBuffer(b), {x});
172 #endif
173     }
174   }
175 
176   // with the user-managed buffer of given size
177   for (std::streamsize b : buffer_sizes) {
178     for (std::streamsize x : io_sizes) {
179       test_write(UserManagedBuffer(b), {x});
180 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
181       test_write_codecvt(UserManagedBuffer(b), {x});
182 #endif
183     }
184   }
185 }
186 
187 // Test two write operations
test_2_writes()188 void test_2_writes() {
189   // with default library buffer size: 4096b
190   for (std::streamsize a : io_sizes_default) {
191     for (std::streamsize b : io_sizes_default) {
192       test_write(LibraryDefaultBuffer(), {a, b});
193 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
194       test_write_codecvt(LibraryDefaultBuffer(), {a, b});
195 #endif
196     }
197   }
198 
199   // with the library-managed buffer of given size
200   for (std::streamsize buf : buffer_sizes) {
201     for (std::streamsize a : io_sizes) {
202       for (std::streamsize b : io_sizes) {
203         test_write(LibraryManagedBuffer(buf), {a, b});
204 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
205         test_write_codecvt(LibraryManagedBuffer(buf), {a, b});
206 #endif
207       }
208     }
209   }
210 
211   // with the user-managed buffer of given size
212   for (std::streamsize buf : buffer_sizes) {
213     for (std::streamsize a : io_sizes) {
214       for (std::streamsize b : io_sizes) {
215         test_write(UserManagedBuffer(buf), {a, b});
216 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
217         test_write_codecvt(UserManagedBuffer(buf), {a, b});
218 #endif
219       }
220     }
221   }
222 }
223 
main(int,char **)224 int main(int, char**) {
225   test_1_write();
226   test_2_writes();
227   return 0;
228 }
229