microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
remote_object.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
10#include "../microfmt.hpp"
11#include "compat32.hpp"
13
14#include <cstddef>
15#include <cstdint>
16#include <type_traits>
17
18namespace microfmt {
19
20// ============================================================================
21// Remote Object Field Metadata & Infrastructure
22// ============================================================================
23
28using remote_field_format_fn = void (*)(address_space_ref space, const void *field_ptr, span<char> work_buf,
29 const sink &out) noexcept;
30
35 const char *name{};
36 size_t offset{};
38};
39
40// ============================================================================
41// Remote Field Formatter Traits & Policies (Uniform Extension Point)
42// ============================================================================
43
49template <typename T, typename Enable = void> struct remote_field_traits {
50 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
51 // checked here.
52 RELOCO_BEGIN_UNSAFE_CODE
53 static void format(address_space_ref, const void *field_ptr, span<char>, const sink &out) noexcept {
54 const auto &val = *static_cast<const T *>(field_ptr);
56 fmt.format(val, out);
57 }
59};
60
61class string_ptr; // forward declaration
62template <> struct remote_field_traits<string_ptr> {
63 // NOLINTNEXTLINE(reloco-no-raw-pointer): the field address is a raw object pointer by the traits contract
64 static void format(address_space_ref space, const void *field_ptr, span<char> work_buf, const sink &out) noexcept;
65};
66
67class string32_ptr; // forward declaration
68template <> struct remote_field_traits<string32_ptr> {
69 // NOLINTNEXTLINE(reloco-no-raw-pointer): the field address is a raw object pointer by the traits contract
70 static void format(address_space_ref space, const void *field_ptr, span<char> work_buf, const sink &out) noexcept;
71};
72
73// ============================================================================
74// Remote String Pointer Wrappers (Synthesizing Foreign String Views)
75// ============================================================================
76
84public:
85 constexpr string_ptr() noexcept : addr_(0) {}
86 constexpr explicit string_ptr(uintptr_t addr) noexcept : addr_(addr) {}
87 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
88 // checked here.
89 RELOCO_BEGIN_UNSAFE_CODE
90 explicit string_ptr(const char *ptr) noexcept : addr_(reinterpret_cast<uintptr_t>(ptr)) {}
92
93 [[nodiscard]] constexpr uintptr_t address() const noexcept { return addr_; }
94 [[nodiscard]] constexpr bool is_null() const noexcept { return addr_ == 0; }
95 [[nodiscard]] constexpr explicit operator bool() const noexcept { return addr_ != 0; }
96
103 [[nodiscard]] constexpr foreign_string_view as_view(address_space_ref space, span<char> scratch RELOCO_LIFETIMEBOUND,
104 size_t max_limit = 4096) const & noexcept RELOCO_LIFETIMEBOUND {
105 return foreign_string_view(addr_, space, scratch, max_limit);
106 }
107
108 [[nodiscard]] constexpr foreign_string_view as_view(address_space_ref space, span<char> scratch RELOCO_LIFETIMEBOUND,
109 size_t max_limit = 4096) const && = delete;
110
111private:
112 uintptr_t addr_{0};
113};
114
115// Implementations of string formatting traits
116// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
117// here.
118RELOCO_BEGIN_UNSAFE_CODE
119inline void remote_field_traits<string_ptr>::format(address_space_ref space, const void *field_ptr, span<char> work_buf,
120 const sink &out) noexcept {
121 const auto &sptr = *static_cast<const string_ptr *>(field_ptr);
122 if (sptr.is_null()) {
123 out.write("(null)");
124 return;
125 }
126 const foreign_string_view fsv = sptr.as_view(space, work_buf);
128 fmt.format(fsv, out);
129}
131
132static_assert(sizeof(string_ptr) == sizeof(void *), "string_ptr must have correct size");
133static_assert(alignof(string_ptr) == sizeof(void *), "string32_ptr must be naturally aligned");
134
141class MICROFMT_API_CLASS alignas(uint32_t) string32_ptr {
142public:
143 constexpr string32_ptr() noexcept : addr_(0) {}
144 constexpr explicit string32_ptr(uint32_t addr) noexcept : addr_(addr) {}
145
146 [[nodiscard]] constexpr uint32_t raw_value() const noexcept { return addr_; }
147 [[nodiscard]] constexpr uintptr_t address() const noexcept { return static_cast<uintptr_t>(addr_); }
148 [[nodiscard]] constexpr bool is_null() const noexcept { return addr_ == 0; }
149 [[nodiscard]] constexpr explicit operator bool() const noexcept { return addr_ != 0; }
150
157 [[nodiscard]] constexpr foreign_string_view as_view(address_space_ref space, span<char> scratch RELOCO_LIFETIMEBOUND,
158 size_t max_limit = 4096) const & noexcept RELOCO_LIFETIMEBOUND {
159 return foreign_string_view(address(), space, scratch, max_limit);
160 }
161
162 [[nodiscard]] constexpr foreign_string_view as_view(address_space_ref space, span<char> scratch RELOCO_LIFETIMEBOUND,
163 size_t max_limit = 4096) const && = delete;
164
165private:
166 uint32_t addr_{0};
167};
168
169// decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths checked
170// here.
171RELOCO_BEGIN_UNSAFE_CODE
172inline void remote_field_traits<string32_ptr>::format(address_space_ref space, const void *field_ptr,
173 span<char> work_buf, const sink &out) noexcept {
174 const auto &sptr = *static_cast<const string32_ptr *>(field_ptr);
175 if (sptr.is_null()) {
176 out.write("(null)");
177 return;
178 }
179 const foreign_string_view fsv = sptr.as_view(space, work_buf);
181 fmt.format(fsv, out);
182}
184
185static_assert(sizeof(string32_ptr) == 4, "string32_ptr must be exactly 4 bytes");
186static_assert(alignof(string32_ptr) == 4, "string32_ptr must be 4-byte aligned");
187
188// ============================================================================
189// Formatters for string_ptr and string32_ptr
190// ============================================================================
191
192template <> struct formatter<string_ptr> {
193 constexpr void parse(format_parse_context &) noexcept {}
194 void format(const string_ptr &ptr, const sink &out) const noexcept {
195 if (ptr.is_null()) {
196 out.write("(null)");
197 } else {
198 microfmt::format_to(out, "0x{:x}", ptr.address());
199 }
200 }
201};
202
203template <> struct formatter<string32_ptr> {
204 constexpr void parse(format_parse_context &) noexcept {}
205 void format(const string32_ptr &ptr, const sink &out) const noexcept {
206 if (ptr.is_null()) {
207 out.write("(null)");
208 } else {
209 microfmt::format_to(out, "{:#010x}", ptr.raw_value());
210 }
211 }
212};
213
217template <typename T, typename Enable = void> struct remote_object_traits {
218 static constexpr bool is_registered = false;
219 static constexpr span<const remote_field_desc> fields() noexcept { return {}; }
220 static constexpr size_t struct_size = 0;
221 static constexpr size_t struct_align = 1;
222};
223
224// ============================================================================
225// Remote Object View Container
226// ============================================================================
227
228// Lightweight type tag to pass types into constructor without instantiation
229template <typename T> struct type_tag {};
230
231class RELOCO_POINTER remote_object_view {
232public:
233 template <typename T>
234 constexpr remote_object_view(uintptr_t addr, address_space_ref space, type_tag<T>,
235 span<std::byte> scratch RELOCO_LIFETIMEBOUND) noexcept
236 : addr_(addr), space_(space), fields_(remote_object_traits<T>::fields()),
238 scratch_(scratch) {
239 static_assert(remote_object_traits<T>::is_registered, "Type T must be registered using MICROFMT_REMOTE_STRUCT");
240 }
241
242 constexpr remote_object_view(uintptr_t addr, address_space_ref space,
243 span<const remote_field_desc> fields RELOCO_LIFETIMEBOUND, size_t struct_size,
244 size_t struct_align, span<std::byte> scratch RELOCO_LIFETIMEBOUND) noexcept
245 : addr_(addr), space_(space), fields_(fields), struct_size_(struct_size), struct_align_(struct_align),
246 scratch_(scratch) {}
247
248 [[nodiscard]] constexpr uintptr_t address() const noexcept { return addr_; }
249 [[nodiscard]] constexpr address_space_ref space() const noexcept { return space_; }
250 [[nodiscard]] constexpr span<const remote_field_desc> fields() const noexcept RELOCO_LIFETIMEBOUND { return fields_; }
251 [[nodiscard]] constexpr size_t struct_size() const noexcept { return struct_size_; }
252 [[nodiscard]] constexpr span<std::byte> scratch() const noexcept RELOCO_LIFETIMEBOUND { return scratch_; }
253 [[nodiscard]] constexpr bool is_null() const noexcept { return addr_ == 0; }
254
255 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
256 // checked here.
257 RELOCO_BEGIN_UNSAFE_CODE
258 [[nodiscard]] expected<const void *, remote_load_error> load_raw() const noexcept {
259 if (addr_ == 0)
260 return unexpected(remote_load_error::null_address);
261 if (scratch_.size() < struct_size_)
262 return unexpected(remote_load_error::scratch_too_small);
263 if (struct_align_ > 0 && reinterpret_cast<uintptr_t>(scratch_.data()) % struct_align_ != 0)
264 return unexpected(remote_load_error::scratch_misaligned);
265 if (!space_)
266 return unexpected(remote_load_error::invalid_address_space);
267
268 if (!space_.read_bytes(addr_, scratch_.data(), struct_size_))
269 return unexpected(remote_load_error::read_failed);
270 return static_cast<const void *>(scratch_.data());
271 }
273
274private:
275 uintptr_t addr_{0};
276 address_space_ref space_{};
277 span<const remote_field_desc> fields_{};
278 size_t struct_size_{0};
279 size_t struct_align_{1};
280 span<std::byte> scratch_{};
281};
282
283// ============================================================================
284// Single Universal Formatter for remote_object_view (Zero Template Bloat)
285// ============================================================================
286
287template <> struct formatter<remote_object_view> {
288 constexpr void parse(format_parse_context &) noexcept {}
289
290 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
291 // checked here.
292 RELOCO_BEGIN_UNSAFE_CODE
293 void format(const remote_object_view &view, const sink &out) const noexcept {
294 if (view.is_null()) {
295 out.write("(null)");
296 return;
297 }
298
299 auto obj = view.load_raw();
300 if (!obj) {
301 microfmt::format_to(out, "<fault@{:#x}>", view.address());
302 return;
303 }
304
305 out.write("{ ");
306 const auto fields = view.fields();
307
308 const auto scratch = view.scratch();
309 span<char> work_buf;
310 if (scratch.size() > view.struct_size()) {
311 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
312
313 work_buf = span<char>(reinterpret_cast<char *>(scratch.data() + view.struct_size()),
314 scratch.size() - view.struct_size());
315
316 RELOCO_END_UNSAFE_BUFFER_USAGE;
317 }
318
319 for (size_t i = 0; i < fields.size(); ++i) {
320 if (i > 0) {
321 out.write(", ");
322 }
323 out.write(fields[i].name);
324 out.write(": ");
325
326 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
327
328 const void *field_ptr = reinterpret_cast<const char *>(*obj) + fields[i].offset;
329
330 RELOCO_END_UNSAFE_BUFFER_USAGE;
331
332 fields[i].format_fn(view.space(), field_ptr, work_buf, out);
333 }
334 out.write(" }");
335 }
337};
338
339} // namespace microfmt
340
341// ============================================================================
342// Begin / Field / End Macro DSL for External/Caller-Defined Remote Structures
343// ============================================================================
344
345#define MICROFMT_REMOTE_STRUCT_BEGIN(StructName) \
346 template <> struct microfmt::remote_object_traits<StructName> { \
347 static constexpr size_t struct_size = sizeof(StructName); \
348 static constexpr size_t struct_align = alignof(StructName); \
349 static constexpr bool is_registered = true; \
350 static microfmt::span<const remote_field_desc> fields() noexcept { \
351 using CurrentStruct = StructName; \
352 static constexpr microfmt::remote_field_desc static_fields[] = {
353
354#define MICROFMT_REMOTE_FIELD(Name, ...) \
355 {#Name, offsetof(CurrentStruct, Name), &microfmt::remote_field_traits<__VA_ARGS__>::format},
356
357#define MICROFMT_REMOTE_STRUCT_END() \
358 } \
359 ; \
360 return {static_fields, sizeof(static_fields) / sizeof(static_fields[0])}; \
361 } \
362 } \
363 ;
Type-erased, two-word handle to a remote address space.
Definition address_space.hpp:204
Streaming view of a foreign or non-contiguous string.
Definition foreign_string_view.hpp:29
Definition microfmt.hpp:881
Definition remote_object.hpp:231
constexpr remote_object_view(uintptr_t addr, address_space_ref space, type_tag< T >, span< std::byte > scratch RELOCO_LIFETIMEBOUND) noexcept
Definition remote_object.hpp:234
constexpr size_t struct_size() const noexcept
Definition remote_object.hpp:251
constexpr span< std::byte > scratch() const noexcept RELOCO_LIFETIMEBOUND
Definition remote_object.hpp:252
constexpr bool is_null() const noexcept
Definition remote_object.hpp:253
constexpr uintptr_t address() const noexcept
Definition remote_object.hpp:248
constexpr span< const remote_field_desc > fields() const noexcept RELOCO_LIFETIMEBOUND
Definition remote_object.hpp:250
constexpr address_space_ref space() const noexcept
Definition remote_object.hpp:249
constexpr remote_object_view(uintptr_t addr, address_space_ref space, span< const remote_field_desc > fields RELOCO_LIFETIMEBOUND, size_t struct_size, size_t struct_align, span< std::byte > scratch RELOCO_LIFETIMEBOUND) noexcept
Definition remote_object.hpp:242
RELOCO_BEGIN_UNSAFE_CODE expected< const void *, remote_load_error > load_raw() const noexcept
Definition remote_object.hpp:258
64-bit/Native remote string pointer wrapper.
Definition remote_object.hpp:83
constexpr string_ptr() noexcept
Definition remote_object.hpp:85
RELOCO_END_UNSAFE_CODE constexpr uintptr_t address() const noexcept
Definition remote_object.hpp:93
constexpr foreign_string_view as_view(address_space_ref space, span< char > scratch RELOCO_LIFETIMEBOUND, size_t max_limit=4096) const &&=delete
RELOCO_BEGIN_UNSAFE_CODE string_ptr(const char *ptr) noexcept
Definition remote_object.hpp:90
constexpr string_ptr(uintptr_t addr) noexcept
Definition remote_object.hpp:86
constexpr bool is_null() const noexcept
Definition remote_object.hpp:94
constexpr foreign_string_view as_view(address_space_ref space, span< char > scratch RELOCO_LIFETIMEBOUND, size_t max_limit=4096) const &noexcept RELOCO_LIFETIMEBOUND
Synthesizes a foreign_string_view over the pointed string.
Definition remote_object.hpp:103
32-bit compatibility pointers and remote view helpers for 64-bit hosts.
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Type-erased foreign string view formatter for microfmt.
Definition fmt.hpp:17
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
void(*)(address_space_ref space, const void *field_ptr, span< char > work_buf, const sink &out) noexcept remote_field_format_fn
Type-erased function pointer for formatting a single remote object field from a loaded local struct b...
Definition remote_object.hpp:29
class MICROFMT_API_CLASS alignas(uint32_t) string32_ptr
32-bit compatibility string pointer wrapper (for 64-bit hosts).
Definition remote_object.hpp:141
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
RELOCO_BEGIN_UNSAFE_CODE void format(const remote_object_view &view, const sink &out) const noexcept
Definition remote_object.hpp:293
constexpr void parse(format_parse_context &) noexcept
Definition remote_object.hpp:288
constexpr void parse(format_parse_context &) noexcept
Definition remote_object.hpp:204
void format(const string32_ptr &ptr, const sink &out) const noexcept
Definition remote_object.hpp:205
constexpr void parse(format_parse_context &) noexcept
Definition remote_object.hpp:193
void format(const string_ptr &ptr, const sink &out) const noexcept
Definition remote_object.hpp:194
Definition microfmt.hpp:985
Static descriptor for a single field in a remote structure layout.
Definition remote_object.hpp:34
Default field formatter policy. Can be specialized or overloaded for custom remote types,...
Definition remote_object.hpp:49
static RELOCO_BEGIN_UNSAFE_CODE void format(address_space_ref, const void *field_ptr, span< char >, const sink &out) noexcept
Definition remote_object.hpp:53
Traits customization point for remote object metadata.
Definition remote_object.hpp:217
static constexpr size_t struct_align
Definition remote_object.hpp:221
static constexpr bool is_registered
Definition remote_object.hpp:218
static constexpr span< const remote_field_desc > fields() noexcept
Definition remote_object.hpp:219
static constexpr size_t struct_size
Definition remote_object.hpp:220
Definition remote_object.hpp:229