Skip to content

Commit 575d9dc

Browse files
authored
Merge pull request #1254 from daggerbot/sdldrop
Make SdlDrop and SubsystemDrop safer to use
2 parents 98d54c3 + 77e0442 commit 575d9dc

File tree

4 files changed

+133
-118
lines changed

4 files changed

+133
-118
lines changed

changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ when upgrading from a version of rust-sdl2 to another.
88

99
[PR #1240](https://github.com/Rust-SDL2/rust-sdl2/pull/1240) **BREAKING CHANGE** Take `PixelMasks` by refrence
1010

11+
[PR #1254](https://github.com/Rust-SDL2/rust-sdl2/pull/1254) **BREAKING CHANGE** Make `SdlDrop` and `SubsystemDrop` safer; forbid external code from constructing `SdlDrop`
12+
1113
### v0.35.2
1214

1315
[PR #1173](https://github.com/Rust-SDL2/rust-sdl2/pull/1173) Fix segfault when using timer callbacks
@@ -60,7 +62,7 @@ Various fixes to CI.
6062
[PR #1033](https://github.com/Rust-SDL2/rust-sdl2/pull/1033) Changed signature of TimerSubsystem::ticks to accept `&self`.
6163

6264
[PR #1057](https://github.com/Rust-SDL2/rust-sdl2/pull/1057): fix memory safety bug in set_error
63-
65+
6466
[PR #1081](https://github.com/Rust-SDL2/rust-sdl2/pull/1081): Allow bundled build to be built in debug mode. Fixes issue when linking binary with mixed debug+release CRT dependencies.
6567

6668
[PR #1080](https://github.com/Rust-SDL2/rust-sdl2/pull/1080): Fix line endings of patches to lf so patching of sources works on Windows.

src/sdl2/keyboard/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl crate::VideoSubsystem {
175175
/// let focused = sdl_context.keyboard().focused_window_id().is_some();
176176
/// ```
177177
pub struct KeyboardUtil {
178-
_sdldrop: ::std::rc::Rc<crate::SdlDrop>,
178+
_sdldrop: crate::SdlDrop,
179179
}
180180

181181
impl KeyboardUtil {

src/sdl2/mouse/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ impl crate::Sdl {
374374
/// sdl_context.mouse().show_cursor(false);
375375
/// ```
376376
pub struct MouseUtil {
377-
_sdldrop: ::std::rc::Rc<crate::SdlDrop>,
377+
_sdldrop: crate::SdlDrop,
378378
}
379379

380380
impl MouseUtil {

src/sdl2/sdl.rs

Lines changed: 128 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use libc::c_char;
2+
use std::cell::Cell;
23
use std::error;
34
use std::ffi::{CStr, CString, NulError};
45
use std::fmt;
56
use std::mem::transmute;
6-
use std::rc::Rc;
7+
use std::os::raw::c_void;
8+
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
79

810
use crate::sys;
911

@@ -45,10 +47,17 @@ impl error::Error for Error {
4547
}
4648
}
4749

48-
use std::sync::atomic::AtomicBool;
49-
/// Only one Sdl context can be alive at a time.
50-
/// Set to false by default (not alive).
51-
static IS_SDL_CONTEXT_ALIVE: AtomicBool = AtomicBool::new(false);
50+
/// True if the main thread has been declared. The main thread is declared when
51+
/// SDL is first initialized.
52+
static IS_MAIN_THREAD_DECLARED: AtomicBool = AtomicBool::new(false);
53+
54+
/// Number of active `SdlDrop` objects keeping SDL alive.
55+
static SDL_COUNT: AtomicU32 = AtomicU32::new(0);
56+
57+
thread_local! {
58+
/// True if the current thread is the main thread.
59+
static IS_MAIN_THREAD: Cell<bool> = Cell::new(false);
60+
}
5261

5362
/// The SDL context type. Initialize with `sdl2::init()`.
5463
///
@@ -64,31 +73,42 @@ static IS_SDL_CONTEXT_ALIVE: AtomicBool = AtomicBool::new(false);
6473
/// the main thread.
6574
#[derive(Clone)]
6675
pub struct Sdl {
67-
sdldrop: Rc<SdlDrop>,
76+
sdldrop: SdlDrop,
6877
}
6978

7079
impl Sdl {
7180
#[inline]
7281
#[doc(alias = "SDL_Init")]
7382
fn new() -> Result<Sdl, String> {
74-
unsafe {
75-
use std::sync::atomic::Ordering;
76-
77-
// Atomically switch the `IS_SDL_CONTEXT_ALIVE` global to true
78-
let was_alive = IS_SDL_CONTEXT_ALIVE.swap(true, Ordering::Relaxed);
79-
80-
if was_alive {
81-
Err("Cannot initialize `Sdl` more than once at a time.".to_owned())
82-
} else if sys::SDL_Init(0) == 0 {
83-
// Initialize SDL without any explicit subsystems (flags = 0).
84-
Ok(Sdl {
85-
sdldrop: Rc::new(SdlDrop),
86-
})
83+
// Check if we can safely initialize SDL on this thread.
84+
let was_main_thread_declared = IS_MAIN_THREAD_DECLARED.swap(true, Ordering::SeqCst);
85+
86+
IS_MAIN_THREAD.with(|is_main_thread| {
87+
if was_main_thread_declared {
88+
if !is_main_thread.get() {
89+
return Err("Cannot initialize `Sdl` from more than once thread.".to_owned());
90+
}
8791
} else {
88-
IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed);
89-
Err(get_error())
92+
is_main_thread.set(true);
93+
}
94+
Ok(())
95+
})?;
96+
97+
// Initialize SDL.
98+
if SDL_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
99+
let result;
100+
101+
unsafe {
102+
result = sys::SDL_Init(0);
103+
}
104+
105+
if result != 0 {
106+
SDL_COUNT.store(0, Ordering::Relaxed);
107+
return Err(get_error());
90108
}
91109
}
110+
111+
Ok(Sdl { sdldrop: SdlDrop { _anticonstructor: std::ptr::null_mut() }})
92112
}
93113

94114
/// Initializes the audio subsystem.
@@ -151,27 +171,38 @@ impl Sdl {
151171

152172
#[inline]
153173
#[doc(hidden)]
154-
pub fn sdldrop(&self) -> Rc<SdlDrop> {
174+
pub fn sdldrop(&self) -> SdlDrop {
155175
self.sdldrop.clone()
156176
}
157177
}
158178

159-
/// When SDL is no longer in use (the refcount in an `Rc<SdlDrop>` reaches 0), the library is quit.
179+
/// When SDL is no longer in use, the library is quit.
160180
#[doc(hidden)]
161181
#[derive(Debug)]
162-
pub struct SdlDrop;
182+
pub struct SdlDrop {
183+
// Make it impossible to construct `SdlDrop` without access to this member,
184+
// and opt out of Send and Sync.
185+
_anticonstructor: *mut c_void,
186+
}
187+
188+
impl Clone for SdlDrop {
189+
fn clone(&self) -> SdlDrop {
190+
let prev_count = SDL_COUNT.fetch_add(1, Ordering::Relaxed);
191+
assert!(prev_count > 0);
192+
SdlDrop { _anticonstructor: std::ptr::null_mut() }
193+
}
194+
}
163195

164196
impl Drop for SdlDrop {
165197
#[inline]
166198
#[doc(alias = "SDL_Quit")]
167199
fn drop(&mut self) {
168-
use std::sync::atomic::Ordering;
169-
170-
let was_alive = IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed);
171-
assert!(was_alive);
172-
173-
unsafe {
174-
sys::SDL_Quit();
200+
let prev_count = SDL_COUNT.fetch_sub(1, Ordering::Relaxed);
201+
assert!(prev_count > 0);
202+
if prev_count == 1 {
203+
unsafe {
204+
sys::SDL_Quit();
205+
}
175206
}
176207
}
177208
}
@@ -182,64 +213,42 @@ impl Drop for SdlDrop {
182213
// the event queue. These subsystems implement `Sync`.
183214

184215
macro_rules! subsystem {
185-
($name:ident, $flag:expr) => {
186-
impl $name {
187-
#[inline]
188-
#[doc(alias = "SDL_InitSubSystem")]
189-
fn new(sdl: &Sdl) -> Result<$name, String> {
190-
let result = unsafe { sys::SDL_InitSubSystem($flag) };
191-
192-
if result == 0 {
193-
Ok($name {
194-
_subsystem_drop: Rc::new(SubsystemDrop {
195-
_sdldrop: sdl.sdldrop.clone(),
196-
flag: $flag,
197-
}),
198-
})
199-
} else {
200-
Err(get_error())
201-
}
202-
}
203-
}
204-
};
205-
($name:ident, $flag:expr, nosync) => {
216+
($name:ident, $flag:expr, $counter:ident, nosync) => {
217+
static $counter: AtomicU32 = AtomicU32::new(0);
218+
206219
#[derive(Debug, Clone)]
207220
pub struct $name {
208221
/// Subsystems cannot be moved or (usually) used on non-main threads.
209222
/// Luckily, Rc restricts use to the main thread.
210-
_subsystem_drop: Rc<SubsystemDrop>,
223+
_subsystem_drop: SubsystemDrop,
211224
}
212225

213226
impl $name {
214-
/// Obtain an SDL context.
215227
#[inline]
216-
pub fn sdl(&self) -> Sdl {
217-
Sdl {
218-
sdldrop: self._subsystem_drop._sdldrop.clone(),
219-
}
220-
}
221-
}
228+
#[doc(alias = "SDL_InitSubSystem")]
229+
fn new(sdl: &Sdl) -> Result<$name, String> {
230+
if $counter.fetch_add(1, Ordering::Relaxed) == 0 {
231+
let result;
222232

223-
subsystem!($name, $flag);
224-
};
225-
($name:ident, $flag:expr, sync) => {
226-
pub struct $name {
227-
/// Subsystems cannot be moved or (usually) used on non-main threads.
228-
/// Luckily, Rc restricts use to the main thread.
229-
_subsystem_drop: Rc<SubsystemDrop>,
230-
}
231-
unsafe impl Sync for $name {}
233+
unsafe {
234+
result = sys::SDL_InitSubSystem($flag);
235+
}
232236

233-
impl std::clone::Clone for $name {
234-
#[inline]
235-
fn clone(&self) -> $name {
236-
$name {
237-
_subsystem_drop: self._subsystem_drop.clone(),
237+
if result != 0 {
238+
$counter.store(0, Ordering::Relaxed);
239+
return Err(get_error());
240+
}
238241
}
242+
243+
Ok($name {
244+
_subsystem_drop: SubsystemDrop {
245+
_sdldrop: sdl.sdldrop.clone(),
246+
counter: &$counter,
247+
flag: $flag,
248+
},
249+
})
239250
}
240-
}
241251

242-
impl $name {
243252
/// Obtain an SDL context.
244253
#[inline]
245254
pub fn sdl(&self) -> Sdl {
@@ -248,49 +257,69 @@ macro_rules! subsystem {
248257
}
249258
}
250259
}
251-
252-
subsystem!($name, $flag);
260+
};
261+
($name:ident, $flag:expr, $counter:ident, sync) => {
262+
subsystem!($name, $flag, $counter, nosync);
263+
unsafe impl Sync for $name {}
253264
};
254265
}
255266

256267
/// When a subsystem is no longer in use (the refcount in an `Rc<SubsystemDrop>` reaches 0),
257268
/// the subsystem is quit.
258-
#[derive(Debug, Clone)]
269+
#[derive(Debug)]
259270
struct SubsystemDrop {
260-
_sdldrop: Rc<SdlDrop>,
271+
_sdldrop: SdlDrop,
272+
counter: &'static AtomicU32,
261273
flag: u32,
262274
}
263275

276+
impl Clone for SubsystemDrop {
277+
fn clone(&self) -> SubsystemDrop {
278+
let prev_count = self.counter.fetch_add(1, Ordering::Relaxed);
279+
assert!(prev_count > 0);
280+
SubsystemDrop {
281+
_sdldrop: self._sdldrop.clone(),
282+
counter: self.counter,
283+
flag: self.flag,
284+
}
285+
}
286+
}
287+
264288
impl Drop for SubsystemDrop {
265289
#[inline]
266290
#[doc(alias = "SDL_QuitSubSystem")]
267291
fn drop(&mut self) {
268-
unsafe {
269-
sys::SDL_QuitSubSystem(self.flag);
292+
let prev_count = self.counter.fetch_sub(1, Ordering::Relaxed);
293+
assert!(prev_count > 0);
294+
if prev_count == 1 {
295+
unsafe {
296+
sys::SDL_QuitSubSystem(self.flag);
297+
}
270298
}
271299
}
272300
}
273301

274-
subsystem!(AudioSubsystem, sys::SDL_INIT_AUDIO, nosync);
302+
subsystem!(AudioSubsystem, sys::SDL_INIT_AUDIO, AUDIO_COUNT, nosync);
275303
subsystem!(
276304
GameControllerSubsystem,
277305
sys::SDL_INIT_GAMECONTROLLER,
306+
GAMECONTROLLER_COUNT,
278307
nosync
279308
);
280-
subsystem!(HapticSubsystem, sys::SDL_INIT_HAPTIC, nosync);
281-
subsystem!(JoystickSubsystem, sys::SDL_INIT_JOYSTICK, nosync);
282-
subsystem!(VideoSubsystem, sys::SDL_INIT_VIDEO, nosync);
309+
subsystem!(HapticSubsystem, sys::SDL_INIT_HAPTIC, HAPTIC_COUNT, nosync);
310+
subsystem!(JoystickSubsystem, sys::SDL_INIT_JOYSTICK, JOYSTICK_COUNT, nosync);
311+
subsystem!(VideoSubsystem, sys::SDL_INIT_VIDEO, VIDEO_COUNT, nosync);
283312
// Timers can be added on other threads.
284-
subsystem!(TimerSubsystem, sys::SDL_INIT_TIMER, sync);
313+
subsystem!(TimerSubsystem, sys::SDL_INIT_TIMER, TIMER_COUNT, sync);
285314
// The event queue can be read from other threads.
286-
subsystem!(EventSubsystem, sys::SDL_INIT_EVENTS, sync);
287-
subsystem!(SensorSubsystem, sys::SDL_INIT_SENSOR, sync);
315+
subsystem!(EventSubsystem, sys::SDL_INIT_EVENTS, EVENTS_COUNT, sync);
316+
subsystem!(SensorSubsystem, sys::SDL_INIT_SENSOR, SENSOR_COUNT, sync);
288317

289-
static mut IS_EVENT_PUMP_ALIVE: bool = false;
318+
static IS_EVENT_PUMP_ALIVE: AtomicBool = AtomicBool::new(false);
290319

291320
/// A thread-safe type that encapsulates SDL event-pumping functions.
292321
pub struct EventPump {
293-
_sdldrop: Rc<SdlDrop>,
322+
_event_subsystem: EventSubsystem,
294323
}
295324

296325
impl EventPump {
@@ -299,24 +328,12 @@ impl EventPump {
299328
#[doc(alias = "SDL_InitSubSystem")]
300329
fn new(sdl: &Sdl) -> Result<EventPump, String> {
301330
// Called on the main SDL thread.
302-
303-
unsafe {
304-
if IS_EVENT_PUMP_ALIVE {
305-
Err("an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time.".to_owned())
306-
} else {
307-
// Initialize the events subsystem, just in case none of the other subsystems have done it yet.
308-
let result = sys::SDL_InitSubSystem(sys::SDL_INIT_EVENTS);
309-
310-
if result == 0 {
311-
IS_EVENT_PUMP_ALIVE = true;
312-
313-
Ok(EventPump {
314-
_sdldrop: sdl.sdldrop.clone(),
315-
})
316-
} else {
317-
Err(get_error())
318-
}
319-
}
331+
if IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed) {
332+
Err("an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time.".to_owned())
333+
} else {
334+
let _event_subsystem = sdl.event()?;
335+
IS_EVENT_PUMP_ALIVE.store(true, Ordering::Relaxed);
336+
Ok(EventPump { _event_subsystem })
320337
}
321338
}
322339
}
@@ -326,12 +343,8 @@ impl Drop for EventPump {
326343
#[doc(alias = "SDL_QuitSubSystem")]
327344
fn drop(&mut self) {
328345
// Called on the main SDL thread.
329-
330-
unsafe {
331-
assert!(IS_EVENT_PUMP_ALIVE);
332-
sys::SDL_QuitSubSystem(sys::SDL_INIT_EVENTS);
333-
IS_EVENT_PUMP_ALIVE = false;
334-
}
346+
assert!(IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed));
347+
IS_EVENT_PUMP_ALIVE.store(false, Ordering::Relaxed);
335348
}
336349
}
337350

0 commit comments

Comments
 (0)