From 26e009c688c1c990ae30656681193041ffe50313 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 18 Aug 2024 10:11:47 -0600 Subject: [PATCH] Add `cpptrace::from_current` (#155) --- CMakeLists.txt | 1 + README.md | 109 ++++++++++- include/cpptrace/cpptrace.hpp | 1 + include/cpptrace/from_current.hpp | 61 +++++++ res/from_current.png | Bin 0 -> 35484 bytes src/cpptrace.cpp | 12 +- src/from_current.cpp | 293 ++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/unit/from_current.cpp | 143 +++++++++++++++ 9 files changed, 612 insertions(+), 9 deletions(-) create mode 100644 include/cpptrace/from_current.hpp create mode 100644 res/from_current.png create mode 100644 src/from_current.cpp create mode 100644 test/unit/from_current.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ac6d9a0..09c69ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,6 +214,7 @@ target_sources( src/binary/safe_dl.cpp src/cpptrace.cpp src/ctrace.cpp + src/from_current.cpp src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_nothing.cpp src/demangle/demangle_with_winapi.cpp diff --git a/README.md b/README.md index ca75eb1..4b0421a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md). - [Raw Traces](#raw-traces) - [Utilities](#utilities) - [Configuration](#configuration) - - [Traced Exceptions](#traced-exceptions) + - [Traces From All Exceptions](#traces-from-all-exceptions) + - [How it works](#how-it-works) + - [Performance](#performance) + - [Traced Exception Objects](#traced-exception-objects) - [Wrapping std::exceptions](#wrapping-stdexceptions) - [Exception handling with cpptrace](#exception-handling-with-cpptrace) - [Signal-Safe Tracing](#signal-safe-tracing) @@ -80,7 +83,30 @@ const auto raw_trace = cpptrace::generate_raw_trace(); raw_trace.resolve().print(); ``` -Cpptrace also provides exception types that store stack traces: +Cpptrace provides a way to produce stack traces on arbitrary exceptions. More information on this system +[below](#traces-from-all-exceptions). +```cpp +#include +void foo() { + throw std::runtime_error("foo failed"); +} +int main() { + CPPTRACE_TRY { + foo(); + } CPPTRACE_CATCH(const std::exception& e) { + std::cerr<<"Exception: "< @@ -89,7 +115,7 @@ void trace() { } ``` -![Inlining](res/exception.png) +![Exception](res/exception.png) Additional notable features: @@ -342,11 +368,70 @@ namespace cpptrace { } ``` -### Traced Exceptions +### Traces From All Exceptions -Cpptrace provides an interface for a traced exceptions, `cpptrace::exception`, as well as a set of exception classes -that that generate stack traces when thrown. These exceptions generate relatively lightweight raw traces and resolve -symbols and line numbers lazily if and when requested. +Cpptrace provides `CPPTRACE_TRY` and `CPPTRACE_CATCH` macros that allow a stack trace to be collected from the current +thrown exception object, with no overhead in the non-throwing path: + +```cpp +CPPTRACE_TRY { + foo(); +} CPPTRACE_CATCH(const std::exception& e) { + std::cout<<"Exception: "< [!NOTE] +> This section is largely obsolete now that cpptrace provides a better mechanism for collecting +> [traces from exceptions](#traces-from-exceptions) + Cpptrace exceptions can provide great information for user-controlled exceptions. For non-cpptrace::exceptions that may originate outside of code you control, e.g. the standard library, cpptrace provides some wrapper utilities that can rethrow these exceptions nested in traced cpptrace exceptions. The trace won't be perfect, the trace will start where @@ -446,6 +535,10 @@ std::cout< [!NOTE] +> This section pertains to cpptrace traced exception objects and not the mechanism for collecting +> [traces from arbitrary exceptions](#traces-from-exceptions) + Working with cpptrace exceptions in your code: ```cpp try { @@ -1000,3 +1093,5 @@ This library is under the MIT license. Cpptrace uses libdwarf on linux, macos, and mingw/cygwin unless configured to use something else. If this library is statically linked with libdwarf then the library's binary will itself be LGPL. + +[P2490R3]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2490r3.html diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index b492381..5e66418 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -298,6 +298,7 @@ namespace cpptrace { lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept; ~lazy_trace_holder(); // access + const raw_trace& get_raw_trace() const; stacktrace& get_resolved_trace(); const stacktrace& get_resolved_trace() const; private: diff --git a/include/cpptrace/from_current.hpp b/include/cpptrace/from_current.hpp new file mode 100644 index 0000000..3c31051 --- /dev/null +++ b/include/cpptrace/from_current.hpp @@ -0,0 +1,61 @@ +#ifndef CPPTRACE_FROM_CURRENT_HPP +#define CPPTRACE_FROM_CURRENT_HPP + +#include + +namespace cpptrace { + CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception(); + CPPTRACE_EXPORT const stacktrace& from_current_exception(); + + namespace detail { + #ifdef _MSC_VER + CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE int exception_filter(); + #else + class CPPTRACE_EXPORT unwind_interceptor { + public: + virtual ~unwind_interceptor(); + }; + + CPPTRACE_EXPORT void do_prepare_unwind_interceptor(); + + #ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON + __attribute__((constructor)) inline void prepare_unwind_interceptor() { + // __attribute__((constructor)) inline functions can be called for every source file they're #included in + // there is still only one copy of the inline function in the final executable, though + // LTO can make the redundant constructs fire only once + // do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard + // against it here too as a fast path, not that this should matter for performance + static bool did_prepare = false; + if(!did_prepare) { + do_prepare_unwind_interceptor(); + did_prepare = true; + } + } + #endif + #endif + } +} + +#ifdef _MSC_VER + // this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++ + // exception handling (try/catch) in the same function." + #define CPPTRACE_TRY \ + try { \ + [&]() { \ + __try { \ + [&]() { + #define CPPTRACE_CATCH(param) \ + }(); \ + } __except(::cpptrace::detail::exception_filter()) {} \ + }(); \ + } catch(param) +#else + #define CPPTRACE_TRY \ + try { \ + try { + #define CPPTRACE_CATCH(param) \ + } catch(::cpptrace::detail::unwind_interceptor&) {} \ + } catch(param) +#endif + +#endif diff --git a/res/from_current.png b/res/from_current.png new file mode 100644 index 0000000000000000000000000000000000000000..395e3af44930e01f9c8012ea0b4f0bb41da04b8f GIT binary patch literal 35484 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 aXrk1t38#*gTQXOGk06V@{T literal 0 HcmV?d00001 diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index ddfcc76..0941f0e 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -592,6 +592,14 @@ namespace cpptrace { clear(); } // access + const raw_trace& lazy_trace_holder::get_raw_trace() const { + if(resolved) { + throw std::logic_error( + "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder" + ); + } + return trace; + } stacktrace& lazy_trace_holder::get_resolved_trace() { if(!resolved) { raw_trace old_trace = std::move(trace); @@ -605,7 +613,7 @@ namespace cpptrace { // TODO: Append to message somehow? std::fprintf( stderr, - "Exception occurred while resolving trace in cpptrace::exception object:\n%s\n", + "Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n", e.what() ); } @@ -616,7 +624,7 @@ namespace cpptrace { const stacktrace& lazy_trace_holder::get_resolved_trace() const { if(!resolved) { throw std::logic_error( - "cpptrace::detaillazy_trace_holder::get_resolved_trace called on unresolved const object" + "cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder" ); } return resolved_trace; diff --git a/src/from_current.cpp b/src/from_current.cpp new file mode 100644 index 0000000..8805fb9 --- /dev/null +++ b/src/from_current.cpp @@ -0,0 +1,293 @@ +#include +#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON +#include + +#include +#include + +#include "utils/common.hpp" +#include "utils/microfmt.hpp" +#include "utils/utils.hpp" + +#ifndef _MSC_VER + #include + #include + #if IS_WINDOWS + #include + #else + #include + #include + #if IS_APPLE + #include + #include + #else + #include + #include + #endif + #endif +#endif + +namespace cpptrace { + namespace detail { + thread_local lazy_trace_holder current_exception_trace; + + #ifndef _MSC_VER + CPPTRACE_FORCE_NO_INLINE + bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) { + current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(1)); + return false; + } + + unwind_interceptor::~unwind_interceptor() = default; + + #if defined(__GLIBCXX__) || defined(__GLIBCPP__) + constexpr size_t vtable_size = 11; + #elif defined(_LIBCPP_VERSION) + constexpr size_t vtable_size = 10; + #else + #warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported" + constexpr size_t vtable_size = 0; + #endif + + #if IS_WINDOWS + int get_page_size() { + SYSTEM_INFO info; + GetSystemInfo(&info); + return info.dwPageSize; + } + constexpr auto memory_readonly = PAGE_READONLY; + constexpr auto memory_readwrite = PAGE_READWRITE; + int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) { + DWORD old_protections; + if(!VirtualProtect(page, page_size, protections, &old_protections)) { + throw std::runtime_error( + microfmt::format( + "VirtualProtect call failed: {}", + std::system_error(GetLastError(), std::system_category()).what() + ) + ); + } + return old_protections; + } + void mprotect_page(void* page, int page_size, int protections) { + mprotect_page_and_return_old_protections(page, page_size, protections); + } + void* allocate_page(int page_size) { + auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite); + if(!page) { + throw std::runtime_error( + microfmt::format( + "VirtualAlloc call failed: {}", + std::system_error(GetLastError(), std::system_category()).what() + ) + ); + } + return page; + } + #else + int get_page_size() { + return getpagesize(); + } + constexpr auto memory_readonly = PROT_READ; + constexpr auto memory_readwrite = PROT_READ | PROT_WRITE; + #if IS_APPLE + int get_page_protections(void* page) { + // https://stackoverflow.com/a/12627784/15675011 + mach_vm_size_t vmsize; + mach_vm_address_t address = (mach_vm_address_t)page; + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = + sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT; + memory_object_name_t object; + kern_return_t status = mach_vm_region( + mach_task_self(), + &address, + &vmsize, + VM_REGION_BASIC_INFO, + (vm_region_info_t)&info, + &info_count, + &object + ); + if(status == KERN_INVALID_ADDRESS) { + throw std::runtime_error("vm_region failed with KERN_INVALID_ADDRESS"); + } + int perms = 0; + if(info.protection & VM_PROT_READ) { + perms |= PROT_READ; + } + if(info.protection & VM_PROT_WRITE) { + perms |= PROT_WRITE; + } + if(info.protection & VM_PROT_EXECUTE) { + perms |= PROT_EXEC; + } + return perms; + } + #else + int get_page_protections(void* page) { + auto page_addr = reinterpret_cast(page); + std::ifstream stream("/proc/self/maps"); + stream>>std::hex; + while(!stream.eof()) { + uintptr_t start; + uintptr_t stop; + stream>>start; + stream.ignore(1); + stream>>stop; + if(stream.eof()) { + break; + } + if(stream.fail()) { + throw std::runtime_error("Failure reading /proc/self/maps"); + } + if(page_addr >= start && page_addr < stop) { + stream.ignore(1); + char r, w, x; + stream>>r>>w>>x; + if(stream.fail() || stream.eof()) { + throw std::runtime_error("Failure reading /proc/self/maps"); + } + int perms = 0; + if(r == 'r') { + perms |= PROT_READ; + } + if(w == 'w') { + perms |= PROT_WRITE; + } + if(x == 'x') { + perms |= PROT_EXEC; + } + // std::cerr<<"--parsed: "<::max(), '\n'); + } + throw std::runtime_error("Failed to find mapping with page in /proc/self/maps"); + } + #endif + void mprotect_page(void* page, int page_size, int protections) { + if(mprotect(page, page_size, protections) != 0) { + throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno))); + } + } + int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) { + auto old_protections = get_page_protections(page); + mprotect_page(page, page_size, protections); + return old_protections; + } + void* allocate_page(int page_size) { + auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if(page == MAP_FAILED) { + throw std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno))); + } + return page; + } + #endif + + // allocated below, cleaned up by OS after exit + void* new_vtable_page = nullptr; + + void perform_typeinfo_surgery(const std::type_info& info) { + if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with + return; + } + void* type_info_pointer = const_cast(static_cast(&info)); + void* type_info_vtable_pointer = *static_cast(type_info_pointer); + // the type info vtable pointer points to two pointers inside the vtable, adjust it back + type_info_vtable_pointer = static_cast(static_cast(type_info_vtable_pointer) - 2); + + // for libstdc++ the class type info vtable looks like + // 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00 + // [offset ][typeinfo pointer ] + // 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0 + // [base destructor ][deleting dtor ] + // 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10 + // [__is_pointer_p ][__is_function_p ] + // 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500 + // [__do_catch ][__do_upcast ] + // 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0 + // [__do_upcast ][__do_dyncast ] + // 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8 + // [__do_find_public_src][other ] + // In libc++ the layout is + // [offset ][typeinfo pointer ] + // [base destructor ][deleting dtor ] + // [noop1 ][noop2 ] + // [can_catch ][search_above_dst ] + // [search_below_dst ][has_unambiguous_public_base] + // Relevant documentation/implementation: + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html + // libstdc++ + // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo + // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc + // libc++ + // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo + // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h + + // shouldn't be anything other than 4096 but out of an abundance of caution + auto page_size = get_page_size(); + if(page_size <= 0 && (page_size & (page_size - 1)) != 0) { + throw std::runtime_error( + microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size) + ); + } + + // allocate a page for the new vtable so it can be made read-only later + new_vtable_page = allocate_page(page_size); + // make our own copy of the vtable + memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*)); + // ninja in the custom __do_catch interceptor + auto new_vtable = static_cast(new_vtable_page); + new_vtable[6] = reinterpret_cast(intercept_unwind); + // make the page read-only + mprotect_page(new_vtable_page, page_size, memory_readonly); + + // make the vtable pointer for unwind_interceptor's type_info point to the new vtable + auto type_info_addr = reinterpret_cast(type_info_pointer); + auto page_addr = type_info_addr & ~(page_size - 1); + // make sure the memory we're going to set is within the page + if(type_info_addr - page_addr + sizeof(void*) > static_cast(page_size)) { + throw std::runtime_error("pointer crosses page boundaries"); + } + auto old_protections = mprotect_page_and_return_old_protections( + reinterpret_cast(page_addr), + page_size, + memory_readwrite + ); + *static_cast(type_info_pointer) = static_cast(new_vtable + 2); + mprotect_page(reinterpret_cast(page_addr), page_size, old_protections); + } + + void do_prepare_unwind_interceptor() { + static bool did_prepare = false; + if(!did_prepare) { + try { + perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor)); + } catch(std::exception& e) { + std::fprintf( + stderr, + "Cpptrace: Exception occurred while preparing from_current support: %s\n", + e.what() + ); + } catch(...) { + std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n"); + } + did_prepare = true; + } + } + #else + CPPTRACE_FORCE_NO_INLINE int exception_filter() { + current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(1)); + return EXCEPTION_CONTINUE_SEARCH; + } + #endif + } + + const raw_trace& raw_trace_from_current_exception() { + return detail::current_exception_trace.get_raw_trace(); + } + + const stacktrace& from_current_exception() { + return detail::current_exception_trace.get_resolved_trace(); + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c7b2aae..6136004 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,6 +83,7 @@ if(NOT CPPTRACE_SKIP_UNIT) unit/raw_trace.cpp unit/object_trace.cpp unit/stacktrace.cpp + unit/from_current.cpp ) target_compile_features(unittest PRIVATE cxx_std_20) target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main) diff --git a/test/unit/from_current.cpp b/test/unit/from_current.cpp new file mode 100644 index 0000000..8f1aa91 --- /dev/null +++ b/test/unit/from_current.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace std::literals; + + +// NOTE: returning something and then return stacktrace_from_current_3(line_numbers) * 2; later helps prevent the call from +// being optimized to a jmp +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_3(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + throw std::runtime_error("foobar"); +} + +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_2(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + return stacktrace_from_current_3(line_numbers) * 2; +} + +CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_1(std::vector& line_numbers) { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + return stacktrace_from_current_2(line_numbers) * 2; +} + +TEST(FromCurrent, Basic) { + std::vector line_numbers; + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::runtime_error& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& trace = cpptrace::from_current_exception(); + ASSERT_GE(trace.frames.size(), 4); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos + && frame.symbol.find("lambda") == std::string::npos; // due to msvc + } + ); + ASSERT_NE(it, trace.frames.end()); + size_t i = static_cast(it - trace.frames.begin()); + int j = 0; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1")); + i++; + j++; + ASSERT_LT(i, trace.frames.size()); + ASSERT_LT(j, line_numbers.size()); + EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp")); + EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]); + EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody")); + } +} + +TEST(FromCurrent, CorrectHandler) { + std::vector line_numbers; + CPPTRACE_TRY { + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::logic_error&) { + FAIL(); + } + } CPPTRACE_CATCH(const std::exception& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& trace = cpptrace::from_current_exception(); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos + && frame.symbol.find("lambda") == std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.symbol.find("FromCurrent_CorrectHandler_Test::TestBody") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + } +} + +TEST(FromCurrent, RawTrace) { + std::vector line_numbers; + CPPTRACE_TRY { + line_numbers.insert(line_numbers.begin(), __LINE__ + 1); + stacktrace_from_current_1(line_numbers); + } CPPTRACE_CATCH(const std::exception& e) { + EXPECT_EQ(e.what(), "foobar"sv); + const auto& raw_trace = cpptrace::raw_trace_from_current_exception(); + auto trace = raw_trace.resolve(); + auto it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.filename.find("from_current.cpp") != std::string::npos + && frame.symbol.find("lambda") == std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + it = std::find_if( + trace.frames.begin(), + trace.frames.end(), + [](const cpptrace::stacktrace_frame& frame) { + return frame.symbol.find("FromCurrent_RawTrace_Test::TestBody") != std::string::npos; + } + ); + EXPECT_NE(it, trace.frames.end()); + } +}