microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
demangle.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
7// std-interop-file: emits "std::..." as string-literal demangled-name text, not real type usage.
8
12#include "../microfmt.hpp"
13#include <cstddef>
14#include <cstdint>
15#include <string_view>
16
17namespace microfmt {
18
23public:
28 constexpr explicit itanium_demangler(microfmt::string_view mangled) noexcept : src_(mangled), pos_(0) {}
29
35 bool demangle_to(const sink &out) noexcept {
36 if (!detail::starts_with(src_, "_Z")) {
37 return false;
38 }
39
40 pos_ = 2; // Skip "_Z"
41
42 // Virtual Tables & RTTI prefixes
43 if (match_prefix("TV")) {
44 out.write("vtable for ");
45 return parse_type(out);
46 }
47 if (match_prefix("TT")) {
48 out.write("VTT for ");
49 return parse_type(out);
50 }
51 if (match_prefix("TI")) {
52 out.write("typeinfo for ");
53 return parse_type(out);
54 }
55 if (match_prefix("TS")) {
56 out.write("typeinfo name for ");
57 return parse_type(out);
58 }
59
60 // Standard symbols (functions / methods / encodings)
61 return parse_encoding(out);
62 }
63
69 static void format_symbol(microfmt::string_view sym, const sink &out) noexcept {
70 itanium_demangler d(sym);
71 if (!d.demangle_to(out)) {
72 out.write(sym); // Fallback to raw symbol
73 }
74 }
75
76private:
78 microfmt::string_view src_;
80 size_t pos_{0};
82 static constexpr size_t kMaxRecursionDepth = 16;
84 size_t depth_{0};
85
89 [[nodiscard]] constexpr bool eof() const noexcept { return pos_ >= src_.size(); }
93 [[nodiscard]] constexpr char peek() const noexcept { return eof() ? '\0' : src_[pos_]; }
97 constexpr char get() noexcept { return eof() ? '\0' : src_[pos_++]; }
98
102 constexpr bool match(char c) noexcept {
103 if (peek() == c) {
104 ++pos_;
105 return true;
106 }
107 return false;
108 }
109
113 constexpr bool match_prefix(microfmt::string_view p) noexcept {
114 if (detail::starts_with(src_.substr(pos_), p)) {
115 pos_ += p.size();
116 return true;
117 }
118 return false;
119 }
120
124 // decodes raw bytes of a target address space; casts, pointers and offsets are bounded by the explicit lengths
125 // checked here.
126 RELOCO_BEGIN_UNSAFE_CODE
127 bool parse_number(size_t &val) noexcept {
128 if (eof() || peek() < '0' || peek() > '9')
129 return false;
130 val = 0;
131 while (!eof() && peek() >= '0' && peek() <= '9') {
132 val = val * 10 + static_cast<size_t>(get() - '0');
133 }
134 return true;
135 }
137
141 bool parse_encoding(const sink &out) noexcept {
142 if (!parse_name(out))
143 return false;
144
145 // Trailing function parameter types
146 if (!eof() && peek() != 'E') {
147 if (peek() == 'v' && (pos_ + 1 == src_.size() || src_[pos_ + 1] == 'E')) {
148 get(); // Consume 'v'
149 out.write("()");
150 } else {
151 out.write("(");
152 bool first = true;
153 while (!eof() && peek() != 'E') {
154 if (!first)
155 out.write(", ");
156 first = false;
157 if (!parse_type(out))
158 return false;
159 }
160 out.write(")");
161 }
162 }
163 return true;
164 }
165
169 bool parse_name(const sink &out) noexcept {
170 if (depth_++ > kMaxRecursionDepth)
171 return false;
172
173 bool ok = false;
174 if (match('N')) {
175 ok = parse_nested_name(out);
176 } else if (match('S')) {
177 ok = parse_substitution(out);
178 if (ok && match('I')) {
179 ok = parse_template_args(out);
180 }
181 } else {
182 ok = parse_unqualified_name(out);
183 if (ok && match('I')) {
184 ok = parse_template_args(out);
185 }
186 }
187
188 --depth_;
189 return ok;
190 }
191
195 bool parse_template_args(const sink &out) noexcept {
196 out.write("<");
197 bool first = true;
198 while (!eof() && peek() != 'E') {
199 if (!first)
200 out.write(", ");
201 first = false;
202 if (!parse_type(out))
203 return false;
204 }
205 out.write(">");
206 return match('E');
207 }
208
212 bool parse_nested_name(const sink &out) noexcept {
213 bool first = true;
214 while (!eof() && peek() != 'E') {
215 // Consume trailing CV qualifiers on member methods
216 if (peek() == 'r' || peek() == 'V' || peek() == 'K') {
217 get();
218 continue;
219 }
220
221 if (match('I')) {
222 if (!parse_template_args(out))
223 return false;
224 continue;
225 }
226
227 if (!first)
228 out.write("::");
229 first = false;
230
231 if (match('S')) {
232 if (!parse_substitution(out))
233 return false;
234 } else if (!parse_unqualified_name(out)) {
235 return false;
236 }
237 }
238 return match('E');
239 }
240
245 bool parse_unqualified_name(const sink &out) noexcept {
246 if (peek() >= '0' && peek() <= '9') {
247 size_t len = 0;
248 if (!parse_number(len))
249 return false;
250 if (pos_ + len > src_.size())
251 return false;
252 out.write(src_.substr(pos_, len));
253 pos_ += len;
254 return true;
255 }
256
257 // Constructors / Destructors
258 if (match_prefix("C1") || match_prefix("C2") || match_prefix("C3")) {
259 out.write("{constructor}");
260 return true;
261 }
262 if (match_prefix("D1") || match_prefix("D2") || match_prefix("D0")) {
263 out.write("~{destructor}");
264 return true;
265 }
266
267 // Operator symbols
268 if (match_prefix("nw")) {
269 out.write("operator new");
270 return true;
271 }
272 if (match_prefix("dl")) {
273 out.write("operator delete");
274 return true;
275 }
276 if (match_prefix("na")) {
277 out.write("operator new[]");
278 return true;
279 }
280 if (match_prefix("da")) {
281 out.write("operator delete[]");
282 return true;
283 }
284 if (match_prefix("pl")) {
285 out.write("operator+");
286 return true;
287 }
288 if (match_prefix("mi")) {
289 out.write("operator-");
290 return true;
291 }
292 if (match_prefix("ml")) {
293 out.write("operator*");
294 return true;
295 }
296 if (match_prefix("dv")) {
297 out.write("operator/");
298 return true;
299 }
300 if (match_prefix("eq")) {
301 out.write("operator==");
302 return true;
303 }
304 if (match_prefix("ne")) {
305 out.write("operator!=");
306 return true;
307 }
308 if (match_prefix("ix")) {
309 out.write("operator[]");
310 return true;
311 }
312 if (match_prefix("cl")) {
313 out.write("operator()");
314 return true;
315 }
316
317 return false;
318 }
319
323 bool parse_substitution(const sink &out) noexcept {
324 // 'St' is the prefix for std:: namespace
325 if (match('t')) {
326 out.write("std::");
327 return parse_unqualified_name(out);
328 }
329 if (match('a')) {
330 out.write("std::allocator");
331 return true;
332 }
333 if (match('b')) {
334 out.write("std::basic_string");
335 return true;
336 }
337 if (match('s')) {
338 out.write("std::string");
339 return true;
340 }
341 if (match('i')) {
342 out.write("std::istream");
343 return true;
344 }
345 if (match('o')) {
346 out.write("std::ostream");
347 return true;
348 }
349 if (match('d')) {
350 out.write("std::iostream");
351 return true;
352 }
353
354 // S_ or S<seq>_ (numbered substitutions)
355 if (match('_')) {
356 out.write("self");
357 return true;
358 }
359
360 while (!eof() && peek() != '_') {
361 get();
362 }
363 match('_');
364 out.write("T");
365 return true;
366 }
367
371 bool parse_type(const sink &out) noexcept {
372 if (depth_++ > kMaxRecursionDepth)
373 return false;
374
375 // Type qualifiers & pointer/reference modifiers
376 if (match('P')) {
377 const bool ok = parse_type(out);
378 out.write("*");
379 --depth_;
380 return ok;
381 }
382 if (match('R')) {
383 const bool ok = parse_type(out);
384 out.write("&");
385 --depth_;
386 return ok;
387 }
388 if (match('O')) {
389 const bool ok = parse_type(out);
390 out.write("&&");
391 --depth_;
392 return ok;
393 }
394 if (match('K')) {
395 const bool ok = parse_type(out);
396 out.write(" const");
397 --depth_;
398 return ok;
399 }
400 if (match('V')) {
401 const bool ok = parse_type(out);
402 out.write(" volatile");
403 --depth_;
404 return ok;
405 }
406
407 // Builtin primitives
408 switch (peek()) {
409 case 'v':
410 get();
411 out.write("void");
412 --depth_;
413 return true;
414 case 'w':
415 get();
416 out.write("wchar_t");
417 --depth_;
418 return true;
419 case 'b':
420 get();
421 out.write("bool");
422 --depth_;
423 return true;
424 case 'c':
425 get();
426 out.write("char");
427 --depth_;
428 return true;
429 case 'a':
430 get();
431 out.write("signed char");
432 --depth_;
433 return true;
434 case 'h':
435 get();
436 out.write("unsigned char");
437 --depth_;
438 return true;
439 case 's':
440 get();
441 out.write("short");
442 --depth_;
443 return true;
444 case 't':
445 get();
446 out.write("unsigned short");
447 --depth_;
448 return true;
449 case 'i':
450 get();
451 out.write("int");
452 --depth_;
453 return true;
454 case 'j':
455 get();
456 out.write("unsigned int");
457 --depth_;
458 return true;
459 case 'l':
460 get();
461 out.write("long");
462 --depth_;
463 return true;
464 case 'm':
465 get();
466 out.write("unsigned long");
467 --depth_;
468 return true;
469 case 'x':
470 get();
471 out.write("long long");
472 --depth_;
473 return true;
474 case 'y':
475 get();
476 out.write("unsigned long long");
477 --depth_;
478 return true;
479 case 'f':
480 get();
481 out.write("float");
482 --depth_;
483 return true;
484 case 'd':
485 get();
486 out.write("double");
487 --depth_;
488 return true;
489 case 'e':
490 get();
491 out.write("long double");
492 --depth_;
493 return true;
494 case 'z':
495 get();
496 out.write("...");
497 --depth_;
498 return true;
499 default:
500 break;
501 }
502
503 // Compound / nested / substituted type name
504 const bool ok = parse_name(out);
505 --depth_;
506 return ok;
507 }
508};
509
513struct MICROFMT_API_CLASS RELOCO_POINTER demangle_view {
515 microfmt::string_view symbol;
516};
517
523[[nodiscard]] constexpr auto as_demangled(microfmt::string_view sym) noexcept { return demangle_view{sym}; }
524
528template <> struct formatter<demangle_view> {
532 constexpr void parse(format_parse_context &) noexcept {}
533
539 void format(const demangle_view &dv, const sink &out) const noexcept {
540 itanium_demangler::format_symbol(dv.symbol, out);
541 }
542};
543
544} // namespace microfmt
Definition microfmt.hpp:881
Zero-allocation Itanium (GCC/Clang) name demangler.
Definition demangle.hpp:22
constexpr itanium_demangler(microfmt::string_view mangled) noexcept
Constructs a demangler over an Itanium-mangled symbol.
Definition demangle.hpp:28
bool demangle_to(const sink &out) noexcept
Demangles the symbol into the sink.
Definition demangle.hpp:35
static void format_symbol(microfmt::string_view sym, const sink &out) noexcept
Demangles a symbol, falling back to the raw input on failure.
Definition demangle.hpp:69
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr auto as_demangled(microfmt::string_view sym) noexcept
Wraps a symbol for demangled formatting.
Definition demangle.hpp:523
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Formattable view over a demangled symbol.
Definition demangle.hpp:513
microfmt::string_view symbol
Mangled symbol.
Definition demangle.hpp:515
constexpr void parse(format_parse_context &) noexcept
No-op parse.
Definition demangle.hpp:532
void format(const demangle_view &dv, const sink &out) const noexcept
Demangles and writes the symbol.
Definition demangle.hpp:539
Definition microfmt.hpp:985