diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt index dffdd7a3c70a6..f4e63c0f93e79 100644 --- a/libcxx/CMakeLists.txt +++ b/libcxx/CMakeLists.txt @@ -131,6 +131,11 @@ option(LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS the shared library they shipped should turn this on and see `include/__configuration/availability.h` for more details." OFF) +option(LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + "For C++23 : whether to allow invocation of `addr2line`, `llvm-addr2line`, or `atos` + at runtime (if it's available in `PATH`) to resolve call-chain addresses in the stacktrace + into source locations, if other methods are not available." ON) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(LIBCXX_DEFAULT_TEST_CONFIG "llvm-libc++-shared-gcc.cfg.in") elseif(MINGW) @@ -758,6 +763,7 @@ config_define(${LIBCXX_ENABLE_UNICODE} _LIBCPP_HAS_UNICODE) config_define(${LIBCXX_ENABLE_WIDE_CHARACTERS} _LIBCPP_HAS_WIDE_CHARACTERS) config_define(${LIBCXX_ENABLE_TIME_ZONE_DATABASE} _LIBCPP_HAS_TIME_ZONE_DATABASE) config_define(${LIBCXX_ENABLE_VENDOR_AVAILABILITY_ANNOTATIONS} _LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS) +config_define(${LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME} _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME) # TODO: Remove in LLVM 21. We're leaving an error to make this fail explicitly. if (LIBCXX_ENABLE_ASSERTIONS) diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst index 9b57b7c8eeb52..9ae573c7aa4a8 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -392,7 +392,7 @@ Status ---------------------------------------------------------- ----------------- ``__cpp_lib_spanstream`` *unimplemented* ---------------------------------------------------------- ----------------- - ``__cpp_lib_stacktrace`` *unimplemented* + ``__cpp_lib_stacktrace`` ``202011L`` ---------------------------------------------------------- ----------------- ``__cpp_lib_stdatomic_h`` ``202011L`` ---------------------------------------------------------- ----------------- diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst index d0383a705190d..37d8274384ad8 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -47,6 +47,8 @@ Implemented Papers - P1222R4: A Standard ``flat_set`` (`Github `__) - P2897R7: ``aligned_accessor``: An mdspan accessor expressing pointer over-alignment (`Github `__) - P3247R2: Deprecate the notion of trivial types (`Github `__) +- P0881R7: A Proposal to add stacktrace library (`Github `__) +- P2301R1: Add a `pmr` alias for `std::stacktrace` (`Github `__) Improvements and New Features ----------------------------- diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv index b5c88611559ef..8ecac9389b342 100644 --- a/libcxx/docs/Status/Cxx23Issues.csv +++ b/libcxx/docs/Status/Cxx23Issues.csv @@ -189,7 +189,7 @@ "`LWG3028 `__","Container requirements tables should distinguish ``const`` and non-``const`` variables","2022-11 (Kona)","","","" "`LWG3118 `__","``fpos`` equality comparison unspecified","2022-11 (Kona)","","","" "`LWG3177 `__","Limit permission to specialize variable templates to program-defined types","2022-11 (Kona)","|Nothing To Do|","","" -"`LWG3515 `__","§[stacktrace.basic.nonmem]: ``operator<<`` should be less templatized","2022-11 (Kona)","","","" +"`LWG3515 `__","§[stacktrace.basic.nonmem]: ``operator<<`` should be less templatized","2022-11 (Kona)","|Nothing To Do|","","" "`LWG3545 `__","``std::pointer_traits`` should be SFINAE-friendly","2022-11 (Kona)","|Complete|","18","" "`LWG3569 `__","``join_view`` fails to support ranges of ranges with non-default_initializable iterators","2022-11 (Kona)","","","" "`LWG3594 `__","``inout_ptr`` — inconsistent ``release()`` in destructor","2022-11 (Kona)","|Complete|","19","" diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index c26363bcda796..d98b848665cf8 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -1,5 +1,5 @@ "Paper #","Paper Name","Meeting","Status","First released version","Notes" -"`P0881R7 `__","A Proposal to add stacktrace library","2020-11 (Virtual)","","","" +"`P0881R7 `__","A Proposal to add stacktrace library","2020-11 (Virtual)","|Complete|","21","" "`P0943R6 `__","Support C atomics in C++","2020-11 (Virtual)","|Complete|","15","" "`P1048R1 `__","A proposal for a type trait to detect scoped enumerations","2020-11 (Virtual)","|Complete|","12","" "`P1679R3 `__","string contains function","2020-11 (Virtual)","|Complete|","12","" @@ -32,7 +32,7 @@ "`P1675R2 `__","``rethrow_exception`` must be allowed to copy","2021-10 (Virtual)","|Nothing To Do|","","" "`P2077R3 `__","Heterogeneous erasure overloads for associative containers","2021-10 (Virtual)","","","" "`P2251R1 `__","Require ``span`` & ``basic_string_view`` to be Trivially Copyable","2021-10 (Virtual)","|Complete|","14","" -"`P2301R1 `__","Add a ``pmr`` alias for ``std::stacktrace``","2021-10 (Virtual)","","","" +"`P2301R1 `__","Add a ``pmr`` alias for ``std::stacktrace``","2021-10 (Virtual)","|Complete|","21","" "`P2321R2 `__","``zip``","2021-10 (Virtual)","|In Progress|","","" "`P2340R1 `__","Clarifying the status of the 'C headers'","2021-10 (Virtual)","|Nothing To Do|","","" "`P2393R1 `__","Cleaning up ``integer``-class types","2021-10 (Virtual)","","","" @@ -110,7 +110,7 @@ "`P2713R1 `__","Escaping improvements in ``std::format``","2023-02 (Issaquah)","|Complete|","19","" "`P2675R1 `__","``format``'s width estimation is too approximate and not forward compatible","2023-02 (Issaquah)","|Complete|","17","" "`P2572R1 `__","``std::format`` fill character allowances","2023-02 (Issaquah)","|Complete|","17","" -"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","2023-02 (Issaquah)","|Partial|","","The formatter for ``stacktrace`` is not implemented, since ``stacktrace`` is not implemented yet" +"`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","2023-02 (Issaquah)","|Partial|","","The formatter for ``stacktrace`` is not implemented yet" "`P2679R2 `__","Fixing ``std::start_lifetime_as`` for arrays","2023-02 (Issaquah)","","","" "`P2674R1 `__","A trait for implicit lifetime types","2023-02 (Issaquah)","|Complete|","20","" "`P2655R3 `__","``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type","2023-02 (Issaquah)","","","" diff --git a/libcxx/docs/VendorDocumentation.rst b/libcxx/docs/VendorDocumentation.rst index 959a28607d75d..bde27aa8c6dab 100644 --- a/libcxx/docs/VendorDocumentation.rst +++ b/libcxx/docs/VendorDocumentation.rst @@ -185,6 +185,14 @@ General purpose options ship the IANA time zone database. When time zones are not supported, time zone support in will be disabled. +.. option:: LIBCXX_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME:BOOL + + **Default**: ``ON`` + + For C++23 : whether to allow invocation of ``addr2line``, ``llvm-addr2line``, or ``atos`` + at runtime (if it's available in ``PATH``) to resolve call-chain addresses in the stacktrace + into source locations, if other methods are not available. + .. option:: LIBCXX_INSTALL_LIBRARY_DIR:PATH **Default**: ``lib${LIBCXX_LIBDIR_SUFFIX}`` diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 43cefd5600646..9e6cc1a5b35e1 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -728,6 +728,12 @@ set(files __ranges/views.h __ranges/zip_view.h __split_buffer + __stacktrace/base.h + __stacktrace/basic.h + __stacktrace/entry.h + __stacktrace/hash.h + __stacktrace/nonmem.h + __stacktrace/to_string.h __std_mbstate_t.h __stop_token/atomic_unique_lock.h __stop_token/intrusive_list_view.h @@ -1048,6 +1054,7 @@ set(files span sstream stack + stacktrace stdatomic.h stdbool.h stddef.h diff --git a/libcxx/include/__config b/libcxx/include/__config index 57223e4f1db18..7b292b1521bdc 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -987,6 +987,33 @@ typedef __char32_t char32_t; # define _LIBCPP_NOINLINE # endif +// Some functions, e.g. std::stacktrace::current, need to avoid being +// tail-called by (and tail-calling other) functions, for proper enumeration of +// call-stack frames. +// clang-format off + +// Disables tail-call optimization for "outbound" calls +// performed in the function annotated with this attribute. +# if __has_cpp_attribute(_Clang::__disable_tail_calls__) +# define _LIBCPP_NO_TAIL_CALLS_OUT [[_Clang::__disable_tail_calls__]] +# elif __has_cpp_attribute(__gnu__::__optimize__) +# define _LIBCPP_NO_TAIL_CALLS_OUT [[__gnu__::__optimize__("no-optimize-sibling-calls")]] +# else +# define _LIBCPP_NO_TAIL_CALLS_OUT +# endif + +// Disables tail-call optimization for "inbound" calls -- that is, +// calls from some other function calling the one having this attribute. +# if __has_cpp_attribute(_Clang::__not_tail_called__) +# define _LIBCPP_NO_TAIL_CALLS_IN [[_Clang::__not_tail_called__]] +# else +# define _LIBCPP_NO_TAIL_CALLS_IN +# endif + +// Disable TCO for calls into, and out from, the annotated function. +# define _LIBCPP_NO_TAIL_CALLS _LIBCPP_NO_TAIL_CALLS_IN _LIBCPP_NO_TAIL_CALLS_OUT +// clang-format on + // We often repeat things just for handling wide characters in the library. // When wide characters are disabled, it can be useful to have a quick way of // disabling it without having to resort to #if-#endif, which has a larger diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in index fc01aaf2d8746..4e25ed040c9d0 100644 --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -33,6 +33,7 @@ #cmakedefine _LIBCPP_HAS_NO_STD_MODULES #cmakedefine01 _LIBCPP_HAS_TIME_ZONE_DATABASE #cmakedefine01 _LIBCPP_INSTRUMENTED_WITH_ASAN +#cmakedefine01 _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME // PSTL backends #cmakedefine _LIBCPP_PSTL_BACKEND_SERIAL diff --git a/libcxx/include/__stacktrace/base.h b/libcxx/include/__stacktrace/base.h new file mode 100644 index 0000000000000..04839b68fd5e8 --- /dev/null +++ b/libcxx/include/__stacktrace/base.h @@ -0,0 +1,131 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_BUILDER +#define _LIBCPP_STACKTRACE_BUILDER + +#include <__config> +#include <__cstddef/byte.h> +#include <__cstddef/size_t.h> +#include <__functional/function.h> +#include <__fwd/format.h> +#include <__fwd/ostream.h> +#include <__memory/allocator_traits.h> +#include <__vector/vector.h> +#include +#include +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +class stacktrace_entry; + +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI alloc final { + function __alloc_bytes_; + function __dealloc_bytes_; + + template + alloc(_Allocator __alloc) { + using _AT = allocator_traits<_Allocator>; + using _BA = typename _AT::template rebind_alloc; + auto __ba = _BA(__alloc); + __alloc_bytes_ = [__ba](size_t __sz) mutable { return __ba.allocate(__sz); }; + __dealloc_bytes_ = [__ba](void* __ptr, size_t __sz) mutable { __ba.deallocate((byte*)__ptr, __sz); }; + } + + template + struct Alloc { + function __alloc_bytes_; + function __dealloc_bytes_; + + Alloc(function __alloc_bytes, function __dealloc_bytes) + : __alloc_bytes_(__alloc_bytes), __dealloc_bytes_(__dealloc_bytes) {} + + template + Alloc(Alloc<_T2> const& __rhs) : Alloc(__rhs.__alloc_bytes_, __rhs.__dealloc_bytes_) {} + + using value_type = _Tp; + [[nodiscard]] _Tp* allocate(size_t __sz) { return (_Tp*)__alloc_bytes_(__sz * sizeof(_Tp)); } + void deallocate(_Tp* __ptr, size_t __sz) { __dealloc_bytes_((byte*)__ptr, __sz * sizeof(_Tp)); } + + template + bool operator==(_A2 const& __rhs) const { + return &__rhs == this; + } + }; + + template + Alloc<_Tp> make_alloc() { + return {__alloc_bytes_, __dealloc_bytes_}; + } + + using str = basic_string, Alloc>; + + template + str make_str(_Args... __args) { + return str(std::forward<_Args>(__args)..., make_alloc()); + } + + template + using vec = vector<_Tp, Alloc<_Tp>>; + + template + vec<_Tp> make_vec(_Args... __args) { + return vec(std::forward<_Args>(__args)..., make_alloc<_Tp>()); + } + + template + using list = ::std::list<_Tp, Alloc<_Tp>>; + + template + list<_Tp> make_list(_Args... __args) { + return list(std::forward<_Args>(__args)..., make_alloc<_Tp>()); + } +}; + +struct _LIBCPP_HIDE_FROM_ABI entry_base { + uintptr_t __addr_actual_{}; // this address, as observed in this current process + uintptr_t __addr_unslid_{}; // address adjusted for ASLR + optional<__stacktrace::alloc::str> __desc_{}; // uses wrapped _Allocator from caller + optional<__stacktrace::alloc::str> __file_{}; // uses wrapped _Allocator from caller + uint_least32_t __line_{}; + + stacktrace_entry to_stacktrace_entry() const; +}; + +struct _LIBCPP_HIDE_FROM_ABI builder final { + alloc __alloc_; // wraps the caller-provided allocator + alloc::vec __entries_; + alloc::str __main_prog_path_; + + template + explicit builder(_Allocator __alloc) + : __alloc_(__alloc), __entries_(__alloc_.make_vec()), __main_prog_path_(__alloc_.make_str()) {} + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void + build_stacktrace(size_t __skip, size_t __max_depth); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_BUILDER diff --git a/libcxx/include/__stacktrace/basic.h b/libcxx/include/__stacktrace/basic.h new file mode 100644 index 0000000000000..6b4ac3e181f4f --- /dev/null +++ b/libcxx/include/__stacktrace/basic.h @@ -0,0 +1,246 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE +#define _LIBCPP_BASIC_STACKTRACE + +#include <__config> +#include <__functional/hash.h> +#include <__fwd/format.h> +#include <__iterator/iterator.h> +#include <__iterator/reverse_iterator.h> +#include <__memory/allocator_traits.h> +#include <__memory_resource/polymorphic_allocator.h> +#include <__stacktrace/base.h> +#include <__stacktrace/entry.h> +#include <__stacktrace/to_string.h> +#include <__type_traits/is_nothrow_constructible.h> +#include <__vector/vector.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.4) +// Class template basic_stacktrace [stacktrace.basic] + +class stacktrace_entry; + +template +class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace { + friend struct hash>; + friend struct __stacktrace::__to_string; + + using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; + constexpr static bool __kPropOnCopy = _ATraits::propagate_on_container_copy_assignment::value; + constexpr static bool __kPropOnMove = _ATraits::propagate_on_container_move_assignment::value; + constexpr static bool __kPropOnSwap = _ATraits::propagate_on_container_swap::value; + constexpr static bool __kAlwaysEqual = _ATraits::is_always_equal::value; + constexpr static bool __kNoThrowDflConstruct = is_nothrow_default_constructible_v<_Allocator>; + constexpr static bool __kNoThrowAlloc = + noexcept(noexcept(_Allocator().allocate(1)) && noexcept(_Allocator().allocate_at_least(1))); + + [[no_unique_address]] _Allocator __alloc_; + + using __entry_vec = vector; + __entry_vec __entries_; + +public: + // (19.6.4.1) + // Overview [stacktrace.basic.overview] + + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using difference_type = ptrdiff_t; + using size_type = size_t; + using allocator_type = _Allocator; + using const_iterator = __entry_vec::const_iterator; + using iterator = const_iterator; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // (19.6.4.2) + // Creation and assignment [stacktrace.basic.cons] + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + return current(1, /* no __max_depth */ ~0, __caller_alloc); + } + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(size_type __skip, const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + return current(__skip + 1, /* no __max_depth */ ~0, __caller_alloc); + } + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI static basic_stacktrace + current(size_type __skip, + size_type __max_depth, + const allocator_type& __caller_alloc = allocator_type()) noexcept(__kNoThrowAlloc) { + __stacktrace::builder __builder(__caller_alloc); + __builder.build_stacktrace(__skip + 1, __max_depth); + basic_stacktrace<_Allocator> __ret{__caller_alloc}; + __ret.__entries_.reserve(__builder.__entries_.size()); + for (auto& __base : __builder.__entries_) { + __ret.__entries_.emplace_back(__base.to_stacktrace_entry()); + } + return __ret; + } + + _LIBCPP_EXPORTED_FROM_ABI constexpr ~basic_stacktrace() = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(__kNoThrowDflConstruct) : basic_stacktrace(allocator_type()) {} + + _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) noexcept + : __alloc_(__alloc), __entries_(__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept = default; + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) + : __alloc_(__alloc), __entries_(__other.__entries_, __alloc) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) + : __alloc_(__alloc) { + if (__kAlwaysEqual || __alloc_ == __other.__alloc_) { + __entries_ = std::move(__other.__entries_); + } else { + // "moving" from a container with a different allocator; we're forced to copy items instead + for (auto const& __entry : __other.__entries_) { + __entries_.push_back(__entry); + } + } + } + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { + if (this == std::addressof(__other)) { + return *this; + } + if (__kPropOnCopy) { + __alloc_ = __other.__alloc_; + } + __entries_ = {__other.__entries_, __alloc_}; + return *this; + } + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& + operator=(basic_stacktrace&& __other) noexcept(__kPropOnMove || __kAlwaysEqual) { + if (this == std::addressof(__other)) { + return *this; + } + if (__kPropOnMove) { + __alloc_ = __other.__alloc_; + __entries_ = std::move(__other.__entries_); + } else { + auto __allocs_eq = __kAlwaysEqual || __alloc_ == __other.__alloc_; + if (__allocs_eq) { + __entries_ = std::move(__other.__entries_); + } else { + // "moving" from a container with a different allocator; + // we're forced to copy items instead + for (auto const& __entry : __other.__entries_) { + __entries_.push_back(__entry); + } + } + } + return *this; + } + + // clang-format on + + // (19.6.4.3) + // [stacktrace.basic.obs], observers + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI allocator_type get_allocator() const noexcept { return __alloc_; } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator begin() const noexcept { return __entries_.begin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator end() const noexcept { return __entries_.end(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator rbegin() const noexcept { return __entries_.rbegin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator rend() const noexcept { return __entries_.rend(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator cbegin() const noexcept { return __entries_.cbegin(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_iterator cend() const noexcept { return __entries_.cend(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator crbegin() const noexcept { + return __entries_.crbegin(); + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reverse_iterator crend() const noexcept { return __entries_.crend(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI bool empty() const noexcept { return __entries_.empty(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI size_type size() const noexcept { return __entries_.size(); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI size_type max_size() const noexcept { return __entries_.max_size(); } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reference operator[](size_type __i) const { return __entries_[__i]; } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI const_reference at(size_type __i) const { return __entries_.at(__i); } + + // (19.6.4.4) + // [stacktrace.basic.cmp], comparisons + + template + _LIBCPP_EXPORTED_FROM_ABI friend bool + operator==(const basic_stacktrace& __x, const basic_stacktrace<_Allocator2>& __y) noexcept { + if (__x.size() != __y.size()) { + return false; + } + auto __xi = __x.begin(); + auto __yi = __y.begin(); + auto __xe = __x.end(); + while (__xi != __xe) { + if (*__xi++ != *__yi++) { + return false; + } + } + return true; + } + + template + _LIBCPP_EXPORTED_FROM_ABI friend strong_ordering + operator<=>(const basic_stacktrace& __x, const basic_stacktrace<_Allocator2>& __y) noexcept { + auto __ret = __x.size() <=> __y.size(); + if (__ret != std::strong_ordering::equal) { + return __ret; + } + auto __xi = __x.begin(); + auto __yi = __y.begin(); + auto __xe = __x.end(); + while ((__ret == std::strong_ordering::equal) && __xi != __xe) { + __ret = *__xi++ <=> *__yi++; + } + return __ret; + } + + // (19.6.4.5) + // [stacktrace.basic.mod], modifiers + + _LIBCPP_EXPORTED_FROM_ABI void swap(basic_stacktrace<_Allocator>& __other) noexcept { + std::swap(__entries_, __other.__entries_); + if (__kPropOnSwap) { + std::swap(__alloc_, __other.__alloc_); + } + } +}; + +using stacktrace = basic_stacktrace>; + +namespace pmr { +using stacktrace = basic_stacktrace>; +} // namespace pmr + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE diff --git a/libcxx/include/__stacktrace/entry.h b/libcxx/include/__stacktrace/entry.h new file mode 100644 index 0000000000000..1e2d1d667130b --- /dev/null +++ b/libcxx/include/__stacktrace/entry.h @@ -0,0 +1,113 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_ENTRY +#define _LIBCPP_STACKTRACE_ENTRY + +#include <__config> +#include <__fwd/format.h> +#include <__fwd/ostream.h> +#include +#include +#include +#include + +#include <__stacktrace/base.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_base { + friend struct __stacktrace::entry_base; + stacktrace_entry(entry_base const& __base) : entry_base(__base) {} + +public: + // (19.6.3.1) Overview [stacktrace.entry.overview] + using native_handle_type = uintptr_t; + + // (19.6.3.2) [stacktrace.entry.cons], constructors + _LIBCPP_EXPORTED_FROM_ABI ~stacktrace_entry() noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry() noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry(const stacktrace_entry&) noexcept = default; + _LIBCPP_EXPORTED_FROM_ABI constexpr stacktrace_entry& operator=(const stacktrace_entry&) noexcept = default; + + // (19.6.3.3) [stacktrace.entry.obs], observers + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr native_handle_type native_handle() const noexcept { + return __addr_actual_; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI constexpr explicit operator bool() const noexcept { + return native_handle() != 0; + } + + // (19.6.3.4) [stacktrace.entry.query], query + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string description() const { + if (__desc_->empty()) { + return ""; + } + return {__desc_->data(), __desc_->size()}; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { + if (__desc_->empty()) { + return ""; + } + return {__file_->data(), __file_->size()}; + } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI uint_least32_t source_line() const { return __line_; } + + // (19.6.3.5) [stacktrace.entry.cmp], comparison + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI friend constexpr bool + operator==(const stacktrace_entry& __x, const stacktrace_entry& __y) noexcept { + return __x.native_handle() == __y.native_handle(); + } + + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI friend constexpr strong_ordering + operator<=>(const stacktrace_entry& __x, const stacktrace_entry& __y) noexcept { + return __x.native_handle() <=> __y.native_handle(); + } +}; + +// (19.6.4.6) +// Non-member functions [stacktrace.basic.nonmem] + +[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry); +_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry); + +// (19.6.5) +// Formatting support [stacktrace.format] + +// TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 +template <> +struct _LIBCPP_EXPORTED_FROM_ABI formatter; + +// (19.6.6) +// Hash support [stacktrace.basic.hash] + +template <> +struct _LIBCPP_EXPORTED_FROM_ABI hash { + [[nodiscard]] size_t operator()(stacktrace_entry const& __entry) const noexcept { + auto __addr = __entry.native_handle(); + return hash()(__addr); + } +}; + +namespace __stacktrace { +inline stacktrace_entry entry_base::to_stacktrace_entry() const { return {*this}; } +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_ENTRY diff --git a/libcxx/include/__stacktrace/hash.h b/libcxx/include/__stacktrace/hash.h new file mode 100644 index 0000000000000..817f4ff35d041 --- /dev/null +++ b/libcxx/include/__stacktrace/hash.h @@ -0,0 +1,50 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE_HASH +#define _LIBCPP_BASIC_STACKTRACE_HASH + +#include <__config> +#include <__functional/hash.h> +#include <__fwd/format.h> +#include <__memory/allocator_traits.h> +#include <__vector/vector.h> +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/to_string.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.6) +// Hash support [stacktrace.basic.hash] + +template +struct _LIBCPP_EXPORTED_FROM_ABI hash> { + [[nodiscard]] size_t operator()(basic_stacktrace<_Allocator> const& __context) const noexcept { + size_t __ret = 1; + for (auto const& __entry : __context.__entries_) { + __ret += hash()(__entry.native_handle()); + } + return __ret; + } +}; + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE_HASH diff --git a/libcxx/include/__stacktrace/nonmem.h b/libcxx/include/__stacktrace/nonmem.h new file mode 100644 index 0000000000000..7199e0cd69092 --- /dev/null +++ b/libcxx/include/__stacktrace/nonmem.h @@ -0,0 +1,55 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_BASIC_STACKTRACE_NONMEM +#define _LIBCPP_BASIC_STACKTRACE_NONMEM + +#include <__config> +#include <__fwd/format.h> +#include <__memory/allocator_traits.h> +#include <__vector/vector.h> +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/to_string.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// (19.6.4.6) +// Non-member functions [stacktrace.basic.nonmem] + +template +_LIBCPP_EXPORTED_FROM_ABI inline void +swap(basic_stacktrace<_Allocator>& __a, basic_stacktrace<_Allocator>& __b) noexcept(noexcept(__a.swap(__b))) { + __a.swap(__b); +} + +template +_LIBCPP_EXPORTED_FROM_ABI inline string to_string(const basic_stacktrace<_Allocator>& __stacktrace) { + return __stacktrace::__to_string()(__stacktrace); +} + +template +_LIBCPP_EXPORTED_FROM_ABI inline ostream& operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __stacktrace) { + auto __str = __stacktrace::__to_string()(__stacktrace); + return __os << __str; +} + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_BASIC_STACKTRACE_NONMEM diff --git a/libcxx/include/__stacktrace/to_string.h b/libcxx/include/__stacktrace/to_string.h new file mode 100644 index 0000000000000..920c41133d5df --- /dev/null +++ b/libcxx/include/__stacktrace/to_string.h @@ -0,0 +1,53 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_TO_STRING +#define _LIBCPP_STACKTRACE_TO_STRING + +#include <__config> +#include <__fwd/ostream.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +template +class basic_stacktrace; + +class stacktrace_entry; + +namespace __stacktrace { + +struct __to_string { + _LIBCPP_EXPORTED_FROM_ABI string operator()(stacktrace_entry const& __entry); + + _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, stacktrace_entry const& __entry); + + _LIBCPP_EXPORTED_FROM_ABI void operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count); + + _LIBCPP_EXPORTED_FROM_ABI string operator()(std::stacktrace_entry const* __entries, size_t __count); + + template + _LIBCPP_EXPORTED_FROM_ABI string operator()(basic_stacktrace<_Allocator> const& __st) { + return (*this)(__st.__entries_.data(), __st.__entries_.size()); + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_TO_STRING diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 7f625cefed1c2..279cf678140d4 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1986,6 +1986,18 @@ module std [system] { export * } + module stacktrace { + module base { header "__stacktrace/base.h" } + module basic { header "__stacktrace/basic.h" } + module entry { header "__stacktrace/entry.h" } + module hash { header "__stacktrace/hash.h" } + module nonmem { header "__stacktrace/nonmem.h" } + module to_string { header "__stacktrace/to_string.h" } + + header "stacktrace" + export * + } + module stdexcept { header "stdexcept" export * diff --git a/libcxx/include/stacktrace b/libcxx/include/stacktrace new file mode 100644 index 0000000000000..1fbdb6b4fe293 --- /dev/null +++ b/libcxx/include/stacktrace @@ -0,0 +1,191 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE +#define _LIBCPP_STACKTRACE + +/* + Header synopsis + (19.6.2) + +#include // see [compare.syn] + +namespace std { + // [stacktrace.entry], class stacktrace_entry + class stacktrace_entry; + + // [stacktrace.basic], class template basic_stacktrace + template + class basic_stacktrace; + + // basic_stacktrace typedef-names + using stacktrace = basic_stacktrace>; + + // [stacktrace.basic.nonmem], non-member functions + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); + + // [stacktrace.format], formatting support + template<> struct formatter; + template struct formatter>; + + namespace pmr { + using stacktrace = basic_stacktrace>; + } + + // [stacktrace.basic.hash], hash support + template struct hash; + template<> struct hash; + template struct hash>; +} + +// [stacktrace.entry] + +namespace std { + class stacktrace_entry { + public: + using native_handle_type = implementation-defined; + + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; + + ~stacktrace_entry(); + + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; + constexpr explicit operator bool() const noexcept; + + // [stacktrace.entry.query], query + string description() const; + string source_file() const; + uint_least32_t source_line() const; + + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + }; +} + +// [stacktrace.basic] + +namespace std { + template + class basic_stacktrace { + public: + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using const_iterator = implementation-defined; // see [stacktrace.basic.obs] + using iterator = const_iterator; + using reverse_iterator = reverse_iterator; + using const_reverse_iterator = reverse_iterator; + using difference_type = implementation-defined; + using size_type = implementation-defined; + using allocator_type = Allocator; + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); + explicit basic_stacktrace(const allocator_type& alloc) noexcept; + + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); + + ~basic_stacktrace(); + + // [stacktrace.basic.obs], observers + allocator_type get_allocator() const noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + const_reference operator[](size_type) const; + const_reference at(size_type) const; + + // [stacktrace.basic.cmp], comparisons + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + + // [stacktrace.basic.mod], modifiers + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + private: + vector frames_; // exposition only + }; +} + +*/ + +#include <__config> + +#if _LIBCPP_STD_VER >= 23 + +# include // according to [stacktrace.syn] + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +_LIBCPP_PUSH_MACROS +# include <__undef_macros> + +# include <__stacktrace/base.h> +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> +# include <__stacktrace/hash.h> +# include <__stacktrace/nonmem.h> +# include <__stacktrace/to_string.h> + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STD_VER >= 23 + +#endif // _LIBCPP_STACKTRACE diff --git a/libcxx/include/version b/libcxx/include/version index 77d97b93adc6c..e0bae02a84d2d 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -524,7 +524,7 @@ __cpp_lib_void_t 201411L // # define __cpp_lib_ranges_zip 202110L // # define __cpp_lib_reference_from_temporary 202202L // # define __cpp_lib_spanstream 202106L -// # define __cpp_lib_stacktrace 202011L +# 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/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist index 162757c7e37ec..7dc67154b26a3 100644 --- a/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/arm64-apple-darwin.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -534,6 +534,7 @@ {'is_defined': True, 'name': '__ZNKSt3__115basic_streambufIwNS_11char_traitsIwEEE6getlocEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115basic_stringbufIcNS_11char_traitsIcEENS_9allocatorIcEEE3strEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__115error_condition7messageEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE11do_groupingEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE13do_neg_formatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__117moneypunct_bynameIcLb0EE13do_pos_formatEv', 'type': 'FUNC'} @@ -974,6 +975,11 @@ {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace7builderB8ne21000016build_stacktraceEmm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -1125,6 +1131,7 @@ {'is_defined': True, 'name': '__ZNSt3__112system_errorD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112system_errorD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__112system_errorD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE11__read_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE12__write_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj', 'type': 'FUNC'} @@ -1305,7 +1312,6 @@ {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__113shared_futureIvEaSERKS1_', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__114__num_get_base10__get_baseERNS_8ios_baseE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__114__num_get_base5__srcE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNSt3__114__num_put_base12__format_intEPcPKcbj', 'type': 'FUNC'} @@ -1508,7 +1514,6 @@ {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117bad_function_callD2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '__ZNKSt3__117bad_function_call4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117iostream_categoryEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117moneypunct_bynameIcLb0EE4initEPKc', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__117moneypunct_bynameIcLb1EE4initEPKc', 'type': 'FUNC'} @@ -1935,6 +1940,7 @@ {'is_defined': True, 'name': '__ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -1944,6 +1950,7 @@ {'is_defined': True, 'name': '__ZNSt3__19to_stringEm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEx', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__19to_stringEy', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__1lsERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNSt8bad_castC1Ev', 'type': 'I'} {'is_defined': True, 'name': '__ZNSt8bad_castC2Ev', 'type': 'I'} @@ -2011,6 +2018,9 @@ {'is_defined': True, 'name': '__ZTINSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTINSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTINSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2225,6 +2235,9 @@ {'is_defined': True, 'name': '__ZTSNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTSNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTSNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} @@ -2447,6 +2460,9 @@ {'is_defined': True, 'name': '__ZTVNSt3__110moneypunctIwLb1EEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__110ostrstreamE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__111regex_errorE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace4atosE', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZTVNSt3__112__stacktrace9addr2lineE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112bad_weak_ptrE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIcEE', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZTVNSt3__112ctype_bynameIwEE', 'size': 0, 'type': 'OBJECT'} diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist index 679a0626d3268..7b0e6d3c82a84 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist @@ -622,6 +622,11 @@ {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112__rs_defaultclEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEEPKNS_16stacktrace_entryEm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace11__to_stringclERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace6__implEmmRNS0_5allocB8ne210000ENS_8functionIFvmEEENS3_IFvmONS_16stacktrace_entryEEEE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112bad_weak_ptrD2Ev', 'type': 'FUNC'} @@ -773,6 +778,7 @@ {'is_defined': True, 'name': '_ZNSt3__112system_errorD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__112system_errorD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE11__read_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE12__write_modeEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113basic_filebufIcNS_11char_traitsIcEEE4openEPKcj', 'type': 'FUNC'} @@ -953,7 +959,6 @@ {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvEaSERKS1_', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base10__get_baseERNS_8ios_baseE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__114__num_get_base5__srcE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZNSt3__114__num_put_base12__format_intEPcPKcbj', 'type': 'FUNC'} @@ -1137,6 +1142,7 @@ {'is_defined': True, 'name': '_ZNSt3__116__narrow_to_utf8ILm32EED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__116__narrow_to_utf8ILm32EED2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__116generic_categoryEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__116stacktrace_entryD2Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117__assoc_sub_state10__sub_waitERNS_11unique_lockINS_5mutexEEE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117__assoc_sub_state12__make_readyEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__117__assoc_sub_state13set_exceptionESt13exception_ptr', 'type': 'FUNC'} @@ -1586,6 +1592,7 @@ {'is_defined': True, 'name': '_ZNSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19strstreamD2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__19to_stringERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEd', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEe', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEf', 'type': 'FUNC'} @@ -1595,6 +1602,7 @@ {'is_defined': True, 'name': '_ZNSt3__19to_stringEm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEx', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__19to_stringEy', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__1lsERNS_13basic_ostreamIcNS_11char_traitsIcEEEERKNS_16stacktrace_entryE', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__1plIcNS_11char_traitsIcEENS_9allocatorIcEEEENS_12basic_stringIT_T0_T1_EEPKS6_RKS9_', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17__throw_bad_allocv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZSt17current_exceptionv', 'type': 'FUNC'} @@ -1604,6 +1612,7 @@ {'is_defined': True, 'name': '_ZSt7nothrow', 'size': 1, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110istrstreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__110ostrstreamE0_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTCNSt3__112__stacktrace10fd_istreamE0_NS_13basic_istreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_ifstreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE0_NS_13basic_istreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTCNSt3__114basic_iostreamIcNS_11char_traitsIcEEEE16_NS_13basic_ostreamIcS2_EE', 'size': 80, 'type': 'OBJECT'} @@ -1618,6 +1627,8 @@ {'is_defined': True, 'name': '_ZTCNSt3__19strstreamE16_NS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental15fundamentals_v112bad_any_castE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt12experimental19bad_optional_accessE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_getE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110__time_putE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__110ctype_baseE', 'size': 16, 'type': 'OBJECT'} @@ -1633,6 +1644,12 @@ {'is_defined': True, 'name': '_ZTINSt3__111__money_putIcEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111__money_putIwEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__111regex_errorE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace10fd_istreamE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace15llvm_symbolizerE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4atosE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace4toolE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace6failedE', 'size': 24, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__112__stacktrace9addr2lineE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112bad_weak_ptrE', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112codecvt_baseE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTINSt3__112ctype_bynameIcEE', 'size': 24, 'type': 'OBJECT'} @@ -1751,6 +1768,8 @@ {'is_defined': True, 'name': '_ZTISt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental15fundamentals_v112bad_any_castE', 'size': 50, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt12experimental19bad_optional_accessE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf6SymbolEEEE', 'size': 64, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFbRKNS_12__stacktrace3elf7SectionEEEE', 'size': 65, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_getE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110__time_putE', 'size': 21, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__110ctype_baseE', 'size': 21, 'type': 'OBJECT'} @@ -1766,6 +1785,12 @@ {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIcEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111__money_putIwEE', 'size': 25, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__111regex_errorE', 'size': 22, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace10fd_istreamE', 'size': 35, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace15llvm_symbolizerE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4atosE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace4toolE', 'size': 28, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace6failedE', 'size': 30, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__112__stacktrace9addr2lineE', 'size': 33, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112bad_weak_ptrE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112codecvt_baseE', 'size': 23, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTSNSt3__112ctype_bynameIcEE', 'size': 26, 'type': 'OBJECT'} @@ -1884,6 +1909,7 @@ {'is_defined': True, 'name': '_ZTSSt19bad_optional_access', 'size': 24, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110istrstreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__110ostrstreamE', 'size': 32, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTTNSt3__112__stacktrace10fd_istreamE', 'size': 32, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_istreamIwNS_11char_traitsIwEEEE', 'size': 16, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTTNSt3__113basic_ostreamIcNS_11char_traitsIcEEEE', 'size': 16, 'type': 'OBJECT'} @@ -1904,6 +1930,11 @@ {'is_defined': True, 'name': '_ZTVNSt3__110moneypunctIwLb1EEE', 'size': 112, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__110ostrstreamE', 'size': 80, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__111regex_errorE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace10fd_istreamE', 'size': 80, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace15llvm_symbolizerE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace4atosE', 'size': 48, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace6failedE', 'size': 40, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTVNSt3__112__stacktrace9addr2lineE', 'size': 48, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112bad_weak_ptrE', 'size': 40, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIcEE', 'size': 104, 'type': 'OBJECT'} {'is_defined': True, 'name': '_ZTVNSt3__112ctype_bynameIwEE', 'size': 136, 'type': 'OBJECT'} diff --git a/libcxx/modules/std.compat.cppm.in b/libcxx/modules/std.compat.cppm.in index 95931447ccdc6..55d21030c031b 100644 --- a/libcxx/modules/std.compat.cppm.in +++ b/libcxx/modules/std.compat.cppm.in @@ -71,9 +71,6 @@ module; # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() -# if __has_include() -# error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" -# endif // __has_include() # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() diff --git a/libcxx/modules/std.cppm.in b/libcxx/modules/std.cppm.in index 5c523691bff4e..7691f7e860655 100644 --- a/libcxx/modules/std.cppm.in +++ b/libcxx/modules/std.cppm.in @@ -124,6 +124,7 @@ module; # include #endif #include +#include #include #include #if _LIBCPP_HAS_LOCALIZATION @@ -181,9 +182,6 @@ module; # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() -# if __has_include() -# error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" -# endif // __has_include() # if __has_include() # error "please update the header information for in headers_not_available in utils/libcxx/header_information.py" # endif // __has_include() diff --git a/libcxx/modules/std/stacktrace.inc b/libcxx/modules/std/stacktrace.inc index c1184087c0df4..e7b31fd29b3a6 100644 --- a/libcxx/modules/std/stacktrace.inc +++ b/libcxx/modules/std/stacktrace.inc @@ -8,7 +8,8 @@ //===----------------------------------------------------------------------===// export namespace std { -#if 0 +#if _LIBCPP_STD_VER >= 23 + // [stacktrace.entry], class stacktrace_­entry using std::stacktrace_entry; @@ -31,5 +32,6 @@ export namespace std { // [stacktrace.basic.hash], hash support using std::hash; -#endif + +#endif // _LIBCPP_STD_VER >= 23 } // namespace std diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 4e9bf900af4c5..3f33d812cde7a 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -40,6 +40,14 @@ set(LIBCXX_SOURCES ryu/d2fixed.cpp ryu/d2s.cpp ryu/f2s.cpp + stacktrace/builder.cpp + stacktrace/linux/impl.cpp + stacktrace/macos/impl.cpp + stacktrace/to_string.cpp + stacktrace/tools/tools.cpp + stacktrace/unwind/impl.cpp + stacktrace/windows/dll.cpp + stacktrace/windows/impl.cpp stdexcept.cpp string.cpp support/runtime/exception_fallback.ipp @@ -334,6 +342,7 @@ endif() add_library(cxx_experimental STATIC ${LIBCXX_EXPERIMENTAL_SOURCES}) target_link_libraries(cxx_experimental PUBLIC cxx-headers) + if (LIBCXX_ENABLE_SHARED) target_link_libraries(cxx_experimental PRIVATE cxx_shared) else() diff --git a/libcxx/src/stacktrace/README.md b/libcxx/src/stacktrace/README.md new file mode 100644 index 0000000000000..9f25eb4ad6e22 --- /dev/null +++ b/libcxx/src/stacktrace/README.md @@ -0,0 +1,6 @@ +# C++23 `` + + +# References + +1. https://eel.is/c++draft/stacktrace diff --git a/libcxx/src/stacktrace/builder.cpp b/libcxx/src/stacktrace/builder.cpp new file mode 100644 index 0000000000000..383e0ecde25cc --- /dev/null +++ b/libcxx/src/stacktrace/builder.cpp @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include <__config_site> + +#include <__stacktrace/base.h> + +#include "stacktrace/linux/impl.h" +#include "stacktrace/macos/impl.h" +#include "stacktrace/tools/tools.h" +#include "stacktrace/unwind/impl.h" +#include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void builder::build_stacktrace(size_t skip, size_t max_depth) { + // First get the instruction addresses, populate __entries_ + win_impl dbghelp{*this}; + unwind unwind{*this}; + dbghelp.collect(skip + 1, max_depth); + unwind.collect(skip + 1, max_depth); + + // (Can't proceed if empty) + if (!__entries_.size()) { + return; + } + + // Associate addrs with binaries (ELF/MachO/etc.) + macos macos{*this}; + linux linux{*this}; + dbghelp.ident_modules(); + macos.ident_modules(); + linux.ident_modules(); + + // Resolve addresses to symbols, filename, linenumber + spawner pspawn{*this}; + dbghelp.resolve_lines(); + pspawn.resolve_lines(); + + // Populate missing symbols, if any. + dbghelp.symbolize(); + macos.symbolize(); + linux.symbolize(); +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/config.h b/libcxx/src/stacktrace/config.h new file mode 100644 index 0000000000000..c4c742d868b69 --- /dev/null +++ b/libcxx/src/stacktrace/config.h @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_CONFIG_H +#define _LIBCPP_STACKTRACE_CONFIG_H + +#include <__config> +#include <__config_site> + +// Check for unwind.h -- could exist on any OS (in theory), but it (or `libunwind`) is likely on Linux systems, and also +// comes with XCode tools on MacOS. +#if __has_include() +# define _LIBCPP_STACKTRACE_UNWIND_IMPL +#endif + +// Whether we can invoke external processes via `posix_spawn` +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME +# define _LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS +#endif + +#endif // _LIBCPP_STACKTRACE_CONFIG_H diff --git a/libcxx/src/stacktrace/linux/impl.cpp b/libcxx/src/stacktrace/linux/impl.cpp new file mode 100644 index 0000000000000..3571826dc82c7 --- /dev/null +++ b/libcxx/src/stacktrace/linux/impl.cpp @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/linux/impl.h" +#include "stacktrace/config.h" + +#if defined(__linux__) + +# include +# include +# include +# include +# include + +# include "stacktrace/config.h" +# include "stacktrace/utils/fd.h" +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +void linux::ident_modules() { + auto& images = images::get(); + + // Aside from the left/right sentinels in the array (hence the 2), + // are there any other real images? + if (images.count_ <= 2) { + return; + } + + auto mainProg = images.mainProg(); + if (mainProg) { + builder_.__main_prog_path_ = mainProg->name_; + } + + unsigned index = 1; // Starts at one, and is moved around in this loop + for (auto& entry : builder_.__entries_) { + while (images[index].loaded_at_ > entry.__addr_actual_) { + --index; + } + while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { + ++index; + } + entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; + entry.__file_ = builder_.__alloc_.make_str(images[index].name_); + } +} + +/** +When trying to collect a stacktrace under Linux, there are narrow (but still quite common) cases where we will fail +to resolve symbols. Linux's `dl` doesn't want to read symbols from the non-exported symbol table at runtime, +and older versions of `addr2line` or `llvm-symbolizer` will also not resolve these. + +This implementation the minimum necessary to resolve symbols. It can identify this as an ELF (32 or 64 bits), locate +the symbol and symbol-string table, and fill in any remaining missing symbols. +*/ +void linux::resolve_main_elf_syms(std::string_view main_elf_name) { + // We can statically initialize these, because main_elf_name should be the same every time. + static fd_mmap _mm(main_elf_name); + if (_mm) { + static elf::ELF _this_elf(_mm.addr_); + if (_this_elf) { + for (auto& entry : builder_.__entries_) { + if (entry.__desc_->empty() && entry.__file_ == main_elf_name) { + auto name = _this_elf.getSym(entry.__addr_unslid_).name(); + entry.__desc_ = builder_.__alloc_.make_str(name); + } + } + } + } +} + +bool symbolize_entry(alloc& alloc, entry_base& entry) { + bool ret = false; + Dl_info info; + if (dladdr((void*)entry.__addr_actual_, &info)) { + ret = true; // at least partially successful + if (info.dli_fname && entry.__file_->empty()) { + // provide at least the binary filename in case we cannot lookup source location + entry.__file_ = alloc.make_str(info.dli_fname); + } + if (info.dli_sname && entry.__desc_->empty()) { + // provide at least the mangled name; try to unmangle in a later step + entry.__desc_ = alloc.make_str(info.dli_sname); + } + } + return ret; +} + +// NOTE: We can use `dlfcn` to resolve addresses to symbols, which works great -- +// except for symbols in the main program. If addr2line-style tools are enabled, that step +// might also be able to get symbols directly from the binary's debug info. +void linux::symbolize() { + for (auto& entry : builder_.__entries_) { + symbolize_entry(builder_.__alloc_, entry); + } + // Symbols might be missing, because both (1) Linux's `dladdr` won't try to resolve non-exported symbols, + // which can be the case for the main program executable; and (2) debug info was not preserved. + // As a last resort, this function (see `linux-elf.cpp`) can still access symbol table directly. + image* mainELF = images::get().mainProg(); + if (mainELF && !mainELF->name_.empty()) { + resolve_main_elf_syms(mainELF->name_); + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ diff --git a/libcxx/src/stacktrace/linux/impl.h b/libcxx/src/stacktrace/linux/impl.h new file mode 100644 index 0000000000000..cd028d070835f --- /dev/null +++ b/libcxx/src/stacktrace/linux/impl.h @@ -0,0 +1,401 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_LINUX_IMPL +#define _LIBCPP_STACKTRACE_LINUX_IMPL + +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct linux { + builder& builder_; + +#if defined(__linux__) + // defined in linux.cpp + void ident_modules(); + void symbolize(); + +private: + void resolve_main_elf_syms(std::string_view elf_name); +#else + // inline-able dummy definitions + void ident_modules() {} + void symbolize() {} +#endif +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#include "stacktrace/config.h" + +#if defined(__linux__) + +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct images { + // How many images this contains, including the left/right sentinels. + unsigned count_{0}; + std::array images_{}; + + int add(dl_phdr_info& info) { + assert(count_ < image::kMaxImages); + auto isFirst = (count_ == 0); + auto& image = images_.at(count_++); + image.loaded_at_ = info.dlpi_addr; + image.slide_ = info.dlpi_addr; + image.name_ = info.dlpi_name; + image.is_main_prog_ = isFirst; // first one is the main ELF + if (image.name_.empty() && isFirst) { + static char buffer[PATH_MAX + 1]; + uint32_t length = sizeof(buffer); + if (readlink("/proc/self/exe", buffer, length) > 0) { + image.name_ = buffer; + } + } + return count_ == image::kMaxImages; // return nonzero if we're at the limit + } + + static int callback(dl_phdr_info* info, size_t, void* self) { return (*(images*)(self)).add(*info); } + + images() { + dl_iterate_phdr(images::callback, this); + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + std::sort(images_.begin(), images_.begin() + count_ - 1); + } + + image& operator[](size_t index) { + assert(index < count_); + return images_.at(index); + } + + image* mainProg() { + for (auto& image : images_) { + if (image.is_main_prog_) { + return ℑ + } + } + return nullptr; + } + + static images& get() { + static images images; + return images; + } +}; + +// Includes ELF constants and structs copied from , with a few changes. + +namespace elf { + +struct Header32 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint32_t entry; /* Entry point virtual address */ + uint32_t phoff; /* Program header table file offset */ + uint32_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section32 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint32_t flags; /* Section flags */ + uint32_t addr; /* Section virtual addr at execution */ + uint32_t offset; /* Section file offset */ + uint32_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint32_t addralign; /* Section alignment */ + uint32_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol32 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint32_t value; /* Symbol value */ + uint32_t size; /* Symbol size */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ +}; + +struct Header64 final { + uint8_t ident[16]; /* Magic number and other info */ + uint16_t type; /* Object file type */ + uint16_t machine; /* Architecture */ + uint32_t version; /* Object file version */ + uint64_t entry; /* Entry point virtual address */ + uint64_t phoff; /* Program header table file offset */ + uint64_t shoff; /* Section header table file offset */ + uint32_t flags; /* Processor-specific flags */ + uint16_t ehsize; /* ELF header size in bytes */ + uint16_t phentsize; /* Program header table entry size */ + uint16_t phnum; /* Program header table entry count */ + uint16_t shentsize; /* Section header table entry size */ + uint16_t shnum; /* Section header table entry count */ + uint16_t shstrndx; /* Section header string table index */ +}; + +struct Section64 final { + uint32_t name; /* Section name (string tbl index) */ + uint32_t type; /* Section type */ + uint64_t flags; /* Section flags */ + uint64_t addr; /* Section virtual addr at execution */ + uint64_t offset; /* Section file offset */ + uint64_t size; /* Section size in bytes */ + uint32_t link; /* Link to another section */ + uint32_t info; /* Additional section information */ + uint64_t addralign; /* Section alignment */ + uint64_t entsize; /* Entry size if section holds table */ +}; + +struct Symbol64 final { + uint32_t name; /* Symbol name (string tbl index) */ + uint8_t info; /* Symbol type and binding */ + uint8_t other; /* Symbol visibility */ + uint16_t shndx; /* Section index */ + uint64_t value; /* Symbol value */ + uint64_t size; /* Symbol size */ +}; + +/** Represents an ELF header. Supports the minimum needed to navigate an ELF file's sections and get at the symbol and + * string tables. */ +struct Header final { + std::byte const* ptr_{}; + uintptr_t shoff_{}; + size_t shnum_{}; + size_t shstrndx_{}; + + Header() = default; + Header(Header const&) = default; + Header& operator=(Header const& rhs) { return *new (this) Header(rhs); } + + operator bool() { return ptr_; } + + template + explicit Header(H* h) + : ptr_((std::byte const*)h), + shoff_(uintptr_t(h->shoff)), + shnum_(size_t(h->shnum)), + shstrndx_(size_t(h->shstrndx)) {} +}; + +struct ELF; +struct StringTable; + +struct Section final { + constexpr static uint32_t kSymTab = 2; // symbol table + constexpr static uint32_t kStrTab = 3; // name table for symbols or sections + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t offset_{}; + size_t size_{}; + + Section() = default; + + template + Section(ELF* elf, S* sec) + : elf_(elf), + ptr_((std::byte const*)sec), + nameIndex_(sec->name), + type_(sec->type), + offset_(sec->offset), + size_(sec->size) {} + + operator bool() const { return ptr_; } + + template + T const* data() const { + return (T const*)(elfBase() + offset_); + } + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +struct Symbol final { + constexpr static uint8_t kFunc = 0x02; // STT_FUNC (code object) + + ELF* elf_{}; + std::byte const* ptr_{}; + uintptr_t nameIndex_{}; + uint32_t type_{}; + uintptr_t value_{}; + + Symbol() = default; + Symbol(Symbol const&) = default; + Symbol& operator=(Symbol const& rhs) { return *new (this) Symbol(rhs); } + + operator bool() { return ptr_; } + + bool isCode() const { return type_ == kFunc; } + + template + Symbol(ELF* elf, S* sym) + : elf_(elf), ptr_((std::byte const*)sym), nameIndex_(sym->name), type_(0x0f & sym->info), value_(sym->value) {} + + std::byte const* elfBase() const; + std::string_view name() const; +}; + +/** Represents one of the ELF's `strtab`s. This is a block of string data, with strings appended one after another, and + * NUL-terminated. Strings are indexed according to their starting offset. At offset 0 is typically an empty string. + */ +struct StringTable { + std::string_view data_{}; + + StringTable() = default; + + /* implicit */ StringTable(Section const& sec) : data_(sec.data(), sec.size_) {} + + operator bool() { return data_.size(); } + + std::string_view at(size_t index) { + auto* ret = data_.data() + index; + return {ret, strlen(ret)}; + } +}; + +/** Encapsulates an ELF image specified by byte-address (e.g. from an mmapped file or a program image or shared object + * in memory). If given a supported ELF image, this will test true with `operator bool` to indicate it is supported and + * was able to parse some basic information from the header. */ +struct ELF { + Header header_{}; + Section (*makeSection_)(ELF*, std::byte const*){}; + Symbol (*makeSymbol_)(ELF*, std::byte const*){}; + size_t secSize_{}; + size_t symSize_{}; + StringTable nametab_{}; + Section symtab_{}; + StringTable strtab_{}; + size_t symCount_{}; + + static Section makeSection32(ELF* elf, std::byte const* ptr) { return Section(elf, (Section32 const*)ptr); } + static Section makeSection64(ELF* elf, std::byte const* ptr) { return Section(elf, (Section64 const*)ptr); } + static Symbol makeSymbol32(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol32 const*)ptr); } + static Symbol makeSymbol64(ELF* elf, std::byte const* ptr) { return Symbol(elf, (Symbol64 const*)ptr); } + + operator bool() { return header_; } + + explicit ELF(std::byte const* image) { + auto* p = (uint8_t const*)image; + // Bytes 0..3: magic bytes: 0x7F, 'E', 'L', 'F' + if (*p++ == 0x7f && *p++ == 0x45 && *p++ == 0x4c && *p++ == 0x46) { + auto klass = *p++; // Byte 4 (EI_CLASS): ELF class, 32- or 64-bit (0x01 or 0x02) + auto dataFormat = *p++; // Byte 5 (EI_DATA): (0x01) little- or (0x02) big-endian + auto fileVersion = *p++; // Byte 6 (EI_VERSION): ELF version: expect 1 (latest ELF version) + constexpr static uint16_t kEndianTestWord{0x0201}; + auto hostEndianness = *(uint8_t const*)&kEndianTestWord; + if (dataFormat == hostEndianness && fileVersion == 1) { + if (klass == 0x01) { + header_ = Header((Header32 const*)image); + makeSection_ = makeSection32; + makeSymbol_ = makeSymbol32; + secSize_ = sizeof(Section32); + symSize_ = sizeof(Symbol32); + } else if (klass == 0x02) { + header_ = Header((Header64 const*)image); + makeSection_ = makeSection64; + makeSymbol_ = makeSymbol64; + secSize_ = sizeof(Section64); + symSize_ = sizeof(Symbol64); + } + } + } + if (*this) { + nametab_ = section(header_.shstrndx_); + eachSection([&](auto& sec) mutable -> bool { + if (sec.type_ == Section::kSymTab && sec.name() == ".symtab") { + symtab_ = sec; + } else if (sec.type_ == Section::kStrTab && sec.name() == ".strtab") { + strtab_ = sec; + } + return !symtab_ || !strtab_; + }); + } + if (symtab_) { + symCount_ = symtab_.size_ / symSize_; + } + } + + Section section(size_t index) { + auto* addr = header_.ptr_ + header_.shoff_ + (index * secSize_); + return makeSection_(this, addr); + } + + Symbol symbol(size_t index) { + auto* addr = symtab_.data() + (index * symSize_); + return makeSymbol_(this, addr); + } + + template + using CB = std::function; + + void eachSection(CB
cb) { + for (size_t i = 0; i < header_.shnum_ && cb(section(i)); i++) + ; + } + + void eachSymbol(CB cb) { + for (size_t i = 0; i < symCount_ && cb(symbol(i)); i++) + ; + } + + Symbol getSym(uintptr_t addr) { + Symbol ret{}; + eachSymbol([&](auto& sym) -> bool { + if (sym.value_ <= addr && sym.value_ > ret.value_) { + ret = sym; + } + return true; + }); + return ret; + } +}; + +inline std::byte const* Section::elfBase() const { return elf_->header_.ptr_; } +inline std::byte const* Symbol::elfBase() const { return elf_->header_.ptr_; } + +inline std::string_view Section::name() const { return elf_->nametab_.at(nameIndex_); } +inline std::string_view Symbol::name() const { return elf_->strtab_.at(nameIndex_); } + +} // namespace elf + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __linux__ + +#endif // _LIBCPP_STACKTRACE_LINUX_IMPL diff --git a/libcxx/src/stacktrace/macos/impl.cpp b/libcxx/src/stacktrace/macos/impl.cpp new file mode 100644 index 0000000000000..312378696b33e --- /dev/null +++ b/libcxx/src/stacktrace/macos/impl.cpp @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/macos/impl.h" + +#if defined(__APPLE__) + +# include +# include +# include +# include +# include + +# include <__stacktrace/base.h> + +# include "stacktrace/utils/image.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +void ident_module(alloc& alloc, entry_base& entry, unsigned& index, image* images) { + if (entry.__addr_actual_) { + while (images[index].loaded_at_ > entry.__addr_actual_) { + --index; + } + while (images[index + 1].loaded_at_ <= entry.__addr_actual_) { + ++index; + } + auto& image = images[index]; + if (image) { + entry.__addr_unslid_ = entry.__addr_actual_ - images[index].slide_; + entry.__file_ = alloc.make_str(images[index].name_); + } + } +} + +bool enum_modules(unsigned& count, auto& images) { + count = std::min(image::kMaxImages, size_t(_dyld_image_count())); + for (size_t i = 0; i < count; i++) { + auto& image = images[i]; + image.slide_ = _dyld_get_image_vmaddr_slide(i); + image.loaded_at_ = uintptr_t(_dyld_get_image_header(i)); + image.name_ = _dyld_get_image_name(i); + } + images[count++] = {0uz, 0}; // sentinel at low end + images[count++] = {~0uz, 0}; // sentinel at high end + std::sort(images.begin(), images.begin() + count - 1); + return true; +} + +void macos::ident_modules() { + static unsigned imageCount; + static std::array images; + static bool atomicInitialized = enum_modules(imageCount, images); + (void)atomicInitialized; + + // Aside from the left/right sentinels in the array (hence the 2), + // are there any other real images? + if (imageCount <= 2) { + return; + } + + // First image (the main program) is at index 1 + builder_.__main_prog_path_ = builder_.__alloc_.make_str(images.at(1).name_); + + unsigned index = 1; // Starts at one, and is moved by 'ident_module' + for (auto& entry : builder_.__entries_) { + ident_module(builder_.__alloc_, (entry_base&)entry, index, images.data()); + } +} + +void symbolize_entry(alloc& alloc, entry_base& entry) { + Dl_info info; + if (dladdr((void*)entry.__addr_actual_, &info)) { + if (info.dli_fname && entry.__file_->empty()) { + // provide at least the binary filename in case we cannot lookup source location + entry.__file_ = alloc.make_str(info.dli_fname); + } + if (info.dli_sname && entry.__desc_->empty()) { + // provide at least the mangled name; try to unmangle in a later step + entry.__desc_ = alloc.make_str(info.dli_sname); + } + } +} + +void macos::symbolize() { + for (auto& entry : builder_.__entries_) { + symbolize_entry(builder_.__alloc_, (entry_base&)entry); + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __APPLE__ diff --git a/libcxx/src/stacktrace/macos/impl.h b/libcxx/src/stacktrace/macos/impl.h new file mode 100644 index 0000000000000..42b8b89d7bbb9 --- /dev/null +++ b/libcxx/src/stacktrace/macos/impl.h @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_MACOS_IMPL +#define _LIBCPP_STACKTRACE_MACOS_IMPL + +#include <__config> +#include <__config_site> +#include +#include + +#include "stacktrace/config.h" +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct macos { + builder& builder_; + +#if defined(__APPLE__) + // defined in macos.cpp + void ident_modules(); + void symbolize(); +#else + // inline-able dummy definitions + void ident_modules() {} + void symbolize() {} +#endif // __APPLE__ +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_MACOS_IMPL diff --git a/libcxx/src/stacktrace/to_string.cpp b/libcxx/src/stacktrace/to_string.cpp new file mode 100644 index 0000000000000..6d02b1c973344 --- /dev/null +++ b/libcxx/src/stacktrace/to_string.cpp @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> +#include <__config_site> + +#include +#include +#include +#include +#include + +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> +#include <__stacktrace/to_string.h> + +_LIBCPP_BEGIN_NAMESPACE_STD + +// `to_string`-related non-member functions + +_LIBCPP_EXPORTED_FROM_ABI string to_string(const stacktrace_entry& __entry) { + return __stacktrace::__to_string()(__entry); +} + +_LIBCPP_EXPORTED_FROM_ABI ostream& operator<<(ostream& __os, const stacktrace_entry& __entry) { + __stacktrace::__to_string()(__os, __entry); + return __os; +} + +namespace __stacktrace { + +/* + * `to_string` Helpers + */ + +_LIBCPP_EXPORTED_FROM_ABI void __to_string::operator()(ostream& __os, std::stacktrace_entry const& entry) { + // Although 64-bit addresses are 16 nibbles long, they're often <= 0x7fff_ffff_ffff + constexpr static int __k_addr_width = (sizeof(void*) > 4) ? 12 : 8; + + __os << "0x" << std::hex << std::setfill('0') << std::setw(__k_addr_width) << entry.native_handle(); + if (!entry.description().empty()) { + __os << ": " << entry.description(); + } + if (!entry.source_file().empty()) { + __os << ": " << entry.source_file(); + } + if (entry.source_line()) { + __os << ":" << std::dec << entry.source_line(); + } +} + +_LIBCPP_EXPORTED_FROM_ABI void +__to_string::operator()(ostream& __os, std::stacktrace_entry const* __entries, size_t __count) { + /* + * Print each entry as a line, as per `operator()`, with additional whitespace + * at the start of the line, and only a newline added at the end: + * + * frame 1: 0xbeefbeefbeef: _symbol_name: /path/to/file.cc:123 + */ + if (!__count) { + __os << "(empty stacktrace)"; + } else { + for (size_t __i = 0; __i < __count; __i++) { + if (__i) { + // Insert newlines between entries (but not before the first or after the last) + __os << std::endl; + } + __os << " frame " << std::setw(3) << std::setfill(' ') << std::dec << (__i + 1) << ": "; + (*this)(__os, __entries[__i]); + } + } +} + +_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const& entry) { + stringstream __ss; + (*this)(__ss, entry); + return __ss.str(); +} + +_LIBCPP_EXPORTED_FROM_ABI string __to_string::operator()(std::stacktrace_entry const* __entries, size_t __count) { + stringstream __ss; + (*this)(__ss, __entries, __count); + return __ss.str(); +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/tools/tools.cpp b/libcxx/src/stacktrace/tools/tools.cpp new file mode 100644 index 0000000000000..aa573567377e8 --- /dev/null +++ b/libcxx/src/stacktrace/tools/tools.cpp @@ -0,0 +1,382 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/config.h" + +#if defined(_LIBCPP_STACKTRACE_CAN_SPAWN_TOOLS) + +# include <__config> +# include <__config_site> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include <__stacktrace/base.h> +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> + +# include "stacktrace/tools/tools.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +_LIBCPP_HIDE_FROM_ABI alloc::str hex_string(alloc& alloc, uintptr_t __addr) { + char __ret[19]; // "0x" + 16 digits + NUL + auto __size = snprintf(__ret, sizeof(__ret), "0x%016llx", (unsigned long long)__addr); + return alloc.make_str(__ret, size_t(__size)); +} + +_LIBCPP_HIDE_FROM_ABI alloc::str u64_string(alloc& alloc, uintptr_t __val) { + char __ret[21]; // 20 digits max + NUL + auto __size = snprintf(__ret, sizeof(__ret), "%zu", __val); + return alloc.make_str(__ret, size_t(__size)); +} + +# define STRINGIFY0(x) #x +# define STRINGIFY(x) STRINGIFY0(x) + +void try_tools(alloc& alloc, function cb) { + char const* prog_name; + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"))) { + if (cb(llvm_symbolizer{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH) + if (cb(llvm_symbolizer{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH)})) { + return; + } +# else + if (cb(llvm_symbolizer{alloc})) { + return; + } +# endif + } + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"))) { + if (cb(addr2line{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH) + if (cb(addr2line{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH)})) { + return; + } +# else + if (cb(addr2line{alloc})) { + return; + } +# endif + } + + if ((prog_name = getenv("LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"))) { + if (cb(atos{alloc, prog_name})) { + return; + } + } else { +# if defined(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH) + if (cb(atos{alloc, STRINGIFY(LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH)})) { + return; + } +# else + if (cb(atos{alloc})) { + return; + } +# endif + } +} + +} // namespace + +void spawner::resolve_lines() { + try_tools(builder_.__alloc_, [&](tool const& prog) { + char buf[512]; + pspawn_tool proc(prog, builder_, buf, sizeof(buf)); + try { + proc.run(); + return true; + } catch (failed const& failed) { + debug() << failed.what(); + if (failed.errno_) { + debug() << " (" << failed.errno_ << " " << strerror(failed.errno_) << ')'; + } + debug() << '\n'; + } + return false; + }); +} + +alloc::list llvm_symbolizer::buildArgs(builder& builder) const { + auto ret = alloc_.make_list(); + ret.push_back(alloc_.make_str(progName_)); + ret.push_back(alloc_.make_str("--demangle")); + ret.push_back(alloc_.make_str("--no-inlines")); + ret.push_back(alloc_.make_str("--verbose")); + ret.push_back(alloc_.make_str("--relativenames")); + ret.push_back(alloc_.make_str("--functions=short")); + for (auto& st_entry : builder.__entries_) { + auto& entry = (entry_base&)st_entry; + auto addr_string = hex_string(alloc_, entry.__addr_unslid_); + if (entry.__file_) { + auto arg = alloc_.make_str(); + arg.reserve(entry.__file_->size() + 40); + arg += "FILE:"; + arg += *entry.__file_; + arg += " "; + arg += addr_string; + ret.push_back(arg); + } else { + ret.push_back(addr_string); + } + } + return ret; +} + +void llvm_symbolizer::parseOutput(builder& builder, __stacktrace::entry_base& entry, std::istream& output) const { + // clang-format off +/* +With "--verbose", parsing is a little easier, or at least, more reliable; +probably the best solution (until we have a JSON parser). +Example output, verbatim, between the '---' lines: +--- +test1 > + Filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start filename: /data/code/llvm-project/libcxx/test/std/diagnostics/stacktrace/basic.cons.pass.cpp + Function start line: 114 + Function start address: 0x8dd0 + Line: 116 + Column: 14 + +--- +Note that this includes an extra empty line as a terminator. +*/ + // clang-format on + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::string_view tmp; + while (true) { + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + if (!line.starts_with(" ")) { + // The symbol has no leading whitespace, while the other + // lines with "fields" like line, column, filename, etc. + // start with two spaces. + if (line != "??") { + entry.__desc_ = line; + } + } else if (line.starts_with(" Filename:")) { + tmp = line; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??") { + entry.__file_ = alloc.make_str(tmp); + } + } else if (line.starts_with(" Line:")) { + tmp = line; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??" && tmp != "0") { + uint32_t lineno = 0; + auto pos = 0; + while (isdigit(tmp[pos])) { + lineno = lineno * 10 + (tmp[pos++] - '0'); + } + entry.__line_ = lineno; + } + } + } +} + +alloc::list addr2line::buildArgs(builder& builder) const { + auto& alloc = builder.__alloc_; + auto ret = alloc.make_list(); + if (builder.__main_prog_path_.empty()) { + // Should not have reached here but be graceful anyway + ret.push_back(alloc.make_str("/bin/false")); + return ret; + } + + ret.push_back(alloc.make_str(progName_)); + ret.push_back(alloc.make_str("--functions")); + ret.push_back(alloc.make_str("--demangle")); + ret.push_back(alloc.make_str("--basenames")); + ret.push_back(alloc.make_str("--pretty-print")); // This "human-readable form" is easier to parse + ret.push_back(alloc.make_str("-e")); + ret.push_back(builder.__main_prog_path_); + for (auto& entry : builder.__entries_) { + ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_unslid_)); + } + return ret; +} + +void addr2line::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { + // clang-format off +/* +Example: +-- +llvm-addr2line -e foo --functions --demangle --basenames --pretty-print --no-inlines 0x11a0 0x1120 0x3d58 0x1284 + +Output: (1 line per input address) +-- +main at foo.cc:15 +register_tm_clones at crtstuff.c:0 +GCC_except_table2 at foo.cc:0 +test::Foo::Foo(int) at foo.cc:11 +*/ + // clang-format on + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + // Split at the sequence " at ". Barring weird symbols + // having " at " in them, this should work. + auto sepIndex = line.find(" at "); + if (sepIndex == std::string::npos) { + return; + } + if (sepIndex > 0) { + entry.__desc_ = alloc.make_str(string_view(line).substr(0, sepIndex)); + } + auto fileBegin = sepIndex + 4; + if (fileBegin >= line.size()) { + return; + } + auto fileline = alloc.make_str(string_view(line).substr(fileBegin)); + auto colon = fileline.find_last_of(":"); + if (colon > 0 && !fileline.starts_with("?")) { + entry.__file_ = alloc.make_str(string_view(fileline).substr(0, colon)); + } + + if (colon == std::string::npos) { + return; + } + uint32_t lineno = 0; + auto pos = colon; + while (isdigit(fileline[++pos])) { + lineno = lineno * 10 + (fileline[pos] - '0'); + } + entry.__line_ = lineno; +} + +alloc::list atos::buildArgs(builder& builder) const { + auto& alloc = builder.__alloc_; + auto ret = alloc.make_list(); + ret.push_back(alloc.make_str(progName_)); + ret.push_back(alloc.make_str("-p")); + ret.push_back(u64_string(alloc, getpid())); + // TODO(stackcx23): Allow options in env, e.g. LIBCPP_STACKTRACE_OPTIONS=FullPath + // ret.push_back("--fullPath"); + for (auto& entry : builder.__entries_) { + ret.push_back(hex_string(alloc, ((entry_base&)entry).__addr_actual_)); + } + return ret; +} + +void atos::parseOutput(builder& builder, entry_base& entry, std::istream& output) const { + // Simple example: + // + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + // + // Assuming this is always atos's format (except when it returns empty lines) + // we can split the string like so: + // + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + // ^^^^-----^^^^^^^^---^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^- + // sym module filename line + // + // Note that very strange filenames or module names can confuse this. + // We'll do the best we can for a decent result, while definitely ensuring safety + // (i.e. careful with our bound-checking). + // + // Another more interesting example (with an added newline for legibility): + // + // std::__1::basic_ios>::fill[abi:ne190107]() const (in testprog) + // (/opt/homebrew/Cellar/llvm/19.1.7_1/bin/../include/c++/v1/ios:0 + // + // If this more or less fits our expected format we'll take these data, + // even if the line number is 0. + + auto& alloc = builder.__alloc_; + auto line = alloc.make_str(); + line.reserve(512); + std::getline(output, line); + while (!line.empty() && isspace(line.back())) { + line.pop_back(); + } + if (line.empty()) { + return; + } + auto buf = line.data(); + auto size = line.size(); + + auto* end = buf + size; + auto* symEnd = strstr(buf, " (in "); + if (!symEnd) { + return; + } + auto* modBegin = symEnd + 5; + auto* modEnd = strstr(modBegin, ") ("); + if (!modEnd) { + return; + } + auto* fileBegin = modEnd + 3; // filename starts just after that + if (fileBegin >= end) { + return; + } + auto const* lastColon = fileBegin; // we'll search for last colon after filename + char const* nextColon; + while ((nextColon = strstr(lastColon + 1, ":"))) { // skip colons in filename (e.g. in "C:\foo.cpp") + lastColon = nextColon; + } + + std::string_view sym{buf, size_t(symEnd - buf)}; + // In case a previous step could not obtain the symbol name, + // we have the name provided by atos; only use that if we have no symbol + // (no need to copy more strings otherwise). + if (entry.__desc_->empty() && !sym.empty()) { + entry.__desc_ = alloc.make_str(sym); + } + + std::string_view file{fileBegin, size_t(lastColon - fileBegin)}; + if (file != "?" && file != "??" && !file.empty()) { + entry.__file_ = alloc.make_str(file); + } + + unsigned lineno = 0; + for (auto* digit = lastColon + 1; digit < end && isdigit(*digit); ++digit) { + lineno = (lineno * 10) + unsigned(*digit - '0'); + } + entry.__line_ = lineno; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/tools.h b/libcxx/src/stacktrace/tools/tools.h new file mode 100644 index 0000000000000..620a1e2f4f9a9 --- /dev/null +++ b/libcxx/src/stacktrace/tools/tools.h @@ -0,0 +1,197 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_TOOLS_H +#define _LIBCPP_STACKTRACE_TOOLS_H + +#include <__config> +#include <__config_site> +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> + +#include "stacktrace/utils/debug.h" +#include "stacktrace/utils/failed.h" +#include "stacktrace/utils/fd.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct tool { + alloc& alloc_; + char const* progName_; + + tool(alloc& alloc, char const* progName) : alloc_(alloc), progName_(progName) {} + virtual ~tool() = default; + + /** Construct complete `argv` for the spawned process. + Includes the program name at argv[0], followed by flags */ + virtual alloc::list buildArgs(builder& trace) const = 0; + + /** Parse line(s) output by the tool, and modify `entry`. */ + virtual void parseOutput(builder& trace, entry_base& entry, std::istream& output) const = 0; +}; + +struct llvm_symbolizer : tool { + virtual ~llvm_symbolizer() = default; + explicit llvm_symbolizer(alloc& alloc) : llvm_symbolizer(alloc, "llvm_symbolizer") {} + llvm_symbolizer(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; +}; + +struct addr2line : tool { + virtual ~addr2line() = default; + explicit addr2line(alloc& alloc) : addr2line(alloc, "addr2line") {} + addr2line(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& stream) const override; +}; + +struct atos : tool { + virtual ~atos() = default; + explicit atos(alloc& alloc) : atos(alloc, "atos") {} + atos(alloc& alloc, char const* progName) : tool{alloc, progName} {} + alloc::list buildArgs(builder& trace) const override; + void parseOutput(builder& trace, entry_base& entry, std::istream& output) const override; +}; + +struct file_actions { + posix_spawn_file_actions_t fa_; + + file_actions() { + if (posix_spawn_file_actions_init(&fa_)) { + throw failed("posix_spawn_file_actions_init", errno); + } + } + + ~file_actions() { posix_spawn_file_actions_destroy(&fa_); } + + void addClose(int fd) { + if (posix_spawn_file_actions_addclose(&fa_, fd)) { + throw failed("posix_spawn_file_actions_addclose", errno); + } + } + void addDup2(int fd, int stdfd) { + if (posix_spawn_file_actions_adddup2(&fa_, fd, stdfd)) { + throw failed("posix_spawn_file_actions_adddup2", errno); + } + } + + fd redirectOutFD() { + int fds[2]; + if (::pipe(fds)) { + throw failed("pipe", errno); + } + addClose(fds[0]); + addDup2(fds[1], 1); + return {fds[0]}; + } + + void redirectInNull() { addDup2(fd::null_fd(), 0); } + void redirectOutNull() { addDup2(fd::null_fd(), 1); } + void redirectErrNull() { addDup2(fd::null_fd(), 2); } +}; + +struct pspawn { + tool const& tool_; + pid_t pid_{0}; + file_actions fa_{}; + + // TODO(stacktrace23): ignore SIGCHLD for spawned subprocess + + ~pspawn() { + if (pid_) { + kill(pid_, SIGTERM); + wait(); + } + } + + void spawn(alloc::list const& argStrings) { + alloc::vec argv = tool_.alloc_.make_vec(); + argv.reserve(argStrings.size() + 1); + for (auto const& str : argStrings) { + argv.push_back(str.data()); + } + argv.push_back(nullptr); + int err; + if ((err = posix_spawnp(&pid_, argv[0], &fa_.fa_, nullptr, const_cast(argv.data()), nullptr))) { + throw failed("posix_spawnp", err); + } + } + + int wait() { + int status; + waitpid(pid_, &status, 0); + return status; + } +}; + +struct pspawn_tool : pspawn { + builder& builder_; + fd fd_; + fd_streambuf buf_; + fd_istream stream_; + + pspawn_tool(tool const& a2l, builder& trace, char* buf, size_t size) + : pspawn{a2l}, builder_(trace), fd_(fa_.redirectOutFD()), buf_(fd_, buf, size), stream_(buf_) { + if (!debug::enabled()) { + fa_.redirectErrNull(); + } + fa_.redirectInNull(); + } + + void run() { + // Cannot run "addr2line" or similar without addresses, since we + // provide them in argv, and if there are none passed in argv, the + // tool will try to read from stdin and hang. + if (builder_.__entries_.empty()) { + return; + } + + auto argStrings = tool_.buildArgs(builder_); + if (debug::enabled()) { + debug() << "Trying to get stacktrace using:"; + for (auto& str : argStrings) { + debug() << " \"" << str << '"'; + } + debug() << '\n'; + } + + spawn(argStrings); + + auto end = builder_.__entries_.end(); + auto it = builder_.__entries_.begin(); + while (it != end) { + auto& entry = (entry_base&)(*it++); + tool_.parseOutput(builder_, entry, stream_); + } + } +}; + +struct spawner { + builder& builder_; + void resolve_lines(); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_TOOLS_H diff --git a/libcxx/src/stacktrace/unwind/impl.cpp b/libcxx/src/stacktrace/unwind/impl.cpp new file mode 100644 index 0000000000000..90603e0422b9b --- /dev/null +++ b/libcxx/src/stacktrace/unwind/impl.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/config.h" + +#if defined(_LIBCPP_STACKTRACE_UNWIND_IMPL) + +# include + +# include "stacktrace/unwind/impl.h" +# include <__stacktrace/basic.h> +# include <__stacktrace/entry.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind_backtrace { + builder& builder_; + size_t skip_; + size_t maxDepth_; + + _Unwind_Reason_Code callback(_Unwind_Context* ucx) { + if (skip_) { + --skip_; + return _Unwind_Reason_Code::_URC_NO_REASON; + } + if (!maxDepth_) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + --maxDepth_; + int ipBefore; + auto ip = _Unwind_GetIPInfo(ucx, &ipBefore); + if (!ip) { + return _Unwind_Reason_Code::_URC_NORMAL_STOP; + } + auto& entry = builder_.__entries_.emplace_back(); + auto& eb = (entry_base&)entry; + eb.__addr_actual_ = (ipBefore ? ip : ip - 1); + eb.__addr_unslid_ = eb.__addr_actual_; // in case we can't un-slide + return _Unwind_Reason_Code::_URC_NO_REASON; + } + + static _Unwind_Reason_Code callback(_Unwind_Context* cx, void* self) { + return ((unwind_backtrace*)self)->callback(cx); + } +}; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void unwind::collect(size_t skip, size_t max_depth) { + unwind_backtrace bt{builder_, skip + 1, max_depth}; // skip this call as well + _Unwind_Backtrace(unwind_backtrace::callback, &bt); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/unwind/impl.h b/libcxx/src/stacktrace/unwind/impl.h new file mode 100644 index 0000000000000..4b552349d75bc --- /dev/null +++ b/libcxx/src/stacktrace/unwind/impl.h @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UNWIND_H +#define _LIBCPP_STACKTRACE_UNWIND_H + +#include <__config> +#include <__config_site> +#include +#include + +#include <__stacktrace/base.h> +#include <__stacktrace/basic.h> +#include <__stacktrace/entry.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind { + builder& builder_; + void collect(size_t skip, size_t max_depth); +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UNWIND_H diff --git a/libcxx/src/stacktrace/utils/debug.h b/libcxx/src/stacktrace/utils/debug.h new file mode 100644 index 0000000000000..cc88224942f6f --- /dev/null +++ b/libcxx/src/stacktrace/utils/debug.h @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_DEBUG +#define _LIBCPP_STACKTRACE_UTILS_DEBUG + +#include <__config> +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/** Debug-message output stream. If `LIBCXX_STACKTRACE_DEBUG` is defined in the environment +or as a macro with exactly the string `1` then this is enabled (prints to `std::cerr`); +otherwise its does nothing by returning a dummy stream. */ +struct _LIBCPP_HIDE_FROM_ABI debug : std::ostream { + _LIBCPP_HIDE_FROM_ABI virtual ~debug() = default; + + _LIBCPP_HIDE_FROM_ABI static bool enabled() { +#if defined(LIBCXX_STACKTRACE_DEBUG) && LIBCXX_STACKTRACE_DEBUG == 1 + return true; +#else + static bool ret = [] { + auto const* val = getenv("LIBCXX_STACKTRACE_DEBUG"); + return val && !strncmp(val, "1", 1); + }(); + return ret; +#endif + } + + /** No-op output stream. */ + struct _LIBCPP_HIDE_FROM_ABI dummy_ostream final : std::ostream { + _LIBCPP_HIDE_FROM_ABI virtual ~dummy_ostream() = default; + friend std::ostream& operator<<(dummy_ostream& bogus, auto const&) { return bogus; } + }; + + friend std::ostream& operator<<(debug& dp, auto const& val) { + static dummy_ostream kdummy; + if (!enabled()) { + return kdummy; + } + std::cerr << val; + return std::cerr; + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_DEBUG diff --git a/libcxx/src/stacktrace/utils/failed.h b/libcxx/src/stacktrace/utils/failed.h new file mode 100644 index 0000000000000..3f8a24dd88eb6 --- /dev/null +++ b/libcxx/src/stacktrace/utils/failed.h @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_FAILED +#define _LIBCPP_STACKTRACE_UTILS_FAILED + +#include <__config> +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct failed : std::runtime_error { + virtual ~failed() = default; + int errno_{0}; + failed() : std::runtime_error({}) {} + failed(char const* msg, int err) : std::runtime_error(msg), errno_(err) {} +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_FAILED diff --git a/libcxx/src/stacktrace/utils/fd.h b/libcxx/src/stacktrace/utils/fd.h new file mode 100644 index 0000000000000..f4be1876e5d37 --- /dev/null +++ b/libcxx/src/stacktrace/utils/fd.h @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_FD +#define _LIBCPP_STACKTRACE_UTILS_FD + +#include <__config> +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stacktrace/utils/failed.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/** Encapsulates a plain old file descriptor `int`. Avoids copies in order to +force some component to "own" this, although it's freely convertible back to +integer form. Default-constructed, closed, and moved-out-of instances will have +the invalid fd `-1`. */ +class _LIBCPP_HIDE_FROM_ABI fd { + int fd_{-1}; + +public: + fd() : fd(-1) {} + fd(int fdint) : fd_(fdint) {} + + fd(fd const&) = delete; + fd& operator=(fd const&) = delete; + + fd(fd&& rhs) { + if (&rhs != this) { + std::exchange(fd_, rhs.fd_); + } + } + + fd& operator=(fd&& rhs) { return *new (this) fd(std::move(rhs)); } + + ~fd() { close(); } + + /** Returns true IFF fd is above zero */ + bool valid() const { return fd_ > 0; } + + /* implicit */ operator int() const { return fd_; } + + void close() { + int fd_old = -1; + std::exchange(fd_old, fd_); + if (fd_old != -1) { + ::close(fd_old); + } + } + + static fd& null_fd() { + static fd ret = {::open("/dev/null", O_RDWR)}; + return ret; + } + + static fd open(std::string_view path) { + fd ret = {::open(path.data(), O_RDONLY)}; + return ret; + } +}; + +/** Wraps a readable fd using the `streambuf` interface. I/O errors arising +from reading the provided fd will result in a `Failed` being thrown. */ +struct _LIBCPP_HIDE_FROM_ABI fd_streambuf final : std::streambuf { + fd& fd_; + char* buf_; + size_t size_; + _LIBCPP_HIDE_FROM_ABI fd_streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} + _LIBCPP_HIDE_FROM_ABI virtual ~fd_streambuf() = default; + + _LIBCPP_HIDE_FROM_ABI int underflow() override { + int bytesRead = ::read(fd_, buf_, size_); + if (bytesRead < 0) { + throw ::std::__stacktrace::failed("I/O error reading from child process", errno); + } + if (bytesRead == 0) { + return traits_type::eof(); + } + setg(buf_, buf_, buf_ + bytesRead); + return int(*buf_); + } +}; + +/** Wraps an `FDInStreamBuffer` in an `istream` */ +struct fd_istream final : std::istream { + fd_streambuf& buf_; + _LIBCPP_HIDE_FROM_ABI virtual ~fd_istream() = default; + _LIBCPP_HIDE_FROM_ABI explicit fd_istream(fd_streambuf& buf) : std::istream(nullptr), buf_(buf) { rdbuf(&buf_); } +}; + +struct fd_mmap final { + fd fd_{}; + size_t size_{0}; + std::byte const* addr_{nullptr}; + + _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(std::string_view path) : fd_mmap(fd::open(path)) {} + + _LIBCPP_HIDE_FROM_ABI explicit fd_mmap(fd&& fd) : fd_(std::move(fd)) { + if (fd_) { + if ((size_ = ::lseek(fd, 0, SEEK_END))) { + addr_ = (std::byte const*)::mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); + } + } + } + + _LIBCPP_HIDE_FROM_ABI operator bool() const { return addr_; } + + _LIBCPP_HIDE_FROM_ABI ~fd_mmap() { + if (addr_) { + ::munmap(const_cast((void const*)addr_), size_); + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_FD diff --git a/libcxx/src/stacktrace/utils/image.h b/libcxx/src/stacktrace/utils/image.h new file mode 100644 index 0000000000000..21119d7a66ec9 --- /dev/null +++ b/libcxx/src/stacktrace/utils/image.h @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_UTILS_IMAGE +#define _LIBCPP_STACKTRACE_UTILS_IMAGE + +#include <__config> +#include <__stacktrace/base.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct image { + constexpr static size_t kMaxImages = 256; + + uintptr_t loaded_at_{}; + intptr_t slide_{}; + std::string_view name_{}; + bool is_main_prog_{}; + + bool operator<(image const& rhs) const { return loaded_at_ < rhs.loaded_at_; } + operator bool() const { return !name_.empty(); } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_UTILS_IMAGE diff --git a/libcxx/src/stacktrace/windows/dll.cpp b/libcxx/src/stacktrace/windows/dll.cpp new file mode 100644 index 0000000000000..d48bead956041 --- /dev/null +++ b/libcxx/src/stacktrace/windows/dll.cpp @@ -0,0 +1,247 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <__config> + +#if defined(_LIBCPP_WIN32API) + +# include <__stacktrace/base.h> + +# include "stacktrace/alloc.h" +# include "stacktrace/config.h" +# include "stacktrace/utils.h" +# include "stacktrace/windows/dll.h" +# include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +/* +Global objects, shared among all threads and among all stacktrace operations. +The `dbghelp` APIs are not safe to call concurrently (according to their docs) +so we claim a lock in the `WinDebugAPIs` constructor. +*/ + +// Statically-initialized +std::mutex gWindowsAPILock; +DbgHelpDLL dbg; +PSAPIDLL ps; + +// Initialized once, in first WinDebugAPIs construction; +// protected by the above mutex. +HANDLE proc; +HMODULE exe; +IMAGE_NT_HEADERS* ntHeaders; +bool globalInitialized{false}; + +// Globals used across invocations of the functions below. +// Also guarded by the above mutex. +bool symsInitialized{false}; +HMODULE moduleHandles[1024]; +size_t moduleCount; // 0 IFF module enumeration failed + +} // namespace + +win_impl::WinDebugAPIs(builder& trace) : builder_(trace), guard_(gWindowsAPILock) { + if (!globalInitialized) { + // Cannot proceed without these DLLs: + if (!dbg) { + return; + } + if (!ps) { + return; + } + proc = GetCurrentProcess(); + if (!(exe = GetModuleHandle(nullptr))) { + return; + } + if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { + return; + } + + globalInitialized = true; + } + + // Initialize symbol machinery. + // Presumably the symbols in this process's space can change between + // stacktraces, so we'll do this each time we take a trace. + // The final `true` means we want the runtime to enumerate all this + // process's modules' symbol tables. + symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); + DWORD symOptions = (*dbg.SymGetOptions)(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + (*dbg.SymSetOptions)(symOptions); +} + +win_impl::~WinDebugAPIs() { + if (symsInitialized) { + (*dbg.SymCleanup)(proc); + symsInitialized = false; + } +} + +void win_impl::ident_modules() { + if (!globalInitialized) { + return; + } + DWORD needBytes; + auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); + if (enumMods) { + moduleCount = needBytes / sizeof(HMODULE); + } else { + moduleCount = 0; + Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; + } +} + +void win_impl::symbolize() { + // Very long symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t disp{0}; + if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { + // Copy chars into the destination string which uses the caller-provided allocator. + ((entry_base&)entry).__desc_ = {sym->Name}; + } else { + Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +void win_impl::resolve_lines() { + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + DWORD disp{0}; + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { + // Copy chars into the destination string which uses the caller-provided allocator. + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } else { + Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +/* +Inlining is disabled from here on; +this is to ensure `collect` below doesn't get merged into its caller +and mess around with the top of the stack (making `skip` inaccurate). +*/ +# pragma auto_inline(off) + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { + if (!globalInitialized) { + return; + } + + auto thread = GetCurrentThread(); + auto machine = ntHeaders->FileHeader.Machine; + + CONTEXT ccx; + RtlCaptureContext(&ccx); + + STACKFRAME64 frame; + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrPC.Offset = ctrace.Rip; + frame.AddrStack.Offset = ctrace.Rsp; + frame.AddrFrame.Offset = ctrace.Rbp; + + while (max_depth && + (*dbg.StackWalk64)( + machine, + proc, + thread, + &frame, + &ccx, + nullptr, + dbg.SymFunctionTableAccess64, + dbg.SymGetModuleBase64, + nullptr)) { + if (skip) { + --skip; + continue; + } + --max_depth; + auto& entry = builder_.__entries_.emplace_back(); + // We don't need to compute the un-slid addr; windbg only needs the actual addresses. + // Assume address is of the instruction after a call instruction, since we can't + // differentiate between a signal, SEH exception handler, or a normal function call. + entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + } +} + +dll::~dll() { FreeLibrary(module_); } + +dll::dll(char const* name) : name_(name), module_(LoadLibrary(name)) { + if (!module_) { + debug() << "LoadLibrary failed: " << name_ << ": " << GetLastError() << '\n'; + } +} + +dbghelp_dll::~dbghelp_dll() = default; + +dbghelp_dll& dbghelp_dll::get() { + dbghelp_dll ret; + return ret; +} + +dbghelp_dll::dbghelp_dll() : dll("dbghelp.dll") { + // clang-format off +if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } + if (!get_func(&StackWalk64, "StackWalk64")) { return; } + if (!get_func(&SymCleanup, "SymCleanup")) { return; } + if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } + if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } + if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } + if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } + if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } + if (!get_func(&SymInitialize, "SymInitialize")) { return; } + if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } + if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } + valid_ = true; + // clang-format on +} + +psapi_dll::~psapi_dll() = default; + +psapi_dll& psapi_dll::get() { + psapi_dll ret; + return ret; +} + +psapi_dll() : dll("psapi.dll") { + // clang-format off +if (!getFunc(&EnumProcessModules, "EnumProcessModules")) { return; } + if (!getFunc(&GetModuleInformation, "GetModuleInformation")) { return; } + if (!getFunc(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } + valid_ = true; + // clang-format on +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_WIN32API diff --git a/libcxx/src/stacktrace/windows/dll.h b/libcxx/src/stacktrace/windows/dll.h new file mode 100644 index 0000000000000..13ed9e42768d5 --- /dev/null +++ b/libcxx/src/stacktrace/windows/dll.h @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_WIN_DLL +#define _LIBCPP_STACKTRACE_WIN_DLL + +#include <__config> +#if defined(_LIBCPP_WIN32API) + +// windows.h must be first +# include +// other windows-specific headers +# include +# define PSAPI_VERSION 1 +# include + +# include <__config_site> +# include +# include +# include +# include + +# include "stacktrace/utils/debug.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +# if defined(_LIBCPP_WIN32API) + +// clang-format off + +struct dll { + char const* name_; + HMODULE module_; + /** Set to true in subclass's ctor if initialized successfully. */ + bool valid_{false}; + + operator bool() const { return valid_; } + + explicit dll(char const* name) + : name_(name), module_(LoadLibrary(name)) { + if (!module_) { + debug() << "LoadLibrary failed: " + << name_ << ": " << GetLastError() << '\n'; + } + } + + virtual ~dll() { FreeLibrary(module_); } + + template + bool get_func(F* func, char const* name) { + if (!(*func = (F)GetProcAddress(module_, name))) { + debug() << "GetProcAddress failed: " + << name << "' (" << name_ << "): " + << GetLastError() << "\n"; + return false; + } + return true; + } +}; + +struct dbghelp_dll final : dll { + virtual ~dbghelp_dll() = default; + static dbghelp_dll& get() { static dbghelp_dll ret; return ret; } + + IMAGE_NT_HEADERS* (*ImageNtHeader)(void*); + bool (*StackWalk64) (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, void*, void*, void*, void*); + bool (*SymCleanup) (HANDLE); + void* (*SymFunctionTableAccess64)(HANDLE, DWORD64); + bool (*SymGetLineFromAddr64)(HANDLE, DWORD64, DWORD*, IMAGEHLP_LINE64*); + DWORD64 (*SymGetModuleBase64) (HANDLE, DWORD64); + DWORD (*SymGetOptions) (); + bool (*SymGetSymFromAddr64)(HANDLE, DWORD64, DWORD64*, IMAGEHLP_SYMBOL64*); + bool (*SymInitialize) (HANDLE, char const*, bool); + DWORD64 (*SymLoadModule64) (HANDLE, HANDLE, char const*, char const*, void*, DWORD); + DWORD (*SymSetOptions) (DWORD); + + dbghelp_dll() : dll("dbghelp.dll") { + if (!get_func(&ImageNtHeader, "ImageNtHeader")) { return; } + if (!get_func(&StackWalk64, "StackWalk64")) { return; } + if (!get_func(&SymCleanup, "SymCleanup")) { return; } + if (!get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64")) { return; } + if (!get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64")) { return; } + if (!get_func(&SymGetModuleBase64, "SymGetModuleBase64")) { return; } + if (!get_func(&SymGetOptions, "SymGetOptions")) { return; } + if (!get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64")) { return; } + if (!get_func(&SymInitialize, "SymInitialize")) { return; } + if (!get_func(&SymLoadModule64, "SymLoadModule64")) { return; } + if (!get_func(&SymSetOptions, "SymSetOptions")) { return; } + valid_ = true; + } +}; + +struct psapi_dll final : dll { + virtual ~psapi_dll() = default; + static psapi_dll& get() { static psapi_dll ret; return ret; } + + bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); + bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); + DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); + + psapi_dll() : dll("psapi.dll") { + if (!get_func(&EnumProcessModules, "EnumProcessModules")) { return; } + if (!get_func(&GetModuleInformation, "GetModuleInformation")) { return; } + if (!get_func(&GetModuleBaseName, "GetModuleBaseNameA")) { return; } + valid_ = true; + } +}; + +#endif + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_WIN32API +#endif // _LIBCPP_STACKTRACE_WIN_DLL diff --git a/libcxx/src/stacktrace/windows/impl.cpp b/libcxx/src/stacktrace/windows/impl.cpp new file mode 100644 index 0000000000000..85eb52bd36439 --- /dev/null +++ b/libcxx/src/stacktrace/windows/impl.cpp @@ -0,0 +1,203 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "stacktrace/windows/impl.h" +#include <__config> + +#if defined(_LIBCPP_WIN32API) +// windows.h must be first +# include +// other windows-specific headers +# include +# define PSAPI_VERSION 1 +# include + +# include + +# include "stacktrace/utils/debug.h" +# include "stacktrace/windows/dll.h" +# include "stacktrace/windows/impl.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { + +/* +Global objects, shared among all threads and among all stacktrace operations. +The `dbghelp` APIs are not safe to call concurrently (according to their docs) +so we claim a lock in the `WinDebugAPIs` constructor. +*/ + +// Statically-initialized +DbgHelpDLL dbg; +PSAPIDLL ps; + +// Initialized once, in first WinDebugAPIs construction; +// protected by the above mutex. +HANDLE proc; +HMODULE exe; +IMAGE_NT_HEADERS* ntHeaders; +bool globalInitialized{false}; + +// Globals used across invocations of the functions below. +// Also guarded by the above mutex. +bool symsInitialized{false}; +HMODULE moduleHandles[1024]; +size_t moduleCount; // 0 IFF module enumeration failed + +} // namespace + +win_impl::global_init() { + if (!globalInitialized) { + // Cannot proceed without these DLLs: + if (!dbg) { + return; + } + if (!ps) { + return; + } + proc = GetCurrentProcess(); + if (!(exe = GetModuleHandle(nullptr))) { + return; + } + if (!(ntHeaders = (*dbg.ImageNtHeader)(exe))) { + return; + } + + globalInitialized = true; + } + + // Initialize symbol machinery. + // Presumably the symbols in this process's space can change between + // stacktraces, so we'll do this each time we take a trace. + // The final `true` means we want the runtime to enumerate all this + // process's modules' symbol tables. + symsInitialized = (*dbg.SymInitialize)(proc, nullptr, true); + DWORD symOptions = (*dbg.SymGetOptions)(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + (*dbg.SymSetOptions)(symOptions); +} + +win_impl::~win_impl() { + if (symsInitialized) { + (*dbg.SymCleanup)(proc); + symsInitialized = false; + } +} + +void win_impl::ident_modules() { + if (!globalInitialized) { + return; + } + DWORD needBytes; + auto enumMods = (*ps.EnumProcessModules)(proc, moduleHandles, sizeof(moduleHandles), LPDWORD(&needBytes)); + if (enumMods) { + moduleCount = needBytes / sizeof(HMODULE); + } else { + moduleCount = 0; + Debug() << "EnumProcessModules failed: " << GetLastError() << '\n'; + } +} + +void win_impl::symbolize() { + // Very long symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t disp{0}; + if ((*dbg.SymGetSymFromAddr64)(proc, entry.__addr_actual_, &disp, sym)) { + // Copy chars into the destination string which uses the caller-provided allocator. + ((entry_base&)entry).__desc_ = {sym->Name}; + } else { + Debug() << "SymGetSymFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +void win_impl::resolve_lines() { + if (!globalInitialized) { + return; + } + + for (auto& entry : builder_.__entries_) { + DWORD disp{0}; + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbg.SymGetLineFromAddr64)(proc, entry.__addr_actual_, &disp, &line)) { + // Copy chars into the destination string which uses the caller-provided allocator. + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } else { + Debug() << "SymGetLineFromAddr64 failed: " << GetLastError() << '\n'; + } + } +} + +/* +Inlining is disabled from here on; +this is to ensure `collect` below doesn't get merged into its caller +and mess around with the top of the stack (making `skip` inaccurate). +*/ +# pragma auto_inline(off) + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void win_impl::collect(size_t skip, size_t max_depth) { + if (!globalInitialized) { + return; + } + + auto thread = GetCurrentThread(); + auto machine = ntHeaders->FileHeader.Machine; + + CONTEXT ccx; + RtlCaptureContext(&ccx); + + STACKFRAME64 frame; + memset(&frame, 0, sizeof(frame)); + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrPC.Offset = ctrace.Rip; + frame.AddrStack.Offset = ctrace.Rsp; + frame.AddrFrame.Offset = ctrace.Rbp; + + while (max_depth && + (*dbg.StackWalk64)( + machine, + proc, + thread, + &frame, + &ccx, + nullptr, + dbg.SymFunctionTableAccess64, + dbg.SymGetModuleBase64, + nullptr)) { + if (skip) { + --skip; + continue; + } + --max_depth; + auto& entry = builder_.__entries_.emplace_back(); + // We don't need to compute the un-slid addr; windbg only needs the actual addresses. + // Assume address is of the instruction after a call instruction, since we can't + // differentiate between a signal, SEH exception handler, or a normal function call. + entry.__addr_actual_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/windows/impl.h b/libcxx/src/stacktrace/windows/impl.h new file mode 100644 index 0000000000000..b88a3f9d8a3a1 --- /dev/null +++ b/libcxx/src/stacktrace/windows/impl.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_STACKTRACE_WIN_IMPL_H +#define _LIBCPP_STACKTRACE_WIN_IMPL_H + +#include <__config> +#include <__config_site> +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct builder; + +struct win_impl { + builder& builder_; + +#if defined(_LIBCPP_WIN32API) + static std::mutex mutex_; + std::lock_guard guard_; + + explicit win_impl(builder& builder) : builder_(builder), guard_(mutex_) { global_init(); } + ~win_impl(); + + void global_init(); + void collect(size_t skip, size_t max_depth); + void ident_modules(); + void symbolize(); + void resolve_lines(); +#else + void global_init() {} + void collect(size_t, size_t) {} + void ident_modules() {} + void symbolize() {} + void resolve_lines() {} +#endif // _LIBCPP_WIN32API +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_WIN_IMPL_H diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp new file mode 100644 index 0000000000000..f40037d7e46e6 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g0 + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // This is 'nodebug', so we cannot get the source file and line: + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + // But this should at least produce the executable filename + assert(entry.source_file().contains("simple.o0.nodebug.pass.cpp")); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp new file mode 100644 index 0000000000000..2c7fa9416f31f --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp new file mode 100644 index 0000000000000..1f6e2bd557460 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cerr << trace << '\n'; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp new file mode 100644 index 0000000000000..fafb0d618b908 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g0 + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // This is 'nodebug', so we cannot get the source file and line: + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + // But this should at least produce the executable filename + assert(entry.source_file().contains("simple.o3.nodebug.pass.cpp")); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp new file mode 100644 index 0000000000000..3690499ae7c25 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp new file mode 100644 index 0000000000000..4b32e33110d1a --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.cpp @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + // Get the current trace. + // uint32_t line_number = __LINE__ + 1; // record where `current` is being called: + auto trace = std::stacktrace::current(); + std::cout << trace << std::endl; + // First entry of this should be `main`. + auto entry = trace.at(0); + assert(entry); + assert(entry.native_handle()); + assert(entry.description() == "main" || entry.description() == "_main"); + // assert(entry.source_file().ends_with(".pass.cpp")); + // assert(entry.source_line() == line_number); + return 0; +} diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv index cb23c7a98de1b..e67db3ac47cf9 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -926,6 +926,27 @@ stack limits stack stdexcept stack tuple stack version +stacktrace cctype +stacktrace climits +stacktrace compare +stacktrace cstddef +stacktrace cstdint +stacktrace cstdio +stacktrace cstring +stacktrace cwchar +stacktrace cwctype +stacktrace initializer_list +stacktrace iosfwd +stacktrace limits +stacktrace list +stacktrace optional +stacktrace stdexcept +stacktrace string +stacktrace string_view +stacktrace tuple +stacktrace typeinfo +stacktrace utility +stacktrace version stop_token atomic stop_token climits stop_token cstdint diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv index ce8f0261f2b27..5a411936672af 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -926,6 +926,27 @@ stack limits stack stdexcept stack tuple stack version +stacktrace cctype +stacktrace climits +stacktrace compare +stacktrace cstddef +stacktrace cstdint +stacktrace cstdio +stacktrace cstring +stacktrace cwchar +stacktrace cwctype +stacktrace initializer_list +stacktrace iosfwd +stacktrace limits +stacktrace list +stacktrace optional +stacktrace stdexcept +stacktrace string +stacktrace string_view +stacktrace tuple +stacktrace typeinfo +stacktrace utility +stacktrace version stop_token atomic stop_token climits stop_token cstdint diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.cpp new file mode 100644 index 0000000000000..5864d241abf35 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.4) Comparisons [stacktrace.basic.cmp] + + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2a() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2b() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st1a = test1(); // [test1, main, ...] + assert(st1a == st1a); + + auto st1b = st1a; + assert(st1a == st1b); + + auto st2a = test2a(); // [test1, test2a, main, ...] + assert(st1a != st2a); + + std::stacktrace empty; // [] + assert(st1a != empty); + assert(st2a != empty); + + assert(st2a.size() > st1a.size()); + assert(st1a.size() > empty.size()); + + auto st2b = test2b(); // [test1, test2b, main, ...] + assert(st2a.size() == st2b.size()); + assert(st2a != st2b); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.cpp new file mode 100644 index 0000000000000..5664e9bc41db2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.4) Comparisons [stacktrace.basic.cmp] + + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2a() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2b() { return test1(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + /* + Returns: x.size() <=> y.size() if x.size() != y.size(); + lexicographical_compare_three_way(x.begin(), x.end(), y.begin(), y.end()) otherwise. + */ + + auto st1a = test1(); // [test1, main, ...] + auto st1b = st1a; + assert(st1a == st1b); + auto st2a = test2a(); // [test1, test2a, main, ...] + assert(st1a != st2a); + std::stacktrace empty; // [] + auto st2b = test2b(); // [test1, test2b, main, ...] + assert(st2a != st2b); + + // empty: [] + // st1a: [test1, main, ...] + // st1b: [test1, main, ...] (copy of st1a) + // st2a: [test1, test2a, main:X, ...] + // st2b: [test1, test2b, main:Y, ...], Y > X + + assert(std::strong_ordering::equal == empty <=> empty); + assert(std::strong_ordering::less == empty <=> st1a); + assert(std::strong_ordering::less == empty <=> st1b); + assert(std::strong_ordering::less == empty <=> st2a); + assert(std::strong_ordering::less == empty <=> st2b); + + assert(std::strong_ordering::greater == st1a <=> empty); + assert(std::strong_ordering::equal == st1a <=> st1a); + assert(std::strong_ordering::equal == st1a <=> st1b); + assert(std::strong_ordering::less == st1a <=> st2a); + assert(std::strong_ordering::less == st1a <=> st2b); + + assert(std::strong_ordering::greater == st1b <=> empty); + assert(std::strong_ordering::equal == st1b <=> st1a); + assert(std::strong_ordering::equal == st1b <=> st1b); + assert(std::strong_ordering::less == st1b <=> st2a); + assert(std::strong_ordering::less == st1b <=> st2b); + + assert(std::strong_ordering::greater == st2a <=> empty); + assert(std::strong_ordering::greater == st2a <=> st1a); + assert(std::strong_ordering::greater == st2a <=> st1b); + assert(std::strong_ordering::equal == st2a <=> st2a); + assert(std::strong_ordering::less == st2a <=> st2b); + + assert(std::strong_ordering::greater == st2b <=> empty); + assert(std::strong_ordering::greater == st2b <=> st1a); + assert(std::strong_ordering::greater == st2b <=> st1b); + assert(std::strong_ordering::greater == st2b <=> st2a); + assert(std::strong_ordering::equal == st2b <=> st2b); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.cpp new file mode 100644 index 0000000000000..ae37fc11b3089 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy_and_move.cpp @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); +*/ + +#include +#include + +// clang-format off +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_copy_move_ctors() { + using A = std::allocator; + A alloc; + auto st = std::basic_stacktrace::current(alloc); + + auto copy_constr = std::basic_stacktrace(st); + assert(st == copy_constr); + + std::basic_stacktrace copy_assign; + copy_assign = std::basic_stacktrace(st); + assert(st == copy_assign); + + auto st2 = test2(alloc); + assert(st2.size()); + std::basic_stacktrace move_constr(std::move(st2)); + assert(move_constr.size()); + assert(!st2.size()); + + auto st3 = test2(alloc); + assert(st3.size()); + std::basic_stacktrace move_assign; + move_assign = std::move(st3); + assert(move_assign.size()); + assert(!st3.size()); + + // TODO(stacktrace23): should we add test cases with `select_on_container_copy_construction`? +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_copy_move_ctors(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.cpp new file mode 100644 index 0000000000000..3b406f1b16a61 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_default_construct() { + std::stacktrace st; + assert(st.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_default_construct(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.cpp new file mode 100644 index 0000000000000..a49f45245f7d2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + explicit basic_stacktrace(const allocator_type& alloc) noexcept; +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} + +template +struct test_alloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + template + struct rebind { + using other = test_alloc; + }; + + std::allocator wrapped_{}; + + test_alloc() = default; + + template + test_alloc(test_alloc const& rhs) : wrapped_(rhs.wrapped_) {} + + bool operator==(auto const& rhs) const { return &rhs == this; } + bool operator==(test_alloc const&) const { return true; } + + T* allocate(size_t n) { return wrapped_.allocate(n); } + auto allocate_at_least(size_t n) { return wrapped_.allocate_at_least(n); } + void deallocate(T* ptr, size_t n) { return wrapped_.deallocate(ptr, n); } +}; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_construct_with_allocator() { + test_alloc alloc; + std::basic_stacktrace st(alloc); + assert(st.empty()); + + st = std::basic_stacktrace::current(alloc); + assert(!st.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_construct_with_allocator(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.cpp new file mode 100644 index 0000000000000..cfd9ebf87c8a7 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.cpp @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include +#include + +uint32_t test1_line; +uint32_t test2_line; + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::stacktrace::current(); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(); + return ret; +} + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { + auto st = test2(); + assert(st.size() >= 3); + assert(st[0]); + assert(st[0].native_handle()); + assert(st[0].description().contains("test1")); + assert(st[0].source_file().contains("basic.cons.pass.cpp")); + assert(st[1]); + assert(st[1].native_handle()); + assert(st[1].description().contains("test2")); + assert(st[1].source_file().contains("basic.cons.pass.cpp")); + assert(st[2]); + assert(st[2].native_handle()); + assert(st[2].description().contains("test_current")); + assert(st[2].source_file().contains("basic.cons.pass.cpp")); + + // We unfortunately cannot guarantee the following; in CI, and possibly on users' build machines, + // there may not be an up-to-date version of e.g. `addr2line`. + // assert(st[0].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[0].source_line() == test1_line); + // assert(st[1].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[1].source_line() == test2_line); + // assert(st[2].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[2].source_line() == main_line); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.cpp new file mode 100644 index 0000000000000..55877f0a49785 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] +*/ + +#include +#include + +/* + Let t be a stacktrace as-if obtained via basic_stacktrace::current(alloc). Let n be t.size(). + Returns: A basic_stacktrace object where frames_ is direct-non-list-initialized from arguments + t.begin() + min(n, skip), t.end(), and alloc, or an empty basic_stacktrace object if the + initialization of frames_ failed. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip() { + // Use default allocator for simplicity; alloc is covered above + auto st_skip0 = std::stacktrace::current(); + assert(st_skip0.size() >= 2); + auto st_skip1 = std::stacktrace::current(1); + assert(st_skip1.size() >= 1); + assert(st_skip0.size() == st_skip1.size() + 1); + assert(st_skip0[1] == st_skip1[0]); + auto st_skip_many = std::stacktrace::current(1 << 20); + assert(st_skip_many.empty()); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current_with_skip(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.cpp new file mode 100644 index 0000000000000..a999845e92e01 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.cpp @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip_depth() { + // current stack is: [this function, main, (possibly something else, e.g. `_start` from libc)] + // so it's probably 3 functions deep -- but certainly at least 2 deep. + auto st = std::stacktrace::current(); + assert(st.size() >= 2); + auto it = st.begin(); + auto entry1 = *(it++); // represents this function + auto entry2 = *(it++); // represents our caller, `main` + + // get current trace again, but skip the 1st + st = std::stacktrace::current(1, 1); + assert(st.size() >= 1); + assert(*st.begin() == entry2); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current_with_skip_depth(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.cpp new file mode 100644 index 0000000000000..2537ce1aec6da --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/only_uses_allocator.cpp @@ -0,0 +1,332 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; [3] + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); [4] + explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] + + basic_stacktrace(const basic_stacktrace& other); [6] + basic_stacktrace(basic_stacktrace&& other) noexcept; [7] + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] + basic_stacktrace& operator=(const basic_stacktrace& other); [10] + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); [11] + + ~basic_stacktrace(); [12] +*/ + +#include +#include +#include + +/** + * This file includes tests which ensure any allocations performed by `basic_stacktrace` + * are done via the user-provided allocator, and not via bare `malloc` / `operator new`s. + * Our overridden `malloc` below will fail if `malloc_disabled` is `true`. + * The program's startup code may need to malloc, so we'll allow malloc initially. + * This is only activated during the "test_no_malloc_or_new_ex_allocator" test, + * during which it should be using a `test_alloc` we'll provide (which knows how to + * unblock `malloc` temporarily). + */ +bool malloc_disabled{false}; + +void* malloc(size_t size) { + // If flag has not been temporarily disabled by our allocator, abort + if (malloc_disabled) { + abort(); + } + // Since we overrode malloc with this function, and there's no way to call up + // to the "real malloc", allocate a different way. Assumes nothing actually uses `calloc`. + return calloc(1, size); +} + +// All the various ways to monkey around with heap memory without an allocator: +// we'll simply redirect these through our gate-keeping `malloc` above. +void* operator new(size_t size) { return malloc(size); } +void* operator new[](size_t size) { return malloc(size); } +void operator delete(void* ptr) noexcept { free(ptr); } +void operator delete(void* ptr, size_t) noexcept { free(ptr); } +void operator delete[](void* ptr) noexcept { free(ptr); } +void operator delete[](void* ptr, size_t) noexcept { free(ptr); } + +/** RAII-style scope object to temporarily permit heap allocations, used by `test_alloc`.*/ +struct scope_enable_malloc { + bool prev_malloc_disabled_; + scope_enable_malloc() : prev_malloc_disabled_(malloc_disabled) { malloc_disabled = false; } + ~scope_enable_malloc() { malloc_disabled = true; } +}; + +template +struct test_alloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + template + struct rebind { + using other = test_alloc; + }; + + std::allocator wrapped_{}; + + test_alloc() = default; + + template + test_alloc(test_alloc const& rhs) : wrapped_(rhs.wrapped_) {} + + bool operator==(auto const& rhs) const { return &rhs == this; } + bool operator==(test_alloc const&) const { return true; } + + T* allocate(size_t n) { return wrapped_.allocate(n); } + + auto allocate_at_least(size_t n) { return wrapped_.allocate_at_least(n); } + + void deallocate(T* ptr, size_t n) { return wrapped_.deallocate(ptr, n); } +}; + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; [1] + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; [2] + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; [3] + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); [4] + explicit basic_stacktrace(const allocator_type& alloc) noexcept; [5] + + basic_stacktrace(const basic_stacktrace& other); [6] + basic_stacktrace(basic_stacktrace&& other) noexcept; [7] + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); [8] + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); [9] + basic_stacktrace& operator=(const basic_stacktrace& other); [10] + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); [11] + + ~basic_stacktrace(); [12] +*/ + +// clang-format off +uint32_t test1_line; +uint32_t test2_line; + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test1(A& alloc) { + test1_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = std::basic_stacktrace::current(alloc); + return ret; +} + +template +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE +std::basic_stacktrace test2(A& alloc) { + test2_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto ret = test1(alloc); + return ret; +} +// clang-format on + +/* + [1] + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; + + Returns: A basic_stacktrace object with frames_ storing the stacktrace of the current evaluation + in the current thread of execution, or an empty basic_stacktrace object if the initialization of + frames_ failed. alloc is passed to the constructor of the frames_ object. + [Note 1: If the stacktrace was successfully obtained, then frames_.front() is the stacktrace_entry + representing approximately the current evaluation, and frames_.back() is the stacktrace_entry + representing approximately the initial function of the current thread of execution. - end note] + */ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current() { + test_alloc alloc; + uint32_t main_line = __LINE__ + 1; + auto st = test2(alloc); + + std::cerr << "*** Stacktrace obtained at line " << main_line << '\n' << st << '\n'; + + assert(st.size() >= 3); + assert(st[0]); + assert(st[0].native_handle()); + assert(st[0].description().contains("test1")); + assert(st[0].source_file().contains("basic.cons.pass.cpp")); + assert(st[1]); + assert(st[1].native_handle()); + assert(st[1].description().contains("test2")); + assert(st[1].source_file().contains("basic.cons.pass.cpp")); + assert(st[2]); + assert(st[2].native_handle()); + assert(st[2].description().contains("test_current")); + assert(st[2].source_file().contains("basic.cons.pass.cpp")); + + // We unfortunately cannot guarantee the following; in CI, and possibly on users' build machines, + // there may not be an up-to-date version of e.g. `addr2line`. + // assert(st[0].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[0].source_line() == test1_line); + // assert(st[1].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[1].source_line() == test2_line); + // assert(st[2].source_file().ends_with("basic.cons.pass.cpp")); + // assert(st[2].source_line() == main_line); +} + +/* + [2] + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; + Let t be a stacktrace as-if obtained via basic_stacktrace::current(alloc). Let n be t.size(). + Returns: A basic_stacktrace object where frames_ is direct-non-list-initialized from arguments + t.begin() + min(n, skip), t.end(), and alloc, or an empty basic_stacktrace object if the + initialization of frames_ failed. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip() { + // Use default allocator for simplicity; alloc is covered above + auto st_skip0 = std::stacktrace::current(); + std::cerr << "*** st_skip0:\n" << st_skip0 << '\n'; + assert(st_skip0.size() >= 2); + auto st_skip1 = std::stacktrace::current(1); + std::cerr << "*** st_skip1:\n" << st_skip1 << '\n'; + assert(st_skip1.size() >= 1); + assert(st_skip0.size() == st_skip1.size() + 1); + assert(st_skip0[1] == st_skip1[0]); + auto st_skip_many = std::stacktrace::current(1 << 20); + assert(st_skip_many.empty()); +} + +/* + [3] + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; + Let t be a stacktrace as-if obtained via basic_stacktrace::current(alloc). Let n be t.size(). + Preconditions: skip <= skip + max_depth is true. + Returns: A basic_stacktrace object where frames_ is direct-non-list-initialized from arguments + t.begin() + min(n, skip), t.begin() + min(n, skip + max_depth), and alloc, or an empty + basic_stacktrace object if the initialization of frames_ failed. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_current_with_skip_depth() { + // current stack is: [this function, main, (possibly something else, e.g. `_start` from libc)] + // so it's probably 3 functions deep -- but certainly at least 2 deep. + auto st = std::stacktrace::current(); + assert(st.size() >= 2); + auto it = st.begin(); + auto entry1 = *(it++); // represents this function + auto entry2 = *(it++); // represents our caller, `main` + + // get current trace again, but skip the 1st + st = std::stacktrace::current(1, 1); + assert(st.size() >= 1); + assert(*st.begin() == entry2); +} + +/* + [4] + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); + Postconditions: empty() is true. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_default_construct() { + std::stacktrace st; + assert(st.empty()); +} + +/* + [5] + explicit basic_stacktrace(const allocator_type& alloc) noexcept; + Effects: alloc is passed to the frames_ constructor. + Postconditions: empty() is true. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_construct_with_allocator() { + test_alloc alloc; + std::basic_stacktrace st(alloc); + assert(st.empty()); + + st = std::basic_stacktrace::current(alloc); + assert(!st.empty()); +} + +/* + [6] basic_stacktrace(const basic_stacktrace& other); + [7] basic_stacktrace(basic_stacktrace&& other) noexcept; + [8] basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + [9] basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + [10] basic_stacktrace& operator=(const basic_stacktrace& other); + [11] basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); + + Remarks: Implementations may strengthen the exception specification for these functions + ([res.on.exception.handling]) by ensuring that empty() is true on failed allocation. +*/ +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE void test_copy_move_ctors() { + using A = std::allocator; + A alloc; + auto st = std::basic_stacktrace::current(alloc); + + auto copy_constr = std::basic_stacktrace(st); + assert(st == copy_constr); + + std::basic_stacktrace copy_assign; + copy_assign = std::basic_stacktrace(st); + assert(st == copy_assign); + + auto st2 = test2(alloc); + assert(st2.size()); + std::basic_stacktrace move_constr(std::move(st2)); + assert(move_constr.size()); + assert(!st2.size()); + + auto st3 = test2(alloc); + assert(st3.size()); + std::basic_stacktrace move_assign; + move_assign = std::move(st3); + assert(move_assign.size()); + assert(!st3.size()); + + // TODO(stacktrace23): should we add test cases with `select_on_container_copy_construction`? +} + +/** Ensure all allocations take place through a given allocator, and that none + * are sneaking around it by accidentally using malloc or operator new. */ +void test_no_malloc_or_new_ex_allocator() { + // A generic allocator we'll use for everything + using A = test_alloc; + A alloc; + // After this point all stacktrace operations must properly use `alloc` + malloc_disabled = true; + // Try all the stack trace operations + auto st = std::basic_stacktrace(alloc); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_current(); + test_current_with_skip(); + test_current_with_skip_depth(); + test_default_construct(); + test_construct_with_allocator(); + test_copy_move_ctors(); + test_no_malloc_or_new_ex_allocator(); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp new file mode 100644 index 0000000000000..843d25aaa2c3e --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.hash.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.6) Hash Support + + template<> struct hash; [1] + template struct hash>; [2] + + The specializations are enabled ([unord.hash]). +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace trace; // initially empty + auto hash_val_empty = std::hash()(trace); + trace = /* reassign */ std::stacktrace::current(); + auto hash_val_nonempty = std::hash()(trace); + assert(hash_val_empty != hash_val_nonempty); + + std::stacktrace_entry empty_entry; + assert(std::hash()(empty_entry) == 0); + auto nonempty_entry = trace[0]; + assert(std::hash()(nonempty_entry) != 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp new file mode 100644 index 0000000000000..147ce1f65810d --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.5) Modifiers [stacktrace.basic.mod] + + template + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + Effects: Exchanges the contents of *this and other. +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace empty; + auto current = std::stacktrace::current(); + + std::stacktrace a(empty); + std::stacktrace b(current); + assert(a == empty); + assert(b == current); + + a.swap(b); + assert(a == current); + assert(b == empty); + + // TODO(stacktrace23): should we also test swap w/ `select_on_container_swap` case + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.cpp new file mode 100644 index 0000000000000..def734fa56697 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); +*/ + +#include +#include +#include + +int main(int, char**) { + auto a = std::stacktrace::current(); + + std::stringstream entry_os; + entry_os << a[0]; + assert(entry_os.str() == std::to_string(a[0])); + + std::stringstream trace_os; + trace_os << a; + assert(trace_os.str() == std::to_string(a)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp new file mode 100644 index 0000000000000..281af1c245245 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace empty; + auto current = std::stacktrace::current(); + + std::stacktrace a(empty); + std::stacktrace b(current); + assert(a == empty); + assert(b == current); + + std::swap(a, b); + assert(a == current); + assert(b == empty); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.cpp new file mode 100644 index 0000000000000..91bc9dc9990a1 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.6) Non-member functions + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); +*/ + +#include +#include + +int main(int, char**) { + auto a = std::stacktrace::current(); + + assert(std::to_string(a[0]).contains("main")); + assert(std::to_string(a[0]).contains("basic.nonmem.pass")); + + assert(std::to_string(a).contains("main")); + assert(std::to_string(a).contains("basic.nonmem.pass")); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp new file mode 100644 index 0000000000000..f9604c761b17c --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reference at(size_type) const; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st = test3(); + + assert(st.at(0)); + assert(st.at(1)); + assert(st.at(2)); + assert(st.at(3)); + + auto f0 = st.at(0); + auto f1 = st.at(1); + auto f2 = st.at(2); + auto f3 = st.at(3); + + auto it = st.begin(); + assert(*it++ == f0); + assert(it != st.end()); + assert(*it++ == f1); + assert(it != st.end()); + assert(*it++ == f2); + assert(it != st.end()); + assert(*it++ == f3); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp new file mode 100644 index 0000000000000..db3189d7cb007 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.begin() == st.end()); + // no longer empty: + st = test3(); + assert(!st.empty()); + assert(st.begin() != st.end()); + auto f0 = st[0]; + auto it = st.begin(); + assert(*it == f0); + assert(it != st.end()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp new file mode 100644 index 0000000000000..3d084d45ef7b2 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.cbegin() == st.cend()); + // no longer empty: + st = test3(); + assert(st.cbegin() != st.cend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp new file mode 100644 index 0000000000000..5d4d6fc6737f9 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.crbegin() == st.crend()); + // no longer empty: + st = test3(); + assert(st.crbegin() != st.crend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp new file mode 100644 index 0000000000000..dc3c6baa8b978 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + bool empty() const noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.empty()); + st = test3(); + assert(!st.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp new file mode 100644 index 0000000000000..edea5cc4e3d1a --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + allocator_type get_allocator() const noexcept; +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace st; + static_assert(std::same_as); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp new file mode 100644 index 0000000000000..158a9594f7907 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + size_type max_size() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.max_size() == (std::vector>().max_size())); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp new file mode 100644 index 0000000000000..d837434f8d643 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/operator_index.pass.cpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reference operator[](size_type) const; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + auto st = test3(); + + assert(st[0]); + assert(st[1]); + assert(st[2]); + assert(st[3]); + + auto f0 = st[0]; + auto f1 = st[1]; + auto f2 = st[2]; + auto f3 = st[3]; + + auto it = st.begin(); + assert(*it++ == f0); + assert(it != st.end()); + assert(*it++ == f1); + assert(it != st.end()); + assert(*it++ == f2); + assert(it != st.end()); + assert(*it++ == f3); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp new file mode 100644 index 0000000000000..f8446d42b5f03 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + static_assert(std::random_access_iterator); + assert(st.rbegin() == st.rend()); + // no longer empty: + st = test3(); + auto it = st.rbegin(); + assert(it != st.rend()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp new file mode 100644 index 0000000000000..cbfac43b4beaf --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4.3) Observers [stacktrace.basic.obs] + + size_type size() const noexcept; +*/ + +#include +#include + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test1() { return std::stacktrace::current(0, 4); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test2() { return test1(); } +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE std::stacktrace test3() { return test2(); } + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace st; + assert(st.size() == 0); + st = test3(); + assert(st.size() > 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp new file mode 100644 index 0000000000000..ac1e4282eeea5 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp @@ -0,0 +1,142 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.4) Class template basic_stacktrace [stacktrace.basic] + (19.6.4.1) Overview [stacktrace.basic.overview] + +namespace std { + template + class basic_stacktrace { + public: + using value_type = stacktrace_entry; + using const_reference = const value_type&; + using reference = value_type&; + using const_iterator = implementation-defined; // see [stacktrace.basic.obs] + using iterator = const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using difference_type = implementation-defined; + using size_type = implementation-defined; + using allocator_type = Allocator; + + // (19.6.4.2) + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, + const allocator_type& alloc = allocator_type()) noexcept; + static basic_stacktrace current(size_type skip, size_type max_depth, + const allocator_type& alloc = allocator_type()) noexcept; + + basic_stacktrace() noexcept(is_nothrow_default_constructible_v); + explicit basic_stacktrace(const allocator_type& alloc) noexcept; + + basic_stacktrace(const basic_stacktrace& other); + basic_stacktrace(basic_stacktrace&& other) noexcept; + basic_stacktrace(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); + + ~basic_stacktrace(); + + // (19.6.4.3) + // [stacktrace.basic.obs], observers + allocator_type get_allocator() const noexcept; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_reverse_iterator rbegin() const noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + const_reference operator[](size_type) const; + const_reference at(size_type) const; + + // (19.6.4.4) + // [stacktrace.basic.cmp], comparisons + template + friend bool operator==(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + template + friend strong_ordering operator<=>(const basic_stacktrace& x, + const basic_stacktrace& y) noexcept; + + // (19.6.4.5) + // [stacktrace.basic.mod], modifiers + void swap(basic_stacktrace& other) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); + + private: + vector frames_; // exposition only + }; + + // (19.6.4.6) + // [stacktrace.basic.nonmem], non-member functions + + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); + + ostream& operator<<(ostream& os, const stacktrace_entry& f); + template + ostream& operator<<(ostream& os, const basic_stacktrace& st); +} + +*/ + +#include +#include +#include +#include + +int main(int, char**) { + // This test will only verify these member types exist and are + // defined as expected; their actual behavior is tested in another .cpp. + + using A = std::allocator; + using S = std::basic_stacktrace; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static_assert(std::forward_iterator); + static_assert(std::forward_iterator); + using CRI = S::const_reverse_iterator; + static_assert(std::is_same_v); + using RI = S::reverse_iterator; + static_assert(std::is_same_v); + + using IterT = S::iterator; + using DiffT = S::difference_type; + static_assert(std::is_same_v); + + static_assert(std::is_integral_v); + static_assert(std::is_same_v); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp new file mode 100644 index 0000000000000..cda5383e361ce --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.5) Comparison [stacktrace.entry.cmp] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; +*/ + +#include +#include + +namespace { +int func1() { return 41; } +int func2() { return 42; } +} // namespace + +int main(int, char**) { + std::stacktrace_entry a; + std::stacktrace_entry b; + std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(&func1); + *(uintptr_t*)(&b) = uintptr_t(&func1); + *(uintptr_t*)(&c) = uintptr_t(&func2); + assert(a == b); + assert(a != c); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp new file mode 100644 index 0000000000000..d7c33c64cb68f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.5) Comparison [stacktrace.entry.cmp] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.cmp], comparison + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; +*/ + +#include +#include + +namespace { +int func1() { return 41; } +int func2() { return 42; } +} // namespace + +int main(int, char**) { + auto addr1 = uintptr_t(&func1); + auto addr2 = uintptr_t(&func2); + assert(addr1 != addr2); + if (addr1 > addr2) { + std::swap(addr1, addr2); + } + + std::stacktrace_entry a; + std::stacktrace_entry b; + std::stacktrace_entry c; + *(uintptr_t*)(&a) = uintptr_t(addr1); + *(uintptr_t*)(&b) = uintptr_t(addr1); + *(uintptr_t*)(&c) = uintptr_t(addr2); + + assert(std::strong_ordering::equal == (a <=> b)); + assert(std::strong_ordering::equivalent == (a <=> b)); + assert(std::strong_ordering::greater == (c <=> a)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.cpp new file mode 100644 index 0000000000000..c993a6df64508 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) noexcept { + static_assert(std::is_nothrow_copy_assignable_v); + std::stacktrace_entry entry_t2; + std::stacktrace_entry entry_t4; + entry_t4 = entry_t2; + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.cpp new file mode 100644 index 0000000000000..85dd6fb7853cc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + std::stacktrace_entry entry_t2; + static_assert(std::is_nothrow_copy_constructible_v); + std::stacktrace_entry entry_t3(entry_t2); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.cpp new file mode 100644 index 0000000000000..03bd82cfd9c73 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.3.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; +} +*/ + +#include +#include +#include + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) noexcept { + // "Postconditions: *this is empty." + static_assert(std::is_default_constructible_v); + static_assert(std::is_nothrow_default_constructible_v); + std::stacktrace_entry entry_t2; + assert(!entry_t2); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.cpp new file mode 100644 index 0000000000000..bf1a23080f307 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.cpp @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.3) Observers [stacktrace.entry.obs] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; +*/ + +#include +#include + +int main(int, char**) noexcept { + std::stacktrace_entry entry_t6; + assert(entry_t6.native_handle() == 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp new file mode 100644 index 0000000000000..0cd3f11260ba7 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.3.3) Observers [stacktrace.entry.obs] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.obs], observers + constexpr explicit operator bool() const noexcept; +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace_entry entry_t6; + // "Returns: false if and only if *this is empty." + assert(!entry_t6); + // Now set addr to something nonzero + *(uintptr_t*)(&entry_t6) = uintptr_t(&main); + assert(entry_t6.native_handle() == uintptr_t(&main)); + assert(entry_t6); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp new file mode 100644 index 0000000000000..39bc5845e9718 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.pass.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +#include + +#include +#include +#include + +/* + (19.6.3) Class stacktrace_entry [stacktrace.entry] + (19.6.3.1) Overview [stacktrace.entry.overview] + +namespace std { + class stacktrace_entry { + public: + using native_handle_type = implementation-defined; + + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; + constexpr stacktrace_entry(const stacktrace_entry& other) noexcept; + constexpr stacktrace_entry& operator=(const stacktrace_entry& other) noexcept; + ~stacktrace_entry(); + + // [stacktrace.entry.obs], observers + constexpr native_handle_type native_handle() const noexcept; + constexpr explicit operator bool() const noexcept; + + // [stacktrace.entry.query], query + string description() const; + string source_file() const; + uint_least32_t source_line() const; + + // [stacktrace.entry.cmp], comparison + friend constexpr bool operator==(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + friend constexpr strong_ordering operator<=>(const stacktrace_entry& x, + const stacktrace_entry& y) noexcept; + }; +} +*/ + +int main(int, char**) { + // [stacktrace.entry.overview] + // "The class stacktrace_entry models regular ([concepts.object]) + // and three_way_comparable ([cmp.concept])." + static_assert(std::regular); + static_assert(std::three_way_comparable); + + // using native_handle_type = implementation-defined; + static_assert(sizeof(std::stacktrace_entry::native_handle_type) >= sizeof(void*)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp new file mode 100644 index 0000000000000..2493e2bd8b490 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + string description() const; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + auto desc = e.description(); + assert(desc.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp new file mode 100644 index 0000000000000..fc2d5efb6a31e --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.pass.cpp @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + string source_file() const; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + auto src = e.source_file(); + assert(src.empty()); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp new file mode 100644 index 0000000000000..e601b74f032f0 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.pass.cpp @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +/* + (19.6.3.4) Query [stacktrace.entry.query] + +namespace std { + class stacktrace_entry { + public: + // [stacktrace.entry.query], query + uint_least32_t source_line() const; +*/ + +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + assert(e.source_line() == 0); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp new file mode 100644 index 0000000000000..bda374e0b2d90 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/format/formatter_basic_stacktrace.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.5) Formatting support [stacktrace.format] + + template struct formatter>; +*/ + +#include +// #include + +int main(int, char**) { + /* + For formatter>, format-spec is empty. + + A basic_stacktrace object s is formatted as if by copying to_string(s) through the + output iterator of the context. + */ + + // TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.cpp b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.cpp new file mode 100644 index 0000000000000..acd04ae33dfc3 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 + +/* + (19.6.5) Formatting support [stacktrace.format] + + template<> struct formatter; +*/ + +#include +// #include + +int main(int, char**) { + /* + formatter interprets format-spec as a stacktrace-entry-format-spec. + The syntax of format specifications is as follows: + + stacktrace-entry-format-spec : + fill-and-align_[opt] width_[opt] + + [Note 1: The productions fill-and-align and width are described in [format.string.std]. - end note] + + A stacktrace_entry object se is formatted as if by copying to_string(se) through the output iterator + of the context with additional padding and adjustments as specified by the format specifiers. + */ + + // TODO: stacktrace formatter: https://github.com/llvm/llvm-project/issues/105257 + + return 0; +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp new file mode 100644 index 0000000000000..8d8ce5665565b --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/stacktrace.version.compile.pass.cpp @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by generate_feature_test_macro_components.py +// and should not be edited manually. + +// + +// Test the feature test macros defined by + +// clang-format off + +#include +#include "test_macros.h" + +#if TEST_STD_VER < 14 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 14 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 20 + +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined before c++23" +# endif + +# ifdef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should not be defined before c++23" +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_formatters +# error "__cpp_lib_formatters should be defined in c++23" +# endif +# if __cpp_lib_formatters != 202302L +# error "__cpp_lib_formatters should have the value 202302L in c++23" +# endif +# else +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!" +# endif +# endif + +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++23" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++23" +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_formatters +# error "__cpp_lib_formatters should be defined in c++26" +# endif +# if __cpp_lib_formatters != 202302L +# error "__cpp_lib_formatters should have the value 202302L in c++26" +# endif +# else +# ifdef __cpp_lib_formatters +# error "__cpp_lib_formatters should not be defined because it is unimplemented in libc++!" +# endif +# endif + +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++26" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++26" +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp index aa33a2788f1eb..881764515b9cf 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -5831,17 +5831,11 @@ # error "__cpp_lib_sstream_from_string_view should not be defined before c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should be defined in c++23" -# endif -# if __cpp_lib_stacktrace != 202011L -# error "__cpp_lib_stacktrace should have the value 202011L in c++23" -# endif -# else -# ifdef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++23" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++23" # endif # ifndef __cpp_lib_starts_ends_with @@ -7771,17 +7765,11 @@ # error "__cpp_lib_sstream_from_string_view should have the value 202306L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should be defined in c++26" -# endif -# if __cpp_lib_stacktrace != 202011L -# error "__cpp_lib_stacktrace should have the value 202011L in c++26" -# endif -# else -# ifdef __cpp_lib_stacktrace -# error "__cpp_lib_stacktrace should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_stacktrace +# error "__cpp_lib_stacktrace should be defined in c++26" +# endif +# if __cpp_lib_stacktrace != 202011L +# error "__cpp_lib_stacktrace should have the value 202011L in c++26" # endif # ifndef __cpp_lib_starts_ends_with diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index 2b7f6fa8a48a9..b3d0a573cf8ce 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1288,7 +1288,6 @@ def add_version_header(tc): "name": "__cpp_lib_stacktrace", "values": {"c++23": 202011}, "headers": ["stacktrace"], - "unimplemented": True, }, { "name": "__cpp_lib_starts_ends_with", diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py index a505d37b65b81..4f569ffba6325 100644 --- a/libcxx/utils/libcxx/header_information.py +++ b/libcxx/utils/libcxx/header_information.py @@ -170,7 +170,6 @@ def __hash__(self) -> int: "linalg", "rcu", "spanstream", - "stacktrace", "stdfloat", "text_encoding", ])) @@ -226,6 +225,7 @@ def __hash__(self) -> int: "semaphore": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "shared_mutex": "// UNSUPPORTED: no-threads, c++03, c++11", "sstream": "// UNSUPPORTED: no-localization", + "stacktrace": "// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20", "stdatomic.h": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17, c++20", "stop_token": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "streambuf": "// UNSUPPORTED: no-localization", @@ -275,6 +275,7 @@ def __hash__(self) -> int: "regex": ["compare", "initializer_list"], "set": ["compare", "initializer_list"], "stack": ["compare", "initializer_list"], + "stacktrace": ["compare"], "string_view": ["compare"], "string": ["compare", "initializer_list"], "syncstream": ["ostream"],