./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
35 / 36
97.2%
Branches:
24 / 26
92.3%
Functions:
7 / 7
100.0%

src/internal/shared_tls_index.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP
2 #define DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP
3
4 /**
5 * @file internal/shared_tls_index.hpp
6 * @brief A Win32 TLS index that counted owners share and the last owner returns.
7 */
8
9 #include <windows.h>
10
11 #include <atomic>
12 #include <cstdint>
13
14 namespace DetourModKit::detail
15 {
16 /**
17 * @brief One Win32 TLS index that counted owners share.
18 * @details An owner is an object whose lifetime spans every write through the index on any thread. The one
19 * exception is a restore of null that runs before other code on its thread (pop_emit_frame). Each acquire
20 * reserves an index when none is published. The last release unpublishes the index before TlsFree. A
21 * later acquire reserves a fresh index. The owner count and index share one word, so a reservation cannot
22 * interleave with a return.
23 *
24 * A reader with a stale index reads its own slot, which TlsFree zeroes, so a read needs no owner.
25 * Constant initialization and a trivial destructor permit use after static destruction ([B-47]).
26 */
27 class SharedTlsIndex
28 {
29 public:
30 constexpr SharedTlsIndex() noexcept = default;
31 SharedTlsIndex(const SharedTlsIndex &) = delete;
32 SharedTlsIndex &operator=(const SharedTlsIndex &) = delete;
33
34 /**
35 * @brief Registers one owner, and reserves an index when none is published.
36 * @param error Receives GetLastError() from a failed reservation. Can be null.
37 * @return The published index, or TLS_OUT_OF_INDEXES when the process has none. The owner stays registered in
38 * both cases, so each call needs one @ref release.
39 */
40 18021 [[nodiscard]] DWORD acquire(DWORD *error = nullptr) noexcept
41 {
42 18021 DWORD spare = TLS_OUT_OF_INDEXES;
43 36041 std::uint64_t state = m_state.load(std::memory_order_acquire);
44 for (;;)
45 {
46 18026 DWORD index = index_of(state);
47
2/2
✓ Branch 11 → 12 taken 5785 times.
✓ Branch 11 → 19 taken 12238 times.
18023 if (index == TLS_OUT_OF_INDEXES)
48 {
49
1/2
✓ Branch 12 → 13 taken 5785 times.
✗ Branch 12 → 18 not taken.
5785 if (spare == TLS_OUT_OF_INDEXES)
50 {
51 5785 spare = ::TlsAlloc();
52
4/4
✓ Branch 14 → 15 taken 9 times.
✓ Branch 14 → 18 taken 5779 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 18 taken 8 times.
5788 if (spare == TLS_OUT_OF_INDEXES && error != nullptr)
53 {
54 1 *error = ::GetLastError();
55 }
56 }
57 5788 index = spare;
58 }
59
2/2
✓ Branch 26 → 27 taken 18021 times.
✓ Branch 26 → 31 taken 6 times.
36053 if (m_state.compare_exchange_weak(
60 state,
61 18026 pack(owners_of(state) + 1, index),
62 std::memory_order_acq_rel,
63 std::memory_order_acquire
64 ))
65 {
66 // A spare that lost to a published index was never visible to another thread.
67
4/4
✓ Branch 27 → 28 taken 5779 times.
✓ Branch 27 → 30 taken 12242 times.
✓ Branch 28 → 29 taken 3 times.
✓ Branch 28 → 30 taken 5776 times.
18021 if (spare != TLS_OUT_OF_INDEXES && spare != index)
68 {
69 3 (void)::TlsFree(spare);
70 }
71 18021 return index;
72 }
73 6 }
74 }
75
76 /// Deregisters one owner. The last owner unpublishes the index, then frees it.
77 17785 void release() noexcept
78 {
79 35568 std::uint64_t state = m_state.load(std::memory_order_acquire);
80 for (;;)
81 {
82 17880 const std::uint32_t owners = owners_of(state);
83
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 17892 times.
17892 if (owners == 0)
84 {
85 ✗ return;
86 }
87
2/2
✓ Branch 13 → 14 taken 12324 times.
✓ Branch 13 → 16 taken 5568 times.
17892 const std::uint64_t next = owners == 1 ? UNOWNED : pack(owners - 1, index_of(state));
88
2/2
✓ Branch 22 → 23 taken 17800 times.
✓ Branch 22 → 32 taken 97 times.
35784 if (m_state.compare_exchange_weak(state, next, std::memory_order_acq_rel, std::memory_order_acquire))
89 {
90
6/6
✓ Branch 23 → 24 taken 5566 times.
✓ Branch 23 → 27 taken 12234 times.
✓ Branch 25 → 26 taken 5564 times.
✓ Branch 25 → 27 taken 2 times.
✓ Branch 28 → 29 taken 5564 times.
✓ Branch 28 → 31 taken 12236 times.
17800 if (owners == 1 && index_of(state) != TLS_OUT_OF_INDEXES)
91 {
92 5564 (void)::TlsFree(index_of(state));
93 }
94 17801 return;
95 }
96 97 }
97 }
98
99 /// Returns the published index, or TLS_OUT_OF_INDEXES.
100 65504206 [[nodiscard]] DWORD index() const noexcept { return index_of(m_state.load(std::memory_order_acquire)); }
101
102 /// Returns the registered owner count.
103 2 [[nodiscard]] std::uint32_t owners() const noexcept
104 {
105 4 return owners_of(m_state.load(std::memory_order_acquire));
106 }
107
108 private:
109 30358 [[nodiscard]] static constexpr std::uint64_t pack(std::uint32_t owners, DWORD index) noexcept
110 {
111 30358 return (static_cast<std::uint64_t>(owners) << 32) | index;
112 }
113
114 32779272 [[nodiscard]] static constexpr DWORD index_of(std::uint64_t state) noexcept
115 {
116 32779272 return static_cast<DWORD>(state & 0xFFFFFFFFULL);
117 }
118
119 35926 [[nodiscard]] static constexpr std::uint32_t owners_of(std::uint64_t state) noexcept
120 {
121 35926 return static_cast<std::uint32_t>(state >> 32);
122 }
123
124 /// No owner and no published index: the high word is zero and the low word is TLS_OUT_OF_INDEXES.
125 static constexpr std::uint64_t UNOWNED = TLS_OUT_OF_INDEXES;
126 static_assert(std::atomic<std::uint64_t>::is_always_lock_free);
127
128 std::atomic<std::uint64_t> m_state{UNOWNED};
129 };
130 } // namespace DetourModKit::detail
131
132 #endif // DETOURMODKIT_INTERNAL_SHARED_TLS_INDEX_HPP
133