src/internal/manifest_record_rules.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_MANIFEST_RECORD_RULES_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_MANIFEST_RECORD_RULES_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file internal/manifest_record_rules.hpp | ||
| 6 | * @brief Shared record validation rules for the manifest sibling TUs. | ||
| 7 | * @details src/manifest.cpp (checked serialization) and src/manifest_overlay.cpp (Signature compile/adopt) enforce | ||
| 8 | * the same record, label, value, binding, and baseline rules, so each rule is stated exactly once here. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include "DetourModKit/hook.hpp" | ||
| 12 | #include "DetourModKit/manifest.hpp" | ||
| 13 | |||
| 14 | #include "internal/scan_shared.hpp" | ||
| 15 | |||
| 16 | #include <cstddef> | ||
| 17 | #include <cstdint> | ||
| 18 | #include <expected> | ||
| 19 | #include <format> | ||
| 20 | #include <limits> | ||
| 21 | #include <optional> | ||
| 22 | #include <string_view> | ||
| 23 | |||
| 24 | namespace DetourModKit::manifest | ||
| 25 | { | ||
| 26 | 386 | [[nodiscard]] inline std::unexpected<Error> fail(ErrorCode code, const char *where) noexcept | |
| 27 | { | ||
| 28 | 386 | return std::unexpected(Error{code, where}); | |
| 29 | } | ||
| 30 | |||
| 31 | [[nodiscard]] constexpr bool | ||
| 32 | 10 | rip_pattern_spans_displacement(const scan::Pattern &pattern, std::size_t displacement_at) noexcept | |
| 33 | { | ||
| 34 | 10 | return DetourModKit::detail::min_match_suffix_length(DetourModKit::detail::pattern_buffer(pattern)) >= | |
| 35 | 10 | displacement_at + sizeof(std::int32_t); | |
| 36 | } | ||
| 37 | |||
| 38 | struct RungSectionName | ||
| 39 | { | ||
| 40 | std::string_view parent; | ||
| 41 | std::size_t index = 0; | ||
| 42 | }; | ||
| 43 | |||
| 44 | // A rung section always uses `[sig.<label>.rung.<N>]`. Treat malformed tails as ordinary labels. A label with | ||
| 45 | // ".rung." in the middle remains legal. | ||
| 46 | 267390 | [[nodiscard]] inline std::optional<RungSectionName> parse_rung_section_name(std::string_view name) noexcept | |
| 47 | { | ||
| 48 | 267390 | const std::size_t pos = name.rfind(".rung."); | |
| 49 |
2/2✓ Branch 3 → 4 taken 267202 times.
✓ Branch 3 → 5 taken 188 times.
|
267390 | if (pos == std::string_view::npos) |
| 50 | { | ||
| 51 | 267202 | return std::nullopt; | |
| 52 | } | ||
| 53 | 188 | const std::string_view tail = name.substr(pos + 6); | |
| 54 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 188 times.
|
188 | if (tail.empty()) |
| 55 | { | ||
| 56 | ✗ | return std::nullopt; | |
| 57 | } | ||
| 58 | 188 | std::size_t index = 0; | |
| 59 |
2/2✓ Branch 17 → 11 taken 256 times.
✓ Branch 17 → 18 taken 188 times.
|
444 | for (const char c : tail) |
| 60 | { | ||
| 61 |
2/4✓ Branch 11 → 12 taken 256 times.
✗ Branch 11 → 13 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 256 times.
|
256 | if (c < '0' || c > '9') |
| 62 | { | ||
| 63 | ✗ | return std::nullopt; | |
| 64 | } | ||
| 65 | 256 | const std::size_t digit = static_cast<std::size_t>(c - '0'); | |
| 66 | 256 | constexpr std::size_t MAX_INDEX = std::numeric_limits<std::size_t>::max(); | |
| 67 |
1/2✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 256 times.
|
256 | if (index > (MAX_INDEX - digit) / 10U) |
| 68 | { | ||
| 69 | ✗ | return std::nullopt; | |
| 70 | } | ||
| 71 | 256 | index = (index * 10U) + digit; | |
| 72 | } | ||
| 73 | 188 | return RungSectionName{ | |
| 74 | 188 | .parent = name.substr(0, pos), | |
| 75 | .index = index, | ||
| 76 | 188 | }; | |
| 77 | } | ||
| 78 | |||
| 79 | // A record label becomes its `[sig.<label>]` section name. Reject a label that cannot round-trip. Reject INI | ||
| 80 | // structural characters and embedded NUL. Reject a blank suffix because SimpleIni strips it and changes the | ||
| 81 | // key. Reject the reserved `.rung.<digits>` grammar. Check the full section name that parse() creates so a bare | ||
| 82 | // `rung.0` label also fails. | ||
| 83 | 602 | [[nodiscard]] inline bool label_is_serializable(std::string_view label) | |
| 84 | { | ||
| 85 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 602 times.
|
602 | if (label.empty()) |
| 86 | { | ||
| 87 | ✗ | return false; | |
| 88 | } | ||
| 89 |
6/6✓ Branch 6 → 7 taken 601 times.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 600 times.
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 13 taken 600 times.
|
602 | if (label.back() == ' ' || label.back() == '\t') |
| 90 | { | ||
| 91 | 2 | return false; | |
| 92 | } | ||
| 93 |
2/2✓ Branch 22 → 15 taken 4156 times.
✓ Branch 22 → 23 taken 587 times.
|
4743 | for (const char c : label) |
| 94 | { | ||
| 95 |
10/10✓ Branch 15 → 16 taken 4155 times.
✓ Branch 15 → 20 taken 1 time.
✓ Branch 16 → 17 taken 4152 times.
✓ Branch 16 → 20 taken 3 times.
✓ Branch 17 → 18 taken 4149 times.
✓ Branch 17 → 20 taken 3 times.
✓ Branch 18 → 19 taken 4146 times.
✓ Branch 18 → 20 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 21 taken 4143 times.
|
4156 | if (c == '\0' || c == '\r' || c == '\n' || c == '[' || c == ']') |
| 96 | { | ||
| 97 | 13 | return false; | |
| 98 | } | ||
| 99 | } | ||
| 100 |
1/2✓ Branch 23 → 24 taken 587 times.
✗ Branch 23 → 31 not taken.
|
587 | return !parse_rung_section_name(std::format("sig.{}", label)).has_value(); |
| 101 | } | ||
| 102 | |||
| 103 | // Validates every free-text value before compile, adopt, or checked serialization. Reject embedded NUL or '\r' | ||
| 104 | // because reload changes the contract. Reject a whitespace-prefixed "<<<" because raw output opens a heredoc. | ||
| 105 | // Reject a heredoc body line equal to "END_OF_TEXT" because the store truncates there. Apply the terminator | ||
| 106 | // scan only to values that use a heredoc. Raw values round-trip verbatim. | ||
| 107 | 2736 | [[nodiscard]] inline bool value_is_unserializable(std::string_view value) noexcept | |
| 108 | { | ||
| 109 |
6/6✓ Branch 3 → 4 taken 2735 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 5 → 6 taken 18 times.
✓ Branch 5 → 7 taken 2717 times.
✓ Branch 8 → 9 taken 19 times.
✓ Branch 8 → 10 taken 2717 times.
|
2736 | if (value.find('\0') != std::string_view::npos || value.find('\r') != std::string_view::npos) |
| 110 | { | ||
| 111 | 19 | return true; | |
| 112 | } | ||
| 113 | 2717 | std::string_view lead = value; | |
| 114 |
7/8✓ Branch 13 → 14 taken 280 times.
✓ Branch 13 → 19 taken 2443 times.
✓ Branch 15 → 16 taken 274 times.
✓ Branch 15 → 18 taken 6 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 274 times.
✓ Branch 20 → 11 taken 6 times.
✓ Branch 20 → 21 taken 2717 times.
|
2723 | while (!lead.empty() && (lead.front() == ' ' || lead.front() == '\t')) |
| 115 | { | ||
| 116 | 6 | lead.remove_prefix(1); | |
| 117 | } | ||
| 118 |
2/2✓ Branch 22 → 23 taken 24 times.
✓ Branch 22 → 24 taken 2693 times.
|
2717 | if (lead.starts_with("<<<")) |
| 119 | { | ||
| 120 | 24 | return true; | |
| 121 | } | ||
| 122 |
4/4✓ Branch 2 → 3 taken 435 times.
✓ Branch 2 → 4 taken 7 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 434 times.
|
442 | const auto is_edge_whitespace = [](char c) noexcept { return c == ' ' || c == '\t'; }; |
| 123 | const bool takes_heredoc_path = | ||
| 124 |
2/2✓ Branch 25 → 26 taken 2666 times.
✓ Branch 25 → 34 taken 27 times.
|
5359 | value.find('\n') != std::string_view::npos || |
| 125 |
6/6✓ Branch 27 → 28 taken 223 times.
✓ Branch 27 → 35 taken 2443 times.
✓ Branch 30 → 31 taken 219 times.
✓ Branch 30 → 34 taken 4 times.
✓ Branch 33 → 34 taken 4 times.
✓ Branch 33 → 35 taken 215 times.
|
2666 | (!value.empty() && (is_edge_whitespace(value.front()) || is_edge_whitespace(value.back()))); |
| 126 |
2/2✓ Branch 36 → 37 taken 2658 times.
✓ Branch 36 → 38 taken 35 times.
|
2693 | if (!takes_heredoc_path) |
| 127 | { | ||
| 128 | 2658 | return false; | |
| 129 | } | ||
| 130 | 35 | constexpr std::string_view heredoc_terminator = "END_OF_TEXT"; | |
| 131 | 35 | std::size_t line_start = 0; | |
| 132 |
2/2✓ Branch 59 → 39 taken 65 times.
✓ Branch 59 → 60 taken 12 times.
|
77 | while (line_start <= value.size()) |
| 133 | { | ||
| 134 | 65 | std::size_t line_end = value.find('\n', line_start); | |
| 135 |
2/2✓ Branch 40 → 41 taken 14 times.
✓ Branch 40 → 42 taken 51 times.
|
65 | if (line_end == std::string_view::npos) |
| 136 | { | ||
| 137 | 14 | line_end = value.size(); | |
| 138 | } | ||
| 139 | 65 | std::string_view line = value.substr(line_start, line_end - line_start); | |
| 140 |
8/8✓ Branch 46 → 47 taken 70 times.
✓ Branch 46 → 52 taken 2 times.
✓ Branch 48 → 49 taken 65 times.
✓ Branch 48 → 51 taken 5 times.
✓ Branch 50 → 51 taken 2 times.
✓ Branch 50 → 52 taken 63 times.
✓ Branch 53 → 44 taken 7 times.
✓ Branch 53 → 54 taken 65 times.
|
72 | while (!line.empty() && (line.back() == ' ' || line.back() == '\t')) |
| 141 | { | ||
| 142 | 7 | line.remove_suffix(1); | |
| 143 | } | ||
| 144 |
2/2✓ Branch 55 → 56 taken 23 times.
✓ Branch 55 → 57 taken 42 times.
|
65 | if (line == heredoc_terminator) |
| 145 | { | ||
| 146 | 23 | return true; | |
| 147 | } | ||
| 148 | 42 | line_start = line_end + 1; | |
| 149 | } | ||
| 150 | 12 | return false; | |
| 151 | } | ||
| 152 | |||
| 153 | // Resolution converts Utf16le evidence through detail::decode_utf8. Persistence uses that rule for all xref text. | ||
| 154 | // Utf8 evidence remains byte-transparent. | ||
| 155 | 675 | [[nodiscard]] inline bool xref_evidence_is_malformed(std::string_view text, scan::StringEncoding encoding) noexcept | |
| 156 | { | ||
| 157 |
4/4✓ Branch 2 → 3 taken 48 times.
✓ Branch 2 → 6 taken 627 times.
✓ Branch 4 → 5 taken 39 times.
✓ Branch 4 → 6 taken 9 times.
|
675 | return encoding == scan::StringEncoding::Utf16le && !DetourModKit::detail::utf8_is_well_formed(text); |
| 158 | } | ||
| 159 | |||
| 160 | // Every inert field must keep its default. An inert edit enters the drift fingerprint but is never emitted. | ||
| 161 | // A recaptured baseline therefore cannot survive its own save and reload. See fold_binding. | ||
| 162 | 456 | [[nodiscard]] inline bool binding_structure_is_valid(const Binding &binding) noexcept | |
| 163 | { | ||
| 164 | 456 | const Binding defaults{}; | |
| 165 | 456 | const bool offsets_inert = binding.offsets.empty(); | |
| 166 | 456 | const bool width_inert = binding.value_width == defaults.value_width; | |
| 167 | 456 | const bool register_inert = binding.read_register == defaults.read_register; | |
| 168 | 456 | const bool xmm_inert = binding.xmm_index == XMM_INDEX_UNUSED; | |
| 169 | 456 | const bool vmt_inert = binding.vmt_index == 0; | |
| 170 |
4/5✓ Branch 3 → 4 taken 423 times.
✓ Branch 3 → 12 taken 13 times.
✓ Branch 3 → 24 taken 12 times.
✓ Branch 3 → 33 taken 8 times.
✗ Branch 3 → 41 not taken.
|
456 | switch (binding.kind) |
| 171 | { | ||
| 172 | 423 | case BindingKind::Address: | |
| 173 |
7/10✓ Branch 4 → 5 taken 423 times.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 422 times.
✓ Branch 5 → 10 taken 1 time.
✓ Branch 6 → 7 taken 422 times.
✗ Branch 6 → 10 not taken.
✓ Branch 7 → 8 taken 422 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 420 times.
✓ Branch 8 → 10 taken 2 times.
|
423 | return offsets_inert && width_inert && register_inert && xmm_inert && vmt_inert; |
| 174 | 13 | case BindingKind::PointerChain: | |
| 175 | 13 | return !binding.offsets.empty() && | |
| 176 |
4/6✓ Branch 14 → 15 taken 12 times.
✗ Branch 14 → 18 not taken.
✓ Branch 15 → 16 taken 12 times.
✗ Branch 15 → 18 not taken.
✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 18 taken 10 times.
|
12 | (binding.value_width == 1 || binding.value_width == 2 || binding.value_width == 4 || |
| 177 |
4/4✓ Branch 17 → 18 taken 1 time.
✓ Branch 17 → 22 taken 1 time.
✓ Branch 18 → 19 taken 10 times.
✓ Branch 18 → 22 taken 1 time.
|
12 | binding.value_width == 8) && |
| 178 |
4/6✓ Branch 13 → 14 taken 12 times.
✓ Branch 13 → 22 taken 1 time.
✓ Branch 19 → 20 taken 10 times.
✗ Branch 19 → 22 not taken.
✓ Branch 20 → 21 taken 10 times.
✗ Branch 20 → 22 not taken.
|
25 | register_inert && xmm_inert && vmt_inert; |
| 179 | 12 | case BindingKind::MidHookRegister: | |
| 180 |
2/4✓ Branch 25 → 26 taken 12 times.
✗ Branch 25 → 31 not taken.
✓ Branch 26 → 27 taken 12 times.
✗ Branch 26 → 31 not taken.
|
12 | return offsets_inert && width_inert && vmt_inert && |
| 181 |
3/4✓ Branch 24 → 25 taken 12 times.
✗ Branch 24 → 31 not taken.
✓ Branch 27 → 28 taken 10 times.
✓ Branch 27 → 31 taken 2 times.
|
34 | static_cast<std::uint8_t>(binding.read_register) <= static_cast<std::uint8_t>(hook::Gpr::R15) && |
| 182 |
4/4✓ Branch 28 → 29 taken 3 times.
✓ Branch 28 → 30 taken 7 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 29 → 31 taken 1 time.
|
22 | (binding.xmm_index == XMM_INDEX_UNUSED || binding.xmm_index < 16); |
| 183 | 8 | case BindingKind::VmtMethod: | |
| 184 | { | ||
| 185 | // VmtHook bounds its captured table to 4096 methods, so no valid handle can expose a larger index. | ||
| 186 | 8 | constexpr std::size_t MAX_VMT_BINDING_SLOTS = 4096; | |
| 187 |
6/8✓ Branch 33 → 34 taken 7 times.
✓ Branch 33 → 39 taken 1 time.
✓ Branch 34 → 35 taken 7 times.
✗ Branch 34 → 39 not taken.
✓ Branch 35 → 36 taken 7 times.
✗ Branch 35 → 39 not taken.
✓ Branch 36 → 37 taken 6 times.
✓ Branch 36 → 39 taken 1 time.
|
14 | return offsets_inert && width_inert && register_inert && xmm_inert && |
| 188 |
2/2✓ Branch 37 → 38 taken 5 times.
✓ Branch 37 → 39 taken 1 time.
|
14 | binding.vmt_index < MAX_VMT_BINDING_SLOTS; |
| 189 | } | ||
| 190 | } | ||
| 191 | ✗ | return false; | |
| 192 | 456 | } | |
| 193 | |||
| 194 | // Persisted-enum range guards reject an out-of-range cast. Such an enum never becomes a permissive token that | ||
| 195 | // the author never expressed. AnchorKind's serializable set is not contiguous, so it needs an explicit | ||
| 196 | // membership test. | ||
| 197 | 626 | [[nodiscard]] constexpr bool is_serializable_anchor_kind(anchor::AnchorKind kind) noexcept | |
| 198 | { | ||
| 199 |
3/3✓ Branch 2 → 3 taken 616 times.
✓ Branch 2 → 4 taken 9 times.
✓ Branch 2 → 5 taken 1 time.
|
626 | switch (kind) |
| 200 | { | ||
| 201 | 616 | case anchor::AnchorKind::VtableIdentity: | |
| 202 | case anchor::AnchorKind::RipGlobal: | ||
| 203 | case anchor::AnchorKind::CodeOperand: | ||
| 204 | case anchor::AnchorKind::StringXref: | ||
| 205 | case anchor::AnchorKind::ExportName: | ||
| 206 | case anchor::AnchorKind::Manual: | ||
| 207 | 616 | return true; | |
| 208 | 9 | case anchor::AnchorKind::CallArgHome: | |
| 209 | case anchor::AnchorKind::Quorum: | ||
| 210 | case anchor::AnchorKind::Unset: | ||
| 211 | 9 | return false; | |
| 212 | } | ||
| 213 | 1 | return false; | |
| 214 | } | ||
| 215 | |||
| 216 | 134 | [[nodiscard]] constexpr bool is_valid_scan_mode(scan::Mode mode) noexcept | |
| 217 | { | ||
| 218 |
2/2✓ Branch 2 → 3 taken 133 times.
✓ Branch 2 → 4 taken 1 time.
|
134 | switch (mode) |
| 219 | { | ||
| 220 | 133 | case scan::Mode::Direct: | |
| 221 | case scan::Mode::RipRelative: | ||
| 222 | case scan::Mode::RttiVtable: | ||
| 223 | case scan::Mode::StringXref: | ||
| 224 | 133 | return true; | |
| 225 | } | ||
| 226 | 1 | return false; | |
| 227 | } | ||
| 228 | |||
| 229 | 598 | [[nodiscard]] constexpr bool is_valid_operand_kind(scan::OperandKind kind) noexcept | |
| 230 | { | ||
| 231 |
4/4✓ Branch 2 → 3 taken 13 times.
✓ Branch 2 → 4 taken 585 times.
✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 5 taken 2 times.
|
598 | return kind == scan::OperandKind::Immediate || kind == scan::OperandKind::MemoryDisplacement; |
| 232 | } | ||
| 233 | |||
| 234 | 729 | [[nodiscard]] constexpr bool is_valid_encoding(scan::StringEncoding encoding) noexcept | |
| 235 | { | ||
| 236 |
4/4✓ Branch 2 → 3 taken 37 times.
✓ Branch 2 → 4 taken 692 times.
✓ Branch 3 → 4 taken 34 times.
✓ Branch 3 → 5 taken 3 times.
|
729 | return encoding == scan::StringEncoding::Utf8 || encoding == scan::StringEncoding::Utf16le; |
| 237 | } | ||
| 238 | |||
| 239 | 726 | [[nodiscard]] constexpr bool is_valid_xref_return(scan::XrefReturn mode) noexcept | |
| 240 | { | ||
| 241 |
2/2✓ Branch 2 → 3 taken 723 times.
✓ Branch 2 → 4 taken 3 times.
|
726 | switch (mode) |
| 242 | { | ||
| 243 | 723 | case scan::XrefReturn::ReferencingInstruction: | |
| 244 | case scan::XrefReturn::EnclosingFunction: | ||
| 245 | case scan::XrefReturn::StringPointerSlot: | ||
| 246 | 723 | return true; | |
| 247 | } | ||
| 248 | 3 | return false; | |
| 249 | } | ||
| 250 | |||
| 251 | 592 | [[nodiscard]] constexpr bool is_valid_pages(scan::Pages pages) noexcept | |
| 252 | { | ||
| 253 |
4/4✓ Branch 2 → 3 taken 12 times.
✓ Branch 2 → 4 taken 580 times.
✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 5 taken 4 times.
|
592 | return pages == scan::Pages::Readable || pages == scan::Pages::Executable; |
| 254 | } | ||
| 255 | |||
| 256 | 588 | [[nodiscard]] constexpr bool is_valid_binding_kind(BindingKind kind) noexcept | |
| 257 | { | ||
| 258 |
2/2✓ Branch 2 → 3 taken 586 times.
✓ Branch 2 → 4 taken 2 times.
|
588 | switch (kind) |
| 259 | { | ||
| 260 | 586 | case BindingKind::Address: | |
| 261 | case BindingKind::PointerChain: | ||
| 262 | case BindingKind::MidHookRegister: | ||
| 263 | case BindingKind::VmtMethod: | ||
| 264 | 586 | return true; | |
| 265 | } | ||
| 266 | 2 | return false; | |
| 267 | } | ||
| 268 | |||
| 269 | // record_policy_domains_are_valid checks whether each persisted policy field belongs to its named domain. | ||
| 270 | // Checked serialization also rejects invalid fields that the active kind ignores, so it never normalizes | ||
| 271 | // garbage into valid syntax. | ||
| 272 | 607 | [[nodiscard]] inline bool record_policy_domains_are_valid(const SignatureRecord &record) noexcept | |
| 273 | { | ||
| 274 |
2/2✓ Branch 5 → 6 taken 596 times.
✓ Branch 5 → 16 taken 2 times.
|
1205 | if (!is_serializable_anchor_kind(record.kind) || !is_valid_operand_kind(record.operand_kind) || |
| 275 |
4/4✓ Branch 7 → 8 taken 594 times.
✓ Branch 7 → 16 taken 2 times.
✓ Branch 9 → 10 taken 592 times.
✓ Branch 9 → 16 taken 2 times.
|
596 | !is_valid_encoding(record.xref_encoding) || !is_valid_xref_return(record.xref_return) || |
| 276 |
8/8✓ Branch 3 → 4 taken 598 times.
✓ Branch 3 → 16 taken 9 times.
✓ Branch 11 → 12 taken 588 times.
✓ Branch 11 → 16 taken 4 times.
✓ Branch 13 → 14 taken 586 times.
✓ Branch 13 → 16 taken 2 times.
✓ Branch 18 → 19 taken 27 times.
✓ Branch 18 → 20 taken 580 times.
|
1791 | !is_valid_pages(record.pages) || !is_valid_binding_kind(record.binding.kind) || |
| 277 |
2/2✓ Branch 15 → 16 taken 6 times.
✓ Branch 15 → 17 taken 580 times.
|
586 | !DetourModKit::detail::valid_code_constant_byte_width(record.byte_width)) |
| 278 | { | ||
| 279 | 27 | return false; | |
| 280 | } | ||
| 281 |
2/2✓ Branch 43 → 22 taken 134 times.
✓ Branch 43 → 44 taken 577 times.
|
1291 | for (const CandidateSpec &spec : record.ladder) |
| 282 | { | ||
| 283 |
6/6✓ Branch 25 → 26 taken 133 times.
✓ Branch 25 → 30 taken 1 time.
✓ Branch 27 → 28 taken 132 times.
✓ Branch 27 → 30 taken 1 time.
✓ Branch 32 → 33 taken 3 times.
✓ Branch 32 → 34 taken 131 times.
|
266 | if (!is_valid_scan_mode(spec.mode) || !is_valid_encoding(spec.string_encoding) || |
| 284 |
2/2✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 31 taken 131 times.
|
132 | !is_valid_xref_return(spec.string_return)) |
| 285 | { | ||
| 286 | 3 | return false; | |
| 287 | } | ||
| 288 | } | ||
| 289 | 577 | return true; | |
| 290 | } | ||
| 291 | |||
| 292 | 522 | [[nodiscard]] constexpr bool image_identity_is_absent(const scan::ImageIdentity &identity) noexcept | |
| 293 | { | ||
| 294 |
4/6✓ Branch 2 → 3 taken 500 times.
✓ Branch 2 → 6 taken 22 times.
✓ Branch 3 → 4 taken 500 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 500 times.
✗ Branch 4 → 6 not taken.
|
522 | return identity.timestamp == 0 && identity.size_of_image == 0 && identity.section_digest == 0; |
| 295 | } | ||
| 296 | |||
| 297 | 522 | [[nodiscard]] constexpr bool image_identity_is_valid(const scan::ImageIdentity &identity) noexcept | |
| 298 | { | ||
| 299 |
4/4✓ Branch 3 → 4 taken 22 times.
✓ Branch 3 → 6 taken 500 times.
✓ Branch 5 → 6 taken 20 times.
✓ Branch 5 → 7 taken 2 times.
|
522 | return image_identity_is_absent(identity) || identity.present(); |
| 300 | } | ||
| 301 | |||
| 302 | // A persisted content baseline is only ever a complete capture, which keeps "present in the file" and | ||
| 303 | // "usable to authorize a mutation" the same condition. | ||
| 304 | 520 | [[nodiscard]] constexpr bool winning_bytes_are_valid(const scan::WinningEvidence &evidence) noexcept | |
| 305 | { | ||
| 306 |
4/4✓ Branch 2 → 3 taken 516 times.
✓ Branch 2 → 5 taken 4 times.
✓ Branch 3 → 4 taken 514 times.
✓ Branch 3 → 5 taken 2 times.
|
520 | return !evidence.truncated && evidence.length <= scan::MAX_MUTATION_WITNESS_BYTES; |
| 307 | } | ||
| 308 | } // namespace DetourModKit::manifest | ||
| 309 | |||
| 310 | #endif // DETOURMODKIT_INTERNAL_MANIFEST_RECORD_RULES_HPP | ||
| 311 |