Skip to content

Commit 5830d18

Browse files
committed
Reword rethrow implementation to use a flag system instead of exception pointers. This also supports MSVC's STL.
1 parent dccf157 commit 5830d18

File tree

8 files changed

+60
-64
lines changed

8 files changed

+60
-64
lines changed

README.md

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -617,10 +617,6 @@ namespace cpptrace {
617617
}
618618
```
619619

620-
> [!NOTE]
621-
> Unfortunately, this is only able to preserve traces under libstdc++ and libc++, not Microsoft's standard library. This
622-
> is due to details of Microsoft's implementation of exceptions and `std::current_exception`.
623-
624620
Example:
625621

626622
```cpp
@@ -650,11 +646,16 @@ rethrown. Cpptrace provides an interface for getting the last rethrow location:
650646

651647
```cpp
652648
namespace cpptrace {
653-
const raw_trace& raw_trace_from_current_exception_last_throw_point();
654-
const stacktrace& from_current_exception_last_throw_point();
649+
const raw_trace& raw_trace_from_current_exception_rethrow();
650+
const stacktrace& from_current_exception_rethrow();
651+
bool current_exception_was_rethrown();
655652
}
656653
```
657654
655+
If the current exception was not rethrown, these functions return references to empty traces.
656+
`current_exception_was_rethrown` can be used to check if the current exception was rethrown and a non-empty rethrow
657+
trace exists.
658+
658659
Example usage, utilizing `foo` and `bar` from the above example:
659660
660661
```cpp
@@ -666,26 +667,11 @@ int main() {
666667
std::cerr<<"Thrown from:"<<std::endl;
667668
cpptrace::from_current_exception().print(); // trace containing main -> foo -> bar
668669
std::cerr<<"Rethrown from:"<<std::endl;
669-
cpptrace::from_current_exception_last_throw_point().print(); // trace containing main -> foo
670+
cpptrace::from_current_exception_rethrow().print(); // trace containing main -> foo
670671
}
671672
}
672673
```
673674

674-
### Implementation Note
675-
676-
In order to preserve the original trace, `cpptrace::rethrow` must store the original trace using the current
677-
`exception_ptr` as a tag. This means both the trace and exception object will remain in memory until a later throw. This
678-
is not something to think twice about in 99% of use cases, however, in applications which are extremely sensitive to
679-
resource constraints or throw very large exception objects this may be worth considering. But, in practice, large
680-
exception objects are incredibly rare. None the less, cpptrace provides a utility to clear the saved `exception_ptr` tag
681-
and saved stack trace in the event it is useful:
682-
683-
```cpp
684-
namespace cpptrace {
685-
void clear_saved_exception_trace_from_rethrow();
686-
}
687-
```
688-
689675
## Traced Exception Objects
690676

691677
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. These

include/cpptrace/exceptions.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ namespace cpptrace {
3939
const raw_trace& get_raw_trace() const;
4040
stacktrace& get_resolved_trace();
4141
const stacktrace& get_resolved_trace() const;
42+
bool is_resolved() const;
4243
private:
4344
void clear();
4445
};

include/cpptrace/from_current.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ namespace cpptrace {
2424
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
2525
CPPTRACE_EXPORT const stacktrace& from_current_exception();
2626

27-
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception_last_throw_point();
28-
CPPTRACE_EXPORT const stacktrace& from_current_exception_last_throw_point();
27+
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception_rethrow();
28+
CPPTRACE_EXPORT const stacktrace& from_current_exception_rethrow();
2929

30-
CPPTRACE_EXPORT void clear_saved_exception_trace_from_rethrow();
30+
CPPTRACE_EXPORT bool current_exception_was_rethrown();
3131

3232
CPPTRACE_EXPORT CPPTRACE_NORETURN CPPTRACE_FORCE_NO_INLINE
3333
void rethrow();

src/exceptions.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ namespace cpptrace {
8989
}
9090
return resolved_trace;
9191
}
92+
bool lazy_trace_holder::is_resolved() const {
93+
return resolved;
94+
}
9295
void lazy_trace_holder::clear() {
9396
if(resolved) {
9497
resolved_trace.~stacktrace();

src/from_current.cpp

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "platform/platform.hpp"
1111
#include "utils/microfmt.hpp"
12+
#include "utils/utils.hpp"
1213

1314
#ifndef _MSC_VER
1415
#include <string.h>
@@ -35,28 +36,20 @@
3536
namespace cpptrace {
3637
namespace detail {
3738
thread_local lazy_trace_holder current_exception_trace;
39+
thread_local lazy_trace_holder saved_rethrow_trace;
3840

39-
struct saved_rethrow_trace_data {
40-
std::exception_ptr ptr{};
41-
lazy_trace_holder rethrown_exception_trace;
42-
};
43-
thread_local saved_rethrow_trace_data saved_rethrow_trace;
44-
45-
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
46-
current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(skip + 1));
47-
}
48-
49-
void save_current_trace_exception_as_rethrow_trace(std::exception_ptr exception) {
50-
// TODO: Maybe move current_exception_trace instead of copy?
51-
saved_rethrow_trace.ptr = std::move(exception);
52-
saved_rethrow_trace.rethrown_exception_trace = current_exception_trace;
41+
bool& get_rethrow_switch() {
42+
static thread_local bool rethrow_switch = false;
43+
return rethrow_switch;
5344
}
5445

55-
lazy_trace_holder& get_current_trace() {
56-
if(saved_rethrow_trace.ptr == std::current_exception()) {
57-
return saved_rethrow_trace.rethrown_exception_trace;
46+
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
47+
auto trace = cpptrace::generate_raw_trace(skip + 1);
48+
if(get_rethrow_switch()) {
49+
saved_rethrow_trace = lazy_trace_holder(std::move(trace));
5850
} else {
59-
return current_exception_trace;
51+
current_exception_trace = lazy_trace_holder(std::move(trace));
52+
saved_rethrow_trace = lazy_trace_holder();
6053
}
6154
}
6255

@@ -340,35 +333,49 @@ namespace cpptrace {
340333
}
341334

342335
const raw_trace& raw_trace_from_current_exception() {
343-
return detail::get_current_trace().get_raw_trace();
336+
return detail::current_exception_trace.get_raw_trace();
344337
}
345338

346339
const stacktrace& from_current_exception() {
347-
return detail::get_current_trace().get_resolved_trace();
340+
return detail::current_exception_trace.get_resolved_trace();
348341
}
349342

350-
const raw_trace& raw_trace_from_current_exception_last_throw_point() {
351-
return detail::current_exception_trace.get_raw_trace();
343+
const raw_trace& raw_trace_from_current_exception_rethrow() {
344+
return detail::saved_rethrow_trace.get_raw_trace();
352345
}
353346

354-
const stacktrace& from_current_exception_last_throw_point() {
355-
return detail::current_exception_trace.get_resolved_trace();
347+
const stacktrace& from_current_exception_rethrow() {
348+
return detail::saved_rethrow_trace.get_resolved_trace();
349+
}
350+
351+
bool current_exception_was_rethrown() {
352+
if(detail::saved_rethrow_trace.is_resolved()) {
353+
return !detail::saved_rethrow_trace.get_resolved_trace().empty();
354+
} else {
355+
return !detail::saved_rethrow_trace.get_raw_trace().empty();
356+
}
357+
}
358+
359+
// called when unwinding starts after rethrowing, after search phase
360+
void rethrow_scope_cleanup() {
361+
detail::get_rethrow_switch() = false;
356362
}
357363

358-
void clear_saved_exception_trace_from_rethrow() {
359-
detail::saved_rethrow_trace.ptr = nullptr;
360-
detail::saved_rethrow_trace.rethrown_exception_trace = detail::lazy_trace_holder{};
364+
detail::scope_guard<void(&)()> setup_rethrow() {
365+
detail::saved_rethrow_trace = detail::current_exception_trace;
366+
detail::get_rethrow_switch() = true;
367+
// will flip the switch back to true as soon as the search phase completes and the unwinding begins
368+
return detail::scope_exit<void(&)()>(rethrow_scope_cleanup);
361369
}
362370

371+
// The non-argument overload is to serve as room for possible future optimization under Microsoft's STL
363372
CPPTRACE_FORCE_NO_INLINE void rethrow() {
364-
// The non-argument overload is to serve as room for possible future optimization under Microsoft's STL
365-
auto ptr = std::current_exception();
366-
detail::save_current_trace_exception_as_rethrow_trace(ptr);
367-
std::rethrow_exception(ptr);
373+
auto guard = setup_rethrow();
374+
std::rethrow_exception(std::current_exception());
368375
}
369376

370377
CPPTRACE_FORCE_NO_INLINE void rethrow(std::exception_ptr exception) {
371-
detail::save_current_trace_exception_as_rethrow_trace(exception);
378+
auto guard = setup_rethrow();
372379
std::rethrow_exception(exception);
373380
}
374381
}

test/unit/tracing/from_current.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ TEST(FromCurrent, Basic) {
4747
static volatile int tco_guard = stacktrace_from_current_1(line_numbers);
4848
(void)tco_guard;
4949
} CPPTRACE_CATCH(const std::runtime_error& e) {
50+
EXPECT_FALSE(cpptrace::current_exception_was_rethrown());
5051
EXPECT_EQ(e.what(), "foobar"sv);
5152
const auto& trace = cpptrace::from_current_exception();
5253
ASSERT_GE(trace.frames.size(), 4);

test/unit/tracing/from_current_z.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ TEST(FromCurrentZ, Basic) {
4848
static volatile int tco_guard = stacktrace_from_current_z_1(line_numbers);
4949
(void)tco_guard;
5050
} CPPTRACE_CATCHZ(const std::runtime_error& e) {
51+
EXPECT_FALSE(cpptrace::current_exception_was_rethrown());
5152
EXPECT_EQ(e.what(), "foobar"sv);
5253
const auto& trace = cpptrace::from_current_exception();
5354
ASSERT_GE(trace.frames.size(), 4);

test/unit/tracing/rethrow.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515

1616
using namespace std::literals;
1717

18-
// only enable for libstdc++ and libc++, not Microsoft's STL
19-
20-
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
21-
2218
static volatile int truthy = 2;
2319

2420
// NOTE: returning something and then return stacktrace_multi_3(line_numbers) * rand(); is done to prevent TCO even
@@ -61,6 +57,7 @@ TEST(Rethrow, RethrowPreservesTrace) {
6157
static volatile int tco_guard = stacktrace_from_current_rethrow_1(line_numbers, rethrow_line_numbers);
6258
(void)tco_guard;
6359
} CPPTRACE_CATCH(const std::runtime_error& e) {
60+
EXPECT_TRUE(cpptrace::current_exception_was_rethrown());
6461
EXPECT_EQ(e.what(), "foobar"sv);
6562
const auto& trace = cpptrace::from_current_exception();
6663
ASSERT_GE(trace.frames.size(), 4);
@@ -112,8 +109,9 @@ TEST(Rethrow, RethrowTraceCorrect) {
112109
static volatile int tco_guard = stacktrace_from_current_rethrow_1(line_numbers, rethrow_line_numbers);
113110
(void)tco_guard;
114111
} CPPTRACE_CATCH(const std::runtime_error& e) {
112+
EXPECT_TRUE(cpptrace::current_exception_was_rethrown());
115113
EXPECT_EQ(e.what(), "foobar"sv);
116-
const auto& rethrow_trace = cpptrace::from_current_exception_last_throw_point();
114+
const auto& rethrow_trace = cpptrace::from_current_exception_rethrow();
117115
ASSERT_GE(rethrow_trace.frames.size(), 4);
118116
auto it = std::find_if(
119117
rethrow_trace.frames.begin(),
@@ -184,6 +182,7 @@ TEST(Rethrow, RethrowDoesntInterfereWithSubsequentTraces) {
184182
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
185183
stacktrace_from_current_basic_1(line_numbers);
186184
} CPPTRACE_CATCH(const std::runtime_error& e) {
185+
EXPECT_FALSE(cpptrace::current_exception_was_rethrown());
187186
EXPECT_EQ(e.what(), "foobar"sv);
188187
const auto& trace = cpptrace::from_current_exception();
189188
ASSERT_GE(trace.frames.size(), 4);
@@ -228,5 +227,3 @@ TEST(Rethrow, RethrowDoesntInterfereWithSubsequentTraces) {
228227
);
229228
}
230229
}
231-
232-
#endif

0 commit comments

Comments
 (0)