Chrysalis - Hooks Test Coverage


integrations/hooks/
File: integrations/hooks/include/HooksManager.h
Date: 2026-08-27 18:35:56
Lines:
209/214
97.7%
Functions:
333/426
78.2%
Branches:
118/276
42.8%
Decisions:
37/50
74.0%
Calls:
580/1289
45.0%

Line Branch Decision Call Exec Source
1 #ifndef CHRYSALIS_HOOKMANAGER_H
2 #define CHRYSALIS_HOOKMANAGER_H
3
4 #include <list>
5 #include <regex>
6 #include <string>
7 #include <optional>
8
9 // ─── undef Qt's emit if it was defined before this header ────────────────────
10 // Qt defines `#define emit` (empty). asmjit (pulled in by polyhook) uses
11 // `emit` as a member-function identifier. If the macro is still live when
12 // asmjit is parsed, `Error emit(InstId instId)` becomes `Error (InstId instId)`
13 // and MSVC fails with "missing ')' before identifier 'instId'".
14 // We undef here defensively — this header does not depend on Qt and does not
15 // restore the macro. TUs that need Qt signal/slot syntax should include Qt
16 // headers after this file.
17 #ifdef emit
18 # undef emit
19 #endif
20
21 #include "types.h"
22 #include "Logging.h"
23 #include "BaseHook.h"
24 #include "HookHandle.h"
25 #include "HooksLibraryExport.h"
26
27 // =============================================================================
28 // HooksManager
29 // Manages x64 detour hooks on arbitrary free and member functions.
30 //
31 // Usage:
32 // HooksManager::addBefore<&QWidget::show>([](HookHandle h, QWidget*& self) {
33 // LOG_INFO("QWidget::show called on {}", self->objectName());
34 // });
35 // =============================================================================
36 class HOOKS HooksManager {
37 static constexpr auto LOGGER_NAME_ = "Hooks Manager";
38 // =========================================================================
39 // HookBase<R, Original, CallArgs...>
40 //
41 // All callback data lives in a heap-allocated instance created when the
42 // first callback is registered and destroyed when the last is removed.
43 // This ensures no static storage lingers after a hook is torn down.
44 //
45 // Only two members remain static:
46 // original — the trampoline pointer filled in by polyhook
47 // _instance — the bridge between the static hook() entry point and the
48 // live instance; set by Hook<> on creation, nulled on deletion
49 //
50 // @tparam R Return type of the hooked function
51 // @tparam Original Type of the trampoline (original function pointer)
52 // @tparam CallArgs Full argument list (first arg is Class* for members)
53 // =========================================================================
54 template<typename R, typename Original, typename... CallArgs>
55 struct HookBase {
56 // ── Callback types ────────────────────────────────────────────────────
57 // Before callbacks receive all call arguments by reference so they can
58 // inspect or modify them before the original function runs.
59 using Before = std::function<void(HookHandle, CallArgs &...)>;
60
61 // After callbacks additionally receive the return value (if non-void)
62 // so they can inspect or replace it. AfterType is used instead of
63 // std::conditional_t to avoid eagerly instantiating void& (ill-formed).
64 using After = AfterType<R, CallArgs...>::type;
65
66 // Replace callbacks fully substitute for the original call: they get
67 // the (mutable) call arguments and must produce the return value.
68 using Replace = ReplaceType<R, CallArgs...>::type;
69
70 // IgnoreConditionally callbacks decide, per call, whether the original
71 // should run — like Before, plus a leading bool& the callback sets.
72 using IgnoreConditionally = IgnoreConditionallyType<R, CallArgs...>::type;
73
74 // ── Instance data — allocated on first use, freed on last removal ─────
75 std::list<Before> _before;
76 std::list<After> _after;
77 std::optional<Replace> _replace; // only one Replace per function
78 std::optional<IgnoreConditionally> _ignoreConditionally; // only one per function
79 bool _ignoreActive = false; // only one Ignore per function (void only)
80 std::list<std::function<void()> > _executeLater;
81 void* _address = nullptr;
82
83 24 void addBefore(Before cb, std::optional<size_t> position = std::nullopt) {
84
1/2
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
9/11
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
24 insertAt(_before, std::move(cb), position);
85 24 }
86 20 void addAfter(After cb, std::optional<size_t> position = std::nullopt) {
87
1/2
✓ Branch 8 taken 10 times.
✗ Branch 9 not taken.
10/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 10 invoked.
✓ Call 11 invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
20 insertAt(_after, std::move(cb), position);
88 20 }
89 24 void removeBeforeAt(size_t position) {
90
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
24 eraseAt(_before, position);
91 24 }
92 20 void removeAfterAt(size_t position) {
93
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
20 eraseAt(_after, position);
94 20 }
95 12 void setReplace(Replace cb) {
96
6/6
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
12 _replace = std::move(cb);
97 12 }
98 8 void clearReplace() {
99
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
8 _replace.reset();
100 8 }
101 8 void setIgnoreConditionally(IgnoreConditionally cb) {
102
6/6
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
8 _ignoreConditionally = std::move(cb);
103 8 }
104 6 void clearIgnoreConditionally() {
105
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
6 _ignoreConditionally.reset();
106 6 }
107
108 // True once no Before/After/Replace/Ignore/IgnoreConditionally
109 // registration remains — the signal that the detour can be torn down.
110 120 bool isEmpty() const {
111
4/4
✓ Branch 10 taken 36 times.
✓ Branch 11 taken 11 times.
✓ Branch 18 taken 33 times.
✓ Branch 19 taken 3 times.
16/16
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✓ Call 14 invoked.
✓ Call 15 invoked.
✓ Call 16 invoked.
✓ Call 17 invoked.
214 return _before.empty() && _after.empty() && !_replace.has_value()
112
7/8
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 33 times.
✓ Branch 5 taken 29 times.
✓ Branch 6 taken 4 times.
✓ Branch 13 taken 23 times.
✓ Branch 14 taken 6 times.
7/7
✓ Call 2 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
214 && !_ignoreActive && !_ignoreConditionally.has_value();
113 }
114
115 // ── iterate ───────────────────────────────────────────────────────────
116 // Walks `list` and invokes each callback with a HookHandle that, when
117 // remove() is called, schedules erasure via _executeLater. Erasure
118 // itself only happens later, when a flush loop runs the queued
119 // closure (see hook()'s tail) — never synchronously from here, since
120 // that could invalidate the very list this loop is iterating.
121 // std::list is used deliberately: erasing by iterator is O(1) and
122 // does not invalidate any other iterators.
123 template<typename T, typename... Args>
124 112 void iterate(std::list<T> &list, Args &... args) {
125
2/2
✓ Branch 10 taken 24 times.
✓ Branch 11 taken 56 times.
2/2
✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 56 times.
10/10
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
160 for (auto it = list.begin(); it != list.end(); ++it) {
126
3/6
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
✓ Branch 13 taken 24 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 24 times.
✗ Branch 17 not taken.
17/22
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✓ Call 15 invoked.
✓ Call 18 invoked.
✓ Call 19 invoked.
✓ Call 20 invoked.
✓ Call 21 invoked.
✓ Call 22 invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
49 (*it)(HookHandle(_executeLater, [this, &list, it] {
127
2/60
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✗ Branch 62 not taken.
✗ Branch 63 not taken.
✗ Branch 66 not taken.
✗ Branch 67 not taken.
✗ Branch 80 not taken.
✗ Branch 81 not taken.
✗ Branch 84 not taken.
✗ Branch 85 not taken.
✗ Branch 98 not taken.
✗ Branch 99 not taken.
✗ Branch 102 not taken.
✗ Branch 103 not taken.
✗ Branch 116 not taken.
✗ Branch 117 not taken.
✗ Branch 120 not taken.
✗ Branch 121 not taken.
✗ Branch 134 not taken.
✗ Branch 135 not taken.
✗ Branch 138 not taken.
✗ Branch 139 not taken.
✗ Branch 152 not taken.
✗ Branch 153 not taken.
✗ Branch 156 not taken.
✗ Branch 157 not taken.
✗ Branch 170 not taken.
✗ Branch 171 not taken.
✗ Branch 174 not taken.
✗ Branch 175 not taken.
✗ Branch 188 not taken.
✗ Branch 189 not taken.
✗ Branch 192 not taken.
✗ Branch 193 not taken.
✓ Branch 206 taken 1 times.
✗ Branch 207 not taken.
✓ Branch 210 taken 1 times.
✗ Branch 211 not taken.
✗ Branch 224 not taken.
✗ Branch 225 not taken.
✗ Branch 228 not taken.
✗ Branch 229 not taken.
✗ Branch 242 not taken.
✗ Branch 243 not taken.
✗ Branch 246 not taken.
✗ Branch 247 not taken.
✗ Branch 260 not taken.
✗ Branch 261 not taken.
✗ Branch 264 not taken.
✗ Branch 265 not taken.
0/1
? Decision couldn't be analyzed.
12/210
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 21 not invoked.
✗ Call 22 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✗ Call 54 not invoked.
✗ Call 55 not invoked.
✗ Call 56 not invoked.
✗ Call 57 not invoked.
✗ Call 58 not invoked.
✗ Call 59 not invoked.
✗ Call 60 not invoked.
✗ Call 61 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 75 not invoked.
✗ Call 76 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 82 not invoked.
✗ Call 83 not invoked.
✗ Call 86 not invoked.
✗ Call 87 not invoked.
✗ Call 88 not invoked.
✗ Call 89 not invoked.
✗ Call 90 not invoked.
✗ Call 91 not invoked.
✗ Call 92 not invoked.
✗ Call 93 not invoked.
✗ Call 94 not invoked.
✗ Call 95 not invoked.
✗ Call 96 not invoked.
✗ Call 97 not invoked.
✗ Call 100 not invoked.
✗ Call 101 not invoked.
✗ Call 104 not invoked.
✗ Call 105 not invoked.
✗ Call 106 not invoked.
✗ Call 107 not invoked.
✗ Call 108 not invoked.
✗ Call 109 not invoked.
✗ Call 110 not invoked.
✗ Call 111 not invoked.
✗ Call 112 not invoked.
✗ Call 113 not invoked.
✗ Call 114 not invoked.
✗ Call 115 not invoked.
✗ Call 118 not invoked.
✗ Call 119 not invoked.
✗ Call 122 not invoked.
✗ Call 123 not invoked.
✗ Call 124 not invoked.
✗ Call 125 not invoked.
✗ Call 126 not invoked.
✗ Call 127 not invoked.
✗ Call 128 not invoked.
✗ Call 129 not invoked.
✗ Call 130 not invoked.
✗ Call 131 not invoked.
✗ Call 132 not invoked.
✗ Call 133 not invoked.
✗ Call 136 not invoked.
✗ Call 137 not invoked.
✗ Call 140 not invoked.
✗ Call 141 not invoked.
✗ Call 142 not invoked.
✗ Call 143 not invoked.
✗ Call 144 not invoked.
✗ Call 145 not invoked.
✗ Call 146 not invoked.
✗ Call 147 not invoked.
✗ Call 148 not invoked.
✗ Call 149 not invoked.
✗ Call 150 not invoked.
✗ Call 151 not invoked.
✗ Call 154 not invoked.
✗ Call 155 not invoked.
✗ Call 158 not invoked.
✗ Call 159 not invoked.
✗ Call 160 not invoked.
✗ Call 161 not invoked.
✗ Call 162 not invoked.
✗ Call 163 not invoked.
✗ Call 164 not invoked.
✗ Call 165 not invoked.
✗ Call 166 not invoked.
✗ Call 167 not invoked.
✗ Call 168 not invoked.
✗ Call 169 not invoked.
✗ Call 172 not invoked.
✗ Call 173 not invoked.
✗ Call 176 not invoked.
✗ Call 177 not invoked.
✗ Call 178 not invoked.
✗ Call 179 not invoked.
✗ Call 180 not invoked.
✗ Call 181 not invoked.
✗ Call 182 not invoked.
✗ Call 183 not invoked.
✗ Call 184 not invoked.
✗ Call 185 not invoked.
✗ Call 186 not invoked.
✗ Call 187 not invoked.
✗ Call 190 not invoked.
✗ Call 191 not invoked.
✗ Call 194 not invoked.
✗ Call 195 not invoked.
✗ Call 196 not invoked.
✗ Call 197 not invoked.
✓ Call 198 invoked.
✓ Call 199 invoked.
✓ Call 200 invoked.
✓ Call 201 invoked.
✓ Call 202 invoked.
✓ Call 203 invoked.
✓ Call 204 invoked.
✓ Call 205 invoked.
✓ Call 208 invoked.
✓ Call 209 invoked.
✓ Call 212 invoked.
✓ Call 213 invoked.
✗ Call 214 not invoked.
✗ Call 215 not invoked.
✗ Call 216 not invoked.
✗ Call 217 not invoked.
✗ Call 218 not invoked.
✗ Call 219 not invoked.
✗ Call 220 not invoked.
✗ Call 221 not invoked.
✗ Call 222 not invoked.
✗ Call 223 not invoked.
✗ Call 226 not invoked.
✗ Call 227 not invoked.
✗ Call 230 not invoked.
✗ Call 231 not invoked.
✗ Call 232 not invoked.
✗ Call 233 not invoked.
✗ Call 234 not invoked.
✗ Call 235 not invoked.
✗ Call 236 not invoked.
✗ Call 237 not invoked.
✗ Call 238 not invoked.
✗ Call 239 not invoked.
✗ Call 240 not invoked.
✗ Call 241 not invoked.
✗ Call 244 not invoked.
✗ Call 245 not invoked.
✗ Call 248 not invoked.
✗ Call 249 not invoked.
✗ Call 250 not invoked.
✗ Call 251 not invoked.
✗ Call 252 not invoked.
✗ Call 253 not invoked.
✗ Call 254 not invoked.
✗ Call 255 not invoked.
✗ Call 256 not invoked.
✗ Call 257 not invoked.
✗ Call 258 not invoked.
✗ Call 259 not invoked.
✗ Call 262 not invoked.
✗ Call 263 not invoked.
✗ Call 266 not invoked.
✗ Call 267 not invoked.
✗ Call 268 not invoked.
✗ Call 269 not invoked.
1 LOG_DEBUG_TO(LOGGER_NAME_, "Removing callback for hook: {}", getName(_address));
128
9/135
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 21 not invoked.
✗ Call 22 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 30 not invoked.
✗ Call 31 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
✗ Call 45 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 48 not invoked.
✗ Call 49 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✗ Call 54 not invoked.
✗ Call 55 not invoked.
✗ Call 56 not invoked.
✗ Call 57 not invoked.
✗ Call 58 not invoked.
✗ Call 59 not invoked.
✗ Call 60 not invoked.
✗ Call 61 not invoked.
✗ Call 62 not invoked.
✗ Call 63 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 66 not invoked.
✗ Call 67 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 75 not invoked.
✗ Call 76 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 80 not invoked.
✗ Call 81 not invoked.
✗ Call 82 not invoked.
✗ Call 83 not invoked.
✗ Call 84 not invoked.
✗ Call 85 not invoked.
✗ Call 86 not invoked.
✗ Call 87 not invoked.
✗ Call 88 not invoked.
✗ Call 89 not invoked.
✗ Call 90 not invoked.
✗ Call 91 not invoked.
✗ Call 92 not invoked.
✗ Call 93 not invoked.
✗ Call 94 not invoked.
✗ Call 95 not invoked.
✗ Call 96 not invoked.
✗ Call 97 not invoked.
✗ Call 98 not invoked.
✓ Call 99 invoked.
✓ Call 100 invoked.
✓ Call 101 invoked.
✓ Call 102 invoked.
✓ Call 103 invoked.
✓ Call 104 invoked.
✓ Call 105 invoked.
✓ Call 106 invoked.
✓ Call 107 invoked.
✗ Call 108 not invoked.
✗ Call 109 not invoked.
✗ Call 110 not invoked.
✗ Call 111 not invoked.
✗ Call 112 not invoked.
✗ Call 113 not invoked.
✗ Call 114 not invoked.
✗ Call 115 not invoked.
✗ Call 116 not invoked.
✗ Call 117 not invoked.
✗ Call 118 not invoked.
✗ Call 119 not invoked.
✗ Call 120 not invoked.
✗ Call 121 not invoked.
✗ Call 122 not invoked.
✗ Call 123 not invoked.
✗ Call 124 not invoked.
✗ Call 125 not invoked.
✗ Call 126 not invoked.
✗ Call 127 not invoked.
✗ Call 128 not invoked.
✗ Call 129 not invoked.
✗ Call 130 not invoked.
✗ Call 131 not invoked.
✗ Call 132 not invoked.
✗ Call 133 not invoked.
✗ Call 134 not invoked.
1 list.erase(it);
129 }), args...);
130 }
131 112 }
132
133 // ── hook ──────────────────────────────────────────────────────────────
134 // Static entry point installed as the detour target — polyhook requires
135 // a plain function pointer, so this cannot be a member function.
136 // It accesses all data through _instance, which is guaranteed non-null
137 // on entry (install() is only called after _instance is set).
138 //
139 // Deferred removals (from handle.remove(), called by any Before/
140 // Replace/IgnoreConditionally/After callback in THIS call) are
141 // flushed at the very end — after Before, Replace/Ignore, and After
142 // have all run — rather than at entry. This is the one safe moment
143 // to also tear the detour down if that flush left nothing registered:
144 // nothing after this point touches `inst` or the trampoline again, so
145 // unHook() invalidating them is harmless. Doing this any earlier (or
146 // synchronously mid-iteration) risks calling through an already-
147 // unhooked/freed trampoline later in the same call — this is exactly
148 // what caused a real crash during development; see git history/PR
149 // discussion for HooksManagerTest's *_RemoveViaHandle tests.
150 //
151 // Replace takes priority over the original call when set (it fully
152 // substitutes for it). Ignore (void functions only) simply skips the
153 // call. Both are mutually exclusive in practice, but if a user sets
154 // both, Replace wins since it is the more explicit instruction.
155 48 static R hook(CallArgs... args) {
156 48 auto *inst = _instance;
157
1/2
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
48 inst->iterate(inst->_before, args...);
158
159
1/1
✓ Call 0 invoked.
52 auto replaceHandle = [inst] {
160
16/81
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✓ Call 18 invoked.
✓ Call 19 invoked.
✓ Call 20 invoked.
✓ Call 21 invoked.
✓ Call 22 invoked.
✓ Call 23 invoked.
✓ Call 24 invoked.
✓ Call 25 invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 30 not invoked.
✗ Call 31 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
✗ Call 45 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 48 not invoked.
✗ Call 49 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✓ Call 54 invoked.
✓ Call 55 invoked.
✓ Call 56 invoked.
✓ Call 57 invoked.
✓ Call 58 invoked.
✓ Call 59 invoked.
✓ Call 60 invoked.
✓ Call 61 invoked.
✗ Call 62 not invoked.
✗ Call 63 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 66 not invoked.
✗ Call 67 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 75 not invoked.
✗ Call 76 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 80 not invoked.
14 return HookHandle(inst->_executeLater, [inst] {
161
5/45
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 21 not invoked.
✗ Call 22 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✓ Call 30 invoked.
✓ Call 31 invoked.
✓ Call 32 invoked.
✓ Call 33 invoked.
✓ Call 34 invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
1 inst->_replace.reset();
162
2/18
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✓ Branch 21 taken 1 times.
✗ Branch 22 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✓ Branch 57 taken 3 times.
✗ Branch 58 not taken.
✗ Branch 66 not taken.
✗ Branch 67 not taken.
✗ Branch 75 not taken.
✗ Branch 76 not taken.
10/63
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✓ Call 18 invoked.
✓ Call 19 invoked.
✓ Call 20 invoked.
✓ Call 23 invoked.
✓ Call 24 invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
✗ Call 45 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✓ Call 54 invoked.
✓ Call 55 invoked.
✓ Call 56 invoked.
✓ Call 59 invoked.
✓ Call 60 invoked.
✗ Call 61 not invoked.
✗ Call 62 not invoked.
✗ Call 63 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 80 not invoked.
16 });
163 };
164
1/1
✓ Call 0 invoked.
53 auto ignoreCondHandle = [inst] {
165
16/81
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 21 not invoked.
✗ Call 22 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 30 not invoked.
✗ Call 31 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
✗ Call 45 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 48 not invoked.
✗ Call 49 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✓ Call 54 invoked.
✓ Call 55 invoked.
✓ Call 56 invoked.
✓ Call 57 invoked.
✓ Call 58 invoked.
✓ Call 59 invoked.
✓ Call 60 invoked.
✓ Call 61 invoked.
✗ Call 62 not invoked.
✗ Call 63 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 66 not invoked.
✗ Call 67 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 75 not invoked.
✗ Call 76 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 80 not invoked.
17 return HookHandle(inst->_executeLater, [inst] {
166
5/45
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ Call 6 not invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 12 not invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 21 not invoked.
✗ Call 22 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✓ Call 30 invoked.
✓ Call 31 invoked.
✓ Call 32 invoked.
✓ Call 33 invoked.
✓ Call 34 invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 39 not invoked.
✗ Call 40 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
1 inst->_ignoreConditionally.reset();
167
2/18
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 48 not taken.
✗ Branch 49 not taken.
✓ Branch 57 taken 3 times.
✗ Branch 58 not taken.
✗ Branch 66 not taken.
✗ Branch 67 not taken.
✗ Branch 75 not taken.
✗ Branch 76 not taken.
10/63
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✗ Call 7 not invoked.
✗ Call 8 not invoked.
✗ Call 9 not invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
✗ Call 17 not invoked.
✗ Call 18 not invoked.
✗ Call 19 not invoked.
✗ Call 20 not invoked.
✗ Call 23 not invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 32 not invoked.
✗ Call 33 not invoked.
✗ Call 34 not invoked.
✗ Call 35 not invoked.
✗ Call 36 not invoked.
✗ Call 37 not invoked.
✗ Call 38 not invoked.
✗ Call 41 not invoked.
✗ Call 42 not invoked.
✗ Call 43 not invoked.
✗ Call 44 not invoked.
✗ Call 45 not invoked.
✗ Call 46 not invoked.
✗ Call 47 not invoked.
✗ Call 50 not invoked.
✗ Call 51 not invoked.
✗ Call 52 not invoked.
✗ Call 53 not invoked.
✓ Call 54 invoked.
✓ Call 55 invoked.
✓ Call 56 invoked.
✓ Call 59 invoked.
✓ Call 60 invoked.
✗ Call 61 not invoked.
✗ Call 62 not invoked.
✗ Call 63 not invoked.
✗ Call 64 not invoked.
✗ Call 65 not invoked.
✗ Call 68 not invoked.
✗ Call 69 not invoked.
✗ Call 70 not invoked.
✗ Call 71 not invoked.
✗ Call 72 not invoked.
✗ Call 73 not invoked.
✗ Call 74 not invoked.
✗ Call 77 not invoked.
✗ Call 78 not invoked.
✗ Call 79 not invoked.
✗ Call 80 not invoked.
20 });
168 };
169
170 if constexpr (std::is_void_v<R>) {
171
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 8 times.
2/2
✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 8 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
18 if (inst->_replace) {
172
2/4
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
10/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
2 (*inst->_replace)(replaceHandle(), args...);
173 } else {
174
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
16 bool ignore = inst->_ignoreActive;
175
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 6 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
16 if (inst->_ignoreConditionally) {
176
2/4
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
10/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
4 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, args...);
177 }
178
4/6
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
0/1
? Decision couldn't be analyzed.
1/1
✓ Call 4 invoked.
16 if (!ignore) original(args...);
179 }
180
1/2
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
18 inst->iterate(inst->_after, args...);
181
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
1/1
✓ Call 0 invoked.
18 finalize(inst);
182 } else if constexpr (std::is_reference_v<R>) {
183 // Reference return: a reference variable cannot be
184 // default-initialized, so use a pointer for result storage.
185 std::remove_reference_t<R>* result_ptr = nullptr;
186 if (inst->_replace) {
187 result_ptr = &(*inst->_replace)(replaceHandle(), args...);
188 } else {
189 bool ignore = inst->_ignoreActive;
190 if (inst->_ignoreConditionally) {
191 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, result_ptr, args...);
192 }
193 if (!ignore) result_ptr = &original(args...);
194 }
195 if (result_ptr) inst->iterate(inst->_after, *result_ptr, args...);
196 finalize(inst);
197 return *result_ptr; // NOLINT: caller must ensure result_ptr is non-null
198 } else {
199
1/1
✓ Call 0 invoked.
30 R result{};
200
2/2
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 12 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 12 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
30 if (inst->_replace) {
201
2/4
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
10/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
6 result = (*inst->_replace)(replaceHandle(), args...);
202 } else {
203
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
24 bool ignore = inst->_ignoreActive;
204
2/2
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 9 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 9 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
24 if (inst->_ignoreConditionally) {
205
2/4
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
10/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✗ Call 14 not invoked.
✗ Call 15 not invoked.
6 (*inst->_ignoreConditionally)(ignoreCondHandle(), ignore, result, args...);
206 }
207
4/6
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 times.
✓ Branch 5 taken 11 times.
✗ Branch 6 not taken.
0/1
? Decision couldn't be analyzed.
1/1
✓ Call 4 invoked.
24 if (!ignore) result = original(args...);
208 }
209
1/2
✓ Branch 5 taken 15 times.
✗ Branch 6 not taken.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
30 inst->iterate(inst->_after, result, args...);
210
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
1/1
✓ Call 0 invoked.
30 finalize(inst);
211
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
1/2
✓ Call 0 invoked.
✗ Call 2 not invoked.
60 return result;
212 }
213 18 }
214
215 // ── finalize ──────────────────────────────────────────────────────────
216 // Shared tail for every hook() variant in this file (this one and the
217 // four hand-rolled hidden-pointer ones below): flush deferred
218 // removals now that every callback for this call has had its chance
219 // to self-remove, then tear the detour down if nothing is left
220 // registered. Must be the LAST thing done with `inst` in the calling
221 // hook() — see hook()'s comment above for why the timing matters.
222 56 static void finalize(HookBase *inst) {
223
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
56 std::list<std::function<void()> > pending;
224
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
56 std::swap(inst->_executeLater, pending);
225
3/4
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 3 times.
✓ Branch 15 taken 28 times.
0/1
? Decision couldn't be analyzed.
12/12
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
62 for (auto &f: pending) f();
226
3/4
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 25 times.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
0/1
? Decision couldn't be analyzed.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
56 if (inst->isEmpty()) remove(inst->_address);
227
1/2
✓ Call 0 invoked.
✗ Call 1 not invoked.
112 }
228
229 // ── Static members ────────────────────────────────────────────────────
230 // Trampoline to the original function, filled in by polyhook.
231 static inline Original original = nullptr;
232
233 // Bridge from the static hook() entry point to the live instance.
234 // Owning pointer: Hook<Target> creates and destroys it.
235 static inline HookBase *_instance = nullptr;
236 };
237
238 // ── HookTraits forward declaration ────────────────────────────────────────
239 // Specialised below for free functions and member functions.
240 template<auto Function>
241 struct HookTraits;
242 // ── Hook<Target> ──────────────────────────────────────────────────────────
243 // Concrete hook stored in the map. Bridges the type-erased BaseHook
244 // with the typed HookTraits so addBefore/addAfter/addReplace/addIgnore
245 // remain type-safe.
246 //
247 // Owns the HookBase instance: creates it in the constructor, deletes it
248 // (and clears _instance) in the destructor. After destruction, all callback
249 // lists and associated memory are freed.
250 //
251 // Note: template constraint uses `requires` clause — MSVC C7600 rejects
252 // the shorthand `template<HookableFunction auto Target>` for non-type params.
253 template<auto Target> requires HookableFunction<Target>
254 struct Hook : BaseHook {
255
1/1
✓ Call 0 invoked.
46 explicit Hook(void *address)
256 : BaseHook(
257 HookTraits<Target>::address(),
258 reinterpret_cast<uint64_t>(&HookTraits<Target>::hook),
259
6/6
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
46 reinterpret_cast<uint64_t *>(&HookTraits<Target>::original)) {
260 // Allocate and wire up the instance BEFORE installing the detour.
261 // If install() came first, the hooked function could fire between
262 // _detour.hook() and the _instance assignment, hitting a null pointer.
263
1/2
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
3/3
✓ Call 0 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
46 auto *inst = new HookTraits<Target>();
264
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
46 inst->_address = address;
265 46 HookTraits<Target>::_instance = inst;
266
267
2/4
✓ Branch 5 taken 23 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 23 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 46 times.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
46 if (!install()) {
268 // Roll back: the detour never took effect (see install()'s
269 // comment for why), so the instance/address are pointing at
270 // an inert registration nothing will ever call into. Leaving
271 // it in place would make every addBefore/addAfter/etc. call
272 // silently do nothing forever — fail loudly instead so the
273 // caller finds out immediately, at the call site, rather
274 // than debugging "why doesn't my hook fire".
275
0/3
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ delete inst;
276 ✗ HookTraits<Target>::_instance = nullptr;
277
0/4
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 4 not invoked.
✗ Call 5 not invoked.
✗ throw std::runtime_error(
278 "HooksManager: failed to install detour — target function's "
279 "compiled prologue is too short to hook (this can happen with "
280 "trivial one-line functions under aggressive optimization)");
281 }
282
0/5
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
46 }
283
284 92 ~Hook() override {
285 // BaseHook's destructor calls unHook() first, guaranteeing the
286 // detour is removed before we free the instance. No in-flight hook()
287 // call can be using _instance after unHook() returns.
288 //
289 // Cast to the concrete type so the correct destructor is called
290 // without requiring a virtual destructor on HookBase.
291
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
46 delete static_cast<HookTraits<Target> *>(HookTraits<Target>::_instance);
292 46 HookTraits<Target>::_instance = nullptr;
293
1/2
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
7/7
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
138 }
294
295 24 void addBefore(HookTraits<Target>::Before cb, std::optional<size_t> position = std::nullopt) {
296
1/2
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
8/10
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
24 HookTraits<Target>::_instance->addBefore(std::move(cb), position);
297 24 }
298
299 20 void addAfter(HookTraits<Target>::After cb, std::optional<size_t> position = std::nullopt) {
300
1/2
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
8/10
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✗ Call 10 not invoked.
✗ Call 11 not invoked.
20 HookTraits<Target>::_instance->addAfter(std::move(cb), position);
301 20 }
302
303 24 void removeBeforeAt(size_t position) {
304
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
24 HookTraits<Target>::_instance->removeBeforeAt(position);
305 24 }
306
307 20 void removeAfterAt(size_t position) {
308
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
20 HookTraits<Target>::_instance->removeAfterAt(position);
309 20 }
310
311 12 void setReplace(HookTraits<Target>::Replace cb) {
312
8/8
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
12 HookTraits<Target>::_instance->setReplace(std::move(cb));
313 12 }
314
315 8 void clearReplace() {
316
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
8 HookTraits<Target>::_instance->clearReplace();
317 8 }
318
319 8 void setIgnoreConditionally(HookTraits<Target>::IgnoreConditionally cb) {
320
8/8
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
8 HookTraits<Target>::_instance->setIgnoreConditionally(std::move(cb));
321 8 }
322
323 6 void clearIgnoreConditionally() {
324
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
6 HookTraits<Target>::_instance->clearIgnoreConditionally();
325 6 }
326
327 12 void setIgnoreActive(bool active) {
328
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
12 HookTraits<Target>::_instance->_ignoreActive = active;
329 12 }
330
331 64 bool empty() const {
332
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
64 return HookTraits<Target>::_instance->isEmpty();
333 }
334 };
335
336 // =========================================================================
337 // HookTraits — free function specialization
338 // @tparam R Return type
339 // @tparam Args Argument types
340 // @tparam Function Pointer to the free function to hook
341 // =========================================================================
342 template<typename R, typename... Args, R(*Function)(Args...)>
343 struct HookTraits<Function> : HookBase<R, R(*)(Args...), Args...> {
344 32 static uint64_t address() {
345 32 return reinterpret_cast<uint64_t>(Function);
346 }
347 };
348
349 // =========================================================================
350 // HookTraits — non-const member function specialization
351 // @tparam R Return type
352 // @tparam Class Class that owns the member function
353 // @tparam Args Argument types (excluding implicit this)
354 // @tparam Function Pointer to the member function to hook
355 //
356 // Handles trivial and void R (no hidden return pointer involved — the
357 // compiler returns the value in a register either way, whether R is
358 // declared here as a real by-value return or the real member function's
359 // own return; both compile identically). Non-trivial, non-void R needs
360 // its own specialization below — see the comment there for why.
361 // =========================================================================
362 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
363 requires (std::is_trivial_v<R> || std::is_void_v<R>)
364 struct HookTraits<Function> : HookBase<R, R(*)(Class *, Args...), Class *, Args...> {
365 4 static uint64_t address() {
366 // Reinterpret a member function pointer as a raw address.
367 // A union is used because member pointers cannot be cast via
368 // reinterpret_cast directly — this is the standard workaround
369 // on MSVC/GCC/Clang for x64 single-inheritance vtable layouts.
370 union {
371 R (Class::*mfp)(Args...);
372 uint64_t addr;
373 } u;
374 4 u.mfp = Function;
375 4 return u.addr;
376 }
377 };
378
379 // =========================================================================
380 // HookTraits — non-const member function, NON-TRIVIAL non-void return
381 //
382 // Why this needs its own specialization instead of just declaring
383 // hook()/original with plain by-value return type R (as the trivial/void
384 // branch above does): R being non-trivial means the real, compiled
385 // Class::Function *itself* uses a hidden caller-allocated return pointer
386 // under the hood — but WHERE that pointer sits in the parameter list is
387 // a property of the *member function* calling convention specifically:
388 // • MSVC x64: this, hidden-return-pointer, then explicit args
389 // • Itanium ABI: hidden-return-pointer, this, then explicit args
390 // hook()/original, as declared here, are ordinary *free* functions that
391 // happen to take an explicit Class* parameter to model `this` — the
392 // compiler has no idea that parameter is meant to be `this`, so it
393 // applies the *free function* rule when deciding where to insert a
394 // hidden pointer (strictly first, before every explicit parameter,
395 // including the one modeling `this`) — which disagrees with where the
396 // real member function actually put it. Trivial/void R never triggers
397 // this because there's no hidden pointer to place either way, so both
398 // rules coincide and the plain by-value branch above works unmodified.
399 //
400 // The fix: declare the hidden pointer *explicitly*, by hand, in the
401 // platform-correct position, rather than relying on the compiler to
402 // insert one implicitly for a by-value R return. CallArgs (used to
403 // derive Before/After/Replace/IgnoreConditionally) deliberately excludes
404 // the pointer — those callback types stay the same plain-R-by-value
405 // shape used everywhere else in this file; only the low-level
406 // hook()/original signatures need to know about it.
407 // =========================================================================
408 #if defined(_MSC_VER)
409 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
410 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
411 struct HookTraits<Function> : HookBase<R, void(*)(Class *, R *, Args...), Class *, Args...> {
412 using Base = HookBase<R, void(*)(Class *, R *, Args...), Class *, Args...>;
413
414 static uint64_t address() {
415 union {
416 R (Class::*mfp)(Args...);
417 uint64_t addr;
418 } u;
419 u.mfp = Function;
420 return u.addr;
421 }
422
423 // See the class-level comment above for why this can't just be a
424 // plain by-value-returning hook(): `ret` is placed explicitly, by
425 // hand, in the position MSVC's x64 ABI actually uses for a member
426 // function (this, ret, args...) — it is NOT the compiler-inferred
427 // hidden pointer for a by-value return, which would land elsewhere.
428 //
429 // `*ret` is exactly ONE of: constructed by original() (normal path),
430 // or placement-constructed here from a Replace/IgnoreConditionally
431 // result (skip path) — never both, never neither.
432 static R* hook(Class* thiz, R* ret, Args... args) {
433
434 auto* inst = Base::_instance;
435 inst->iterate(inst->_before, thiz, args...);
436
437 if (inst->_replace) {
438 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
439 inst->_replace.reset();
440 }), thiz, args...));
441 } else {
442 bool ignore = inst->_ignoreActive; // always false here (Ignore is void-only)
443 R localResult{}; // safe placeholder — NOT *ret
444 if (inst->_ignoreConditionally) {
445 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
446 inst->_ignoreConditionally.reset();
447 }), ignore, localResult, thiz, args...);
448 }
449 if (!ignore) {
450 Base::original(thiz, ret, args...); // constructs *ret itself
451 } else {
452 new (ret) R(std::move(localResult)); // *ret still raw: construct from the substitute
453 }
454 }
455
456 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
457
458 Base::finalize(inst);
459
460 return ret;
461 }
462 };
463 #else
464 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...)>
465 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
466 struct HookTraits<Function> : HookBase<R, void(*)(R *, Class *, Args...), Class *, Args...> {
467 using Base = HookBase<R, void(*)(R *, Class *, Args...), Class *, Args...>;
468
469 static uint64_t address() {
470 union {
471 R (Class::*mfp)(Args...);
472 uint64_t addr;
473 } u;
474 u.mfp = Function;
475 return u.addr;
476 }
477
478 // Itanium ABI order: hidden return pointer first, then `this`. See
479 // the class-level comment above for the full explanation.
480 static R* hook(R* ret, Class* thiz, Args... args) {
481
482 auto* inst = Base::_instance;
483 inst->iterate(inst->_before, thiz, args...);
484
485 if (inst->_replace) {
486 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
487 inst->_replace.reset();
488 }), thiz, args...));
489 } else {
490 bool ignore = inst->_ignoreActive;
491 R localResult{};
492 if (inst->_ignoreConditionally) {
493 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
494 inst->_ignoreConditionally.reset();
495 }), ignore, localResult, thiz, args...);
496 }
497 if (!ignore) {
498 Base::original(ret, thiz, args...);
499 } else {
500 new (ret) R(std::move(localResult));
501 }
502 }
503
504 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
505
506 Base::finalize(inst);
507
508 return ret;
509 }
510 };
511 #endif
512
513 // =========================================================================
514 // HookTraits — const member function specialization
515 // Mirrors the non-const variant above, including the trivial/void vs
516 // non-trivial split and why it's needed — see the comment above the
517 // non-const non-trivial specialization for the full explanation. The
518 // implicit `this` pointer becomes `const Class*` throughout.
519 //
520 // Without this specialization any addBefore<&Foo::constMethod> call
521 // silently falls through to the undefined primary template and fails to
522 // compile. Examples: QSettings::value, QSettings::contains, QVariant::toString.
523 // =========================================================================
524 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
525 requires (std::is_trivial_v<R> || std::is_void_v<R>)
526 struct HookTraits<Function> : HookBase<R, R(*)(const Class *, Args...), const Class *, Args...> {
527 4 static uint64_t address() {
528 union {
529 R (Class::*mfp)(Args...) const;
530 uint64_t addr;
531 } u;
532 4 u.mfp = Function;
533 4 return u.addr;
534 }
535 };
536
537 #if defined(_MSC_VER)
538 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
539 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
540 struct HookTraits<Function> : HookBase<R, void(*)(const Class *, R *, Args...), const Class *, Args...> {
541 using Base = HookBase<R, void(*)(const Class *, R *, Args...), const Class *, Args...>;
542
543 static uint64_t address() {
544 union {
545 R (Class::*mfp)(Args...) const;
546 uint64_t addr;
547 } u;
548 u.mfp = Function;
549 return u.addr;
550 }
551
552 static R* hook(const Class* thiz, R* ret, Args... args) {
553 auto* inst = Base::_instance;
554 inst->iterate(inst->_before, thiz, args...);
555
556 if (inst->_replace) {
557 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
558 inst->_replace.reset();
559 }), thiz, args...));
560 } else {
561 bool ignore = inst->_ignoreActive;
562 R localResult{};
563 if (inst->_ignoreConditionally) {
564 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
565 inst->_ignoreConditionally.reset();
566 }), ignore, localResult, thiz, args...);
567 }
568 if (!ignore) {
569 Base::original(thiz, ret, args...);
570 } else {
571 new (ret) R(std::move(localResult));
572 }
573 }
574
575 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
576
577 Base::finalize(inst);
578
579 return ret;
580 }
581 };
582 #else
583 template<typename R, typename Class, typename... Args, R(Class::*Function)(Args...) const>
584 requires (!std::is_trivial_v<R> && !std::is_void_v<R>)
585 struct HookTraits<Function> : HookBase<R, void(*)(R *, const Class *, Args...), const Class *, Args...> {
586 using Base = HookBase<R, void(*)(R *, const Class *, Args...), const Class *, Args...>;
587
588 3 static uint64_t address() {
589 union {
590 R (Class::*mfp)(Args...) const;
591 uint64_t addr;
592 } u;
593 3 u.mfp = Function;
594 3 return u.addr;
595 }
596
597 4 static R* hook(R* ret, const Class* thiz, Args... args) {
598
599 4 auto* inst = Base::_instance;
600
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
4 inst->iterate(inst->_before, thiz, args...);
601
602
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 3 times.
2/2
✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 3 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
4 if (inst->_replace) {
603
2/6
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 18 taken 1 times.
✗ Branch 19 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
21/27
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✓ Call 16 invoked.
✓ Call 17 invoked.
✓ Call 20 invoked.
✓ Call 21 invoked.
✓ Call 22 invoked.
✓ Call 23 invoked.
✓ Call 24 invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
✗ Call 29 not invoked.
✗ Call 32 not invoked.
1 new (ret) R((*inst->_replace)(HookHandle(inst->_executeLater, [inst] {
604
0/5
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ inst->_replace.reset();
605 }), thiz, args...));
606 } else {
607
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
3 bool ignore = inst->_ignoreActive;
608
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
3 R localResult{};
609
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 1 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
3 if (inst->_ignoreConditionally) {
610
2/4
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 2 times.
✗ Branch 18 not taken.
20/25
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✓ Call 13 invoked.
✓ Call 16 invoked.
✓ Call 19 invoked.
✓ Call 20 invoked.
✓ Call 21 invoked.
✓ Call 22 invoked.
✓ Call 23 invoked.
✗ Call 24 not invoked.
✗ Call 25 not invoked.
✗ Call 26 not invoked.
✗ Call 27 not invoked.
✗ Call 28 not invoked.
2 (*inst->_ignoreConditionally)(HookHandle(inst->_executeLater, [inst] {
611
0/5
✗ Call 0 not invoked.
✗ Call 1 not invoked.
✗ Call 2 not invoked.
✗ Call 3 not invoked.
✗ Call 4 not invoked.
✗ inst->_ignoreConditionally.reset();
612 }), ignore, localResult, thiz, args...);
613 }
614
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 1 times.
3 if (!ignore) {
615
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
1/1
✓ Call 0 invoked.
2 Base::original(ret, thiz, args...);
616 } else {
617
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
1 new (ret) R(std::move(localResult));
618 }
619
1/2
✓ Call 0 invoked.
✗ Call 1 not invoked.
3 }
620
621
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 4 times.
✗ Decision 'false' not taken.
6/6
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
4 if (Base::_instance) inst->iterate(inst->_after, *ret, thiz, args...);
622
623
1/1
✓ Call 0 invoked.
4 Base::finalize(inst);
624
625 4 return ret;
626 }
627 };
628 #endif
629
630 // ── list helpers — positional insert/erase ────────────────────────────────
631 // Shared by Before/After lists so "insert at any place" / "remove via
632 // position" behave identically for both callback kinds.
633 template<typename T>
634 44 static void insertAt(std::list<T> &list, T item, std::optional<size_t> position) {
635
5/6
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 19 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 19 times.
✓ Branch 10 taken 3 times.
2/2
✓ Decision 'true' taken 38 times.
✓ Decision 'false' taken 6 times.
5/5
✓ Call 0 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
44 if (!position || *position >= list.size()) {
636
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
38 list.push_back(std::move(item));
637 } else {
638
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
6 auto it = list.begin();
639
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
6 std::advance(it, static_cast<long>(*position));
640
1/2
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
7/8
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 8 invoked.
✗ Call 9 not invoked.
6 list.insert(it, std::move(item));
641 }
642 44 }
643
644 template<typename T>
645 44 static void eraseAt(std::list<T> &list, size_t position) {
646
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 21 times.
2/2
✓ Decision 'true' taken 1 times.
✓ Decision 'false' taken 21 times.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
44 if (position >= list.size()) return;
647
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
42 auto it = list.begin();
648
1/1
✓ Call 0 invoked.
42 std::advance(it, static_cast<long>(position));
649
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
42 list.erase(it);
650 }
651 // ── getHook ───────────────────────────────────────────────────────────────
652 // Returns the existing Hook for F, or creates and registers a new one.
653 template<auto F> requires HookableFunction<F>
654 70 static Hook<F> *getHook() {
655 // Extract the opaque address from the function pointer.
656 union {
657 decltype(F) p;
658 void *addr;
659
1/1
✓ Call 0 invoked.
70 } u;
660 70 u.p = F;
661
662
1/2
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
70 const auto it = _hooks.find(u.addr);
663
2/2
✓ Branch 5 taken 23 times.
✓ Branch 6 taken 12 times.
2/2
✓ Decision 'true' taken 23 times.
✓ Decision 'false' taken 12 times.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
70 if (it == _hooks.end()) {
664
2/6
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 23 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
3/4
✓ Call 0 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✗ Call 9 not invoked.
46 auto *hook = new Hook<F>(u.addr);
665
1/2
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
2/2
✓ Call 0 invoked.
✓ Call 3 invoked.
46 _hooks[u.addr] = hook;
666
3/6
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 23 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 23 times.
✗ Branch 12 not taken.
9/11
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 9 invoked.
✓ Call 10 invoked.
✓ Call 13 invoked.
✓ Call 14 invoked.
✗ Call 15 not invoked.
✗ Call 16 not invoked.
46 LOG_INFO_TO(LOGGER_NAME_, "Hook has been created: {}", getName(u.addr));
667 46 return hook;
668 }
669
670
1/2
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
24 return dynamic_cast<Hook<F>*>(it->second);
671 }
672
673 // ── findHook ──────────────────────────────────────────────────────────────
674 // Like getHook, but never creates one. Used by the position/remove-style
675 // APIs, which are no-ops on a function that was never hooked.
676 template<auto F> requires HookableFunction<F>
677 72 static Hook<F> *findHook() {
678 union {
679 decltype(F) p;
680 void *addr;
681
1/1
✓ Call 0 invoked.
72 } u;
682 72 u.p = F;
683
684
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
2/2
✓ Call 0 invoked.
✓ Call 1 invoked.
72 const auto it = _hooks.find(u.addr);
685
2/2
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 32 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 32 times.
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
72 if (it == _hooks.end()) return nullptr;
686
1/2
✓ Branch 3 taken 32 times.
✗ Branch 4 not taken.
3/3
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
64 return dynamic_cast<Hook<F>*>(it->second);
687 }
688
689 // ── tearDownIfEmpty ───────────────────────────────────────────────────────
690 // After an external (non-handle) removal, detach the detour entirely once
691 // no Before/After/Replace/Ignore registration remains — same policy as
692 // the deferred, handle-based removal path in HookBase::finalize(), which
693 // runs this same check at the end of every hook() call.
694 template<auto F>
695 64 static void tearDownIfEmpty(Hook<F> *hook) {
696
2/2
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 12 times.
2/2
✓ Decision 'true' taken 20 times.
✓ Decision 'false' taken 12 times.
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
64 if (hook->empty()) {
697 union {
698 decltype(F) p;
699 void *addr;
700 } u;
701 40 u.p = F;
702
1/1
✓ Call 0 invoked.
40 remove(u.addr);
703 }
704 64 }
705
706 // ── remove ────────────────────────────────────────────────────────────────
707 // Unregisters and destroys the Hook for `address`. The Hook destructor
708 // unregisters the detour and deletes the HookBase instance, freeing all
709 // callback lists. Called automatically when the last callback is removed.
710 static void remove(void* address);
711
712 // ── getName ───────────────────────────────────────────────────────────────
713 // Extracts a human-readable "Class::method" label from the RTTI type name
714 // of the hook stored at `address`.
715 static std::string getName(void* address);
716
717 // ── _hooks ────────────────────────────────────────────────────────────────
718 // Global map from opaque function address to its live BaseHook.
719 // std::unordered_map gives O(1) average lookup.
720 static inline std::unordered_map<void*, BaseHook*> _hooks;
721
722 public:
723 // ── addBefore ─────────────────────────────────────────────────────────────
724 // Register a callback to run before function F. Multiple callbacks may be
725 // registered per function; by default the callback is appended, but an
726 // explicit `position` inserts it at that index (0 = front) instead.
727 // Creates the hook automatically on first registration.
728 //
729 // Compile-time guarantees:
730 // • F must be a free or member function pointer (HookableFunction)
731 // • Callback signature must match (HookHandle, Args&...) (BeforeCallbackFor)
732 template<auto F, typename Callback> requires BeforeCallbackFor<Callback, F>
733 24 static void addBefore(Callback &&callback, std::optional<size_t> position = std::nullopt) {
734
1/2
✓ Branch 9 taken 12 times.
✗ Branch 10 not taken.
11/13
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
24 getHook<F>()->addBefore(std::forward<Callback>(callback), position);
735 24 }
736
737 // ── removeBefore ──────────────────────────────────────────────────────────
738 // Remove the Before callback at `position` (0-based) without needing a
739 // HookHandle. No-op if F isn't hooked or position is out of range.
740 template<auto F> requires HookableFunction<F>
741 26 static void removeBefore(size_t position) {
742
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 1 times.
2/2
✓ Decision 'true' taken 12 times.
✓ Decision 'false' taken 1 times.
1/1
✓ Call 0 invoked.
26 if (auto *hook = findHook<F>()) {
743
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
24 hook->removeBeforeAt(position);
744
1/1
✓ Call 0 invoked.
24 tearDownIfEmpty<F>(hook);
745 }
746 26 }
747
748 // ── addAfter ──────────────────────────────────────────────────────────────
749 // Register a callback to run after function F. Multiple callbacks may be
750 // registered per function; by default the callback is appended, but an
751 // explicit `position` inserts it at that index (0 = front) instead.
752 // Creates the hook automatically on first registration.
753 //
754 // Compile-time guarantees:
755 // • F must be a free or member function pointer (HookableFunction)
756 // • Callback signature must match (HookHandle[, R&], Args&...) (AfterCallbackFor)
757 template<auto F, typename Callback> requires AfterCallbackFor<Callback, F>
758 20 static void addAfter(Callback &&callback, std::optional<size_t> position = std::nullopt) {
759
1/2
✓ Branch 9 taken 10 times.
✗ Branch 10 not taken.
11/13
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
20 getHook<F>()->addAfter(std::forward<Callback>(callback), position);
760 20 }
761
762 // ── removeAfter ───────────────────────────────────────────────────────────
763 // Remove the After callback at `position` (0-based) without needing a
764 // HookHandle. No-op if F isn't hooked or position is out of range.
765 template<auto F> requires HookableFunction<F>
766 22 static void removeAfter(size_t position) {
767
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 1 times.
2/2
✓ Decision 'true' taken 10 times.
✓ Decision 'false' taken 1 times.
1/1
✓ Call 0 invoked.
22 if (auto *hook = findHook<F>()) {
768
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
20 hook->removeAfterAt(position);
769
1/1
✓ Call 0 invoked.
20 tearDownIfEmpty<F>(hook);
770 }
771 22 }
772
773 // ── addReplace ────────────────────────────────────────────────────────────
774 // Register the (single) callback that completely replaces function F: the
775 // original body is never invoked, and the callback itself must produce
776 // the return value. Registering again simply overwrites the previous
777 // replacement, since only one Replace can exist per function. The
778 // callback receives a HookHandle exactly like Before/After, so it can
779 // remove itself the same way (handle.remove() inside the callback).
780 //
781 // Compile-time guarantees:
782 // • F must be a free or member function pointer (HookableFunction)
783 // • Callback signature must match (HookHandle, Args&...) -> R (ReplaceCallbackFor)
784 template<auto F, typename Callback> requires ReplaceCallbackFor<Callback, F>
785 12 static void addReplace(Callback &&callback) {
786
1/2
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
11/13
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
12 getHook<F>()->setReplace(std::forward<Callback>(callback));
787 12 }
788
789 // ── removeReplace ─────────────────────────────────────────────────────────
790 // Remove the Replace callback for F (if any) without needing its
791 // HookHandle. No-op if F isn't hooked or has no Replace registered.
792 template<auto F> requires HookableFunction<F>
793 10 static void removeReplace() {
794
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1 times.
2/2
✓ Decision 'true' taken 4 times.
✓ Decision 'false' taken 1 times.
1/1
✓ Call 0 invoked.
10 if (auto *hook = findHook<F>()) {
795
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
8 hook->clearReplace();
796
1/1
✓ Call 0 invoked.
8 tearDownIfEmpty<F>(hook);
797 }
798 10 }
799
800 // ── addIgnoreConditionally ────────────────────────────────────────────────
801 // Register the (single) callback that decides, per call, whether the
802 // original body of F should run — like Before, but with a leading bool&
803 // the callback sets to skip the call. For non-void F it also receives a
804 // mutable result reference to supply a substitute value when it chooses
805 // to skip. This is the direct successor to the old callback-based Ignore
806 // hook, now capped at one registration per function. Registering again
807 // simply overwrites the previous callback.
808 //
809 // Compile-time guarantees:
810 // • F must be a free or member function pointer (HookableFunction)
811 // • Callback signature must match (HookHandle, bool&[, R&], Args&...) (IgnoreConditionallyCallbackFor)
812 template<auto F, typename Callback> requires IgnoreConditionallyCallbackFor<Callback, F>
813 8 static void addIgnoreConditionally(Callback &&callback) {
814
1/2
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
11/13
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
✓ Call 5 invoked.
✓ Call 6 invoked.
✓ Call 7 invoked.
✓ Call 8 invoked.
✓ Call 11 invoked.
✓ Call 12 invoked.
✗ Call 13 not invoked.
✗ Call 14 not invoked.
8 getHook<F>()->setIgnoreConditionally(std::forward<Callback>(callback));
815 8 }
816
817 // ── removeIgnoreConditionally ─────────────────────────────────────────────
818 // Remove the IgnoreConditionally callback for F (if any) without needing
819 // its HookHandle. No-op if F isn't hooked or has none registered.
820 template<auto F> requires HookableFunction<F>
821 8 static void removeIgnoreConditionally() {
822
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1 times.
2/2
✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 1 times.
1/1
✓ Call 0 invoked.
8 if (auto *hook = findHook<F>()) {
823
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
6 hook->clearIgnoreConditionally();
824
1/1
✓ Call 0 invoked.
6 tearDownIfEmpty<F>(hook);
825 }
826 8 }
827
828 // ── addIgnore ─────────────────────────────────────────────────────────────
829 // Activate Ignore for function F: the original body stops being called
830 // (After callbacks, if any, still run). Only one Ignore state exists per
831 // function — it is a toggle, not a callback list — and it takes no
832 // callback argument because there is nothing for it to do besides skip
833 // the call. Restricted to void-returning functions (VoidHookableFunction):
834 // a non-void function has no sensible result to produce when skipped.
835 // Safe to call again after removeIgnore() to reactivate.
836 template<auto F> requires VoidHookableFunction<F>
837 6 static void addIgnore() {
838
5/5
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
✓ Call 4 invoked.
6 getHook<F>()->setIgnoreActive(true);
839 6 }
840
841 // ── removeIgnore ──────────────────────────────────────────────────────────
842 // Deactivate Ignore for function F, letting the original body run again.
843 // No-op if F isn't hooked or Ignore was never activated.
844 template<auto F> requires VoidHookableFunction<F>
845 6 static void removeIgnore() {
846
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
1/2
✓ Decision 'true' taken 3 times.
✗ Decision 'false' not taken.
1/1
✓ Call 0 invoked.
6 if (auto *hook = findHook<F>()) {
847
4/4
✓ Call 0 invoked.
✓ Call 1 invoked.
✓ Call 2 invoked.
✓ Call 3 invoked.
6 hook->setIgnoreActive(false);
848
1/1
✓ Call 0 invoked.
6 tearDownIfEmpty<F>(hook);
849 }
850 6 }
851 };
852
853 #endif // CHRYSALIS_HOOKMANAGER_H
854