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