@@ -5,7 +5,7 @@ use std::{
5
5
marker:: PhantomData ,
6
6
mem,
7
7
os:: raw:: c_void,
8
- panic:: { catch_unwind, resume_unwind, RefUnwindSafe , UnwindSafe } ,
8
+ panic:: { catch_unwind, resume_unwind, AssertUnwindSafe , RefUnwindSafe , UnwindSafe } ,
9
9
ptr,
10
10
rc:: { Rc , Weak } ,
11
11
sync:: mpsc,
@@ -203,10 +203,15 @@ impl<T> EventLoop<T> {
203
203
return Err ( ExternalError :: AlreadyRunning ) ;
204
204
}
205
205
206
- // This transmute is always safe, in case it was reached through `run`, since our
207
- // lifetime will be already 'static. In other cases caller should ensure that all data
208
- // they passed to callback will actually outlive it, some apps just can't move
209
- // everything to event loop, so this is something that they should care about.
206
+ // # Safety
207
+ // We are erasing the lifetime of the application callback here so that we
208
+ // can (temporarily) store it within 'static global `AppState` that's
209
+ // accessible to objc delegate callbacks.
210
+ //
211
+ // The safety of this depends on on making sure to also clear the callback
212
+ // from the global `AppState` before we return from here, ensuring that
213
+ // we don't retain a reference beyond the real lifetime of the callback.
214
+
210
215
let callback = unsafe {
211
216
mem:: transmute :: <
212
217
Rc < RefCell < dyn FnMut ( Event < ' _ , T > , & RootWindowTarget < T > , & mut ControlFlow ) > > ,
@@ -224,23 +229,46 @@ impl<T> EventLoop<T> {
224
229
let weak_cb: Weak < _ > = Rc :: downgrade ( & callback) ;
225
230
drop ( callback) ;
226
231
227
- AppState :: set_callback ( weak_cb, Rc :: clone ( & self . window_target ) ) ;
228
-
229
- if AppState :: is_launched ( ) {
230
- debug_assert ! ( !AppState :: is_running( ) ) ;
231
- AppState :: start_running ( ) ; // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
232
+ // # Safety
233
+ // We make sure to call `AppState::clear_callback` before returning
234
+ unsafe {
235
+ AppState :: set_callback ( weak_cb, Rc :: clone ( & self . window_target ) ) ;
232
236
}
233
- AppState :: set_stop_app_before_wait ( false ) ;
234
- unsafe { app. run ( ) } ;
235
237
236
- if let Some ( panic) = self . panic_info . take ( ) {
237
- drop ( self . _callback . take ( ) ) ;
238
- AppState :: clear_callback ( ) ;
239
- resume_unwind ( panic) ;
238
+ // catch panics to make sure we can't unwind without clearing the set callback
239
+ // (which would leave the global `AppState` in an undefined, unsafe state)
240
+ let catch_result = catch_unwind ( AssertUnwindSafe ( || {
241
+ if AppState :: is_launched ( ) {
242
+ debug_assert ! ( !AppState :: is_running( ) ) ;
243
+ AppState :: start_running ( ) ; // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
244
+ }
245
+ AppState :: set_stop_app_before_wait ( false ) ;
246
+ unsafe { app. run ( ) } ;
247
+
248
+ // While the app is running it's possible that we catch a panic
249
+ // to avoid unwinding across an objective-c ffi boundary, which
250
+ // will lead to us stopping the `NSApp` and saving the
251
+ // `PanicInfo` so that we can resume the unwind at a controlled,
252
+ // safe point in time.
253
+ if let Some ( panic) = self . panic_info . take ( ) {
254
+ resume_unwind ( panic) ;
255
+ }
256
+
257
+ AppState :: exit ( )
258
+ } ) ) ;
259
+
260
+ // # Safety
261
+ // This pairs up with the `unsafe` call to `set_callback` above and ensures that
262
+ // we always clear the application callback from the global `AppState` before
263
+ // returning
264
+ drop ( self . _callback . take ( ) ) ;
265
+ AppState :: clear_callback ( ) ;
266
+
267
+ match catch_result {
268
+ Ok ( exit_code) => exit_code,
269
+ Err ( payload) => resume_unwind ( payload) ,
240
270
}
241
- AppState :: exit ( )
242
271
} ) ;
243
- drop ( self . _callback . take ( ) ) ;
244
272
245
273
if exit_code == 0 {
246
274
Ok ( ( ) )
@@ -253,10 +281,15 @@ impl<T> EventLoop<T> {
253
281
where
254
282
F : FnMut ( Event < ' _ , T > , & RootWindowTarget < T > , & mut ControlFlow ) ,
255
283
{
256
- // This transmute is always safe, in case it was reached through `run`, since our
257
- // lifetime will be already 'static. In other cases caller should ensure that all data
258
- // they passed to callback will actually outlive it, some apps just can't move
259
- // everything to event loop, so this is something that they should care about.
284
+ // # Safety
285
+ // We are erasing the lifetime of the application callback here so that we
286
+ // can (temporarily) store it within 'static global `AppState` that's
287
+ // accessible to objc delegate callbacks.
288
+ //
289
+ // The safety of this depends on on making sure to also clear the callback
290
+ // from the global `AppState` before we return from here, ensuring that
291
+ // we don't retain a reference beyond the real lifetime of the callback.
292
+
260
293
let callback = unsafe {
261
294
mem:: transmute :: <
262
295
Rc < RefCell < dyn FnMut ( Event < ' _ , T > , & RootWindowTarget < T > , & mut ControlFlow ) > > ,
@@ -274,49 +307,73 @@ impl<T> EventLoop<T> {
274
307
let weak_cb: Weak < _ > = Rc :: downgrade ( & callback) ;
275
308
drop ( callback) ;
276
309
277
- AppState :: set_callback ( weak_cb, Rc :: clone ( & self . window_target ) ) ;
278
-
279
- // Note: there are two possible `Init` conditions we have to handle - either the
280
- // `NSApp` is not yet launched, or else the `EventLoop` is not yet running.
281
-
282
- // As a special case, if the `NSApp` hasn't been launched yet then we at least run
283
- // the loop until it has fully launched.
284
- if !AppState :: is_launched ( ) {
285
- debug_assert ! ( !AppState :: is_running( ) ) ;
310
+ // # Safety
311
+ // We will make sure to call `AppState::clear_callback` before returning
312
+ // to ensure that we don't hold on to the callback beyond its (erased)
313
+ // lifetime
314
+ unsafe {
315
+ AppState :: set_callback ( weak_cb, Rc :: clone ( & self . window_target ) ) ;
316
+ }
286
317
287
- AppState :: request_stop_on_launch ( ) ;
288
- unsafe {
289
- app. run ( ) ;
318
+ // catch panics to make sure we can't unwind without clearing the set callback
319
+ // (which would leave the global `AppState` in an undefined, unsafe state)
320
+ let catch_result = catch_unwind ( AssertUnwindSafe ( || {
321
+ // As a special case, if the `NSApp` hasn't been launched yet then we at least run
322
+ // the loop until it has fully launched.
323
+ if !AppState :: is_launched ( ) {
324
+ debug_assert ! ( !AppState :: is_running( ) ) ;
325
+
326
+ AppState :: request_stop_on_launch ( ) ;
327
+ unsafe {
328
+ app. run ( ) ;
329
+ }
330
+
331
+ // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched
332
+ } else if !AppState :: is_running ( ) {
333
+ // Even though the NSApp may have been launched, it's possible we aren't running
334
+ // if the `EventLoop` was run before and has since exited. This indicates that
335
+ // we just starting to re-run the same `EventLoop` again.
336
+ AppState :: start_running ( ) ; // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
337
+ } else {
338
+ // Make sure we can't block any external loop indefinitely by stopping the NSApp
339
+ // and returning after dispatching any `RedrawRequested` event or whenever the
340
+ // `RunLoop` needs to wait for new events from the OS
341
+ AppState :: set_stop_app_on_redraw_requested ( true ) ;
342
+ AppState :: set_stop_app_before_wait ( true ) ;
343
+ unsafe {
344
+ app. run ( ) ;
345
+ }
290
346
}
291
347
292
- // Note: we dispatch `NewEvents(Init)` + `Resumed` events after the `NSApp` has launched
293
- } else if ! AppState :: is_running ( ) {
294
- AppState :: start_running ( ) ; // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
295
- } else {
296
- AppState :: set_stop_app_before_wait ( true ) ;
297
- unsafe {
298
- app . run ( ) ;
348
+ // While the app is running it's possible that we catch a panic
349
+ // to avoid unwinding across an objective-c ffi boundary, which
350
+ // will lead to us stopping the `NSApp` and saving the
351
+ // `PanicInfo` so that we can resume the unwind at a controlled,
352
+ // safe point in time.
353
+ if let Some ( panic ) = self . panic_info . take ( ) {
354
+ resume_unwind ( panic ) ;
299
355
}
300
- }
301
356
302
- if let Some ( panic) = self . panic_info . take ( ) {
303
- drop ( self . _callback . take ( ) ) ;
304
- AppState :: clear_callback ( ) ;
305
- resume_unwind ( panic) ;
357
+ if let ControlFlow :: ExitWithCode ( code) = AppState :: control_flow ( ) {
358
+ AppState :: exit ( ) ;
359
+ PumpStatus :: Exit ( code)
360
+ } else {
361
+ PumpStatus :: Continue
362
+ }
363
+ } ) ) ;
364
+
365
+ // # Safety
366
+ // This pairs up with the `unsafe` call to `set_callback` above and ensures that
367
+ // we always clear the application callback from the global `AppState` before
368
+ // returning
369
+ AppState :: clear_callback ( ) ;
370
+ drop ( self . _callback . take ( ) ) ;
371
+
372
+ match catch_result {
373
+ Ok ( pump_status) => pump_status,
374
+ Err ( payload) => resume_unwind ( payload) ,
306
375
}
307
- } ) ;
308
-
309
- let status = if let ControlFlow :: ExitWithCode ( code) = AppState :: control_flow ( ) {
310
- AppState :: exit ( ) ;
311
- PumpStatus :: Exit ( code)
312
- } else {
313
- PumpStatus :: Continue
314
- } ;
315
-
316
- AppState :: clear_callback ( ) ;
317
- drop ( self . _callback . take ( ) ) ;
318
-
319
- status
376
+ } )
320
377
}
321
378
322
379
pub fn create_proxy ( & self ) -> EventLoopProxy < T > {
0 commit comments