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