GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.7% 104 / 0 / 111
Functions: 100.0% 16 / 0 / 16
Branches: 87.0% 47 / 0 / 54

src/internal/input_delivery_scope.cpp
Line Branch Exec Source
1 /**
2 * @file input_delivery_scope.cpp
3 * @brief Reserved-Win32-TLS depth plus the stack-local teardown registry behind input_delivery_scope.hpp.
4 * @details Every ordinary failure mode reports "not recorded" to the constructing frame and nothing at all to other
5 * threads, so a gate can refuse the delivery it was about to run instead of the marker guessing on its
6 * behalf. The teardown registry has no failure mode: its node lives on the caller's stack and the list is a
7 * pointer splice under a statically initialized SRW lock.
8 */
9
10 #include "internal/input_delivery_scope.hpp"
11
12 #include <windows.h>
13
14 #include <array>
15 #include <atomic>
16 #include <cstddef>
17 #include <cstdint>
18 #include <mutex>
19
20 namespace DetourModKit
21 {
22 namespace detail
23 {
24 namespace
25 {
26 constexpr DWORD TLS_UNAVAILABLE = TLS_OUT_OF_INDEXES - 1;
27
28 // Reserved TLS slot holding this thread's gate-delivery depth as an integer cast into the void* value.
29 // TLS_OUT_OF_INDEXES means reservation has not run; TLS_UNAVAILABLE records a permanent failure.
30 std::atomic<DWORD> s_depth_tls{TLS_OUT_OF_INDEXES};
31
32 // Serializes the one-time slot reservation. Control-plane only; the hot delivery path never reaches it.
33 std::mutex s_depth_tls_mutex;
34
35 // Teardown registry. SRWLOCK_INIT is a constant initializer, so the lock is usable from the first
36 // teardown in the process without a dynamic initializer that could itself fail or order badly.
37 SRWLOCK s_teardown_lock = SRWLOCK_INIT;
38 TeardownRegistration *s_teardown_head{nullptr};
39 // Lets the query skip the lock entirely while no teardown span is open, which is the ordinary case on the
40 // delivery path that also asks this question.
41 std::atomic<bool> s_teardown_open{false};
42
43 #if defined(DMK_ENABLE_TEST_SEAMS)
44 DeliveryScopeReservationSeam s_reservation_seam{nullptr};
45 // Native thread identities whose depth store must report failure. A fixed array keeps the seam exact and
46 // allocation-free while letting the cross-gate compositions refuse on two threads at once.
47 constexpr std::size_t MAX_STORE_FAILURE_THREADS = 4;
48 std::array<std::atomic<std::uint32_t>, MAX_STORE_FAILURE_THREADS> s_store_failure_threads{};
49
50 22600 [[nodiscard]] bool store_failure_armed(std::uint32_t thread) noexcept
51 {
52
2/2
✓ Branch 13 → 3 taken 90367 times.
✓ Branch 13 → 14 taken 22588 times.
112955 for (const std::atomic<std::uint32_t> &slot : s_store_failure_threads)
53 {
54
2/2
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 12 taken 90355 times.
180734 if (slot.load(std::memory_order_acquire) == thread)
55 {
56 12 return true;
57 }
58 }
59 22588 return false;
60 }
61 #endif
62
63 334 void register_teardown(TeardownRegistration &node) noexcept
64 {
65 334 ::AcquireSRWLockExclusive(&s_teardown_lock);
66 334 node.next = s_teardown_head;
67 334 s_teardown_head = &node;
68 334 s_teardown_open.store(true, std::memory_order_release);
69 334 ::ReleaseSRWLockExclusive(&s_teardown_lock);
70 334 }
71
72 334 void unregister_teardown(TeardownRegistration &node) noexcept
73 {
74 334 ::AcquireSRWLockExclusive(&s_teardown_lock);
75
1/2
✓ Branch 7 → 4 taken 334 times.
✗ Branch 7 → 8 not taken.
334 for (TeardownRegistration **link = &s_teardown_head; *link != nullptr; link = &(*link)->next)
76 {
77
1/2
✓ Branch 4 → 5 taken 334 times.
✗ Branch 4 → 6 not taken.
334 if (*link == &node)
78 {
79 334 *link = node.next;
80 334 break;
81 }
82 }
83 334 node.next = nullptr;
84 334 s_teardown_open.store(s_teardown_head != nullptr, std::memory_order_release);
85 334 ::ReleaseSRWLockExclusive(&s_teardown_lock);
86 334 }
87
88 29289 [[nodiscard]] bool teardown_registered(std::uint32_t thread) noexcept
89 {
90
2/2
✓ Branch 3 → 4 taken 29270 times.
✓ Branch 3 → 5 taken 19 times.
29289 if (!s_teardown_open.load(std::memory_order_acquire))
91 {
92 29270 return false;
93 }
94 19 bool found = false;
95 19 ::AcquireSRWLockShared(&s_teardown_lock);
96
2/2
✓ Branch 10 → 7 taken 23 times.
✓ Branch 10 → 11 taken 7 times.
30 for (const TeardownRegistration *node = s_teardown_head; node != nullptr; node = node->next)
97 {
98
2/2
✓ Branch 7 → 8 taken 13 times.
✓ Branch 7 → 9 taken 10 times.
23 if (node->thread == thread)
99 {
100 13 found = true;
101 13 break;
102 }
103 }
104 20 ::ReleaseSRWLockShared(&s_teardown_lock);
105 20 return found;
106 }
107
108 // The one store whose failure a caller's correctness depends on. Routed through a single function so the
109 // seam sits exactly where the platform call does and cannot drift away from the branch it drives.
110 22591 [[nodiscard]] bool store_depth(DWORD index, std::uintptr_t depth) noexcept
111 {
112 #if defined(DMK_ENABLE_TEST_SEAMS)
113
2/2
✓ Branch 4 → 5 taken 12 times.
✓ Branch 4 → 6 taken 22579 times.
22591 if (store_failure_armed(static_cast<std::uint32_t>(::GetCurrentThreadId())))
114 {
115 12 return false;
116 }
117 #endif
118 22579 return ::TlsSetValue(index, reinterpret_cast<void *>(depth)) != FALSE;
119 }
120
121 24335 bool ensure_depth_tls() noexcept
122 {
123 24336 const DWORD current_index = s_depth_tls.load(std::memory_order_acquire);
124
2/2
✓ Branch 9 → 10 taken 11 times.
✓ Branch 9 → 11 taken 24325 times.
24336 if (current_index == TLS_UNAVAILABLE)
125 {
126 11 return false;
127 }
128
2/2
✓ Branch 11 → 12 taken 23923 times.
✓ Branch 11 → 13 taken 402 times.
24325 if (current_index != TLS_OUT_OF_INDEXES)
129 {
130 23923 return true;
131 }
132 #if defined(DMK_ENABLE_TEST_SEAMS)
133
2/2
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 400 times.
402 if (s_reservation_seam != nullptr)
134 {
135 2 s_reservation_seam();
136 }
137 #endif
138 try
139 {
140
1/2
✓ Branch 15 → 16 taken 402 times.
✗ Branch 15 → 50 not taken.
401 std::scoped_lock lock{s_depth_tls_mutex};
141 402 const DWORD locked_index = s_depth_tls.load(std::memory_order_relaxed);
142
2/2
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 400 times.
402 if (locked_index != TLS_OUT_OF_INDEXES)
143 {
144 2 return locked_index != TLS_UNAVAILABLE;
145 }
146
1/2
✓ Branch 25 → 26 taken 400 times.
✗ Branch 25 → 48 not taken.
400 const DWORD index = ::TlsAlloc();
147
2/2
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 36 taken 399 times.
400 if (index == TLS_OUT_OF_INDEXES)
148 {
149 s_depth_tls.store(TLS_UNAVAILABLE, std::memory_order_release);
150 1 return false;
151 }
152 s_depth_tls.store(index, std::memory_order_release);
153 399 return true;
154 402 }
155 catch (...)
156 {
157 DWORD expected = TLS_OUT_OF_INDEXES;
158 (void)s_depth_tls.compare_exchange_strong(
159 expected,
160 TLS_UNAVAILABLE,
161 std::memory_order_release,
162 std::memory_order_relaxed
163 );
164 return false;
165 }
166 }
167
168 29657 [[nodiscard]] bool depth_recorded_for_this_thread() noexcept
169 {
170 29657 const DWORD index = s_depth_tls.load(std::memory_order_acquire);
171
4/4
✓ Branch 9 → 10 taken 29601 times.
✓ Branch 9 → 11 taken 56 times.
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 12 taken 29589 times.
29657 if (index == TLS_OUT_OF_INDEXES || index == TLS_UNAVAILABLE)
172 {
173 68 return false;
174 }
175 29589 return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)) != 0;
176 }
177 } // namespace
178
179 1735 bool reserve_delivery_scope_tls() noexcept
180 {
181 1735 return ensure_depth_tls();
182 }
183
184 30611 std::uint32_t current_native_thread_id() noexcept
185 {
186 30611 return static_cast<std::uint32_t>(::GetCurrentThreadId());
187 }
188
189 #if defined(DMK_ENABLE_TEST_SEAMS)
190 2 void set_delivery_scope_reservation_seam_for_test(DeliveryScopeReservationSeam seam) noexcept
191 {
192 2 s_reservation_seam = seam;
193 2 }
194
195 18 bool set_delivery_scope_store_failure_for_test(bool fail) noexcept
196 {
197 18 const auto thread = static_cast<std::uint32_t>(::GetCurrentThreadId());
198
2/2
✓ Branch 3 → 4 taken 9 times.
✓ Branch 3 → 15 taken 9 times.
18 if (!fail)
199 {
200
1/2
✓ Branch 13 → 5 taken 12 times.
✗ Branch 13 → 14 not taken.
12 for (std::atomic<std::uint32_t> &slot : s_store_failure_threads)
201 {
202 12 std::uint32_t expected = thread;
203
2/2
✓ Branch 10 → 11 taken 9 times.
✓ Branch 10 → 12 taken 3 times.
24 if (slot.compare_exchange_strong(expected, 0, std::memory_order_acq_rel, std::memory_order_relaxed))
204 {
205 9 return true;
206 }
207 }
208 return true;
209 }
210
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 9 times.
9 if (store_failure_armed(thread))
211 {
212 return true;
213 }
214
1/2
✓ Branch 27 → 19 taken 12 times.
✗ Branch 27 → 28 not taken.
12 for (std::atomic<std::uint32_t> &slot : s_store_failure_threads)
215 {
216 12 std::uint32_t expected = 0;
217
2/2
✓ Branch 24 → 25 taken 9 times.
✓ Branch 24 → 26 taken 3 times.
24 if (slot.compare_exchange_strong(
218 expected,
219 thread,
220 std::memory_order_acq_rel,
221 std::memory_order_relaxed
222 ))
223 {
224 9 return true;
225 }
226 }
227 return false;
228 }
229 #endif
230
231 29658 bool current_thread_in_delivery() noexcept
232 {
233
2/2
✓ Branch 3 → 4 taken 370 times.
✓ Branch 3 → 5 taken 29288 times.
29658 if (depth_recorded_for_this_thread())
234 {
235 370 return true;
236 }
237 29288 return teardown_registered(current_native_thread_id());
238 }
239
240 22601 DeliveryScope::DeliveryScope() noexcept : m_admitted(false)
241 {
242
2/2
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 22591 times.
22601 if (!ensure_depth_tls())
243 {
244 10 return;
245 }
246 22591 const DWORD index = s_depth_tls.load(std::memory_order_acquire);
247 22591 const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index));
248 // A store can fail for a high slot index whose lazily heap-allocated TEB expansion array cannot be grown
249 // under OOM. Leaving the depth understated would let a nested release wrongly conclude it is control-plane
250 // and block into the ABBA, so the frame is refused instead and the caller declines the delivery.
251
2/2
✓ Branch 14 → 15 taken 12 times.
✓ Branch 14 → 16 taken 22579 times.
22591 if (!store_depth(index, depth + 1))
252 {
253 12 return;
254 }
255 22579 m_admitted = true;
256 }
257
258 45163 DeliveryScope::~DeliveryScope() noexcept
259 {
260
2/2
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 4 taken 22569 times.
22591 if (!m_admitted)
261 {
262 22 return;
263 }
264 22561 const DWORD index = s_depth_tls.load(std::memory_order_acquire);
265 22561 const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index));
266 // The matching push succeeded, so this thread's expansion array for the slot already exists and the store
267 // cannot fail for want of one. Floor at zero defensively.
268 22567 (void)::TlsSetValue(index, reinterpret_cast<void *>(depth > 0 ? depth - 1 : 0));
269 22594 }
270
271 334 MandatoryDeliveryScope::MandatoryDeliveryScope() noexcept
272 {
273 334 m_registration.thread = current_native_thread_id();
274 334 register_teardown(m_registration);
275 334 }
276
277 333 MandatoryDeliveryScope::~MandatoryDeliveryScope() noexcept
278 {
279 333 unregister_teardown(m_registration);
280 334 }
281 } // namespace detail
282 } // namespace DetourModKit
283