Skip to content

Commit 66be81a

Browse files
authored
[breaking][core-graphics] Fix unsoundness in CGEventTap API (#710)
* Fix unsoundness in CGEventTap API * Fix lifetime unsoundness in CGEventTap, add ::with
1 parent cf4c9c0 commit 66be81a

File tree

1 file changed

+63
-23
lines changed

1 file changed

+63
-23
lines changed

core-graphics/src/event.rs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bitflags::bitflags;
66
use core::ffi::{c_ulong, c_void};
77
use core_foundation::{
88
base::{CFRelease, CFRetain, CFTypeID, TCFType},
9-
mach_port::{CFMachPort, CFMachPortRef},
9+
mach_port::{CFMachPort, CFMachPortInvalidate, CFMachPortRef},
1010
};
1111
use foreign_types::{foreign_type, ForeignType};
1212
use std::mem::ManuallyDrop;
@@ -417,7 +417,7 @@ macro_rules! CGEventMaskBit {
417417
}
418418

419419
pub type CGEventTapProxy = *const c_void;
420-
pub type CGEventTapCallBackFn<'tap_life> =
420+
type CGEventTapCallBackFn<'tap_life> =
421421
Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>;
422422
type CGEventTapCallBackInternal = unsafe extern "C" fn(
423423
proxy: CGEventTapProxy,
@@ -443,51 +443,81 @@ unsafe extern "C" fn cg_event_tap_callback_internal(
443443
}
444444

445445
/// ```no_run
446-
///use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
447-
///use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
448-
///let current = CFRunLoop::get_current();
449-
///match CGEventTap::new(
446+
/// use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop};
447+
/// use core_graphics::event::{CGEventTap, CGEventTapLocation, CGEventTapPlacement, CGEventTapOptions, CGEventType};
448+
/// let current = CFRunLoop::get_current();
449+
///
450+
/// CGEventTap::with(
450451
/// CGEventTapLocation::HID,
451452
/// CGEventTapPlacement::HeadInsertEventTap,
452453
/// CGEventTapOptions::Default,
453454
/// vec![CGEventType::MouseMoved],
454-
/// |_a, _b, d| {
455-
/// println!("{:?}", d.location());
455+
/// |_proxy, _type, event| {
456+
/// println!("{:?}", event.location());
456457
/// None
457458
/// },
458-
/// ) {
459-
/// Ok(tap) => unsafe {
459+
/// |tap| {
460460
/// let loop_source = tap
461-
/// .mach_port
461+
/// .mach_port()
462462
/// .create_runloop_source(0)
463-
/// .expect("Somethings is bad ");
464-
/// current.add_source(&loop_source, kCFRunLoopCommonModes);
463+
/// .expect("Runloop source creation failed");
464+
/// current.add_source(&loop_source, unsafe { kCFRunLoopCommonModes });
465465
/// tap.enable();
466466
/// CFRunLoop::run_current();
467467
/// },
468-
/// Err(_) => (assert!(false)),
469-
/// }
468+
/// ).expect("Failed to install event tap");
470469
/// ```
471470
pub struct CGEventTap<'tap_life> {
472-
pub mach_port: CFMachPort,
473-
pub callback_ref:
474-
Box<dyn Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>,
471+
mach_port: CFMachPort,
472+
_callback: Box<CGEventTapCallBackFn<'tap_life>>,
475473
}
476474

477-
impl<'tap_life> CGEventTap<'tap_life> {
478-
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life>(
475+
impl CGEventTap<'static> {
476+
pub fn new<F: Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'static>(
479477
tap: CGEventTapLocation,
480478
place: CGEventTapPlacement,
481479
options: CGEventTapOptions,
482480
events_of_interest: std::vec::Vec<CGEventType>,
483481
callback: F,
484-
) -> Result<CGEventTap<'tap_life>, ()> {
482+
) -> Result<Self, ()> {
483+
// SAFETY: callback is 'static so even if this object is forgotten it
484+
// will be valid to call.
485+
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback) }
486+
}
487+
}
488+
489+
impl<'tap_life> CGEventTap<'tap_life> {
490+
pub fn with<R>(
491+
tap: CGEventTapLocation,
492+
place: CGEventTapPlacement,
493+
options: CGEventTapOptions,
494+
events_of_interest: std::vec::Vec<CGEventType>,
495+
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
496+
with_fn: impl FnOnce(&Self) -> R,
497+
) -> Result<R, ()> {
498+
// SAFETY: We are okay to bypass the 'static restriction because the
499+
// event tap is dropped before returning. The callback therefore cannot
500+
// be called after its lifetime expires.
501+
let event_tap: Self =
502+
unsafe { Self::new_unchecked(tap, place, options, events_of_interest, callback)? };
503+
Ok(with_fn(&event_tap))
504+
}
505+
506+
/// Caller is responsible for ensuring that this object is dropped before
507+
/// `'tap_life` expires.
508+
pub unsafe fn new_unchecked(
509+
tap: CGEventTapLocation,
510+
place: CGEventTapPlacement,
511+
options: CGEventTapOptions,
512+
events_of_interest: std::vec::Vec<CGEventType>,
513+
callback: impl Fn(CGEventTapProxy, CGEventType, &CGEvent) -> Option<CGEvent> + 'tap_life,
514+
) -> Result<Self, ()> {
485515
let event_mask: CGEventMask = events_of_interest
486516
.iter()
487517
.fold(CGEventType::Null as CGEventMask, |mask, &etype| {
488518
mask | CGEventMaskBit!(etype)
489519
});
490-
let cb = Box::new(Box::new(callback) as CGEventTapCallBackFn);
520+
let cb: Box<CGEventTapCallBackFn> = Box::new(Box::new(callback));
491521
let cbr = Box::into_raw(cb);
492522
unsafe {
493523
let event_tap_ref = CGEventTapCreate(
@@ -502,7 +532,7 @@ impl<'tap_life> CGEventTap<'tap_life> {
502532
if !event_tap_ref.is_null() {
503533
Ok(Self {
504534
mach_port: (CFMachPort::wrap_under_create_rule(event_tap_ref)),
505-
callback_ref: Box::from_raw(cbr),
535+
_callback: Box::from_raw(cbr),
506536
})
507537
} else {
508538
let _ = Box::from_raw(cbr);
@@ -511,11 +541,21 @@ impl<'tap_life> CGEventTap<'tap_life> {
511541
}
512542
}
513543

544+
pub fn mach_port(&self) -> &CFMachPort {
545+
&self.mach_port
546+
}
547+
514548
pub fn enable(&self) {
515549
unsafe { CGEventTapEnable(self.mach_port.as_concrete_TypeRef(), true) }
516550
}
517551
}
518552

553+
impl Drop for CGEventTap<'_> {
554+
fn drop(&mut self) {
555+
unsafe { CFMachPortInvalidate(self.mach_port.as_CFTypeRef() as *mut _) };
556+
}
557+
}
558+
519559
foreign_type! {
520560
#[doc(hidden)]
521561
pub unsafe type CGEvent {

0 commit comments

Comments
 (0)