Skip to content

Commit 541804c

Browse files
committed
Harden unsafe usage across Rust codebase
- Replace `MainThreadMarker::new_unchecked()` with checked `::new().expect()` in `lib.rs` and `menu/macos.rs` — turns silent UB into a clear panic if ever called off the main thread - Add `// SAFETY:` comments to `transmute` calls in `drag_image_detection.rs` documenting the expected ObjC method signatures - Replace generic `SendWrapper<T>` with narrow `SendableCFRunLoop` in `fsevent-stream` — scopes the `unsafe impl Send` to exactly the type that needs it, with safety rationale in the doc comment
1 parent b096659 commit 541804c

4 files changed

Lines changed: 21 additions & 17 deletions

File tree

apps/desktop/src-tauri/src/drag_image_detection.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ fn emit_modifiers_forced() {
184184
unsafe fn call_original_entered(this: &AnyObject, cmd: Sel, drag_info: &AnyObject) -> usize {
185185
unsafe {
186186
if let Some(&original) = ORIGINAL_ENTERED_IMP.get() {
187+
// SAFETY: The original IMP was obtained from `draggingEntered:` which has the ObjC
188+
// signature `- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender`, matching
189+
// the C ABI as `fn(&AnyObject, Sel, &AnyObject) -> usize`.
187190
let f =
188191
std::mem::transmute::<Imp, unsafe extern "C-unwind" fn(&AnyObject, Sel, &AnyObject) -> usize>(original);
189192
f(this, cmd, drag_info)
@@ -197,6 +200,7 @@ unsafe fn call_original_entered(this: &AnyObject, cmd: Sel, drag_info: &AnyObjec
197200
unsafe fn call_original_updated(this: &AnyObject, cmd: Sel, drag_info: &AnyObject) -> usize {
198201
unsafe {
199202
if let Some(&original) = ORIGINAL_UPDATED_IMP.get() {
203+
// SAFETY: Same as call_original_entered — `draggingUpdated:` has an identical signature.
200204
let f =
201205
std::mem::transmute::<Imp, unsafe extern "C-unwind" fn(&AnyObject, Sel, &AnyObject) -> usize>(original);
202206
f(this, cmd, drag_info)
@@ -211,6 +215,8 @@ unsafe fn call_original_updated(this: &AnyObject, cmd: Sel, drag_info: &AnyObjec
211215
unsafe fn call_original_exited(this: &AnyObject, cmd: Sel, drag_info: &AnyObject) {
212216
unsafe {
213217
if let Some(&original) = ORIGINAL_EXITED_IMP.get() {
218+
// SAFETY: Same as call_original_entered — `draggingExited:` has the same parameter
219+
// signature but returns void instead of NSDragOperation.
214220
let f = std::mem::transmute::<Imp, unsafe extern "C-unwind" fn(&AnyObject, Sel, &AnyObject)>(original);
215221
f(this, cmd, drag_info)
216222
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ fn send_native_clipboard_action(menu_id: &str) {
150150
_ => return,
151151
};
152152

153-
// Safety: we're on the main thread (called from on_menu_event which runs on the main thread).
154-
let mtm = unsafe { objc2::MainThreadMarker::new_unchecked() };
153+
let mtm = objc2::MainThreadMarker::new()
154+
.expect("send_native_clipboard_action must be called from the main thread");
155155
let ns_app = NSApplication::sharedApplication(mtm);
156156

157157
// sendAction:to:from: with nil `to` sends to the first responder, exactly like

apps/desktop/src-tauri/src/menu/macos.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,8 @@ pub(crate) fn cleanup_macos_menus() {
340340
}
341341

342342
fn cleanup_macos_menus_inner() {
343-
// This runs during Tauri setup on the main thread.
344-
let mtm = unsafe { MainThreadMarker::new_unchecked() };
343+
let mtm = MainThreadMarker::new()
344+
.expect("cleanup_macos_menus_inner must be called from the main thread");
345345
let app = NSApplication::sharedApplication(mtm);
346346
let Some(main_menu) = app.mainMenu() else {
347347
return;
@@ -413,7 +413,8 @@ pub(crate) fn set_macos_menu_icons() {
413413
}
414414

415415
fn set_macos_menu_icons_inner() {
416-
let mtm = unsafe { MainThreadMarker::new_unchecked() };
416+
let mtm = MainThreadMarker::new()
417+
.expect("set_macos_menu_icons_inner must be called from the main thread");
417418
let app = NSApplication::sharedApplication(mtm);
418419
let Some(main_menu) = app.mainMenu() else {
419420
return;

crates/fsevent-stream/src/stream.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@ pub(crate) struct StreamContextInfo {
175175

176176
impl_release_callback!(release_context, StreamContextInfo);
177177

178-
struct SendWrapper<T>(T);
179-
180-
unsafe impl<T> Send for SendWrapper<T> {}
178+
/// A CFRunLoop that can be sent across threads.
179+
///
180+
/// SAFETY: CFRunLoop is a Core Foundation type. Apple documents that CF objects can be
181+
/// retained/released and used across threads. CFRunLoopStop (the only cross-thread operation
182+
/// we perform) is explicitly documented as thread-safe.
183+
/// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
184+
struct SendableCFRunLoop(CFRunLoop);
181185

182-
impl<T> SendWrapper<T> {
183-
const unsafe fn new(t: T) -> Self {
184-
Self(t)
185-
}
186-
}
186+
unsafe impl Send for SendableCFRunLoop {}
187187

188188
/// Create a new [`EventStream`](EventStream) and [`EventStreamHandler`](EventStreamHandler) pair.
189189
///
@@ -265,11 +265,8 @@ pub fn create_event_stream<P: AsRef<Path>>(
265265
stream.start();
266266

267267
// the calling to CFRunLoopRun will be terminated by CFRunLoopStop call in drop()
268-
// Safety:
269-
// - According to the Apple documentation, it's safe to move `CFRef`s across threads.
270-
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
271268
runloop_tx
272-
.send(unsafe { SendWrapper::new(current_runloop) })
269+
.send(SendableCFRunLoop(current_runloop))
273270
.expect("send runloop to stream");
274271

275272
CFRunLoop::run_current();

0 commit comments

Comments
 (0)