| 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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
6 |
tearDownIfEmpty<F>(hook); |
| 849 |
|
|
|
|
} |
| 850 |
|
|
|
6 |
} |
| 851 |
|
|
|
|
}; |
| 852 |
|
|
|
|
|
| 853 |
|
|
|
|
#endif // CHRYSALIS_HOOKMANAGER_H |
| 854 |
|
|
|
|
|