xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/include/std/stacktrace (revision 0a3071956a3a9fdebdbf7f338cf2d439b45fc728)
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
42struct __glibcxx_backtrace_state;
43struct __glibcxx_backtrace_simple_data;
44
45extern "C"
46{
47__glibcxx_backtrace_state*
48__glibcxx_backtrace_create_state(const char*, int,
49				 void(*)(void*, const char*, int),
50				 void*);
51
52int
53__glibcxx_backtrace_simple(__glibcxx_backtrace_state*, int,
54			   int (*) (void*, uintptr_t),
55			   void(*)(void*, const char*, int),
56			   void*);
57int
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
64int
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
72namespace __cxxabiv1
73{
74  extern "C" char*
75  __cxa_demangle(const char* __mangled_name, char* __output_buffer,
76		 size_t* __length, int* __status);
77}
78
79namespace 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