./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
50 / 50
100.0%
Branches:
8 / 8
100.0%
Functions:
35 / 35
100.0%

include/DetourModKit/address.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_ADDRESS_HPP
2 #define DETOURMODKIT_ADDRESS_HPP
3
4 /**
5 * @file address.hpp
6 * @brief The Address value type, the single addressing vocabulary of the public surface.
7 * @details Only `rip()` reads process memory. Only the pointer constructor, `as<T>()`, `ptr<T>()`, and `rip()` cast
8 * between an integer and a pointer.
9 */
10
11 #include "DetourModKit/defines.hpp"
12
13 #include <compare>
14 #include <cstddef>
15 #include <cstdint>
16 #include <cstring>
17 #include <type_traits>
18
19 namespace DetourModKit
20 {
21 /**
22 * @brief A strongly-typed machine address with constexpr arithmetic and an explicit cast surface.
23 * @details Address is trivially copyable and exactly one machine pointer in size and alignment.
24 */
25 class Address
26 {
27 208 std::uintptr_t m_value{0};
28
29 public:
30 /// Constructs a null address (numeric value zero).
31 3017 constexpr Address() noexcept = default;
32
33 /** @brief Constructs from a raw numeric address. */
34 26718 constexpr explicit Address(std::uintptr_t value) noexcept : m_value{value} {}
35
36 /** @brief Constructs a null address from `nullptr`. */
37 7 constexpr Address(std::nullptr_t) noexcept : m_value{0} {}
38
39 /** @brief Constructs from any object or function pointer. */
40 222450 template <class T> explicit Address(T *pointer) noexcept : m_value{reinterpret_cast<std::uintptr_t>(pointer)} {}
41
42 /// Returns the underlying numeric address.
43 263349 [[nodiscard]] constexpr std::uintptr_t raw() const noexcept { return m_value; }
44
45 /// Tests whether the address is non-null.
46 6990 [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_value != 0; }
47
48 /**
49 * @brief Returns this address advanced by a signed byte delta.
50 * @details The arithmetic wraps modulo 2^64, so a negative delta moves backward. A null base is also valid.
51 */
52 3955 [[nodiscard]] constexpr Address offset(std::ptrdiff_t delta) const noexcept
53 {
54 3955 return Address{m_value + static_cast<std::uintptr_t>(delta)};
55 }
56
57 /**
58 * @brief Returns this address rounded up to a multiple of @p alignment.
59 * @param alignment A power of two in bytes. Any other value yields a meaningless result, not a diagnostic.
60 * @details An address within `alignment - 1` bytes of the address-space top wraps modulo 2^64, so the result
61 * is then numerically below this address.
62 */
63 3 [[nodiscard]] constexpr Address align_up(std::size_t alignment) const noexcept
64 {
65 3 const std::uintptr_t mask = static_cast<std::uintptr_t>(alignment) - 1U;
66 3 return Address{(m_value + mask) & ~mask};
67 }
68
69 /**
70 * @brief Resolves a RIP-relative reference whose displacement is embedded in the instruction at this address.
71 * @param displacement_at Byte offset from this address to the signed 4-byte displacement field.
72 * @param instruction_length Total length of the instruction in bytes.
73 * @return The absolute target: (this + instruction_length) + sign-extended disp32.
74 * @details The read is unchecked. Use it only for a located and validated instruction whose bytes are mapped.
75 * For a guarded read, use scan::resolve_rip_relative().
76 */
77 1 [[nodiscard]] Address rip(std::ptrdiff_t displacement_at, std::size_t instruction_length) const noexcept
78 {
79 // The disp32 field is rarely 4-byte aligned, so memcpy is the defined unaligned load.
80 1 std::int32_t displacement = 0;
81 1 std::memcpy(
82 &displacement,
83 1 reinterpret_cast<const void *>(m_value + static_cast<std::uintptr_t>(displacement_at)),
84 sizeof(displacement)
85 );
86 1 const std::uintptr_t next_instruction = m_value + static_cast<std::uintptr_t>(instruction_length);
87 1 return Address{next_instruction + static_cast<std::uintptr_t>(static_cast<std::intptr_t>(displacement))};
88 }
89
90 /**
91 * @brief Reinterprets the address as a value of type @p T.
92 * @tparam T A pointer type, or an integral type at least as wide as `std::uintptr_t`. For a narrower integer,
93 * use @ref raw(). For a typed view of the addressed bytes, use @ref ptr().
94 * @note Callback-safe: a pure cast, with no allocation, lock, or I/O.
95 */
96 template <class T>
97 requires(std::is_pointer_v<T> || (std::is_integral_v<T> && sizeof(T) >= sizeof(std::uintptr_t)))
98 2382 [[nodiscard]] T as() const noexcept
99 {
100 if constexpr (std::is_integral_v<T>)
101 {
102 5 return static_cast<T>(m_value);
103 }
104 else
105 {
106 2377 return reinterpret_cast<T>(m_value);
107 }
108 }
109
110 /// Reinterprets the address as a `T*`, the shorthand for `as<T*>()`.
111 291 template <class T> [[nodiscard]] T *ptr() const noexcept { return reinterpret_cast<T *>(m_value); }
112
113 /// Orders and compares addresses by their numeric value.
114
6/6
✓ Branch 2 → 3 taken 31 times.
✓ Branch 2 → 6 taken 7 times.
✓ Branch 3 → 4 taken 15 times.
✓ Branch 3 → 5 taken 16 times.
✓ Branch 8 → 9 taken 31 times.
✓ Branch 8 → 10 taken 7 times.
45 [[nodiscard]] constexpr auto operator<=>(const Address &) const noexcept = default;
115
2/2
✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 4 taken 153 times.
170 [[nodiscard]] constexpr bool operator==(const Address &) const noexcept = default;
116 };
117
118 static_assert(
119 sizeof(Address) == sizeof(void *) && alignof(Address) == alignof(void *),
120 "Address must be exactly a machine pointer in size and alignment."
121 );
122 static_assert(std::is_trivially_copyable_v<Address>, "Address must stay trivially copyable.");
123
124 } // namespace DetourModKit
125
126 #endif // DETOURMODKIT_ADDRESS_HPP
127