./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
8 / 15
53.3%
Branches:
2 / 6
33.3%
Functions:
3 / 3
100.0%

src/internal/hook_coordinator.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP
2 #define DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP
3
4 /**
5 * @file internal/hook_coordinator.hpp
6 * @brief Backend-free hold on the process route coordinator for translation units outside the hook island.
7 * @details The backend serializes every patch transaction, including its trap window, through one process
8 * coordinator. A read of a code page outside that coordinator can record the transient trap protection
9 * (`[B-18]`, `[B-66]`, `[B-109]`). src/hook.cpp defines the hold, so this header names no backend type.
10 */
11
12 #include <cstddef>
13 #include <cstdint>
14 #include <string_view>
15
16 namespace DetourModKit::detail
17 {
18 /// Why a @ref BackendCoordinatorHold holds nothing.
19 enum class CoordinatorHoldRefusal : std::uint8_t
20 {
21 None,
22 /// Another thread held the coordinator for one backend wait slice. Only this cause clears by itself.
23 Timeout,
24 Poisoned,
25 Incompatible,
26 Unavailable,
27 };
28
29 /// The log fragment that names @p refusal.
30 [[nodiscard]] constexpr std::string_view
31 5 coordinator_hold_refusal_description(CoordinatorHoldRefusal refusal) noexcept
32 {
33
2/6
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✓ Branch 2 → 5 taken 4 times.
✓ Branch 2 → 6 taken 1 time.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 8 not taken.
5 switch (refusal)
34 {
35 ✗ case CoordinatorHoldRefusal::Timeout:
36 ✗ return "the process coordinator wait timed out";
37 ✗ case CoordinatorHoldRefusal::Poisoned:
38 ✗ return "the process coordinator is poisoned";
39 4 case CoordinatorHoldRefusal::Incompatible:
40 4 return "the process coordinator is incompatible";
41 1 case CoordinatorHoldRefusal::Unavailable:
42 1 return "the process coordinator is unavailable";
43 ✗ case CoordinatorHoldRefusal::None:
44 ✗ break;
45 }
46 ✗ return {};
47 }
48
49 /// Selects the @ref BackendCoordinatorHold form that waits until held. The names label its one wait line.
50 struct WaitUntilHeld
51 {
52 std::string_view where;
53 std::string_view name;
54 };
55
56 /**
57 * @class BackendCoordinatorHold
58 * @brief RAII acquisition of the backend process coordinator.
59 * @details The owning thread acquires again without a wait. A refused hold reports false and holds nothing.
60 * Position 4 of the hook lock order (docs/design/hooking.md `[B-109]`): a holder must not wait for a
61 * HookLedger target slot or reservation, take a counted module reference, or run a consumer callable.
62 * `HookInstallCoordinator.InstallToggleAndRetireKeepTheLockOrder` pins the order.
63 */
64 class BackendCoordinatorHold
65 {
66 public:
67 /// Waits for one bounded backend wait slice.
68 BackendCoordinatorHold() noexcept;
69
70 /**
71 * @brief Repeats the bounded backend wait while the cause is @ref CoordinatorHoldRefusal::Timeout, with no
72 * attempt count and no time cap. Any other cause refuses at once.
73 * @details After the first timed-out slice, logs one Warning that names @p wait and the holder thread. The
74 * line runs while this object holds no coordinator, so a wait that never ends stays visible.
75 */
76 explicit BackendCoordinatorHold(WaitUntilHeld wait) noexcept;
77
78 ~BackendCoordinatorHold() noexcept;
79 BackendCoordinatorHold(const BackendCoordinatorHold &) = delete;
80 BackendCoordinatorHold &operator=(const BackendCoordinatorHold &) = delete;
81
82 /// True while this object holds the coordinator.
83 34799 [[nodiscard]] explicit operator bool() const noexcept { return m_held; }
84
85 /// Why construction holds nothing, or @ref CoordinatorHoldRefusal::None.
86 10 [[nodiscard]] CoordinatorHoldRefusal refusal() const noexcept { return m_refusal; }
87
88 /// Releases the coordinator before scope exit. A second call is a no-op.
89 void release() noexcept;
90
91 /// Bytes reserved for the backend object. src/hook.cpp asserts the backend's size and alignment against it.
92 static constexpr std::size_t STORAGE_BYTES = 2 * sizeof(void *);
93
94 private:
95 /// Constructs the backend coordinator for one bounded wait and records the outcome.
96 void acquire() noexcept;
97
98 alignas(void *) unsigned char m_storage[STORAGE_BYTES]{};
99 bool m_constructed{false};
100 bool m_held{false};
101 CoordinatorHoldRefusal m_refusal{CoordinatorHoldRefusal::None};
102 };
103 } // namespace DetourModKit::detail
104
105 #endif // DETOURMODKIT_INTERNAL_HOOK_COORDINATOR_HPP
106