Skip to content

Commit 2b20da7

Browse files
committed
Ensure AndroidAppWaker owns an ALooper reference
This ensures we call `ALooper_acquire` before `create_waker()` wraps the Looper pointer with `AndroidAppWaker` and it also ensures that `::clone()` and `::drop()` call `ALooper_acquire()` and `ALooper_release()` respectively. Contrary to what the comment for the `looper` member said previously, it was not safe to assume that the application's looper pointer had a `'static` lifetime. The looper pointer would only be valid up until `android_main` returns, but unlike a traditional `main()` function an `android_main()` runs with respect to an `Activity` lifecycle and not a process lifecycle. It's technically possible for `android_main()` to return (at which point any looper stored in `'static` storage would have previously become an invalid pointer) and then JNI could be used to re-enter Rust and potentially try and dereference that invalid pointer. This adds a shared implementation of `AndroidAppWaker` to `src/waker.rs` instead of having each backend implement `AndroidAppWaker`. Fixes: #226
1 parent f44d837 commit 2b20da7

File tree

4 files changed

+69
-59
lines changed

4 files changed

+69
-59
lines changed

android-activity/src/game_activity/mod.rs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use log::{error, trace};
1515

1616
use jni::sys::*;
1717

18-
use ndk_sys::{ALooper, ALooper_pollOnce, ALooper_wake};
18+
use ndk_sys::ALooper_pollOnce;
1919

2020
use ndk::asset::AssetManager;
2121
use ndk::configuration::Configuration;
@@ -27,7 +27,8 @@ use crate::util::{
2727
try_get_path_from_ptr,
2828
};
2929
use crate::{
30-
AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
30+
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
31+
WindowManagerFlags,
3132
};
3233

3334
mod ffi;
@@ -105,24 +106,6 @@ impl StateLoader<'_> {
105106
}
106107
}
107108

108-
#[derive(Clone)]
109-
pub struct AndroidAppWaker {
110-
// The looper pointer is owned by the android_app and effectively
111-
// has a 'static lifetime, and the ALooper_wake C API is thread
112-
// safe, so this can be cloned safely and is send + sync safe
113-
looper: NonNull<ALooper>,
114-
}
115-
unsafe impl Send for AndroidAppWaker {}
116-
unsafe impl Sync for AndroidAppWaker {}
117-
118-
impl AndroidAppWaker {
119-
pub fn wake(&self) {
120-
unsafe {
121-
ALooper_wake(self.looper.as_ptr());
122-
}
123-
}
124-
}
125-
126109
impl AndroidApp {
127110
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: jni::JavaVM) -> Self {
128111
// We attach to the thread before creating the AndroidApp
@@ -619,13 +602,10 @@ impl AndroidAppInner {
619602
}
620603

621604
pub fn create_waker(&self) -> AndroidAppWaker {
605+
// Safety: we know that the app and looper pointers are valid
622606
unsafe {
623-
// From the application's pov we assume the app_ptr and looper pointer
624-
// have static lifetimes and we can safely assume they are never NULL.
625607
let app_ptr = self.native_app.as_ptr();
626-
AndroidAppWaker {
627-
looper: NonNull::new_unchecked((*app_ptr).looper),
628-
}
608+
AndroidAppWaker::new((*app_ptr).looper)
629609
}
630610
}
631611

android-activity/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ mod util;
187187

188188
mod jni_utils;
189189

190+
mod waker;
191+
pub use waker::AndroidAppWaker;
192+
190193
/// A rectangle with integer edge coordinates. Used to represent window insets, for example.
191194
#[derive(Clone, Debug, Default, Eq, PartialEq)]
192195
pub struct Rect {
@@ -344,7 +347,6 @@ pub enum InputStatus {
344347
}
345348

346349
use activity_impl::AndroidAppInner;
347-
pub use activity_impl::AndroidAppWaker;
348350

349351
bitflags! {
350352
/// Flags for [`AndroidApp::set_window_flags`]

android-activity/src/native_activity/mod.rs

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use ndk::{asset::AssetManager, native_window::NativeWindow};
1414

1515
use crate::error::InternalResult;
1616
use crate::{
17-
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
17+
util, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
18+
WindowManagerFlags,
1819
};
1920

2021
pub mod input;
@@ -60,31 +61,6 @@ impl StateLoader<'_> {
6061
}
6162
}
6263

63-
/// A means to wake up the main thread while it is blocked waiting for I/O
64-
#[derive(Clone)]
65-
pub struct AndroidAppWaker {
66-
// The looper pointer is owned by the android_app and effectively
67-
// has a 'static lifetime, and the ALooper_wake C API is thread
68-
// safe, so this can be cloned safely and is send + sync safe
69-
looper: NonNull<ndk_sys::ALooper>,
70-
}
71-
unsafe impl Send for AndroidAppWaker {}
72-
unsafe impl Sync for AndroidAppWaker {}
73-
74-
impl AndroidAppWaker {
75-
/// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
76-
///
77-
/// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
78-
/// callback with a [PollEvent::Wake][wake_event] event.
79-
///
80-
/// [wake_event]: crate::PollEvent::Wake
81-
pub fn wake(&self) {
82-
unsafe {
83-
ndk_sys::ALooper_wake(self.looper.as_ptr());
84-
}
85-
}
86-
}
87-
8864
impl AndroidApp {
8965
pub(crate) fn new(native_activity: NativeActivityGlue, jvm: JavaVM) -> Self {
9066
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
@@ -305,13 +281,8 @@ impl AndroidAppInner {
305281
}
306282

307283
pub fn create_waker(&self) -> AndroidAppWaker {
308-
unsafe {
309-
// From the application's pov we assume the looper pointer has a static
310-
// lifetimes and we can safely assume it is never NULL.
311-
AndroidAppWaker {
312-
looper: NonNull::new_unchecked(self.looper.ptr),
313-
}
314-
}
284+
// Safety: we know that the looper is a valid, non-null pointer
285+
unsafe { AndroidAppWaker::new(self.looper.ptr) }
315286
}
316287

317288
pub fn config(&self) -> ConfigurationRef {

android-activity/src/waker.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::ptr::NonNull;
2+
3+
#[cfg(doc)]
4+
use crate::AndroidApp;
5+
6+
/// A means to wake up the main thread while it is blocked waiting for I/O
7+
pub struct AndroidAppWaker {
8+
looper: NonNull<ndk_sys::ALooper>,
9+
}
10+
11+
impl Clone for AndroidAppWaker {
12+
fn clone(&self) -> Self {
13+
unsafe { ndk_sys::ALooper_acquire(self.looper.as_ptr()) }
14+
Self {
15+
looper: self.looper,
16+
}
17+
}
18+
}
19+
20+
impl Drop for AndroidAppWaker {
21+
fn drop(&mut self) {
22+
unsafe { ndk_sys::ALooper_release(self.looper.as_ptr()) }
23+
}
24+
}
25+
26+
unsafe impl Send for AndroidAppWaker {}
27+
unsafe impl Sync for AndroidAppWaker {}
28+
29+
impl AndroidAppWaker {
30+
/// Acquire a ref to a looper as a means to be able to wake up the event loop
31+
///
32+
/// # Safety
33+
///
34+
/// The `ALooper` pointer must be valid and not null.
35+
pub(crate) unsafe fn new(looper: *mut ndk_sys::ALooper) -> Self {
36+
assert!(!looper.is_null(), "looper pointer must not be null");
37+
unsafe {
38+
// Give the waker its own reference to the looper
39+
ndk_sys::ALooper_acquire(looper);
40+
AndroidAppWaker {
41+
looper: NonNull::new_unchecked(looper),
42+
}
43+
}
44+
}
45+
46+
/// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
47+
///
48+
/// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
49+
/// callback with a [PollEvent::Wake][wake_event] event.
50+
///
51+
/// [wake_event]: crate::PollEvent::Wake
52+
pub fn wake(&self) {
53+
unsafe {
54+
ndk_sys::ALooper_wake(self.looper.as_ptr());
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)