STL/stl/inc/stacktrace

422 строки
16 KiB
C++

// stacktrace standard header
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef _STACKTRACE_
#define _STACKTRACE_
#include <yvals_core.h>
#if _STL_COMPILER_PREPROCESSOR
#if !_HAS_CXX23
_EMIT_STL_WARNING(STL4038, "The contents of <stacktrace> are available only with C++23 or later.");
#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
#ifdef __cpp_lib_concepts
#include <format>
#endif // __cpp_lib_concepts
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new
// The separately compiled part of the <stacktrace> implementation calls a function pointer of _Stacktrace_string_fill
// type to allocate a buffer for string output. The called function does the buffer allocation and calls a function
// pointer of _Stacktrace_string_fill_callback type to fill the buffer. This is needed to type-erase <string> or
// <ostream>, and makes it possible to keep the separately compiled implementation in the import library.
//
// Additionally, _Stacktrace_string_fill can be called with a null _Callback argument to determine the already reserved
// string buffer for cases when the size is not known before an attempt to fill it -- this potentially avoids both an
// extra fill attempt and an extra allocation if the reserved size is enough.
extern "C" {
using _Stacktrace_string_fill_callback = size_t(__stdcall*)(char* _Data, size_t _Size, void* _Context) _NOEXCEPT_FNPTR;
using _Stacktrace_string_fill = size_t(__stdcall*)(
size_t _Size, void* _String, void* _Context, _Stacktrace_string_fill_callback _Callback);
_NODISCARD unsigned short __stdcall __std_stacktrace_capture(unsigned long _Frames_to_skip,
unsigned long _Frames_to_capture, void** _Back_trace, unsigned long* _Back_trace_hash) noexcept;
// Some of these functions may throw
// (they would propagate bad_alloc potentially thrown from string::resize_and_overwrite)
void __stdcall __std_stacktrace_address_to_string(
const void* _Address, void* _Str, _Stacktrace_string_fill _Fill) noexcept(false);
void __stdcall __std_stacktrace_description( //
const void* _Address, void* _Str, _Stacktrace_string_fill _Fill) noexcept(false);
void __stdcall __std_stacktrace_source_file( //
const void* _Address, void* _Str, _Stacktrace_string_fill _Fill) noexcept(false);
_NODISCARD unsigned int __stdcall __std_stacktrace_source_line(const void* _Address) noexcept;
void __stdcall __std_stacktrace_to_string(
const void* const* _Addresses, size_t _Size, void* _Str, _Stacktrace_string_fill _Fill) noexcept(false);
} // extern "C"
_STD_BEGIN
inline size_t __stdcall _Stacktrace_string_fill_impl(
const size_t _Size, void* const _String, void* const _Context, const _Stacktrace_string_fill_callback _Callback) {
if (_Callback) {
static_cast<string*>(_String)->resize_and_overwrite(_Size,
[_Callback, _Context](char* _Data, size_t _Size) noexcept { return _Callback(_Data, _Size, _Context); });
return static_cast<string*>(_String)->size();
} else {
return static_cast<string*>(_String)->capacity();
}
}
_EXPORT_STD class stacktrace_entry {
public:
using native_handle_type = void*;
constexpr stacktrace_entry() noexcept = default;
constexpr stacktrace_entry(const stacktrace_entry&) noexcept = default;
constexpr stacktrace_entry& operator=(const stacktrace_entry&) noexcept = default;
~stacktrace_entry() = default;
_NODISCARD constexpr native_handle_type native_handle() const noexcept {
return _Address;
}
_NODISCARD constexpr explicit operator bool() const noexcept {
return _Address != nullptr;
}
_NODISCARD string description() const {
string _Result;
__std_stacktrace_description(_Address, &_Result, _Stacktrace_string_fill_impl);
return _Result;
}
_NODISCARD string source_file() const {
string _Result;
__std_stacktrace_source_file(_Address, &_Result, _Stacktrace_string_fill_impl);
return _Result;
}
_NODISCARD uint_least32_t source_line() const noexcept /* strengthened */ {
return __std_stacktrace_source_line(_Address);
}
_NODISCARD_FRIEND constexpr bool operator==(const stacktrace_entry&, const stacktrace_entry&) noexcept = default;
_NODISCARD_FRIEND constexpr strong_ordering operator<=>(
const stacktrace_entry&, const stacktrace_entry&) noexcept = default;
private:
void* _Address = nullptr;
};
_EXPORT_STD template <class _Alloc>
class basic_stacktrace {
private:
using _Frames_t = vector<stacktrace_entry, _Alloc>;
public:
using value_type = stacktrace_entry;
using const_reference = const value_type&;
using reference = value_type&;
using const_iterator = _Frames_t::const_iterator;
using iterator = const_iterator;
using reverse_iterator = _STD reverse_iterator<iterator>;
using const_reverse_iterator = _STD reverse_iterator<const_iterator>;
using difference_type = _Frames_t::difference_type;
using size_type = _Frames_t::size_type;
using allocator_type = _Alloc;
// __declspec(noinline) to make the same behavior for debug and release.
// We force the current function to be always noinline and add its frame to skipped.
_NODISCARD __declspec(noinline) static basic_stacktrace
current(const allocator_type& _Al = allocator_type()) noexcept {
_TRY_BEGIN
basic_stacktrace _Result{_Internal_t{}, _Max_frames, _Al};
const unsigned short _Actual_size = __std_stacktrace_capture(
1, static_cast<unsigned long>(_Max_frames), _Result._To_voidptr_array(), &_Result._Hash);
_Result._Frames.resize(_Actual_size);
return _Result;
_CATCH_ALL
return basic_stacktrace{_Al};
_CATCH_END
}
_NODISCARD __declspec(noinline) static basic_stacktrace
current(const size_type _Skip, const allocator_type& _Al = allocator_type()) noexcept {
_TRY_BEGIN
basic_stacktrace _Result{_Internal_t{}, _Max_frames, _Al};
const unsigned short _Actual_size = __std_stacktrace_capture(
_Adjust_skip(_Skip), static_cast<unsigned long>(_Max_frames), _Result._To_voidptr_array(), &_Result._Hash);
_Result._Frames.resize(_Actual_size);
return _Result;
_CATCH_ALL
return basic_stacktrace{_Al};
_CATCH_END
}
_NODISCARD __declspec(noinline) static basic_stacktrace
current(const size_type _Skip, size_type _Max_depth, const allocator_type& _Al = allocator_type()) noexcept {
_TRY_BEGIN
if (_Max_depth > _Max_frames) {
_Max_depth = _Max_frames;
}
basic_stacktrace _Result{_Internal_t{}, _Max_depth, _Al};
const unsigned short _Actual_size = __std_stacktrace_capture(
_Adjust_skip(_Skip), static_cast<unsigned long>(_Max_depth), _Result._To_voidptr_array(), &_Result._Hash);
_Result._Frames.resize(_Actual_size);
return _Result;
_CATCH_ALL
return basic_stacktrace{_Al};
_CATCH_END
}
basic_stacktrace() noexcept(is_nothrow_default_constructible_v<allocator_type>) = default;
explicit basic_stacktrace(const allocator_type& _Al) noexcept : _Frames(_Al) {}
basic_stacktrace(const basic_stacktrace&) = default;
basic_stacktrace(basic_stacktrace&&) noexcept = default;
basic_stacktrace(const basic_stacktrace& _Other, const allocator_type& _Al)
: _Frames(_Other._Frames, _Al), _Hash(_Other._Hash) {}
basic_stacktrace(basic_stacktrace&& _Other, const allocator_type& _Al)
: _Frames(_STD move(_Other._Frames), _Al), _Hash(_Other._Hash) {}
basic_stacktrace& operator=(const basic_stacktrace&) = default;
basic_stacktrace& operator=(basic_stacktrace&&) noexcept(_Noex_move) = default;
~basic_stacktrace() = default;
_NODISCARD allocator_type get_allocator() const noexcept {
return _Frames.get_allocator();
}
_NODISCARD const_iterator begin() const noexcept {
return _Frames.cbegin();
}
_NODISCARD const_iterator end() const noexcept {
return _Frames.cend();
}
_NODISCARD const_reverse_iterator rbegin() const noexcept {
return _Frames.crbegin();
}
_NODISCARD const_reverse_iterator rend() const noexcept {
return _Frames.crend();
}
_NODISCARD const_iterator cbegin() const noexcept {
return _Frames.cbegin();
}
_NODISCARD const_iterator cend() const noexcept {
return _Frames.cend();
}
_NODISCARD const_reverse_iterator crbegin() const noexcept {
return _Frames.crbegin();
}
_NODISCARD const_reverse_iterator crend() const noexcept {
return _Frames.crend();
}
_NODISCARD_EMPTY_STACKTRACE_MEMBER bool empty() const noexcept {
return _Frames.empty();
}
_NODISCARD size_type size() const noexcept {
return _Frames.size();
}
_NODISCARD size_type max_size() const noexcept {
return _Frames.max_size();
}
_NODISCARD const_reference operator[](const size_type _Sx) const noexcept /* strengthened */ {
return _Frames[_Sx];
}
_NODISCARD const_reference at(const size_type _Sx) const {
return _Frames.at(_Sx);
}
template <class _Al2>
_NODISCARD_FRIEND bool operator==(const basic_stacktrace& _Lhs, const basic_stacktrace<_Al2>& _Rhs) noexcept {
return _Lhs._Hash == _Rhs._Hash && _STD equal(_Lhs.begin(), _Lhs.end(), _Rhs.begin(), _Rhs.end());
}
template <class _Al2>
_NODISCARD_FRIEND strong_ordering operator<=>(
const basic_stacktrace& _Lhs, const basic_stacktrace<_Al2>& _Rhs) noexcept {
const auto _Result = _Lhs._Frames.size() <=> _Rhs._Frames.size();
if (_Result != strong_ordering::equal) {
return _Result;
}
#ifdef __cpp_lib_concepts
return _STD lexicographical_compare_three_way(_Lhs.begin(), _Lhs.end(), _Rhs.begin(), _Rhs.end());
#else // ^^^ defined(__cpp_lib_concepts) / !defined(__cpp_lib_concepts) vvv
for (size_t _Ix = 0, _Mx = _Lhs._Frames.size(); _Ix != _Mx; ++_Ix) {
if (_Lhs._Frames[_Ix] != _Rhs._Frames[_Ix]) {
return _Lhs._Frames[_Ix] <=> _Rhs._Frames[_Ix];
}
}
return strong_ordering::equal;
#endif // ^^^ !defined(__cpp_lib_concepts) ^^^
}
void swap(basic_stacktrace& _Other) noexcept(allocator_traits<_Alloc>::propagate_on_container_swap::value
|| allocator_traits<_Alloc>::is_always_equal::value) {
_Frames.swap(_Other._Frames);
_STD swap(_Hash, _Other._Hash);
}
_NODISCARD unsigned long _Get_hash() const noexcept {
return _Hash;
}
_STL_INTERNAL_STATIC_ASSERT(sizeof(void*) == sizeof(stacktrace_entry));
_STL_INTERNAL_STATIC_ASSERT(alignof(void*) == alignof(stacktrace_entry));
_NODISCARD const void* const* _To_voidptr_array() const noexcept {
return reinterpret_cast<const void* const*>(_Frames.data());
}
_NODISCARD void** _To_voidptr_array() noexcept {
return reinterpret_cast<void**>(_Frames.data());
}
private:
static constexpr size_t _Max_frames = 0xFFFF;
static constexpr bool _Noex_move = allocator_traits<_Alloc>::propagate_on_container_move_assignment::value
|| allocator_traits<_Alloc>::is_always_equal::value;
struct _Internal_t {
explicit _Internal_t() noexcept = default;
};
basic_stacktrace(_Internal_t, size_type _Max_depth, const allocator_type& _Al) : _Frames(_Max_depth, _Al) {}
_NODISCARD static unsigned long _Adjust_skip(size_t _Skip) noexcept {
return _Skip < ULONG_MAX - 1 ? static_cast<unsigned long>(_Skip + 1) : ULONG_MAX;
}
_Frames_t _Frames;
unsigned long _Hash = 0;
};
_EXPORT_STD using stacktrace = basic_stacktrace<allocator<stacktrace_entry>>;
_EXPORT_STD template <class _Alloc>
void swap(basic_stacktrace<_Alloc>& _Ax, basic_stacktrace<_Alloc>& _Bx) noexcept(noexcept(_Ax.swap(_Bx))) {
_Ax.swap(_Bx);
}
_EXPORT_STD _NODISCARD inline string to_string(const stacktrace_entry& _Fx) {
string _Result;
__std_stacktrace_address_to_string(_Fx.native_handle(), &_Result, _Stacktrace_string_fill_impl);
return _Result;
}
_EXPORT_STD template <class _Alloc>
_NODISCARD string to_string(const basic_stacktrace<_Alloc>& _St) {
string _Result;
__std_stacktrace_to_string(_St._To_voidptr_array(), _St.size(), &_Result, _Stacktrace_string_fill_impl);
return _Result;
}
_EXPORT_STD template <int = 0>
ostream& operator<<(ostream& _Os, const stacktrace_entry& _Fx) {
return _Os << _STD to_string(_Fx);
}
_EXPORT_STD template <class _Alloc>
ostream& operator<<(ostream& _Os, const basic_stacktrace<_Alloc>& _St) {
return _Os << _STD to_string(_St);
}
#ifdef __cpp_lib_concepts
template <>
struct formatter<stacktrace_entry> {
constexpr format_parse_context::iterator parse(format_parse_context& _Parse_ctx) {
return _Impl._Parse(_Parse_ctx);
}
template <class _FormatContext>
_FormatContext::iterator format(const stacktrace_entry& _Val, _FormatContext& _Format_ctx) const {
const auto _Str = _STD to_string(_Val);
return _Impl._Format(_Format_ctx, static_cast<int>(_Str.size()), _Fmt_align::_Left,
[&](_FormatContext::iterator _Out) { return _RANGES copy(_Str, _STD move(_Out)).out; });
}
private:
_Fill_align_and_width_formatter<char> _Impl;
};
template <class _Alloc>
struct formatter<basic_stacktrace<_Alloc>> {
constexpr format_parse_context::iterator parse(format_parse_context& _Parse_ctx) {
const auto _First = _Parse_ctx.begin();
if (_First != _Parse_ctx.end() && *_First != '}') {
_Throw_format_error("For formatter<basic_stacktrace<Allocator>>, format-spec must be empty.");
}
return _First;
}
template <class _FormatContext>
_FormatContext::iterator format(const basic_stacktrace<_Alloc>& _Val, _FormatContext& _Format_ctx) const {
return _RANGES copy(_STD to_string(_Val), _Format_ctx.out()).out;
}
};
#endif // __cpp_lib_concepts
namespace pmr {
_EXPORT_STD using stacktrace = basic_stacktrace<polymorphic_allocator<stacktrace_entry>>;
}
template <>
struct hash<stacktrace_entry> {
// This is a C++23 feature, so argument_type and result_type are omitted.
_NODISCARD size_t operator()(const stacktrace_entry& _Val) const noexcept {
return _Hash_representation(_Val.native_handle());
}
};
template <class _Alloc>
struct hash<basic_stacktrace<_Alloc>> {
// This is a C++23 feature, so argument_type and result_type are omitted.
_NODISCARD size_t operator()(const basic_stacktrace<_Alloc>& _Val) const noexcept {
return _Val._Get_hash();
}
};
_STD_END
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // ^^^ _HAS_CXX23 ^^^
#endif // _STL_COMPILER_PREPROCESSOR
#endif // _STACKTRACE_