Implement a system for preventing redundant tracing during search, and other various improvements for the from_current system (#159)
This commit is contained in:
parent
0249b50698
commit
3b0eb54797
@ -317,6 +317,10 @@ if(NOT CPPTRACE_STD_FORMAT)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_UNPREFIXED_TRY_CATCH)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNPREFIXED_TRY_CATCH)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
SET(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
|
||||
SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
|
||||
|
||||
133
README.md
133
README.md
@ -29,6 +29,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Utilities](#utilities)
|
||||
- [Configuration](#configuration)
|
||||
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
||||
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
|
||||
- [How it works](#how-it-works)
|
||||
- [Performance](#performance)
|
||||
- [Traced Exception Objects](#traced-exception-objects)
|
||||
@ -371,20 +372,27 @@ namespace cpptrace {
|
||||
### Traces From All Exceptions
|
||||
|
||||
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:
|
||||
thrown exception object, with minimal or no overhead in the non-throwing path:
|
||||
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cout<<"Exception: "<<e.what()<<std::endl;
|
||||
std::cout<<cpptrace::from_current_exception().to_string(true)<<std::endl;
|
||||
#include <cpptrace/from_current.hpp>
|
||||
void foo() {
|
||||
throw std::runtime_error("foo failed");
|
||||
}
|
||||
int main() {
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This functionality is entirely opt-in, to access this use `#include <cpptrace/from_current.hpp>`.
|
||||
|
||||
Any declarator `catch` accepts works with `CPPTRACE_CATCH`, including `...`.
|
||||
Any declarator `catch` accepts works with `CPPTRACE_CATCH`, including `...`. This works with any thrown object, not just
|
||||
`std::exceptions`, it even works with `throw 0;`
|
||||
|
||||

|
||||
|
||||
@ -396,36 +404,121 @@ API functions:
|
||||
- `cpptrace::from_current_exception`: Returns a resolved `const stacktrace&` from the current exception. Invalidates
|
||||
references to traces returned by `cpptrace::raw_trace_from_current_exception`.
|
||||
|
||||
There is a performance tradeoff with this functionality: Either the try-block can be zero overhead in the
|
||||
non-throwing path with potential expense in the throwing path, or the try-block can have very minimal overhead
|
||||
in the non-throwing path due to bookkeeping with guarantees about the expense of the throwing path. More details on
|
||||
this tradeoff [below](#performance). Cpptrace provides macros for both sides of this tradeoff:
|
||||
- `CPPTRACE_TRY`/`CPPTRACE_CATCH`: Minimal overhead in the non-throwing path (one `mov` on x86, and this may be
|
||||
optimized out if the compiler is able)
|
||||
- `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ`: Zero overhead in the non-throwing path, potential extra cost in the throwing path
|
||||
|
||||
Note: It's important to not mix the `Z` variants with the non-`Z` variants.
|
||||
|
||||
Unfortunately the try/catch macros are needed to insert some magic to perform a trace during the unwinding search phase.
|
||||
In order to have multiple catch alternatives, either `CPPTRACE_CATCH_ALT` or a normal `catch` must be used:
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception&) { // <- First catch must be CPPTRACE_CATCH
|
||||
// ...
|
||||
} CPPTRACE_CATCH_ALT(const std::exception&) { // <- Ok
|
||||
// ...
|
||||
} catch(const std::exception&) { // <- Also Ok
|
||||
// ...
|
||||
} CPPTRACE_CATCH(const std::exception&) { // <- Not Ok
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note: The current exception is the exception most recently seen by a cpptrace try-catch macro block.
|
||||
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
throw std::runtime_error("foo");
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("foo")
|
||||
CPPTRACE_TRY {
|
||||
throw std::runtime_error("bar");
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar")
|
||||
}
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar"), again
|
||||
}
|
||||
```
|
||||
|
||||
#### Removing the `CPPTRACE_` prefix
|
||||
|
||||
`CPPTRACE_TRY` is a little cumbersome to type. To remove the `CPPTRACE_` prefix you can use the
|
||||
`CPPTRACE_UNPREFIXED_TRY_CATCH` cmake option or the `CPPTRACE_UNPREFIXED_TRY_CATCH` preprocessor definition:
|
||||
|
||||
```cpp
|
||||
TRY {
|
||||
foo();
|
||||
} CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
```
|
||||
|
||||
This is not done by default for macro safety/hygiene reasons. If you do not want `TRY`/`CATCH` macros defined, as they
|
||||
are common macro names, you can easily modify the following snippet to provide your own aliases:
|
||||
|
||||
```cpp
|
||||
#define TRY CPPTRACE_TRY
|
||||
#define CATCH(param) CPPTRACE_CATCH(param)
|
||||
#define TRYZ CPPTRACE_TRYZ
|
||||
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
|
||||
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
|
||||
```
|
||||
|
||||
#### How it works
|
||||
|
||||
C++ does not provide any language support for collecting stack traces when exceptions are thrown, however, exception
|
||||
handling under both the Itanium ABI and by SEH (used to implement C++ exceptions on windows) involves unwinding the
|
||||
stack twice, the first unwind searches for an appropriate `catch` handler, the second actually unwinds the stack and
|
||||
stack twice. The first unwind searches for an appropriate `catch` handler, the second actually unwinds the stack and
|
||||
calls destructors. Since the stack remains intact during the search phase it's possible to collect a stack trace with
|
||||
zero overhead when the `catch` is considered for matching the exception.
|
||||
little to no overhead when the `catch` is considered for matching the exception. The try/catch macros for cpptrace set
|
||||
up a special try/catch system that can collect a stack trace when considered during a search phase.
|
||||
|
||||
N.b.: This mechanism is also discussed in [P2490R3][P2490R3].
|
||||
|
||||
#### Performance
|
||||
|
||||
`CPPTRACE_CATCH` internally generates lightweight raw traces when considered in the search phase. These are quite fast
|
||||
to generate and are only resolved when `cpptrace::from_current_exception` is called.
|
||||
The fundamental mechanism for this functionality is generating a trace when a catch block is considered during an
|
||||
exception handler search phase. Internally a lightweight raw trace is generated upon consideration, which is quite
|
||||
fast. This raw trace is only resolved when `cpptrace::from_current_exception` is called, or when the user manually
|
||||
resolves a trace from `cpptrace::raw_trace_from_current_exception`.
|
||||
|
||||
Currently `CPPTRACE_CATCH` always generates a raw trace when considered as a candidate. That means that if there is a
|
||||
nesting of handlers, either directly in code or as a result of the current call stack, the current stack may be traced
|
||||
mutliple times until the appropriate handler is found.
|
||||
It's tricky, however, from the library's standpoint to check if the catch will end up matching. The library could simply
|
||||
generate a trace every time a `CPPTRACE_CATCH` is considered, however, in a deep nesting of catch's, e.g. as a result of
|
||||
recusion, where a matching handler is not found quickly this could introduce a non-trivial cost in the throwing pat due
|
||||
to tracing the stack multiple times. Thus, there is a performance tradeoff between a little book keeping to prevent
|
||||
duplicate tracing or biting the bullet, so to speak, in the throwing path and unwinding multiple times.
|
||||
|
||||
This should not matter for the vast majority applications given that performance very rarely is critical in throwing
|
||||
paths, how exception handling is usually used, and the shallowness of most call stacks. However, it's something to be
|
||||
aware of.
|
||||
> [!TIP]
|
||||
> The choice between the `Z` and non-`Z` (zero-overhead and non-zero-overhead) variants of the exception handlers should
|
||||
> not matter 99% of the time, however, both are provided in the rare case that it does.
|
||||
>
|
||||
> `CPPTRACE_TRY`/`CPPTRACE_CATCH` could only hurt performance if used in a hot loop where the compiler can't optimize
|
||||
> away the internal bookkeeping, otherwise the bookkeeping should be completely negligible.
|
||||
>
|
||||
> `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ` could only hurt performance when there is an exceptionally deep nesting of exception
|
||||
> handlers in a call stack before a matching handler.
|
||||
|
||||
More information on performance considerations with the zero-overhead variant:
|
||||
|
||||
Tracing the stack multiple times in throwing paths should not matter for the vast majority applications given that:
|
||||
1. Performance very rarely is critical in throwing paths and exceptions should be exceptionally rare
|
||||
2. Exception handling is not usually used in such a way that you could have a deep nesting of handlers before finding a
|
||||
matching handler
|
||||
3. Most call stacks are fairly shallow
|
||||
|
||||
To put the scale of this performance consideration into perspective: In my benchmarking I have found generation of raw
|
||||
traces to take on the order of `100ns` per frame. Thus, even if there were 100 non-matching handlers before a matching
|
||||
handler in a 100-deep call stack the total time would stil be on the order of one millisecond.
|
||||
|
||||
It's possible to avoid this by adding some bookkeeping to the `CPPTRACE_TRY` block. With the tradeoff between
|
||||
zero-overhead try-catch in the happy path and a little extra overhead in the unhappy throwing path I decided to keep
|
||||
try-catch zero-overhead. Should this be a concern to anyone, I'm happy to facilitate both solutions.
|
||||
Nonetheless, I chose a default bookkeeping behavior for `CPPTRACE_TRY`/`CPPTRACE_CATCH` since it is safer with better
|
||||
performance guarantees for the most general possible set of users.
|
||||
|
||||
### Traced Exception Objects
|
||||
|
||||
|
||||
@ -174,6 +174,7 @@ option(CPPTRACE_WERROR_BUILD "" OFF)
|
||||
option(CPPTRACE_POSITION_INDEPENDENT_CODE "" ON)
|
||||
option(CPPTRACE_SKIP_UNIT "" OFF)
|
||||
option(CPPTRACE_STD_FORMAT "" ON)
|
||||
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
|
||||
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
|
||||
set(CPPTRACE_ZSTD_REPO "https://github.com/facebook/zstd.git" CACHE STRING "")
|
||||
set(CPPTRACE_ZSTD_TAG "794ea1b0afca0f020f4e57b6732332231fb23c70" CACHE STRING "") # v1.5.6
|
||||
|
||||
@ -8,15 +8,54 @@ namespace cpptrace {
|
||||
CPPTRACE_EXPORT const stacktrace& from_current_exception();
|
||||
|
||||
namespace detail {
|
||||
// Trace switch is to prevent multiple tracing of stacks on call stacks with multiple catches that don't
|
||||
// immediately match
|
||||
inline bool& get_trace_switch() {
|
||||
static thread_local bool trace_switch = true;
|
||||
return trace_switch;
|
||||
}
|
||||
|
||||
class CPPTRACE_EXPORT try_canary {
|
||||
public:
|
||||
~try_canary() {
|
||||
// Fires when we exit a try block, either via normal means or during unwinding.
|
||||
// Either way: Flip the switch.
|
||||
get_trace_switch() = true;
|
||||
}
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip);
|
||||
|
||||
// this function can be void, however, a char return is used to prevent TCO of the collect_current_trace
|
||||
CPPTRACE_FORCE_NO_INLINE inline char exception_unwind_interceptor(std::size_t skip) {
|
||||
if(get_trace_switch()) {
|
||||
// Done during a search phase. Flip the switch off, no more traces until an unwind happens
|
||||
get_trace_switch() = false;
|
||||
collect_current_trace(skip + 1);
|
||||
}
|
||||
return 42;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE int exception_filter();
|
||||
CPPTRACE_FORCE_NO_INLINE inline int exception_filter() {
|
||||
exception_unwind_interceptor(1);
|
||||
return 0; // EXCEPTION_CONTINUE_SEARCH
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE inline int unconditional_exception_filter() {
|
||||
collect_current_trace(1);
|
||||
return 0; // EXCEPTION_CONTINUE_SEARCH
|
||||
}
|
||||
#else
|
||||
class CPPTRACE_EXPORT unwind_interceptor {
|
||||
public:
|
||||
virtual ~unwind_interceptor();
|
||||
};
|
||||
class CPPTRACE_EXPORT unconditional_unwind_interceptor {
|
||||
public:
|
||||
virtual ~unconditional_unwind_interceptor();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT void do_prepare_unwind_interceptor();
|
||||
CPPTRACE_EXPORT void do_prepare_unwind_interceptor(char(*)(std::size_t));
|
||||
|
||||
#ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
|
||||
__attribute__((constructor)) inline void prepare_unwind_interceptor() {
|
||||
@ -27,7 +66,7 @@ namespace cpptrace {
|
||||
// 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();
|
||||
do_prepare_unwind_interceptor(exception_unwind_interceptor);
|
||||
did_prepare = true;
|
||||
}
|
||||
}
|
||||
@ -41,6 +80,7 @@ namespace cpptrace {
|
||||
// exception handling (try/catch) in the same function."
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
::cpptrace::detail::try_canary cpptrace_try_canary; \
|
||||
[&]() { \
|
||||
__try { \
|
||||
[&]() {
|
||||
@ -49,13 +89,43 @@ namespace cpptrace {
|
||||
} __except(::cpptrace::detail::exception_filter()) {} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#define CPPTRACE_TRYZ \
|
||||
try { \
|
||||
[&]() { \
|
||||
__try { \
|
||||
[&]() {
|
||||
#define CPPTRACE_CATCHZ(param) \
|
||||
}(); \
|
||||
} __except(::cpptrace::detail::unconditional_exception_filter()) {} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#else
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wshadow\"") \
|
||||
::cpptrace::detail::try_canary cpptrace_try_canary; \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
try {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
} catch(::cpptrace::detail::unwind_interceptor&) {} \
|
||||
} catch(param)
|
||||
#define CPPTRACE_TRYZ \
|
||||
try { \
|
||||
try {
|
||||
#define CPPTRACE_CATCHZ(param) \
|
||||
} catch(::cpptrace::detail::unconditional_unwind_interceptor&) {} \
|
||||
} catch(param)
|
||||
#endif
|
||||
|
||||
#define CPPTRACE_CATCH_ALT(param) catch(param)
|
||||
|
||||
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
|
||||
#define TRY CPPTRACE_TRY
|
||||
#define CATCH(param) CPPTRACE_CATCH(param)
|
||||
#define TRYZ CPPTRACE_TRYZ
|
||||
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
|
||||
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@ -32,14 +32,32 @@ namespace cpptrace {
|
||||
namespace detail {
|
||||
thread_local lazy_trace_holder current_exception_trace;
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
|
||||
current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(skip + 1));
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// set only once by do_prepare_unwind_interceptor
|
||||
char (*intercept_unwind_handler)(std::size_t) = nullptr;
|
||||
|
||||
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));
|
||||
if(intercept_unwind_handler) {
|
||||
intercept_unwind_handler(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
bool unconditional_exception_unwind_interceptor(const std::type_info*, const std::type_info*, void**, unsigned) {
|
||||
collect_current_trace(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
using do_catch_fn = decltype(intercept_unwind);
|
||||
|
||||
unwind_interceptor::~unwind_interceptor() = default;
|
||||
unconditional_unwind_interceptor::~unconditional_unwind_interceptor() = default;
|
||||
|
||||
#if IS_LIBSTDCXX
|
||||
constexpr size_t vtable_size = 11;
|
||||
@ -185,10 +203,7 @@ namespace cpptrace {
|
||||
}
|
||||
#endif
|
||||
|
||||
// allocated below, cleaned up by OS after exit
|
||||
void* new_vtable_page = nullptr;
|
||||
|
||||
void perform_typeinfo_surgery(const std::type_info& info) {
|
||||
void perform_typeinfo_surgery(const std::type_info& info, do_catch_fn* do_catch_function) {
|
||||
if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with
|
||||
return;
|
||||
}
|
||||
@ -234,12 +249,13 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
// allocate a page for the new vtable so it can be made read-only later
|
||||
new_vtable_page = allocate_page(page_size);
|
||||
// the OS cleans this up, no cleanup done here for it
|
||||
void* 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<void**>(new_vtable_page);
|
||||
new_vtable[6] = reinterpret_cast<void*>(intercept_unwind);
|
||||
new_vtable[6] = reinterpret_cast<void*>(do_catch_function);
|
||||
// make the page read-only
|
||||
mprotect_page(new_vtable_page, page_size, memory_readonly);
|
||||
|
||||
@ -259,11 +275,16 @@ namespace cpptrace {
|
||||
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, old_protections);
|
||||
}
|
||||
|
||||
void do_prepare_unwind_interceptor() {
|
||||
void do_prepare_unwind_interceptor(char(*intercept_unwind_handler)(std::size_t)) {
|
||||
static bool did_prepare = false;
|
||||
if(!did_prepare) {
|
||||
cpptrace::detail::intercept_unwind_handler = intercept_unwind_handler;
|
||||
try {
|
||||
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor));
|
||||
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor), intercept_unwind);
|
||||
perform_typeinfo_surgery(
|
||||
typeid(cpptrace::detail::unconditional_unwind_interceptor),
|
||||
unconditional_exception_unwind_interceptor
|
||||
);
|
||||
} catch(std::exception& e) {
|
||||
std::fprintf(
|
||||
stderr,
|
||||
@ -276,11 +297,6 @@ namespace cpptrace {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -84,6 +84,7 @@ if(NOT CPPTRACE_SKIP_UNIT)
|
||||
unit/object_trace.cpp
|
||||
unit/stacktrace.cpp
|
||||
unit/from_current.cpp
|
||||
unit/from_current_z.cpp
|
||||
unit/traced_exception.cpp
|
||||
)
|
||||
target_compile_features(unittest PRIVATE cxx_std_20)
|
||||
|
||||
143
test/unit/from_current_z.cpp
Normal file
143
test/unit/from_current_z.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
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_z_3(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
throw std::runtime_error("foobar");
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_z_2(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
return stacktrace_from_current_z_3(line_numbers) * 2;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_z_1(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
return stacktrace_from_current_z_2(line_numbers) * 2;
|
||||
}
|
||||
|
||||
TEST(FromCurrentZ, Basic) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRYZ {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_z_1(line_numbers);
|
||||
} CPPTRACE_CATCHZ(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_z.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<size_t>(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_z.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_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_z.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_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_z.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_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_z.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrentZ_Basic_Test::TestBody"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FromCurrentZ, CorrectHandler) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRYZ {
|
||||
CPPTRACE_TRYZ {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_z_1(line_numbers);
|
||||
} CPPTRACE_CATCHZ(const std::logic_error&) {
|
||||
FAIL();
|
||||
}
|
||||
} CPPTRACE_CATCHZ(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_z.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("FromCurrentZ_CorrectHandler_Test::TestBody") != std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FromCurrentZ, RawTrace) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRYZ {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_z_1(line_numbers);
|
||||
} CPPTRACE_CATCHZ(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_z.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("FromCurrentZ_RawTrace_Test::TestBody") != std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user