GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 75.8% 238 / 0 / 314
Functions: 90.0% 18 / 0 / 20
Branches: 33.6% 229 / 0 / 681

src/hook_manager.cpp
Line Branch Exec Source
1 #include "DetourModKit/hook_manager.hpp"
2 #include "DetourModKit/format.hpp"
3
4 #include <algorithm>
5 #include <cstddef>
6 #include <format>
7
8 using namespace DetourModKit;
9 using namespace DetourModKit::Scanner;
10
11 namespace
12 {
13 struct DeferredLog
14 {
15 std::string msg;
16 LogLevel level;
17 };
18 } // anonymous namespace
19
20 90 HookManager &HookManager::get_instance()
21 {
22
5/10
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 9 taken 89 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 11 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 11 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
90 static HookManager instance;
23 90 return instance;
24 }
25
26 1 HookManager::HookManager(Logger &logger)
27 1 : m_logger(logger)
28 {
29
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 17 not taken.
1 m_allocator = safetyhook::Allocator::global();
30
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 13 taken 1 time.
1 if (!m_allocator)
31 {
32 m_logger.error("HookManager: Failed to get SafetyHook global allocator! Hook creation will fail.");
33 }
34 else
35 {
36
1/2
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 19 not taken.
1 m_logger.info("HookManager: SafetyHook global allocator obtained.");
37 }
38
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 20 not taken.
1 m_logger.info("HookManager: Initialized.");
39 1 }
40
41 1 HookManager::~HookManager() noexcept
42 {
43
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 18 not taken.
1 if (!m_shutdown_called.load(std::memory_order_acquire))
44 {
45 1 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
46
1/2
✗ Branch 14 → 7 not taken.
✓ Branch 14 → 15 taken 1 time.
1 for (auto &[name, hook] : m_hooks)
47 {
48 hook->disable();
49 }
50 1 m_hooks.clear();
51 1 }
52 1 }
53
54 9 void HookManager::shutdown()
55 {
56 9 bool expected = false;
57
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 9 times.
9 if (!m_shutdown_called.compare_exchange_strong(expected, true, std::memory_order_acq_rel))
58 return;
59
60 {
61
1/2
✓ Branch 5 → 6 taken 9 times.
✗ Branch 5 → 24 not taken.
9 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
62
2/2
✓ Branch 15 → 8 taken 4 times.
✓ Branch 15 → 16 taken 9 times.
13 for (auto &[name, hook] : m_hooks)
63 {
64
1/2
✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 21 not taken.
4 hook->disable();
65 }
66 9 m_hooks.clear();
67
68 // Reset under the lock so concurrent create_*_hook calls cannot
69 // observe the flag as true (rejected) and then immediately see it
70 // as false (accepted) before the map is fully cleared.
71 9 m_shutdown_called.store(false, std::memory_order_release);
72 9 }
73 }
74
75 std::string HookManager::error_to_string(const safetyhook::InlineHook::Error &err) const
76 {
77 const int type_int = static_cast<int>(err.type);
78 const auto ip_str = DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(err.ip));
79
80 switch (err.type)
81 {
82 case safetyhook::InlineHook::Error::BAD_ALLOCATION:
83 return std::format("SafetyHook InlineHook Error (Type: {}): Bad allocation (Allocator error: {})",
84 type_int, static_cast<int>(err.allocator_error));
85 case safetyhook::InlineHook::Error::FAILED_TO_DECODE_INSTRUCTION:
86 return std::format("SafetyHook InlineHook Error (Type: {}): Failed to decode instruction at address {}",
87 type_int, ip_str);
88 case safetyhook::InlineHook::Error::SHORT_JUMP_IN_TRAMPOLINE:
89 return std::format("SafetyHook InlineHook Error (Type: {}): Short jump found in trampoline at address {}",
90 type_int, ip_str);
91 case safetyhook::InlineHook::Error::IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE:
92 return std::format("SafetyHook InlineHook Error (Type: {}): IP-relative instruction out of range at address {}",
93 type_int, ip_str);
94 case safetyhook::InlineHook::Error::UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE:
95 return std::format("SafetyHook InlineHook Error (Type: {}): Unsupported instruction in trampoline at address {}",
96 type_int, ip_str);
97 case safetyhook::InlineHook::Error::FAILED_TO_UNPROTECT:
98 return std::format("SafetyHook InlineHook Error (Type: {}): Failed to unprotect memory at address {}",
99 type_int, ip_str);
100 case safetyhook::InlineHook::Error::NOT_ENOUGH_SPACE:
101 return std::format("SafetyHook InlineHook Error (Type: {}): Not enough space for the hook (prologue too short) at address {}",
102 type_int, ip_str);
103 default:
104 return std::format("SafetyHook InlineHook Error (Type: {}): Unknown error type", type_int);
105 }
106 }
107
108 std::string HookManager::error_to_string(const safetyhook::MidHook::Error &err) const
109 {
110 const int type_int = static_cast<int>(err.type);
111
112 switch (err.type)
113 {
114 case safetyhook::MidHook::Error::BAD_ALLOCATION:
115 return std::format("SafetyHook MidHook Error (Type: {}): Bad allocation (Allocator error: {})",
116 type_int, static_cast<int>(err.allocator_error));
117 case safetyhook::MidHook::Error::BAD_INLINE_HOOK:
118 return std::format("SafetyHook MidHook Error (Type: {}): Bad underlying inline hook. Details: {}",
119 type_int, error_to_string(err.inline_hook_error));
120 default:
121 return std::format("SafetyHook MidHook Error (Type: {}): Unknown error type", type_int);
122 }
123 }
124
125 // Non-locking internal helpers - caller must hold m_hooks_mutex
126 54 bool HookManager::hook_id_exists_locked(std::string_view hook_id) const
127 {
128
1/2
✓ Branch 4 → 5 taken 54 times.
✗ Branch 4 → 13 not taken.
54 const std::string key{hook_id};
129
1/2
✓ Branch 7 → 8 taken 54 times.
✗ Branch 7 → 16 not taken.
108 return m_hooks.find(key) != m_hooks.end();
130 54 }
131
132 39 std::expected<std::string, HookError> HookManager::create_inline_hook(
133 std::string_view name,
134 uintptr_t target_address,
135 void *detour_function,
136 void **original_trampoline,
137 const HookConfig &config)
138 {
139 39 auto [result, deferred_logs] = [&]() -> std::pair<std::expected<std::string, HookError>, std::vector<DeferredLog>>
140 {
141
1/2
✓ Branch 2 → 3 taken 39 times.
✗ Branch 2 → 441 not taken.
39 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
142
143
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 19 taken 39 times.
39 if (m_shutdown_called.load(std::memory_order_acquire))
144 {
145 return {std::unexpected(HookError::ShutdownInProgress),
146 {{std::format("HookManager: Shutdown in progress. Cannot create inline hook '{}'.", name), LogLevel::Error}}};
147 }
148
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 35 taken 39 times.
39 if (!m_allocator)
149 {
150 return {std::unexpected(HookError::AllocatorNotAvailable),
151 {{std::format("HookManager: Allocator not available. Cannot create inline hook '{}'.", name), LogLevel::Error}}};
152 }
153
2/2
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 50 taken 37 times.
39 if (target_address == 0)
154 {
155 2 return {std::unexpected(HookError::InvalidTargetAddress),
156
3/6
✓ Branch 40 → 41 taken 2 times.
✗ Branch 40 → 219 not taken.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 47 taken 2 times.
✗ Branch 223 → 224 not taken.
✗ Branch 223 → 225 not taken.
6 {{std::format("HookManager: Target address is NULL for inline hook '{}'.", name), LogLevel::Error}}};
157 }
158
2/2
✓ Branch 50 → 51 taken 2 times.
✓ Branch 50 → 65 taken 35 times.
37 if (detour_function == nullptr)
159 {
160 2 return {std::unexpected(HookError::InvalidDetourFunction),
161
3/6
✓ Branch 55 → 56 taken 2 times.
✗ Branch 55 → 239 not taken.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 60 → 62 taken 2 times.
✗ Branch 243 → 244 not taken.
✗ Branch 243 → 245 not taken.
6 {{std::format("HookManager: Detour function is NULL for inline hook '{}'.", name), LogLevel::Error}}};
162 }
163
2/2
✓ Branch 65 → 66 taken 1 time.
✓ Branch 65 → 80 taken 34 times.
35 if (original_trampoline == nullptr)
164 {
165 1 return {std::unexpected(HookError::InvalidTrampolinePointer),
166
3/6
✓ Branch 70 → 71 taken 1 time.
✗ Branch 70 → 259 not taken.
✓ Branch 75 → 76 taken 1 time.
✓ Branch 75 → 77 taken 1 time.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 265 not taken.
3 {{std::format("HookManager: Original trampoline pointer (output) is NULL for inline hook '{}'.", name), LogLevel::Error}}};
167 }
168 34 *original_trampoline = nullptr;
169
170
3/4
✓ Branch 80 → 81 taken 34 times.
✗ Branch 80 → 439 not taken.
✓ Branch 81 → 82 taken 1 time.
✓ Branch 81 → 96 taken 33 times.
34 if (hook_id_exists_locked(name))
171 {
172 1 return {std::unexpected(HookError::HookAlreadyExists),
173
3/6
✓ Branch 86 → 87 taken 1 time.
✗ Branch 86 → 279 not taken.
✓ Branch 91 → 92 taken 1 time.
✓ Branch 91 → 93 taken 1 time.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 285 not taken.
3 {{std::format("HookManager: A hook with the name '{}' already exists.", name), LogLevel::Error}}};
174 }
175
176 try
177 {
178 33 safetyhook::InlineHook::Flags sh_flags = config.inline_flags;
179
2/2
✓ Branch 96 → 97 taken 2 times.
✓ Branch 96 → 98 taken 31 times.
33 if (!config.auto_enable)
180 {
181 2 sh_flags = static_cast<safetyhook::InlineHook::Flags>(
182 2 static_cast<uint32_t>(sh_flags) | static_cast<uint32_t>(safetyhook::InlineHook::StartDisabled));
183 }
184
185 auto hook_creation_result = safetyhook::InlineHook::create(
186 33 m_allocator,
187 33 reinterpret_cast<void *>(target_address),
188 detour_function,
189
1/2
✓ Branch 98 → 99 taken 33 times.
✗ Branch 98 → 361 not taken.
33 sh_flags);
190
191
1/2
✗ Branch 100 → 101 not taken.
✓ Branch 100 → 120 taken 33 times.
33 if (!hook_creation_result)
192 {
193 return {std::unexpected(HookError::SafetyHookError),
194 {{std::format("HookManager: Failed to create SafetyHook::InlineHook for '{}' at {}. Error: {}",
195 name, DetourModKit::Format::format_address(target_address), error_to_string(hook_creation_result.error())),
196 LogLevel::Error}}};
197 }
198
199
2/4
✓ Branch 120 → 121 taken 33 times.
✗ Branch 120 → 359 not taken.
✓ Branch 123 → 124 taken 33 times.
✗ Branch 123 → 359 not taken.
66 auto sh_inline_hook_ptr = std::make_unique<safetyhook::InlineHook>(std::move(hook_creation_result.value()));
200 33 void *trampoline = sh_inline_hook_ptr->original<void *>();
201
202
2/2
✓ Branch 128 → 129 taken 31 times.
✓ Branch 128 → 130 taken 2 times.
33 HookStatus initial_status = sh_inline_hook_ptr->enabled() ? HookStatus::Active : HookStatus::Disabled;
203
204 // Pre-build log entries before committing to m_hooks so that
205 // allocation failures in std::format cannot leave a ghost hook.
206
3/4
✓ Branch 133 → 134 taken 31 times.
✓ Branch 133 → 135 taken 2 times.
✓ Branch 136 → 137 taken 33 times.
✗ Branch 136 → 325 not taken.
33 std::string status_message = (initial_status == HookStatus::Active) ? "and enabled" : "(disabled)";
207 33 std::vector<DeferredLog> logs;
208
1/2
✓ Branch 138 → 139 taken 33 times.
✗ Branch 138 → 353 not taken.
33 logs.reserve(2);
209 33 logs.push_back({std::format("HookManager: Successfully created {} inline hook '{}' targeting {}.",
210 status_message, name, DetourModKit::Format::format_address(target_address)),
211 LogLevel::Info});
212
213
3/4
✓ Branch 146 → 147 taken 2 times.
✓ Branch 146 → 154 taken 31 times.
✗ Branch 147 → 148 not taken.
✓ Branch 147 → 154 taken 2 times.
33 if (initial_status == HookStatus::Disabled && config.auto_enable)
214 {
215 logs.push_back({std::format("HookManager: Inline hook '{}' was configured for auto-enable but is currently disabled post-creation.", name),
216 LogLevel::Warning});
217 }
218
219
1/2
✓ Branch 156 → 157 taken 33 times.
✗ Branch 156 → 345 not taken.
99 std::string name_str{name};
220
1/2
✓ Branch 160 → 161 taken 33 times.
✗ Branch 160 → 351 not taken.
33 auto managed_hook = std::make_unique<InlineHook>(name_str, target_address, std::move(sh_inline_hook_ptr), initial_status);
221
1/2
✓ Branch 163 → 164 taken 33 times.
✗ Branch 163 → 348 not taken.
66 m_hooks.emplace(name_str, std::move(managed_hook));
222 33 *original_trampoline = trampoline;
223
224 66 return {std::move(name_str), std::move(logs)};
225 33 }
226 catch (const std::exception &e)
227 {
228 return {std::unexpected(HookError::UnknownError),
229 {{std::format("HookManager: An std::exception occurred during inline hook creation for '{}': {}", name, e.what()),
230 LogLevel::Error}}};
231 }
232 catch (...)
233 {
234 return {std::unexpected(HookError::UnknownError),
235 {{std::format("HookManager: An unknown exception occurred during inline hook creation for '{}'.", name),
236 LogLevel::Error}}};
237 }
238
13/112
✓ Branch 2 → 3 taken 39 times.
✗ Branch 2 → 29 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 189 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 209 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✓ Branch 37 → 38 taken 2 times.
✗ Branch 37 → 229 not taken.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 2 times.
✓ Branch 52 → 53 taken 2 times.
✗ Branch 52 → 249 not taken.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 2 times.
✓ Branch 67 → 68 taken 1 time.
✗ Branch 67 → 269 not taken.
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 79 taken 1 time.
✓ Branch 83 → 84 taken 1 time.
✗ Branch 83 → 289 not taken.
✗ Branch 93 → 94 not taken.
✓ Branch 93 → 95 taken 1 time.
✗ Branch 105 → 106 not taken.
✗ Branch 105 → 309 not taken.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✓ Branch 139 → 140 taken 33 times.
✗ Branch 139 → 336 not taken.
✓ Branch 140 → 141 taken 33 times.
✗ Branch 140 → 333 not taken.
✓ Branch 141 → 142 taken 33 times.
✗ Branch 141 → 328 not taken.
✗ Branch 143 → 144 not taken.
✓ Branch 143 → 145 taken 33 times.
✗ Branch 148 → 149 not taken.
✗ Branch 148 → 343 not taken.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 338 not taken.
✗ Branch 151 → 152 not taken.
✗ Branch 151 → 153 not taken.
✗ Branch 186 → 187 not taken.
✗ Branch 186 → 188 not taken.
✗ Branch 190 → 191 not taken.
✗ Branch 190 → 194 not taken.
✗ Branch 192 → 193 not taken.
✗ Branch 192 → 194 not taken.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
✗ Branch 210 → 211 not taken.
✗ Branch 210 → 214 not taken.
✗ Branch 212 → 213 not taken.
✗ Branch 212 → 214 not taken.
✗ Branch 226 → 227 not taken.
✗ Branch 226 → 228 not taken.
✗ Branch 230 → 231 not taken.
✗ Branch 230 → 234 not taken.
✗ Branch 232 → 233 not taken.
✗ Branch 232 → 234 not taken.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 248 not taken.
✗ Branch 250 → 251 not taken.
✗ Branch 250 → 254 not taken.
✗ Branch 252 → 253 not taken.
✗ Branch 252 → 254 not taken.
✗ Branch 266 → 267 not taken.
✗ Branch 266 → 268 not taken.
✗ Branch 270 → 271 not taken.
✗ Branch 270 → 274 not taken.
✗ Branch 272 → 273 not taken.
✗ Branch 272 → 274 not taken.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 288 not taken.
✗ Branch 290 → 291 not taken.
✗ Branch 290 → 294 not taken.
✗ Branch 292 → 293 not taken.
✗ Branch 292 → 294 not taken.
✗ Branch 306 → 307 not taken.
✗ Branch 306 → 308 not taken.
✗ Branch 316 → 317 not taken.
✗ Branch 316 → 320 not taken.
✗ Branch 318 → 319 not taken.
✗ Branch 318 → 320 not taken.
✗ Branch 330 → 331 not taken.
✗ Branch 330 → 332 not taken.
✗ Branch 340 → 341 not taken.
✗ Branch 340 → 342 not taken.
✗ Branch 366 → 367 not taken.
✗ Branch 366 → 404 not taken.
✗ Branch 376 → 377 not taken.
✗ Branch 376 → 378 not taken.
✗ Branch 381 → 382 not taken.
✗ Branch 381 → 427 not taken.
✗ Branch 391 → 392 not taken.
✗ Branch 391 → 393 not taken.
✗ Branch 393 → 176 not taken.
✗ Branch 393 → 439 not taken.
✗ Branch 401 → 402 not taken.
✗ Branch 401 → 403 not taken.
✗ Branch 406 → 407 not taken.
✗ Branch 406 → 410 not taken.
✗ Branch 408 → 409 not taken.
✗ Branch 408 → 410 not taken.
✗ Branch 424 → 425 not taken.
✗ Branch 424 → 426 not taken.
✗ Branch 428 → 429 not taken.
✗ Branch 428 → 432 not taken.
✗ Branch 430 → 431 not taken.
✗ Branch 430 → 432 not taken.
150 }();
239
240
2/2
✓ Branch 20 → 7 taken 39 times.
✓ Branch 20 → 21 taken 39 times.
117 for (const auto &entry : deferred_logs)
241 {
242
1/2
✓ Branch 10 → 11 taken 39 times.
✗ Branch 10 → 30 not taken.
39 m_logger.log(entry.level, entry.msg);
243 }
244 78 return result;
245
1/4
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 39 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
78 }
246
247 6 std::expected<std::string, HookError> HookManager::create_inline_hook_aob(
248 std::string_view name,
249 uintptr_t module_base,
250 size_t module_size,
251 std::string_view aob_pattern_str,
252 ptrdiff_t aob_offset,
253 void *detour_function,
254 void **original_trampoline,
255 const HookConfig &config)
256 {
257
1/2
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 35 not taken.
6 m_logger.debug("HookManager: Attempting AOB scan for inline hook '{}' with pattern: \"{}\", offset: {}.",
258
1/2
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 38 not taken.
12 name, aob_pattern_str, DetourModKit::Format::format_hex(aob_offset));
259
260
1/2
✓ Branch 5 → 6 taken 6 times.
✗ Branch 5 → 53 not taken.
6 auto pattern = parse_aob(aob_pattern_str);
261
2/2
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 14 taken 3 times.
6 if (!pattern.has_value())
262 {
263
1/2
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 39 not taken.
3 m_logger.error("HookManager: AOB pattern parsing failed for inline hook '{}'. Pattern: \"{}\".", name, aob_pattern_str);
264
1/2
✓ Branch 9 → 10 taken 3 times.
✗ Branch 9 → 11 not taken.
3 if (original_trampoline)
265 3 *original_trampoline = nullptr;
266 3 return std::unexpected(HookError::InvalidTargetAddress);
267 }
268
269
2/4
✓ Branch 14 → 15 taken 3 times.
✗ Branch 14 → 51 not taken.
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 51 not taken.
3 const std::byte *found_address_start = find_pattern(reinterpret_cast<const std::byte *>(module_base), module_size, pattern.value());
270
2/2
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 23 taken 2 times.
3 if (!found_address_start)
271 {
272
1/2
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 40 not taken.
1 m_logger.error("HookManager: AOB pattern not found for inline hook '{}'. Pattern: \"{}\".", name, aob_pattern_str);
273
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 20 not taken.
1 if (original_trampoline)
274 1 *original_trampoline = nullptr;
275 1 return std::unexpected(HookError::InvalidTargetAddress);
276 }
277
278 2 uintptr_t target_address = reinterpret_cast<uintptr_t>(found_address_start) + aob_offset;
279
1/2
✓ Branch 26 → 27 taken 2 times.
✗ Branch 26 → 41 not taken.
2 m_logger.info("HookManager: AOB pattern for inline hook '{}' found at {}. Applying offset {}. Final target hook address: {}.",
280
1/2
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 44 not taken.
4 name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(found_address_start)),
281
2/4
✓ Branch 23 → 24 taken 2 times.
✗ Branch 23 → 50 not taken.
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 47 not taken.
4 DetourModKit::Format::format_hex(aob_offset), DetourModKit::Format::format_address(target_address));
282
283
1/2
✓ Branch 30 → 31 taken 2 times.
✗ Branch 30 → 51 not taken.
2 return create_inline_hook(name, target_address, detour_function, original_trampoline, config);
284 6 }
285
286 23 std::expected<std::string, HookError> HookManager::create_mid_hook(
287 std::string_view name,
288 uintptr_t target_address,
289 safetyhook::MidHookFn detour_function,
290 const HookConfig &config)
291 {
292 23 auto [result, deferred_logs] = [&]() -> std::pair<std::expected<std::string, HookError>, std::vector<DeferredLog>>
293 {
294
1/2
✓ Branch 2 → 3 taken 23 times.
✗ Branch 2 → 404 not taken.
23 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
295
296
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 19 taken 23 times.
23 if (m_shutdown_called.load(std::memory_order_acquire))
297 {
298 return {std::unexpected(HookError::ShutdownInProgress),
299 {{std::format("HookManager: Shutdown in progress. Cannot create mid hook '{}'.", name), LogLevel::Error}}};
300 }
301
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 35 taken 23 times.
23 if (!m_allocator)
302 {
303 return {std::unexpected(HookError::AllocatorNotAvailable),
304 {{std::format("HookManager: Allocator not available. Cannot create mid hook '{}'.", name), LogLevel::Error}}};
305 }
306
2/2
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 50 taken 21 times.
23 if (target_address == 0)
307 {
308 2 return {std::unexpected(HookError::InvalidTargetAddress),
309
3/6
✓ Branch 40 → 41 taken 2 times.
✗ Branch 40 → 202 not taken.
✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 47 taken 2 times.
✗ Branch 206 → 207 not taken.
✗ Branch 206 → 208 not taken.
6 {{std::format("HookManager: Target address is NULL for mid hook '{}'.", name), LogLevel::Error}}};
310 }
311
2/2
✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 65 taken 20 times.
21 if (detour_function == nullptr)
312 {
313 1 return {std::unexpected(HookError::InvalidDetourFunction),
314
3/6
✓ Branch 55 → 56 taken 1 time.
✗ Branch 55 → 222 not taken.
✓ Branch 60 → 61 taken 1 time.
✓ Branch 60 → 62 taken 1 time.
✗ Branch 226 → 227 not taken.
✗ Branch 226 → 228 not taken.
3 {{std::format("HookManager: Detour function is NULL for mid hook '{}'.", name), LogLevel::Error}}};
315 }
316
317
3/4
✓ Branch 65 → 66 taken 20 times.
✗ Branch 65 → 402 not taken.
✓ Branch 66 → 67 taken 1 time.
✓ Branch 66 → 81 taken 19 times.
20 if (hook_id_exists_locked(name))
318 {
319 1 return {std::unexpected(HookError::HookAlreadyExists),
320
3/6
✓ Branch 71 → 72 taken 1 time.
✗ Branch 71 → 242 not taken.
✓ Branch 76 → 77 taken 1 time.
✓ Branch 76 → 78 taken 1 time.
✗ Branch 246 → 247 not taken.
✗ Branch 246 → 248 not taken.
3 {{std::format("HookManager: A hook with the name '{}' already exists.", name), LogLevel::Error}}};
321 }
322
323 try
324 {
325 19 safetyhook::MidHook::Flags sh_flags = config.mid_flags;
326
2/2
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 83 taken 17 times.
19 if (!config.auto_enable)
327 {
328 2 sh_flags = static_cast<safetyhook::MidHook::Flags>(
329 2 static_cast<uint32_t>(sh_flags) | static_cast<uint32_t>(safetyhook::MidHook::StartDisabled));
330 }
331
332 auto hook_creation_result = safetyhook::MidHook::create(
333 19 m_allocator,
334 19 reinterpret_cast<void *>(target_address),
335 detour_function,
336
1/2
✓ Branch 83 → 84 taken 19 times.
✗ Branch 83 → 324 not taken.
19 sh_flags);
337
338
1/2
✗ Branch 85 → 86 not taken.
✓ Branch 85 → 105 taken 19 times.
19 if (!hook_creation_result)
339 {
340 return {std::unexpected(HookError::SafetyHookError),
341 {{std::format("HookManager: Failed to create SafetyHook::MidHook for '{}' at {}. Error: {}",
342 name, DetourModKit::Format::format_address(target_address), error_to_string(hook_creation_result.error())),
343 LogLevel::Error}}};
344 }
345
346
2/4
✓ Branch 105 → 106 taken 19 times.
✗ Branch 105 → 322 not taken.
✓ Branch 108 → 109 taken 19 times.
✗ Branch 108 → 322 not taken.
38 auto sh_mid_hook_ptr = std::make_unique<safetyhook::MidHook>(std::move(hook_creation_result.value()));
347
348
2/2
✓ Branch 111 → 112 taken 17 times.
✓ Branch 111 → 113 taken 2 times.
19 HookStatus initial_status = sh_mid_hook_ptr->enabled() ? HookStatus::Active : HookStatus::Disabled;
349
350 // Pre-build log entries before committing to m_hooks so that
351 // allocation failures in std::format cannot leave a ghost hook.
352
3/4
✓ Branch 116 → 117 taken 17 times.
✓ Branch 116 → 118 taken 2 times.
✓ Branch 119 → 120 taken 19 times.
✗ Branch 119 → 288 not taken.
19 std::string status_message = (initial_status == HookStatus::Active) ? "and enabled" : "(disabled)";
353 19 std::vector<DeferredLog> logs;
354
1/2
✓ Branch 121 → 122 taken 19 times.
✗ Branch 121 → 316 not taken.
19 logs.reserve(2);
355 19 logs.push_back({std::format("HookManager: Successfully created {} mid hook '{}' targeting {}.",
356 status_message, name, DetourModKit::Format::format_address(target_address)),
357 LogLevel::Info});
358
359
3/4
✓ Branch 129 → 130 taken 2 times.
✓ Branch 129 → 137 taken 17 times.
✗ Branch 130 → 131 not taken.
✓ Branch 130 → 137 taken 2 times.
19 if (initial_status == HookStatus::Disabled && config.auto_enable)
360 {
361 logs.push_back({std::format("HookManager: Mid hook '{}' was configured for auto-enable but is currently disabled post-creation.", name),
362 LogLevel::Warning});
363 }
364
365
1/2
✓ Branch 139 → 140 taken 19 times.
✗ Branch 139 → 308 not taken.
57 std::string name_str{name};
366
1/2
✓ Branch 143 → 144 taken 19 times.
✗ Branch 143 → 314 not taken.
19 auto managed_hook = std::make_unique<MidHook>(name_str, target_address, std::move(sh_mid_hook_ptr), initial_status);
367
1/2
✓ Branch 146 → 147 taken 19 times.
✗ Branch 146 → 311 not taken.
38 m_hooks.emplace(name_str, std::move(managed_hook));
368
369 38 return {std::move(name_str), std::move(logs)};
370 19 }
371 catch (const std::exception &e)
372 {
373 return {std::unexpected(HookError::UnknownError),
374 {{std::format("HookManager: An std::exception occurred during mid hook creation for '{}': {}", name, e.what()),
375 LogLevel::Error}}};
376 }
377 catch (...)
378 {
379 return {std::unexpected(HookError::UnknownError),
380 {{std::format("HookManager: An unknown exception occurred during mid hook creation for '{}'.", name),
381 LogLevel::Error}}};
382 }
383
11/102
✓ Branch 2 → 3 taken 23 times.
✗ Branch 2 → 29 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 172 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 192 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✓ Branch 37 → 38 taken 2 times.
✗ Branch 37 → 212 not taken.
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 49 taken 2 times.
✓ Branch 52 → 53 taken 1 time.
✗ Branch 52 → 232 not taken.
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 1 time.
✓ Branch 68 → 69 taken 1 time.
✗ Branch 68 → 252 not taken.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 80 taken 1 time.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 272 not taken.
✗ Branch 100 → 101 not taken.
✗ Branch 100 → 102 not taken.
✓ Branch 122 → 123 taken 19 times.
✗ Branch 122 → 299 not taken.
✓ Branch 123 → 124 taken 19 times.
✗ Branch 123 → 296 not taken.
✓ Branch 124 → 125 taken 19 times.
✗ Branch 124 → 291 not taken.
✗ Branch 126 → 127 not taken.
✓ Branch 126 → 128 taken 19 times.
✗ Branch 131 → 132 not taken.
✗ Branch 131 → 306 not taken.
✗ Branch 132 → 133 not taken.
✗ Branch 132 → 301 not taken.
✗ Branch 134 → 135 not taken.
✗ Branch 134 → 136 not taken.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 177 not taken.
✗ Branch 175 → 176 not taken.
✗ Branch 175 → 177 not taken.
✗ Branch 189 → 190 not taken.
✗ Branch 189 → 191 not taken.
✗ Branch 193 → 194 not taken.
✗ Branch 193 → 197 not taken.
✗ Branch 195 → 196 not taken.
✗ Branch 195 → 197 not taken.
✗ Branch 209 → 210 not taken.
✗ Branch 209 → 211 not taken.
✗ Branch 213 → 214 not taken.
✗ Branch 213 → 217 not taken.
✗ Branch 215 → 216 not taken.
✗ Branch 215 → 217 not taken.
✗ Branch 229 → 230 not taken.
✗ Branch 229 → 231 not taken.
✗ Branch 233 → 234 not taken.
✗ Branch 233 → 237 not taken.
✗ Branch 235 → 236 not taken.
✗ Branch 235 → 237 not taken.
✗ Branch 249 → 250 not taken.
✗ Branch 249 → 251 not taken.
✗ Branch 253 → 254 not taken.
✗ Branch 253 → 257 not taken.
✗ Branch 255 → 256 not taken.
✗ Branch 255 → 257 not taken.
✗ Branch 269 → 270 not taken.
✗ Branch 269 → 271 not taken.
✗ Branch 279 → 280 not taken.
✗ Branch 279 → 283 not taken.
✗ Branch 281 → 282 not taken.
✗ Branch 281 → 283 not taken.
✗ Branch 293 → 294 not taken.
✗ Branch 293 → 295 not taken.
✗ Branch 303 → 304 not taken.
✗ Branch 303 → 305 not taken.
✗ Branch 329 → 330 not taken.
✗ Branch 329 → 367 not taken.
✗ Branch 339 → 340 not taken.
✗ Branch 339 → 341 not taken.
✗ Branch 344 → 345 not taken.
✗ Branch 344 → 390 not taken.
✗ Branch 354 → 355 not taken.
✗ Branch 354 → 356 not taken.
✗ Branch 356 → 159 not taken.
✗ Branch 356 → 402 not taken.
✗ Branch 364 → 365 not taken.
✗ Branch 364 → 366 not taken.
✗ Branch 369 → 370 not taken.
✗ Branch 369 → 373 not taken.
✗ Branch 371 → 372 not taken.
✗ Branch 371 → 373 not taken.
✗ Branch 387 → 388 not taken.
✗ Branch 387 → 389 not taken.
✗ Branch 391 → 392 not taken.
✗ Branch 391 → 395 not taken.
✗ Branch 393 → 394 not taken.
✗ Branch 393 → 395 not taken.
88 }();
384
385
2/2
✓ Branch 20 → 7 taken 23 times.
✓ Branch 20 → 21 taken 23 times.
69 for (const auto &entry : deferred_logs)
386 {
387
1/2
✓ Branch 10 → 11 taken 23 times.
✗ Branch 10 → 30 not taken.
23 m_logger.log(entry.level, entry.msg);
388 }
389 46 return result;
390
1/4
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 23 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
46 }
391
392 4 std::expected<std::string, HookError> HookManager::create_mid_hook_aob(
393 std::string_view name,
394 uintptr_t module_base,
395 size_t module_size,
396 std::string_view aob_pattern_str,
397 ptrdiff_t aob_offset,
398 safetyhook::MidHookFn detour_function,
399 const HookConfig &config)
400 {
401
1/2
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 31 not taken.
4 m_logger.debug("HookManager: Attempting AOB scan for mid hook '{}' with pattern: \"{}\", offset: {}.",
402
1/2
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 34 not taken.
8 name, aob_pattern_str, DetourModKit::Format::format_hex(aob_offset));
403
404
1/2
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 49 not taken.
4 auto pattern = parse_aob(aob_pattern_str);
405
2/2
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 12 taken 2 times.
4 if (!pattern.has_value())
406 {
407
1/2
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 35 not taken.
2 m_logger.error("HookManager: AOB pattern parsing failed for mid hook '{}'. Pattern: \"{}\".", name, aob_pattern_str);
408 2 return std::unexpected(HookError::InvalidTargetAddress);
409 }
410
411
2/4
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 47 not taken.
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 47 not taken.
2 const std::byte *found_address_start = find_pattern(reinterpret_cast<const std::byte *>(module_base), module_size, pattern.value());
412
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 19 taken 1 time.
2 if (!found_address_start)
413 {
414
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 36 not taken.
1 m_logger.error("HookManager: AOB pattern not found for mid hook '{}'. Pattern: \"{}\".", name, aob_pattern_str);
415 1 return std::unexpected(HookError::InvalidTargetAddress);
416 }
417
418 1 uintptr_t target_address = reinterpret_cast<uintptr_t>(found_address_start) + aob_offset;
419
1/2
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 37 not taken.
1 m_logger.info("HookManager: AOB pattern for mid hook '{}' found at {}. Applying offset {}. Final target hook address: {}.",
420
1/2
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 40 not taken.
2 name, DetourModKit::Format::format_address(reinterpret_cast<uintptr_t>(found_address_start)),
421
2/4
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 46 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 43 not taken.
2 DetourModKit::Format::format_hex(aob_offset), DetourModKit::Format::format_address(target_address));
422
423
1/2
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 47 not taken.
1 return create_mid_hook(name, target_address, detour_function, config);
424 4 }
425
426 16 bool HookManager::remove_hook(std::string_view hook_id)
427 {
428
1/2
✓ Branch 2 → 3 taken 16 times.
✗ Branch 2 → 44 not taken.
16 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
429
1/2
✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 31 not taken.
16 const std::string key{hook_id};
430
1/2
✓ Branch 7 → 8 taken 16 times.
✗ Branch 7 → 40 not taken.
16 auto it = m_hooks.find(key);
431
2/2
✓ Branch 10 → 11 taken 14 times.
✓ Branch 10 → 25 taken 2 times.
16 if (it != m_hooks.end())
432 {
433
1/2
✓ Branch 14 → 15 taken 14 times.
✗ Branch 14 → 38 not taken.
14 std::string name_of_removed_hook = it->second->get_name();
434 14 HookType type_of_removed_hook = it->second->get_type();
435
1/2
✓ Branch 18 → 19 taken 14 times.
✗ Branch 18 → 36 not taken.
14 m_hooks.erase(it);
436 m_logger.info("HookManager: Hook '{}' of type '{}' has been removed and unhooked.",
437
3/4
✓ Branch 19 → 20 taken 8 times.
✓ Branch 19 → 21 taken 6 times.
✓ Branch 22 → 23 taken 14 times.
✗ Branch 22 → 34 not taken.
14 name_of_removed_hook, (type_of_removed_hook == HookType::Inline ? "Inline" : "Mid"));
438 14 return true;
439 14 }
440
1/2
✓ Branch 25 → 26 taken 2 times.
✗ Branch 25 → 39 not taken.
2 m_logger.warning("HookManager: Attempted to remove hook with ID '{}', but it was not found.", hook_id);
441 2 return false;
442 16 }
443
444 185 void HookManager::remove_all_hooks()
445 {
446
1/2
✓ Branch 2 → 3 taken 185 times.
✗ Branch 2 → 21 not taken.
185 std::unique_lock<std::shared_mutex> lock(m_hooks_mutex);
447
2/2
✓ Branch 4 → 5 taken 30 times.
✓ Branch 4 → 10 taken 155 times.
185 if (!m_hooks.empty())
448 {
449 30 size_t num_hooks = m_hooks.size();
450
1/2
✓ Branch 6 → 7 taken 30 times.
✗ Branch 6 → 15 not taken.
30 m_logger.info("HookManager: Removing all {} managed hooks...", num_hooks);
451 30 m_hooks.clear();
452
1/2
✓ Branch 8 → 9 taken 30 times.
✗ Branch 8 → 16 not taken.
30 m_logger.info("HookManager: All {} managed hooks have been removed and unhooked.", num_hooks);
453 }
454 else
455 {
456
1/2
✓ Branch 10 → 11 taken 155 times.
✗ Branch 10 → 18 not taken.
155 m_logger.debug("HookManager: remove_all_hooks called, but no hooks were active to remove.");
457 }
458
459 // Reset shutdown flag to allow reuse after a full reset.
460 // Safe because all hooks have been cleared — no double-free risk.
461 185 m_shutdown_called.store(false, std::memory_order_release);
462 185 }
463
464 148 bool HookManager::enable_hook(std::string_view hook_id)
465 {
466
1/2
✓ Branch 2 → 3 taken 148 times.
✗ Branch 2 → 47 not taken.
148 std::shared_lock<std::shared_mutex> lock(m_hooks_mutex);
467
1/2
✓ Branch 5 → 6 taken 147 times.
✗ Branch 5 → 34 not taken.
148 const std::string key{hook_id};
468
1/2
✓ Branch 7 → 8 taken 147 times.
✗ Branch 7 → 43 not taken.
147 auto it = m_hooks.find(key);
469
2/2
✓ Branch 10 → 11 taken 42 times.
✓ Branch 10 → 13 taken 104 times.
147 if (it == m_hooks.end())
470 {
471
1/2
✓ Branch 11 → 12 taken 42 times.
✗ Branch 11 → 37 not taken.
42 m_logger.warning("HookManager: Hook ID '{}' not found for enable operation.", hook_id);
472 42 return false;
473 }
474
475 104 Hook *hook = it->second.get();
476
3/4
✓ Branch 15 → 16 taken 105 times.
✗ Branch 15 → 43 not taken.
✓ Branch 16 → 17 taken 11 times.
✓ Branch 16 → 19 taken 94 times.
104 if (hook->enable())
477 {
478
1/2
✓ Branch 17 → 18 taken 11 times.
✗ Branch 17 → 38 not taken.
11 m_logger.info("HookManager: Hook '{}' successfully enabled.", hook_id);
479 11 return true;
480 }
481
482 94 const auto status = hook->get_status();
483
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 23 taken 94 times.
94 if (status == HookStatus::Active)
484 {
485 m_logger.debug("HookManager: Hook '{}' is already active. Enable request ignored.", hook_id);
486 return true;
487 }
488
489
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 26 taken 94 times.
94 if (status == HookStatus::Disabled)
490 {
491 m_logger.error("HookManager: Failed to enable hook '{}'. Underlying SafetyHook call may have failed.", hook_id);
492 }
493 else
494 {
495
1/2
✓ Branch 27 → 28 taken 92 times.
✗ Branch 27 → 41 not taken.
94 m_logger.warning("HookManager: Hook '{}' cannot be enabled. Current status: {}", hook_id, Hook::status_to_string(status));
496 }
497 92 return false;
498 145 }
499
500 147 bool HookManager::disable_hook(std::string_view hook_id)
501 {
502
1/2
✓ Branch 2 → 3 taken 148 times.
✗ Branch 2 → 47 not taken.
147 std::shared_lock<std::shared_mutex> lock(m_hooks_mutex);
503
1/2
✓ Branch 5 → 6 taken 147 times.
✗ Branch 5 → 34 not taken.
148 const std::string key{hook_id};
504
1/2
✓ Branch 7 → 8 taken 147 times.
✗ Branch 7 → 43 not taken.
147 auto it = m_hooks.find(key);
505
2/2
✓ Branch 10 → 11 taken 42 times.
✓ Branch 10 → 13 taken 104 times.
147 if (it == m_hooks.end())
506 {
507
1/2
✓ Branch 11 → 12 taken 42 times.
✗ Branch 11 → 37 not taken.
42 m_logger.warning("HookManager: Hook ID '{}' not found for disable operation.", hook_id);
508 42 return false;
509 }
510
511 104 Hook *hook = it->second.get();
512
3/4
✓ Branch 15 → 16 taken 103 times.
✗ Branch 15 → 43 not taken.
✓ Branch 16 → 17 taken 10 times.
✓ Branch 16 → 19 taken 93 times.
105 if (hook->disable())
513 {
514
1/2
✓ Branch 17 → 18 taken 10 times.
✗ Branch 17 → 38 not taken.
10 m_logger.info("HookManager: Hook '{}' successfully disabled.", hook_id);
515 10 return true;
516 }
517
518 93 const auto status = hook->get_status();
519
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 23 taken 94 times.
94 if (status == HookStatus::Disabled)
520 {
521 m_logger.debug("HookManager: Hook '{}' is already disabled. Disable request ignored.", hook_id);
522 return true;
523 }
524
525
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 26 taken 94 times.
94 if (status == HookStatus::Active)
526 {
527 m_logger.error("HookManager: Failed to disable hook '{}'. Underlying SafetyHook call may have failed.", hook_id);
528 }
529 else
530 {
531
1/2
✓ Branch 27 → 28 taken 95 times.
✗ Branch 27 → 41 not taken.
94 m_logger.warning("HookManager: Hook '{}' cannot be disabled. Current status: {}", hook_id, Hook::status_to_string(status));
532 }
533 95 return false;
534 147 }
535
536 258 std::optional<HookStatus> HookManager::get_hook_status(std::string_view hook_id) const
537 {
538
1/2
✓ Branch 2 → 3 taken 260 times.
✗ Branch 2 → 31 not taken.
258 std::shared_lock<std::shared_mutex> lock(m_hooks_mutex);
539
1/2
✓ Branch 5 → 6 taken 258 times.
✗ Branch 5 → 24 not taken.
260 const std::string key{hook_id};
540
1/2
✓ Branch 7 → 8 taken 257 times.
✗ Branch 7 → 27 not taken.
258 auto it = m_hooks.find(key);
541
2/2
✓ Branch 10 → 11 taken 213 times.
✓ Branch 10 → 17 taken 44 times.
257 if (it != m_hooks.end())
542 {
543 213 return it->second->get_status();
544 }
545 44 return std::nullopt;
546 257 }
547
548 42 std::unordered_map<HookStatus, size_t> HookManager::get_hook_counts() const
549 {
550
1/2
✓ Branch 2 → 3 taken 42 times.
✗ Branch 2 → 32 not taken.
42 std::shared_lock<std::shared_mutex> lock(m_hooks_mutex);
551 42 std::unordered_map<HookStatus, size_t> counts;
552
1/2
✓ Branch 4 → 5 taken 42 times.
✗ Branch 4 → 22 not taken.
42 counts[HookStatus::Active] = 0;
553
1/2
✓ Branch 5 → 6 taken 42 times.
✗ Branch 5 → 23 not taken.
42 counts[HookStatus::Disabled] = 0;
554
1/2
✓ Branch 6 → 7 taken 42 times.
✗ Branch 6 → 24 not taken.
42 counts[HookStatus::Failed] = 0;
555
1/2
✓ Branch 7 → 8 taken 42 times.
✗ Branch 7 → 25 not taken.
42 counts[HookStatus::Removed] = 0;
556
2/2
✓ Branch 18 → 10 taken 1 time.
✓ Branch 18 → 19 taken 42 times.
43 for (const auto &[name, hook_ptr] : m_hooks)
557 {
558
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 26 not taken.
1 counts[hook_ptr->get_status()]++;
559 }
560 42 return counts;
561 42 }
562
563 54 std::vector<std::string> HookManager::get_hook_ids(std::optional<HookStatus> status_filter) const
564 {
565
1/2
✓ Branch 2 → 3 taken 54 times.
✗ Branch 2 → 31 not taken.
54 std::shared_lock<std::shared_mutex> lock(m_hooks_mutex);
566 54 std::vector<std::string> ids;
567
1/2
✓ Branch 4 → 5 taken 54 times.
✗ Branch 4 → 27 not taken.
54 ids.reserve(m_hooks.size());
568
2/2
✓ Branch 22 → 7 taken 7 times.
✓ Branch 22 → 23 taken 54 times.
61 for (const auto &[name, hook_ptr] : m_hooks)
569 {
570
5/8
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 16 taken 3 times.
✓ Branch 14 → 15 taken 4 times.
✗ Branch 14 → 26 not taken.
✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 7 times.
✗ Branch 18 → 20 not taken.
7 if (!status_filter.has_value() || hook_ptr->get_status() == status_filter.value())
571 {
572
1/2
✓ Branch 19 → 20 taken 7 times.
✗ Branch 19 → 26 not taken.
7 ids.push_back(name);
573 }
574 }
575 54 return ids;
576 54 }
577