Skip to content

Commit 0285901

Browse files
committed
[generator] fix clang crash on exception
1 parent f73037a commit 0285901

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

include/itlib/generator.hpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// itlib-generator v1.01
1+
// itlib-generator v1.02
22
//
33
// Simple coroutine generator class for C++20 and later
44
//
@@ -28,6 +28,8 @@
2828
//
2929
// VERSION HISTORY
3030
//
31+
// 1.02 (2024-07-18) Store exception to work around clang's ridiculous
32+
// and overly complicated handling of coroutines
3133
// 1.01 (2024-07-18) Add missing header for newer, more stringent compilers
3234
// 1.00 (2024-07-17) Initial release
3335
//
@@ -76,6 +78,7 @@
7678
#pragma once
7779
#include <coroutine>
7880
#include <type_traits>
81+
#include <exception>
7982
#include <optional>
8083
#include <utility>
8184

@@ -107,6 +110,7 @@ class generator {
107110

108111
struct promise_type {
109112
generator_value<T> m_val;
113+
std::exception_ptr m_exception;
110114

111115
promise_type() noexcept = default;
112116

@@ -126,7 +130,9 @@ class generator {
126130
return {};
127131
}
128132
void return_void() noexcept {}
129-
void unhandled_exception() { throw; }
133+
void unhandled_exception() noexcept {
134+
m_exception = std::current_exception();
135+
}
130136

131137
value_ret_t val() & noexcept {
132138
return *m_val;
@@ -171,8 +177,7 @@ class generator {
171177

172178
generator_value<T> next() {
173179
if (done()) return {};
174-
m_handle.promise().clear_value();
175-
m_handle.resume();
180+
safe_resume(m_handle);
176181
return std::move(m_handle.promise().m_val);
177182
}
178183

@@ -193,8 +198,7 @@ class generator {
193198
}
194199

195200
pseudo_iterator& operator++() {
196-
m_handle.promise().clear_value();
197-
m_handle.resume();
201+
safe_resume(m_handle);
198202
return *this;
199203
}
200204

@@ -208,7 +212,7 @@ class generator {
208212
};
209213

210214
pseudo_iterator begin() {
211-
m_handle.resume();
215+
safe_resume(m_handle);
212216
return pseudo_iterator{m_handle};
213217
}
214218

@@ -217,6 +221,15 @@ class generator {
217221
}
218222

219223
private:
224+
static void safe_resume(handle_t& h) {
225+
auto& p = h.promise();
226+
p.clear_value();
227+
h.resume();
228+
if (p.m_exception) {
229+
std::rethrow_exception(p.m_exception);
230+
}
231+
}
232+
220233
handle_t m_handle;
221234
explicit generator(handle_t handle) noexcept : m_handle(handle) {}
222235
};

test/t-generator-20.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,16 @@
1111
#include <span>
1212

1313
itlib::generator<int> range(int begin, int end) {
14+
// this absolutely pointless vector here is to trigger clang's ridiculous handling of coroutines
15+
// if we simply rethrow in unhandled_exception(), for some reason it calls the destructors of locals twice
16+
// having a local whose destructor is not safe to call twice will cause a crash
17+
// if we don't crash here on clang, then generator works as expected
18+
std::vector<int> store;
1419
for (int i = begin; i < end; ++i) {
20+
store.emplace_back(i);
21+
}
22+
23+
for (auto i : store) {
1524
if (i == 103) throw std::runtime_error("test exception");
1625
co_yield i;
1726
}

0 commit comments

Comments
 (0)