| // <stacktrace> -*- C++ -*- |
| |
| // Copyright The GNU Toolchain Authors. |
| // |
| // This file is part of the GNU ISO C++ Library. This library is free |
| // software; you can redistribute it and/or modify it under the |
| // terms of the GNU General Public License as published by the |
| // Free Software Foundation; either version 3. |
| |
| // This library is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| // GNU General Public License for more details. |
| |
| // Under Section 7 of GPL version 3, you are granted additional |
| // permissions described in the GCC Runtime Library Exception, version |
| // 3.1, as published by the Free Software Foundation. |
| |
| // You should have received a copy of the GNU General Public License and |
| // a copy of the GCC Runtime Library Exception along with this program; |
| // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| // <http://www.gnu.org/licenses/>. |
| |
| #ifndef _GLIBCXX_STACKTRACE |
| #define _GLIBCXX_STACKTRACE 1 |
| |
| #pragma GCC system_header |
| |
| #include <bits/requires_hosted.h> // std::string bound |
| |
| #include <bits/c++config.h> |
| |
| #if __cplusplus > 202002L && _GLIBCXX_HAVE_STACKTRACE |
| #include <compare> |
| #include <new> |
| #include <string> |
| #include <sstream> |
| #include <bits/memory_resource.h> |
| #include <bits/stl_algobase.h> |
| #include <bits/stl_algo.h> |
| #include <bits/stl_iterator.h> |
| #include <bits/stl_uninitialized.h> |
| #include <ext/numeric_traits.h> |
| |
| struct __glibcxx_backtrace_state; |
| struct __glibcxx_backtrace_simple_data; |
| |
| extern "C" |
| { |
| __glibcxx_backtrace_state* |
| __glibcxx_backtrace_create_state(const char*, int, |
| void(*)(void*, const char*, int), |
| void*); |
| |
| int |
| __glibcxx_backtrace_simple(__glibcxx_backtrace_state*, int, |
| int (*) (void*, __UINTPTR_TYPE__), |
| void(*)(void*, const char*, int), |
| void*); |
| int |
| __glibcxx_backtrace_pcinfo(__glibcxx_backtrace_state*, __UINTPTR_TYPE__, |
| int (*)(void*, __UINTPTR_TYPE__, |
| const char*, int, const char*), |
| void(*)(void*, const char*, int), |
| void*); |
| |
| int |
| __glibcxx_backtrace_syminfo(__glibcxx_backtrace_state*, __UINTPTR_TYPE__ addr, |
| void (*) (void*, __UINTPTR_TYPE__, const char*, |
| __UINTPTR_TYPE__, __UINTPTR_TYPE__), |
| void(*)(void*, const char*, int), |
| void*); |
| } |
| |
| namespace __cxxabiv1 |
| { |
| extern "C" char* |
| __cxa_demangle(const char* __mangled_name, char* __output_buffer, |
| size_t* __length, int* __status); |
| } |
| |
| namespace std _GLIBCXX_VISIBILITY(default) |
| { |
| _GLIBCXX_BEGIN_NAMESPACE_VERSION |
| |
| #define __cpp_lib_stacktrace 202011L |
| |
| // [stacktrace.entry], class stacktrace_entry |
| class stacktrace_entry |
| { |
| using uint_least32_t = __UINT_LEAST32_TYPE__; |
| using uintptr_t = __UINTPTR_TYPE__; |
| |
| public: |
| using native_handle_type = uintptr_t; |
| |
| // [stacktrace.entry.ctor], constructors |
| |
| constexpr |
| stacktrace_entry() noexcept = default; |
| |
| constexpr |
| stacktrace_entry(const stacktrace_entry& __other) noexcept = default; |
| |
| constexpr stacktrace_entry& |
| operator=(const stacktrace_entry& __other) noexcept = default; |
| |
| ~stacktrace_entry() = default; |
| |
| // [stacktrace.entry.obs], observers |
| |
| constexpr native_handle_type |
| native_handle() const noexcept { return _M_pc; } |
| |
| constexpr explicit operator bool() const noexcept { return _M_pc != -1; } |
| |
| // [stacktrace.entry.query], query |
| string |
| description() const |
| { |
| string __s; |
| _M_get_info(&__s, nullptr, nullptr); |
| return __s; |
| } |
| |
| string |
| source_file() const |
| { |
| string __s; |
| _M_get_info(nullptr, &__s, nullptr); |
| return __s; |
| } |
| |
| uint_least32_t |
| source_line() const |
| { |
| int __line = 0; |
| _M_get_info(nullptr, nullptr, &__line); |
| return __line; |
| } |
| |
| // [stacktrace.entry.cmp], comparison |
| friend constexpr bool |
| operator==(const stacktrace_entry& __x, |
| const stacktrace_entry& __y) noexcept |
| { return __x._M_pc == __y._M_pc; } |
| |
| friend constexpr strong_ordering |
| operator<=>(const stacktrace_entry& __x, |
| const stacktrace_entry& __y) noexcept |
| { return __x._M_pc <=> __y._M_pc; } |
| |
| private: |
| native_handle_type _M_pc = -1; |
| |
| template<typename _Allocator> friend class basic_stacktrace; |
| |
| static __glibcxx_backtrace_state* |
| _S_init() |
| { |
| static __glibcxx_backtrace_state* __state |
| = __glibcxx_backtrace_create_state(nullptr, 1, nullptr, nullptr); |
| return __state; |
| } |
| |
| template<typename _CharT, typename _Traits> |
| friend basic_ostream<_CharT, _Traits>& |
| operator<<(basic_ostream<_CharT, _Traits>&, const stacktrace_entry&); |
| |
| bool |
| _M_get_info(string* __desc, string* __file, int* __line) const |
| { |
| if (!*this) |
| return false; |
| |
| struct _Data |
| { |
| string* _M_desc; |
| string* _M_file; |
| int* _M_line; |
| } __data = { __desc, __file, __line }; |
| |
| auto __cb = [](void* __data, uintptr_t, const char* __filename, |
| int __lineno, const char* __function) -> int { |
| auto& __d = *static_cast<_Data*>(__data); |
| if (__function && __d._M_desc) |
| *__d._M_desc = _S_demangle(__function); |
| if (__filename && __d._M_file) |
| *__d._M_file = __filename; |
| if (__d._M_line) |
| *__d._M_line = __lineno; |
| return __function != nullptr; |
| }; |
| const auto __state = _S_init(); |
| if (::__glibcxx_backtrace_pcinfo(__state, _M_pc, +__cb, nullptr, &__data)) |
| return true; |
| if (__desc && __desc->empty()) |
| { |
| auto __cb2 = [](void* __data, uintptr_t, const char* __symname, |
| uintptr_t, uintptr_t) { |
| if (__symname) |
| *static_cast<_Data*>(__data)->_M_desc = _S_demangle(__symname); |
| }; |
| if (::__glibcxx_backtrace_syminfo(__state, _M_pc, +__cb2, nullptr, |
| &__data)) |
| return true; |
| } |
| return false; |
| } |
| |
| static string |
| _S_demangle(const char* __name) |
| { |
| string __s; |
| int __status; |
| char* __str = __cxxabiv1::__cxa_demangle(__name, nullptr, nullptr, |
| &__status); |
| if (__status == 0) |
| __s = __str; |
| __builtin_free(__str); |
| return __s; |
| } |
| }; |
| |
| // [stacktrace.basic], class template basic_stacktrace |
| template<typename _Allocator> |
| class basic_stacktrace |
| { |
| using _AllocTraits = allocator_traits<_Allocator>; |
| using uintptr_t = __UINTPTR_TYPE__; |
| |
| public: |
| using value_type = stacktrace_entry; |
| using const_reference = const value_type&; |
| using reference = value_type&; |
| using const_iterator |
| = __gnu_cxx::__normal_iterator<value_type*, basic_stacktrace>; |
| using iterator = const_iterator; |
| using reverse_iterator = std::reverse_iterator<iterator>; |
| using const_reverse_iterator = std::reverse_iterator<const_iterator>; |
| using difference_type = ptrdiff_t; |
| using size_type = unsigned short; |
| using allocator_type = _Allocator; |
| |
| // [stacktrace.basic.ctor], creation and assignment |
| |
| [[__gnu__::__noinline__]] |
| static basic_stacktrace |
| current(const allocator_type& __alloc = allocator_type()) noexcept |
| { |
| basic_stacktrace __ret(__alloc); |
| if (auto __cb = __ret._M_prepare()) [[likely]] |
| { |
| auto __state = stacktrace_entry::_S_init(); |
| if (__glibcxx_backtrace_simple(__state, 1, __cb, nullptr, |
| std::__addressof(__ret))) |
| __ret._M_clear(); |
| } |
| return __ret; |
| } |
| |
| [[__gnu__::__noinline__]] |
| static basic_stacktrace |
| current(size_type __skip, |
| const allocator_type& __alloc = allocator_type()) noexcept |
| { |
| basic_stacktrace __ret(__alloc); |
| if (__skip >= __INT_MAX__) [[unlikely]] |
| return __ret; |
| if (auto __cb = __ret._M_prepare()) [[likely]] |
| { |
| auto __state = stacktrace_entry::_S_init(); |
| if (__glibcxx_backtrace_simple(__state, __skip + 1, __cb, nullptr, |
| std::__addressof(__ret))) |
| __ret._M_clear(); |
| } |
| |
| return __ret; |
| } |
| |
| [[__gnu__::__noinline__]] |
| static basic_stacktrace |
| current(size_type __skip, size_type __max_depth, |
| const allocator_type& __alloc = allocator_type()) noexcept |
| { |
| __glibcxx_assert(__skip <= (size_type(-1) - __max_depth)); |
| |
| basic_stacktrace __ret(__alloc); |
| if (__max_depth == 0) [[unlikely]] |
| return __ret; |
| if (__skip >= __INT_MAX__) [[unlikely]] |
| return __ret; |
| if (auto __cb = __ret._M_prepare(__max_depth)) [[likely]] |
| { |
| auto __state = stacktrace_entry::_S_init(); |
| int __err = __glibcxx_backtrace_simple(__state, __skip + 1, __cb, |
| nullptr, |
| std::__addressof(__ret)); |
| if (__err < 0) |
| __ret._M_clear(); |
| else if (__ret.size() > __max_depth) |
| { |
| __ret._M_impl._M_resize(__max_depth, __ret._M_alloc); |
| |
| if (__ret._M_impl._M_capacity / 2 >= __max_depth) |
| { |
| // shrink to fit |
| _Impl __tmp = __ret._M_impl._M_clone(__ret._M_alloc); |
| if (__tmp._M_capacity) |
| { |
| __ret._M_clear(); |
| __ret._M_impl = __tmp; |
| } |
| } |
| } |
| } |
| return __ret; |
| } |
| |
| basic_stacktrace() |
| noexcept(is_nothrow_default_constructible_v<allocator_type>) |
| { } |
| |
| explicit |
| basic_stacktrace(const allocator_type& __alloc) noexcept |
| : _M_alloc(__alloc) |
| { } |
| |
| basic_stacktrace(const basic_stacktrace& __other) noexcept |
| : basic_stacktrace(__other, |
| _AllocTraits::select_on_container_copy_construction(__other._M_alloc)) |
| { } |
| |
| basic_stacktrace(basic_stacktrace&& __other) noexcept |
| : _M_alloc(std::move(__other._M_alloc)), |
| _M_impl(std::__exchange(__other._M_impl, {})) |
| { } |
| |
| basic_stacktrace(const basic_stacktrace& __other, |
| const allocator_type& __alloc) noexcept |
| : _M_alloc(__alloc) |
| { |
| if (const auto __s = __other._M_impl._M_size) |
| _M_impl = __other._M_impl._M_clone(_M_alloc); |
| } |
| |
| basic_stacktrace(basic_stacktrace&& __other, |
| const allocator_type& __alloc) noexcept |
| : _M_alloc(__alloc) |
| { |
| if constexpr (_Allocator::is_always_equal::value) |
| _M_impl = std::__exchange(__other._M_impl, {}); |
| else if (_M_alloc == __other._M_alloc) |
| _M_impl = std::__exchange(__other._M_impl, {}); |
| else if (const auto __s = __other._M_impl._M_size) |
| _M_impl = __other._M_impl._M_clone(_M_alloc); |
| } |
| |
| basic_stacktrace& |
| operator=(const basic_stacktrace& __other) noexcept |
| { |
| if (std::__addressof(__other) == this) |
| return *this; |
| |
| constexpr bool __pocca |
| = _AllocTraits::propagate_on_container_copy_assignment::value; |
| constexpr bool __always_eq = _AllocTraits::is_always_equal::value; |
| |
| const auto __s = __other.size(); |
| |
| if constexpr (!__always_eq && __pocca) |
| { |
| if (_M_alloc != __other._M_alloc) |
| { |
| // Cannot keep the same storage, so deallocate it now. |
| _M_clear(); |
| } |
| } |
| |
| if (_M_impl._M_capacity < __s) |
| { |
| // Need to allocate new storage. |
| _M_clear(); |
| |
| if constexpr (__pocca) |
| _M_alloc = __other._M_alloc; |
| |
| _M_impl = __other._M_impl._M_clone(_M_alloc); |
| } |
| else |
| { |
| // Current storage is large enough. |
| _M_impl._M_resize(0, _M_alloc); |
| _M_impl._M_assign(__other._M_impl, _M_alloc); |
| |
| if constexpr (__pocca) |
| _M_alloc = __other._M_alloc; |
| } |
| |
| return *this; |
| } |
| |
| basic_stacktrace& |
| operator=(basic_stacktrace&& __other) noexcept |
| { |
| if (std::__addressof(__other) == this) |
| return *this; |
| |
| constexpr bool __pocma |
| = _AllocTraits::propagate_on_container_move_assignment::value; |
| |
| if constexpr (_AllocTraits::is_always_equal::value) |
| std::swap(_M_impl, __other._M_impl); |
| else if (_M_alloc == __other._M_alloc) |
| std::swap(_M_impl, __other._M_impl); |
| else if constexpr (__pocma) |
| { |
| // Free current storage and take ownership of __other's storage. |
| _M_clear(); |
| _M_impl = std::__exchange(__other._M_impl, {}); |
| } |
| else // Allocators are unequal and don't propagate. |
| { |
| const size_type __s = __other.size(); |
| |
| if (_M_impl._M_capacity < __s) |
| { |
| // Need to allocate new storage. |
| _M_clear(); |
| _M_impl = __other._M_impl._M_clone(_M_alloc); |
| } |
| else |
| { |
| // Current storage is large enough. |
| _M_impl._M_resize(0, _M_alloc); |
| _M_impl._M_assign(__other._M_impl, _M_alloc); |
| } |
| } |
| |
| if constexpr (__pocma) |
| _M_alloc = std::move(__other._M_alloc); |
| |
| return *this; |
| } |
| |
| constexpr ~basic_stacktrace() |
| { |
| _M_clear(); |
| } |
| |
| // [stacktrace.basic.obs], observers |
| allocator_type get_allocator() const noexcept { return _M_alloc; } |
| |
| const_iterator |
| begin() const noexcept |
| { return const_iterator{_M_impl._M_frames}; } |
| |
| const_iterator |
| end() const noexcept |
| { return begin() + size(); } |
| |
| const_reverse_iterator |
| rbegin() const noexcept |
| { return std::make_reverse_iterator(end()); } |
| |
| const_reverse_iterator |
| rend() const noexcept |
| { return std::make_reverse_iterator(begin()); } |
| |
| const_iterator cbegin() const noexcept { return begin(); } |
| const_iterator cend() const noexcept { return end(); } |
| const_reverse_iterator crbegin() const noexcept { return rbegin(); }; |
| const_reverse_iterator crend() const noexcept { return rend(); }; |
| |
| [[nodiscard]] bool empty() const noexcept { return size() == 0; } |
| size_type size() const noexcept { return _M_impl._M_size; } |
| |
| size_type |
| max_size() const noexcept |
| { return _Impl::_S_max_size(_M_impl._M_alloc); } |
| |
| const_reference |
| operator[](size_type __n) const noexcept |
| { |
| __glibcxx_assert(__n < size()); |
| return begin()[__n]; |
| } |
| |
| const_reference |
| at(size_type __n) const |
| { |
| if (__n >= size()) |
| __throw_out_of_range("basic_stack_trace::at: bad frame number"); |
| return begin()[__n]; |
| } |
| |
| // [stacktrace.basic.cmp], comparisons |
| template<typename _Allocator2> |
| friend bool |
| operator==(const basic_stacktrace& __x, |
| const basic_stacktrace<_Allocator2>& __y) noexcept |
| { return std::equal(__x.begin(), __x.end(), __y.begin(), __y.end()); } |
| |
| template<typename _Allocator2> |
| friend strong_ordering |
| operator<=>(const basic_stacktrace& __x, |
| const basic_stacktrace<_Allocator2>& __y) noexcept |
| { |
| if (auto __s = __x.size() <=> __y.size(); __s != 0) |
| return __s; |
| return std::lexicographical_compare_three_way(__x.begin(), __x.end(), |
| __y.begin(), __y.end()); |
| } |
| |
| // [stacktrace.basic.mod], modifiers |
| void |
| swap(basic_stacktrace& __other) noexcept |
| { |
| std::swap(_M_impl, __other._M_impl); |
| if constexpr (_AllocTraits::propagate_on_container_swap::value) |
| std::swap(_M_alloc, __other._M_alloc); |
| else if constexpr (!_AllocTraits::is_always_equal::value) |
| { |
| __glibcxx_assert(_M_alloc == __other._M_alloc); |
| } |
| } |
| |
| private: |
| bool |
| _M_push_back(const value_type& __x) noexcept |
| { |
| return _M_impl._M_push_back(_M_alloc, __x); |
| } |
| |
| void |
| _M_clear() noexcept |
| { |
| _M_impl._M_resize(0, _M_alloc); |
| _M_impl._M_deallocate(_M_alloc); |
| } |
| |
| // Precondition: __max_depth != 0 |
| auto |
| _M_prepare(size_type __max_depth = -1) noexcept |
| -> int (*) (void*, uintptr_t) |
| { |
| auto __cb = +[](void* __data, uintptr_t __pc) { |
| auto& __s = *static_cast<basic_stacktrace*>(__data); |
| stacktrace_entry __f; |
| __f._M_pc = __pc; |
| if (__s._M_push_back(__f)) [[likely]] |
| return 0; // continue tracing |
| return -1; // stop tracing due to error |
| }; |
| |
| if (__max_depth > 128) |
| __max_depth = 64; // soft limit, _M_push_back will reallocate |
| else |
| __cb = [](void* __data, uintptr_t __pc) { |
| auto& __s = *static_cast<basic_stacktrace*>(__data); |
| stacktrace_entry __f; |
| __f._M_pc = __pc; |
| if (__s.size() == __s._M_impl._M_capacity) [[unlikely]] |
| return 1; // stop tracing due to reaching max depth |
| if (__s._M_push_back(__f)) [[likely]] |
| return 0; // continue tracing |
| return -1; // stop tracing due to error |
| }; |
| |
| if (_M_impl._M_allocate(_M_alloc, __max_depth)) [[likely]] |
| return __cb; |
| return nullptr; |
| } |
| |
| struct _Impl |
| { |
| using pointer = typename _AllocTraits::pointer; |
| |
| pointer _M_frames = nullptr; |
| size_type _M_size = 0; |
| size_type _M_capacity = 0; |
| |
| static size_type |
| _S_max_size(const allocator_type& __alloc) noexcept |
| { |
| const size_t __size_max = __gnu_cxx::__int_traits<size_type>::__max; |
| const size_t __alloc_max = _AllocTraits::max_size(__alloc); |
| return std::min(__size_max, __alloc_max); |
| } |
| |
| #if __has_builtin(__builtin_operator_new) >= 201802L |
| # define _GLIBCXX_OPERATOR_NEW __builtin_operator_new |
| # define _GLIBCXX_OPERATOR_DELETE __builtin_operator_delete |
| #else |
| # define _GLIBCXX_OPERATOR_NEW ::operator new |
| # define _GLIBCXX_OPERATOR_DELETE ::operator delete |
| #endif |
| |
| // Precondition: _M_frames == nullptr && __n != 0 |
| pointer |
| _M_allocate(allocator_type& __alloc, size_type __n) noexcept |
| { |
| if (__n <= _S_max_size(__alloc)) [[likely]] |
| { |
| if constexpr (is_same_v<allocator_type, allocator<value_type>>) |
| { |
| __n *= sizeof(value_type); |
| void* const __p = _GLIBCXX_OPERATOR_NEW (__n, nothrow_t{}); |
| if (__p == nullptr) [[unlikely]] |
| return nullptr; |
| _M_frames = static_cast<pointer>(__p); |
| } |
| else |
| { |
| __try |
| { |
| _M_frames = __alloc.allocate(__n); |
| } |
| __catch (const std::bad_alloc&) |
| { |
| return nullptr; |
| } |
| } |
| _M_capacity = __n; |
| return _M_frames; |
| } |
| return nullptr; |
| } |
| |
| void |
| _M_deallocate(allocator_type& __alloc) noexcept |
| { |
| if (_M_capacity) |
| { |
| if constexpr (is_same_v<allocator_type, allocator<value_type>>) |
| _GLIBCXX_OPERATOR_DELETE (static_cast<void*>(_M_frames), |
| _M_capacity * sizeof(value_type)); |
| else |
| __alloc.deallocate(_M_frames, _M_capacity); |
| _M_frames = nullptr; |
| _M_capacity = 0; |
| } |
| } |
| |
| #undef _GLIBCXX_OPERATOR_DELETE |
| #undef _GLIBCXX_OPERATOR_NEW |
| |
| // Precondition: __n <= _M_size |
| void |
| _M_resize(size_type __n, allocator_type& __alloc) noexcept |
| { |
| for (size_type __i = __n; __i < _M_size; ++__i) |
| _AllocTraits::destroy(__alloc, &_M_frames[__i]); |
| _M_size = __n; |
| } |
| |
| bool |
| _M_push_back(allocator_type& __alloc, |
| const stacktrace_entry& __f) noexcept |
| { |
| if (_M_size == _M_capacity) [[unlikely]] |
| { |
| _Impl __tmp = _M_xclone(_M_capacity ? _M_capacity : 8, __alloc); |
| if (!__tmp._M_capacity) [[unlikely]] |
| return false; |
| _M_resize(0, __alloc); |
| _M_deallocate(__alloc); |
| *this = __tmp; |
| } |
| stacktrace_entry* __addr = std::to_address(_M_frames + _M_size++); |
| _AllocTraits::construct(__alloc, __addr, __f); |
| return true; |
| } |
| |
| // Precondition: _M_size != 0 |
| _Impl |
| _M_clone(allocator_type& __alloc) const noexcept |
| { |
| return _M_xclone(_M_size, __alloc); |
| } |
| |
| // Precondition: _M_size != 0 || __extra != 0 |
| _Impl |
| _M_xclone(size_type __extra, allocator_type& __alloc) const noexcept |
| { |
| _Impl __i; |
| if (__i._M_allocate(__alloc, _M_size + __extra)) [[likely]] |
| __i._M_assign(*this, __alloc); |
| return __i; |
| } |
| |
| // Precondition: _M_capacity >= __other._M_size |
| void |
| _M_assign(const _Impl& __other, allocator_type& __alloc) noexcept |
| { |
| std::__uninitialized_copy_a(__other._M_frames, |
| __other._M_frames + __other._M_size, |
| _M_frames, __alloc); |
| _M_size = __other._M_size; |
| } |
| }; |
| |
| [[no_unique_address]] allocator_type _M_alloc{}; |
| |
| _Impl _M_impl{}; |
| }; |
| |
| // basic_stacktrace typedef names |
| using stacktrace = basic_stacktrace<allocator<stacktrace_entry>>; |
| |
| // [stacktrace.basic.nonmem], non-member functions |
| template<typename _Allocator> |
| inline void |
| swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b) |
| noexcept(noexcept(__a.swap(__b))) |
| { __a.swap(__b); } |
| |
| template<typename _CharT, typename _Traits> |
| inline basic_ostream<_CharT, _Traits>& |
| operator<<(basic_ostream<_CharT, _Traits>& __os, |
| const stacktrace_entry& __f) |
| { |
| string __desc, __file; |
| int __line; |
| if (__f._M_get_info(&__desc, &__file, &__line)) |
| { |
| __os.width(4); |
| __os << __desc << " at " << __file << ':' << __line; |
| } |
| return __os; |
| } |
| |
| template<typename _CharT, typename _Traits, typename _Allocator> |
| inline basic_ostream<_CharT, _Traits>& |
| operator<<(basic_ostream<_CharT, _Traits>& __os, |
| const basic_stacktrace<_Allocator>& __st) |
| { |
| for (stacktrace::size_type __i = 0; __i < __st.size(); ++__i) |
| { |
| __os.width(4); |
| __os << __i << "# " << __st[__i] << '\n'; |
| } |
| return __os; |
| } |
| |
| inline string |
| to_string(const stacktrace_entry& __f) |
| { |
| std::ostringstream __os; |
| __os << __f; |
| return std::move(__os).str(); |
| } |
| |
| template<typename _Allocator> |
| string |
| to_string(const basic_stacktrace<_Allocator>& __st) |
| { |
| std::ostringstream __os; |
| __os << __st; |
| return std::move(__os).str(); |
| } |
| |
| namespace pmr |
| { |
| using stacktrace |
| = basic_stacktrace<polymorphic_allocator<stacktrace_entry>>; |
| } |
| |
| // [stacktrace.basic.hash], hash support |
| |
| template<> |
| struct hash<stacktrace_entry> |
| { |
| size_t |
| operator()(const stacktrace_entry& __f) const noexcept |
| { |
| using __h = hash<stacktrace_entry::native_handle_type>; |
| return __h()(__f.native_handle()); |
| } |
| }; |
| |
| template<typename _Allocator> |
| struct hash<basic_stacktrace<_Allocator>> |
| { |
| size_t |
| operator()(const basic_stacktrace<_Allocator>& __st) const noexcept |
| { |
| hash<stacktrace_entry::native_handle_type> __h; |
| size_t __val = _Hash_impl::hash(__st.size()); |
| for (const auto& __f : __st) |
| __val = _Hash_impl::__hash_combine(__h(__f), __val); |
| return __val; |
| } |
| }; |
| |
| _GLIBCXX_END_NAMESPACE_VERSION |
| } // namespace std |
| #endif // C++23 |
| |
| #endif /* _GLIBCXX_STACKTRACE */ |