diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt index 85514cc7547a9..6e4624326100e 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) @@ -750,6 +755,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 a36848ebd24b4..072647bd3c740 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -396,7 +396,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 91123ffa3e34b..5483c4e7c12cb 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -55,6 +55,8 @@ Implemented Papers - P2655R3: ``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type (`Github `__) - P2944R3: Comparisons for ``reference_wrapper`` (`Github `__) - P3379R0: Constrain ``std::expected equality`` operators (`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 189f8452e0678..50ba4275b1c7d 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 f1d8e9a2bd09c..9637e0cf65078 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)","|Complete|","21","The paper is implemented as a DR to C++20" diff --git a/libcxx/docs/VendorDocumentation.rst b/libcxx/docs/VendorDocumentation.rst index aede8f9a81dd2..971925bb8a7d0 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 c6b87a34a43e9..79ec05a822424 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -740,6 +740,10 @@ set(files __ranges/zip_transform_view.h __ranges/zip_view.h __split_buffer + __stacktrace/basic_stacktrace.h + __stacktrace/images.h + __stacktrace/memory.h + __stacktrace/stacktrace_entry.h __std_mbstate_t.h __stop_token/atomic_unique_lock.h __stop_token/intrusive_list_view.h @@ -1058,6 +1062,7 @@ set(files span sstream stack + stacktrace stdatomic.h stdbool.h stddef.h diff --git a/libcxx/include/__config b/libcxx/include/__config index 77a71b6cf1cae..bc407f41b3600 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -965,6 +965,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 b68c0c8258366..d17fa45119439 100644 --- a/libcxx/include/__config_site.in +++ b/libcxx/include/__config_site.in @@ -32,6 +32,7 @@ #cmakedefine01 _LIBCPP_HAS_WIDE_CHARACTERS #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/basic_stacktrace.h b/libcxx/include/__stacktrace/basic_stacktrace.h new file mode 100644 index 0000000000000..95beb5baca830 --- /dev/null +++ b/libcxx/include/__stacktrace/basic_stacktrace.h @@ -0,0 +1,332 @@ +// -*- 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_BASIC +#define _LIBCPP_STACKTRACE_BASIC + +#include "memory.h" +#include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 23 + +# include <__assert> +# include <__cstddef/size_t.h> +# include <__functional/function.h> +# include <__functional/hash.h> +# include <__fwd/format.h> +# include <__fwd/ostream.h> +# include <__iterator/iterator.h> +# include <__iterator/reverse_iterator.h> +# include <__memory/allocator_traits.h> +# include <__memory_resource/polymorphic_allocator.h> +# include <__new/allocate.h> +# include <__type_traits/is_nothrow_constructible.h> +# include <__vector/vector.h> +# include +# include +# include +# include +# include + +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI base { + constexpr static size_t __default_max_depth = 64; + constexpr static size_t __absolute_max_depth = 256; + constexpr static size_t __k_init_pool_on_stack = 1 << 12; + + std::function __entries_size_; + std::function __emplace_entry_; + std::function __entries_data_; + std::function __entry_at_; + + template + base(_Vp* __entries) + : __entries_size_([=]() { return __entries->size(); }), + __emplace_entry_([=]() -> entry_base& { return (entry_base&)__entries->emplace_back(); }), + __entries_data_([=]() -> entry_base* { return (entry_base*)__entries->data(); }), + __entry_at_([=](size_t __i) -> entry_base& { return (entry_base&)__entries->at(__i); }) {} + + _LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void + current(arena& __arena, size_t __skip, size_t __max_depth); + + _LIBCPP_EXPORTED_FROM_ABI void find_images(arena& __arena); + _LIBCPP_EXPORTED_FROM_ABI void find_symbols(arena& __arena); + _LIBCPP_EXPORTED_FROM_ABI void find_source_locs(arena& __arena); + + entry_base* entries_begin() { return __entries_data_(); } + entry_base* entries_end() { return __entries_data_() + __entries_size_(); } + + _LIBCPP_EXPORTED_FROM_ABI std::ostream& write_to(std::ostream& __os) const; + _LIBCPP_EXPORTED_FROM_ABI string to_string() const; +}; + +} // namespace __stacktrace + +// (19.6.4) +// Class template basic_stacktrace [stacktrace.basic] + +class stacktrace_entry; + +template +class _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace : private __stacktrace::base { + friend struct hash>; + + using _ATraits _LIBCPP_NODEBUG = allocator_traits<_Allocator>; + constexpr static bool __kPropOnCopyAssign = _ATraits::propagate_on_container_copy_assignment::value; + constexpr static bool __kPropOnMoveAssign = _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 __kNoThrowAlloc = + noexcept(noexcept(_Allocator().allocate(1)) && noexcept(_Allocator().allocate_at_least(1))); + + [[no_unique_address]] + _Allocator __alloc_; + + using entry_vec _LIBCPP_NODEBUG = std::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) { + size_type __skip = 1; + size_type __max_depth = __default_max_depth; + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); + return __ret; + } + + _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) { + ++__skip; + size_type __max_depth = __default_max_depth; + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); + return __ret; + } + + _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) { + ++__skip; + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __skip <= __skip + __max_depth, "sum of skip and max_depth overflows size_type"); + basic_stacktrace __ret{__caller_alloc}; + if (__max_depth) [[likely]] { + __stacktrace::stack_bytes<__k_init_pool_on_stack> __stack_bytes; + __stacktrace::byte_pool __stack_pool = __stack_bytes.pool(); + __stacktrace::arena __arena(__stack_pool, __caller_alloc); + ((base&)__ret).current(__arena, __skip, __max_depth); + } + return __ret; + } + + _LIBCPP_EXPORTED_FROM_ABI constexpr ~basic_stacktrace() = default; + + _LIBCPP_EXPORTED_FROM_ABI explicit basic_stacktrace(const allocator_type& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other, allocator_type const& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(__other.__entries_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other, allocator_type const& __alloc) + : base(&__entries_), __alloc_(__alloc), __entries_(std::move(__other.__entries_)) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace() noexcept(is_nothrow_default_constructible_v) + : basic_stacktrace(allocator_type()) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace const& __other) noexcept + : basic_stacktrace(__other, _ATraits::select_on_container_copy_construction(__other.__alloc_)) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace(basic_stacktrace&& __other) noexcept + : basic_stacktrace(std::move(__other), __other.__alloc_) {} + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& operator=(const basic_stacktrace& __other) { + if (std::addressof(__other) != this) { + if (__kPropOnCopyAssign) { + new (this) basic_stacktrace(__other, __other.__alloc_); + } else { + new (this) basic_stacktrace(__other); + } + } + return *this; + } + + _LIBCPP_EXPORTED_FROM_ABI basic_stacktrace& + operator=(basic_stacktrace&& __other) noexcept(__kPropOnMoveAssign || __kAlwaysEqual) { + if (std::addressof(__other) != this) { + if (__kPropOnMoveAssign) { + auto __alloc = __other.__alloc_; + new (this) basic_stacktrace(std::move(__other), __alloc); + } else { + new (this) basic_stacktrace(std::move(__other)); + } + } + return *this; + } + + // (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& __other) noexcept( + allocator_traits<_Allocator>::propagate_on_container_swap::value || + allocator_traits<_Allocator>::is_always_equal::value) { + std::swap(__entries_, __other.__entries_); + if (__kPropOnSwap) { + std::swap(__alloc_, __other.__alloc_); + } + } +}; + +using stacktrace = basic_stacktrace>; + +namespace pmr { +using stacktrace = basic_stacktrace>; +} // namespace pmr + +// (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 ostream& operator<<(ostream& __os, const basic_stacktrace<_Allocator>& __stacktrace) { + return ((__stacktrace::base const&)__stacktrace).write_to(__os); +} + +template +_LIBCPP_EXPORTED_FROM_ABI string to_string(const basic_stacktrace<_Allocator>& __stacktrace) { + return ((__stacktrace::base const&)__stacktrace).to_string(); +} + +// (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 + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_BASIC diff --git a/libcxx/include/__stacktrace/images.h b/libcxx/include/__stacktrace/images.h new file mode 100644 index 0000000000000..52144d38310f5 --- /dev/null +++ b/libcxx/include/__stacktrace/images.h @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// 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_IMAGES_H +#define _LIBCPP_STACKTRACE_IMAGES_H + +#include <__stacktrace/memory.h> +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct image; +struct images; + +struct image { + uintptr_t loaded_at_{}; + uintptr_t slide_{}; + fixed_str name_{}; + bool is_main_prog_{}; + + bool operator<(image const& __rhs) const { return loaded_at_ < __rhs.loaded_at_; } + operator bool() const { return !name_.empty(); } +}; + +/** + * Contains an array `images_`, which will include `prog_image` objects (see above) + * collected in an OS-dependent way. After construction these images will be sorted + * according to their load address; there will also be two sentinels with dummy + * addresses (0x0000... and 0xFFFF...) to simplify search functions. + * + * After construction, images_ and count_ look like: + * [0] [1] [2] [3] ... [count_ - 1] + * (sentinel) foo.exe libc++so.1 libc.so.6 (sentinel) + * 0x000000000000 0x000100000000 0x7def00000000 0x7c0300000000 0xffffffffffff + */ +struct _LIBCPP_EXPORTED_FROM_ABI images { + constexpr static size_t k_max_images = 256; + std::array images_{}; // space for the L/R sentinels + unsigned count_{0}; // image count, including sentinels + + /** An OS-specific constructor is defined. */ + _LIBCPP_EXPORTED_FROM_ABI images(); + + /** Get prog_image by index (0 <= index < count_) */ + _LIBCPP_EXPORTED_FROM_ABI image& operator[](size_t __index) { + _LIBCPP_ASSERT(__index < count_, "index out of range"); + return images_.at(__index); + } + + /** Image representing the main program (nullptr if we couldn't figure that out) */ + _LIBCPP_EXPORTED_FROM_ABI image* main_prog_image() { + for (size_t __i = 1; __i < count_ - 1; __i++) { + auto& __image = images_[__i]; + if (__image.is_main_prog_) { + return &__image; + } + } + return nullptr; + } + + /** Search the sorted images array for one containing this address. */ + _LIBCPP_EXPORTED_FROM_ABI void find(size_t* __index, uintptr_t __addr) { + // `index` slides left/right as we search through images. + // It's (probably) likely several consecutive entries are from the same image, so + // each iteration's `find` uses the same starting point, making it (probably) constant-time. + // XXX Is this more efficient in practice than e.g. `std::set` and `upper_bound`? + if (*__index < 1) { + *__index = 1; + } + if (*__index > count_ - 1) { + *__index = count_ - 1; + } + while (images_[*__index]) { + if (__addr < images_[*__index].loaded_at_) { + --*__index; + } else if (__addr >= images_[*__index + 1].loaded_at_) { + ++*__index; + } else { + break; + } + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_IMAGES_H diff --git a/libcxx/include/__stacktrace/memory.h b/libcxx/include/__stacktrace/memory.h new file mode 100644 index 0000000000000..6c8dd3c8eafd5 --- /dev/null +++ b/libcxx/include/__stacktrace/memory.h @@ -0,0 +1,268 @@ +// -*- 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_CSTR_BUFFER_H +#define _LIBCPP_STACKTRACE_CSTR_BUFFER_H + +#include <__config> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 23 + +# include <__assert> +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/* +A few memory-related utilities: + + * An `arena` which provides allocator-like / malloc-like functionality for us, + for objects used internally as well as objects returned to the caller. + Uses the caller's provided allocator, so none of these involve heap allocations + "outside of" the caller-provided allocator. + * A `str` class, inheriting from `std::string`, ensuring allocations happen via `arena` + +A small amount of glue / hacks are done here to allow the rest of the stacktrace-related +code to use familiar string etc. operations, while encapsulating away the details of where +memory might come from, since we need to be careful about unexpected allocations. +*/ + +struct byte_pool final { + byte* ptr_; + function destroy_; + byte_pool* link_; + byte* end_; + + byte_pool( + byte* __bytes, size_t __size, function __destroy = [] {}, byte_pool* __link = nullptr) noexcept + : ptr_(__bytes), destroy_(__destroy), link_(__link), end_(__bytes + __size) {} + + byte* operator()(size_t __sz, size_t __align) noexcept { + auto __ptr = uintptr_t(ptr_); // convert curr ptr to integer, to do math + auto __misalign = __ptr % __align; // if current ptr not aligned, + if (__misalign) { + __ptr += (__align - __misalign); + } // waste a few bytes to ensure alignment + auto __ret = __ptr; // we would return this aligned position + __ptr += __sz; // next object will start here + if (__ptr > uintptr_t(end_)) { + return nullptr; + } // if this exceeds our space, then fail + ptr_ = (byte*)__ptr; // otherwise update current position + return (byte*)__ret; // returned aligned position as byte ptr + } +}; + +template +struct stack_bytes final { + byte bytes_[_Sz]; + + ~stack_bytes() = default; + stack_bytes() noexcept = default; + stack_bytes(const stack_bytes&) = delete; + stack_bytes(stack_bytes&&) = delete; + + byte_pool pool() { + return {bytes_, _Sz, [] {}, nullptr}; + } +}; + +struct arena { + function new_bytes_; // new byte-array factory + function del_bytes_; // byte-array destroyer + byte_pool* curr_pool_; // byte pool currently "in effect" + byte_pool* next_pool_; // allocated (from curr_pool_) but not initialized + size_t allocs_{}; // number of successful allocations + size_t deallocs_{}; // incremented on each dealloc; dtor ensures these are equal! + + // An arena is scoped to a `basic_stacktrace::current` invocation, so this is usable by only one thread. + // Additionally, it's used internally throughout many function calls, so for convenience, store it here. + // Also avoids the need for state inside `alloc`, since it can use this pointer instead of an internal one. + static thread_local arena* active_arena_ptr_; + + static arena& get_active() { + auto* __ret = active_arena_ptr_; + _LIBCPP_ASSERT(__ret, "no active arena for this thread"); + return *__ret; + } + + ~arena() { + _LIBCPP_ASSERT(active_arena_ptr_ == this, "different arena unexpectively set as the active one"); + active_arena_ptr_ = nullptr; + _LIBCPP_ASSERT(deallocs_ == allocs_, "destructed arena still has live objects"); + while (curr_pool_) { + curr_pool_->destroy_(); + curr_pool_ = curr_pool_->link_; + } + } + + arena(auto&& __new_bytes, auto&& __del_bytes, byte_pool& __initial_pool) noexcept + : new_bytes_(__new_bytes), del_bytes_(__del_bytes), curr_pool_(&__initial_pool) { + prep_next_pool(); + _LIBCPP_ASSERT(!active_arena_ptr_, "already an active arena"); + active_arena_ptr_ = this; + } + + template + static auto as_byte_alloc(_UA const& __user_alloc) { + return (typename allocator_traits<_UA>::template rebind_alloc)(__user_alloc); + } + + template + arena(byte_pool& __initial_pool, _UA const& __user_alloc) + : arena([&__user_alloc](size_t __sz) { return as_byte_alloc(__user_alloc).allocate(__sz); }, + [&__user_alloc](void* __ptr, size_t __sz) { + return as_byte_alloc(__user_alloc).deallocate((byte*)__ptr, __sz); + }, + __initial_pool) {} + + arena(arena const&) = delete; + arena& operator=(arena const&) = delete; + + void prep_next_pool() noexcept { + // Allocate (via current pool) a new byte_pool record, while we have enough space. + // When the current pool runs out of space, this one will be ready to use. + next_pool_ = (byte_pool*)(*curr_pool_)(sizeof(byte_pool), alignof(byte_pool)); + _LIBCPP_ASSERT(next_pool_, "could not allocate next pool"); + } + + void expand(size_t __atleast) noexcept { + constexpr static size_t __k_default_new_pool = 1 << 12; + auto __size = max(__atleast, __k_default_new_pool); + // "next_pool_" was already allocated, just need to initialize it + auto* __bytes = new_bytes_(__size); + _LIBCPP_ASSERT(__bytes, "could not allocate more bytes for arena"); + curr_pool_ = new (next_pool_) byte_pool(__bytes, __size, [=, this] { del_bytes_(__bytes, __size); }, curr_pool_); + prep_next_pool(); + } + + /** Does nothing; all memory is released when arena is destroyed. */ + void dealloc(std::byte*, size_t) noexcept { ++deallocs_; } + + std::byte* alloc(size_t __size, size_t __align) noexcept { + auto* __ret = (*curr_pool_)(__size, __align); + if (__ret) [[likely]] { + goto success; + } + // Need a new pool to accommodate this request + internal structs + expand(__size + __align + sizeof(byte_pool) + alignof(byte_pool)); // upper bound + __ret = (*curr_pool_)(__size, __align); + _LIBCPP_ASSERT(__ret, "arena failed to allocate"); + success: + ++allocs_; + return __ret; + } +}; + +template +struct alloc { + using value_type = _Tp; + + _Tp* allocate(size_t __n) { + auto& __arena = arena::get_active(); + return (_Tp*)__arena.alloc(__n * sizeof(_Tp), alignof(_Tp)); + } + + void deallocate(_Tp* __ptr, size_t __n) { + auto& __arena = arena::get_active(); + __arena.dealloc((std::byte*)__ptr, __n * sizeof(_Tp)); + } +}; + +struct str : std::basic_string, alloc> { + using _Base _LIBCPP_NODEBUG = std::basic_string, alloc>; + using _Base::basic_string; + using _Base::operator=; + + bool valid() const { return data() != nullptr; } + + operator bool() const { return valid() && !empty(); } + + template + static str makef(char const* __fmt, _AL&&... __args) { + str __ret{}; + +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wformat-security" +# pragma clang diagnostic ignored "-Wformat-nonliteral" +# endif + auto __need = std::snprintf(nullptr, 0, __fmt, __args...); + __ret.resize_and_overwrite(__need + 1, [&](char* __data, size_t __size) { + return std::snprintf(__data, __size + 1, __fmt, std::forward<_AL>(__args)...); + }); +# ifdef __clang__ +# pragma clang diagnostic pop +# endif + + return __ret; + } +}; + +/** A string that contains its own fixed-size, fixed-location buffer. */ +template +struct fixed_str final { + size_t __size_{0}; + char __buf_[_Sz]{0}; + + ~fixed_str() = default; + fixed_str() = default; + + size_t size() const { return __size_; } + bool empty() const { return !size(); } + auto* data(this auto& __self) { return __self.__buf_; } + operator std::string_view() const { return {__buf_, __size_}; } + + fixed_str& operator=(std::string_view __sv) { + strncpy(__buf_, __sv.data(), std::min(_Sz, __sv.size() + 1)); + __size_ = __sv.size(); + __buf_[__size_] = 0; + return *this; + } + + fixed_str(auto const& __rhs) : fixed_str() { *this = __rhs; } + fixed_str& operator=(auto const& __rhs) { return (*this = std::string_view(__rhs)); } + + template + requires requires { _S2 <= _Sz; } + fixed_str& operator=(fixed_str<_S2> const& __rhs) { + return (*this = std::string_view(__rhs)); + } + + template + requires requires { _S2 <= _Sz; } + fixed_str(fixed_str<_S2> const& __rhs) { + *this = std::string_view(__rhs); + } + + fixed_str(fixed_str const& __rhs) { *this = std::string_view(__rhs); } + fixed_str& operator=(fixed_str const& __rhs) { return (*this = std::string_view(__rhs)); } + + friend std::ostream& operator<<(std::ostream& __os, fixed_str const& __f) { return __os << std::string_view(__f); } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_CSTR_BUFFER_H diff --git a/libcxx/include/__stacktrace/stacktrace_entry.h b/libcxx/include/__stacktrace/stacktrace_entry.h new file mode 100644 index 0000000000000..cec802f2c739d --- /dev/null +++ b/libcxx/include/__stacktrace/stacktrace_entry.h @@ -0,0 +1,133 @@ +// -*- 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> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#if _LIBCPP_STD_VER >= 23 + +# include <__fwd/format.h> +# include <__fwd/ostream.h> +# include <__stacktrace/memory.h> +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD + +class stacktrace_entry; + +namespace __stacktrace { + +struct _LIBCPP_HIDE_FROM_ABI image; + +struct _LIBCPP_EXPORTED_FROM_ABI entry_base { + constexpr static size_t __max_sym_len = 512; + constexpr static size_t __max_file_len = PATH_MAX; + + uintptr_t __addr_{}; + fixed_str<__max_sym_len> __desc_; + fixed_str<__max_file_len> __file_; + uint_least32_t __line_{}; + image* __image_{}; + + std::ostream& _LIBCPP_EXPORTED_FROM_ABI write_to(std::ostream& __os) const; + string _LIBCPP_EXPORTED_FROM_ABI to_string() const; + uintptr_t _LIBCPP_EXPORTED_FROM_ABI adjusted_addr() const; +}; + +} // namespace __stacktrace + +class _LIBCPP_EXPORTED_FROM_ABI stacktrace_entry : private __stacktrace::entry_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_; + } + [[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 { return string(__desc_); } + [[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string source_file() const { return string(__file_); } + [[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(); + } +}; + +/** `stacktrace_entry` and `stacktrace_entry` have the same layout, +so a pointer to one can be safely casted as the other. */ +static_assert(sizeof(stacktrace_entry) == sizeof(stacktrace_entry)); + +// (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 inline ostream& operator<<(ostream& __os, std::stacktrace_entry const& __entry) { + return ((__stacktrace::entry_base const*)&__entry)->write_to(__os); +} + +_LIBCPP_EXPORTED_FROM_ABI inline string to_string(std::stacktrace_entry const& __entry) { + return ((__stacktrace::entry_base const*)&__entry)->to_string(); +} + +// (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); + } +}; + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP_STACKTRACE_ENTRY diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index c431c0cb407f3..61e425c4628f8 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -2010,6 +2010,32 @@ module std [system] { export * } + module stacktrace { + module basic_stacktrace { header "__stacktrace/basic_stacktrace.h" } + module memory { header "__stacktrace/memory.h" } + module stacktrace_entry { header "__stacktrace/stacktrace_entry.h" } + + header "stacktrace" + export * + // TODO: Workaround for https://github.com/llvm/llvm-project/issues/120108 + export std.cstddef.byte + export std.cstddef.size_t + export std.format.escaped_output_table + export std.format.formatter + export std.format.width_estimation_table + export std.functional.function + export std.functional.hash + export std.iterator.iterator + export std.iterator.reverse_iterator + export std.memory.allocator + export std.memory.allocator_traits + export std.memory_resource.polymorphic_allocator + export std.new.allocate + export std.ostream.basic_ostream + export std.type_traits.is_nothrow_constructible + export std.vector.vector + } + module stdexcept { header "stdexcept" export * diff --git a/libcxx/include/stacktrace b/libcxx/include/stacktrace new file mode 100644 index 0000000000000..5bcbdb50a6373 --- /dev/null +++ b/libcxx/include/stacktrace @@ -0,0 +1,186 @@ +// -*- 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 + }; +} + +*/ + +#if __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) +# include <__cxx03/__config> +#else +# include <__config> + +# if _LIBCPP_STD_VER >= 23 +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/stacktrace_entry.h> +# include // per [stacktrace.syn] +# endif + +# include + +# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +# endif + +#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS) + +#endif // _LIBCPP_STACKTRACE diff --git a/libcxx/include/version b/libcxx/include/version index aae9277a7dfc6..9016743955cc5 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -531,7 +531,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..d9916b3edfd5a 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 @@ -364,6 +364,9 @@ {'is_defined': False, 'name': '___cxa_vec_new3', 'type': 'U'} {'is_defined': False, 'name': '___dynamic_cast', 'type': 'U'} {'is_defined': False, 'name': '___gxx_personality_v0', 'type': 'U'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZGVZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZNKSt10bad_typeid4whatEv', 'type': 'I'} {'is_defined': True, 'name': '__ZNKSt11logic_error4whatEv', 'type': 'I'} {'is_defined': True, 'name': '__ZNKSt12bad_any_cast4whatEv', 'type': 'FUNC'} @@ -413,6 +416,11 @@ {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_negative_signEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_positive_signEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__110moneypunctIwLb1EE16do_thousands_sepEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base13adjusted_addrEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base8write_toERNS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace10entry_base9to_stringEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace4baseB8ne2200008write_toERNS_13basic_ostreamIcNS_11char_traitsIcEEEE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNKSt3__112__stacktrace4baseB8ne2200009to_stringEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112bad_weak_ptr4whatEv', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE12find_last_ofEPKcmm', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE13find_first_ofEPKcmm', 'type': 'FUNC'} @@ -534,6 +542,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 +983,25 @@ {'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__stacktrace10__run_toolINS0_15llvm_symbolizerEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace10__run_toolINS0_4atosEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace10__run_toolINS0_9addr2lineEEEbRNS0_4baseB8ne220000ERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_15llvm_symbolizerEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_4atosEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace17__executable_nameINS0_9addr2lineEE3getEv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace18__executable_worksEPKc', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbv', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000011find_imagesERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000012find_symbolsERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne22000016find_source_locsERNS0_5arenaE', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace4baseB8ne2200007currentERNS0_5arenaEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6images15main_prog_imageB8ne220000Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6images4findEPmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesC2Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZNSt3__112__stacktrace6imagesixB8ne220000Em', '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 +1153,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 +1334,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 +1536,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'} @@ -1944,6 +1971,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'} @@ -2566,6 +2594,7 @@ {'is_defined': True, 'name': '__ZTVSt9bad_alloc', 'type': 'I'} {'is_defined': True, 'name': '__ZTVSt9exception', 'type': 'I'} {'is_defined': True, 'name': '__ZTVSt9type_info', 'type': 'I'} +{'is_defined': True, 'name': '__ZTWNSt3__112__stacktrace5arena17active_arena_ptr_E', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZThn16_NSt3__19strstreamD0Ev', 'type': 'FUNC'} @@ -2586,6 +2615,9 @@ {'is_defined': True, 'name': '__ZTv0_n24_NSt3__114basic_iostreamIcNS_11char_traitsIcEEED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZTv0_n24_NSt3__19strstreamD0Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '__ZTv0_n24_NSt3__19strstreamD1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_15llvm_symbolizerEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_4atosEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} +{'is_defined': True, 'name': '__ZZNSt3__112__stacktrace24__has_working_executableINS0_9addr2lineEEEbvE3ret', 'size': 0, 'type': 'OBJECT'} {'is_defined': True, 'name': '__ZdaPv', 'type': 'I'} {'is_defined': True, 'name': '__ZdaPvRKSt9nothrow_t', 'type': 'I'} {'is_defined': True, 'name': '__ZdaPvSt11align_val_t', 'type': 'I'} 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 8c55c4385f6f6..7932533c88996 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 @@ -225,6 +225,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'} @@ -622,6 +623,13 @@ {'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__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', '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 +781,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 +962,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'} @@ -1156,7 +1164,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'} @@ -1584,6 +1591,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'} @@ -1593,6 +1601,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'} @@ -1602,6 +1611,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'} @@ -1616,6 +1626,11 @@ {'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__baseIFPSt4bytemEEE', 'size': 16, '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__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFvPSt4bytemEEE', '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'} @@ -1631,6 +1646,11 @@ {'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__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'} @@ -1749,6 +1769,11 @@ {'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__baseIFPSt4bytemEEE', 'size': 41, '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__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 58, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 42, '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'} @@ -1764,6 +1789,11 @@ {'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__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'} @@ -1882,6 +1912,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'} @@ -1902,6 +1933,10 @@ {'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__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/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist index 51caa07a74330..cb93142f99f90 100644 --- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist +++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.noexceptions.nonew.abilist @@ -196,6 +196,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'} @@ -593,6 +594,13 @@ {'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__stacktrace4base16build_stacktraceEmm', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC1Ev', 'type': 'FUNC'} +{'is_defined': True, 'name': '_ZNSt3__112__stacktrace4baseC2Ev', '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'} @@ -744,6 +752,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'} @@ -923,7 +932,6 @@ {'is_defined': True, 'name': '_ZNSt3__113random_deviceclEv', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED1Ev', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvED2Ev', 'type': 'FUNC'} -{'is_defined': True, 'name': '_ZNSt3__113__hash_memoryEPKvm', 'type': 'FUNC'} {'is_defined': True, 'name': '_ZNSt3__113shared_futureIvEaSERKS1_', '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'} @@ -1127,7 +1135,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'} @@ -1555,6 +1562,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'} @@ -1564,6 +1572,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'} @@ -1573,6 +1582,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'} @@ -1587,6 +1597,11 @@ {'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__baseIFPSt4bytemEEE', 'size': 16, '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__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 16, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTINSt3__110__function6__baseIFvPSt4bytemEEE', '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'} @@ -1602,6 +1617,11 @@ {'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__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'} @@ -1720,6 +1740,11 @@ {'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__baseIFPSt4bytemEEE', 'size': 41, '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__function6__baseIFbRKNS_12__stacktrace4toolEEEE', 'size': 58, 'type': 'OBJECT'} +{'is_defined': True, 'name': '_ZTSNSt3__110__function6__baseIFvPSt4bytemEEE', 'size': 42, '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'} @@ -1735,6 +1760,11 @@ {'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__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'} @@ -1853,6 +1883,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'} @@ -1873,6 +1904,10 @@ {'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__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 dd7385bf33a42..91d4b051adac4 100644 --- a/libcxx/modules/std.compat.cppm.in +++ b/libcxx/modules/std.compat.cppm.in @@ -69,9 +69,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 984b18321923c..a70199d6f3b17 100644 --- a/libcxx/modules/std.cppm.in +++ b/libcxx/modules/std.cppm.in @@ -102,6 +102,7 @@ module; #include #include #include +#include #include #include #include @@ -153,9 +154,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..8b71bed583022 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,9 @@ export namespace std { // [stacktrace.basic.hash], hash support using std::hash; -#endif + + // [stacktrace.format], formatting support + using std::formatter; + +#endif // _LIBCPP_STD_VER >= 23 } // namespace std diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index f59fe0e08fccb..95d024f89a425 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -40,6 +40,17 @@ set(LIBCXX_SOURCES ryu/d2fixed.cpp ryu/d2s.cpp ryu/f2s.cpp + stacktrace.cpp + stacktrace/fd.h + stacktrace/images.cpp + stacktrace/impl_generic.cpp + stacktrace/impl_windows.cpp + stacktrace/libbacktrace.h + stacktrace/tools/apple_atos.cpp + stacktrace/tools/gnu_addr2line.cpp + stacktrace/tools/llvm_symbolizer.cpp + stacktrace/tools/tools.h + stacktrace/unwinding.h stdexcept.cpp string.cpp support/runtime/exception_fallback.ipp @@ -335,6 +346,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.cpp b/libcxx/src/stacktrace.cpp new file mode 100644 index 0000000000000..9c751a46ffecc --- /dev/null +++ b/libcxx/src/stacktrace.cpp @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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 <__stacktrace/basic_stacktrace.h> +#include <__stacktrace/images.h> +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +namespace __stacktrace { + +_LIBCPP_EXPORTED_FROM_ABI thread_local arena* arena::active_arena_ptr_{nullptr}; + +_LIBCPP_EXPORTED_FROM_ABI ostream& entry_base::write_to(ostream& __os) const { + // 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) << __addr_; + if (__desc_.size()) { + __os << ": " << __desc_; + } + if (__file_.size()) { + __os << ": " << __file_; + } + if (__line_) { + __os << ":" << std::dec << __line_; + } + return __os; +} + +_LIBCPP_EXPORTED_FROM_ABI ostream& base::write_to(std::ostream& __os) const { + auto __count = __entries_size_(); + if (!__count) { + __os << "(empty stacktrace)"; + } else { + for (size_t __i = 0; __i < __count; __i++) { + // Insert newlines between entries (but not before the first or after the last) + if (__i) { + __os << '\n'; + } + __os << " frame " << std::setw(3) << std::setfill(' ') << std::dec << (__i + 1) << ": " + << (stacktrace_entry&)__entry_at_(__i); + } + } + return __os; +} + +_LIBCPP_EXPORTED_FROM_ABI string entry_base::to_string() const { + stringstream __ss; + write_to(__ss); + return __ss.str(); +} + +_LIBCPP_EXPORTED_FROM_ABI string base::to_string() const { + stringstream __ss; + write_to(__ss); + return __ss.str(); +} + +uintptr_t entry_base::adjusted_addr() const { + auto sub = __image_ ? __image_->slide_ : 0; + return __addr_ - sub; +} + +} // namespace __stacktrace + +_LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/src/stacktrace/fd.h b/libcxx/src/stacktrace/fd.h new file mode 100644 index 0000000000000..cd5b8053d0644 --- /dev/null +++ b/libcxx/src/stacktrace/fd.h @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// 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_FD +#define _LIBCPP_STACKTRACE_FD + +#include <__config> +#include +#include +#include +#include +#include +#include +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +/* Wraps a C file descriptor, converting to/from `int`s. + * Not copyable; should only be moved, so that it only has one "owner". */ +struct _LIBCPP_HIDE_FROM_ABI fd { + int fd_{-1}; // invalid iff negative + + fd() = default; + ~fd() { close(); } + + // To / from plain old ints + fd(int x) : fd_(x) {} + operator int() const { return fd_; } // implicit + + // No copying (other than contorting to/from ints) + fd(fd const&) = delete; + fd& operator=(fd const&) = delete; + + // Moving is ok, and the moved-from object is invalidated (gets fd of -1) + fd(fd&& rhs) { std::swap(fd_, rhs.fd_); } + fd& operator=(fd&& rhs) { return (std::addressof(rhs) == this) ? *this : *new (this) fd(std::move(rhs)); } + + bool valid() const { return fd_ >= 0; } + + void close() { + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } + } + + /** Open `/dev/null` for reading and writing */ + static fd& null_fd() { + static fd ret{::open("/dev/null", O_RDWR)}; + return ret; + } + + static fd open_ro(std::string_view path) { + fd ret = {::open(path.data(), O_RDONLY)}; + return ret; + } + + /** Create pipe pair via `pipe`, assign into these two destination `fd`s */ + static int pipe_pair(fd& read_fd, fd& write_fd) { + int fd_ints[2]; + if (::pipe(fd_ints) == -1) { + return errno; + } + read_fd = fd_ints[0]; + write_fd = fd_ints[1]; + return 0; + } + + struct _LIBCPP_HIDE_FROM_ABI streambuf; + struct _LIBCPP_HIDE_FROM_ABI istream; + struct _LIBCPP_HIDE_FROM_ABI mmap; +}; + +/** Wraps a readable fd using the `streambuf` interface. */ +struct _LIBCPP_HIDE_FROM_ABI fd::streambuf final : std::streambuf { + fd& fd_; + char* buf_; + size_t size_; + + _LIBCPP_HIDE_FROM_ABI streambuf(fd& fd, char* buf, size_t size) : fd_(fd), buf_(buf), size_(size) {} + _LIBCPP_HIDE_FROM_ABI virtual ~streambuf() = default; + + _LIBCPP_HIDE_FROM_ABI int underflow() override { + int count = ::read(fd_, buf_, size_); + if (count <= 0) { + // error or EOF: return eof to stop + return traits_type::eof(); + } + auto ret = int(*buf_); + setg(buf_, buf_, buf_ + count); + return ret; + } +}; + +/** Wraps an `FDInStreamBuffer` in an `istream` */ +struct fd::istream final : std::istream { + fd::streambuf& buf_; + _LIBCPP_HIDE_FROM_ABI virtual ~istream() = default; + _LIBCPP_HIDE_FROM_ABI explicit istream(fd::streambuf& buf) : std::istream(nullptr), buf_(buf) { rdbuf(&buf_); } +}; + +/** Read-only memory mapping. Requires an `fd`, or a path to open an `fd` out of. Takes ownership and destruction duty + * of the fd. */ +struct fd::mmap final { + fd fd_{}; + size_t size_{0}; + std::byte const* addr_{nullptr}; + + _LIBCPP_HIDE_FROM_ABI explicit mmap(std::string_view path) : mmap(fd::open_ro(path)) {} + + _LIBCPP_HIDE_FROM_ABI explicit 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 ~mmap() { + if (addr_) { + ::munmap(const_cast((void const*)addr_), size_); + } + } +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_STACKTRACE_FD diff --git a/libcxx/src/stacktrace/images.cpp b/libcxx/src/stacktrace/images.cpp new file mode 100644 index 0000000000000..fc8769699c8d4 --- /dev/null +++ b/libcxx/src/stacktrace/images.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 +// +//===----------------------------------------------------------------------===// + +// +// OS-specific construction +// + +#include "__config" + +#if defined(__APPLE__) +// MacOS-specific: use the `dyld` loader to access info about loaded Mach-O images. +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include +# include +# include +# include +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +// TODO: consider cases where e.g. libraries are loaded/unloaded, and recomputing image list. + +_LIBCPP_EXPORTED_FROM_ABI images::images() { + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + auto dyld_count = _dyld_image_count(); + for (unsigned i = 0; i < dyld_count && count_ < k_max_images; i++) { + auto& image = images_[count_++]; + image.slide_ = uintptr_t(_dyld_get_image_vmaddr_slide(i)); + image.loaded_at_ = uintptr_t(_dyld_get_image_header(i)); + image.is_main_prog_ = (i == 0); + image.name_ = _dyld_get_image_name(i); + } + std::sort(images_.begin(), images_.begin() + count_); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#elif !defined(_WIN32) +// Non-MacOS and non-Windows, including Linux: assume environment has these headers. +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include +# include +# include +# include +# include + +// TODO: consider cases where e.g. libraries are loaded/unloaded, and recomputing image list. + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +namespace { +int add_image(dl_phdr_info* info, size_t, void* images_v) { + auto& imgs = *(images*)images_v; + if (imgs.count_ == images::k_max_images) { + return 0; + } + auto is_first = (imgs.count_ == 0); + auto& image = imgs.images_.at(imgs.count_++); + // Absolute address at which this ELF is loaded + image.loaded_at_ = info->dlpi_addr; + // This also happens to be the "slide" amount since ELF has zero-relative offsets + image.slide_ = info->dlpi_addr; + image.name_ = info->dlpi_name; + // `dl_iterate_phdr` gives us the main program image first + image.is_main_prog_ = is_first; + if (image.name_.empty() && is_first) { + char buf[PATH_MAX + 1]; + // Disregards errno, but leaves `name_` empty + auto len = readlink("/proc/self/exe", buf, sizeof(buf)); + if (len != -1 && unsigned(len) < sizeof(buf) - 1) { + image.name_ = buf; + } + } + // If we're at the limit, return nonzero to stop iterating + return imgs.count_ == images::k_max_images; +} +} // namespace + +_LIBCPP_EXPORTED_FROM_ABI images::images() { + dl_iterate_phdr(add_image, this); + images_[count_++] = {0uz, 0}; // sentinel at low end + images_[count_++] = {~0uz, 0}; // sentinel at high end + std::sort(images_.begin(), images_.begin() + count_); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/impl_generic.cpp b/libcxx/src/stacktrace/impl_generic.cpp new file mode 100644 index 0000000000000..3c6e727ea8c3d --- /dev/null +++ b/libcxx/src/stacktrace/impl_generic.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 +// +//===----------------------------------------------------------------------===// + +/* +"Generic" implementation for any platform that doesn't have its own special implementation. +(Currently this means any platform other than Windows) +*/ + +#if !defined(_WIN32) + +# include <__config> +# include <__stacktrace/stacktrace_entry.h> + +# include "stacktrace/tools/tools.h" +# include "stacktrace/unwinding.h" +# include <__stacktrace/images.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void +base::current(arena& arena, size_t skip, size_t max_depth) { + if (!max_depth) [[unlikely]] { + return; + } + + /* + Build up the stacktrace entries and fill out their fields the best we can. + An `entry_base` structure looks more or less like: + + __addr_ uintptr_t Instruction address (of a call instruction, faulting insn, ...) + __desc_ string "Description" which we'll say is synonymous with "symbol" + __file_ string Source filename, or if that's not available, program/library path + __line_ int Line number within the source file, or failing that, zero + __image_ image* The image, loaded in by the OS, containing that address (program, library) + + On Windows, there are DLLs which take care of all this (dbghelp, psapi), with essentially + zero overlap with any other OS, so it's in its own file (impl_windows). Otherwise, i.e. + on non-Windows platforms, taking a stacktrace looks like: + + 0. Create the basic_stacktrace, its internal vector, using provided allocator. + (This was handled by the `current` member functions inside `basic_stacktrace`.) + 1. Collect instruction addresses to build up the entries, observing skip and max_depth. + The unwinding library we have available to us should take care of walking the call stack + and finding addresses. + 2. Resolve addresses into the program images (executable, libraries); normalize the addresses + from step 1, as they were seen in the wild, into program-image-relative addresses + (i.e. deal with ASLR) + 3. Resolve adjusted addresses into their symbols; some environments provide this out of the box + (MacOS) and others make this less easy (Linux) + 4. To get the source file and line number, we have to dig through debug information (DWARF format); + we might need the help of an external tool or library. + 4A: Ideally we would have a library inside libcxx, possibly refactored from somewhere within + compiler-rt, lldb, llvm-symbolizer, that could handle all of this. + (XXX we don't have this currently) + 4B: If the local system happens to have a library that does this, that will work too. + Look for: libbacktrace, libdwarf, etc. + (XXX we don't do this yet) + 4C: Use an external tool (i.e. spawn a child process) which can do this. + + */ + + // (1) Collect instruction addresses; build vector, populate their `__addr_`'s + unwind_addrs(*this, skip + 1, max_depth); + if (!__entries_size_()) { + return; + } + + // (2) Map these addresses to their respective program images, populate `__image_` + find_images(arena); + + // (3) Use system loader and/or `dl` to get symbols + find_symbols(arena); + + // (4C) Use an external tool to get source file/line, as well as any missing symbols + find_source_locs(arena); +} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_images(arena& arena) { + images images; + size_t i = 0; + auto* it = entries_begin(); + auto* end = entries_end(); + while (it != end) { + auto& entry = *it++; + images.find(&i, entry.__addr_); + if (auto& image = images[i]) { + entry.__image_ = ℑ + // While we're in this loop, get the executable's path, and tentatively use this for source file. + entry.__file_ = image.name_; + } + } +} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_symbols(arena& arena) {} + +_LIBCPP_EXPORTED_FROM_ABI void base::find_source_locs(arena& arena) { +# if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + (void)(false // + || (__has_working_executable() && __run_tool(*this, arena)) // + || (__has_working_executable() && __run_tool(*this, arena)) // + || (__has_working_executable() && __run_tool(*this, arena))); // +# endif +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/impl_windows.cpp b/libcxx/src/stacktrace/impl_windows.cpp new file mode 100644 index 0000000000000..864d129f4becd --- /dev/null +++ b/libcxx/src/stacktrace/impl_windows.cpp @@ -0,0 +1,258 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if defined(_WIN32) + +# include <__config> +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +// clang-format off + +// +#include "windows.h" +// +#include "dbghelp.h" +#include "psapi.h" +// +#include "common.h" + +#include +#include + +namespace { + +struct dll { + HMODULE module_ {}; + bool loaded_ {}; + + explicit dll(char const* name) : module_(LoadLibrary(name)) {} + + ~dll() { if (module_) { FreeLibrary(module_); } } + + template bool get_func(F* func, char const* name) { + *func = (F) GetProcAddress(module_, name); + return func != nullptr; + } +}; + +struct dbghelp_dll final : dll { + 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 (*SymGetSearchPath) (HANDLE, char const*, DWORD); + 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); + bool (*SymSetSearchPath) (HANDLE, char const*); + + dbghelp_dll() : dll("dbghelp.dll") { + loaded_ = true + && get_func(&ImageNtHeader, "ImageNtHeader") + && get_func(&StackWalk64, "StackWalk64") + && get_func(&SymCleanup, "SymCleanup") + && get_func(&SymFunctionTableAccess64, "SymFunctionTableAccess64") + && get_func(&SymGetLineFromAddr64, "SymGetLineFromAddr64") + && get_func(&SymGetModuleBase64, "SymGetModuleBase64") + && get_func(&SymGetOptions, "SymGetOptions") + && get_func(&SymGetSearchPath, "SymGetSearchPath") + && get_func(&SymGetSymFromAddr64, "SymGetSymFromAddr64") + && get_func(&SymInitialize, "SymInitialize") + && get_func(&SymLoadModule64, "SymLoadModule64") + && get_func(&SymSetOptions, "SymSetOptions") + && get_func(&SymSetSearchPath, "SymSetSearchPath") + ; + } +}; + +struct psapi_dll final : dll { + bool (*EnumProcessModules) (HANDLE, HMODULE*, DWORD, DWORD*); + bool (*GetModuleInformation) (HANDLE, HMODULE, MODULEINFO*, DWORD); + DWORD (*GetModuleBaseName) (HANDLE, HMODULE, char**, DWORD); + + psapi_dll() : dll("psapi.dll") { + loaded_ = true + && get_func(&EnumProcessModules, "EnumProcessModules") + && get_func(&GetModuleInformation, "GetModuleInformation") + && get_func(&GetModuleBaseName, "GetModuleBaseNameA") + ; + } +}; + +struct sym_init_scope { + dbghelp_dll& dbghelp_; + HANDLE proc_; + + sym_init_scope(dbghelp_dll& dbghelp, HANDLE proc) + : dbghelp_(dbghelp), proc_(proc) { + (*dbghelp_.SymInitialize)(proc_, nullptr, true); + } + ~sym_init_scope() { + (*dbghelp_.SymCleanup)(proc_); + } +}; + +} // namespace + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE _LIBCPP_EXPORTED_FROM_ABI void +base::current(arena&, size_t skip, size_t max_depth) { + if (!max_depth) [[unlikely]] { + return; + } + + static psapi_dll psapi; + static dbghelp_dll dbghelp; + if (!psapi.loaded_ || !dbghelp.loaded_) { return; } + + // Not thread-safe according to docs + static std::mutex api_mutex; + std::lock_guard api_guard(api_mutex); + + HANDLE proc; + proc = GetCurrentProcess(); + + HMODULE exe; + if (!(exe = GetModuleHandle(nullptr))) { return; } + + sym_init_scope symscope(dbghelp, proc); + + TCHAR sym_path[MAX_PATH * 4]; + if (!(*dbghelp.SymGetSearchPath)(proc, sym_path, sizeof(sym_path))) { return; } + + TCHAR exe_dir[MAX_PATH]; + if (!GetModuleFileName(nullptr, exe_dir, sizeof(exe_dir))) { return; } + size_t exe_dir_len = strlen(exe_dir); + while (exe_dir_len > 0 && exe_dir[exe_dir_len - 1] != '\\') { exe_dir[--exe_dir_len] = 0; } + if (exe_dir_len > 0) { exe_dir[--exe_dir_len] = 0; } // strip last backslash + + if (!strstr(sym_path, exe_dir)) { + (void) strncat(sym_path, ";", sizeof(sym_path)); + (void) strncat(sym_path, exe_dir, sizeof(sym_path)); + if (!(*dbghelp.SymSetSearchPath)(proc, sym_path)) { return; } + } + + IMAGE_NT_HEADERS* nt_headers; + if (!(nt_headers = (*dbghelp.ImageNtHeader)(exe))) { return; } + + (*dbghelp.SymSetOptions)( + (*dbghelp.SymGetOptions)() + | SYMOPT_LOAD_LINES + | SYMOPT_UNDNAME); + + auto thread = GetCurrentThread(); + auto machine = nt_headers->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; +#if defined(_M_IX86) + frame.AddrPC.Offset = ccx.Eip; + frame.AddrStack.Offset = ccx.Esp; + frame.AddrFrame.Offset = ccx.Ebp; +#elif defined(_M_AMD64) + frame.AddrPC.Offset = ccx.Rip; + frame.AddrStack.Offset = ccx.Rsp; + frame.AddrFrame.Offset = ccx.Rbp; +#elif defined(_M_ARM) + frame.AddrPC.Offset = ccx.Pc; + frame.AddrStack.Offset = ccx.Sp; + frame.AddrFrame.Offset = ccx.Fp; +#elif defined(_M_ARM64) + frame.AddrPC.Offset = ccx.Pc; + frame.AddrStack.Offset = ccx.Sp; + frame.AddrFrame.Offset = ccx.Fp; +#else +#error Unhandled CPU/arch for stacktrace +#endif + + ++skip; // skip call to this `populate` func + while (max_depth) { + if (!(*dbghelp.StackWalk64)( + machine, proc, thread, &frame, &ccx, nullptr, + dbghelp.SymFunctionTableAccess64, dbghelp.SymGetModuleBase64, + nullptr)) { + break; } + + if (skip && skip--) { continue; } + if (!frame.AddrPC.Offset) { break; } + + auto& entry = this->__emplace_entry_(); + // Note: can't differentiate between a signal, SEH exception handler, or a normal function call + entry.__addr_ = frame.AddrPC.Offset - 1; // Back up 1 byte to get into prev insn range + + --max_depth; + } + + DWORD need_bytes = 0; + HMODULE module_handles[1024] {0}; + size_t module_count = 0; // 0 IFF module enumeration failed + if (!(*psapi.EnumProcessModules)( + proc, module_handles, sizeof(module_handles), LPDWORD(&need_bytes))) { + return; + } + module_count = need_bytes / sizeof(HMODULE); + + // Symbols longer than this amount will be truncated. + static constexpr size_t kMaxSymName = 256; + + auto* it = entries_begin(); + auto* end = entries_end(); + while (it != end) { +#if defined(_M_ARM64) || defined(_M_AMD64) + auto& entry = *it++; + char space[sizeof(IMAGEHLP_SYMBOL64) + kMaxSymName + 1]; + auto* sym = (IMAGEHLP_SYMBOL64*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + sym->MaxNameLength = kMaxSymName; + uint64_t symdisp{0}; + DWORD linedisp{0}; + IMAGEHLP_LINE64 line; + if ((*dbghelp.SymGetSymFromAddr64)(proc, entry.__addr_, &symdisp, sym)) { + entry.__desc_ = sym->Name; + } + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if ((*dbghelp.SymGetLineFromAddr64)(proc, entry.__addr_, &linedisp, &line)) { + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } +#else + char space[sizeof(IMAGEHLP_SYMBOL) + kMaxSymName + 1]; + auto* sym = (IMAGEHLP_SYMBOL*)space; + sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); + sym->MaxNameLength = kMaxSymName; + uint32_t symdisp{0}; + DWORD linedisp{0}; + IMAGEHLP_LINE line; + if ((*dbghelp.SymGetSymFromAddr)(proc, entry.__addr_, &symdisp, sym)) { + entry.__desc_ = sym->Name; + } + line.SizeOfStruct = sizeof(IMAGEHLP_LINE); + if ((*dbghelp.SymGetLineFromAddr)(proc, entry.__addr_, &linedisp, &line)) { + entry.__file_ = line.FileName; + entry.__line_ = line.LineNumber; + } +#endif + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/libbacktrace.h b/libcxx/src/stacktrace/libbacktrace.h new file mode 100644 index 0000000000000..c41b242fdfcc6 --- /dev/null +++ b/libcxx/src/stacktrace/libbacktrace.h @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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_LIBBACKTRACE_H +#define __LIBCPP_STACKTRACE_LIBBACKTRACE_H + +#include <__stacktrace/basic_stacktrace.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { +bool try_libbacktrace(); +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#if !defined(_WIN32) && __has_include() + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +auto z = XXXXXXXXXXXXXXXXXXXXXXXX___BACKTRACE___XXXXXXXXXXXXXXXXXXXXXXXX; + +inline bool try_libbacktrace() { return false; } + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#else + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +inline bool try_libbacktrace() { return false; } + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // !defined(_WIN32) && __has_include() + +#endif // __LIBCPP_STACKTRACE_LIBBACKTRACE_H diff --git a/libcxx/src/stacktrace/tools/apple_atos.cpp b/libcxx/src/stacktrace/tools/apple_atos.cpp new file mode 100644 index 0000000000000..7a40a6f0c163f --- /dev/null +++ b/libcxx/src/stacktrace/tools/apple_atos.cpp @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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 __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include + +# include "stacktrace/tools/tools.h" + +// clang-format off + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool atos::build_argv() { + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("-p"); + push_arg("%d", getpid()); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + push_arg("%p", (void*)entry.__addr_); + } + return true; +} + +void atos::parse(entry_base& entry, std::string_view view) const { + // With debug info we should get everything we need in one line: + // main (in testprog) (/Users/steve/code/notes/testprog.cc:208) + + // view: main (in t.tmp.exe) (simple.o0.nosplit.pass.cpp:19) + // advance i to: ^ + size_t i = 0; + while (i < view.size() && !isspace(view[i])) { ++i; } + entry.__desc_ = view.substr(0, i); + view = lstrip(ldrop(view, i)); + + // view: (in t.tmp.exe) (simple.o0.nosplit.pass.cpp:19) + // looking for: ^^^ + auto pos = view.find(") ("); + if (pos == std::string_view::npos) { return; } + view = ldrop(view, pos + 3); // simple.o0.nosplit.pass.cpp:19) + view = drop_suffix(view, ")"); // simple.o0.nosplit.pass.cpp:19 + pos = view.find_last_of(":"); // ^here + if (pos == std::string_view::npos) { return; } + entry.__file_ = view.substr(0, pos); + auto lineno = view.substr(pos + 1); + entry.__line_ = lineno.empty() ? 0 : stoi(string(lineno)); +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + atos tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + str line; // our read buffer + auto* entry_iter = base.entries_begin(); // position at first entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::getline(spawner.stream_, line); // consume a line from stdout + auto view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // skip blank lines + tool.parse(*entry_iter, view); + ++entry_iter; // one line per entry + } + + return true; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/gnu_addr2line.cpp b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp new file mode 100644 index 0000000000000..f857636cc4d42 --- /dev/null +++ b/libcxx/src/stacktrace/tools/gnu_addr2line.cpp @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// 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 __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/tools/tools.h" +# include <__stacktrace/images.h> + +// clang-format off + +// XXX addr2line only supports one input file to resolve addresses for; +// XXX should invoke once for each program image we get in our stacktrace? + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool addr2line::build_argv() { + auto* main_image = images().main_prog_image(); + _LIBCPP_ASSERT(main_image, "could not determine main program image"); + _LIBCPP_ASSERT(!main_image->name_.empty(), "could not determine main program image name"); + if (!(main_image && !main_image->name_.empty())) { + return false; + } + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("--functions"); + push_arg("--demangle"); + push_arg("--basenames"); + push_arg("-e"); + push_arg(main_image->name_); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + push_arg("%p", (void*)entry.adjusted_addr()); + } + return true; +} + +/* +Example: +-- +addr2line \ + --functions --demangle --basenames \ + -e $BUILDDIR/libcxx/test/libcxx/stacktrace/Output/use_available_progs.pass.cpp.dir/t.tmp.exe \ + 0x000100000610 0x000100000618 0x000100000620 + +NOTE: might not demangle even if we ask for `--demangle` +NOTE: currently seeing a malloc double-free in homebrew (macos) binutils 2.45 build of addr2line + (which we ignore) + +Output: (2 lines per input address) +--- +Z5func0v +use_available_progs.pass.cpp:78 +Z5func1v +use_available_progs.pass.cpp:81 +Z5func2v +use_available_progs.pass.cpp:84 +*/ + +void addr2line::parse_sym(entry_base& entry, std::string_view view) const { + if (!view.starts_with("??")) { + // XXX should check for "_Z" prefix (mangled symbol) and use cxxabi.h / demangle? + entry.__desc_ = view; + } +} + +void addr2line::parse_loc(entry_base& entry, std::string_view view) const { + if (!view.starts_with("??")) { + auto colon = view.find_last_of(":"); + if (colon != string_view::npos) { + entry.__file_ = view.substr(0, colon); + entry.__line_ = atoi(view.data() + colon + 1); + } + } +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + addr2line tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + str line ; // our read buffer + auto* entry_iter = base.entries_begin(); // position at first entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::string_view view; + + std::getline(spawner.stream_, line); // consume one line + view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // blank line: restart loop, checking for EOF + tool.parse_sym(*entry_iter, view); // expecting symbol name + + std::getline(spawner.stream_, line); // consume one line + view = tool_base::strip(line); // remove trailing and leading whitespace + if (view.empty()) { continue; } // blank line: restart loop, checking for EOF + tool.parse_loc(*entry_iter, view); // expecting "/path/to/sourcefile.cpp:42" + + ++entry_iter; // one entry per two lines + } + + return true; +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif diff --git a/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp new file mode 100644 index 0000000000000..f31c811a233ed --- /dev/null +++ b/libcxx/src/stacktrace/tools/llvm_symbolizer.cpp @@ -0,0 +1,106 @@ +//===----------------------------------------------------------------------===// +// +// 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 __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/images.h> +# include <__stacktrace/memory.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include + +# include "stacktrace/tools/tools.h" + +// clang-format off + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +bool llvm_symbolizer::build_argv() { + push_arg("/usr/bin/env"); + push_arg(tool_prog_); + push_arg("--demangle"); + push_arg("--no-inlines"); + push_arg("--verbose"); + push_arg("--relativenames"); + push_arg("--functions=short"); + auto* it = base_.entries_begin(); + auto* end = base_.entries_end(); + while (it != end) { + auto& entry = *(entry_base*)(it++); + if (entry.__image_ && !entry.__image_->name_.empty()) { + push_arg("FILE:%s %p", entry.__image_->name_.data(), (void*)entry.adjusted_addr()); + } else { + push_arg("%p", (void*)entry.adjusted_addr()); + } + } + return true; +} + +void llvm_symbolizer::parse(entry_base** entry_iter, std::string_view view) const { + /* + Parsing is most reliable with `--verbose` option (short of having a JSON parser). Example: + + 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 + */ + + if (!view.starts_with(" ")) { // line without leading whitespace starts a new entry + ++*entry_iter; // advance to next entry + _LIBCPP_ASSERT(*entry_iter >= base_.entries_begin(), "out of range"); + _LIBCPP_ASSERT(*entry_iter < base_.entries_end(), "out of range"); + auto& entry = **entry_iter; + if (view != "??") { entry.__desc_ = view; } + + } else if (view.starts_with(" Filename:")) { + auto& entry = **entry_iter; + auto tmp = view.substr(view.find_first_of(":") + 2); // skip ": " + if (tmp != "??") { entry.__file_ = tmp; } + + } else if (view.starts_with(" Line:")) { + auto& entry = **entry_iter; + auto tmp = view; + tmp = tmp.substr(tmp.find_first_of(":") + 2); // skip ": " + if (tmp != "??" && tmp != "0") { entry.__line_ = atoi(tmp.data()); } + } +} + +template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template<> bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base& base, arena& arena) { + llvm_symbolizer tool{base, arena}; + if (!tool.build_argv()) { return false; } + spawner spawner{tool, base}; + if (spawner.errno_) { return false; } + + str line; // our read buffer + auto* entry_iter = base.entries_begin() - 1; // "before first" entry + while (spawner.stream_.good()) { // loop until we get EOF from tool stdout + std::getline(spawner.stream_, line); // consume a line from stdout + auto view = tool_base::rstrip(line); // remove trailing (but not leading) whitespace + if (tool_base::rstrip(view).empty()) { continue; } // skip if line had nothing, or _only_ whitespace + tool.parse(&entry_iter, view); // send to parser (who might update entry_iter) + } + + return true; +} + +} // 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..6a5cc58ea0dbb --- /dev/null +++ b/libcxx/src/stacktrace/tools/tools.h @@ -0,0 +1,381 @@ +//===----------------------------------------------------------------------===// +// +// 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_TOOL_DEFN +#define _LIBCPP_STACKTRACE_TOOLS_TOOL_DEFN + +#include <__config> +#include + +#if __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +# include <__stacktrace/basic_stacktrace.h> +# include <__stacktrace/stacktrace_entry.h> +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include "stacktrace/fd.h" + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct tool_base { + constexpr static size_t k_max_argv_ = base::__absolute_max_depth + 10; + base& base_; + arena& arena_; + char const* tool_prog_; + str argvs_[k_max_argv_]{}; // will hold our generated arg strings + char* argv_[k_max_argv_]{nullptr}; // refers to argvs_ strings as char** (includes null terminator) + size_t argc_{0}; // number of args. Note: argv_[argc_] is nullptr + + tool_base(base& base, arena& arena, char const* tool_prog) : base_(base), arena_(arena), tool_prog_(tool_prog) { + argv_[0] = nullptr; + } + + void push_arg(std::string_view sv) { + _LIBCPP_ASSERT(argc_ < k_max_argv_ - 1, "too many args"); + argvs_[argc_] = sv; // Have to copy the string_view into a new string + argv_[argc_] = argvs_[argc_].data(); // then we have a char pointer into that string + argv_[++argc_] = nullptr; // ensure there's always trailing null after last arg + } + + void push_arg(str __str) { push_arg(std::string_view{__str.data(), __str.size()}); } + + template + void push_arg(char const* format, _Args&&... args) { + push_arg(str::makef(format, std::forward<_Args>(args)...)); + } + + // Helper functions for dealing with string views. + // All these take a string_view (by copy) and return a modified view. Inputs are validated + // and if invalid, the function returns an empty view (instead of throwing). + + /** Drop `n` chars from the start of the string; empty string if `n` exceeds string size */ + static string_view ldrop(string_view sv, size_t n = 1) { + sv.remove_prefix(std::min(sv.size(), n)); + return sv; + } + + /** Drop `n` chars from the end of the string; empty string if `n` exceeds string size */ + static string_view rdrop(string_view sv, size_t n = 1) { + sv.remove_suffix(std::min(sv.size(), n)); + return sv; + } + + /** Strip whitespace from the start of the string */ + static string_view lstrip(string_view sv) { + while (!sv.empty() && isspace(sv.front())) { + sv = ldrop(sv); + }; + return sv; + } + + /** Strip whitespace from the back of the string */ + static string_view rstrip(string_view sv) { + while (!sv.empty() && isspace(sv.back())) { + sv = rdrop(sv); + }; + return sv; + } + + /** Strip whitespace from the start and end of the string */ + static string_view strip(string_view sv) { return lstrip(rstrip(sv)); } + + /** Drop prefix if exists; if not found, and if required, return empty (failure); else original arg */ + static string_view drop_prefix(string_view sv, string_view pre, bool required = true) { + if (sv.starts_with(pre)) { + return ldrop(sv, pre.size()); + } + return required ? string_view{} : sv; + } + + /** Drop suffix if exists; if not found, and if required, return empty (failure); else original arg */ + static string_view drop_suffix(string_view sv, string_view suf, bool required = true) { + if (sv.ends_with(suf)) { + return rdrop(sv, suf.size()); + } + return required ? string_view{} : sv; + } +}; + +/** Set up a `posix_spawn_file_actions_t` for use with a symbolizer with redirected stdout. */ +struct file_actions { + optional fa_{}; + fd stdout_read_; // read end of subprocess's stdout, IFF redir_stdout used + fd stdout_write_; // write end of subprocess's stdout, IFF redir_stdout used + int errno_{}; // set to nonzero if any of these C calls failed + + bool failed() const { return errno_; } + + posix_spawn_file_actions_t* fa() { + if (!fa_) { + fa_.emplace(); + if (posix_spawn_file_actions_init(&fa_.value())) { + errno_ = errno; + _LIBCPP_ASSERT(false, "file_actions_init failed"); + fa_.reset(); + } + } + return fa_ ? &fa_.value() : nullptr; + } + + ~file_actions() { + if (fa_) { + // Do best-effort teardown, ignore errors + (void)posix_spawn_file_actions_destroy(&fa_.value()); + fa_.reset(); + } + } + + file_actions() = default; + file_actions(file_actions const&) = delete; + file_actions& operator=(file_actions const&) = delete; + + file_actions(file_actions&& rhs) { + fa_ = std::move(rhs.fa_); + rhs.fa_.reset(); + stdout_read_ = std::move(rhs.stdout_read_); + stdout_write_ = std::move(rhs.stdout_write_); + errno_ = rhs.errno_; + } + + file_actions& operator=(file_actions&& rhs) { + return (std::addressof(rhs) == this) ? *this : *(new (this) file_actions(std::move(rhs))); + } + + // These have no effect if this is already in `failed` state. + + file_actions& no_stdin() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDIN_FILENO)) { + _LIBCPP_ASSERT(false, "no_stdin: adddup2 failed"); + errno_ = errno; + } + return *this; + } + + file_actions& no_stdout() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDOUT_FILENO)) { + _LIBCPP_ASSERT(false, "no_stdout: adddup2 failed"); + errno_ = errno; + } + return *this; + } + + file_actions& no_stderr() { + if (!failed() && posix_spawn_file_actions_adddup2(fa(), fd::null_fd(), STDERR_FILENO)) { + _LIBCPP_ASSERT(false, "no_stderr: adddup2 failed"); + errno_ = errno; + } + return *this; + } + + file_actions& redir_stdout() { + if (!failed() && fd::pipe_pair(stdout_read_, stdout_write_)) { + _LIBCPP_ASSERT(false, "redir_stdout: pipe failed"); + errno_ = errno; + } else if (!failed() && posix_spawn_file_actions_adddup2(fa(), stdout_write_, STDOUT_FILENO)) { + _LIBCPP_ASSERT(false, "redir_stdout: adddup2 failed"); + errno_ = errno; + } else if (!failed() && posix_spawn_file_actions_addclose(fa(), stdout_read_)) { + _LIBCPP_ASSERT(false, "redir_stdout: pipe failed"); + errno_ = errno; + } + return *this; + } +}; + +/** While in-scope, this enables SIGCHLD default handling (allowing `waitpid` to work). +Restores the old signal action on destruction. + +XXX Thread safety issue almost certainly exists here +*/ +struct sigchld_enable { + struct sigaction old_; + + ~sigchld_enable() { + int res = sigaction(SIGCHLD, &old_, nullptr); // restore old behavior + _LIBCPP_ASSERT(!res, "~sigchld_enable: sigaction failed"); + } + + sigchld_enable() { + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = SIG_DFL; + int res = sigaction(SIGCHLD, &act, &old_); + _LIBCPP_ASSERT(!res, "sigchld_enable: sigaction failed"); + } +}; + +struct pid_waiter { + pid_t pid_{}; + int status_{}; // value is valid iff wait() completed + int errno_{}; // set to nonzero if any of these C calls failed + bool done_{}; + + operator pid_t() const { return pid_; } + bool running() const { return pid_ && !kill(pid_, 0); } + bool failed() const { return errno_; } + + [[nodiscard]] int wait() { + while (!done_) { // Until successful waitpid, or a hard error: + int result; + if (waitpid(pid_, &result, 0) == pid_) { // attempt a blocking wait, updates status_ + if (WIFEXITED(result)) { // process exited? (not signaled) + status_ = WEXITSTATUS(result); // get exit code + done_ = true; // + } else if (WIFSIGNALED(result)) { // if signaled: + status_ = -WTERMSIG(result); // use negative to indicate signal + done_ = true; // + } + } else if (errno != EINTR) { // for errors other than interrupted syscall (which we retry), + errno_ = errno; // record the error, putting this in `failed` state + done_ = true; // don't bother attempting another wait + status_ = -1; // nonzero bogus value + } + } + return status_; + } + + ~pid_waiter() { + if (pid_ && !done_) { + // this represents a valid but non-waited pid + if (running()) { + kill(pid_, SIGKILL); + } + (void)/* ignore status */ wait(); + } + } +}; + +struct spawner { + tool_base& tool_; + base& base_; + file_actions fa_{}; // redirects stdout for us + char cbuf_[PATH_MAX + 512]; // buffer space for the streambuf: + fd::streambuf sbuf_; // streambuf interface for the istream: + fd::istream stream_; // istream interface from which we can `getline` + sigchld_enable chld_enable_; // temporarily enables SIGCHLD so `waitpid` works + pid_waiter pid_{0}; // set during successful `spawn`, can `waitpid` automatically + int errno_{}; // set to nonzero if any of these C calls failed + + bool failed() const { return errno_; } + + spawner(tool_base& tool, base& base) + : tool_{tool}, + base_(base), + fa_(std::move(file_actions().no_stdin().no_stderr().redir_stdout())), + sbuf_(fa_.stdout_read_, cbuf_, sizeof(cbuf_)), + stream_(sbuf_) { + // Inherit any errors from during fileactions setup + errno_ = fa_.errno_; + if (!failed() && posix_spawnp(&pid_.pid_, tool_.argv_[0], fa_.fa(), nullptr, tool_.argv_, nullptr)) { + _LIBCPP_ASSERT(false, "spawner: posix_spawnp failed"); + errno_ = errno; + } else if (!failed() && close(fa_.stdout_write_)) { + _LIBCPP_ASSERT(false, "spawner: close failed"); + errno_ = errno; + } + } +}; + +template +struct _LIBCPP_EXPORTED_FROM_ABI __executable_name { + static char const* get() { + auto* env_var = T::__override_prog_env; + if (env_var) { + auto* env_val = getenv(env_var); + if (env_val) { + return env_val; + } + } + return T::__default_prog_name; + } +}; + +/** Run `/usr/bin/env $TOOL --help`. Succeeds iff `env` can find the tool in path, and tool exits without error. */ +inline bool _LIBCPP_EXPORTED_FROM_ABI __executable_works(char const* prog_name) { + char const* argv[4] = {"/usr/bin/env", prog_name, "--help", nullptr}; + pid_waiter pid; + auto fa = std::move(file_actions().no_stdin().no_stdout().no_stderr()); + return posix_spawn(&pid.pid_, argv[0], fa.fa(), nullptr, const_cast(argv), nullptr) + ? false // spawn failed (don't care why), can't run prog + : !pid.wait(); // otherwise, tool should return without error +} + +/** Checks (and memoizes) whether tool's binary exists and runs */ +template +inline bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable() { + static bool ret = __executable_works(__executable_name::get()); + return ret; +} + +template +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct llvm_symbolizer; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct addr2line; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct atos; +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +template <> +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +struct llvm_symbolizer : tool_base { + constexpr static char const* __default_prog_name = "llvm-symbolizer"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH"; + + llvm_symbolizer(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse(entry_base** entry_iter, std::string_view view) const; +}; + +struct addr2line : tool_base { + constexpr static char const* __default_prog_name = "addr2line"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH"; + + addr2line(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse_sym(entry_base& entry, std::string_view view) const; + void parse_loc(entry_base& entry, std::string_view view) const; +}; + +struct atos : tool_base { + constexpr static char const* __default_prog_name = "atos"; + constexpr static char const* __override_prog_env = "LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH"; + + atos(base& base, arena& arena) : tool_base{base, arena, __executable_name::get()} {} + bool build_argv(); + void parse(entry_base& entry, std::string_view view) const; +}; + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#endif // __has_include() && _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +#endif // _LIBCPP_STACKTRACE_TOOLS_TOOL_DEFN diff --git a/libcxx/src/stacktrace/unwinding.h b/libcxx/src/stacktrace/unwinding.h new file mode 100644 index 0000000000000..087b04e7b742d --- /dev/null +++ b/libcxx/src/stacktrace/unwinding.h @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// 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_ADDRS_H +#define __LIBCPP_STACKTRACE_UNWIND_ADDRS_H + +#include <__stacktrace/basic_stacktrace.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { +void unwind_addrs(std::__stacktrace::base& base, size_t skip, size_t depth); +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +#ifndef _WIN32 + +/* +Implements `unwind_addrs` using an unwind library, generally `libunwind`. +This will work with the interface provided in either `libunwind.h` or `Unwind.h`; +they provide different ways of using the same library code under the hood. + +On Windows this file will provide no definitions, since a separate implementation +exists for that OS. +*/ + +# if __has_include() + +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +_LIBCPP_NO_TAIL_CALLS _LIBCPP_NOINLINE inline void unwind_addrs(base& base, size_t skip, size_t depth) { + if (!depth) { + return; + } + unw_context_t cx; + unw_getcontext(&cx); + unw_cursor_t cur; + unw_init_local(&cur, &cx); + while (unw_step(&cur) > 0) { + if (skip && skip--) { + continue; + } + if (!depth--) { + break; + } + auto& entry = base.__emplace_entry_(); + unw_get_reg(&cur, UNW_REG_IP, &entry.__addr_); + if (!unw_is_signal_frame(&cur)) { + --entry.__addr_; + } + } +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +# elif __has_include() + +# include + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct unwind_backtrace { + base& base_; + 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 = base_.__emplace_entry_(); + auto& eb = (entry_base&)entry; + eb.__addr_ = (ipBefore ? ip : ip - 1); + 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 inline void unwind_addrs(base& base, size_t skip, size_t depth) { + if (!depth) { + return; + } + unwind_backtrace bt{base, skip + 1, depth}; // skip this call as well + _Unwind_Backtrace(unwind_backtrace::callback, &bt); +} + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +# else +# error need or +# endif + +#endif // _WIN32 + +#endif // __LIBCPP_STACKTRACE_UNWIND_ADDRS_H diff --git a/libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp b/libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp new file mode 100644 index 0000000000000..e1ab24594b4b7 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/only_uses_allocator.pass.cpp @@ -0,0 +1,111 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// UNSUPPORTED: asan, msan, tsan, hwasan, sanitizer-new-delete +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +#include +#include +#include + +/* + * This file includes tests which ensure any allocations performed by `basic_stacktrace` + * are done via the user-provided allocator. We intercept the usual ways to allocate, + * counting the number of calls, through and not through the allocator. + * (This won't work properly with sanitizers, hence the `UNSUPPORTED` above.) + */ + +unsigned new_count; +unsigned del_count; +unsigned custom_alloc; +unsigned custom_dealloc; + +void* operator new(size_t size) { + ++new_count; + return malloc(size); +} +void* operator new[](size_t size) { + ++new_count; + return malloc(size); +} +void operator delete(void* ptr) noexcept { + ++del_count; + free(ptr); +} +void operator delete(void* ptr, size_t) noexcept { + ++del_count; + free(ptr); +} +void operator delete[](void* ptr) noexcept { + ++del_count; + free(ptr); +} +void operator delete[](void* ptr, size_t) noexcept { + ++del_count; + free(ptr); +} + +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) { + ++custom_alloc; + return wrapped_.allocate(n); + } + + auto allocate_at_least(size_t n) { + ++custom_alloc; + return wrapped_.allocate_at_least(n); + } + + void deallocate(T* ptr, size_t n) { + ++custom_dealloc; + wrapped_.deallocate(ptr, n); + } +}; + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + (void)std::stacktrace::current(); + + // Clear these counters in case anything was created/deleted prior to `main`, + // and in case taking a stacktrace involved initialization of objects in bss + // or some other space. + new_count = del_count = 0; + + { + using A = test_alloc; + auto st = std::basic_stacktrace::current(); + assert(custom_alloc > 0); // Ensure allocator was called at some point + } // Exit this scope to destroy stacktrace (as well as allocator) + + assert(custom_alloc == new_count); // All "new" calls should have been through allocator + assert(custom_alloc == custom_dealloc); // and all allocations should be deallocated + + return 0; +} 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..eeb92bd0724b1 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nodebug.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 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g0 + +#include +#include +#include + +int main(int, char**) { + // 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..f9b6f90ca3919 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.nosplit.pass.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 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g + +#include +#include +#include + +int main(int, char**) { + 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")); // this cpp file, and not t.tmp.exe + 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..701183a1a87a0 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o0.split.pass.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 +// ADDITIONAL_COMPILE_FLAGS: -O0 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + 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")); // this cpp file, and not t.tmp.exe + 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..b3e6f41c23a91 --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nodebug.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 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g0 + +#include +#include +#include + +int main(int, char**) { + // 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..be715570db1ec --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.nosplit.pass.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 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g + +#include +#include +#include + +int main(int, char**) { + 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")); // this cpp file, and not t.tmp.exe + 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..32f6dfa1d4acb --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/simple.o3.split.pass.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 +// ADDITIONAL_COMPILE_FLAGS: -O3 -g -gsplit-dwarf + +#include +#include +#include + +int main(int, char**) { + 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/use_available_progs.pass.cpp b/libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp new file mode 100644 index 0000000000000..8c46ffc18780a --- /dev/null +++ b/libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// 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 -gdwarf-3 +// FIXME: also requires _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME + +/* +Note: requires _LIBCPP_STACKTRACE_ALLOW_TOOLS_AT_RUNTIME, as well as at least +one such tool installed on the local system and findable with PATH. +You can also run this locally like so: + +``` +BUILDDIR=build +ninja -C "${BUILDDIR}" cxx-test-depends + +# Build and run via lit. Use the default program names and let `env` try to find full paths. +"${BUILDDIR}/bin/llvm-lit" -sv libcxx/test/libcxx/stacktrace/use_available_progs.pass.cpp + +# To force use of a particular path for a tool (not relying on PATH), specify these env variables +and run the test program directly (`lit` won't pass these variables). Use `/bin/false` to disable a tool. +Examples: + +LIBCXX_STACKTRACE_FORCE_GNU_ADDR2LINE_PATH=/opt/homebrew/Cellar/binutils/2.45/bin/addr2line \ +LIBCXX_STACKTRACE_FORCE_APPLE_ATOS_PATH=/usr/bin/atos \ +LIBCXX_STACKTRACE_FORCE_LLVM_SYMBOLIZER_PATH=/opt/homebrew/Cellar/llvm/20.1.7/bin/llvm-symbolizer \ + $BUILDDIR/libcxx/test/libcxx/stacktrace/Output/use_available_progs.pass.cpp.dir/t.tmp.exe +``` +*/ + +#include <__config> +#include <__stacktrace/memory.h> +#include +#include +#include + +#include <__stacktrace/images.h> + +_LIBCPP_BEGIN_NAMESPACE_STD +namespace __stacktrace { + +struct addr2line; +struct arena; +struct atos; +struct base; +struct llvm_symbolizer; + +template +struct _LIBCPP_EXPORTED_FROM_ABI __executable_name { + static char const* get(); +}; + +template +bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); + +template +bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +extern template struct _LIBCPP_EXPORTED_FROM_ABI __executable_name; +extern template bool _LIBCPP_EXPORTED_FROM_ABI __has_working_executable(); +extern template bool _LIBCPP_EXPORTED_FROM_ABI __run_tool(base&, arena&); + +} // namespace __stacktrace +_LIBCPP_END_NAMESPACE_STD + +int func0() { return 1000; } +constexpr static int _FUNC0_LINE = __LINE__ - 1; + +int func1() { return 1001; } +constexpr static int _FUNC1_LINE = __LINE__ - 1; + +int func2() { return 1002; } +constexpr static int _FUNC2_LINE = __LINE__ - 1; + +namespace { + +/** +Produce a fake stacktrace with 3 entries, having (in order of index) addresses of `func0`, `func1`, `func2`. +Only addresses are populated; no symbols / source locations are in these entries. +The addresses are the base addrs of these functions (not instructions' addresses, nor +are they decremented by 1 to get the calling instruction and not return address, as the unwinder would). +These addresses come from the main program's address space (this one, "use_available_progs.pass.cpp") +so populate their `__image_` with a pointer to the main program image, so address adjustment +(for ASLR) works. +*/ +std::stacktrace fake_stacktrace() { + static std::__stacktrace::images imgs; + static auto* main_image = imgs.main_prog_image(); + + std::stacktrace ret; + auto& base = *(std::__stacktrace::base*)(&ret); + auto& e0 = base.__emplace_entry_(); + e0.__addr_ = uintptr_t(&func0); + e0.__image_ = main_image; + auto& e1 = base.__emplace_entry_(); + e1.__addr_ = uintptr_t(&func1); + e1.__image_ = main_image; + auto& e2 = base.__emplace_entry_(); + e2.__addr_ = uintptr_t(&func2); + e2.__image_ = main_image; + return ret; +} + +void check_stacktrace(std::stacktrace& st) { + assert(st.at(0).native_handle() == uintptr_t(&func0)); + assert(st.at(0).description().contains("func0")); // e.g.: _func0, func0, func0(), other variations maybe + assert(st.at(0).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(0).source_line() == _FUNC0_LINE); + + assert(st.at(1).native_handle() == uintptr_t(&func1)); + assert(st.at(1).description().contains("func1")); + assert(st.at(1).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(1).source_line() == _FUNC1_LINE); + + assert(st.at(2).native_handle() == uintptr_t(&func2)); + assert(st.at(2).description().contains("func2")); + assert(st.at(2).source_file().ends_with("use_available_progs.pass.cpp")); + assert(st.at(2).source_line() == _FUNC2_LINE); +} + +template +int try_tool() { + std::cerr << "*** trying tool: " << std::__stacktrace::__executable_name::get() << '\n'; + if (std::__stacktrace::__has_working_executable()) { + auto st = fake_stacktrace(); + auto& base = (std::__stacktrace::base&)st; + std::__stacktrace::stack_bytes stack_bytes; + std::__stacktrace::byte_pool stack_pool = stack_bytes.pool(); + std::__stacktrace::arena arena(stack_pool, st.get_allocator()); + std::__stacktrace::__run_tool(base, arena); + std::cout << st << std::endl; + check_stacktrace(st); + std::cerr << "... succeeded\n"; + return 1; + } else { + std::cerr << "... not found\n"; + } + return 0; +} + +} // namespace + +int main(int, char**) { + /* + If for some reason all tools failed to run, we don't quite want to declare a success, + so this is false until a tool ran (and succeeded). + + If any of these tools exist, but the stacktrace operation failed when using it, + the `assert`s within that test will abort immediately. + + Therefore, we can't assume one's machine (or CI) has any one of these tools; but assume + it will have at least _something_, and ensure that something works. + */ + int something_worked = 0; + something_worked += try_tool(); + something_worked += try_tool(); + something_worked += try_tool(); + assert(something_worked); + 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 5f906338f4b7c..fa29ac86a2f7e 100644 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -911,6 +911,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.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp new file mode 100644 index 0000000000000..c254c71dcebb8 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/equality.pass.cpp @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// 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.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, ...] + + static_assert(noexcept(st1a == st1a)); + + 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); + + static_assert(noexcept(st2a == st2b)); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp new file mode 100644 index 0000000000000..d01713d03903f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cmp/strong_ordering.pass.cpp @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// 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.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; + + static_assert(noexcept(st1a <=> st1b)); + + 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/assert.current.no_overflow.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.pass.cpp new file mode 100644 index 0000000000000..094ae3b4406ce --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/assert.current.no_overflow.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, has-unix-headers, libcpp-hardening-mode={{extensive|debug}} +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + Hardened requirements for the `current` call with given `skip` and `max_depth` amounts: + https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3697r0.html#basic_stacktrace + Specifically: "Hardened preconditions: skip <= skip + max_depth is true." + + // (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 "check_assertion.h" + +int main(int, char**) { + TEST_LIBCPP_ASSERT_FAILURE( + std::stacktrace::current(1, 0xffffffffffffffff), "sum of skip and max_depth overflows size_type"); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp new file mode 100644 index 0000000000000..db5d8960f05f3 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/copy.pass.cpp @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// 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(const basic_stacktrace& other, const allocator_type& alloc); + basic_stacktrace& operator=(const basic_stacktrace& other); +*/ + +#include +#include + +#include "../test_allocs.h" + +void test_copy_construct() { + auto a = std::stacktrace::current(); + std::stacktrace b{a}; + assert(a == b); +} + +void test_copy_assign() { + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should not propagate + assert(a1 != a2); + } + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } + { + using A = + TestAlloc; + auto s1 = std::basic_stacktrace::current(); + std::basic_stacktrace s2{s1}; + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_copy_construct(); + test_copy_assign(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp new file mode 100644 index 0000000000000..ed1aac95fc83e --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_no_args.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#include + +#include "../test_allocs.h" + +void test_default_construct() { + std::stacktrace st; + assert(st.empty()); +} + +void test_default_construct_noexcept() { + static_assert(noexcept(std::stacktrace())); + + using A1 = std::allocator; + static_assert(std::is_nothrow_default_constructible_v); + static_assert(noexcept(std::basic_stacktrace())); + + using A2 = TestAlloc; + static_assert(!std::is_nothrow_default_constructible_v); + static_assert(!noexcept(std::basic_stacktrace())); +} + +int main(int, char**) { + test_default_construct(); + test_default_construct_noexcept(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.cpp new file mode 100644 index 0000000000000..e4cdc29d68986 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/ctor_with_alloc.pass.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 + explicit basic_stacktrace(const allocator_type& alloc) noexcept; +*/ + +#include +#include +#include + +#include "../test_allocs.h" + +void test_construct_with_alloc() { + std::stacktrace st; + assert(st.empty()); +} + +void test_construct_with_alloc_noexcept() { + static_assert(noexcept(std::stacktrace())); + + using A1 = std::allocator; + static_assert(std::is_nothrow_constructible_v); + using A2 = TestAlloc; + static_assert(!std::is_nothrow_constructible_v); + + static_assert(!noexcept(std::basic_stacktrace(A2()))); +} + +int main(int, char**) { + test_construct_with_alloc(); + test_construct_with_alloc_noexcept(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.cpp new file mode 100644 index 0000000000000..ce5ed4a5c24ba --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_no_args.pass.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: -O0 -g + +/* + (19.6.4.2) + + // [stacktrace.basic.cons], creation and assignment + static basic_stacktrace current(const allocator_type& alloc = allocator_type()) noexcept; +*/ + +#include +#include +#include + +uint32_t test1_line; +uint32_t test2_line; +uint32_t main_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() { + main_line = __LINE__ + 1; // add 1 to get the next line (where the call to `current` occurs) + auto st = test2(); + std::cout << 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().ends_with("current_no_args.pass.cpp")); + assert(st[0].source_line() == test1_line); + assert(st[1]); + assert(st[1].native_handle()); + assert(st[1].description().contains("test2")); + assert(st[1].source_file().ends_with("current_no_args.pass.cpp")); + assert(st[1].source_line() == test2_line); + assert(st[2]); + assert(st[2].native_handle()); + assert(st[2].description().contains("test_current")); + assert(st[2].source_file().ends_with("current_no_args.pass.cpp")); + assert(st[2].source_line() == main_line); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + static_assert(noexcept(std::stacktrace::current())); + test_current(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp new file mode 100644 index 0000000000000..55dc16c1cdc59 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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**) { + static_assert(noexcept(std::stacktrace::current(0))); + test_current_with_skip(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp new file mode 100644 index 0000000000000..e87564d004010 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/current_skip_depth.pass.cpp @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#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(0); + std::cout << st << '\n'; + assert(st.size() >= 2); + auto it = st.begin(); + ++it; + auto entry = *it; // represents our caller, `main` + + // get current trace again, but skip the 1st + st = std::stacktrace::current(1); + std::cout << st << '\n'; + assert(st.size() >= 1); + it = st.begin(); + assert(*it == entry); +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + static_assert(noexcept(std::stacktrace::current(0, 0))); + test_current_with_skip_depth(); + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp new file mode 100644 index 0000000000000..f859fff61e4dc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.cons/move.pass.cpp @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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(basic_stacktrace&& other) noexcept; + basic_stacktrace(basic_stacktrace&& other, const allocator_type& alloc); + basic_stacktrace& operator=(basic_stacktrace&& other) + noexcept(allocator_traits::propagate_on_container_move_assignment::value || + allocator_traits::is_always_equal::value); +*/ + +#include +#include + +#include "../test_allocs.h" + +void test_move_construct() { + auto a = std::stacktrace::current(); + std::stacktrace b{a}; + assert(a == b); +} + +void test_move_assign() { + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + assert(s1 == s2); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should not propagate + assert(a1 != a2); + } + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } + { + using A = + TestAlloc; + auto s0 = std::basic_stacktrace::current(); + std::basic_stacktrace s1{s0}; + std::basic_stacktrace s2(std::move(s0)); + auto a1 = s1.get_allocator(); + auto a2 = s2.get_allocator(); + // Allocator should propagate + assert(a1 == a2); + } +} + +_LIBCPP_NO_TAIL_CALLS +int main(int, char**) { + test_move_construct(); + test_move_assign(); + 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..ccdd195e2980f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.hash.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.6) Hash Support + + template<> struct hash; [1] + template struct hash>; [2] + + The specializations are enabled ([unord.hash]). +*/ + +#include +#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..bdff39b2745d1 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.mod/swap.pass.cpp @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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.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 + +#include "../test_allocs.h" + +int main(int, char**) { + std::stacktrace empty; + auto a = std::stacktrace::current(); + std::stacktrace b(empty); + assert(!a.empty()); + assert(b.empty()); + + a.swap(b); + assert(a.empty()); + assert(!b.empty()); + + // Check `noexcept`: `swap` is noexcept if either: + // (1) the allocator propagates on swap + // (2) if instances of that allocator type are always equal. + + // `AllocPropagate` satisfies the first (but not the second); stacktrace swap should be noexcept + AllocPropagate prop1; + AllocPropagate prop2; + auto prop_st1 = std::basic_stacktrace(prop1); + auto prop_st2 = std::basic_stacktrace(prop2); + static_assert(noexcept(prop_st1.swap(prop_st2))); + + // `AllocNoPropagate` satisfies neither; stacktrace swap should not be noexcept + AllocNoPropagate no_prop1; + AllocNoPropagate no_prop2; + auto no_prop_st1 = std::basic_stacktrace(no_prop1); + auto no_prop_st2 = std::basic_stacktrace(no_prop2); + static_assert(!noexcept(no_prop_st1.swap(no_prop_st2))); + + // `AllocAlwaysEqual` satisfies second; stacktrace swap should be noexcept + AllocAlwaysEqual always_eq1; + AllocAlwaysEqual always_eq2; + auto always_eq_st1 = std::basic_stacktrace(always_eq1); + auto always_eq_st2 = std::basic_stacktrace(always_eq2); + static_assert(noexcept(always_eq_st1.swap(always_eq_st2))); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.pass.cpp new file mode 100644 index 0000000000000..ac9488eec8a0f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/operator_left_shift.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 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (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..e803815ae2ffc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/swap.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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.6) Non-member functions + + template + void swap(basic_stacktrace& a, basic_stacktrace& b) + noexcept(noexcept(a.swap(b))); +*/ + +#include +#include + +#include "../test_allocs.h" + +int main(int, char**) { + std::stacktrace empty; + auto a = std::stacktrace::current(); + std::stacktrace b(empty); + assert(!a.empty()); + assert(b.empty()); + + std::swap(a, b); + assert(a.empty()); + assert(!b.empty()); + + // `AllocPropagate` satisfies the first (but not the second); stacktrace swap should be noexcept + AllocPropagate prop1; + AllocPropagate prop2; + auto prop_st1 = std::basic_stacktrace(prop1); + auto prop_st2 = std::basic_stacktrace(prop2); + static_assert(noexcept(std::swap(prop_st1, prop_st2))); + + // `AllocNoPropagate` satisfies neither; stacktrace swap should not be noexcept + AllocNoPropagate no_prop1; + AllocNoPropagate no_prop2; + auto no_prop_st1 = std::basic_stacktrace(no_prop1); + auto no_prop_st2 = std::basic_stacktrace(no_prop2); + static_assert(!noexcept(std::swap(no_prop_st1, no_prop_st2))); + + // `AllocAlwaysEqual` satisfies second; stacktrace swap should be noexcept + AllocAlwaysEqual always_eq1; + AllocAlwaysEqual always_eq2; + auto always_eq_st1 = std::basic_stacktrace(always_eq1); + auto always_eq_st2 = std::basic_stacktrace(always_eq2); + static_assert(noexcept(std::swap(always_eq_st1, always_eq_st2))); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.pass.cpp new file mode 100644 index 0000000000000..2d74121afe6f4 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.nonmem/to_string.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 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (19.6.4.6) Non-member functions + + string to_string(const stacktrace_entry& f); + + template + string to_string(const basic_stacktrace& st); +*/ + +#include +#include +#include + +int main(int, char**) { + auto a = std::stacktrace::current(); + auto astr = std::to_string(a); + std::cerr << astr << '\n'; + + assert(std::to_string(a[0]).contains("main")); + assert(std::to_string(a[0]).contains("to_string.pass")); + + assert(std::to_string(a).contains("main")); + assert(std::to_string(a).contains("to_string.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..91e3b16f46111 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/at.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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.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..bddfa3a8e1909 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/begin_end.pass.cpp @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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.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(noexcept(st.begin())); + static_assert(noexcept(st.end())); + 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..8edc2cd093e16 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/cbegin_cend.pass.cpp @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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.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(noexcept(st.cbegin())); + static_assert(noexcept(st.cend())); + 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..9c520c2cf61e1 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/crbegin_crend.pass.cpp @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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.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(noexcept(st.crbegin())); + static_assert(noexcept(st.crend())); + 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..ea056da9beec0 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/empty.pass.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.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; + static_assert(noexcept(st.empty())); + 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..f102422d79020 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/get_allocator.pass.cpp @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// 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.3) Observers [stacktrace.basic.obs] + + allocator_type get_allocator() const noexcept; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace st; + static_assert(noexcept(st.get_allocator())); + 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..3dd654ce0a676 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/max_size.pass.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.4.3) Observers [stacktrace.basic.obs] + + size_type max_size() const noexcept; +*/ + +#include +#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(noexcept(st.max_size())); + 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..4258d8b3d178b --- /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 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (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..53e01239bc96d --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/rbegin_rend.pass.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++23 +// ADDITIONAL_COMPILE_FLAGS: -g + +/* + (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(noexcept(st.rbegin())); + static_assert(noexcept(st.rend())); + 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..843c8c8a965fa --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.obs/size.pass.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.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; + static_assert(noexcept(st.size())); + 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..39a52fd9fd856 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/basic.pass.cpp @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#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..37edce6f7f313 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/equality.pass.cpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 +#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); + + static_assert(noexcept(a == b)); + + 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..fb89592a5177f --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cmp/strong_ordering.pass.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// 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 +#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); + + static_assert(noexcept(a <=> b)); + + 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.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.pass.cpp new file mode 100644 index 0000000000000..3c22957a68b32 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_assign.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.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**) { + static_assert(std::is_nothrow_copy_assignable_v); + + auto& e1 = std::stacktrace::current()[0]; + std::stacktrace_entry e2; + static_assert(noexcept(e2 = e1)); + e2 = e1; + assert(e2 == e1); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.cpp new file mode 100644 index 0000000000000..9a5f88ddbd510 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/copy_construct.pass.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.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**) { + static_assert(std::is_nothrow_copy_constructible_v); + + auto& e1 = std::stacktrace::current()[0]; + static_assert(noexcept(std::stacktrace_entry(e1))); + std::stacktrace_entry e2(e1); + assert(e2 == e1); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.pass.cpp new file mode 100644 index 0000000000000..7bf2a171eb745 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.cons/default.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.2) Constructors [stacktrace.entry.cons] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.cons], constructors + constexpr stacktrace_entry() noexcept; +} +*/ + +#include +#include +#include + +int main(int, char**) { + static_assert(std::is_nothrow_default_constructible_v); + + std::stacktrace_entry entry; + // "Postconditions: *this is empty." + assert(!entry); + + return 0; +} diff --git a/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.cpp new file mode 100644 index 0000000000000..fa11b419ff9ee --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/native_handle.pass.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 e; + static_assert(noexcept(e.native_handle())); + assert(e.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..5bc64a6a907bc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.obs/operator_bool.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.3.3) Observers [stacktrace.entry.obs] + +namespace std { + class stacktrace_entry { + // [stacktrace.entry.obs], observers + constexpr explicit operator bool() const noexcept; +*/ + +#include +#include +#include + +int main(int, char**) { + std::stacktrace_entry e; + // "Returns: false if and only if *this is empty." + assert(!e); + // Now set addr to something nonzero + *(uintptr_t*)(&e) = uintptr_t(&main); + assert(e.native_handle() == uintptr_t(&main)); + assert(e); + + static_assert(noexcept(bool(e))); + + 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..c681caa3a36e0 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/description.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.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..6c6ad520f09bc --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_file.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.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..6d171238f58b7 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/entry.query/source_line.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 + +/* + (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.pass.cpp b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.pass.cpp new file mode 100644 index 0000000000000..acd04ae33dfc3 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/format/formatter_stacktrace_entry.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.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/diagnostics/stacktrace/test_allocs.h b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h new file mode 100644 index 0000000000000..42138700551e7 --- /dev/null +++ b/libcxx/test/std/diagnostics/stacktrace/test_allocs.h @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/* +Allocator class useful for testing various propagation, always-equal scenarios. +*/ + +#ifndef _LIBCPP_STACKTRACE_TEST_ALLOCS_H +#define _LIBCPP_STACKTRACE_TEST_ALLOCS_H + +#include +#include + +template +struct TestAlloc { + using size_type = size_t; + using value_type = T; + using pointer = T*; + using const_pointer = T const*; + + using Self = TestAlloc; + + template + using Other = TestAlloc; + + using propagate_on_container_copy_assignment = typename std::bool_constant<_KPropagate>; + using propagate_on_container_move_assignment = typename std::bool_constant<_KPropagate>; + using propagate_on_container_swap = typename std::bool_constant<_KPropagate>; + using is_always_equal = typename std::bool_constant<_KAlwaysEqual>; + + auto select_on_container_copy_construction(this auto& self) { return _KPropagate ? self : Self(); } + + template + struct rebind { + using other = Other; + }; + + static std::shared_ptr> new_alloc() { + return std::make_shared>(); + } + + static std::shared_ptr> global_alloc() { + static auto ret = new_alloc(); + return ret; + } + + /** Type-erased allocator used for servicing allocate and deallocate. + Two `TestAlloc`'s are equal IFF they contain the same alloc pointer. + Always-equal `TestAlloc`'s get a pointer to a shared `global_alloc`. */ + std::shared_ptr> alloc_; + + /** Instances are equal IFF they have the same alloc pointer (even if this is "always_equals", + since such instances point to the global alloc). */ + bool operator==(auto const& rhs) const noexcept { return alloc_.get() == rhs.alloc_.get(); } + + /** Construct with a new alloc, or, if always-equal, the global alloc. */ + TestAlloc() noexcept(_KNoExCtors) : alloc_(_KAlwaysEqual ? global_alloc() : new_alloc()) {} + + template + TestAlloc(Other const& rhs) : alloc_(rhs.alloc_) {} + + template + TestAlloc& operator=(Other const& rhs) { + alloc_ = rhs.alloc_; + } + + std::allocator& alloc() { return *(std::allocator*)alloc_.get(); } + + T* allocate(size_t n) noexcept(_KNoExAlloc) { return alloc().allocate(n); } + auto allocate_at_least(size_t n) noexcept(_KNoExAlloc) { return alloc().allocate_at_least(n); } + void deallocate(T* ptr, size_t n) noexcept(_KNoExAlloc) { return alloc().deallocate(ptr, n); } +}; + +// For convenience and readability: + +template +using AllocPropagate = + TestAlloc; + +template +using AllocNoPropagate = + TestAlloc; + +template +using AllocAlwaysEqual = + TestAlloc; + +#endif 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 7bd8e8979e6f3..e9b95901b9444 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 @@ -5937,17 +5937,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 @@ -7900,17 +7894,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 d9317e00e3f4a..0efe7d06812f8 100644 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1313,7 +1313,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 d06271a7908cc..410ffce8795a2 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", ])) @@ -198,6 +197,7 @@ def __hash__(self) -> int: "print": "// UNSUPPORTED: no-filesystem, c++03, c++11, c++14, c++17, c++20, availability-fp_to_chars-missing", # TODO PRINT investigate "semaphore": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17", "shared_mutex": "// UNSUPPORTED: no-threads, c++03, c++11", + "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", "thread": "// UNSUPPORTED: no-threads, c++03", @@ -244,6 +244,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"],