GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 88.4% 99 / 0 / 112
Functions: 100.0% 17 / 0 / 17
Branches: 72.9% 51 / 0 / 70

src/win_file_stream.cpp
Line Branch Exec Source
1 #include "DetourModKit/win_file_stream.hpp"
2
3 #include <windows.h>
4 #include <algorithm>
5 #include <cassert>
6 #include <climits>
7 #include <cstring>
8
9 namespace DetourModKit
10 {
11 // --- WinFileStreamBuf ---
12
13 1148 WinFileStreamBuf::WinFileStreamBuf() noexcept : m_handle(INVALID_HANDLE_VALUE)
14 {
15 3444 setp(m_buffer.data(), m_buffer.data() + BUFFER_SIZE);
16 1148 }
17
18 1148 WinFileStreamBuf::~WinFileStreamBuf() noexcept
19 {
20 1148 close();
21 1148 }
22
23 1338 bool WinFileStreamBuf::open(const std::wstring &path, std::ios_base::openmode mode)
24 {
25
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1337 times.
1338 if (is_open())
26 {
27 1 close();
28 }
29
30 1338 const bool append = (mode & std::ios_base::app) != 0;
31
32 // Append mode requests FILE_APPEND_DATA so the OS positions every WriteFile at the current end of file
33 // atomically. This lets multiple writers sharing one file (e.g. several log sinks) append without
34 // interleaving or clobbering each other's bytes. A GENERIC_WRITE handle plus a one-time
35 // SetFilePointer(FILE_END) seek cannot guarantee that: the seek positions the file pointer once at open, so a
36 // second writer's WriteFile lands at a stale offset and overwrites the first writer's data. Truncating
37 // ("out") mode keeps GENERIC_WRITE + CREATE_ALWAYS.
38
2/2
✓ Branch 6 → 7 taken 5 times.
✓ Branch 6 → 8 taken 1333 times.
1338 const DWORD access = append ? FILE_APPEND_DATA : GENERIC_WRITE;
39
2/2
✓ Branch 9 → 10 taken 5 times.
✓ Branch 9 → 11 taken 1333 times.
1338 const DWORD creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
40
41 1338 m_handle = CreateFileW(path.c_str(), access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
42 creation, FILE_ATTRIBUTE_NORMAL, nullptr);
43
44
2/2
✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 16 taken 1330 times.
1338 if (m_handle == INVALID_HANDLE_VALUE)
45 {
46 8 return false;
47 }
48
49 3990 setp(m_buffer.data(), m_buffer.data() + BUFFER_SIZE);
50 1330 return true;
51 }
52
53 72 bool WinFileStreamBuf::open(const std::string &path, std::ios_base::openmode mode)
54 {
55 // Convert narrow string to wide. Try UTF-8 first, fall back to ACP.
56
1/2
✓ Branch 3 → 4 taken 72 times.
✗ Branch 3 → 27 not taken.
72 int wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.c_str(), -1, nullptr, 0);
57 72 UINT code_page = CP_UTF8;
58
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 8 taken 71 times.
72 if (wide_len <= 0)
59 {
60 1 code_page = CP_ACP;
61
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 27 not taken.
1 wide_len = MultiByteToWideChar(CP_ACP, 0, path.c_str(), -1, nullptr, 0);
62 }
63
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 72 times.
72 if (wide_len <= 0)
64 {
65 return false;
66 }
67
68
1/2
✓ Branch 12 → 13 taken 72 times.
✗ Branch 12 → 22 not taken.
72 std::wstring wide_path(static_cast<size_t>(wide_len - 1), L'\0');
69
1/2
✓ Branch 16 → 17 taken 72 times.
✗ Branch 16 → 25 not taken.
72 MultiByteToWideChar(code_page, 0, path.c_str(), -1, wide_path.data(), wide_len);
70
71
1/2
✓ Branch 17 → 18 taken 72 times.
✗ Branch 17 → 25 not taken.
72 return open(wide_path, mode);
72 72 }
73
74 73825 bool WinFileStreamBuf::is_open() const noexcept
75 {
76 73825 return m_handle != INVALID_HANDLE_VALUE;
77 }
78
79 2437 void WinFileStreamBuf::close() noexcept
80 {
81
2/2
✓ Branch 3 → 4 taken 1107 times.
✓ Branch 3 → 5 taken 1330 times.
2437 if (!is_open())
82 {
83 1107 return;
84 }
85
86 1330 flush_buffer();
87 1330 CloseHandle(static_cast<HANDLE>(m_handle));
88 1330 m_handle = INVALID_HANDLE_VALUE;
89 1330 setp(nullptr, nullptr);
90 }
91
92 2 WinFileStreamBuf::int_type WinFileStreamBuf::overflow(int_type ch)
93 {
94
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
2 if (!is_open())
95 {
96 1 return traits_type::eof();
97 }
98
99
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
1 if (!flush_buffer())
100 {
101 return traits_type::eof();
102 }
103
104
1/2
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 14 not taken.
1 if (!traits_type::eq_int_type(ch, traits_type::eof()))
105 {
106 1 *pptr() = traits_type::to_char_type(ch);
107 1 pbump(1);
108 }
109
110 1 return traits_type::not_eof(ch);
111 }
112
113 2913 int WinFileStreamBuf::sync()
114 {
115
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2912 times.
2913 if (!is_open())
116 {
117 1 return -1;
118 }
119
120
1/2
✓ Branch 6 → 7 taken 2912 times.
✗ Branch 6 → 8 not taken.
2912 return flush_buffer() ? 0 : -1;
121 }
122
123 61416 std::streamsize WinFileStreamBuf::xsputn(const char *s, std::streamsize count)
124 {
125
6/6
✓ Branch 3 → 4 taken 61415 times.
✓ Branch 3 → 5 taken 1 time.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 61411 times.
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 9 taken 61411 times.
61416 if (!is_open() || count <= 0)
126 {
127 5 return 0;
128 }
129
130 61411 std::streamsize written = 0;
131
132
2/2
✓ Branch 27 → 10 taken 61418 times.
✓ Branch 27 → 28 taken 61411 times.
122829 while (written < count)
133 {
134 61418 const auto available = static_cast<std::streamsize>(epptr() - pptr());
135 61418 const auto to_copy = std::min(count - written, available);
136
137
1/2
✓ Branch 13 → 14 taken 61418 times.
✗ Branch 13 → 20 not taken.
61418 if (to_copy > 0)
138 {
139 61418 std::memcpy(pptr(), s + written, static_cast<size_t>(to_copy));
140
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 61418 times.
61418 assert(to_copy <= INT_MAX);
141 61418 pbump(static_cast<int>(to_copy));
142 61418 written += to_copy;
143 }
144
145
2/2
✓ Branch 22 → 23 taken 9 times.
✓ Branch 22 → 26 taken 61409 times.
61418 if (pptr() == epptr())
146 {
147
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 9 times.
9 if (!flush_buffer())
148 {
149 break;
150 }
151 }
152 }
153
154 61411 return written;
155 }
156
157 4252 bool WinFileStreamBuf::flush_buffer() noexcept
158 {
159 4252 const auto count = static_cast<DWORD>(pptr() - pbase());
160
2/2
✓ Branch 4 → 5 taken 1571 times.
✓ Branch 4 → 6 taken 2681 times.
4252 if (count == 0)
161 {
162 1571 return true;
163 }
164
165 // WriteFile may satisfy only part of the request, reporting success with bytes_written < count (a short write
166 // on a near-full volume, a pipe-backed handle, or an interrupted write). A single call that reset the put area
167 // on a count == bytes_written assumption would silently drop the unwritten tail, so drain the remainder in a
168 // loop: advance past what each call wrote and reissue for the rest. Only a hard error (result == 0) or a
169 // zero-byte success (no forward progress is possible, so looping would spin) aborts. The put area is reset
170 // once, after the buffer has fully drained or the write has failed, so a partial failure leaves no stale tail
171 // behind.
172 2681 const char *cursor = pbase();
173 2681 DWORD remaining = count;
174 2681 bool drained = true;
175
2/2
✓ Branch 13 → 8 taken 2681 times.
✓ Branch 13 → 14 taken 2681 times.
5362 while (remaining != 0)
176 {
177 2681 DWORD bytes_written = 0;
178 2681 const BOOL result = WriteFile(static_cast<HANDLE>(m_handle), cursor, remaining, &bytes_written, nullptr);
179
2/4
✓ Branch 9 → 10 taken 2681 times.
✗ Branch 9 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2681 times.
2681 if (result == 0 || bytes_written == 0)
180 {
181 drained = false;
182 break;
183 }
184 2681 cursor += bytes_written;
185 2681 remaining -= bytes_written;
186 }
187
188 8043 setp(m_buffer.data(), m_buffer.data() + BUFFER_SIZE);
189 2681 return drained;
190 }
191
192 // --- WinFileStream ---
193
194
1/2
✓ Branch 3 → 4 taken 1076 times.
✗ Branch 3 → 6 not taken.
1076 WinFileStream::WinFileStream() : std::ostream(&m_buf) {}
195
196
1/2
✓ Branch 3 → 4 taken 51 times.
✗ Branch 3 → 11 not taken.
51 WinFileStream::WinFileStream(const std::string &path, std::ios_base::openmode mode) : std::ostream(&m_buf)
197 {
198
1/4
DetourModKit::WinFileStream::WinFileStream(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::_Ios_Openmode):
✓ Branch 5 → 6 taken 51 times.
✗ Branch 5 → 7 not taken.
DetourModKit::WinFileStream::WinFileStream(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::_Ios_Openmode):
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
51 open(path, mode);
199 51 }
200
201 1127 WinFileStream::~WinFileStream() noexcept = default;
202
203 55 void WinFileStream::open(const std::string &path, std::ios_base::openmode mode)
204 {
205 55 clear();
206
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 53 times.
55 if (!m_buf.open(path, mode))
207 {
208 2 setstate(std::ios_base::failbit);
209 }
210 55 }
211
212 1265 void WinFileStream::open(const std::wstring &path, std::ios_base::openmode mode)
213 {
214 1265 clear();
215
2/2
✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 6 taken 1260 times.
1265 if (!m_buf.open(path, mode))
216 {
217 5 setstate(std::ios_base::failbit);
218 }
219 1265 }
220
221 5712 bool WinFileStream::is_open() const noexcept
222 {
223 5712 return m_buf.is_open();
224 }
225
226 1271 void WinFileStream::close() noexcept
227 {
228 1271 m_buf.close();
229 1271 }
230
231 } // namespace DetourModKit
232