stacktrace revision 1.1.1.3 1 // <stacktrace> -*- C++ -*-
2
3 // Copyright The GNU Toolchain Authors.
4 //
5 // This file is part of the GNU ISO C++ Library. This library is free
6 // software; you can redistribute it and/or modify it under the
7 // terms of the GNU General Public License as published by the
8 // Free Software Foundation; either version 3.
9
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14
15 // Under Section 7 of GPL version 3, you are granted additional
16 // permissions described in the GCC Runtime Library Exception, version
17 // 3.1, as published by the Free Software Foundation.
18
19 // You should have received a copy of the GNU General Public License and
20 // a copy of the GCC Runtime Library Exception along with this program;
21 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
22 // <http://www.gnu.org/licenses/>.
23
24 #ifndef _GLIBCXX_STACKTRACE
25 #define _GLIBCXX_STACKTRACE 1
26
27 #pragma GCC system_header
28
29 #include <bits/requires_hosted.h> // std::string bound
30
31 #include <bits/c++config.h>
32
33 #define __glibcxx_want_stacktrace
34 #define __glibcxx_want_formatters
35 #include <bits/version.h>
36
37 #ifdef __cpp_lib_stacktrace // C++ >= 23 && hosted && HAVE_STACKTRACE
38 #include <compare>
39 #include <format>
40 #include <new>
41 #include <string>
42 #include <sstream>
43 #include <bits/memory_resource.h>
44 #include <bits/stl_algobase.h>
45 #include <bits/stl_algo.h>
46 #include <bits/stl_iterator.h>
47 #include <bits/stl_uninitialized.h>
48 #include <bits/stl_tempbuf.h> // __get_temporary_buffer
49 #include <ext/numeric_traits.h>
50
51 namespace std _GLIBCXX_VISIBILITY(default)
52 {
53 _GLIBCXX_BEGIN_NAMESPACE_VERSION
54
55 // [stacktrace.entry], class stacktrace_entry
56 class stacktrace_entry
57 {
58 using uint_least32_t = __UINT_LEAST32_TYPE__;
59 using uintptr_t = __UINTPTR_TYPE__;
60
61 public:
62 using native_handle_type = uintptr_t;
63
64 // [stacktrace.entry.ctor], constructors
65
66 constexpr
67 stacktrace_entry() noexcept = default;
68
69 constexpr
70 stacktrace_entry(const stacktrace_entry& __other) noexcept = default;
71
72 constexpr stacktrace_entry&
73 operator=(const stacktrace_entry& __other) noexcept = default;
74
75 ~stacktrace_entry() = default;
76
77 // [stacktrace.entry.obs], observers
78
79 [[nodiscard]]
80 constexpr native_handle_type
81 native_handle() const noexcept { return _M_pc; }
82
83 constexpr explicit operator bool() const noexcept
84 { return _M_pc != (native_handle_type)-1; }
85
86 // [stacktrace.entry.query], query
87 [[nodiscard]]
88 string
89 description() const
90 {
91 string __s;
92 _M_get_info(&__s, nullptr, nullptr);
93 return __s;
94 }
95
96 [[nodiscard]]
97 string
98 source_file() const
99 {
100 string __s;
101 _M_get_info(nullptr, &__s, nullptr);
102 return __s;
103 }
104
105 [[nodiscard]]
106 uint_least32_t
107 source_line() const
108 {
109 int __line = 0;
110 _M_get_info(nullptr, nullptr, &__line);
111 return __line;
112 }
113
114 // [stacktrace.entry.cmp], comparison
115 [[nodiscard]]
116 friend constexpr bool
117 operator==(const stacktrace_entry& __x,
118 const stacktrace_entry& __y) noexcept
119 { return __x._M_pc == __y._M_pc; }
120
121 [[nodiscard]]
122 friend constexpr strong_ordering
123 operator<=>(const stacktrace_entry& __x,
124 const stacktrace_entry& __y) noexcept
125 { return __x._M_pc <=> __y._M_pc; }
126
127 private:
128 native_handle_type _M_pc = -1;
129
130 template<typename _Allocator> friend class basic_stacktrace;
131
132 friend ostream&
133 operator<<(ostream&, const stacktrace_entry&);
134
135 // Type-erased wrapper for the fields of a stacktrace entry.
136 // This type is independent of which std::string ABI is in use.
137 struct _Info
138 {
139 void* _M_desc;
140 void* _M_file;
141 int* _M_line;
142 void (*_M_set)(void*, const char*);
143
144 _GLIBCXX_DEFAULT_ABI_TAG
145 static void
146 _S_set(void* __dest, const char* __str)
147 { static_cast<string*>(__dest)->assign(__str); }
148
149 _Info(string* __desc, string* __file, int* __line)
150 : _M_desc(__desc), _M_file(__file), _M_line(__line), _M_set(_S_set)
151 { }
152
153 bool
154 _M_populate(native_handle_type);
155
156 void _M_set_file(const char*);
157 void _M_set_desc(const char*);
158 };
159
160 bool
161 _M_get_info(string* __desc, string* __file, int* __line) const
162 {
163 if (!*this)
164 return false;
165 return _Info(__desc, __file, __line)._M_populate(_M_pc);
166 }
167 };
168
169 class __stacktrace_impl
170 {
171 protected:
172 static int _S_current(int (*) (void*, __UINTPTR_TYPE__), void*, int = 0);
173 };
174
175 // [stacktrace.basic], class template basic_stacktrace
176 template<typename _Allocator>
177 class basic_stacktrace
178 : private __stacktrace_impl
179 {
180 using _AllocTraits = allocator_traits<_Allocator>;
181 using uintptr_t = __UINTPTR_TYPE__;
182
183 public:
184 using value_type = stacktrace_entry;
185 using const_reference = const value_type&;
186 using reference = value_type&;
187 using const_iterator
188 = __gnu_cxx::__normal_iterator<value_type*, basic_stacktrace>;
189 using iterator = const_iterator;
190 using reverse_iterator = std::reverse_iterator<iterator>;
191 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
192 using difference_type = ptrdiff_t;
193 using size_type = unsigned short;
194 using allocator_type = _Allocator;
195
196 // [stacktrace.basic.ctor], creation and assignment
197
198 [[__gnu__::__noinline__]]
199 static basic_stacktrace
200 current(const allocator_type& __alloc = allocator_type()) noexcept
201 {
202 basic_stacktrace __ret(__alloc);
203 if (auto __cb = __ret._M_prepare()) [[likely]]
204 {
205 if (_S_current(__cb, std::__addressof(__ret)))
206 __ret._M_clear();
207 }
208 return __ret;
209 }
210
211 [[__gnu__::__noinline__]]
212 static basic_stacktrace
213 current(size_type __skip,
214 const allocator_type& __alloc = allocator_type()) noexcept
215 {
216 basic_stacktrace __ret(__alloc);
217 if (__skip >= __INT_MAX__) [[unlikely]]
218 return __ret;
219 if (auto __cb = __ret._M_prepare()) [[likely]]
220 {
221 if (_S_current(__cb, std::__addressof(__ret), __skip))
222 __ret._M_clear();
223 }
224
225 return __ret;
226 }
227
228 [[__gnu__::__noinline__]]
229 static basic_stacktrace
230 current(size_type __skip, size_type __max_depth,
231 const allocator_type& __alloc = allocator_type()) noexcept
232 {
233 __glibcxx_assert(__skip <= (size_type(-1) - __max_depth));
234
235 basic_stacktrace __ret(__alloc);
236 if (__max_depth == 0) [[unlikely]]
237 return __ret;
238 if (__skip >= __INT_MAX__) [[unlikely]]
239 return __ret;
240 if (auto __cb = __ret._M_prepare(__max_depth)) [[likely]]
241 {
242 if (_S_current(__cb, std::__addressof(__ret), __skip) < 0)
243 __ret._M_clear();
244 else if (__ret.size() > __max_depth)
245 {
246 __ret._M_impl._M_resize(__max_depth, __ret._M_alloc);
247
248 if (__ret._M_impl._M_capacity / 2 >= __max_depth)
249 {
250 // shrink to fit
251 _Impl __tmp = __ret._M_impl._M_clone(__ret._M_alloc);
252 if (__tmp._M_capacity)
253 {
254 __ret._M_clear();
255 __ret._M_impl = __tmp;
256 }
257 }
258 }
259 }
260 return __ret;
261 }
262
263 basic_stacktrace()
264 noexcept(is_nothrow_default_constructible_v<allocator_type>)
265 { }
266
267 explicit
268 basic_stacktrace(const allocator_type& __alloc) noexcept
269 : _M_alloc(__alloc)
270 { }
271
272 basic_stacktrace(const basic_stacktrace& __other) noexcept
273 : basic_stacktrace(__other,
274 _AllocTraits::select_on_container_copy_construction(__other._M_alloc))
275 { }
276
277 basic_stacktrace(basic_stacktrace&& __other) noexcept
278 : _M_alloc(std::move(__other._M_alloc)),
279 _M_impl(std::__exchange(__other._M_impl, {}))
280 { }
281
282 basic_stacktrace(const basic_stacktrace& __other,
283 const allocator_type& __alloc) noexcept
284 : _M_alloc(__alloc)
285 {
286 if (const auto __s = __other._M_impl._M_size)
287 _M_impl = __other._M_impl._M_clone(_M_alloc);
288 }
289
290 basic_stacktrace(basic_stacktrace&& __other,
291 const allocator_type& __alloc) noexcept
292 : _M_alloc(__alloc)
293 {
294 if constexpr (_AllocTraits::is_always_equal::value)
295 _M_impl = std::__exchange(__other._M_impl, {});
296 else if (_M_alloc == __other._M_alloc)
297 _M_impl = std::__exchange(__other._M_impl, {});
298 else if (const auto __s = __other._M_impl._M_size)
299 _M_impl = __other._M_impl._M_clone(_M_alloc);
300 }
301
302 basic_stacktrace&
303 operator=(const basic_stacktrace& __other) noexcept
304 {
305 if (std::__addressof(__other) == this)
306 return *this;
307
308 constexpr bool __pocca
309 = _AllocTraits::propagate_on_container_copy_assignment::value;
310 constexpr bool __always_eq = _AllocTraits::is_always_equal::value;
311
312 const auto __s = __other.size();
313
314 if constexpr (!__always_eq && __pocca)
315 {
316 if (_M_alloc != __other._M_alloc)
317 {
318 // Cannot keep the same storage, so deallocate it now.
319 _M_clear();
320 }
321 }
322
323 if (_M_impl._M_capacity < __s)
324 {
325 // Need to allocate new storage.
326 _M_clear();
327
328 if constexpr (__pocca)
329 _M_alloc = __other._M_alloc;
330
331 _M_impl = __other._M_impl._M_clone(_M_alloc);
332 }
333 else
334 {
335 // Current storage is large enough.
336 _M_impl._M_resize(0, _M_alloc);
337 _M_impl._M_assign(__other._M_impl, _M_alloc);
338
339 if constexpr (__pocca)
340 _M_alloc = __other._M_alloc;
341 }
342
343 return *this;
344 }
345
346 basic_stacktrace&
347 operator=(basic_stacktrace&& __other) noexcept
348 {
349 if (std::__addressof(__other) == this)
350 return *this;
351
352 constexpr bool __pocma
353 = _AllocTraits::propagate_on_container_move_assignment::value;
354
355 if constexpr (_AllocTraits::is_always_equal::value)
356 std::swap(_M_impl, __other._M_impl);
357 else if (_M_alloc == __other._M_alloc)
358 std::swap(_M_impl, __other._M_impl);
359 else if constexpr (__pocma)
360 {
361 // Free current storage and take ownership of __other's storage.
362 _M_clear();
363 _M_impl = std::__exchange(__other._M_impl, {});
364 }
365 else // Allocators are unequal and don't propagate.
366 {
367 const size_type __s = __other.size();
368
369 if (_M_impl._M_capacity < __s)
370 {
371 // Need to allocate new storage.
372 _M_clear();
373 _M_impl = __other._M_impl._M_clone(_M_alloc);
374 }
375 else
376 {
377 // Current storage is large enough.
378 _M_impl._M_resize(0, _M_alloc);
379 _M_impl._M_assign(__other._M_impl, _M_alloc);
380 }
381 }
382
383 if constexpr (__pocma)
384 _M_alloc = std::move(__other._M_alloc);
385
386 return *this;
387 }
388
389 constexpr ~basic_stacktrace()
390 {
391 _M_clear();
392 }
393
394 // [stacktrace.basic.obs], observers
395 [[nodiscard]]
396 allocator_type get_allocator() const noexcept { return _M_alloc; }
397
398 [[nodiscard]]
399 const_iterator
400 begin() const noexcept
401 { return const_iterator{_M_impl._M_frames}; }
402
403 [[nodiscard]]
404 const_iterator
405 end() const noexcept
406 { return begin() + size(); }
407
408 [[nodiscard]]
409 const_reverse_iterator
410 rbegin() const noexcept
411 { return std::make_reverse_iterator(end()); }
412
413 [[nodiscard]]
414 const_reverse_iterator
415 rend() const noexcept
416 { return std::make_reverse_iterator(begin()); }
417
418 [[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
419 [[nodiscard]] const_iterator cend() const noexcept { return end(); }
420
421 [[nodiscard]]
422 const_reverse_iterator
423 crbegin() const noexcept { return rbegin(); };
424
425 [[nodiscard]]
426 const_reverse_iterator
427 crend() const noexcept { return rend(); };
428
429 [[nodiscard]] bool empty() const noexcept { return size() == 0; }
430 [[nodiscard]] size_type size() const noexcept { return _M_impl._M_size; }
431
432 [[nodiscard]]
433 size_type
434 max_size() const noexcept
435 { return _Impl::_S_max_size(_M_alloc); }
436
437 [[nodiscard]]
438 const_reference
439 operator[](size_type __n) const noexcept
440 {
441 __glibcxx_assert(__n < size());
442 return begin()[__n];
443 }
444
445 [[nodiscard]]
446 const_reference
447 at(size_type __n) const
448 {
449 if (__n >= size())
450 __throw_out_of_range("basic_stacktrace::at: bad frame number");
451 return begin()[__n];
452 }
453
454 // [stacktrace.basic.cmp], comparisons
455 template<typename _Allocator2>
456 [[nodiscard]]
457 friend bool
458 operator==(const basic_stacktrace& __x,
459 const basic_stacktrace<_Allocator2>& __y) noexcept
460 { return std::equal(__x.begin(), __x.end(), __y.begin(), __y.end()); }
461
462 template<typename _Allocator2>
463 [[nodiscard]]
464 friend strong_ordering
465 operator<=>(const basic_stacktrace& __x,
466 const basic_stacktrace<_Allocator2>& __y) noexcept
467 {
468 if (auto __s = __x.size() <=> __y.size(); __s != 0)
469 return __s;
470 return std::lexicographical_compare_three_way(__x.begin(), __x.end(),
471 __y.begin(), __y.end());
472 }
473
474 // [stacktrace.basic.mod], modifiers
475 void
476 swap(basic_stacktrace& __other) noexcept
477 {
478 std::swap(_M_impl, __other._M_impl);
479 if constexpr (_AllocTraits::propagate_on_container_swap::value)
480 std::swap(_M_alloc, __other._M_alloc);
481 else if constexpr (!_AllocTraits::is_always_equal::value)
482 {
483 __glibcxx_assert(_M_alloc == __other._M_alloc);
484 }
485 }
486
487 private:
488 bool
489 _M_push_back(const value_type& __x) noexcept
490 {
491 return _M_impl._M_push_back(_M_alloc, __x);
492 }
493
494 void
495 _M_clear() noexcept
496 {
497 _M_impl._M_resize(0, _M_alloc);
498 _M_impl._M_deallocate(_M_alloc);
499 }
500
501 // Precondition: __max_depth != 0
502 auto
503 _M_prepare(size_type __max_depth = -1) noexcept
504 -> int (*) (void*, uintptr_t)
505 {
506 auto __cb = +[](void* __data, uintptr_t __pc) {
507 auto& __s = *static_cast<basic_stacktrace*>(__data);
508 stacktrace_entry __f;
509 __f._M_pc = __pc;
510 if (__s._M_push_back(__f)) [[likely]]
511 return 0; // continue tracing
512 return -1; // stop tracing due to error
513 };
514
515 if (__max_depth > 128)
516 __max_depth = 64; // soft limit, _M_push_back will reallocate
517 else
518 __cb = [](void* __data, uintptr_t __pc) {
519 auto& __s = *static_cast<basic_stacktrace*>(__data);
520 stacktrace_entry __f;
521 __f._M_pc = __pc;
522 if (__s.size() == __s._M_impl._M_capacity) [[unlikely]]
523 return 1; // stop tracing due to reaching max depth
524 if (__s._M_push_back(__f)) [[likely]]
525 return 0; // continue tracing
526 return -1; // stop tracing due to error
527 };
528
529 if (_M_impl._M_allocate(_M_alloc, __max_depth)) [[likely]]
530 return __cb;
531 return nullptr;
532 }
533
534 struct _Impl
535 {
536 using pointer = typename _AllocTraits::pointer;
537
538 pointer _M_frames = nullptr;
539 size_type _M_size = 0;
540 size_type _M_capacity = 0;
541
542 static size_type
543 _S_max_size(const allocator_type& __alloc) noexcept
544 {
545 const size_t __size_max = __gnu_cxx::__int_traits<size_type>::__max;
546 const size_t __alloc_max = _AllocTraits::max_size(__alloc);
547 return std::min(__size_max, __alloc_max);
548 }
549
550 // Precondition: _M_frames == nullptr && __n != 0
551 pointer
552 _M_allocate(allocator_type& __alloc, size_type __n) noexcept
553 {
554 if (__n <= _S_max_size(__alloc)) [[likely]]
555 {
556 if constexpr (is_same_v<allocator_type, allocator<value_type>>)
557 {
558 // Use non-throwing __get_temporary_buffer, so that we
559 // don't need to handle exceptions from __alloc.allocate(n).
560 auto __p = __detail::__get_temporary_buffer<value_type>(__n);
561 if (__p == nullptr) [[unlikely]]
562 return nullptr;
563 _M_frames = __p;
564 }
565 else
566 {
567 __try
568 {
569 _M_frames = __alloc.allocate(__n);
570 }
571 __catch (...)
572 {
573 return nullptr;
574 }
575 }
576 _M_capacity = __n;
577 return _M_frames;
578 }
579 return nullptr;
580 }
581
582 void
583 _M_deallocate(allocator_type& __alloc) noexcept
584 {
585 if (_M_capacity)
586 {
587 if constexpr (is_same_v<allocator_type, allocator<value_type>>)
588 __detail::__return_temporary_buffer(_M_frames, _M_capacity);
589 else
590 __alloc.deallocate(_M_frames, _M_capacity);
591 _M_frames = nullptr;
592 _M_capacity = 0;
593 }
594 }
595
596 // Precondition: __n <= _M_size
597 void
598 _M_resize(size_type __n, allocator_type& __alloc) noexcept
599 {
600 for (size_type __i = __n; __i < _M_size; ++__i)
601 _AllocTraits::destroy(__alloc, &_M_frames[__i]);
602 _M_size = __n;
603 }
604
605 bool
606 _M_push_back(allocator_type& __alloc,
607 const stacktrace_entry& __f) noexcept
608 {
609 if (_M_size == _M_capacity) [[unlikely]]
610 {
611 _Impl __tmp = _M_xclone(_M_capacity ? _M_capacity : 8, __alloc);
612 if (!__tmp._M_capacity) [[unlikely]]
613 return false;
614 _M_resize(0, __alloc);
615 _M_deallocate(__alloc);
616 *this = __tmp;
617 }
618 stacktrace_entry* __addr = std::to_address(_M_frames + _M_size++);
619 _AllocTraits::construct(__alloc, __addr, __f);
620 return true;
621 }
622
623 // Precondition: _M_size != 0
624 _Impl
625 _M_clone(allocator_type& __alloc) const noexcept
626 {
627 return _M_xclone(_M_size, __alloc);
628 }
629
630 // Precondition: _M_size != 0 || __extra != 0
631 _Impl
632 _M_xclone(size_type __extra, allocator_type& __alloc) const noexcept
633 {
634 _Impl __i;
635 if (__i._M_allocate(__alloc, _M_size + __extra)) [[likely]]
636 __i._M_assign(*this, __alloc);
637 return __i;
638 }
639
640 // Precondition: _M_capacity >= __other._M_size
641 void
642 _M_assign(const _Impl& __other, allocator_type& __alloc) noexcept
643 {
644 std::__uninitialized_copy_a(__other._M_frames,
645 __other._M_frames + __other._M_size,
646 _M_frames, __alloc);
647 _M_size = __other._M_size;
648 }
649 };
650
651 [[no_unique_address]] allocator_type _M_alloc{};
652
653 _Impl _M_impl{};
654 };
655
656 // basic_stacktrace typedef names
657 using stacktrace = basic_stacktrace<allocator<stacktrace_entry>>;
658
659 // [stacktrace.basic.nonmem], non-member functions
660 template<typename _Allocator>
661 inline void
662 swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b)
663 noexcept(noexcept(__a.swap(__b)))
664 { __a.swap(__b); }
665
666 inline ostream&
667 operator<<(ostream& __os, const stacktrace_entry& __f)
668 {
669 string __desc, __file;
670 int __line;
671 if (__f._M_get_info(&__desc, &__file, &__line))
672 {
673 __os.width(4);
674 __os << __desc << " at " << __file << ':' << __line;
675 }
676 return __os;
677 }
678
679 template<typename _Allocator>
680 inline ostream&
681 operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __st)
682 {
683 for (stacktrace::size_type __i = 0; __i < __st.size(); ++__i)
684 {
685 __os.width(4);
686 __os << __i << "# " << __st[__i] << '\n';
687 }
688 return __os;
689 }
690
691 [[nodiscard]]
692 inline string
693 to_string(const stacktrace_entry& __f)
694 {
695 std::ostringstream __os;
696 __os << __f;
697 return std::move(__os).str();
698 }
699
700 template<typename _Allocator>
701 [[nodiscard]]
702 string
703 to_string(const basic_stacktrace<_Allocator>& __st)
704 {
705 std::ostringstream __os;
706 __os << __st;
707 return std::move(__os).str();
708 }
709
710 template<>
711 class formatter<stacktrace_entry>
712 {
713 public:
714 constexpr typename basic_format_parse_context<char>::iterator
715 parse(basic_format_parse_context<char>& __pc)
716 {
717 __format::_Spec<char> __spec{};
718 const auto __last = __pc.end();
719 auto __first = __pc.begin();
720
721 auto __finalize = [this, &__spec] {
722 _M_spec = __spec;
723 };
724
725 auto __finished = [&] {
726 if (__first == __last || *__first == '}')
727 {
728 __finalize();
729 return true;
730 }
731 return false;
732 };
733
734 if (__finished())
735 return __first;
736
737 __first = __spec._M_parse_fill_and_align(__first, __last);
738 if (__finished())
739 return __first;
740
741 __first = __spec._M_parse_width(__first, __last, __pc);
742 if (__finished())
743 return __first;
744
745 __throw_format_error("format error: invalid format-spec for "
746 "std::stacktrace_entry");
747 }
748
749 template<typename _Out>
750 typename basic_format_context<_Out, char>::iterator
751 format(const stacktrace_entry& __x,
752 basic_format_context<_Out, char>& __fc) const
753 {
754 std::ostringstream __os;
755 __os << __x;
756 auto __str = __os.view();
757 return __format::__write_padded_as_spec(__str, __str.size(),
758 __fc, _M_spec);
759 }
760
761 private:
762 __format::_Spec<char> _M_spec;
763 };
764
765 template<typename _Allocator>
766 class formatter<basic_stacktrace<_Allocator>>
767 {
768 public:
769 constexpr typename basic_format_parse_context<char>::iterator
770 parse(basic_format_parse_context<char>& __pc)
771 {
772 const auto __first = __pc.begin();
773 if (__first == __pc.end() || *__first == '}')
774 return __first;
775 __throw_format_error("format error: invalid format-spec for "
776 "std::basic_stacktrace");
777 }
778
779 template<typename _Out>
780 typename basic_format_context<_Out, char>::iterator
781 format(const basic_stacktrace<_Allocator>& __x,
782 basic_format_context<_Out, char>& __fc) const
783 {
784 std::ostringstream __os;
785 __os << __x;
786 return __format::__write(__fc.out(), __os.view());
787 }
788 };
789
790 namespace pmr
791 {
792 using stacktrace
793 = basic_stacktrace<polymorphic_allocator<stacktrace_entry>>;
794 }
795
796 // [stacktrace.basic.hash], hash support
797
798 template<>
799 struct hash<stacktrace_entry>
800 {
801 [[nodiscard]]
802 size_t
803 operator()(const stacktrace_entry& __f) const noexcept
804 {
805 using __h = hash<stacktrace_entry::native_handle_type>;
806 return __h()(__f.native_handle());
807 }
808 };
809
810 template<typename _Allocator>
811 struct hash<basic_stacktrace<_Allocator>>
812 {
813 [[nodiscard]]
814 size_t
815 operator()(const basic_stacktrace<_Allocator>& __st) const noexcept
816 {
817 hash<stacktrace_entry> __h;
818 size_t __val = _Hash_impl::hash(__st.size());
819 for (const auto& __f : __st)
820 __val = _Hash_impl::__hash_combine(__h(__f), __val);
821 return __val;
822 }
823 };
824
825 _GLIBCXX_END_NAMESPACE_VERSION
826 } // namespace std
827 #endif // __cpp_lib_stacktrace
828 #endif /* _GLIBCXX_STACKTRACE */
829