Skip to content

Commit ffd3e4c

Browse files
committed
Add CxxException to wrap a C++ exception
The exception object can be passed as a pointer via `CxxException` error through Rust frames. A method to create the default message-based `rust::Error` and to drop and clone the exception are provided.
1 parent 03b0c8d commit ffd3e4c

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

src/cxx.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,57 @@ static const char *errorCopy(const char *ptr, std::size_t len) {
454454
return copy;
455455
}
456456

457+
namespace {
458+
template <>
459+
class impl<Error> final {
460+
public:
461+
static Error error_copy(const char *ptr, std::size_t len) noexcept {
462+
Error error;
463+
error.msg = errorCopy(ptr, len);
464+
error.len = len;
465+
return error;
466+
}
467+
};
468+
} // namespace
469+
470+
// Ensure statically that `std::exception_ptr` is really a pointer.
471+
static_assert(sizeof(std::exception_ptr) == sizeof(void *),
472+
"Unsupported std::exception_ptr size");
473+
static_assert(alignof(std::exception_ptr) == alignof(void *),
474+
"Unsupported std::exception_ptr alignment");
475+
457476
extern "C" {
458477
const char *cxxbridge1$error(const char *ptr, std::size_t len) noexcept {
459478
return errorCopy(ptr, len);
460479
}
480+
481+
void *cxxbridge1$default_exception(const char *ptr, std::size_t len) noexcept {
482+
// Construct an `std::exception_ptr` for the default `rust::Error` in the
483+
// space provided by the pointer itself (placement new).
484+
//
485+
// The `std::exception_ptr` itself is just a pointer, so this effectively
486+
// converts it to the pointer.
487+
void *eptr;
488+
new (&eptr) std::exception_ptr(
489+
std::make_exception_ptr(impl<Error>::error_copy(ptr, len)));
490+
return eptr;
491+
}
492+
493+
void cxxbridge1$drop_exception(char *ptr) noexcept {
494+
// Implement the `drop` for `CxxException` on the Rust side, which is just a
495+
// pointer to the exception stored in `std::exception_ptr`.
496+
auto eptr = std::move(*reinterpret_cast<std::exception_ptr *>(&ptr));
497+
// eptr goes out of scope and deallocates `std::exception_ptr`.
498+
}
499+
500+
void *cxxbridge1$clone_exception(const char *ptr) noexcept {
501+
// Implement the `clone` for `CxxException` on the Rust side, which is just a
502+
// pointer to the exception stored in `std::exception_ptr`.
503+
void *eptr;
504+
new (&eptr) std::exception_ptr(
505+
*reinterpret_cast<const std::exception_ptr *const>(&ptr));
506+
return eptr;
507+
}
461508
} // extern "C"
462509

463510
Error::Error(const Error &other)

src/result.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,63 @@ pub struct PtrLen {
1717
pub len: usize,
1818
}
1919

20+
extern "C" {
21+
/// Helper to construct the default exception from the error message.
22+
#[link_name = "cxxbridge1$default_exception"]
23+
fn default_exception(ptr: *const u8, len: usize) -> *mut u8;
24+
/// Helper to clone the instance of `std::exception_ptr` on the C++ side.
25+
#[link_name = "cxxbridge1$clone_exception"]
26+
fn clone_exception(ptr: *const u8) -> *mut u8;
27+
/// Helper to drop the instance of `std::exception_ptr` on the C++ side.
28+
#[link_name = "cxxbridge1$drop_exception"]
29+
fn drop_exception(ptr: *mut u8);
30+
}
31+
32+
/// C++ exception containing `std::exception_ptr`.
33+
///
34+
/// This object is the Rust wrapper over `std::exception_ptr`, so it owns the exception pointer.
35+
/// I.e., the exception is either referenced by a `std::exception_ptr` on the C++ side or the
36+
/// reference is moved to this object on the Rust side.
37+
#[repr(C)]
38+
#[must_use]
39+
pub struct CxxException(NonNull<u8>);
40+
41+
impl CxxException {
42+
/// Construct the default `rust::Error` exception from the specified `exc_text`.
43+
pub fn new_default(exc_text: &str) -> Self {
44+
let exception_ptr = unsafe {
45+
default_exception(exc_text.as_ptr(), exc_text.len())
46+
};
47+
CxxException(
48+
NonNull::new(exception_ptr)
49+
.expect("Exception conversion returned a null pointer")
50+
)
51+
}
52+
}
53+
54+
impl Clone for CxxException {
55+
fn clone(&self) -> Self {
56+
let clone_ptr = unsafe { clone_exception(self.0.as_ptr()) };
57+
Self(
58+
NonNull::new(clone_ptr)
59+
.expect("Exception cloning returned a null pointer")
60+
)
61+
}
62+
}
63+
64+
impl Drop for CxxException {
65+
fn drop(&mut self) {
66+
unsafe { drop_exception(self.0.as_ptr()) };
67+
}
68+
}
69+
70+
// SAFETY: This is safe, since the C++ exception referenced by `std::exception_ptr`
71+
// is not thread-local.
72+
unsafe impl Send for CxxException {}
73+
// SAFETY: This is safe, since the C++ exception referenced by `std::exception_ptr`
74+
// can be shared across threads read-only.
75+
unsafe impl Sync for CxxException {}
76+
2077
#[repr(C)]
2178
pub union Result {
2279
err: PtrLen,

0 commit comments

Comments
 (0)