GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 30 / 0 / 30
Functions: 100.0% 9 / 0 / 9
Branches: 75.0% 6 / 0 / 8

src/internal/diagnostics_population.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INTERNAL_DIAGNOSTICS_POPULATION_HPP
2 #define DETOURMODKIT_INTERNAL_DIAGNOSTICS_POPULATION_HPP
3
4 /**
5 * @file diagnostics_population.hpp
6 * @brief Defines constant-initialized module-pin, hook-population, and lifecycle counters.
7 */
8
9 #include <array>
10 #include <atomic>
11 #include <cstddef>
12 #include <cstdint>
13
14 namespace DetourModKit::diagnostics
15 {
16 enum class ModulePinReason : std::uint8_t; // Full definition: DetourModKit/diagnostics.hpp.
17 } // namespace DetourModKit::diagnostics
18
19 namespace DetourModKit::detail
20 {
21 namespace hook_population
22 {
23 /// Active occupies the low 32 bits, total the high 32.
24 inline constexpr std::uint64_t ACTIVE_UNIT = 1ULL;
25 inline constexpr std::uint64_t TOTAL_UNIT = 1ULL << 32U;
26 inline constexpr std::uint64_t ACTIVE_MASK = 0xFFFFFFFFULL;
27
28 // Loader-lock teardown requires updates that cannot allocate or enter a lock-backed atomic implementation.
29 static_assert(
30 std::atomic<std::uint64_t>::is_always_lock_free,
31 "the hook population tally must be lock-free to stay callable under the loader lock"
32 );
33
34 // constinit makes any future dynamic initialization a compile-time error.
35 inline constinit std::atomic<std::uint64_t> s_counts{0};
36
37 /**
38 * @brief Records a completed install.
39 * @param active True if creation publishes Active. VMT hooks start Active. Inline and mid hooks start Disabled.
40 */
41 14768 inline void record_created(bool active) noexcept
42 {
43
2/2
✓ Branch 2 → 3 taken 103 times.
✓ Branch 2 → 4 taken 14665 times.
14768 s_counts.fetch_add(active ? TOTAL_UNIT + ACTIVE_UNIT : TOTAL_UNIT, std::memory_order_relaxed);
44 14768 }
45
46 /// Records a completed disabled-to-active transition.
47 18159 inline void record_enabled() noexcept
48 {
49 s_counts.fetch_add(ACTIVE_UNIT, std::memory_order_relaxed);
50 18159 }
51
52 /// Records a completed active-to-disabled transition.
53 17154 inline void record_disabled() noexcept
54 {
55 s_counts.fetch_sub(ACTIVE_UNIT, std::memory_order_relaxed);
56 17154 }
57
58 /**
59 * @brief Records removal from the live population.
60 * @param was_active True if the entry state counted this hook as Active. Teardown can force Disabled before it
61 * emits. Pass the entry state so removal subtracts the Active unit.
62 */
63 15655 inline void record_removed(bool was_active) noexcept
64 {
65
2/2
✓ Branch 2 → 3 taken 322 times.
✓ Branch 2 → 4 taken 15333 times.
15655 s_counts.fetch_sub(was_active ? TOTAL_UNIT + ACTIVE_UNIT : TOTAL_UNIT, std::memory_order_relaxed);
66 15655 }
67
68 /// Reads all three figures from one load, so they are always mutually consistent.
69 14045 inline void read(std::size_t &total, std::size_t &active, std::size_t &disabled) noexcept
70 {
71 14348 const std::uint64_t packed = s_counts.load(std::memory_order_relaxed);
72 14348 total = static_cast<std::size_t>(packed >> 32U);
73 14348 active = static_cast<std::size_t>(packed & ACTIVE_MASK);
74 14348 disabled = total - active;
75 14348 }
76 } // namespace hook_population
77
78 /**
79 * @brief Provides counters behind @ref DetourModKit::diagnostics::lifecycle_counters.
80 * @details Each relaxed atomic is an independent monotonic event tally with no cross-counter order obligation. The
81 * recorders run on reaper and teardown paths:
82 * - They allocate no memory.
83 * - They take no lock.
84 * - They make no Win32 call.
85 */
86 namespace lifecycle_observability
87 {
88 static_assert(
89 std::atomic<std::size_t>::is_always_lock_free,
90 "lifecycle observability must stay lock-free on teardown paths"
91 );
92
93 inline constinit std::atomic<std::size_t> s_reaper_started{0};
94 inline constinit std::atomic<std::size_t> s_permanent_pins{0};
95 inline constinit std::atomic<std::size_t> s_abandoned_owners{0};
96
97 /// Records the process-lifetime reaper thread launch and its permanent module reference.
98 10 inline void record_reaper_started() noexcept
99 {
100 s_reaper_started.fetch_add(1, std::memory_order_relaxed);
101 s_permanent_pins.fetch_add(1, std::memory_order_relaxed);
102 10 }
103
104 /// Records a failed retirement that the reaper retains permanently.
105 3 inline void record_abandoned_owner() noexcept
106 {
107 s_abandoned_owners.fetch_add(1, std::memory_order_relaxed);
108 3 }
109 } // namespace lifecycle_observability
110
111 /**
112 * @brief Provides the outstanding-count storage behind @ref DetourModKit::diagnostics::module_pin_count.
113 * @details One relaxed atomic per @ref DetourModKit::diagnostics::ModulePinReason holds acquires minus releases.
114 * The recorders run on install, teardown, and loader-lock paths:
115 * - They allocate no memory.
116 * - They take no lock.
117 * - They make no Win32 call.
118 */
119 namespace module_pin_observability
120 {
121 /// Mirrors ModulePinReason::Count. diagnostics.cpp static_asserts that the two values stay equal.
122 inline constexpr std::size_t MODULE_PIN_REASON_COUNT = 11;
123
124 static_assert(
125 std::atomic<std::size_t>::is_always_lock_free,
126 "module pin observability must stay lock-free on teardown paths"
127 );
128
129 inline constinit std::array<std::atomic<std::size_t>, MODULE_PIN_REASON_COUNT> s_outstanding{};
130
131 /// Records one counted module reference taken under @p reason.
132 3407 inline void note_acquired(DetourModKit::diagnostics::ModulePinReason reason) noexcept
133 {
134 3407 const auto index = static_cast<std::size_t>(reason);
135
1/2
✓ Branch 2 → 3 taken 3407 times.
✗ Branch 2 → 7 not taken.
3407 if (index < MODULE_PIN_REASON_COUNT)
136 {
137 3407 s_outstanding[index].fetch_add(1, std::memory_order_relaxed);
138 }
139 3407 }
140
141 /**
142 * @brief Records the release of one counted module reference taken under @p reason.
143 * @details A release must pass the reason its acquire passed.
144 * An unmatched release wraps the unsigned count.
145 * The wrap exposes the imbalance.
146 */
147 3255 inline void note_released(DetourModKit::diagnostics::ModulePinReason reason) noexcept
148 {
149 3255 const auto index = static_cast<std::size_t>(reason);
150
1/2
✓ Branch 2 → 3 taken 3255 times.
✗ Branch 2 → 7 not taken.
3255 if (index < MODULE_PIN_REASON_COUNT)
151 {
152 3255 s_outstanding[index].fetch_sub(1, std::memory_order_relaxed);
153 }
154 3255 }
155 } // namespace module_pin_observability
156 } // namespace DetourModKit::detail
157
158 #endif // DETOURMODKIT_INTERNAL_DIAGNOSTICS_POPULATION_HPP
159