Skip to content

Commit 5590e51

Browse files
authored
Double borrow panic lacks details (#82)
It's currently difficult to tell which script function is panicking when a double mutable borrow occurs.
1 parent 05d23bc commit 5590e51

File tree

2 files changed

+53
-8
lines changed

2 files changed

+53
-8
lines changed

rust-script/src/runtime/call_context.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ use crate::interface::GodotScriptImpl;
1515

1616
use super::rust_script_instance::{GodotScriptObject, RustScriptInstance};
1717

18+
/// A call context for a script method call.
19+
///
20+
/// The call context can be used to perform re-entrant calls into engine APIs. Its lifetime is constrained to the duration of the current
21+
/// function.
1822
pub struct Context<'a, Script: GodotScriptImpl + ?Sized> {
1923
cell: *const GdCell<Box<dyn GodotScriptObject>>,
2024
data_ptr: *mut Box<dyn GodotScriptObject>,
@@ -29,24 +33,27 @@ impl<Script: GodotScriptImpl> Debug for Context<'_, Script> {
2933
}
3034

3135
impl<Script: GodotScriptImpl> Context<'_, Script> {
36+
/// Create a scope in which the current mutable ref to [`Self`] is released.
37+
///
38+
/// A re-entrant scope allows to use engine APIs that call back into the current script.
3239
pub fn reentrant_scope<T: GodotScriptObject + 'static, Args, Return>(
3340
&mut self,
3441
self_ref: &mut T,
3542
scope: impl ReentrantScope<Script::ImplBase, Args, Return>,
3643
) -> Return {
37-
let known_ptr = unsafe {
38-
let any = (*self.data_ptr).as_any_mut();
39-
40-
any.downcast_mut::<T>().unwrap() as *mut T
41-
};
44+
// SAFETY: the caller guaranteed that the data_ptr is valid for the lifetime of `Self`.
45+
let known_box_ptr = unsafe { &mut *self.data_ptr };
46+
let known_ptr = known_box_ptr.as_any_mut().downcast_mut::<T>().unwrap() as *mut T;
4247

4348
let self_ptr = self_ref as *mut _;
4449

4550
if known_ptr != self_ptr {
4651
panic!("unable to create reentrant scope with unrelated self reference!");
4752
}
4853

54+
// SAFETY: the caller guaranteed that the data_ptr is valid for the lifetime of `Self`.
4955
let current_ref = unsafe { &mut *self.data_ptr };
56+
// SAFETY: the caller guaranteed that the cell is valid for the lifetime of `Self`.
5057
let cell = unsafe { &*self.cell };
5158
let guard = cell.make_inaccessible(current_ref).unwrap();
5259

@@ -58,13 +65,20 @@ impl<Script: GodotScriptImpl> Context<'_, Script> {
5865
}
5966
}
6067

68+
/// A generic script call context that is not tied to a specific script type.
6169
pub struct GenericContext<'a> {
6270
cell: *const GdCell<Box<dyn GodotScriptObject>>,
6371
data_ptr: *mut Box<dyn GodotScriptObject>,
6472
base: ScriptBaseMut<'a, RustScriptInstance>,
6573
}
6674

6775
impl<'a> GenericContext<'a> {
76+
/// Create a new script call context.
77+
///
78+
/// # Safety
79+
/// - cell must be a valid pointer to a [`GdCell`] & not null.
80+
/// - data_ptr must be a valid pointer to the [`Box<dyn GodotScriptObject>`] inside the [`GdCell`].
81+
/// - both `cell` and `data_ptr` must out-live the `base`.
6882
pub(super) unsafe fn new(
6983
cell: *const GdCell<Box<dyn GodotScriptObject>>,
7084
data_ptr: *mut Box<dyn GodotScriptObject>,

rust-script/src/runtime/rust_script_instance.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,29 @@ impl ScriptInstance for RustScriptInstance {
131131

132132
fn set_property(this: SiMut<Self>, name: StringName, value: &Variant) -> bool {
133133
let cell_ref = &this.data;
134-
let mut mut_data = cell_ref.borrow_mut().unwrap();
134+
let mut mut_data = match cell_ref.borrow_mut() {
135+
Ok(guard) => guard,
136+
Err(err) => {
137+
panic!(
138+
"Error while writing to script property {}::{name}: {err}",
139+
this.script.bind().get_class_name()
140+
);
141+
}
142+
};
135143

136144
mut_data.set(name, value.to_owned())
137145
}
138146

139147
fn get_property(&self, name: StringName) -> Option<Variant> {
140-
let guard = self.data.borrow().unwrap();
148+
let guard = match self.data.borrow() {
149+
Ok(guard) => guard,
150+
Err(err) => {
151+
panic!(
152+
"Error while reading script property {}::{name}: {err}",
153+
self.script.bind().get_class_name()
154+
);
155+
}
156+
};
141157

142158
guard.get(name)
143159
}
@@ -159,10 +175,25 @@ impl ScriptInstance for RustScriptInstance {
159175

160176
let base = this.base_mut();
161177

162-
let mut data_guard = unsafe { &*cell }.borrow_mut().unwrap();
178+
// SAFETY: cell pointer was just created and is valid. It will not out-live the current function.
179+
let mut data_guard = match unsafe { &*cell }.borrow_mut() {
180+
Ok(guard) => guard,
181+
Err(err) => {
182+
drop(base);
183+
184+
panic!(
185+
"Error while calling script function {}::{}: {}",
186+
this.script.bind().get_class_name(),
187+
method,
188+
err
189+
);
190+
}
191+
};
163192
let data = data_guard.deref_mut();
164193
let data_ptr = data as *mut _;
165194

195+
// SAFETY: cell & data_ptr are valid for the duration of the call. The context can not out-live the current function as it's tied
196+
// to the lifetime of the base ref.
166197
let context = unsafe { GenericContext::new(cell, data_ptr, base) };
167198

168199
data.call(method, args, context)

0 commit comments

Comments
 (0)