Skip to content

Commit b7bb463

Browse files
committed
Fix handling of MSVC std::exception_ptr
Contrary to the most platforms, which just store a single pointer in `std::exception_ptr`, MSVC stores two. Add necessary code to handle this.
1 parent e7b7116 commit b7bb463

File tree

4 files changed

+117
-99
lines changed

4 files changed

+117
-99
lines changed

gen/src/builtin.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,17 @@ pub(super) fn write(out: &mut OutFile) {
225225
writeln!(out, " void *ptr;");
226226
writeln!(out, " ::std::size_t len;");
227227
writeln!(out, "}};");
228+
writeln!(out, "struct CxxException final {{");
229+
writeln!(out, " void *repr_ptr;");
230+
writeln!(out, "#if _MSC_VER >= 1700");
231+
writeln!(out, " void *repr_ptr_2;");
232+
writeln!(out, "#endif");
233+
writeln!(out, "}};");
228234
writeln!(out, "struct CxxResult final {{");
229-
writeln!(out, " void *ptr;");
235+
writeln!(out, " CxxException exc;");
230236
writeln!(out, "}};");
231237
writeln!(out, "struct Exception final {{");
232-
writeln!(out, " CxxResult exc;");
238+
writeln!(out, " CxxResult res;");
233239
writeln!(out, " PtrLen msg;");
234240
writeln!(out, "}};");
235241
}

gen/src/write.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,9 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
856856
}
857857
writeln!(out, ";");
858858
if efn.throws {
859-
writeln!(out, " throw$.exc.ptr = nullptr;");
859+
writeln!(out, " throw$.res.exc.repr_ptr = nullptr;");
860+
#[cfg(target_env = "msvc")]
861+
writeln!(out, " throw$.res.exc.repr_ptr_2 = nullptr;");
860862
writeln!(out, " throw$.msg.ptr = nullptr;");
861863
writeln!(out, " }},");
862864
writeln!(out, " ::rust::detail::Fail(throw$));");
@@ -1124,9 +1126,12 @@ fn write_rust_function_shim_impl(
11241126
writeln!(out, ";");
11251127
if sig.throws {
11261128
out.builtin.rust_error = true;
1127-
writeln!(out, " if (error$.ptr) {{");
1128-
writeln!(out, " void *ppexc = &error$.ptr;");
1129-
writeln!(out, " std::rethrow_exception(std::move(*static_cast<std::exception_ptr*>(ppexc)));");
1129+
writeln!(out, " if (error$.exc.repr_ptr) {{");
1130+
writeln!(out, " void *ppexc = &error$.exc;");
1131+
writeln!(
1132+
out,
1133+
" std::rethrow_exception(std::move(*static_cast<std::exception_ptr*>(ppexc)));"
1134+
);
11301135
writeln!(out, " }}");
11311136
}
11321137
if indirect_return {

src/cxx.cc

+51-43
Original file line numberDiff line numberDiff line change
@@ -467,43 +467,6 @@ class impl<Error> final {
467467
};
468468
} // namespace
469469

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-
476-
extern "C" {
477-
void *cxxbridge1$default_exception(const char *ptr, std::size_t len) noexcept {
478-
// Construct an `std::exception_ptr` for the default `rust::Error` in the
479-
// space provided by the pointer itself (placement new).
480-
//
481-
// The `std::exception_ptr` itself is just a pointer, so this effectively
482-
// converts it to the pointer.
483-
void *eptr;
484-
new (&eptr) std::exception_ptr(
485-
std::make_exception_ptr(impl<Error>::error_copy(ptr, len)));
486-
return eptr;
487-
}
488-
489-
void cxxbridge1$drop_exception(char *ptr) noexcept {
490-
// Implement the `drop` for `CxxException` on the Rust side, which is just a
491-
// pointer to the exception stored in `std::exception_ptr`.
492-
auto eptr = std::move(*reinterpret_cast<std::exception_ptr *>(&ptr));
493-
// eptr goes out of scope and deallocates `std::exception_ptr`.
494-
}
495-
496-
void *cxxbridge1$clone_exception(const char *ptr) noexcept {
497-
// Implement the `clone` for `CxxException` on the Rust side, which is just a
498-
// pointer to the exception stored in `std::exception_ptr`.
499-
void *eptr;
500-
const void *pptr = &ptr;
501-
new (&eptr)
502-
std::exception_ptr(*static_cast<const std::exception_ptr *>(pptr));
503-
return eptr;
504-
}
505-
} // extern "C"
506-
507470
Error::Error(const Error &other)
508471
: std::exception(other),
509472
msg(other.msg ? errorCopy(other.msg, other.len) : nullptr),
@@ -556,19 +519,64 @@ struct PtrLen final {
556519
void *ptr;
557520
std::size_t len;
558521
};
522+
struct CxxException final {
523+
void *repr_ptr;
524+
#if _MSC_VER >= 1700
525+
// NOTE: MSVC uses two pointers to store `std::exception_ptr`
526+
void *repr_ptr_2;
527+
#endif
528+
};
559529
struct CxxResult final {
560-
void *ptr;
530+
CxxException exc;
561531
};
562532
struct Exception final {
563-
CxxResult exc;
533+
CxxResult res;
564534
PtrLen msg;
565535
};
566536
} // namespace repr
567537

538+
// Ensure statically that `std::exception_ptr` is really a pointer.
539+
static_assert(sizeof(std::exception_ptr) == sizeof(repr::CxxException),
540+
"Unsupported std::exception_ptr size");
541+
static_assert(alignof(std::exception_ptr) == alignof(repr::CxxException),
542+
"Unsupported std::exception_ptr alignment");
543+
568544
extern "C" {
569545
repr::PtrLen cxxbridge1$exception(const char *, std::size_t len) noexcept;
546+
547+
repr::CxxException cxxbridge1$default_exception(const char *ptr,
548+
std::size_t len) noexcept {
549+
// Construct an `std::exception_ptr` for the default `rust::Error` in the
550+
// space provided by the pointer itself (placement new).
551+
//
552+
// The `std::exception_ptr` itself is just a pointer, so this effectively
553+
// converts it to the pointer.
554+
repr::CxxException eptr;
555+
new (&eptr) std::exception_ptr(
556+
std::make_exception_ptr(impl<Error>::error_copy(ptr, len)));
557+
return eptr;
570558
}
571559

560+
void cxxbridge1$drop_exception(repr::CxxException ptr) noexcept {
561+
// Implement the `drop` for `CxxException` on the Rust side, which is just a
562+
// pointer to the exception stored in `std::exception_ptr`.
563+
void *pptr = &ptr;
564+
std::exception_ptr eptr = std::move(*static_cast<std::exception_ptr *>(pptr));
565+
// eptr goes out of scope and deallocates `std::exception_ptr`.
566+
}
567+
568+
repr::CxxException
569+
cxxbridge1$clone_exception(repr::CxxException &ptr) noexcept {
570+
// Implement the `clone` for `CxxException` on the Rust side, which is just a
571+
// pointer to the exception stored in `std::exception_ptr`.
572+
repr::CxxException eptr;
573+
const void *pptr = &ptr;
574+
new (&eptr)
575+
std::exception_ptr(*static_cast<const std::exception_ptr *>(pptr));
576+
return eptr;
577+
}
578+
} // extern "C"
579+
572580
namespace detail {
573581
// On some platforms size_t is the same C++ type as one of the sized integer
574582
// types; on others it is a distinct type. Only in the latter case do we need to
@@ -593,16 +601,16 @@ class Fail final {
593601
};
594602

595603
void Fail::operator()(const char *catch$) noexcept {
596-
void *eptr;
604+
repr::CxxException eptr;
597605
new (&eptr)::std::exception_ptr(::std::current_exception());
598-
throw$.exc.ptr = eptr;
606+
throw$.res.exc = eptr;
599607
throw$.msg = cxxbridge1$exception(catch$, std::strlen(catch$));
600608
}
601609

602610
void Fail::operator()(const std::string &catch$) noexcept {
603-
void *eptr;
611+
repr::CxxException eptr;
604612
new (&eptr)::std::exception_ptr(::std::current_exception());
605-
throw$.exc.ptr = eptr;
613+
throw$.res.exc = eptr;
606614
throw$.msg = cxxbridge1$exception(catch$.data(), catch$.length());
607615
}
608616
} // namespace detail

src/result.rs

+49-50
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,61 @@ pub(crate) struct PtrLen {
1919
pub len: usize,
2020
}
2121

22+
/// Representation of C++ `std::exception_ptr` for all targets except MSVC.
23+
///
24+
/// This is a single pointer.
25+
#[repr(C)]
26+
#[derive(Copy, Clone)]
27+
#[cfg(not(target_env = "msvc"))]
28+
struct CxxExceptionRepr {
29+
ptr: NonNull<u8>,
30+
}
31+
32+
/// Representation of C++ `std::exception_ptr` for MSVC.
33+
///
34+
/// Unfortunately, MSVC uses two pointers for `std::exception_ptr`, so we have
35+
/// to account for that.
36+
#[repr(C)]
37+
#[derive(Copy, Clone)]
38+
#[cfg(target_env = "msvc")]
39+
struct CxxExceptionRepr {
40+
ptr: NonNull<u8>,
41+
_ptr2: *mut u8,
42+
}
43+
2244
extern "C" {
2345
/// Helper to construct the default exception from the error message.
2446
#[link_name = "cxxbridge1$default_exception"]
25-
fn default_exception(ptr: *const u8, len: usize) -> *mut u8;
47+
fn default_exception(ptr: *const u8, len: usize) -> CxxExceptionRepr;
2648
/// Helper to clone the instance of `std::exception_ptr` on the C++ side.
2749
#[link_name = "cxxbridge1$clone_exception"]
28-
fn clone_exception(ptr: *const u8) -> *mut u8;
50+
fn clone_exception(ptr: &CxxExceptionRepr) -> CxxExceptionRepr;
2951
/// Helper to drop the instance of `std::exception_ptr` on the C++ side.
3052
#[link_name = "cxxbridge1$drop_exception"]
31-
fn drop_exception(ptr: *mut u8);
53+
fn drop_exception(ptr: CxxExceptionRepr);
3254
}
3355

34-
/// C++ exception containing `std::exception_ptr`.
56+
/// C++ exception containing an `std::exception_ptr`.
3557
///
3658
/// This object is the Rust wrapper over `std::exception_ptr`, so it owns the exception pointer.
3759
/// I.e., the exception is either referenced by a `std::exception_ptr` on the C++ side or the
3860
/// reference is moved to this object on the Rust side.
3961
#[repr(C)]
4062
#[must_use]
41-
pub struct CxxException(NonNull<u8>);
63+
pub struct CxxException(CxxExceptionRepr);
4264

4365
impl CxxException {
4466
/// Construct the default `rust::Error` exception from the specified `exc_text`.
4567
fn new_default(exc_text: &str) -> Self {
46-
let exception_ptr = unsafe {
47-
default_exception(exc_text.as_ptr(), exc_text.len())
48-
};
49-
CxxException(
50-
NonNull::new(exception_ptr)
51-
.expect("Exception conversion returned a null pointer")
52-
)
68+
let exception_repr = unsafe { default_exception(exc_text.as_ptr(), exc_text.len()) };
69+
CxxException(exception_repr)
5370
}
5471
}
5572

5673
impl Clone for CxxException {
5774
fn clone(&self) -> Self {
58-
let clone_ptr = unsafe { clone_exception(self.0.as_ptr()) };
59-
Self(
60-
NonNull::new(clone_ptr)
61-
.expect("Exception cloning returned a null pointer")
62-
)
75+
let exception_repr = unsafe { clone_exception(&self.0) };
76+
Self(exception_repr)
6377
}
6478
}
6579

@@ -71,7 +85,7 @@ impl From<Exception> for CxxException {
7185

7286
impl Drop for CxxException {
7387
fn drop(&mut self) {
74-
unsafe { drop_exception(self.0.as_ptr()) };
88+
unsafe { drop_exception(self.0) };
7589
}
7690
}
7791

@@ -84,49 +98,34 @@ unsafe impl Sync for CxxException {}
8498

8599
/// C++ "result" containing `std::exception_ptr` or a `null`.
86100
#[repr(C)]
87-
pub struct CxxResult(*mut u8);
101+
pub struct CxxResult(Option<CxxException>);
88102

89103
impl From<CxxException> for CxxResult {
90104
fn from(value: CxxException) -> Self {
91-
let res = Self(value.0.as_ptr());
92-
// NOTE: we are copying the pointer, so we need to forget it here,
93-
// otherwise we'd double-free the `std::exception_ptr`.
94-
core::mem::forget(value);
95-
res
96-
}
97-
}
98-
99-
impl Drop for CxxResult {
100-
fn drop(&mut self) {
101-
if !self.0.is_null() {
102-
unsafe { drop_exception(self.0) };
103-
}
105+
Self(Some(value))
104106
}
105107
}
106108

107109
impl CxxResult {
108110
/// Construct an empty `Ok` result.
109111
pub fn new() -> Self {
110-
Self(core::ptr::null_mut())
112+
Self(None)
111113
}
112114
}
113115

114116
impl CxxResult {
115117
unsafe fn exception(self) -> Result<(), CxxException> {
116118
// SAFETY: We know that the `Result` can only contain a valid `std::exception_ptr` or null.
117-
match unsafe { self.0.as_mut() } {
119+
match self.0 {
118120
None => Ok(()),
119-
Some(ptr) => {
120-
let res = CxxException(NonNull::from(ptr));
121-
// NOTE: we are copying the pointer, so we need to forget this
122-
// object, otherwise we'd double-free the `std::exception_ptr`.
123-
core::mem::forget(self);
124-
Err(res)
125-
}
121+
Some(ptr) => Err(ptr),
126122
}
127123
}
128124
}
129125

126+
// Assert that the result is not larger than the exception (`Option` will use the niche).
127+
const _: () = assert!(core::mem::size_of::<CxxResult>() == core::mem::size_of::<CxxException>());
128+
130129
#[repr(C)]
131130
pub struct CxxResultWithMessage {
132131
pub(crate) res: CxxResult,
@@ -142,19 +141,20 @@ impl CxxResultWithMessage {
142141
// SAFETY: The message is always given for the exception and we constructed it in
143142
// a `Box` in `cxxbridge1$exception()`. We just reconstruct it here.
144143
let what = unsafe {
145-
str::from_utf8_unchecked_mut(
146-
slice::from_raw_parts_mut(self.msg.ptr.as_ptr(), self.msg.len))
144+
str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(
145+
self.msg.ptr.as_ptr(),
146+
self.msg.len,
147+
))
147148
};
148149
Err(Exception {
149150
src,
150-
what: unsafe { Box::from_raw(what) }
151+
what: unsafe { Box::from_raw(what) },
151152
})
152153
}
153154
}
154155
}
155156
}
156157

157-
158158
/// Trait to convert an arbitrary Rust error into a C++ exception.
159159
///
160160
/// If an implementation of [`ToCxxException`] is explicitly provided for an `E`, then this
@@ -211,9 +211,8 @@ impl<T: Display> ToCxxExceptionDefault for &T {
211211
};
212212
// we have sufficient buffer size, just construct from the inplace
213213
// buffer
214-
let exc_text = unsafe {
215-
std::str::from_utf8_unchecked(&buffer.assume_init_ref()[0..size])
216-
};
214+
let exc_text =
215+
unsafe { std::str::from_utf8_unchecked(&buffer.assume_init_ref()[0..size]) };
217216
CxxException::new_default(exc_text)
218217
}
219218
#[cfg(not(feature = "std"))]
@@ -234,8 +233,8 @@ macro_rules! map_rust_error_to_cxx_exception {
234233
// the need for `specialization` feature. Namely, `ToCxxException` for `T` has higher
235234
// weight and is selected before `ToCxxExceptionDefault`, which is defined on `&T` (and
236235
// requires auto-deref). If it's not defined, then the default is used.
237-
use $crate::ToCxxExceptionDefault;
238236
use $crate::ToCxxException;
237+
use $crate::ToCxxExceptionDefault;
239238
(&$err).to_cxx_exception()
240239
};
241240
exc
@@ -250,7 +249,7 @@ macro_rules! map_rust_result_to_cxx_result {
250249
unsafe { ::core::ptr::write($ret_ptr, ok) };
251250
$crate::private::CxxResult::new()
252251
}
253-
Err(err) => $crate::private::CxxResult::from(err)
252+
Err(err) => $crate::private::CxxResult::from(err),
254253
}
255254
};
256255
}

0 commit comments

Comments
 (0)