./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
96 / 99
97.0%
Branches:
38 / 44
86.4%
Functions:
18 / 18
100.0%

src/internal/input_delivery_scope.cpp
Line Branch Exec Source
1 /**
2 * @file input_delivery_scope.cpp
3 * @brief Owned-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 "internal/shared_tls_index.hpp"
13
14 #include <windows.h>
15
16 #include <array>
17 #include <atomic>
18 #include <cstddef>
19 #include <cstdint>
20
21 namespace DetourModKit
22 {
23 namespace detail
24 {
25 namespace
26 {
27 // Owned TLS slot that holds this thread's gate-delivery depth as an integer cast into the void* value.
28 constinit SharedTlsIndex s_depth_tls;
29
30 // Teardown registry. SRWLOCK_INIT is a constant initializer, so the lock is usable from the first
31 // teardown in the process without a dynamic initializer that could itself fail or order badly.
32 SRWLOCK s_teardown_lock = SRWLOCK_INIT;
33 TeardownRegistration *s_teardown_head{nullptr};
34 // Lets the query skip the lock entirely while no teardown span is open, which is the ordinary case on the
35 // delivery path that also asks this question.
36 std::atomic<bool> s_teardown_open{false};
37
38 #if defined(DMK_ENABLE_TEST_SEAMS)
39 DeliveryScopeReservationSeam s_reservation_seam{nullptr};
40 // Native thread identities whose depth store must report failure. A fixed array keeps the seam exact and
41 // allocation-free while letting the cross-gate compositions refuse on two threads at once.
42 constexpr std::size_t MAX_STORE_FAILURE_THREADS = 4;
43 std::array<std::atomic<std::uint32_t>, MAX_STORE_FAILURE_THREADS> s_store_failure_threads{};
44
45 3284 [[nodiscard]] bool store_failure_armed(std::uint32_t thread) noexcept
46 {
47
2/2
✓ Branch 13 → 3 taken 13100 times.
✓ Branch 13 → 14 taken 3270 times.
16370 for (const std::atomic<std::uint32_t> &slot : s_store_failure_threads)
48 {
49
2/2
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 12 taken 13086 times.
26199 if (slot.load(std::memory_order_acquire) == thread)
50 {
51 13 return true;
52 }
53 }
54 3270 return false;
55 }
56 #endif
57
58 548 void register_teardown(TeardownRegistration &node) noexcept
59 {
60 548 ::AcquireSRWLockExclusive(&s_teardown_lock);
61 549 node.next = s_teardown_head;
62 549 s_teardown_head = &node;
63 549 s_teardown_open.store(true, std::memory_order_release);
64 549 ::ReleaseSRWLockExclusive(&s_teardown_lock);
65 549 }
66
67 549 void unregister_teardown(TeardownRegistration &node) noexcept
68 {
69 549 ::AcquireSRWLockExclusive(&s_teardown_lock);
70
1/2
✓ Branch 7 → 4 taken 549 times.
✗ Branch 7 → 8 not taken.
549 for (TeardownRegistration **link = &s_teardown_head; *link != nullptr; link = &(*link)->next)
71 {
72
1/2
✓ Branch 4 → 5 taken 549 times.
✗ Branch 4 → 6 not taken.
549 if (*link == &node)
73 {
74 549 *link = node.next;
75 549 break;
76 }
77 }
78 549 node.next = nullptr;
79 549 s_teardown_open.store(s_teardown_head != nullptr, std::memory_order_release);
80 549 ::ReleaseSRWLockExclusive(&s_teardown_lock);
81 549 }
82
83 13217 [[nodiscard]] bool teardown_registered(std::uint32_t thread) noexcept
84 {
85
2/2
✓ Branch 3 → 4 taken 13197 times.
✓ Branch 3 → 5 taken 20 times.
13217 if (!s_teardown_open.load(std::memory_order_acquire))
86 {
87 13197 return false;
88 }
89 20 bool found = false;
90 20 ::AcquireSRWLockShared(&s_teardown_lock);
91
2/2
✓ Branch 10 → 7 taken 22 times.
✓ Branch 10 → 11 taken 7 times.
29 for (const TeardownRegistration *node = s_teardown_head; node != nullptr; node = node->next)
92 {
93
2/2
✓ Branch 7 → 8 taken 13 times.
✓ Branch 7 → 9 taken 9 times.
22 if (node->thread == thread)
94 {
95 13 found = true;
96 13 break;
97 }
98 }
99 20 ::ReleaseSRWLockShared(&s_teardown_lock);
100 20 return found;
101 }
102
103 // The one store whose failure a caller's correctness depends on. Routed through a single function so the
104 // seam sits exactly where the platform call does and cannot drift away from the branch it drives.
105 3275 [[nodiscard]] bool store_depth(DWORD index, std::uintptr_t depth) noexcept
106 {
107 #if defined(DMK_ENABLE_TEST_SEAMS)
108
2/2
✓ Branch 4 → 5 taken 13 times.
✓ Branch 4 → 6 taken 3261 times.
3275 if (store_failure_armed(static_cast<std::uint32_t>(::GetCurrentThreadId())))
109 {
110 13 return false;
111 }
112 #endif
113 3261 return ::TlsSetValue(index, reinterpret_cast<void *>(depth)) != FALSE;
114 }
115
116 13562 [[nodiscard]] bool depth_recorded_for_this_thread() noexcept
117 {
118 13562 const DWORD index = s_depth_tls.index();
119
2/2
✓ Branch 3 → 4 taken 2919 times.
✓ Branch 3 → 5 taken 10640 times.
13559 if (index == TLS_OUT_OF_INDEXES)
120 {
121 2919 return false;
122 }
123 10640 return reinterpret_cast<std::uintptr_t>(::TlsGetValue(index)) != 0;
124 }
125 } // namespace
126
127 1968 DeliveryTlsOwner::DeliveryTlsOwner() noexcept
128 {
129 #if defined(DMK_ENABLE_TEST_SEAMS)
130
5/6
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 6 taken 1966 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 1966 times.
1968 if (s_reservation_seam != nullptr && s_depth_tls.index() == TLS_OUT_OF_INDEXES)
131 {
132 2 s_reservation_seam();
133 }
134 #endif
135 1968 m_reserved = s_depth_tls.acquire() != TLS_OUT_OF_INDEXES;
136 1968 }
137
138 1953 DeliveryTlsOwner::~DeliveryTlsOwner() noexcept
139 {
140 1953 s_depth_tls.release();
141 1958 }
142
143 15146 std::uint32_t current_native_thread_id() noexcept
144 {
145 15146 return static_cast<std::uint32_t>(::GetCurrentThreadId());
146 }
147
148 #if defined(DMK_ENABLE_TEST_SEAMS)
149 2 void set_delivery_scope_reservation_seam_for_test(DeliveryScopeReservationSeam seam) noexcept
150 {
151 2 s_reservation_seam = seam;
152 2 }
153
154 12 DWORD delivery_scope_tls_index_for_test() noexcept
155 {
156 12 return s_depth_tls.index();
157 }
158
159 2 std::uint32_t delivery_scope_tls_owners_for_test() noexcept
160 {
161 2 return s_depth_tls.owners();
162 }
163
164 20 bool set_delivery_scope_store_failure_for_test(bool fail) noexcept
165 {
166 20 const auto thread = static_cast<std::uint32_t>(::GetCurrentThreadId());
167
2/2
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 15 taken 10 times.
20 if (!fail)
168 {
169
1/2
✓ Branch 13 → 5 taken 13 times.
✗ Branch 13 → 14 not taken.
13 for (std::atomic<std::uint32_t> &slot : s_store_failure_threads)
170 {
171 13 std::uint32_t expected = thread;
172
2/2
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 12 taken 3 times.
26 if (slot.compare_exchange_strong(expected, 0, std::memory_order_acq_rel, std::memory_order_relaxed))
173 {
174 10 return true;
175 }
176 }
177 ✗ return true;
178 }
179
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 10 times.
10 if (store_failure_armed(thread))
180 {
181 ✗ return true;
182 }
183
1/2
✓ Branch 27 → 19 taken 13 times.
✗ Branch 27 → 28 not taken.
13 for (std::atomic<std::uint32_t> &slot : s_store_failure_threads)
184 {
185 13 std::uint32_t expected = 0;
186
2/2
✓ Branch 24 → 25 taken 10 times.
✓ Branch 24 → 26 taken 3 times.
26 if (slot.compare_exchange_strong(
187 expected,
188 thread,
189 std::memory_order_acq_rel,
190 std::memory_order_relaxed
191 ))
192 {
193 10 return true;
194 }
195 }
196 ✗ return false;
197 }
198 #endif
199
200 13561 bool current_thread_in_delivery() noexcept
201 {
202
2/2
✓ Branch 3 → 4 taken 341 times.
✓ Branch 3 → 5 taken 13220 times.
13561 if (depth_recorded_for_this_thread())
203 {
204 341 return true;
205 }
206 13220 return teardown_registered(current_native_thread_id());
207 }
208
209 3280 DeliveryScope::DeliveryScope() noexcept : m_index(s_depth_tls.index()), m_admitted(false)
210 {
211 // The enclosing gate owns the index, so it stays published until this frame ends.
212
2/2
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 3274 times.
3280 if (m_index == TLS_OUT_OF_INDEXES)
213 {
214 6 return;
215 }
216 3274 const DWORD index = m_index;
217 3274 const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(index));
218 // A store can fail for a high slot index whose lazily heap-allocated TEB expansion array cannot be grown
219 // under OOM. Leaving the depth understated would let a nested release wrongly conclude it is control-plane
220 // and block into the ABBA, so the frame is refused instead and the caller declines the delivery.
221
2/2
✓ Branch 7 → 8 taken 14 times.
✓ Branch 7 → 9 taken 3261 times.
3275 if (!store_depth(index, depth + 1))
222 {
223 14 return;
224 }
225 3261 m_admitted = true;
226 }
227
228 6529 DeliveryScope::~DeliveryScope() noexcept
229 {
230
2/2
✓ Branch 2 → 3 taken 20 times.
✓ Branch 2 → 4 taken 3254 times.
3274 if (!m_admitted)
231 {
232 20 return;
233 }
234 3254 const auto depth = reinterpret_cast<std::uintptr_t>(::TlsGetValue(m_index));
235 // The matching push succeeded, so this thread's expansion array for the slot already exists and the store
236 // cannot fail for want of one. Floor at zero defensively.
237 3255 (void)::TlsSetValue(m_index, reinterpret_cast<void *>(depth > 0 ? depth - 1 : 0));
238 3275 }
239
240 549 MandatoryDeliveryScope::MandatoryDeliveryScope() noexcept
241 {
242 549 m_registration.thread = current_native_thread_id();
243 548 register_teardown(m_registration);
244 549 }
245
246 549 MandatoryDeliveryScope::~MandatoryDeliveryScope() noexcept
247 {
248 549 unregister_teardown(m_registration);
249 549 }
250 } // namespace detail
251 } // namespace DetourModKit
252