Skip to content

Commit eb691a2

Browse files
authored
guard the WorldPointer against UB (#83)
1 parent a3bccba commit eb691a2

File tree

3 files changed

+142
-25
lines changed
  • bevy_mod_scripting_core/src
  • languages
    • bevy_mod_scripting_lua/src
    • bevy_mod_scripting_rhai/src

3 files changed

+142
-25
lines changed

bevy_mod_scripting_core/src/world.rs

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::ops::Deref;
12
use std::sync::Arc;
23

34
use bevy::prelude::World;
@@ -6,37 +7,153 @@ use parking_lot::{
67
};
78

89
/// Pointer to a bevy world, safely allows multiple access via RwLock
9-
/// # Safety
10-
/// This pointer does not prevent dangling pointers, i.e. you must ensure the world is not dropped while any world pointers still exist,
11-
/// the world must also not change, from the moment a world pointer is created it must always point to the same world.
10+
///
11+
/// If the original [WorldPointerGuard] that created this pointer is dropped,
12+
/// the `read` and `write` methods will panic, and the "try" variants will
13+
/// return `None`.
1214
#[derive(Debug, Clone)]
13-
pub struct WorldPointer(Arc<RwLock<*mut World>>);
15+
pub struct WorldPointer(Arc<RwLock<Option<*mut World>>>);
16+
17+
/// Guarded pointer to a bevy world, can be used to `clone` additional
18+
/// [WorldPointer]s for safe access.
19+
///
20+
/// # Safety
21+
/// The original `&mut World` used to create this guard _must not_ be used after
22+
/// the guard is created in order for the cloned [WorldPointer]s to be safe.
23+
///
24+
/// On [Drop], it will "take back" access to the `&mut World`, preventing the
25+
/// `WorldPointer`s from invoking UB.
26+
#[derive(Debug)]
27+
pub struct WorldPointerGuard(WorldPointer);
28+
29+
impl Deref for WorldPointerGuard {
30+
type Target = WorldPointer;
31+
fn deref(&self) -> &Self::Target {
32+
&self.0
33+
}
34+
}
1435

1536
unsafe impl Send for WorldPointer {}
1637
unsafe impl Sync for WorldPointer {}
1738

18-
impl WorldPointer {
39+
impl WorldPointerGuard {
1940
/// Creates a new world pointer.
2041
/// # Safety
21-
/// satisfies world constancy, since it's impossible to change the underlying pointer
22-
/// However you must ensure that the world does not go out of scope while this pointer is live
42+
/// The original `&mut World` must not be used while this guard is in scope.
43+
/// The [World] may only be accessed through this guard or one of its cloned
44+
/// [WorldPointer]s.
2345
#[allow(clippy::arc_with_non_send_sync)]
2446
pub unsafe fn new(world: &mut World) -> Self {
25-
WorldPointer(Arc::new(RwLock::new(world)))
47+
WorldPointerGuard(WorldPointer(Arc::new(RwLock::new(Some(world)))))
48+
}
49+
}
50+
51+
impl Drop for WorldPointerGuard {
52+
fn drop(&mut self) {
53+
// Being explicit about the types here to make sure we're getting things
54+
// correct.
55+
let world_ptr: &WorldPointer = &self.0;
56+
let _: Option<*mut World> = RwLock::write(&world_ptr.0).take();
2657
}
58+
}
2759

60+
impl WorldPointer {
2861
/// Returns a read guard which can be used for immutable world access.
62+
///
63+
/// Panics if the pointer is already locked or has gone out of scope.
2964
pub fn read(&self) -> MappedRwLockReadGuard<World> {
30-
RwLockReadGuard::map(self.0.try_read().expect(""), |ptr: &*mut World| unsafe {
31-
&**ptr
32-
})
65+
self.try_read().expect("concurrent read/write world access")
3366
}
3467

3568
/// Returns a write guard which can be used for mutable world access.
69+
///
70+
/// Panics if the pointer is already locked or has gone out of scope.
3671
pub fn write(&self) -> MappedRwLockWriteGuard<World> {
37-
RwLockWriteGuard::map(
38-
self.0.try_write().expect(""),
39-
|ptr: &mut *mut World| unsafe { &mut **ptr },
40-
)
72+
self.try_write()
73+
.expect("concurrent read/write world access")
74+
}
75+
76+
/// Returns a read guard which can be used for immutable world access.
77+
///
78+
/// Returns `None` if the pointer is already locked or has gone out of
79+
/// scope.
80+
pub fn try_read(&self) -> Option<MappedRwLockReadGuard<World>> {
81+
self.try_read_inner(false)
82+
}
83+
84+
/// Returns a write guard which can be used for mutable world access.
85+
///
86+
/// Returns `None` if the pointer is already locked or has gone out of
87+
/// scope.
88+
pub fn try_write(&self) -> Option<MappedRwLockWriteGuard<World>> {
89+
self.try_write_inner(false)
90+
}
91+
92+
/// Returns a read guard which can be used for immutable world access.
93+
///
94+
/// Panics if the pointer has gone out of scope. May block if another thread
95+
/// holds the lock.
96+
pub fn read_blocking(&self) -> MappedRwLockReadGuard<World> {
97+
self.try_read_blocking()
98+
.expect("the world pointer is out of scope")
99+
}
100+
101+
/// Returns a write guard which can be used for mutable world access.
102+
///
103+
/// Panics if the pointer has gone out of scope. May block if another thread
104+
/// holds the lock.
105+
pub fn write_blocking(&self) -> MappedRwLockWriteGuard<World> {
106+
self.try_write_blocking()
107+
.expect("the world pointer is out of scope")
108+
}
109+
110+
/// Returns a read guard which can be used for immutable world access.
111+
///
112+
/// Returns `None` if has gone out of scope. May block if another thread
113+
/// holds the lock.
114+
pub fn try_read_blocking(&self) -> Option<MappedRwLockReadGuard<World>> {
115+
self.try_read_inner(true)
116+
}
117+
118+
/// Returns a write guard which can be used for mutable world access.
119+
///
120+
/// Returns `None` if has gone out of scope. May block if another thread
121+
/// holds the lock.
122+
pub fn try_write_blocking(&self) -> Option<MappedRwLockWriteGuard<World>> {
123+
self.try_write_inner(true)
124+
}
125+
126+
fn try_read_inner(&self, blocking: bool) -> Option<MappedRwLockReadGuard<World>> {
127+
let guard = if blocking {
128+
self.0.read()
129+
} else {
130+
self.0.try_read()?
131+
};
132+
// Check if the inner pointer is there so we can invert the `Option`.
133+
if guard.is_none() {
134+
return None;
135+
}
136+
137+
Some(RwLockReadGuard::map(
138+
guard,
139+
|ptr: &Option<*mut World>| unsafe { &*ptr.unwrap() },
140+
))
141+
}
142+
143+
fn try_write_inner(&self, blocking: bool) -> Option<MappedRwLockWriteGuard<World>> {
144+
let guard = if blocking {
145+
self.0.write()
146+
} else {
147+
self.0.try_write()?
148+
};
149+
// Check if the inner pointer is there so we can invert the `Option`.
150+
if guard.is_none() {
151+
return None;
152+
}
153+
154+
Some(RwLockWriteGuard::map(
155+
guard,
156+
|ptr: &mut Option<*mut World>| unsafe { &mut *ptr.unwrap() },
157+
))
41158
}
42159
}

languages/bevy_mod_scripting_lua/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
docs::LuaDocFragment,
44
};
55
use bevy::{ecs::schedule::ScheduleLabel, prelude::*};
6-
use bevy_mod_scripting_core::{prelude::*, systems::*, world::WorldPointer};
6+
use bevy_mod_scripting_core::{prelude::*, systems::*, world::WorldPointerGuard};
77

88
use std::fmt;
99
use std::marker::PhantomData;
@@ -146,12 +146,12 @@ impl<A: LuaArg> ScriptHost for LuaScriptHost<A> {
146146
) {
147147
// safety:
148148
// - we have &mut World access
149-
// - we do not use world_ptr after using the world reference which it's derived from
150-
let world_ptr = unsafe { WorldPointer::new(world) };
149+
// - we do not use the original reference again anywhere in this function
150+
let world = unsafe { WorldPointerGuard::new(world) };
151151

152152
ctxs.for_each(|(script_data, ctx)| {
153153
providers
154-
.setup_runtime_all(world_ptr.clone(), &script_data, ctx)
154+
.setup_runtime_all(world.clone(), &script_data, ctx)
155155
.expect("Could not setup script runtime");
156156

157157
let ctx = ctx.get_mut().expect("Poison error in context");
@@ -172,7 +172,7 @@ impl<A: LuaArg> ScriptHost for LuaScriptHost<A> {
172172
};
173173

174174
if let Err(error) = f.call::<_, ()>(event.args.clone()) {
175-
let mut world = world_ptr.write();
175+
let mut world = world.write();
176176
let mut state: CachedScriptState<Self> = world.remove_resource().unwrap();
177177

178178
let (_, mut error_wrt, _) = state.event_state.get_mut(&mut world);

languages/bevy_mod_scripting_rhai/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
docs::RhaiDocFragment,
44
};
55
use bevy::{ecs::schedule::ScheduleLabel, prelude::*};
6-
use bevy_mod_scripting_core::{prelude::*, systems::*, world::WorldPointer};
6+
use bevy_mod_scripting_core::{prelude::*, systems::*, world::WorldPointerGuard};
77
use rhai::*;
88
use std::marker::PhantomData;
99

@@ -150,12 +150,12 @@ impl<A: FuncArgs + Send + Clone + Sync + 'static> ScriptHost for RhaiScriptHost<
150150
) {
151151
// safety:
152152
// - we have &mut World access
153-
// - we do not use world_ptr after we use the original reference again anywhere in this function
154-
let world_ptr = unsafe { WorldPointer::new(world) };
153+
// - we do not use the original reference again anywhere in this function
154+
let world = unsafe { WorldPointerGuard::new(world) };
155155

156156
ctxs.for_each(|(fd, ctx)| {
157157
providers
158-
.setup_runtime_all(world_ptr.clone(), &fd, ctx)
158+
.setup_runtime_all(world.clone(), &fd, ctx)
159159
.expect("Failed to setup script runtime");
160160

161161
for event in events.iter() {
@@ -172,7 +172,7 @@ impl<A: FuncArgs + Send + Clone + Sync + 'static> ScriptHost for RhaiScriptHost<
172172
) {
173173
Ok(v) => v,
174174
Err(e) => {
175-
let mut world = world_ptr.write();
175+
let mut world = world.write();
176176
let mut state: CachedScriptState<Self> = world.remove_resource().unwrap();
177177

178178
match *e {

0 commit comments

Comments
 (0)