Skip to content

Commit cc9c70c

Browse files
committed
Handle crash and improve error messages in init and predelete
1 parent 0c9fdf4 commit cc9c70c

File tree

3 files changed

+89
-11
lines changed

3 files changed

+89
-11
lines changed

godot-core/src/obj/bounds.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ impl DynMemory for MemRefCounted {
221221
}
222222
obj.with_ref_counted(|refc| {
223223
let success = refc.init_ref();
224-
assert!(success, "init_ref() failed");
224+
if !success {
225+
crate::godot_error!("init_ref() failed, make sure you don't create a `Gd` pointer to base/self in predelete or drop()");
226+
};
225227
});
226228

227229
/*
@@ -249,6 +251,10 @@ impl DynMemory for MemRefCounted {
249251
return false;
250252
}
251253
obj.with_ref_counted(|refc| {
254+
if refc.get_reference_count() == 0 {
255+
crate::godot_error!("Trying to unreference a RefCounted whose reference count is already zero, make sure you don't create a `Gd` pointer to base/self in predelete or drop()");
256+
return false;
257+
}
252258
let is_last = refc.unreference();
253259
out!(" +-- was last={is_last}");
254260
is_last

godot-core/src/registry/callbacks.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use crate::builder::ClassBuilder;
2020
use crate::builtin::{StringName, Variant};
2121
use crate::classes::Object;
2222
use crate::meta::PropertyInfo;
23-
use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass};
23+
use crate::obj::{
24+
bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, PassiveGd, UserClass,
25+
};
2426
use crate::private::{handle_panic, IntoVirtualMethodReceiver, PanicPayload};
2527
use crate::registry::plugin::ErasedDynGd;
2628
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
@@ -100,24 +102,25 @@ where
100102
{
101103
let base_class_name = T::Base::class_id();
102104
let base_ptr = unsafe { sys::classdb_construct_object(base_class_name.string_sys()) };
105+
let raw_id = unsafe { interface_fn!(object_get_instance_id)(base_ptr) };
103106

104107
let postinit = |base_ptr| {
105108
#[cfg(since_api = "4.4")]
106109
if notify_postinitialize {
107110
// Should notify it with a weak pointer, during `NOTIFICATION_POSTINITIALIZE`, ref-counted object is not yet fully-initialized.
108-
let mut obj = unsafe { Gd::<Object>::from_obj_sys_weak(base_ptr) };
111+
let mut obj = unsafe { PassiveGd::<Object>::from_obj_sys(base_ptr) };
109112
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
110-
obj.drop_weak();
111113
}
112114
};
113115

114116
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr, postinit) {
115117
Ok(_extension_ptr) => Ok(base_ptr),
116118
Err(payload) => {
117-
// Creation of extension object failed; we must now also destroy the base object to avoid leak.
118-
// SAFETY: `base_ptr` was just created above.
119-
unsafe { interface_fn!(object_destroy)(base_ptr) };
120-
119+
if crate::global::is_instance_id_valid(raw_id as i64) {
120+
// Creation of extension object failed; we must now also destroy the base object to avoid leak.
121+
// SAFETY: the validity of `base_ptr` is checked above.
122+
unsafe { interface_fn!(object_destroy)(base_ptr) };
123+
}
121124
Err(payload)
122125
}
123126
}
@@ -144,11 +147,21 @@ where
144147
//out!("create callback: {}", class_name.backing);
145148

146149
let base = unsafe { Base::from_sys(base_ptr) };
150+
let raw_id = unsafe { interface_fn!(object_get_instance_id)(base_ptr) };
147151

148152
// User constructor init() can panic, which crashes the engine if unhandled.
149153
let context = || format!("panic during {class_name}::init() constructor");
150154
let code = || make_user_instance(unsafe { Base::from_base(&base) });
151-
let user_instance = handle_panic(context, std::panic::AssertUnwindSafe(code))?;
155+
let user_instance = handle_panic(
156+
context,
157+
std::panic::AssertUnwindSafe(|| {
158+
let instance = code();
159+
debug_assert!(crate::global::is_instance_id_valid(raw_id as i64),
160+
"Object is released during {class_name}::init(). For RefCounted, make sure you don't create a `Gd` pointer to base/self in init(). For Object, make sure you don't call `Gd.free()` in init()"
161+
);
162+
instance
163+
}),
164+
)?;
152165

153166
// Print shouldn't be necessary as panic itself is printed. If this changes, re-enable in error case:
154167
// godot_error!("failed to create instance of {class_name}; Rust init() panicked");

itest/rust/src/object_tests/object_test.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ use std::cell::{Cell, RefCell};
1212
use std::rc::Rc;
1313

1414
use godot::builtin::{GString, StringName, Variant, Vector3};
15+
use godot::classes::notify::ObjectNotification;
1516
use godot::classes::{
16-
file_access, Engine, FileAccess, IRefCounted, Node, Node2D, Node3D, Object, RefCounted,
17+
file_access, Engine, FileAccess, IObject, IRefCounted, Node, Node2D, Node3D, Object, RefCounted,
1718
};
1819
use godot::global::godot_str;
1920
#[allow(deprecated)]
2021
use godot::meta::{FromGodot, GodotType, ToGodot};
21-
use godot::obj::{Base, Gd, Inherits, InstanceId, NewAlloc, NewGd, RawGd};
22+
use godot::obj::{Base, Gd, Inherits, InstanceId, NewAlloc, NewGd, RawGd, WithBaseField};
2223
use godot::register::{godot_api, GodotClass};
2324
use godot::sys::{self, interface_fn, GodotFfi};
2425

@@ -1114,6 +1115,64 @@ fn double_use_reference() {
11141115
emitter.free();
11151116
}
11161117

1118+
#[derive(GodotClass)]
1119+
#[class(base=Object)]
1120+
pub struct ObjInitFree {
1121+
base: Base<Object>,
1122+
}
1123+
1124+
#[godot_api]
1125+
impl IObject for ObjInitFree {
1126+
fn init(base: Base<Object>) -> Self {
1127+
let sf = Self { base };
1128+
Gd::<Self::Base>::from_instance_id(sf.base().instance_id()).free();
1129+
sf
1130+
}
1131+
}
1132+
1133+
#[derive(GodotClass)]
1134+
#[class]
1135+
pub struct RefcInitUnref {
1136+
base: Base<RefCounted>,
1137+
}
1138+
1139+
#[godot_api]
1140+
impl IRefCounted for RefcInitUnref {
1141+
fn init(base: Base<RefCounted>) -> Self {
1142+
let sf = Self { base };
1143+
Gd::<Self::Base>::from_instance_id(sf.base().instance_id());
1144+
sf
1145+
}
1146+
}
1147+
1148+
#[derive(GodotClass)]
1149+
#[class(init)]
1150+
struct RefcPredeleteUnref {
1151+
base: Base<RefCounted>,
1152+
}
1153+
1154+
#[godot_api]
1155+
impl IRefCounted for RefcPredeleteUnref {
1156+
fn on_notification(&mut self, what: ObjectNotification) {
1157+
if what == ObjectNotification::PREDELETE {
1158+
Gd::<Self>::from_instance_id(self.base().instance_id());
1159+
}
1160+
}
1161+
}
1162+
1163+
#[itest]
1164+
fn refcounted_unexpected_drop() {
1165+
expect_panic("Object freed in init()", || {
1166+
ObjInitFree::new_alloc().free();
1167+
});
1168+
expect_panic("RefCounted ref and unref in init()", || {
1169+
RefcInitUnref::new_gd();
1170+
});
1171+
1172+
// RefCounted ref and unref in predelete.
1173+
RefcPredeleteUnref::new_gd();
1174+
}
1175+
11171176
// ----------------------------------------------------------------------------------------------------------------------------------------------
11181177

11191178
// Test that one class can be declared multiple times (using #[cfg]) without conflicts

0 commit comments

Comments
 (0)