microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
advanced_scanners.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2// SPDX-License-Identifier: BSD-2-Clause
3
14#pragma once
15
16#include "../microfmt.hpp"
17#include "../reloco.hpp"
18#include "address_space.hpp"
19#include "memory_pattern_scanner.hpp" // For memory_scan_result and linear_memory_scanner_context
20#include <cstddef>
21#include <cstdint>
22#include <cstring>
23#include <limits>
24#include <type_traits>
25#include <utility>
26
27namespace microfmt {
28
29// ============================================================================
30// Value Range Scanner (Finds T where min <= val <= max)
31// ============================================================================
32
38template <typename Tag, typename T> struct value_range_scanner_traits;
39
48template <typename T> class RELOCO_POINTER value_range_scanner_ref {
49public:
50 static_assert(std::is_trivially_copyable_v<T>, "Scanned type must be trivially copyable.");
51
52 struct vtable {
53 expected<memory_scan_result, address_space_error> (*scan)(const void *ctx, uintptr_t start_addr,
54 size_t search_length, const T &min_val, const T &max_val,
55 size_t stride) noexcept;
56 };
57
58 constexpr value_range_scanner_ref() noexcept = default;
59
60 template <typename Tag, typename Traits = value_range_scanner_traits<Tag, T>,
61 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
62 constexpr explicit value_range_scanner_ref(Tag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<Tag>) {}
63
64 template <typename Tag, typename Context, typename Traits = value_range_scanner_traits<Tag, T>,
65 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
66 std::is_convertible_v<const Context *, const typename Traits::context_type *>,
67 int> = 0>
69 const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
70 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
71
72 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
73 constexpr value_range_scanner_ref(Tag, Context &&) = delete;
74
75 template <typename Tag, typename Traits = value_range_scanner_traits<Tag, T>,
76 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
77 [[nodiscard]] static constexpr value_range_scanner_ref make() noexcept {
78 return value_range_scanner_ref(Tag{});
79 }
80
81 template <typename Tag, typename Context, typename Traits = value_range_scanner_traits<Tag, T>,
82 std::enable_if_t<!std::is_void_v<typename Traits::context_type>, int> = 0>
83 [[nodiscard]] static constexpr value_range_scanner_ref make(const Context &context RELOCO_LIFETIMEBOUND) noexcept {
84 return value_range_scanner_ref(Tag{}, context);
85 }
86
95 [[nodiscard]] expected<memory_scan_result, address_space_error> scan(uintptr_t start_addr, size_t search_length,
96 const T &min_val, const T &max_val,
97 size_t stride = alignof(T)) const noexcept {
98 if (!vtbl_)
99 return unexpected(address_space_error::invalid_handle);
100 return vtbl_->scan(ctx_.get(), start_addr, search_length, min_val, max_val, stride);
101 }
102
103 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
104
105private:
106 template <typename Tag>
107 static expected<memory_scan_result, address_space_error> scan_entry(const void *context, uintptr_t start_addr,
108 size_t search_length, const T &min_val,
109 const T &max_val, size_t stride) noexcept {
110 using context_type = typename value_range_scanner_traits<Tag, T>::context_type;
111 if constexpr (std::is_void_v<context_type>) {
112 return value_range_scanner_traits<Tag, T>::scan(start_addr, search_length, min_val, max_val, stride);
113 } else {
114 const auto &typed_context = *static_cast<const context_type *>(context);
115 return value_range_scanner_traits<Tag, T>::scan(value_ref<const context_type>(typed_context), start_addr,
116 search_length, min_val, max_val, stride);
117 }
118 }
119
120 template <typename Tag> static constexpr vtable s_vtbl{&scan_entry<Tag>};
121
122 value_ptr<const void> ctx_{};
123 const vtable *vtbl_{nullptr};
124};
125
135template <typename Tag, typename T,
136 bool Stateless = std::is_void_v<typename value_range_scanner_traits<Tag, T>::context_type>>
138
139template <typename Tag, typename T> class RELOCO_OWNER value_range_scanner<Tag, T, false> {
140public:
143
144 constexpr explicit value_range_scanner(context_type context) noexcept : context_(std::move(context)) {}
145
146 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
147 return value_ref<context_type>(context_);
148 }
149
150 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
151 return value_ref<const context_type>(context_);
152 }
153
154 [[nodiscard]] constexpr value_range_scanner_ref<T> ref() const & noexcept RELOCO_LIFETIMEBOUND {
155 return value_range_scanner_ref<T>(Tag{}, context_);
156 }
157
158 [[nodiscard]] explicit constexpr operator value_range_scanner_ref<T>() const & noexcept RELOCO_LIFETIMEBOUND {
159 return ref();
160 }
161
162 value_ref<context_type> context() && = delete;
163 value_ref<const context_type> context() const && = delete;
164 value_range_scanner_ref<T> ref() const && = delete;
165 operator value_range_scanner_ref<T>() const && = delete;
166
167private:
168 context_type context_;
169};
170
171template <typename Tag, typename T> class value_range_scanner<Tag, T, true> {
172public:
174 using context_type = void;
175
176 [[nodiscard]] static constexpr value_range_scanner_ref<T> ref() noexcept { return value_range_scanner_ref<T>(Tag{}); }
177};
178
179// ============================================================================
180// Dependent Value Scanner (Custom predicate over a sliding window)
181// ============================================================================
182
189using dependent_predicate_fn = bool (*)(const void *window_bytes, void *user_context) noexcept;
190
195template <typename Tag> struct dependent_value_scanner_traits;
196
205class RELOCO_POINTER dependent_value_scanner_ref {
206public:
207 struct vtable {
208 expected<memory_scan_result, address_space_error> (*scan)(const void *ctx, uintptr_t start_addr,
209 size_t search_length, size_t window_size, size_t stride,
210 dependent_predicate_fn predicate,
211 void *user_context) noexcept = nullptr;
212 };
213
214 constexpr dependent_value_scanner_ref() noexcept = default;
215
216 template <typename Tag, typename Traits = dependent_value_scanner_traits<Tag>,
217 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
218 constexpr explicit dependent_value_scanner_ref(Tag) noexcept : ctx_(nullptr), vtbl_(&s_vtbl<Tag>) {}
219
220 template <typename Tag, typename Context, typename Traits = dependent_value_scanner_traits<Tag>,
221 std::enable_if_t<!std::is_void_v<typename Traits::context_type> &&
222 std::is_convertible_v<const Context *, const typename Traits::context_type *>,
223 int> = 0>
225 Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
226 : ctx_(&ctx), vtbl_(&s_vtbl<Tag>) {}
227
228 template <typename Tag, typename Context, std::enable_if_t<!std::is_lvalue_reference_v<Context>, int> = 0>
229 constexpr dependent_value_scanner_ref(Tag, Context &&) = delete;
230
231 template <typename Tag, typename Traits = dependent_value_scanner_traits<Tag>,
232 std::enable_if_t<std::is_void_v<typename Traits::context_type>, int> = 0>
233 [[nodiscard]] static constexpr dependent_value_scanner_ref make() noexcept {
234 return dependent_value_scanner_ref(Tag{});
235 }
236
237 template <typename Tag, typename Context, typename Traits = dependent_value_scanner_traits<Tag>,
238 std::enable_if_t<!std::is_void_v<typename Traits::context_type>, int> = 0>
239 [[nodiscard]] static constexpr dependent_value_scanner_ref
240 make(const Context &context RELOCO_LIFETIMEBOUND) noexcept {
241 return dependent_value_scanner_ref(Tag{}, context);
242 }
243
253 [[nodiscard]] expected<memory_scan_result, address_space_error> scan(uintptr_t start_addr, size_t search_length,
254 size_t window_size, size_t stride,
255 dependent_predicate_fn predicate,
256 void *user_context = nullptr) const noexcept {
257 if (vtbl_ == nullptr)
258 return unexpected(address_space_error::invalid_handle);
259 return vtbl_->scan(ctx_.get(), start_addr, search_length, window_size, stride, predicate, user_context);
260 }
261
262 [[nodiscard]] constexpr explicit operator bool() const noexcept { return vtbl_ != nullptr; }
263
264private:
265 template <typename Tag>
266 static expected<memory_scan_result, address_space_error>
267 scan_entry(const void *context, uintptr_t start_addr, size_t search_length, size_t window_size, size_t stride,
268 dependent_predicate_fn predicate, void *user_context) noexcept {
269 using context_type = typename dependent_value_scanner_traits<Tag>::context_type;
270 if constexpr (std::is_void_v<context_type>) {
271 return dependent_value_scanner_traits<Tag>::scan(start_addr, search_length, window_size, stride, predicate,
272 user_context);
273 } else {
274 const auto &typed_context = *static_cast<const context_type *>(context);
275 return dependent_value_scanner_traits<Tag>::scan(value_ref<const context_type>(typed_context), start_addr,
276 search_length, window_size, stride, predicate, user_context);
277 }
278 }
279
280 template <typename Tag> static constexpr vtable s_vtbl{&scan_entry<Tag>};
281
282 value_ptr<const void> ctx_{};
283 const vtable *vtbl_{nullptr};
284};
285
294template <typename Tag, bool Stateless = std::is_void_v<typename dependent_value_scanner_traits<Tag>::context_type>>
296
297template <typename Tag> class RELOCO_OWNER dependent_value_scanner<Tag, false> {
298public:
301
302 constexpr explicit dependent_value_scanner(context_type context) noexcept : context_(std::move(context)) {}
303
304 [[nodiscard]] constexpr value_ref<context_type> context() & noexcept RELOCO_LIFETIMEBOUND {
305 return value_ref<context_type>(context_);
306 }
307
308 [[nodiscard]] constexpr value_ref<const context_type> context() const & noexcept RELOCO_LIFETIMEBOUND {
309 return value_ref<const context_type>(context_);
310 }
311
312 [[nodiscard]] constexpr dependent_value_scanner_ref ref() const & noexcept RELOCO_LIFETIMEBOUND {
313 return dependent_value_scanner_ref(Tag{}, context_);
314 }
315
316 [[nodiscard]] explicit constexpr operator dependent_value_scanner_ref() const & noexcept RELOCO_LIFETIMEBOUND {
317 return ref();
318 }
319
320 value_ref<context_type> context() && = delete;
321 value_ref<const context_type> context() const && = delete;
322 dependent_value_scanner_ref ref() const && = delete;
323 operator dependent_value_scanner_ref() const && = delete;
324
325private:
326 context_type context_;
327};
328
329template <typename Tag> class dependent_value_scanner<Tag, true> {
330public:
332 using context_type = void;
333
334 [[nodiscard]] static constexpr dependent_value_scanner_ref ref() noexcept {
335 return dependent_value_scanner_ref(Tag{});
336 }
337};
338
339// ============================================================================
340// Standard Implementations (Backed by linear_memory_scanner_context)
341// ============================================================================
342
343template <typename T> struct linear_value_range_scanner_tag {};
345
346// Range Scanner Traits
349
350 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
351 // checked here.
352 RELOCO_BEGIN_UNSAFE_CODE
353 static expected<memory_scan_result, address_space_error> scan(value_ref<const context_type> ctx, uintptr_t start_addr,
354 size_t search_length, const T &min_val,
355 const T &max_val, size_t stride) noexcept {
356 if (stride == 0)
357 stride = 1;
358 const size_t window_size = sizeof(T);
359 if (ctx->scratch.size() < window_size)
360 return unexpected(address_space_error::invalid_buffer);
361 if (!ctx->space)
362 return unexpected(address_space_error::invalid_handle);
363
364 uintptr_t current_addr = start_addr;
365 size_t remaining = search_length;
366
367 while (remaining >= window_size) {
368 size_t to_read = (remaining < ctx->scratch.size()) ? remaining : ctx->scratch.size();
369 auto read_res = ctx->space.read_bytes(current_addr, ctx->scratch.data(), to_read);
370 if (!read_res)
371 return unexpected(read_res.error());
372
373 size_t search_limit = to_read - window_size;
374 for (size_t offset = 0; offset <= search_limit; offset += stride) {
375 T val;
376 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
377 std::memcpy(&val, ctx->scratch.data() + offset, sizeof(T));
378 RELOCO_END_UNSAFE_BUFFER_USAGE;
379 if (val >= min_val && val <= max_val) {
380 if (current_addr > std::numeric_limits<uintptr_t>::max() - offset) {
381 return unexpected(address_space_error::invalid_address);
382 }
383 return memory_scan_result{true, current_addr + offset};
384 }
385 }
386
387 size_t advance = ((search_limit) / stride) * stride + stride;
388 if (current_addr > std::numeric_limits<uintptr_t>::max() - advance) {
389 return unexpected(address_space_error::invalid_address);
390 }
391 current_addr += advance;
392 remaining = (advance > remaining) ? 0 : remaining - advance;
393 }
394 return memory_scan_result{false, 0};
395 }
397};
398
399// Dependent Scanner Traits
402
403 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
404 // checked here.
405 RELOCO_BEGIN_UNSAFE_CODE
406 static expected<memory_scan_result, address_space_error> scan(value_ref<const context_type> ctx, uintptr_t start_addr,
407 size_t search_length, size_t window_size, size_t stride,
408 dependent_predicate_fn predicate,
409 void *user_context) noexcept {
410 if (stride == 0)
411 stride = 1;
412 if (window_size == 0)
413 return unexpected(address_space_error::invalid_buffer);
414 if (ctx->scratch.size() < window_size)
415 return unexpected(address_space_error::invalid_buffer);
416 if (!ctx->space)
417 return unexpected(address_space_error::invalid_handle);
418 if (predicate == nullptr)
419 return unexpected(address_space_error::invalid_buffer);
420
421 uintptr_t current_addr = start_addr;
422 size_t remaining = search_length;
423
424 while (remaining >= window_size) {
425 const size_t to_read = (remaining < ctx->scratch.size()) ? remaining : ctx->scratch.size();
426 auto read_res = ctx->space.read_bytes(current_addr, ctx->scratch.data(), to_read);
427 if (!read_res)
428 return unexpected(read_res.error());
429
430 const size_t search_limit = to_read - window_size;
431 for (size_t offset = 0; offset <= search_limit; offset += stride) {
432 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
433 const void *window = ctx->scratch.data() + offset;
434 RELOCO_END_UNSAFE_BUFFER_USAGE;
435 if (predicate(window, user_context)) {
436 if (current_addr > std::numeric_limits<uintptr_t>::max() - offset) {
437 return unexpected(address_space_error::invalid_address);
438 }
439 return memory_scan_result{true, current_addr + offset};
440 }
441 }
442
443 const size_t advance = ((search_limit) / stride) * stride + stride;
444 if (current_addr > std::numeric_limits<uintptr_t>::max() - advance) {
445 return unexpected(address_space_error::invalid_address);
446 }
447 current_addr += advance;
448 remaining = (advance > remaining) ? 0 : remaining - advance;
449 }
450 return memory_scan_result{false, 0};
451 }
453};
454
455} // namespace microfmt
Type-erased remote address-space access and remote string/object views.
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:304
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:308
value_ref< context_type > context() &&=delete
typename dependent_value_scanner_traits< Tag >::context_type context_type
Definition advanced_scanners.hpp:300
constexpr dependent_value_scanner(context_type context) noexcept
Definition advanced_scanners.hpp:302
constexpr dependent_value_scanner_ref ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:312
value_ref< const context_type > context() const &&=delete
void context_type
Definition advanced_scanners.hpp:332
static constexpr dependent_value_scanner_ref ref() noexcept
Definition advanced_scanners.hpp:334
Type-erased reference for scanning memory using a custom logical predicate.
Definition advanced_scanners.hpp:205
static constexpr dependent_value_scanner_ref make(const Context &context RELOCO_LIFETIMEBOUND) noexcept
Definition advanced_scanners.hpp:240
constexpr dependent_value_scanner_ref() noexcept=default
constexpr dependent_value_scanner_ref(Tag, Context &&)=delete
expected< memory_scan_result, address_space_error > scan(uintptr_t start_addr, size_t search_length, size_t window_size, size_t stride, dependent_predicate_fn predicate, void *user_context=nullptr) const noexcept
Scans memory using a custom sliding-window callback.
Definition advanced_scanners.hpp:253
static constexpr dependent_value_scanner_ref make() noexcept
Definition advanced_scanners.hpp:233
constexpr dependent_value_scanner_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition advanced_scanners.hpp:224
Typed wrapper owning the context for a dependent value scanner.
Definition advanced_scanners.hpp:295
constexpr value_ref< const context_type > context() const &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:150
constexpr value_ref< context_type > context() &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:146
typename value_range_scanner_traits< Tag, T >::context_type context_type
Definition advanced_scanners.hpp:142
value_ref< const context_type > context() const &&=delete
value_ref< context_type > context() &&=delete
constexpr value_range_scanner_ref< T > ref() const &noexcept RELOCO_LIFETIMEBOUND
Definition advanced_scanners.hpp:154
constexpr value_range_scanner(context_type context) noexcept
Definition advanced_scanners.hpp:144
void context_type
Definition advanced_scanners.hpp:174
static constexpr value_range_scanner_ref< T > ref() noexcept
Definition advanced_scanners.hpp:176
Type-erased reference for scanning memory for a scalar value within a [min, max] range.
Definition advanced_scanners.hpp:48
static constexpr value_range_scanner_ref make() noexcept
Definition advanced_scanners.hpp:77
static constexpr value_range_scanner_ref make(const Context &context RELOCO_LIFETIMEBOUND) noexcept
Definition advanced_scanners.hpp:83
expected< memory_scan_result, address_space_error > scan(uintptr_t start_addr, size_t search_length, const T &min_val, const T &max_val, size_t stride=alignof(T)) const noexcept
Scans memory for a value falling within the inclusive bounds [min_val, max_val].
Definition advanced_scanners.hpp:95
constexpr value_range_scanner_ref(Tag, Context &&)=delete
constexpr value_range_scanner_ref() noexcept=default
constexpr value_range_scanner_ref(Tag, const Context &ctx RELOCO_LIFETIMEBOUND RELOCO_LIFETIME_CAPTURE_BY_THIS) noexcept
Definition advanced_scanners.hpp:68
Typed wrapper owning the context for a value range scanner.
Definition advanced_scanners.hpp:137
Type-erased, zero-allocation memory pattern scanner for exact and masked byte sequences.
Definition assert.hpp:19
bool(*)(const void *window_bytes, void *user_context) noexcept dependent_predicate_fn
Function pointer type for evaluating a logical predicate over a memory window.
Definition advanced_scanners.hpp:189
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Definition advanced_scanners.hpp:207
static RELOCO_BEGIN_UNSAFE_CODE expected< memory_scan_result, address_space_error > scan(value_ref< const context_type > ctx, uintptr_t start_addr, size_t search_length, size_t window_size, size_t stride, dependent_predicate_fn predicate, void *user_context) noexcept
Definition advanced_scanners.hpp:406
Static customization point for scanning memory using a sliding-window predicate.
Definition advanced_scanners.hpp:195
Definition advanced_scanners.hpp:344
State retained by the standard linear memory scanner.
Definition memory_pattern_scanner.hpp:209
Definition advanced_scanners.hpp:343
Result of a memory pattern scan operation.
Definition memory_pattern_scanner.hpp:30
Definition advanced_scanners.hpp:52
static RELOCO_BEGIN_UNSAFE_CODE expected< memory_scan_result, address_space_error > scan(value_ref< const context_type > ctx, uintptr_t start_addr, size_t search_length, const T &min_val, const T &max_val, size_t stride) noexcept
Definition advanced_scanners.hpp:353
Static customization point for scanning memory for a scalar within a specific range.
Definition advanced_scanners.hpp:38