Skip to content

Commit 25aeef4

Browse files
bartlomiejuclaude
andcommitted
Address PR feedback
- Pass caching_callback directly to Finish instead of wrapping in a lambda - Capture isolate pointer in the Rust closure instead of threading it through the C++ resolution callback - Use a rule-of-five RustCallable class to track Box lifetime through std::function, preventing leaks if the callback is never invoked Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6dc5c32 commit 25aeef4

File tree

2 files changed

+94
-53
lines changed

2 files changed

+94
-53
lines changed

src/binding.cc

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3808,34 +3808,72 @@ void v8__WasmModuleCompilation__OnBytesReceived(
38083808
self->OnBytesReceived(bytes, size);
38093809
}
38103810

3811+
// A callable wrapper that properly tracks a Rust Box pointer lifetime through
3812+
// std::function's copy/move/destroy operations (rule of five). This ensures
3813+
// the boxed Rust closure is freed when the std::function is destroyed, even
3814+
// if the callback is never invoked.
3815+
class RustCallable {
3816+
public:
3817+
using Callback = void (*)(void* data, const v8::WasmModuleObject* module,
3818+
const v8::Value* error);
3819+
3820+
RustCallable(Callback callback, void* data, void (*drop)(void*))
3821+
: callback_(callback), data_(data), drop_(drop), alive_(true) {}
3822+
3823+
~RustCallable() {
3824+
if (alive_) drop_(data_);
3825+
}
3826+
3827+
RustCallable(const RustCallable& other)
3828+
: callback_(other.callback_),
3829+
data_(other.data_),
3830+
drop_(other.drop_),
3831+
alive_(false) {}
3832+
3833+
RustCallable(RustCallable&& other)
3834+
: callback_(other.callback_),
3835+
data_(other.data_),
3836+
drop_(other.drop_),
3837+
alive_(other.alive_) {
3838+
other.alive_ = false;
3839+
}
3840+
3841+
RustCallable& operator=(const RustCallable&) = delete;
3842+
RustCallable& operator=(RustCallable&&) = delete;
3843+
3844+
void operator()(
3845+
std::variant<v8::Local<v8::WasmModuleObject>, v8::Local<v8::Value>>
3846+
result) {
3847+
if (auto* module =
3848+
std::get_if<v8::Local<v8::WasmModuleObject>>(&result)) {
3849+
callback_(data_, local_to_ptr(*module), nullptr);
3850+
} else {
3851+
callback_(data_, nullptr,
3852+
local_to_ptr(std::get<v8::Local<v8::Value>>(result)));
3853+
}
3854+
// After invoking a FnOnce, the drop is handled by Rust inside the
3855+
// callback. Prevent double-free when the std::function is destroyed.
3856+
alive_ = false;
3857+
}
3858+
3859+
private:
3860+
Callback callback_;
3861+
void* data_;
3862+
void (*drop_)(void*);
3863+
bool alive_;
3864+
};
3865+
38113866
void v8__WasmModuleCompilation__Finish(
38123867
v8::WasmModuleCompilation* self, v8::Isolate* isolate,
38133868
void (*caching_callback)(v8::WasmStreaming::ModuleCachingInterface&),
3814-
void (*resolution_callback)(void* data, v8::Isolate* isolate,
3869+
void (*resolution_callback)(void* data,
38153870
const v8::WasmModuleObject* module,
38163871
const v8::Value* error),
3817-
void* resolution_data) {
3818-
v8::WasmModuleCompilation::ModuleCachingCallback cc;
3819-
if (caching_callback) {
3820-
cc = [caching_callback](v8::WasmStreaming::ModuleCachingInterface& mci) {
3821-
caching_callback(mci);
3822-
};
3823-
}
3824-
self->Finish(
3825-
isolate, cc,
3826-
[resolution_callback, resolution_data, isolate](
3827-
std::variant<v8::Local<v8::WasmModuleObject>, v8::Local<v8::Value>>
3828-
result) {
3829-
if (auto* module =
3830-
std::get_if<v8::Local<v8::WasmModuleObject>>(&result)) {
3831-
resolution_callback(resolution_data, isolate, local_to_ptr(*module),
3832-
nullptr);
3833-
} else {
3834-
resolution_callback(
3835-
resolution_data, isolate, nullptr,
3836-
local_to_ptr(std::get<v8::Local<v8::Value>>(result)));
3837-
}
3838-
});
3872+
void* resolution_data,
3873+
void (*drop_resolution_data)(void* data)) {
3874+
self->Finish(isolate, caching_callback,
3875+
RustCallable(resolution_callback, resolution_data,
3876+
drop_resolution_data));
38393877
}
38403878

38413879
void v8__WasmModuleCompilation__Abort(v8::WasmModuleCompilation* self) {

src/wasm.rs

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,27 @@ impl WasmModuleCompilation {
333333
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
334334
) + 'static,
335335
) {
336+
// Capture the isolate pointer in the closure so it doesn't need to be
337+
// threaded through C++.
338+
let isolate_ptr = scope.get_isolate_ptr();
339+
let wrapped = move |module: *const WasmModuleObject, error: *const Value| {
340+
let isolate = unsafe { Isolate::from_raw_ptr(isolate_ptr) };
341+
if !module.is_null() {
342+
resolution_callback(
343+
&isolate,
344+
Ok(unsafe { Local::from_raw(module) }.unwrap()),
345+
);
346+
} else {
347+
resolution_callback(
348+
&isolate,
349+
Err(unsafe { Local::from_raw(error) }.unwrap()),
350+
);
351+
}
352+
};
353+
336354
// Double-box: the outer Box gives us a thin pointer suitable for void*.
337-
let boxed: Box<
338-
Box<
339-
dyn FnOnce(
340-
&Isolate,
341-
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
342-
),
343-
>,
344-
> = Box::new(Box::new(resolution_callback));
355+
let boxed: Box<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>> =
356+
Box::new(Box::new(wrapped));
345357
let data = Box::into_raw(boxed) as *mut c_void;
346358

347359
unsafe {
@@ -351,6 +363,7 @@ impl WasmModuleCompilation {
351363
caching_callback,
352364
resolution_trampoline,
353365
data,
366+
drop_resolution_data,
354367
);
355368
}
356369
}
@@ -424,30 +437,20 @@ impl Drop for WasmModuleCompilation {
424437

425438
unsafe extern "C" fn resolution_trampoline(
426439
data: *mut c_void,
427-
isolate: *mut RealIsolate,
428440
module: *const WasmModuleObject,
429441
error: *const Value,
430442
) {
431-
let callback: Box<
432-
Box<
433-
dyn FnOnce(
434-
&Isolate,
435-
Result<Local<'_, WasmModuleObject>, Local<'_, Value>>,
436-
),
437-
>,
438-
> = unsafe { Box::from_raw(data as *mut _) };
439-
let isolate = unsafe { Isolate::from_raw_ptr(isolate) };
440-
if !module.is_null() {
441-
callback(
442-
&isolate,
443-
Ok(unsafe { Local::from_raw(module) }.unwrap()),
444-
);
445-
} else {
446-
callback(
447-
&isolate,
448-
Err(unsafe { Local::from_raw(error) }.unwrap()),
449-
);
450-
}
443+
let callback: Box<Box<dyn FnOnce(*const WasmModuleObject, *const Value)>> =
444+
unsafe { Box::from_raw(data as *mut _) };
445+
callback(module, error);
446+
}
447+
448+
unsafe extern "C" fn drop_resolution_data(data: *mut c_void) {
449+
let _ = unsafe {
450+
Box::from_raw(
451+
data as *mut Box<dyn FnOnce(*const WasmModuleObject, *const Value)>,
452+
)
453+
};
451454
}
452455

453456
unsafe extern "C" fn serialization_trampoline(
@@ -587,11 +590,11 @@ unsafe extern "C" {
587590
caching_callback: Option<ModuleCachingCallback>,
588591
resolution_callback: unsafe extern "C" fn(
589592
*mut c_void,
590-
*mut RealIsolate,
591593
*const WasmModuleObject,
592594
*const Value,
593595
),
594596
resolution_data: *mut c_void,
597+
drop_resolution_data: unsafe extern "C" fn(*mut c_void),
595598
);
596599
fn v8__WasmModuleCompilation__Abort(
597600
this: *mut InternalWasmModuleCompilation,

0 commit comments

Comments
 (0)