diff --git a/stl/CMakeLists.txt b/stl/CMakeLists.txt index ddc59d3df77..3a18357bc32 100644 --- a/stl/CMakeLists.txt +++ b/stl/CMakeLists.txt @@ -190,6 +190,7 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/inc/spanstream ${CMAKE_CURRENT_LIST_DIR}/inc/sstream ${CMAKE_CURRENT_LIST_DIR}/inc/stack + ${CMAKE_CURRENT_LIST_DIR}/inc/stacktrace ${CMAKE_CURRENT_LIST_DIR}/inc/stdatomic.h ${CMAKE_CURRENT_LIST_DIR}/inc/stdexcept ${CMAKE_CURRENT_LIST_DIR}/inc/stop_token @@ -267,6 +268,7 @@ set(IMPLIB_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/locale0_implib.cpp ${CMAKE_CURRENT_LIST_DIR}/src/nothrow.cpp ${CMAKE_CURRENT_LIST_DIR}/src/sharedmutex.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/stacktrace.cpp ${CMAKE_CURRENT_LIST_DIR}/src/syserror_import_lib.cpp ${CMAKE_CURRENT_LIST_DIR}/src/vector_algorithms.cpp ${CMAKE_CURRENT_LIST_DIR}/src/xonce2.cpp diff --git a/stl/inc/__msvc_all_public_headers.hpp b/stl/inc/__msvc_all_public_headers.hpp index 21b84a61315..ac80a03038b 100644 --- a/stl/inc/__msvc_all_public_headers.hpp +++ b/stl/inc/__msvc_all_public_headers.hpp @@ -122,6 +122,7 @@ #include #include #include +#include #include #include #include diff --git a/stl/inc/header-units.json b/stl/inc/header-units.json index 72cd63edffb..cd32ed7c47d 100644 --- a/stl/inc/header-units.json +++ b/stl/inc/header-units.json @@ -100,6 +100,7 @@ "spanstream", "sstream", "stack", + "stacktrace", "stdatomic.h", "stdexcept", "stop_token", diff --git a/stl/inc/stacktrace b/stl/inc/stacktrace new file mode 100644 index 00000000000..eeb84e0b659 --- /dev/null +++ b/stl/inc/stacktrace @@ -0,0 +1,382 @@ +// stacktrace standard header + +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once +#ifndef _STACKTRACE_ +#define _STACKTRACE_ +#include +#if _STL_COMPILER_PREPROCESSOR + +#if !_HAS_CXX23 +#pragma message("The contents of are available only with C++23 or later.") +#else // ^^^ !_HAS_CXX23 / _HAS_CXX23 vvv + +#include +#include +#include +#include + +#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 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 or +// , 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. + +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); + +_EXTERN_C +_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); +_END_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)->resize_and_overwrite(_Size, + [_Callback, _Context](char* _Data, size_t _Size) noexcept { return _Callback(_Data, _Size, _Context); }); + return static_cast(_String)->size(); + } else { + return static_cast(_String)->capacity(); + } +} + +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; +}; + +template +class basic_stacktrace { +private: + using _Frames_t = vector; + +public: + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using const_iterator = typename _Frames_t::const_iterator; + using iterator = const_iterator; + using reverse_iterator = _STD reverse_iterator; + using const_reverse_iterator = _STD reverse_iterator; + using difference_type = typename _Frames_t::difference_type; + using size_type = typename _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(_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(_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(_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) = 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 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 + _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 + _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 // ^^^ __cpp_lib_concepts ^^^ / vvv !__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 // ^^^ !__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(_Frames.data()); + } + + _NODISCARD void** _To_voidptr_array() noexcept { + return reinterpret_cast(_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(_Skip + 1) : ULONG_MAX; + } + + _Frames_t _Frames; + unsigned long _Hash = 0; +}; + +using stacktrace = basic_stacktrace>; + +template +void swap(basic_stacktrace<_Alloc>& _Ax, basic_stacktrace<_Alloc>& _Bx) noexcept(noexcept(_Ax.swap(_Bx))) { + _Ax.swap(_Bx); +} + +_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; +} + +template +_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; +} + +template +basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const stacktrace_entry& _Fx) { + return _Os << _STD to_string(_Fx); +} + +template +basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& _Os, const basic_stacktrace<_Alloc>& _St) { + return _Os << _STD to_string(_St); +} + +namespace pmr { + using stacktrace = basic_stacktrace>; +} + +template <> +struct hash { + // 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 +struct hash> { + // 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_ diff --git a/stl/inc/yvals_core.h b/stl/inc/yvals_core.h index 40d7a928a07..09a35743f76 100644 --- a/stl/inc/yvals_core.h +++ b/stl/inc/yvals_core.h @@ -286,6 +286,7 @@ // P0448R4 // P0627R6 unreachable() // P0798R8 Monadic Operations For optional +// P0881R7 // P0943R6 Supporting C Atomics In C++ // P1048R1 is_scoped_enum // P1072R10 basic_string::resize_and_overwrite @@ -1462,6 +1463,7 @@ #endif // __cpp_lib_concepts #define __cpp_lib_spanstream 202106L +#define __cpp_lib_stacktrace 202011L #define __cpp_lib_stdatomic_h 202011L #define __cpp_lib_string_contains 202011L #define __cpp_lib_string_resize_and_overwrite 202110L diff --git a/stl/msbuild/stl_base/stl.files.settings.targets b/stl/msbuild/stl_base/stl.files.settings.targets index 2efe7ed57aa..c62d50b6775 100644 --- a/stl/msbuild/stl_base/stl.files.settings.targets +++ b/stl/msbuild/stl_base/stl.files.settings.targets @@ -166,6 +166,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception $(CrtRoot)\github\stl\src\locale0_implib.cpp; $(CrtRoot)\github\stl\src\nothrow.cpp; $(CrtRoot)\github\stl\src\sharedmutex.cpp; + $(CrtRoot)\github\stl\src\stacktrace.cpp; $(CrtRoot)\github\stl\src\syserror_import_lib.cpp; $(CrtRoot)\github\stl\src\vector_algorithms.cpp; $(CrtRoot)\github\stl\src\xonce2.cpp; diff --git a/stl/src/stacktrace.cpp b/stl/src/stacktrace.cpp new file mode 100644 index 00000000000..84da66adc9a --- /dev/null +++ b/stl/src/stacktrace.cpp @@ -0,0 +1,341 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// This must be as small as possible, because its contents are +// injected into the msvcprt.lib and msvcprtd.lib import libraries. +// Do not include or define anything else here. +// In particular, basic_string must not be included here. + +#include + +#include +#include + +// clang-format off +#include // should be before any header that includes +#include +#include +// clang-format on + +// The below function pointer types must be in sync with + +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); + +namespace { + template + size_t string_fill(const _Stacktrace_string_fill callback, const size_t size, void* const str, F f) { + return callback(size, str, &f, + [](char* s, size_t sz, void* context) noexcept -> size_t { return (*static_cast(context))(s, sz); }); + } + + // TRANSITION, GH-2285. Use SRWLOCK instead of std::mutex to avoid nontrivial constructor and nontrivial destructor + void lock_and_uninitialize() noexcept; + + class [[nodiscard]] dbg_eng_data { + public: + dbg_eng_data() noexcept { + AcquireSRWLockExclusive(&srw); + } + + ~dbg_eng_data() { + ReleaseSRWLockExclusive(&srw); + } + + dbg_eng_data(const dbg_eng_data&) = delete; + dbg_eng_data& operator=(const dbg_eng_data&) = delete; + + void uninitialize() noexcept { + // "Phoenix singleton" - destroy and set to null, so that it can be initialized later again + + if (debug_client != nullptr) { + if (attached) { + (void) debug_client->DetachProcesses(); + attached = false; + } + + debug_client->Release(); + debug_client = nullptr; + } + + if (debug_control != nullptr) { + debug_control->Release(); + debug_control = nullptr; + } + + if (debug_symbols != nullptr) { + debug_symbols->Release(); + debug_symbols = nullptr; + } + + if (dbgeng != nullptr) { + (void) FreeLibrary(dbgeng); + dbgeng = nullptr; + } + + initialize_attempted = false; + } + + [[nodiscard]] bool try_initialize() noexcept { + if (!initialize_attempted) { + initialize_attempted = true; + + if (std::atexit(lock_and_uninitialize) != 0) { + return false; + } + + dbgeng = LoadLibraryExW(L"dbgeng.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (dbgeng != nullptr) { + const auto debug_create = + reinterpret_cast(GetProcAddress(dbgeng, "DebugCreate")); + + // Deliberately not calling CoInitialize[Ex]. DbgEng.h API works fine without it. + // COM initialization may have undesired interference with user's code. + if (debug_create != nullptr + && SUCCEEDED(debug_create(IID_IDebugClient, reinterpret_cast(&debug_client))) + && SUCCEEDED( + debug_client->QueryInterface(IID_IDebugSymbols, reinterpret_cast(&debug_symbols))) + && SUCCEEDED(debug_client->QueryInterface( + IID_IDebugControl, reinterpret_cast(&debug_control)))) { + attached = SUCCEEDED(debug_client->AttachProcess( + 0, GetCurrentProcessId(), DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND)); + if (attached) { + (void) debug_control->WaitForEvent(0, INFINITE); + } + + constexpr ULONG add_options = 0x1 /* SYMOPT_CASE_INSENSITIVE */ + | 0x2 /* SYMOPT_UNDNAME */ + | 0x4 /* SYMOPT_DEFERRED_LOADS */ + | 0x10 /* SYMOPT_LOAD_LINES */ + | 0x20 /* SYMOPT_OMAP_FIND_NEAREST */ + | 0x100 /* SYMOPT_FAIL_CRITICAL_ERRORS */ + | 0x10000 /* SYMOPT_AUTO_PUBLICS */ + | 0x80000 /* SYMOPT_NO_PROMPTS */; + + constexpr ULONG remove_options = 0x8 /* SYMOPT_NO_CPP */ + | 0x40 /* SYMOPT_LOAD_ANYTHING */ + | 0x100 /* SYMOPT_NO_UNQUALIFIED_LOADS */ + | 0x400 /* SYMOPT_EXACT_SYMBOLS */ + | 0x1000 /* SYMOPT_IGNORE_NT_SYMPATH */ + | 0x4000 /* SYMOPT_PUBLICS_ONLY */ + | 0x8000 /* SYMOPT_NO_PUBLICS */ + | 0x20000 /* SYMOPT_NO_IMAGE_SEARCH */; + + (void) debug_symbols->AddSymbolOptions(add_options); + (void) debug_symbols->RemoveSymbolOptions(remove_options); + } + } + } + + return attached; + } + + size_t get_description( + const void* const address, void* const str, size_t off, const _Stacktrace_string_fill fill) { + // Initially pass the current capacity, will retry with bigger buffer if it fails. + size_t size = fill(0, str, nullptr, nullptr) - off; + HRESULT hr = E_UNEXPECTED; + ULONG64 displacement = 0; + + for (;;) { + ULONG new_size = 0; + + const size_t new_off = string_fill( + fill, off + size, str, [address, off, size, &new_size, &hr, &displacement](char* s, size_t) { + hr = debug_symbols->GetNameByOffset(reinterpret_cast(address), s + off, + static_cast(size + 1), &new_size, &displacement); + + return (hr == S_OK) ? off + new_size - 1 : off; + }); + + if (hr == S_OK) { + off = new_off; + break; + } else if (hr == S_FALSE) { + size = new_size - 1; // retry with bigger buffer + } else { + return off; + } + } + + if (displacement != 0) { + constexpr size_t max_disp_num = sizeof("+0x1122334455667788") - 1; // maximum possible offset + + off = string_fill(fill, off + max_disp_num, str, [displacement, off](char* s, size_t) { + const int ret = std::snprintf(s + off, max_disp_num, "+0x%llX", displacement); + _STL_VERIFY(ret > 0, "formatting error"); + return off + ret; + }); + } + + return off; + } + + size_t source_file(const void* const address, void* const str, size_t off, ULONG* const line, + const _Stacktrace_string_fill fill) { + // Initially pass the current capacity, will retry with bigger buffer if fails. + size_t size = fill(0, str, nullptr, nullptr) - off; + HRESULT hr = E_UNEXPECTED; + + for (;;) { + ULONG new_size = 0; + + const size_t new_off = + string_fill(fill, off + size, str, [address, off, size, line, &new_size, &hr](char* s, size_t) { + hr = debug_symbols->GetLineByOffset(reinterpret_cast(address), line, s + off, + static_cast(size + 1), &new_size, nullptr); + + return (hr == S_OK) ? off + new_size - 1 : off; + }); + + if (hr == S_OK) { + off = new_off; + break; + } else if (hr == S_FALSE) { + size = new_size - 1; // retry with bigger buffer + } else { + if (line) { + *line = 0; + } + + return off; + } + } + + return off; + } + + [[nodiscard]] unsigned int source_line(const void* const address) noexcept { + ULONG line = 0; + + if (FAILED(debug_symbols->GetLineByOffset( + reinterpret_cast(address), &line, nullptr, 0, nullptr, nullptr))) { + return 0; + } + + return line; + } + + size_t address_to_string( + const void* const address, void* const str, size_t off, const _Stacktrace_string_fill fill) { + ULONG line = 0; + + off = source_file(address, str, off, &line, fill); + + if (line != 0) { + constexpr size_t max_line_num = sizeof("(4294967295): ") - 1; // maximum possible line number + + off = string_fill(fill, off + max_line_num, str, [line, off](char* s, size_t) { + const int ret = std::snprintf(s + off, max_line_num, "(%u): ", line); + _STL_VERIFY(ret > 0, "formatting error"); + return off + ret; + }); + } + + return get_description(address, str, off, fill); + } + + private: + inline static SRWLOCK srw = SRWLOCK_INIT; + inline static IDebugClient* debug_client = nullptr; + inline static IDebugSymbols* debug_symbols = nullptr; + inline static IDebugControl* debug_control = nullptr; + inline static bool attached = false; + inline static bool initialize_attempted = false; + inline static HMODULE dbgeng = nullptr; + }; + + void lock_and_uninitialize() noexcept { + dbg_eng_data locked_data; + + locked_data.uninitialize(); + } +} // namespace + +_EXTERN_C +#pragma optimize("", off) // inhibit tail call optimization to have consistent _Frames_to_skip adjustment here +[[nodiscard]] unsigned short __stdcall __std_stacktrace_capture(unsigned long _Frames_to_skip, + const unsigned long _Frames_to_capture, void** const _Back_trace, unsigned long* const _Back_trace_hash) noexcept { + return CaptureStackBackTrace(_Frames_to_skip + 1, _Frames_to_capture, _Back_trace, _Back_trace_hash); +} +#pragma optimize("", on) // end inhibit tail call optimization + +// Some of these functions may throw (They would propagate bad_alloc potentially thrown from +// string::resize_and_overwrite) + +void __stdcall __std_stacktrace_description( + const void* const _Address, void* const _Str, const _Stacktrace_string_fill _Fill) noexcept(false) { + dbg_eng_data locked_data; + + if (!locked_data.try_initialize()) { + return; + } + + locked_data.get_description(_Address, _Str, 0, _Fill); +} + +void __stdcall __std_stacktrace_source_file( + const void* const _Address, void* const _Str, const _Stacktrace_string_fill _Fill) noexcept(false) { + dbg_eng_data locked_data; + + if (!locked_data.try_initialize()) { + return; + } + + locked_data.source_file(_Address, _Str, 0, nullptr, _Fill); +} + +[[nodiscard]] unsigned int __stdcall __std_stacktrace_source_line(const void* const _Address) noexcept { + dbg_eng_data locked_data; + + if (!locked_data.try_initialize()) { + return 0; + } + + return locked_data.source_line(_Address); +} + +void __stdcall __std_stacktrace_address_to_string( + const void* const _Address, void* const _Str, const _Stacktrace_string_fill _Fill) noexcept(false) { + dbg_eng_data locked_data; + + if (!locked_data.try_initialize()) { + return; + } + + locked_data.address_to_string(_Address, _Str, 0, _Fill); +} + +void __stdcall __std_stacktrace_to_string(const void* const* const _Addresses, const size_t _Size, void* const _Str, + const _Stacktrace_string_fill _Fill) noexcept(false) { + dbg_eng_data locked_data; + + if (!locked_data.try_initialize()) { + return; + } + + size_t off = 0; + + for (size_t i = 0; i != _Size; ++i) { + if (off != 0) { + off = string_fill(_Fill, off + 1, _Str, [](char* s, size_t sz) { + s[sz - 1] = '\n'; + return sz; + }); + } + + constexpr size_t max_entry_num = sizeof("65536> ") - 1; // maximum possible entry number + + off = string_fill(_Fill, off + max_entry_num, _Str, [off, i](char* s, size_t) { + const int ret = std::snprintf(s + off, max_entry_num, "%u> ", static_cast(i)); + _STL_VERIFY(ret > 0, "formatting error"); + return off + ret; + }); + + off = locked_data.address_to_string(_Addresses[i], _Str, off, _Fill); + } +} +_END_EXTERN_C diff --git a/tests/std/test.lst b/tests/std/test.lst index 31330e84976..51129d5dd46 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -325,6 +325,7 @@ tests\P0784R7_library_machinery tests\P0784R7_library_support_for_more_constexpr_containers tests\P0798R8_monadic_operations_for_std_optional tests\P0811R3_midpoint_lerp +tests\P0881R7_stacktrace tests\P0896R4_common_iterator tests\P0896R4_common_iterator_death tests\P0896R4_counted_iterator diff --git a/tests/std/tests/GH_000545_include_compare/test.cpp b/tests/std/tests/GH_000545_include_compare/test.cpp index 82732c59472..9bba56a9318 100644 --- a/tests/std/tests/GH_000545_include_compare/test.cpp +++ b/tests/std/tests/GH_000545_include_compare/test.cpp @@ -17,6 +17,7 @@ void test_ranges(); void test_regex(); void test_set(); void test_stack(); +void test_stacktrace(); void test_string(); void test_string_view(); void test_system_error(); @@ -46,6 +47,7 @@ int main() { test_regex(); test_set(); test_stack(); + test_stacktrace(); test_string(); test_string_view(); test_system_error(); diff --git a/tests/std/tests/GH_000545_include_compare/test_stacktrace.cpp b/tests/std/tests/GH_000545_include_compare/test_stacktrace.cpp new file mode 100644 index 00000000000..3439aa43c43 --- /dev/null +++ b/tests/std/tests/GH_000545_include_compare/test_stacktrace.cpp @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#ifdef __cpp_lib_stacktrace + +#include + +// Testing LWG-3330 "Include from most library headers" by intentionally NOT including + +static_assert(std::is_eq(std::partial_ordering::equivalent)); + +#endif // __cpp_lib_stacktrace + +void test_stacktrace() {} diff --git a/tests/std/tests/P0881R7_stacktrace/env.lst b/tests/std/tests/P0881R7_stacktrace/env.lst new file mode 100644 index 00000000000..1349aaccb51 --- /dev/null +++ b/tests/std/tests/P0881R7_stacktrace/env.lst @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst +RUNALL_CROSSLIST +PM_CL="/Zi /DHAS_DEBUG_INFO" PM_LINK="/debug" +PM_CL="/DHAS_EXPORT" +PM_CL="" diff --git a/tests/std/tests/P0881R7_stacktrace/postexecute.pl b/tests/std/tests/P0881R7_stacktrace/postexecute.pl new file mode 100644 index 00000000000..94076adee7b --- /dev/null +++ b/tests/std/tests/P0881R7_stacktrace/postexecute.pl @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +use strict; +use warnings; + +sub PostExecuteHook() +{ + Run::Delete("P0881R7_stacktrace.exe"); + Run::Delete("P0881R7_stacktrace.exp"); + Run::Delete("P0881R7_stacktrace.lib"); + Run::Delete("P0881R7_stacktrace.pdb"); + Run::Delete("test.obj"); + Run::Delete("vc140.pdb"); +} +1 diff --git a/tests/std/tests/P0881R7_stacktrace/test.cpp b/tests/std/tests/P0881R7_stacktrace/test.cpp new file mode 100644 index 00000000000..74e67bf3300 --- /dev/null +++ b/tests/std/tests/P0881R7_stacktrace/test.cpp @@ -0,0 +1,281 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAS_EXPORT +#define MAYBE_EXPORT __declspec(dllexport) +#else // ^^^ HAS_EXPORT ^^^ / vvv !HAS_EXPORT vvv +#define MAYBE_EXPORT +#endif // ^^^ !HAS_EXPORT ^^^ + +using namespace std; + +[[maybe_unused]] const int base_line = __LINE__; + +// Note: the below assumes that tail call optimization is disabled, which is the case in /Od + +MAYBE_EXPORT stacktrace all_innermost() { + return stacktrace::current(); +} + +MAYBE_EXPORT stacktrace all_inner() { + return all_innermost(); +} + +MAYBE_EXPORT stacktrace all_outer() { + return all_inner(); +} + +MAYBE_EXPORT stacktrace all_outermost() { + return all_outer(); +} + +MAYBE_EXPORT stacktrace all_but_top_innermost() { + return stacktrace::current(1); +} + +MAYBE_EXPORT stacktrace all_but_top_inner() { + return all_but_top_innermost(); +} + +MAYBE_EXPORT stacktrace all_but_top_outer() { + return all_but_top_inner(); +} + +MAYBE_EXPORT stacktrace all_but_top_outermost() { + return all_but_top_outer(); +} + +MAYBE_EXPORT stacktrace three_excluding_top_innermost() { + return stacktrace::current(1, 3); +} + +MAYBE_EXPORT stacktrace three_excluding_top_inner() { + return three_excluding_top_innermost(); +} + +MAYBE_EXPORT stacktrace three_excluding_top_outer() { + return three_excluding_top_inner(); +} + +MAYBE_EXPORT stacktrace three_excluding_top_outermost() { + return three_excluding_top_outer(); +} + +string trim_past_plus(string str) { + if (size_t pos = str.rfind("+", string::npos); pos != string::npos) { + str.resize(pos); + } + return str; +} + +string to_string_using_low_level_members(const stacktrace& st) { + ostringstream oss; + int n = 0; + for (const auto& i : st) { + oss << n << "> "; + ++n; + auto l = i.source_line(); + if (l != 0) { + oss << i.source_file() << "(" << l << "): "; + } + oss << i.description() << "\n"; + } + return oss.str(); +} + +string to_string_using_stream_entry(const stacktrace& st) { + ostringstream oss; + int n = 0; + for (const auto& i : st) { + oss << n << "> "; + ++n; + oss << i << "\n"; + } + return oss.str(); +} + +string to_string_using_to_string_entry(const stacktrace& st) { + ostringstream oss; + int n = 0; + for (const auto& i : st) { + oss << n << "> "; + ++n; + oss << to_string(i) << "\n"; + } + return oss.str(); +} + +string to_string_using_stream(const stacktrace& st) { + stringstream oss; + oss << st << "\n"; + return oss.str(); +} + +string to_string_using_to_string(const stacktrace& st) { + return to_string(st) + "\n"; +} + +#if defined(HAS_DEBUG_INFO) || defined(HAS_EXPORT) +#define HAS_NAMES +#endif // ^^^ defined(HAS_DEBUG_INFO) || defined(HAS_EXPORT) ^^^ + +void test_impl() { + auto all = all_outermost(); + assert(all.size() >= 4); + +#ifdef HAS_DEBUG_INFO + assert(filesystem::path(all.at(0).source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(all.at(1).source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(all.at(2).source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(all.at(3).source_file()).filename() == "test.cpp"sv); + + assert(all.at(0).source_line() == base_line + 5); + assert(all.at(1).source_line() == base_line + 9); + assert(all.at(2).source_line() == base_line + 13); + assert(all.at(3).source_line() == base_line + 17); +#else // ^^^ HAS_DEBUG_INFO ^^^ / vvv !HAS_DEBUG_INFO vvv + assert(filesystem::path(all.at(0).source_file()).filename() == ""sv); + assert(filesystem::path(all.at(1).source_file()).filename() == ""sv); + assert(filesystem::path(all.at(2).source_file()).filename() == ""sv); + assert(filesystem::path(all.at(3).source_file()).filename() == ""sv); + + assert(all.at(0).source_line() == 0); + assert(all.at(1).source_line() == 0); + assert(all.at(2).source_line() == 0); + assert(all.at(3).source_line() == 0); +#endif // ^^^ !HAS_DEBUG_INFO ^^^ + +#ifdef HAS_NAMES + assert(trim_past_plus(all.at(0).description()) == "P0881R7_stacktrace!all_innermost"sv); + assert(trim_past_plus(all.at(1).description()) == "P0881R7_stacktrace!all_inner"sv); + assert(trim_past_plus(all.at(2).description()) == "P0881R7_stacktrace!all_outer"sv); + assert(trim_past_plus(all.at(3).description()) == "P0881R7_stacktrace!all_outermost"sv); +#else // ^^^ HAS_NAMES ^^^ / vvv !HAS_NAMES vvv + assert(trim_past_plus(all.at(0).description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(all.at(1).description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(all.at(2).description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(all.at(3).description()) == "P0881R7_stacktrace"sv); +#endif // ^^^ !HAS_NAMES ^^^ + + auto all_but_top = all_but_top_outermost(); + assert(all_but_top.size() >= 3); + +#ifdef HAS_DEBUG_INFO + assert(filesystem::path(all_but_top[0].source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(all_but_top[1].source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(all_but_top[2].source_file()).filename() == "test.cpp"sv); + + assert(all_but_top[0].source_line() == base_line + 25); + assert(all_but_top[1].source_line() == base_line + 29); + assert(all_but_top[2].source_line() == base_line + 33); +#else // ^^^ HAS_DEBUG_INFO ^^^ / vvv !HAS_DEBUG_INFO vvv + assert(filesystem::path(all_but_top[0].source_file()).filename() == ""sv); + assert(filesystem::path(all_but_top[1].source_file()).filename() == ""sv); + assert(filesystem::path(all_but_top[2].source_file()).filename() == ""sv); + + assert(all_but_top[0].source_line() == 0); + assert(all_but_top[1].source_line() == 0); + assert(all_but_top[2].source_line() == 0); +#endif // ^^^ !HAS_DEBUG_INFO ^^^ + +#ifdef HAS_NAMES + assert(trim_past_plus(all_but_top[0].description()) == "P0881R7_stacktrace!all_but_top_inner"sv); + assert(trim_past_plus(all_but_top[1].description()) == "P0881R7_stacktrace!all_but_top_outer"sv); + assert(trim_past_plus(all_but_top[2].description()) == "P0881R7_stacktrace!all_but_top_outermost"sv); +#else // ^^^ HAS_NAMES ^^^ / vvv !HAS_NAMES vvv + assert(trim_past_plus(all_but_top[0].description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(all_but_top[1].description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(all_but_top[2].description()) == "P0881R7_stacktrace"sv); +#endif // ^^^ !HAS_NAMES ^^^ + + auto three_excluding_top = three_excluding_top_outermost(); + assert(three_excluding_top.size() == 3); +#ifdef HAS_DEBUG_INFO + assert(filesystem::path(three_excluding_top[0].source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(three_excluding_top[1].source_file()).filename() == "test.cpp"sv); + assert(filesystem::path(three_excluding_top[2].source_file()).filename() == "test.cpp"sv); + + assert(three_excluding_top[0].source_line() == base_line + 41); + assert(three_excluding_top[1].source_line() == base_line + 45); + assert(three_excluding_top[2].source_line() == base_line + 49); +#else // ^^^ HAS_DEBUG_INFO ^^^ / vvv !HAS_DEBUG_INFO vvv + assert(filesystem::path(three_excluding_top[0].source_file()).filename() == ""sv); + assert(filesystem::path(three_excluding_top[1].source_file()).filename() == ""sv); + assert(filesystem::path(three_excluding_top[2].source_file()).filename() == ""sv); + + assert(three_excluding_top[0].source_line() == 0); + assert(three_excluding_top[1].source_line() == 0); + assert(three_excluding_top[2].source_line() == 0); +#endif // ^^^ !HAS_DEBUG_INFO ^^^ + +#ifdef HAS_NAMES + assert(trim_past_plus(three_excluding_top[0].description()) == "P0881R7_stacktrace!three_excluding_top_inner"sv); + assert(trim_past_plus(three_excluding_top[1].description()) == "P0881R7_stacktrace!three_excluding_top_outer"sv); + assert( + trim_past_plus(three_excluding_top[2].description()) == "P0881R7_stacktrace!three_excluding_top_outermost"sv); +#else // ^^^ HAS_NAMES ^^^ / vvv !HAS_NAMES vvv + assert(trim_past_plus(three_excluding_top[0].description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(three_excluding_top[1].description()) == "P0881R7_stacktrace"sv); + assert(trim_past_plus(three_excluding_top[2].description()) == "P0881R7_stacktrace"sv); +#endif // ^^^ !HAS_NAMES ^^^ + + try { + (void) all.at(all.size()); + assert(false); // should have thrown + } catch (const out_of_range&) { + } + + auto all_copy = all; + + assert(all == all_copy); + assert(all != all_but_top); + assert(all > all_but_top); + assert(three_excluding_top < all_but_top); + + assert((all <=> all_copy) == strong_ordering::equal); + assert((all <=> all_but_top) == strong_ordering::greater); + assert((three_excluding_top <=> all_but_top) == strong_ordering::less); + + assert(hash{}(all) == hash{}(all_copy)); + assert(hash{}(all[0]) == hash{}(all_copy[0])); + + assert(!all.empty()); + assert(distance(all.begin(), all.end()) == static_cast(all.size())); + assert(distance(all.rbegin(), all.rend()) == static_cast(all.size())); + assert(distance(all.cbegin(), all.cend()) == static_cast(all.size())); + assert(distance(all.crbegin(), all.crend()) == static_cast(all.size())); + + stacktrace empty_trace; + assert(empty_trace.size() == 0); + assert(empty_trace.begin() == empty_trace.end()); + assert(empty_trace.rbegin() == empty_trace.rend()); + assert(empty_trace.cbegin() == empty_trace.cend()); + assert(empty_trace.crbegin() == empty_trace.crend()); + + stacktrace_entry empty_entry; + assert(empty_entry.description() == ""sv); + assert(empty_entry.source_file() == ""sv); + assert(empty_entry.source_line() == 0); + + auto s = to_string_using_low_level_members(all); + assert(s == to_string_using_stream_entry(all)); + assert(s == to_string_using_stream(all)); + assert(s == to_string_using_to_string_entry(all)); + assert(s == to_string_using_to_string(all)); +} + +int main() { + jthread t{test_impl}; + test_impl(); +} diff --git a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc index ffff358f324..ad8b6c8ab9e 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc +++ b/tests/std/tests/P1502R1_standard_library_header_units/importable_cxx_library_headers.jsonc @@ -62,6 +62,7 @@ "spanstream", "sstream", "stack", + "stacktrace", "stdexcept", "stop_token", "streambuf", diff --git a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp index db34dc79c74..fad9faad162 100644 --- a/tests/std/tests/P1502R1_standard_library_header_units/test.cpp +++ b/tests/std/tests/P1502R1_standard_library_header_units/test.cpp @@ -71,6 +71,7 @@ import ; import ; import ; import ; +import ; import ; import ; import ; @@ -110,7 +111,8 @@ constexpr bool test_source_location() { return true; } -int main() { +__declspec(dllexport) // for test export main to have it named even without debug info + int main() { { puts("Testing ."); constexpr int arr[]{11, 0, 22, 0, 33, 0, 44, 0, 55}; @@ -767,6 +769,21 @@ int main() { assert(s.empty()); } + { + puts("Testing ."); + auto desc = stacktrace::current().at(0).description(); + + if (auto pos = desc.find("!"); pos != string::npos) { + desc = desc.substr(pos + 1); + } + + if (auto pos = desc.find("+"); pos != string::npos) { + desc.resize(pos); + } + + assert(desc == "main"); + } + { puts("Testing ."); bool caught_puppies = false; diff --git a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp index 83f3c86bf29..cdfe66e1eb5 100644 --- a/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp +++ b/tests/std/tests/VSO_0157762_feature_test_macros/test.compile.pass.cpp @@ -1644,6 +1644,20 @@ STATIC_ASSERT(__cpp_lib_ssize == 201902L); #endif #endif +#if _HAS_CXX23 +#ifndef __cpp_lib_stacktrace +#error __cpp_lib_stacktrace is not defined +#elif __cpp_lib_stacktrace != 202011L +#error __cpp_lib_stacktrace is not 202011L +#else +STATIC_ASSERT(__cpp_lib_stacktrace == 202011L); +#endif +#else +#ifdef __cpp_lib_stacktrace +#error __cpp_lib_stacktrace is defined +#endif +#endif + #if _HAS_CXX20 #ifndef __cpp_lib_starts_ends_with #error __cpp_lib_starts_ends_with is not defined diff --git a/tests/std/tests/include_each_header_alone_matrix.lst b/tests/std/tests/include_each_header_alone_matrix.lst index 9aa79d26b07..e9fdcfa9c7e 100644 --- a/tests/std/tests/include_each_header_alone_matrix.lst +++ b/tests/std/tests/include_each_header_alone_matrix.lst @@ -66,6 +66,7 @@ PM_CL="/DMEOW_HEADER=span" PM_CL="/DMEOW_HEADER=spanstream" PM_CL="/DMEOW_HEADER=sstream" PM_CL="/DMEOW_HEADER=stack" +PM_CL="/DMEOW_HEADER=stacktrace" PM_CL="/DMEOW_HEADER=stdatomic.h" PM_CL="/DMEOW_HEADER=stdexcept" PM_CL="/DMEOW_HEADER=stop_token"