Skip to content

Commit fdfedcf

Browse files
committed
Safe abstraction around RawGd::ffi_cast()
1 parent f6a8b67 commit fdfedcf

File tree

3 files changed

+113
-27
lines changed

3 files changed

+113
-27
lines changed

godot-core/src/obj/casts.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use crate::obj::{GodotClass, RawGd};
9+
use godot_ffi::GodotNullableFfi;
10+
use std::marker::PhantomData;
11+
use std::mem::ManuallyDrop;
12+
13+
/// Represents a successful low-level cast from `T` to `U`.
14+
///
15+
/// This exists to provide a safe API for casting, without the need for clone (and thus ref-count increments).
16+
///
17+
/// It achieves this by keeping the destination (cast result) as a weak pointer. If dropped, nothing happens.
18+
/// To extract the destination, the caller must submit a strong pointer of the source type `T` in exchange.
19+
///
20+
/// See [`RawGd::ffi_cast()`].
21+
pub(crate) struct CastSuccess<T: GodotClass, U: GodotClass> {
22+
_phantom: PhantomData<*mut T>,
23+
24+
/// Weak pointer. Drop does not decrement ref-count.
25+
dest: ManuallyDrop<RawGd<U>>,
26+
}
27+
28+
impl<T: GodotClass, U: GodotClass> CastSuccess<T, U> {
29+
/// Create from weak pointer.
30+
pub(crate) fn from_weak(weak: RawGd<U>) -> CastSuccess<T, U>
31+
where
32+
U: GodotClass,
33+
{
34+
Self {
35+
_phantom: PhantomData,
36+
dest: ManuallyDrop::new(weak),
37+
}
38+
}
39+
40+
/// Successful cast from null to null.
41+
pub fn null() -> Self {
42+
Self {
43+
_phantom: PhantomData,
44+
dest: ManuallyDrop::new(RawGd::null()),
45+
}
46+
}
47+
48+
/// Access shared reference to destination, without consuming object.
49+
pub fn as_dest_ref(&self) -> &RawGd<U> {
50+
self.check_validity();
51+
&self.dest
52+
}
53+
54+
/// Access exclusive reference to destination, without consuming object.
55+
pub fn as_dest_mut(&mut self) -> &mut RawGd<U> {
56+
self.check_validity();
57+
&mut self.dest
58+
}
59+
60+
/// Extracts destination object, sacrificing the source in exchange.
61+
///
62+
/// This trade is needed because the result is a weak pointer (no ref-count increment). By submitting a strong pointer in its place,
63+
/// we can retain the overall ref-count balance.
64+
pub fn into_dest(self, traded_source: RawGd<T>) -> RawGd<U> {
65+
debug_assert_eq!(
66+
traded_source.instance_id_unchecked(),
67+
self.dest.instance_id_unchecked(),
68+
"traded_source must point to the same object as the destination"
69+
);
70+
self.check_validity();
71+
72+
std::mem::forget(traded_source);
73+
ManuallyDrop::into_inner(self.dest)
74+
}
75+
76+
fn check_validity(&self) {
77+
debug_assert!(self.dest.is_null() || self.dest.is_instance_valid());
78+
}
79+
}

godot-core/src/obj/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! * [`Gd`], a smart pointer that manages instances of Godot classes.
1313
1414
mod base;
15+
mod casts;
1516
mod dyn_gd;
1617
mod gd;
1718
mod guards;

godot-core/src/obj/raw_gd.rs

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::meta::{
1616
CallContext, ClassName, FromGodot, GodotConvert, GodotFfiVariant, GodotType, RefArg, ToGodot,
1717
};
1818
use crate::obj::bounds::{Declarer, DynMemory as _};
19+
use crate::obj::casts::CastSuccess;
1920
use crate::obj::rtti::ObjectRtti;
2021
use crate::obj::{bounds, Bounds, GdDerefTarget, GdMut, GdRef, GodotClass, InstanceId};
2122
use crate::storage::{InstanceCache, InstanceStorage, Storage};
@@ -137,21 +138,17 @@ impl<T: GodotClass> RawGd<T> {
137138
// The Deref/DerefMut impls for T implement an "implicit upcast" on the object (not Gd) level and
138139
// rely on this (e.g. &Node3D -> &Node).
139140

140-
let result = unsafe { self.ffi_cast::<U>() };
141-
match result {
142-
Some(cast_obj) => {
143-
// duplicated ref, one must be wiped
144-
std::mem::forget(self);
145-
Ok(cast_obj)
146-
}
147-
None => Err(self),
141+
match self.ffi_cast::<U>() {
142+
Ok(success) => Ok(success.into_dest(self)),
143+
Err(_) => Err(self),
148144
}
149145
}
150146

151-
/// # Safety
152-
/// Does not transfer ownership and is thus unsafe. Also operates on shared ref. Either the parameter or
153-
/// the return value *must* be forgotten (since reference counts are not updated).
154-
pub(super) unsafe fn ffi_cast<U>(&self) -> Option<RawGd<U>>
147+
/// Low-level cast that allows selective use of either input or output type.
148+
///
149+
/// On success, you'll get a `CastSuccess<T, U>` instance, which holds a weak `RawGd<U>`. You can only extract that one by trading
150+
/// a strong `RawGd<T>` for it, to maintain the balance.
151+
pub(super) fn ffi_cast<U>(&self) -> Result<CastSuccess<T, U>, ()>
155152
where
156153
U: GodotClass,
157154
{
@@ -164,7 +161,7 @@ impl<T: GodotClass> RawGd<T> {
164161
if self.is_null() {
165162
// Null can be cast to anything.
166163
// Forgetting a null doesn't do anything, since dropping a null also does nothing.
167-
return Some(RawGd::null());
164+
return Ok(CastSuccess::null());
168165
}
169166

170167
// Before Godot API calls, make sure the object is alive (and in Debug mode, of the correct type).
@@ -173,23 +170,32 @@ impl<T: GodotClass> RawGd<T> {
173170
// a bug that must be solved by the user.
174171
self.check_rtti("ffi_cast");
175172

176-
let class_tag = interface_fn!(classdb_get_class_tag)(U::class_name().string_sys());
177-
let cast_object_ptr = interface_fn!(object_cast_to)(self.obj_sys(), class_tag);
173+
let cast_object_ptr = unsafe {
174+
let class_tag = interface_fn!(classdb_get_class_tag)(U::class_name().string_sys());
175+
interface_fn!(object_cast_to)(self.obj_sys(), class_tag)
176+
};
177+
178+
if cast_object_ptr.is_null() {
179+
return Err(());
180+
}
178181

179182
// Create weak object, as ownership will be moved and reference-counter stays the same.
180-
sys::ptr_then(cast_object_ptr, |ptr| RawGd::from_obj_sys_weak(ptr))
183+
let weak = unsafe { RawGd::from_obj_sys_weak(cast_object_ptr) };
184+
Ok(CastSuccess::from_weak(weak))
181185
}
182186

183187
pub(crate) fn with_ref_counted<R>(&self, apply: impl Fn(&mut classes::RefCounted) -> R) -> R {
184188
// Note: this previously called Declarer::scoped_mut() - however, no need to go through bind() for changes in base RefCounted.
185189
// Any accesses to user objects (e.g. destruction if refc=0) would bind anyway.
186190

187-
let tmp = unsafe { self.ffi_cast::<classes::RefCounted>() };
188-
let mut tmp = tmp.expect("object expected to inherit RefCounted");
189-
let return_val = apply(tmp.as_target_mut());
191+
let mut cast_obj = self
192+
.ffi_cast::<classes::RefCounted>()
193+
.expect("object expected to inherit RefCounted");
190194

191-
std::mem::forget(tmp); // no ownership transfer
192-
return_val
195+
// Using as_dest_mut() ensures that there is no refcount increment happening, i.e. any apply() function happens on *current* object.
196+
// Apart from performance considerations, this is relevant when examining RefCounted::get_reference_count() -- otherwise we have an
197+
// Observer effect, where reading the RefCounted object changes its reference count -- e.g. in Debug impl.
198+
apply(cast_obj.as_dest_mut().as_target_mut())
193199
}
194200

195201
// TODO replace the above with this -- last time caused UB; investigate.
@@ -301,21 +307,21 @@ impl<T: GodotClass> RawGd<T> {
301307
#[cfg(debug_assertions)]
302308
{
303309
// SAFETY: we forget the object below and do not leave the function before.
304-
let ffi_ref: RawGd<Base> =
305-
unsafe { self.ffi_cast::<Base>().expect("failed FFI upcast") };
310+
let ffi_dest = self.ffi_cast::<Base>().expect("failed FFI upcast");
306311

307312
// The ID check is not that expressive; we should do a complete comparison of the ObjectRtti, but currently the dynamic types can
308313
// be different (see comment in ObjectRtti struct). This at least checks that the transmuted object is not complete garbage.
309314
// We get direct_id from Self and not Base because the latter has no API with current bounds; but this equivalence is tested in Deref.
310315
let direct_id = self.instance_id_unchecked().expect("direct_id null");
311-
let ffi_id = ffi_ref.instance_id_unchecked().expect("ffi_id null");
316+
let ffi_id = ffi_dest
317+
.as_dest_ref()
318+
.instance_id_unchecked()
319+
.expect("ffi_id null");
312320

313321
assert_eq!(
314322
direct_id, ffi_id,
315-
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to gdext maintainers."
323+
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to godot-rust maintainers."
316324
);
317-
318-
std::mem::forget(ffi_ref);
319325
}
320326
}
321327

0 commit comments

Comments
 (0)