Skip to content

Commit a2b57de

Browse files
bors[bot]Bromeon
andauthored
Merge #112
112: Fix incorrect reference counts upon `Gd` object initialization r=Bromeon a=Bromeon Changes: * `Gd::from_obj_sys()` now always initializes a strong ref (the default); previously needed `.ready()` * `Gd::from_obj_sys_weak()` for the rare cases where refcount is not incremented * Fixes several cases where accidental weak initialization was used * Also remove `mem::forget()` for ptrcall return values for now; consider explicit `inc_ref` later * Unit tests for `Variant` <-> `Object` conversions with ref counts Fixes #108. Co-authored-by: Jan Haller <[email protected]>
2 parents 61c8dc6 + ac02be1 commit a2b57de

File tree

6 files changed

+84
-33
lines changed

6 files changed

+84
-33
lines changed

godot-core/src/builtin/meta/signature.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ macro_rules! impl_signature_for_tuple {
162162
unsafe { <$R as sys::GodotFuncMarshal>::try_write_sys(&ret_val, ret) }
163163
.unwrap_or_else(|e| return_error::<$R>(method_name, &e));
164164

165-
// FIXME should be inc_ref instead of forget
166-
std::mem::forget(ret_val);
165+
// FIXME is inc_ref needed here?
166+
// std::mem::forget(ret_val);
167167
}
168168
}
169169
};

godot-core/src/macros.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ macro_rules! gdext_ptrcall {
413413
)*);
414414

415415
<$($RetTy)+ as sys::GodotFfi>::write_sys(&ret_val, $ret);
416-
// FIXME should be inc_ref instead of forget
417-
#[allow(clippy::forget_copy)]
418-
std::mem::forget(ret_val);
416+
// FIXME is inc_ref needed here?
417+
// #[allow(clippy::forget_copy)]
418+
// std::mem::forget(ret_val);
419419
};
420420
}

godot-core/src/obj/base.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ impl<T: GodotClass> Base<T> {
3939
pub(crate) unsafe fn from_sys(base_ptr: sys::GDExtensionObjectPtr) -> Self {
4040
assert!(!base_ptr.is_null(), "instance base is null pointer");
4141

42-
let obj = Gd::from_obj_sys(base_ptr);
42+
// Initialize only as weak pointer (don't increment reference count)
43+
let obj = Gd::from_obj_sys_weak(base_ptr);
4344

4445
// This obj does not contribute to the strong count, otherwise we create a reference cycle:
4546
// 1. RefCounted (dropped in GDScript)

godot-core/src/obj/gd.rs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,10 @@ where
101101
T::Mem::maybe_init_ref(&result);
102102
result*/
103103

104-
let result = unsafe {
104+
unsafe {
105105
let object_ptr = callbacks::create::<T>(ptr::null_mut());
106106
Gd::from_obj_sys(object_ptr)
107-
};
108-
109-
T::Mem::maybe_init_ref(&result);
110-
result
107+
}
111108
}
112109

113110
// FIXME use ```no_run instead of ```ignore, as soon as unit test #[cfg] mess is cleaned up
@@ -138,10 +135,7 @@ where
138135
F: FnOnce(crate::obj::Base<T::Base>) -> T,
139136
{
140137
let object_ptr = callbacks::create_custom(init);
141-
let result = unsafe { Gd::from_obj_sys(object_ptr) };
142-
143-
T::Mem::maybe_init_ref(&result);
144-
result
138+
unsafe { Gd::from_obj_sys(object_ptr) }
145139
}
146140

147141
/// Hands out a guard for a shared borrow, through which the user instance can be read.
@@ -210,7 +204,7 @@ impl<T: GodotClass> Gd<T> {
210204
None
211205
} else {
212206
// SAFETY: assumes that the returned GDExtensionObjectPtr is convertible to Object* (i.e. C++ upcast doesn't modify the pointer)
213-
let untyped = unsafe { Gd::<engine::Object>::from_obj_sys(ptr).ready() };
207+
let untyped = unsafe { Gd::<engine::Object>::from_obj_sys(ptr) };
214208
untyped.owned_cast::<T>().ok()
215209
}
216210
}
@@ -277,14 +271,6 @@ impl<T: GodotClass> Gd<T> {
277271
}
278272
}
279273

280-
/// Needed to initialize ref count -- must be explicitly invoked.
281-
///
282-
/// Could be made part of FFI methods, but there are some edge cases where this is not intended.
283-
pub(crate) fn ready(self) -> Self {
284-
T::Mem::maybe_inc_ref(&self);
285-
self
286-
}
287-
288274
/// **Upcast:** convert into a smart pointer to a base class. Always succeeds.
289275
///
290276
/// Moves out of this value. If you want to create _another_ smart pointer instance,
@@ -366,7 +352,8 @@ impl<T: GodotClass> Gd<T> {
366352
let class_tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
367353
let cast_object_ptr = interface_fn!(object_cast_to)(self.obj_sys(), class_tag);
368354

369-
sys::ptr_then(cast_object_ptr, |ptr| Gd::from_obj_sys(ptr))
355+
// Create weak object, as ownership will be moved and reference-counter stays the same
356+
sys::ptr_then(cast_object_ptr, |ptr| Gd::from_obj_sys_weak(ptr))
370357
}
371358

372359
pub(crate) fn as_ref_counted<R>(&self, apply: impl Fn(&mut engine::RefCounted) -> R) -> R {
@@ -401,11 +388,30 @@ impl<T: GodotClass> Gd<T> {
401388
ffi_methods! {
402389
type sys::GDExtensionObjectPtr = Opaque;
403390

404-
fn from_obj_sys = from_sys;
405-
fn from_obj_sys_init = from_sys_init;
391+
fn from_obj_sys_weak = from_sys;
406392
fn obj_sys = sys;
407393
fn write_obj_sys = write_sys;
408394
}
395+
396+
/// Initializes this `Gd<T>` from the object pointer as a **strong ref**, meaning
397+
/// it initializes/increments the reference counter and keeps the object alive.
398+
///
399+
/// This is the default for most initializations from FFI. In cases where reference counter
400+
/// should explicitly **not** be updated, [`Self::from_obj_sys_weak`] is available.
401+
#[doc(hidden)]
402+
pub unsafe fn from_obj_sys(ptr: sys::GDExtensionObjectPtr) -> Self {
403+
// Initialize reference counter, if needed
404+
Self::from_obj_sys_weak(ptr).with_inc_refcount()
405+
}
406+
407+
/// Returns `self` but with initialized ref-count.
408+
fn with_inc_refcount(self) -> Self {
409+
// Note: use init_ref and not inc_ref, since this might be the first reference increment.
410+
// Godot expects RefCounted::init_ref to be called instead of RefCounted::reference in that case.
411+
// init_ref also doesn't hurt (except 1 possibly unnecessary check).
412+
T::Mem::maybe_init_ref(&self);
413+
self
414+
}
409415
}
410416

411417
/// _The methods in this impl block are only available for objects `T` that are manually managed,
@@ -501,7 +507,6 @@ impl<T: GodotClass> Gd<T> {
501507
/// # Safety
502508
/// `init_fn` must be a function that correctly handles a _type pointer_ pointing to an _object pointer_.
503509
#[doc(hidden)]
504-
// TODO unsafe on init_fn instead of this fn?
505510
pub unsafe fn from_sys_init_opt(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Option<Self> {
506511
// Note: see _call_native_mb_ret_obj() in godot-cpp, which does things quite different (e.g. querying the instance binding).
507512

@@ -569,7 +574,7 @@ impl<T: GodotClass> Drop for Gd<T> {
569574
impl<T: GodotClass> Share for Gd<T> {
570575
fn share(&self) -> Self {
571576
out!("Gd::share");
572-
Self::from_opaque(self.opaque).ready()
577+
Self::from_opaque(self.opaque).with_inc_refcount()
573578
}
574579
}
575580

@@ -579,19 +584,23 @@ impl<T: GodotClass> Share for Gd<T> {
579584
impl<T: GodotClass> FromVariant for Gd<T> {
580585
fn try_from_variant(variant: &Variant) -> Result<Self, VariantConversionError> {
581586
let result = unsafe {
582-
let result = Self::from_sys_init(|self_ptr| {
587+
Self::from_sys_init(|self_ptr| {
583588
let converter = sys::builtin_fn!(object_from_variant);
584589
converter(self_ptr, variant.var_sys());
585-
});
586-
result.ready()
590+
})
587591
};
588592

589-
Ok(result)
593+
// The conversion method `variant_to_object` does NOT increment the reference-count of the object; we need to do that manually.
594+
// (This behaves differently in the opposite direction `object_to_variant`.)
595+
Ok(result.with_inc_refcount())
590596
}
591597
}
592598

593599
impl<T: GodotClass> ToVariant for Gd<T> {
594600
fn to_variant(&self) -> Variant {
601+
// The conversion method `object_to_variant` DOES increment the reference-count of the object; so nothing to do here.
602+
// (This behaves differently in the opposite direction `variant_to_object`.)
603+
595604
unsafe {
596605
Variant::from_var_sys_init(|variant_ptr| {
597606
let converter = sys::builtin_fn!(object_to_variant);

itest/rust/src/object_test.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub fn run() -> bool {
3636
ok &= object_from_instance_id_unrelated_type();
3737
ok &= object_user_convert_variant();
3838
ok &= object_engine_convert_variant();
39+
ok &= object_user_convert_variant_refcount();
40+
ok &= object_engine_convert_variant_refcount();
3941
ok &= object_engine_up_deref();
4042
ok &= object_engine_up_deref_mut();
4143
ok &= object_engine_upcast();
@@ -225,6 +227,44 @@ fn object_engine_convert_variant() {
225227
obj.free();
226228
}
227229

230+
#[itest]
231+
fn object_user_convert_variant_refcount() {
232+
let obj: Gd<ObjPayload> = Gd::new(ObjPayload { value: -22222 });
233+
let obj = obj.upcast::<RefCounted>();
234+
check_convert_variant_refcount(obj)
235+
}
236+
237+
#[itest]
238+
fn object_engine_convert_variant_refcount() {
239+
let obj = RefCounted::new();
240+
check_convert_variant_refcount(obj);
241+
}
242+
243+
/// Converts between Object <-> Variant and verifies the reference counter at each stage.
244+
fn check_convert_variant_refcount(obj: Gd<RefCounted>) {
245+
// Freshly created -> refcount 1
246+
assert_eq!(obj.get_reference_count(), 1);
247+
248+
{
249+
// Variant created from object -> increment
250+
let variant = obj.to_variant();
251+
assert_eq!(obj.get_reference_count(), 2);
252+
253+
{
254+
// Yet another object created *from* variant -> increment
255+
let another_object = variant.to::<Gd<RefCounted>>();
256+
assert_eq!(obj.get_reference_count(), 3);
257+
assert_eq!(another_object.get_reference_count(), 3);
258+
}
259+
260+
// `another_object` destroyed -> decrement
261+
assert_eq!(obj.get_reference_count(), 2);
262+
}
263+
264+
// `variant` destroyed -> decrement
265+
assert_eq!(obj.get_reference_count(), 1);
266+
}
267+
228268
#[itest]
229269
fn object_engine_up_deref() {
230270
let node3d: Gd<Node3D> = Node3D::new_alloc();

itest/rust/src/variant_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ fn variant_type_correct() {
287287
VariantType::Dictionary
288288
);
289289
}
290+
290291
// ----------------------------------------------------------------------------------------------------------------------------------------------
291292

292293
fn roundtrip<T>(value: T)

0 commit comments

Comments
 (0)