GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 47 / 0 / 47
Functions: 100.0% 12 / 0 / 12
Branches: 90.9% 40 / 0 / 44

src/scan_candidates.cpp
Line Branch Exec Source
1 /**
2 * @file scan_candidates.cpp
3 * @brief The candidate-ladder vocabulary functions: order_candidates() and borrow().
4 * @details The Candidate factories and accessors are inline in scan.hpp (the variant payload model), so this TU owns
5 * only the ordering permutation and the borrowed-request packer. order_candidates is pure index math over the
6 * variant tiers; borrow packs the borrowed views into a ScanRequest. Both are noexcept and allocate nothing.
7 */
8
9 #include "DetourModKit/scan.hpp"
10
11 #include <algorithm>
12 #include <cstddef>
13 #include <span>
14
15 namespace DetourModKit
16 {
17 namespace scan
18 {
19 std::size_t
20 392 order_candidates(CandidateOrder order, std::span<const Candidate> ladder, std::span<std::size_t> out) noexcept
21 {
22 392 const std::size_t count = std::min(ladder.size(), out.size());
23 // This noexcept helper cannot report InvalidArg, so an unknown value preserves declaration order and never
24 // selects the UniqueFirst promotion.
25
2/2
✓ Branch 5 → 6 taken 382 times.
✓ Branch 5 → 11 taken 9 times.
391 if (order != CandidateOrder::UniqueFirst)
26 {
27
2/2
✓ Branch 9 → 7 taken 394 times.
✓ Branch 9 → 10 taken 382 times.
776 for (std::size_t i = 0; i < count; ++i)
28 {
29 394 out[i] = i;
30 }
31 382 return count;
32 }
33
34 // UniqueFirst: three stable passes over the declared order. Every candidate falls into exactly one pass, so
35 // the result is a permutation of [0, count).
36 9 std::size_t written = 0;
37 27 const auto emit = [&](auto predicate)
38 {
39
15/18
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}) const:
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 13 taken 9 times.
✓ Branch 11 → 12 taken 13 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 3 taken 13 times.
✓ Branch 14 → 15 taken 9 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}) const:
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 13 taken 9 times.
✓ Branch 11 → 12 taken 13 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 3 taken 13 times.
✓ Branch 14 → 15 taken 9 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}) const:
✓ Branch 10 → 11 taken 13 times.
✗ Branch 10 → 13 not taken.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 13 taken 9 times.
✓ Branch 14 → 3 taken 4 times.
✓ Branch 14 → 15 taken 9 times.
57 for (std::size_t i = 0; i < ladder.size() && written < count; ++i)
40 {
41
6/6
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#3}) const:
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 8 taken 11 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#4}) const:
✓ Branch 5 → 6 taken 9 times.
✓ Branch 5 → 8 taken 4 times.
auto DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(auto:1)#1}::operator()<DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}>(DetourModKit::scan::order_candidates(DetourModKit::scan::CandidateOrder, std::span<DetourModKit::scan::Candidate const, 18446744073709551615ull>, std::span<unsigned long long, 18446744073709551615ull>)::{lambda(DetourModKit::scan::Candidate const&)#5}) const:
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 8 taken 2 times.
30 if (predicate(ladder[i]))
42 {
43 13 out[written] = i;
44 13 ++written;
45 }
46 }
47 36 };
48 4 const auto is_byte_mode = [](const Candidate &candidate)
49
3/4
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 3 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
4 { return candidate.mode() == Mode::Direct || candidate.mode() == Mode::RipRelative; };
50 16 const auto is_anchored_byte = [&](const Candidate &candidate)
51 {
52 // A byte tier (Direct / RipRelative) whose compiled Pattern carries a fully-known rarest byte the
53 // prefilter can anchor on; that makes the scan far more selective than a wildcard-led pattern.
54
2/2
✓ Branch 3 → 4 taken 13 times.
✓ Branch 3 → 5 taken 3 times.
16 if (const DirectPattern *direct = candidate.as_direct())
55 {
56 13 return direct->pattern.has_anchor();
57 }
58
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 2 times.
3 if (const RipRelativePattern *rip = candidate.as_rip_relative())
59 {
60 1 return rip->pattern.has_anchor();
61 }
62 2 return false;
63 };
64
65 // Pass 1: the unique-only text tiers, which fail closed on ambiguity by construction.
66 9 emit([](const Candidate &candidate)
67
4/4
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 11 times.
13 { return candidate.mode() == Mode::RttiVtable || candidate.mode() == Mode::StringXref; });
68 // Pass 2: anchored byte patterns (a fully-known rarest byte makes the scan far more selective).
69 22 emit([&](const Candidate &candidate) { return is_anchored_byte(candidate); });
70 // Pass 3: the remaining byte patterns (no fully-known byte to anchor on).
71
4/4
✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 1 time.
13 emit([&](const Candidate &candidate) { return is_byte_mode(candidate) && !is_anchored_byte(candidate); });
72 10 return written;
73 }
74
75 9 ScanRequest borrow(
76 std::span<const Candidate> ladder,
77 std::string_view label,
78 Region scope,
79 FallbackPolicy fallback_policy,
80 FallbackWitness fallback_witness,
81 bool require_unique,
82 CandidateOrder order,
83 Pages pages
84 ) noexcept
85 {
86 return ScanRequest{
87 .ladder = ladder,
88 .label = label,
89 .scope = scope,
90 .fallback_policy = fallback_policy,
91 .fallback_witness = fallback_witness,
92 .require_unique = require_unique,
93 .order = order,
94 .pages = pages,
95 9 };
96 }
97
98 7 ScanRequest borrow_code_target(
99 std::span<const Candidate> ladder,
100 std::string_view label,
101 Region scope,
102 FallbackPolicy fallback_policy,
103 FallbackWitness fallback_witness
104 ) noexcept
105 {
106 // The code-target policy: scan only executable pages (an instruction signature must not alias a byte run in
107 // data), promote the unique-only / anchored tiers first, and enable hooked-prologue recovery so a target
108 // another mod already inline-hooked is still resolved. The recovery strictness comes from the caller
109 // (WarnOnly by default, RequireIdentity + a witness to fail closed on an unconfirmed near-twin).
110 // require_unique stays true. Route through borrow() so the common ScanRequest fields are defined in one
111 // place, then require its final address to be execute-readable too: a RipRelative candidate can match code
112 // bytes but resolve its disp32 to data, and text tiers do not use the byte-page filter at all.
113 7 ScanRequest request = borrow(
114 ladder,
115 label,
116 scope,
117 fallback_policy,
118 fallback_witness,
119 /*require_unique=*/true,
120 CandidateOrder::UniqueFirst,
121 Pages::Executable
122 );
123 7 request.require_executable_result = true;
124 7 return request;
125 }
126
127 2 ScanRequest borrow_code_target_strict(
128 std::span<const Candidate> ladder,
129 std::string_view label,
130 FallbackWitness fallback_witness,
131 Region scope
132 ) noexcept
133 {
134 // The strict code-target policy: identical to borrow_code_target but pinned to RequireIdentity, so a
135 // hooked-prologue recovery is trusted only when the witness confirms it and a coincidental near-twin fails
136 // closed. The witness parameter is non-defaulted so a call site states one, but its predicate can still be
137 // null, and RequireIdentity then fails closed on every recovery (the FallbackWitness contract). Route
138 // through borrow_code_target so the shared code-target fields (Pages::Executable,
139 // require_executable_result, UniqueFirst, require_unique) live in exactly one place.
140 2 return borrow_code_target(ladder, label, scope, FallbackPolicy::RequireIdentity, fallback_witness);
141 }
142 } // namespace scan
143 } // namespace DetourModKit
144