GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 92.6% 25 / 0 / 27
Functions: 100.0% 9 / 0 / 9
Branches: 78.6% 11 / 0 / 14

src/scanner_parallel.cpp
Line Branch Exec Source
1 /**
2 * @file scanner_parallel.cpp
3 * @brief Opt-in fork-join batch resolvers: scan many compiled patterns, or resolve many cascades, concurrently.
4 *
5 * Sibling translation unit of the scanner module (scanner.cpp / scanner_cascade.cpp), sharing the public scanner.hpp
6 * surface and the internal scanner_internal.hpp entry points. It adds no new scan or resolve primitive: each batch item
7 * is dispatched to an existing serial entry point on a worker thread, and results are gathered in input order by the
8 * shared fork-join driver in fork_join.hpp. scan_regions_batch / scan_module_batch fan the per-pattern region walk
9 * (scan_executable_regions / scan_readable_regions for the whole process, detail::scan_module_* for one image) across
10 * workers; resolve_cascade_batch fans the cascade resolvers (resolve_cascade / resolve_cascade_in_module and their
11 * prologue-fallback variants).
12 *
13 * Correctness rests on read-only sharing of caller-owned inputs. The batch scanners rely on the CompiledPattern
14 * immutability contract: find_pattern and the region walk take the pattern by const reference and never write back (an
15 * un-anchored pattern recomputes its anchor into a local), so a compiled pattern is safe to read concurrently from
16 * many threads without cloning. The cascade resolvers compile each candidate into a per-call local CompiledPattern, so
17 * concurrent requests share only the immutable candidate tables. The runtime CPUID feature caches and the Logger are
18 * thread-safe, and VirtualQuery is an OS-level read, so concurrent serial work shares no mutable state.
19 */
20
21 #include "DetourModKit/scanner.hpp"
22 #include "DetourModKit/memory.hpp"
23
24 #include "fork_join.hpp"
25 #include "scanner_internal.hpp"
26
27 #include <cstddef>
28 #include <expected>
29 #include <optional>
30 #include <span>
31 #include <vector>
32
33 namespace DetourModKit
34 {
35 namespace Scanner
36 {
37 13 std::vector<const std::byte *> scan_regions_batch(std::span<const BatchScanItem> items, ScannerKind kind,
38 std::size_t max_workers)
39 {
40 return DetourModKit::detail::run_fork_join<BatchScanItem, const std::byte *>(
41 items, max_workers,
42 79 [kind](const BatchScanItem &item) -> const std::byte *
43 {
44 // The null check guards the *item.pattern dereference below. An empty pattern or
45 // occurrence == 0 is intentionally not re-checked here: the serial scanners already fail both
46 // closed (their pattern.empty() / occurrence == 0 guards return nullptr).
47
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 78 times.
79 if (item.pattern == nullptr)
48 {
49 1 return nullptr;
50 }
51
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 77 times.
78 return (kind == ScannerKind::Readable) ? scan_readable_regions(*item.pattern, item.occurrence)
52 78 : scan_executable_regions(*item.pattern, item.occurrence);
53 },
54 92 [](const BatchScanItem &) noexcept -> const std::byte * { return nullptr; });
55 }
56
57 2 std::vector<const std::byte *> scan_module_batch(std::span<const BatchScanItem> items,
58 Memory::ModuleRange range, ScannerKind kind,
59 std::size_t max_workers)
60 {
61 return DetourModKit::detail::run_fork_join<BatchScanItem, const std::byte *>(
62 items, max_workers,
63 10 [kind, range](const BatchScanItem &item) -> const std::byte *
64 {
65 // The null check guards the *item.pattern dereference below. An empty pattern or
66 // occurrence == 0 is intentionally not re-checked here: the serial module scanners already fail
67 // both closed (their pattern.empty() / occurrence == 0 guards return nullptr).
68
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 10 times.
10 if (item.pattern == nullptr)
69 {
70 return nullptr;
71 }
72 10 return (kind == ScannerKind::Readable)
73
1/2
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 6 not taken.
10 ? detail::scan_module_readable(*item.pattern, range, item.occurrence)
74 10 : detail::scan_module_executable(*item.pattern, range, item.occurrence);
75 },
76 12 [](const BatchScanItem &) noexcept -> const std::byte * { return nullptr; });
77 }
78
79 std::vector<std::expected<ResolveHit, ResolveError>>
80 7 resolve_cascade_batch(std::span<const CascadeRequest> requests, std::size_t max_workers)
81 {
82 using CascadeResult = std::expected<ResolveHit, ResolveError>;
83 return DetourModKit::detail::run_fork_join<CascadeRequest, CascadeResult>(
84 requests, max_workers,
85 70 [](const CascadeRequest &request) -> CascadeResult
86 {
87
2/2
✓ Branch 3 → 4 taken 68 times.
✓ Branch 3 → 10 taken 2 times.
70 if (request.range)
88 {
89
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 8 taken 67 times.
68 if (request.prologue_fallback)
90 {
91 1 return resolve_cascade_in_module_with_prologue_fallback(request.candidates, request.label,
92 2 *request.range);
93 }
94 67 return resolve_cascade_in_module(request.candidates, request.label, *request.range);
95 }
96
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
2 if (request.prologue_fallback)
97 {
98 return resolve_cascade_with_prologue_fallback(request.candidates, request.label);
99 }
100 2 return resolve_cascade(request.candidates, request.label, request.kind);
101 },
102 70 [](const CascadeRequest &) noexcept -> CascadeResult
103 77 { return std::unexpected(ResolveError::NoMatch); });
104 }
105 } // namespace Scanner
106 } // namespace DetourModKit
107