./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
370 / 412
89.8%
Branches:
275 / 365
75.3%
Functions:
38 / 38
100.0%

src/internal/xinput_raw_scope.cpp
Line Branch Exec Source
1 #include "internal/xinput_raw_scope.hpp"
2
3 #include <windows.h>
4
5 #include <array>
6 #include <atomic>
7 #include <cstddef>
8 #include <cstdint>
9 #include <cwchar>
10 #include <limits>
11 #include <new>
12
13 namespace DetourModKit
14 {
15 namespace detail
16 {
17 namespace
18 {
19 constexpr std::uint64_t DESCRIPTOR_MAGIC = 0x444D4B5849524157ULL;
20 constexpr std::uint32_t DESCRIPTOR_VERSION = 3;
21 constexpr DWORD DESCRIPTOR_WAIT_MS = 2000;
22 constexpr std::uint64_t ADMISSION_CLOSED = std::uint64_t{1} << 63;
23 constexpr std::uint64_t SCOPE_COUNT_MASK = ADMISSION_CLOSED - 1;
24 constexpr std::uintptr_t CONSUME_BUTTONS_MASK = 0xFFFF;
25 constexpr unsigned CONSUME_EXPECTED_SHIFT = 16;
26 constexpr std::uintptr_t CONSUME_EXPECTED_MASK = CONSUME_BUTTONS_MASK << CONSUME_EXPECTED_SHIFT;
27 constexpr std::uintptr_t CONSUME_VALID = std::uintptr_t{1} << 32;
28 constexpr std::uintptr_t CONSUME_DEPTH_MASK = (std::uintptr_t{1} << 29) - 1;
29 constexpr unsigned CONSUME_DEPTH_SHIFT = 33;
30 constexpr unsigned CONSUME_USER_SHIFT = 62;
31 constexpr DWORD CONSUME_USER_MAX = 3;
32 static_assert(sizeof(std::uintptr_t) == 8);
33
34 struct RawDescriptor
35 {
36 std::uint64_t magic;
37 std::uint32_t version;
38 std::uint32_t bytes;
39 std::uint64_t canonical_address;
40 std::uint64_t mutex_value;
41 std::uint64_t mapping_value;
42 std::uint64_t alias_probe;
43 std::uint32_t tls_index;
44 std::uint32_t owners;
45 std::uint32_t poisoned;
46 std::uint32_t reserved;
47 std::uint32_t consume_identity_index;
48 std::uint32_t consume_metadata_index;
49 volatile LONG consume_poison;
50 std::uint32_t consume_reserved;
51 };
52 static_assert(sizeof(RawDescriptor) == 80);
53 static_assert(offsetof(RawDescriptor, tls_index) == 48);
54
55 SRWLOCK s_owner_lock = SRWLOCK_INIT;
56 RawDescriptor *s_descriptor{nullptr};
57 std::uint32_t s_local_owners{0};
58 bool s_retained_registration{false};
59 std::atomic<DWORD> s_cached_index{TLS_OUT_OF_INDEXES};
60 std::atomic<DWORD> s_cached_identity_index{TLS_OUT_OF_INDEXES};
61 std::atomic<DWORD> s_cached_metadata_index{TLS_OUT_OF_INDEXES};
62 std::atomic<std::uint64_t> s_scope_state{ADMISSION_CLOSED};
63
64 class LastErrorGuard
65 {
66 public:
67 39630 LastErrorGuard() noexcept : m_error(::GetLastError()) {}
68 39622 ~LastErrorGuard() noexcept { ::SetLastError(m_error); }
69 LastErrorGuard(const LastErrorGuard &) = delete;
70 LastErrorGuard &operator=(const LastErrorGuard &) = delete;
71 LastErrorGuard(LastErrorGuard &&) = delete;
72 LastErrorGuard &operator=(LastErrorGuard &&) = delete;
73
74 private:
75 DWORD m_error;
76 };
77
78 #if defined(DMK_ENABLE_TEST_SEAMS)
79 constexpr std::size_t FAILURE_THREADS = 8;
80 std::array<std::atomic<std::uint64_t>, FAILURE_THREADS> s_failures{};
81
82 46688 [[nodiscard]] bool failure_for_thread(XInputRawScopeFailure failure) noexcept
83 {
84 const std::uint64_t expected =
85 46688 (static_cast<std::uint64_t>(::GetCurrentThreadId()) << 8) | static_cast<std::uint8_t>(failure);
86
2/2
✓ Branch 14 → 4 taken 373340 times.
✓ Branch 14 → 15 taken 46660 times.
420000 for (const auto &slot : s_failures)
87 {
88
2/2
✓ Branch 11 → 12 taken 22 times.
✓ Branch 11 → 13 taken 373313 times.
746675 if (slot.load(std::memory_order_acquire) == expected)
89 {
90 22 return true;
91 }
92 }
93 46660 return false;
94 }
95 #endif
96
97 2668 [[nodiscard]] HANDLE descriptor_mutex(const RawDescriptor &descriptor) noexcept
98 {
99 2668 return reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(descriptor.mutex_value));
100 }
101
102 1920 [[nodiscard]] bool descriptor_header_valid(const RawDescriptor &descriptor) noexcept
103 {
104 #if defined(DMK_ENABLE_TEST_SEAMS)
105 1920 const std::uint32_t expected_version = failure_for_thread(XInputRawScopeFailure::DescriptorVersion)
106
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1919 times.
1920 ? DESCRIPTOR_VERSION + 1
107 1920 : DESCRIPTOR_VERSION;
108 #else
109 constexpr std::uint32_t expected_version = DESCRIPTOR_VERSION;
110 #endif
111
2/2
✓ Branch 7 → 8 taken 1917 times.
✓ Branch 7 → 12 taken 2 times.
1919 return descriptor.magic == DESCRIPTOR_MAGIC && descriptor.version == expected_version &&
112
5/6
✓ Branch 6 → 7 taken 1919 times.
✓ Branch 6 → 12 taken 1 time.
✓ Branch 8 → 9 taken 1916 times.
✓ Branch 8 → 12 taken 1 time.
✓ Branch 9 → 10 taken 1916 times.
✗ Branch 9 → 12 not taken.
5755 descriptor.bytes == sizeof(RawDescriptor) && descriptor.reserved == 0 &&
113
1/2
✓ Branch 10 → 11 taken 1916 times.
✗ Branch 10 → 12 not taken.
3836 descriptor.consume_reserved == 0;
114 }
115
116 293 [[nodiscard]] bool canonical_alias(RawDescriptor *view, RawDescriptor *canonical) noexcept
117 {
118 293 MEMORY_BASIC_INFORMATION region{};
119
1/2
✓ Branch 4 → 5 taken 292 times.
✗ Branch 4 → 10 not taken.
292 if (canonical == nullptr || ::VirtualQuery(canonical, &region, sizeof(region)) != sizeof(region) ||
120
3/6
✓ Branch 5 → 6 taken 292 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 292 times.
✗ Branch 6 → 10 not taken.
✓ Branch 7 → 8 taken 292 times.
✗ Branch 7 → 10 not taken.
292 region.BaseAddress != canonical || region.State != MEM_COMMIT || region.Type != MEM_MAPPED ||
121
6/8
✓ Branch 2 → 3 taken 292 times.
✓ Branch 2 → 10 taken 1 time.
✓ Branch 8 → 9 taken 292 times.
✗ Branch 8 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 292 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 292 times.
585 region.Protect != PAGE_READWRITE || region.RegionSize < sizeof(RawDescriptor))
122 {
123 1 return false;
124 }
125 292 volatile std::uint64_t &written = view->alias_probe;
126 292 const volatile std::uint64_t &observed = canonical->alias_probe;
127 292 const std::uint64_t original = written;
128 292 const std::uint64_t probe = original ^ 1;
129 292 written = probe;
130 292 const bool same = observed == probe;
131 292 written = original;
132
4/4
✓ Branch 14 → 15 taken 291 times.
✓ Branch 14 → 17 taken 1 time.
✓ Branch 15 → 16 taken 290 times.
✓ Branch 15 → 17 taken 1 time.
292 return same && observed == original;
133 }
134
135 568 [[nodiscard]] RawDescriptor *connect_descriptor(DWORD &error) noexcept
136 {
137 #if defined(DMK_ENABLE_TEST_SEAMS)
138
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 568 times.
568 if (failure_for_thread(XInputRawScopeFailure::DescriptorInit))
139 {
140 ✗ error = ERROR_NOT_ENOUGH_MEMORY;
141 ✗ return nullptr;
142 }
143 #endif
144 568 FILETIME created{}, exited{}, kernel{}, user{};
145
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 568 times.
568 if (!::GetProcessTimes(::GetCurrentProcess(), &created, &exited, &kernel, &user))
146 {
147 ✗ error = ::GetLastError();
148 ✗ return nullptr;
149 }
150 568 wchar_t mutex_name[128]{};
151 568 wchar_t mapping_name[128]{};
152 1136 const int mutex_length = std::swprintf(
153 mutex_name,
154 std::size(mutex_name),
155 L"Local\\DMK.XInputRaw.%lu.%08lx%08lx.Lock",
156 ::GetCurrentProcessId(),
157 created.dwHighDateTime,
158 created.dwLowDateTime
159 );
160 1136 const int mapping_length = std::swprintf(
161 mapping_name,
162 std::size(mapping_name),
163 L"Local\\DMK.XInputRaw.%lu.%08lx%08lx.Data",
164 ::GetCurrentProcessId(),
165 created.dwHighDateTime,
166 created.dwLowDateTime
167 );
168
2/4
✓ Branch 18 → 19 taken 568 times.
✗ Branch 18 → 20 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 568 times.
568 if (mutex_length < 0 || mapping_length < 0)
169 {
170 ✗ error = ERROR_INSUFFICIENT_BUFFER;
171 ✗ return nullptr;
172 }
173 568 const HANDLE mutex = ::CreateMutexW(nullptr, FALSE, mutex_name);
174
1/2
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 25 taken 568 times.
568 if (mutex == nullptr)
175 {
176 ✗ error = ::GetLastError();
177 ✗ return nullptr;
178 }
179 568 const DWORD acquired = ::WaitForSingleObject(mutex, DESCRIPTOR_WAIT_MS);
180
1/4
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 34 taken 568 times.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 34 not taken.
568 if (acquired != WAIT_OBJECT_0 && acquired != WAIT_ABANDONED)
181 {
182
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 31 not taken.
✗ error = acquired == WAIT_FAILED ? ::GetLastError() : ERROR_TIMEOUT;
183 ✗ ::CloseHandle(mutex);
184 ✗ return nullptr;
185 }
186 568 const HANDLE mapping = ::CreateFileMappingW(
187 INVALID_HANDLE_VALUE,
188 nullptr,
189 PAGE_READWRITE,
190 0,
191 static_cast<DWORD>(sizeof(RawDescriptor)),
192 mapping_name
193 );
194 568 const DWORD mapping_error = ::GetLastError();
195
3/4
✓ Branch 36 → 37 taken 568 times.
✗ Branch 36 → 39 not taken.
✓ Branch 37 → 38 taken 296 times.
✓ Branch 37 → 39 taken 272 times.
568 const bool existing = mapping != nullptr && mapping_error == ERROR_ALREADY_EXISTS;
196 auto *const view =
197 mapping == nullptr
198
1/2
✓ Branch 40 → 41 taken 568 times.
✗ Branch 40 → 43 not taken.
568 ? nullptr
199 : static_cast<RawDescriptor *>(
200 568 ::MapViewOfFile(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(RawDescriptor))
201 ✗ );
202
2/4
✓ Branch 44 → 45 taken 568 times.
✗ Branch 44 → 49 not taken.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 48 taken 568 times.
568 error = mapping == nullptr ? mapping_error : view == nullptr ? ::GetLastError() : ERROR_INVALID_DATA;
203 568 RawDescriptor *result = nullptr;
204 568 bool process_owner = false;
205
1/2
✓ Branch 50 → 51 taken 568 times.
✗ Branch 50 → 71 not taken.
568 if (view != nullptr)
206 {
207
3/4
✓ Branch 51 → 52 taken 272 times.
✓ Branch 51 → 58 taken 296 times.
✓ Branch 52 → 53 taken 272 times.
✗ Branch 52 → 58 not taken.
568 if (!existing && acquired == WAIT_OBJECT_0)
208 {
209 544 ::new (static_cast<void *>(view)) RawDescriptor{
210 .magic = 0,
211 .version = DESCRIPTOR_VERSION,
212 .bytes = sizeof(RawDescriptor),
213 .canonical_address = reinterpret_cast<std::uintptr_t>(view),
214 .mutex_value = reinterpret_cast<std::uintptr_t>(mutex),
215 .mapping_value = reinterpret_cast<std::uintptr_t>(mapping),
216 .alias_probe = 0,
217 .tls_index = TLS_OUT_OF_INDEXES,
218 .owners = 0,
219 .poisoned = 0,
220 .reserved = 0,
221 .consume_identity_index = TLS_OUT_OF_INDEXES,
222 .consume_metadata_index = TLS_OUT_OF_INDEXES,
223 .consume_poison = 0,
224 .consume_reserved = 0,
225
1/2
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 57 taken 272 times.
272 };
226 272 view->magic = DESCRIPTOR_MAGIC;
227 272 result = view;
228 272 process_owner = true;
229 }
230
2/2
✓ Branch 59 → 60 taken 293 times.
✓ Branch 59 → 69 taken 3 times.
296 else if (descriptor_header_valid(*view))
231 {
232 293 auto *const canonical =
233 293 reinterpret_cast<RawDescriptor *>(static_cast<std::uintptr_t>(view->canonical_address));
234
5/6
✓ Branch 61 → 62 taken 290 times.
✓ Branch 61 → 65 taken 3 times.
✓ Branch 63 → 64 taken 290 times.
✗ Branch 63 → 65 not taken.
✓ Branch 66 → 67 taken 290 times.
✓ Branch 66 → 69 taken 3 times.
293 if (canonical_alias(view, canonical) && descriptor_header_valid(*canonical))
235 {
236 290 result = canonical;
237
1/2
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 69 taken 290 times.
290 if (acquired == WAIT_ABANDONED)
238 {
239 ✗ result->poisoned = 1;
240 }
241 }
242 }
243
2/2
✓ Branch 69 → 70 taken 296 times.
✓ Branch 69 → 71 taken 272 times.
568 if (!process_owner)
244 {
245 296 ::UnmapViewOfFile(view);
246 }
247 }
248
3/4
✓ Branch 71 → 72 taken 568 times.
✗ Branch 71 → 74 not taken.
✓ Branch 72 → 73 taken 296 times.
✓ Branch 72 → 74 taken 272 times.
568 if (mapping != nullptr && !process_owner)
249 {
250 296 ::CloseHandle(mapping);
251 }
252 568 ::ReleaseMutex(mutex);
253
2/2
✓ Branch 75 → 76 taken 296 times.
✓ Branch 75 → 77 taken 272 times.
568 if (!process_owner)
254 {
255 296 ::CloseHandle(mutex);
256 }
257 568 return result;
258 }
259
260 1334 [[nodiscard]] bool lock_descriptor(RawDescriptor &descriptor, DWORD &error) noexcept
261 {
262 1334 const DWORD acquired = ::WaitForSingleObject(descriptor_mutex(descriptor), DESCRIPTOR_WAIT_MS);
263
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 8 taken 1334 times.
1334 if (acquired == WAIT_ABANDONED)
264 {
265 ✗ descriptor.poisoned = 1;
266 ✗ ::ReleaseMutex(descriptor_mutex(descriptor));
267 ✗ error = ERROR_INVALID_DATA;
268 ✗ return false;
269 }
270
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 1334 times.
1334 if (acquired != WAIT_OBJECT_0)
271 {
272
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 12 not taken.
✗ error = acquired == WAIT_FAILED ? ::GetLastError() : ERROR_TIMEOUT;
273 ✗ return false;
274 }
275 3327 const bool indices_empty = descriptor.tls_index == TLS_OUT_OF_INDEXES &&
276
3/4
✓ Branch 14 → 15 taken 659 times.
✓ Branch 14 → 18 taken 675 times.
✓ Branch 15 → 16 taken 659 times.
✗ Branch 15 → 18 not taken.
1993 descriptor.consume_identity_index == TLS_OUT_OF_INDEXES &&
277
1/2
✓ Branch 16 → 17 taken 659 times.
✗ Branch 16 → 18 not taken.
659 descriptor.consume_metadata_index == TLS_OUT_OF_INDEXES;
278 3343 const bool indices_live = descriptor.tls_index != TLS_OUT_OF_INDEXES &&
279
1/2
✓ Branch 20 → 21 taken 675 times.
✗ Branch 20 → 26 not taken.
675 descriptor.consume_identity_index != TLS_OUT_OF_INDEXES &&
280
1/2
✓ Branch 21 → 22 taken 675 times.
✗ Branch 21 → 26 not taken.
675 descriptor.consume_metadata_index != TLS_OUT_OF_INDEXES &&
281
1/2
✓ Branch 22 → 23 taken 675 times.
✗ Branch 22 → 26 not taken.
675 descriptor.tls_index != descriptor.consume_identity_index &&
282
3/4
✓ Branch 19 → 20 taken 675 times.
✓ Branch 19 → 26 taken 659 times.
✓ Branch 23 → 24 taken 675 times.
✗ Branch 23 → 26 not taken.
2684 descriptor.tls_index != descriptor.consume_metadata_index &&
283
1/2
✓ Branch 24 → 25 taken 675 times.
✗ Branch 24 → 26 not taken.
675 descriptor.consume_identity_index != descriptor.consume_metadata_index;
284
5/6
✓ Branch 28 → 29 taken 1333 times.
✓ Branch 28 → 33 taken 1 time.
✓ Branch 29 → 30 taken 1333 times.
✗ Branch 29 → 33 not taken.
✓ Branch 35 → 36 taken 1 time.
✓ Branch 35 → 39 taken 1333 times.
2667 if (!descriptor_header_valid(descriptor) || descriptor.poisoned != 0 ||
285
4/6
✓ Branch 30 → 31 taken 658 times.
✓ Branch 30 → 32 taken 675 times.
✗ Branch 31 → 33 not taken.
✓ Branch 31 → 34 taken 658 times.
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 34 taken 675 times.
1333 (descriptor.owners == 0 ? !indices_empty : !indices_live))
286 {
287 1 error = ERROR_INVALID_DATA;
288 1 ::ReleaseMutex(descriptor_mutex(descriptor));
289 1 return false;
290 }
291 1333 return true;
292 }
293
294 5867 [[nodiscard]] bool store_depth(DWORD index, std::uintptr_t depth, [[maybe_unused]] bool entry) noexcept
295 {
296 #if defined(DMK_ENABLE_TEST_SEAMS)
297
4/4
✓ Branch 2 → 3 taken 2935 times.
✓ Branch 2 → 4 taken 2932 times.
✓ Branch 6 → 7 taken 5 times.
✓ Branch 6 → 8 taken 5862 times.
5867 if (failure_for_thread(entry ? XInputRawScopeFailure::EntryStore : XInputRawScopeFailure::Restore))
298 {
299 5 return false;
300 }
301 #endif
302 5862 return ::TlsSetValue(index, reinterpret_cast<void *>(depth)) != FALSE;
303 }
304
305 enum class ConsumeStore : std::uint8_t
306 {
307 EntryIdentity,
308 EntryMetadata,
309 Rollback,
310 Original,
311 RestoreIdentity,
312 RestoreMetadata,
313 };
314
315 [[nodiscard]] bool
316 28519 store_consume(DWORD index, std::uintptr_t value, [[maybe_unused]] ConsumeStore phase) noexcept
317 {
318 #if defined(DMK_ENABLE_TEST_SEAMS)
319 85555 const XInputRawScopeFailure fault = [&]() noexcept -> XInputRawScopeFailure
320 {
321
6/7
✓ Branch 2 → 3 taken 6936 times.
✓ Branch 2 → 4 taken 7175 times.
✓ Branch 2 → 5 taken 6 times.
✓ Branch 2 → 6 taken 294 times.
✓ Branch 2 → 7 taken 6934 times.
✓ Branch 2 → 8 taken 7173 times.
✗ Branch 2 → 9 not taken.
28518 switch (phase)
322 {
323 6936 case ConsumeStore::EntryIdentity:
324 6936 return XInputRawScopeFailure::ConsumeEntryIdentity;
325 7175 case ConsumeStore::EntryMetadata:
326 7175 return XInputRawScopeFailure::ConsumeEntryMetadata;
327 6 case ConsumeStore::Rollback:
328 6 return XInputRawScopeFailure::ConsumeEntryRollback;
329 294 case ConsumeStore::Original:
330 294 return XInputRawScopeFailure::ConsumeOriginalStore;
331 6934 case ConsumeStore::RestoreIdentity:
332 6934 return XInputRawScopeFailure::ConsumeRestoreIdentity;
333 7173 case ConsumeStore::RestoreMetadata:
334 7173 return XInputRawScopeFailure::ConsumeRestoreMetadata;
335 }
336 ✗ return XInputRawScopeFailure::None;
337 28519 }();
338
8/8
✓ Branch 4 → 5 taken 28508 times.
✓ Branch 4 → 8 taken 11 times.
✓ Branch 5 → 6 taken 7174 times.
✓ Branch 5 → 9 taken 21334 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 7173 times.
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 12 taken 28507 times.
35692 if (failure_for_thread(fault) || (phase == ConsumeStore::EntryMetadata &&
339 7174 failure_for_thread(XInputRawScopeFailure::ConsumeEntryRollback)))
340 {
341 12 return false;
342 }
343 #endif
344 28507 return ::TlsSetValue(index, reinterpret_cast<void *>(value)) != FALSE;
345 }
346
347 12 void poison_consume_context() noexcept
348 {
349 12 ::InterlockedExchange(&s_descriptor->consume_poison, 1);
350 12 }
351
352 7949 [[nodiscard]] std::uint32_t consume_depth(std::uintptr_t metadata) noexcept
353 {
354 7949 return static_cast<std::uint32_t>((metadata >> CONSUME_DEPTH_SHIFT) & CONSUME_DEPTH_MASK);
355 }
356
357 1015 [[nodiscard]] DWORD consume_user(std::uintptr_t metadata) noexcept
358 {
359 1015 return static_cast<DWORD>(metadata >> CONSUME_USER_SHIFT);
360 }
361
362 15897 [[nodiscard]] bool read_consume(DWORD index, std::uintptr_t &value) noexcept
363 {
364 15897 value = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index));
365 15898 return ::GetLastError() == ERROR_SUCCESS;
366 }
367
368 654 [[nodiscard]] bool reserve_scope_indices(RawDescriptor &descriptor, DWORD &error) noexcept
369 {
370 654 std::array<DWORD, 3> indices{TLS_OUT_OF_INDEXES, TLS_OUT_OF_INDEXES, TLS_OUT_OF_INDEXES};
371
2/2
✓ Branch 33 → 3 taken 1959 times.
✓ Branch 33 → 34 taken 651 times.
5220 for (std::size_t i = 0; i < indices.size(); ++i)
372 {
373 #if defined(DMK_ENABLE_TEST_SEAMS)
374
2/2
✓ Branch 3 → 4 taken 1305 times.
✓ Branch 3 → 7 taken 654 times.
3264 const XInputRawScopeFailure fault = i == 0 ? XInputRawScopeFailure::Reservation
375
2/2
✓ Branch 4 → 5 taken 653 times.
✓ Branch 4 → 6 taken 652 times.
1305 : i == 1 ? XInputRawScopeFailure::IdentityReservation
376 : XInputRawScopeFailure::MetadataReservation;
377
2/2
✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 11 taken 1956 times.
1959 if (failure_for_thread(fault))
378 {
379 3 error = ERROR_NOT_ENOUGH_MEMORY;
380 }
381 else
382 #endif
383 {
384 1956 indices[i] = ::TlsAlloc();
385
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 17 taken 1956 times.
1956 if (indices[i] == TLS_OUT_OF_INDEXES)
386 {
387 ✗ error = ::GetLastError();
388 }
389 }
390
2/2
✓ Branch 18 → 19 taken 3 times.
✓ Branch 18 → 30 taken 1956 times.
1959 if (indices[i] == TLS_OUT_OF_INDEXES)
391 {
392
2/2
✓ Branch 28 → 20 taken 9 times.
✓ Branch 28 → 29 taken 3 times.
12 for (const DWORD allocated : indices)
393 {
394
4/6
✓ Branch 20 → 21 taken 3 times.
✓ Branch 20 → 24 taken 6 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 3 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 9 times.
9 if (allocated != TLS_OUT_OF_INDEXES && !::TlsFree(allocated))
395 {
396 ✗ descriptor.poisoned = 1;
397 }
398 }
399 3 return false;
400 }
401 }
402 651 descriptor.tls_index = indices[0];
403 651 descriptor.consume_identity_index = indices[1];
404 651 descriptor.consume_metadata_index = indices[2];
405 651 ::InterlockedExchange(&descriptor.consume_poison, 0);
406 651 return true;
407 }
408
409 10125 [[nodiscard]] bool admit_scope() noexcept
410 {
411 10125 std::uint64_t state = s_scope_state.load(std::memory_order_seq_cst);
412
3/4
✓ Branch 17 → 18 taken 10122 times.
✓ Branch 17 → 19 taken 3 times.
✓ Branch 18 → 10 taken 10122 times.
✗ Branch 18 → 19 not taken.
10125 while ((state & ADMISSION_CLOSED) == 0 && (state & SCOPE_COUNT_MASK) != SCOPE_COUNT_MASK)
413 {
414
1/2
✓ Branch 15 → 16 taken 10122 times.
✗ Branch 15 → 17 not taken.
20244 if (s_scope_state.compare_exchange_weak(
415 state,
416 state + 1,
417 std::memory_order_seq_cst,
418 std::memory_order_seq_cst
419 ))
420 {
421 10122 return true;
422 }
423 }
424 3 return false;
425 }
426 } // namespace
427
428 771 bool acquire_xinput_raw_scope_owner(DWORD *error) noexcept
429 {
430 771 const LastErrorGuard preserve_error;
431 771 DWORD failure = ERROR_SUCCESS;
432 771 ::AcquireSRWLockExclusive(&s_owner_lock);
433
4/4
✓ Branch 4 → 5 taken 685 times.
✓ Branch 4 → 6 taken 86 times.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 16 taken 683 times.
771 if (s_local_owners != 0 || s_retained_registration)
434 {
435
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 88 times.
88 if (s_local_owners == std::numeric_limits<std::uint32_t>::max())
436 {
437 ✗ failure = ERROR_TOO_MANY_OPEN_FILES;
438 }
439 else
440 {
441 88 ++s_local_owners;
442 88 s_retained_registration = false;
443 s_scope_state.fetch_and(SCOPE_COUNT_MASK, std::memory_order_seq_cst);
444 88 ::ReleaseSRWLockExclusive(&s_owner_lock);
445
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 88 times.
88 if (error != nullptr)
446 {
447 ✗ *error = ERROR_SUCCESS;
448 }
449 88 return true;
450 }
451 }
452 else
453 {
454 #if defined(DMK_ENABLE_TEST_SEAMS)
455
2/2
✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 19 taken 682 times.
683 if (failure_for_thread(XInputRawScopeFailure::DescriptorInit))
456 {
457 1 failure = ERROR_NOT_ENOUGH_MEMORY;
458 }
459 else
460 #endif
461 {
462
2/2
✓ Branch 19 → 20 taken 568 times.
✓ Branch 19 → 22 taken 114 times.
682 if (s_descriptor == nullptr)
463 {
464 568 s_descriptor = connect_descriptor(failure);
465 }
466
6/6
✓ Branch 22 → 23 taken 676 times.
✓ Branch 22 → 26 taken 6 times.
✓ Branch 24 → 25 taken 675 times.
✓ Branch 24 → 26 taken 1 time.
✓ Branch 27 → 28 taken 675 times.
✓ Branch 27 → 74 taken 7 times.
682 if (s_descriptor != nullptr && lock_descriptor(*s_descriptor, failure))
467 {
468
1/2
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 675 times.
675 if (s_descriptor->owners == std::numeric_limits<std::uint32_t>::max())
469 {
470 ✗ failure = ERROR_TOO_MANY_OPEN_FILES;
471 }
472 else
473 {
474
2/2
✓ Branch 31 → 32 taken 654 times.
✓ Branch 31 → 33 taken 21 times.
675 if (s_descriptor->tls_index == TLS_OUT_OF_INDEXES)
475 {
476 654 (void)reserve_scope_indices(*s_descriptor, failure);
477 }
478
2/2
✓ Branch 33 → 34 taken 672 times.
✓ Branch 33 → 72 taken 3 times.
675 if (s_descriptor->tls_index != TLS_OUT_OF_INDEXES)
479 {
480 672 ++s_descriptor->owners;
481 672 s_local_owners = 1;
482 672 s_cached_identity_index.store(
483 672 s_descriptor->consume_identity_index,
484 std::memory_order_relaxed
485 );
486 672 s_cached_metadata_index.store(
487 672 s_descriptor->consume_metadata_index,
488 std::memory_order_relaxed
489 );
490 672 s_cached_index.store(s_descriptor->tls_index, std::memory_order_release);
491 s_scope_state.store(0, std::memory_order_seq_cst);
492 672 ::ReleaseMutex(descriptor_mutex(*s_descriptor));
493 672 ::ReleaseSRWLockExclusive(&s_owner_lock);
494
1/2
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 672 times.
672 if (error != nullptr)
495 {
496 ✗ *error = ERROR_SUCCESS;
497 }
498 672 return true;
499 }
500 }
501 3 ::ReleaseMutex(descriptor_mutex(*s_descriptor));
502 }
503 }
504 }
505 11 ::ReleaseSRWLockExclusive(&s_owner_lock);
506
1/2
✓ Branch 75 → 76 taken 11 times.
✗ Branch 75 → 77 not taken.
11 if (error != nullptr)
507 {
508 11 *error = failure;
509 }
510 11 return false;
511 771 }
512
513 738 void release_xinput_raw_scope_owner() noexcept
514 {
515 738 const LastErrorGuard preserve_error;
516 738 ::AcquireSRWLockExclusive(&s_owner_lock);
517
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 738 times.
738 if (s_local_owners == 0)
518 {
519 ✗ ::ReleaseSRWLockExclusive(&s_owner_lock);
520 ✗ return;
521 }
522
2/2
✓ Branch 7 → 8 taken 86 times.
✓ Branch 7 → 10 taken 652 times.
738 if (--s_local_owners != 0)
523 {
524 86 ::ReleaseSRWLockExclusive(&s_owner_lock);
525 86 return;
526 }
527 652 const std::uint64_t scopes = s_scope_state.fetch_or(ADMISSION_CLOSED, std::memory_order_seq_cst);
528 652 DWORD error = ERROR_SUCCESS;
529
6/8
✓ Branch 12 → 13 taken 650 times.
✓ Branch 12 → 16 taken 2 times.
✓ Branch 13 → 14 taken 650 times.
✗ Branch 13 → 16 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 650 times.
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 21 taken 650 times.
652 if ((scopes & SCOPE_COUNT_MASK) != 0 || s_descriptor == nullptr || !lock_descriptor(*s_descriptor, error))
530 {
531 2 s_retained_registration = true;
532 2 ::ReleaseSRWLockExclusive(&s_owner_lock);
533 2 return;
534 }
535 s_cached_index.store(TLS_OUT_OF_INDEXES, std::memory_order_release);
536 s_cached_identity_index.store(TLS_OUT_OF_INDEXES, std::memory_order_relaxed);
537 s_cached_metadata_index.store(TLS_OUT_OF_INDEXES, std::memory_order_relaxed);
538
2/2
✓ Branch 45 → 46 taken 629 times.
✓ Branch 45 → 54 taken 21 times.
650 if (--s_descriptor->owners == 0)
539 {
540 const std::array<DWORD, 3> indices{
541 629 s_descriptor->tls_index,
542 629 s_descriptor->consume_identity_index,
543 629 s_descriptor->consume_metadata_index,
544 629 };
545 629 s_descriptor->tls_index = TLS_OUT_OF_INDEXES;
546 629 s_descriptor->consume_identity_index = TLS_OUT_OF_INDEXES;
547 629 s_descriptor->consume_metadata_index = TLS_OUT_OF_INDEXES;
548
2/2
✓ Branch 52 → 48 taken 1887 times.
✓ Branch 52 → 53 taken 629 times.
2516 for (const DWORD index : indices)
549 {
550
1/2
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 51 taken 1887 times.
1887 if (!::TlsFree(index))
551 {
552 ✗ s_descriptor->poisoned = 1;
553 }
554 }
555 }
556 650 ::ReleaseMutex(descriptor_mutex(*s_descriptor));
557 650 ::ReleaseSRWLockExclusive(&s_owner_lock);
558
2/2
✓ Branch 59 → 60 taken 650 times.
✓ Branch 59 → 62 taken 88 times.
738 }
559
560 4140 std::uint64_t active_xinput_raw_scopes() noexcept
561 {
562 4140 return s_scope_state.load(std::memory_order_seq_cst) & SCOPE_COUNT_MASK;
563 }
564
565 18566 bool xinput_consume_context_healthy() noexcept
566 {
567
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 18566 times.
18566 if (s_cached_index.load(std::memory_order_acquire) == TLS_OUT_OF_INDEXES)
568 {
569 ✗ return false;
570 }
571 37132 return ::InterlockedCompareExchange(&s_descriptor->consume_poison, 0, 0) == 0;
572 }
573
574 17392 bool is_xinput_raw_call() noexcept
575 {
576 17347 const DWORD index = s_cached_index.load(std::memory_order_acquire);
577
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 17347 times.
17347 if (index == TLS_OUT_OF_INDEXES)
578 {
579 ✗ return false;
580 }
581 17347 const LastErrorGuard preserve_error;
582 17300 return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)) != 0;
583 17331 }
584
585 2938 XInputRawScope::XInputRawScope() noexcept
586 {
587 2938 const LastErrorGuard preserve_error;
588
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 2936 times.
2938 if (!admit_scope())
589 {
590 2 return;
591 }
592 2936 m_index = s_cached_index.load(std::memory_order_acquire);
593
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 17 taken 2936 times.
2936 if (m_index == TLS_OUT_OF_INDEXES)
594 {
595 s_scope_state.fetch_sub(1, std::memory_order_release);
596 ✗ return;
597 }
598 2936 m_previous_depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(m_index));
599
5/6
✓ Branch 19 → 20 taken 2936 times.
✗ Branch 19 → 24 not taken.
✓ Branch 21 → 22 taken 2935 times.
✓ Branch 21 → 24 taken 1 time.
✓ Branch 26 → 27 taken 4 times.
✓ Branch 26 → 30 taken 2932 times.
5871 if (::GetLastError() != ERROR_SUCCESS || m_previous_depth == std::numeric_limits<std::uintptr_t>::max() ||
600
2/2
✓ Branch 23 → 24 taken 3 times.
✓ Branch 23 → 25 taken 2932 times.
2935 !store_depth(m_index, m_previous_depth + 1, true))
601 {
602 s_scope_state.fetch_sub(1, std::memory_order_release);
603 4 return;
604 }
605 2932 m_admitted = true;
606 2932 m_active = true;
607
2/2
✓ Branch 32 → 33 taken 2932 times.
✓ Branch 32 → 35 taken 6 times.
2938 }
608
609 2938 XInputRawScope::~XInputRawScope() noexcept
610 {
611 2938 (void)finish();
612 2938 }
613
614 5872 bool XInputRawScope::finish() noexcept
615 {
616
2/2
✓ Branch 2 → 3 taken 2932 times.
✓ Branch 2 → 9 taken 2940 times.
5872 if (m_active)
617 {
618 2932 const LastErrorGuard preserve_error;
619 2932 m_restored = store_depth(m_index, m_previous_depth, false);
620 2932 m_active = false;
621 s_scope_state.fetch_sub(1, std::memory_order_release);
622 2932 }
623 5872 return m_restored;
624 }
625
626 7194 XInputConsumeScope::XInputConsumeScope(DWORD user, const void *state_address) noexcept
627 7194 : m_user(user), m_identity(reinterpret_cast<std::uintptr_t>(state_address))
628 {
629 7194 const LastErrorGuard preserve_error;
630
7/8
✓ Branch 3 → 4 taken 7187 times.
✓ Branch 3 → 7 taken 7 times.
✓ Branch 4 → 5 taken 7187 times.
✗ Branch 4 → 7 not taken.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 7186 times.
✓ Branch 9 → 10 taken 8 times.
✓ Branch 9 → 11 taken 7186 times.
7194 if (user > CONSUME_USER_MAX || m_identity == 0 || !admit_scope())
631 {
632 8 return;
633 }
634
2/2
✓ Branch 12 → 13 taken 9 times.
✓ Branch 12 → 16 taken 7177 times.
7186 if (!xinput_consume_context_healthy())
635 {
636 9 m_restored = false;
637 s_scope_state.fetch_sub(1, std::memory_order_release);
638 9 return;
639 }
640 7177 m_identity_index = s_cached_identity_index.load(std::memory_order_relaxed);
641 7177 m_metadata_index = s_cached_metadata_index.load(std::memory_order_relaxed);
642
2/4
✓ Branch 31 → 32 taken 7177 times.
✗ Branch 31 → 34 not taken.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 41 taken 7177 times.
14354 if (!read_consume(m_identity_index, m_previous_identity) ||
643
1/2
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 7177 times.
7177 !read_consume(m_metadata_index, m_previous_metadata))
644 {
645 ✗ poison_consume_context();
646 ✗ m_restored = false;
647 s_scope_state.fetch_sub(1, std::memory_order_release);
648 ✗ return;
649 }
650 7177 const std::uint32_t previous_depth = consume_depth(m_previous_metadata);
651 7177 m_joined =
652
6/6
✓ Branch 42 → 43 taken 247 times.
✓ Branch 42 → 47 taken 6930 times.
✓ Branch 43 → 44 taken 243 times.
✓ Branch 43 → 47 taken 4 times.
✓ Branch 45 → 46 taken 240 times.
✓ Branch 45 → 47 taken 3 times.
7177 previous_depth != 0 && m_identity == m_previous_identity && user == consume_user(m_previous_metadata);
653
4/4
✓ Branch 48 → 49 taken 240 times.
✓ Branch 48 → 53 taken 6937 times.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 49 → 53 taken 239 times.
7177 if (m_joined && previous_depth == CONSUME_DEPTH_MASK)
654 {
655 s_scope_state.fetch_sub(1, std::memory_order_release);
656 1 return;
657 }
658
2/2
✓ Branch 53 → 54 taken 239 times.
✓ Branch 53 → 55 taken 6937 times.
7176 m_depth = m_joined ? previous_depth + 1 : 1;
659
2/2
✓ Branch 56 → 57 taken 239 times.
✓ Branch 56 → 58 taken 6937 times.
7176 const std::uintptr_t metadata = m_joined ? m_previous_metadata + (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT)
660 6937 : (static_cast<std::uintptr_t>(user) << CONSUME_USER_SHIFT) |
661 (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT);
662 const bool identity_stored =
663
4/4
✓ Branch 59 → 60 taken 6936 times.
✓ Branch 59 → 62 taken 240 times.
✓ Branch 61 → 62 taken 6935 times.
✓ Branch 61 → 63 taken 2 times.
7176 m_joined || store_consume(m_identity_index, m_identity, ConsumeStore::EntryIdentity);
664 const bool metadata_stored =
665
4/4
✓ Branch 64 → 65 taken 7175 times.
✓ Branch 64 → 68 taken 2 times.
✓ Branch 66 → 67 taken 7173 times.
✓ Branch 66 → 68 taken 2 times.
7177 identity_stored && store_consume(m_metadata_index, metadata, ConsumeStore::EntryMetadata);
666
2/2
✓ Branch 69 → 70 taken 3 times.
✓ Branch 69 → 82 taken 7174 times.
7177 if (!metadata_stored)
667 {
668 3 poison_consume_context();
669 const bool identity_restored =
670 3 store_consume(m_identity_index, m_previous_identity, ConsumeStore::Rollback);
671 const bool metadata_restored =
672 3 store_consume(m_metadata_index, m_previous_metadata, ConsumeStore::Rollback);
673
4/6
✓ Branch 73 → 74 taken 2 times.
✓ Branch 73 → 78 taken 1 time.
✓ Branch 74 → 75 taken 2 times.
✗ Branch 74 → 78 not taken.
✗ Branch 76 → 77 not taken.
✓ Branch 76 → 78 taken 2 times.
3 m_restored = identity_restored && metadata_restored && xinput_consume_context_healthy();
674 s_scope_state.fetch_sub(1, std::memory_order_release);
675 3 return;
676 }
677 7174 m_admitted = true;
678 7174 m_active = true;
679
2/2
✓ Branch 84 → 85 taken 7173 times.
✓ Branch 84 → 87 taken 21 times.
7195 }
680
681 7194 XInputConsumeScope::~XInputConsumeScope() noexcept
682 {
683 7194 (void)finish();
684 7194 }
685
686 538 bool XInputConsumeScope::original_buttons(WORD observed, WORD &original) noexcept
687 {
688 538 const LastErrorGuard preserve_error;
689
6/6
✓ Branch 3 → 4 taken 536 times.
✓ Branch 3 → 6 taken 2 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 7 taken 533 times.
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 10 taken 533 times.
538 if (!m_active || !xinput_consume_context_healthy())
690 {
691 5 return false;
692 }
693 533 std::uintptr_t identity = 0;
694 533 std::uintptr_t metadata = 0;
695
1/2
✓ Branch 13 → 14 taken 533 times.
✗ Branch 13 → 19 not taken.
1066 if (!read_consume(m_identity_index, identity) || !read_consume(m_metadata_index, metadata) ||
696
5/10
✓ Branch 11 → 12 taken 533 times.
✗ Branch 11 → 19 not taken.
✓ Branch 14 → 15 taken 533 times.
✗ Branch 14 → 19 not taken.
✓ Branch 16 → 17 taken 533 times.
✗ Branch 16 → 19 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 533 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 533 times.
1066 identity != m_identity || consume_user(metadata) != m_user || consume_depth(metadata) != m_depth)
697 {
698 ✗ poison_consume_context();
699 ✗ return false;
700 }
701 533 const auto expected = static_cast<WORD>((metadata & CONSUME_EXPECTED_MASK) >> CONSUME_EXPECTED_SHIFT);
702
4/4
✓ Branch 24 → 25 taken 244 times.
✓ Branch 24 → 26 taken 289 times.
✓ Branch 25 → 26 taken 5 times.
✓ Branch 25 → 30 taken 239 times.
533 if ((metadata & CONSUME_VALID) == 0 || observed != expected)
703 {
704 294 metadata = (metadata & ~(CONSUME_BUTTONS_MASK | CONSUME_EXPECTED_MASK)) | observed |
705 294 (static_cast<std::uintptr_t>(observed) << CONSUME_EXPECTED_SHIFT) | CONSUME_VALID;
706
2/2
✓ Branch 27 → 28 taken 3 times.
✓ Branch 27 → 30 taken 291 times.
294 if (!store_consume(m_metadata_index, metadata, ConsumeStore::Original))
707 {
708 3 poison_consume_context();
709 3 return false;
710 }
711 }
712
1/2
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 530 times.
530 if (!xinput_consume_context_healthy())
713 {
714 ✗ return false;
715 }
716 530 original = static_cast<WORD>(metadata & CONSUME_BUTTONS_MASK);
717 530 return true;
718 538 }
719
720 13881 bool XInputConsumeScope::finish() noexcept
721 {
722 13881 return finish_context(false, 0);
723 }
724
725 502 bool XInputConsumeScope::finish(WORD returned_buttons) noexcept
726 {
727 502 return finish_context(true, returned_buttons);
728 }
729
730 14383 bool XInputConsumeScope::finish_context(bool publish_return, WORD returned_buttons) noexcept
731 {
732
2/2
✓ Branch 2 → 3 taken 7173 times.
✓ Branch 2 → 38 taken 7210 times.
14383 if (m_active)
733 {
734 7173 const LastErrorGuard preserve_error;
735 7173 bool identity_restored = true;
736 7173 bool metadata_restored = false;
737
2/2
✓ Branch 4 → 5 taken 239 times.
✓ Branch 4 → 21 taken 6934 times.
7173 if (m_joined)
738 {
739 239 std::uintptr_t identity = 0;
740 239 std::uintptr_t metadata = 0;
741
1/2
✓ Branch 8 → 9 taken 239 times.
✗ Branch 8 → 15 not taken.
478 if (read_consume(m_identity_index, identity) && read_consume(m_metadata_index, metadata) &&
742
4/8
✓ Branch 6 → 7 taken 239 times.
✗ Branch 6 → 15 not taken.
✓ Branch 9 → 10 taken 239 times.
✗ Branch 9 → 15 not taken.
✓ Branch 11 → 12 taken 239 times.
✗ Branch 11 → 15 not taken.
✓ Branch 16 → 17 taken 239 times.
✗ Branch 16 → 20 not taken.
717 identity == m_identity && consume_user(metadata) == m_user &&
743
1/2
✓ Branch 13 → 14 taken 239 times.
✗ Branch 13 → 15 not taken.
239 consume_depth(metadata) == m_depth)
744 {
745
1/2
✓ Branch 17 → 18 taken 239 times.
✗ Branch 17 → 19 not taken.
239 if (publish_return)
746 {
747 239 metadata = (metadata & ~CONSUME_EXPECTED_MASK) |
748 239 (static_cast<std::uintptr_t>(returned_buttons) << CONSUME_EXPECTED_SHIFT);
749 }
750 239 metadata_restored = store_consume(
751 m_metadata_index,
752 metadata - (std::uintptr_t{1} << CONSUME_DEPTH_SHIFT),
753 ConsumeStore::RestoreMetadata
754 );
755 }
756 }
757 else
758 {
759 identity_restored =
760 6934 store_consume(m_identity_index, m_previous_identity, ConsumeStore::RestoreIdentity);
761
2/2
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 6932 times.
6934 if (!identity_restored)
762 {
763 2 poison_consume_context();
764 }
765 metadata_restored =
766 6934 store_consume(m_metadata_index, m_previous_metadata, ConsumeStore::RestoreMetadata);
767 }
768
4/4
✓ Branch 25 → 26 taken 7171 times.
✓ Branch 25 → 27 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 7169 times.
7173 if (!identity_restored || !metadata_restored)
769 {
770 4 poison_consume_context();
771 }
772
6/6
✓ Branch 28 → 29 taken 7171 times.
✓ Branch 28 → 33 taken 2 times.
✓ Branch 29 → 30 taken 7169 times.
✓ Branch 29 → 33 taken 2 times.
✓ Branch 31 → 32 taken 7163 times.
✓ Branch 31 → 33 taken 6 times.
7173 m_restored = identity_restored && metadata_restored && xinput_consume_context_healthy();
773 7173 m_active = false;
774 s_scope_state.fetch_sub(1, std::memory_order_release);
775 7173 }
776 14383 return m_restored;
777 }
778
779 #if defined(DMK_ENABLE_TEST_SEAMS)
780 40 bool set_xinput_raw_scope_failure_for_test(XInputRawScopeFailure failure) noexcept
781 {
782 40 const std::uint64_t thread = ::GetCurrentThreadId();
783
2/2
✓ Branch 26 → 4 taken 180 times.
✓ Branch 26 → 27 taken 20 times.
200 for (auto &slot : s_failures)
784 {
785 180 std::uint64_t current = slot.load(std::memory_order_acquire);
786
4/4
✓ Branch 11 → 12 taken 20 times.
✓ Branch 11 → 22 taken 160 times.
✓ Branch 23 → 24 taken 20 times.
✓ Branch 23 → 25 taken 160 times.
200 if ((current >> 8) == thread &&
787
2/4
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 20 times.
✓ Branch 20 → 21 taken 20 times.
✗ Branch 20 → 22 not taken.
40 slot.compare_exchange_strong(
788 current,
789 ✗ failure == XInputRawScopeFailure::None ? 0 : (thread << 8) | static_cast<std::uint8_t>(failure),
790 std::memory_order_acq_rel,
791 std::memory_order_acquire
792 ))
793 {
794 20 return true;
795 }
796 }
797
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 20 times.
20 if (failure == XInputRawScopeFailure::None)
798 {
799 ✗ return true;
800 }
801
1/2
✓ Branch 38 → 30 taken 20 times.
✗ Branch 38 → 39 not taken.
20 for (auto &slot : s_failures)
802 {
803 20 std::uint64_t empty = 0;
804
1/2
✓ Branch 35 → 36 taken 20 times.
✗ Branch 35 → 37 not taken.
20 if (slot.compare_exchange_strong(
805 empty,
806 20 (thread << 8) | static_cast<std::uint8_t>(failure),
807 std::memory_order_acq_rel,
808 std::memory_order_acquire
809 ))
810 {
811 20 return true;
812 }
813 }
814 ✗ return false;
815 }
816
817 52 DWORD xinput_raw_scope_index_for_test() noexcept
818 {
819 52 return s_cached_index.load(std::memory_order_acquire);
820 }
821
822 8 std::uint32_t xinput_raw_scope_owners_for_test() noexcept
823 {
824 8 const LastErrorGuard preserve_error;
825 8 ::AcquireSRWLockExclusive(&s_owner_lock);
826 8 DWORD error = ERROR_SUCCESS;
827 8 std::uint32_t owners = 0;
828
3/6
✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 8 not taken.
✓ Branch 6 → 7 taken 8 times.
✗ Branch 6 → 8 not taken.
✓ Branch 9 → 10 taken 8 times.
✗ Branch 9 → 12 not taken.
8 if (s_descriptor != nullptr && lock_descriptor(*s_descriptor, error))
829 {
830 8 owners = s_descriptor->owners;
831 8 ::ReleaseMutex(descriptor_mutex(*s_descriptor));
832 }
833 8 ::ReleaseSRWLockExclusive(&s_owner_lock);
834 16 return owners;
835 8 }
836
837 7 std::uintptr_t xinput_raw_scope_depth_for_test() noexcept
838 {
839 7 const DWORD index = s_cached_index.load(std::memory_order_acquire);
840
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 7 times.
7 if (index == TLS_OUT_OF_INDEXES)
841 {
842 ✗ return 0;
843 }
844 7 const LastErrorGuard preserve_error;
845 7 return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index));
846 7 }
847
848 8 std::uintptr_t xinput_raw_scope_descriptor_for_test() noexcept
849 {
850 8 ::AcquireSRWLockExclusive(&s_owner_lock);
851 8 const auto address = reinterpret_cast<std::uintptr_t>(s_descriptor);
852 8 ::ReleaseSRWLockExclusive(&s_owner_lock);
853 8 return address;
854 }
855
856 13 DWORD xinput_consume_identity_index_for_test() noexcept
857 {
858 13 return s_cached_identity_index.load(std::memory_order_relaxed);
859 }
860
861 13 DWORD xinput_consume_metadata_index_for_test() noexcept
862 {
863 13 return s_cached_metadata_index.load(std::memory_order_relaxed);
864 }
865 #endif
866 } // namespace detail
867 } // namespace DetourModKit
868