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 |