Skip to content

Commit 8361797

Browse files
authored
Merge pull request #454 from godot-rust/bugfix/free-while-bound
Bugfix: prevent `Gd` destruction while user object is bound
2 parents c9c2793 + ae75bd1 commit 8361797

File tree

12 files changed

+349
-124
lines changed

12 files changed

+349
-124
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ members = [
1515
# utils
1616
"godot-fmt",
1717
]
18+
19+
#[patch."https://github.com/godot-rust/godot4-prebuilt"]
20+
#godot4-prebuilt = { git = "https://github.com//godot-rust/godot4-prebuilt", branch = "4.1"}

godot-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub mod private {
8282
&& *global_config.is_editor.get_or_init(is_editor)
8383
}
8484

85-
fn print_panic(err: Box<dyn std::any::Any + Send>) {
85+
pub fn print_panic(err: Box<dyn std::any::Any + Send>) {
8686
if let Some(s) = err.downcast_ref::<&'static str>() {
8787
print_panic_message(s);
8888
} else if let Some(s) = err.downcast_ref::<String>() {

godot-core/src/obj/gd.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,12 @@ impl<T: GodotClass> Gd<T> {
206206
})
207207
}
208208

209-
/// ⚠️ Returns the last known, possibly invalid instance ID of this object.
209+
/// Returns the last known, possibly invalid instance ID of this object.
210210
///
211211
/// This function does not check that the returned instance ID points to a valid instance!
212212
/// Unless performance is a problem, use [`instance_id()`][Self::instance_id] instead.
213+
///
214+
/// This method is safe and never panics.
213215
pub fn instance_id_unchecked(&self) -> InstanceId {
214216
// SAFETY:
215217
// A `Gd` can only be created from a non-null `RawGd`. Meaning `raw.instance_id_unchecked()` will
@@ -390,16 +392,22 @@ where
390392
/// example to the node tree in case of nodes.
391393
///
392394
/// # Panics
393-
/// * When the referred-to object has already been destroyed.
394-
/// * When this is invoked on an upcast `Gd<Object>` that dynamically points to a reference-counted type (i.e. operation not supported).
395+
/// - When the referred-to object has already been destroyed.
396+
/// - When this is invoked on an upcast `Gd<Object>` that dynamically points to a reference-counted type (i.e. operation not supported).
397+
/// - When the object is bound by an ongoing `bind()` or `bind_mut()` call (through a separate `Gd` pointer).
395398
pub fn free(self) {
399+
// Note: this method is NOT invoked when the free() call happens dynamically (e.g. through GDScript or reflection).
400+
// As such, do not use it for operations and validations to perform upon destruction.
401+
396402
// TODO disallow for singletons, either only at runtime or both at compile time (new memory policy) and runtime
403+
use dom::Domain;
397404

398405
// Runtime check in case of T=Object, no-op otherwise
399406
let ref_counted = T::Mem::is_ref_counted(&self.raw);
400407
assert_ne!(
401408
ref_counted, Some(true),
402-
"called free() on Gd<Object> which points to a RefCounted dynamic type; free() only supported for manually managed types."
409+
"called free() on Gd<Object> which points to a RefCounted dynamic type; free() only supported for manually managed types\n\
410+
object: {self:?}"
403411
);
404412

405413
// If ref_counted returned None, that means the instance was destroyed
@@ -408,7 +416,17 @@ where
408416
"called free() on already destroyed object"
409417
);
410418

411-
// This destroys the Storage instance, no need to run destructor again
419+
// SAFETY: object must be alive, which was just checked above. No multithreading here.
420+
// Also checked in the C free_instance_func callback, however error message can be more precise here and we don't need to instruct
421+
// the engine about object destruction. Both paths are tested.
422+
let bound = unsafe { T::Declarer::is_currently_bound(&self.raw) };
423+
assert!(
424+
!bound,
425+
"called free() while a bind() or bind_mut() call is active"
426+
);
427+
428+
// SAFETY: object alive as checked.
429+
// This destroys the Storage instance, no need to run destructor again.
412430
unsafe {
413431
sys::interface_fn!(object_destroy)(self.raw.obj_sys());
414432
}

godot-core/src/obj/guards.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
use godot_ffi::out;
8+
79
#[cfg(not(feature = "experimental-threads"))]
810
use std::cell;
911
use std::fmt::Debug;
@@ -31,6 +33,7 @@ impl<'a, T> GdRef<'a, T> {
3133

3234
#[cfg(feature = "experimental-threads")]
3335
pub(crate) fn from_cell(cell_ref: sync::RwLockReadGuard<'a, T>) -> Self {
36+
out!("GdRef init: {:?}", std::any::type_name::<T>());
3437
Self { cell_ref }
3538
}
3639
}
@@ -43,6 +46,12 @@ impl<T> Deref for GdRef<'_, T> {
4346
}
4447
}
4548

49+
impl<T> Drop for GdRef<'_, T> {
50+
fn drop(&mut self) {
51+
out!("GdRef drop: {:?}", std::any::type_name::<T>());
52+
}
53+
}
54+
4655
// TODO Clone or Share
4756

4857
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -67,6 +76,7 @@ impl<'a, T> GdMut<'a, T> {
6776

6877
#[cfg(feature = "experimental-threads")]
6978
pub(crate) fn from_cell(cell_ref: sync::RwLockWriteGuard<'a, T>) -> Self {
79+
out!("GdMut init: {:?}", std::any::type_name::<T>());
7080
Self { cell_ref }
7181
}
7282
}
@@ -84,3 +94,9 @@ impl<T> DerefMut for GdMut<'_, T> {
8494
self.cell_ref.deref_mut()
8595
}
8696
}
97+
98+
impl<T> Drop for GdMut<'_, T> {
99+
fn drop(&mut self) {
100+
out!("GdMut drop: {:?}", std::any::type_name::<T>());
101+
}
102+
}

godot-core/src/obj/raw.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ impl<T: GodotClass> Drop for RawGd<T> {
461461
fn drop(&mut self) {
462462
// No-op for manually managed objects
463463

464-
out!("RawGd::drop <{}>", std::any::type_name::<T>());
464+
// out!("RawGd::drop <{}>", std::any::type_name::<T>());
465+
465466
// SAFETY: This `Gd` wont be dropped again after this.
466467
let is_last = unsafe { T::Mem::maybe_dec_ref(self) }; // may drop
467468
if is_last {

godot-core/src/obj/traits.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ pub mod dom {
275275
where
276276
T: GodotClass<Declarer = Self>,
277277
F: FnOnce(&mut T) -> R;
278+
279+
/// Check if the object is a user object *and* currently locked by a `bind()` or `bind_mut()` guard.
280+
///
281+
/// # Safety
282+
/// Object must be alive.
283+
#[doc(hidden)]
284+
unsafe fn is_currently_bound<T>(obj: &RawGd<T>) -> bool
285+
where
286+
T: GodotClass<Declarer = Self>;
278287
}
279288

280289
/// Expresses that a class is declared by the Godot engine.
@@ -293,6 +302,13 @@ pub mod dom {
293302
.expect("scoped mut should not be called on a null object"),
294303
)
295304
}
305+
306+
unsafe fn is_currently_bound<T>(_obj: &RawGd<T>) -> bool
307+
where
308+
T: GodotClass<Declarer = Self>,
309+
{
310+
false
311+
}
296312
}
297313

298314
/// Expresses that a class is declared by the user.
@@ -309,6 +325,13 @@ pub mod dom {
309325
let mut guard = obj.bind_mut();
310326
closure(&mut *guard)
311327
}
328+
329+
unsafe fn is_currently_bound<T>(obj: &RawGd<T>) -> bool
330+
where
331+
T: GodotClass<Declarer = Self>,
332+
{
333+
obj.storage().unwrap_unchecked().is_bound()
334+
}
312335
}
313336
}
314337

0 commit comments

Comments
 (0)