Skip to content

Commit 8c91d89

Browse files
committed
Add OnceString for the "leak_string_sys" pattern
1 parent 6a9fd81 commit 8c91d89

File tree

12 files changed

+150
-74
lines changed

12 files changed

+150
-74
lines changed

godot-codegen/src/class_generator.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ fn make_constructor(class: &Class, ctx: &Context, class_name_str: &Literal) -> T
6969
quote! {
7070
pub fn singleton() -> Gd<Self> {
7171
unsafe {
72-
let class_name = StringName::from(#class_name_str);
73-
let object_ptr = sys::interface_fn!(global_get_singleton)(class_name.leak_string_sys());
72+
let class_name = OnceString::new(#class_name_str);
73+
let object_ptr = sys::interface_fn!(global_get_singleton)(class_name.leak_sys());
7474
Gd::from_obj_sys(object_ptr)
7575
}
7676
}
@@ -83,8 +83,8 @@ fn make_constructor(class: &Class, ctx: &Context, class_name_str: &Literal) -> T
8383
quote! {
8484
pub fn new() -> Gd<Self> {
8585
unsafe {
86-
let class_name = StringName::from(#class_name_str);
87-
let object_ptr = sys::interface_fn!(classdb_construct_object)(class_name.leak_string_sys());
86+
let class_name = OnceString::new(#class_name_str);
87+
let object_ptr = sys::interface_fn!(classdb_construct_object)(class_name.leak_sys());
8888
//let instance = Self { object_ptr };
8989
Gd::from_obj_sys(object_ptr)
9090
}
@@ -96,8 +96,8 @@ fn make_constructor(class: &Class, ctx: &Context, class_name_str: &Literal) -> T
9696
#[must_use]
9797
pub fn new_alloc() -> Gd<Self> {
9898
unsafe {
99-
let class_name = StringName::from(#class_name_str);
100-
let object_ptr = sys::interface_fn!(classdb_construct_object)(class_name.leak_string_sys());
99+
let class_name = OnceString::new(#class_name_str);
100+
let object_ptr = sys::interface_fn!(classdb_construct_object)(class_name.leak_sys());
101101
Gd::from_obj_sys(object_ptr)
102102
}
103103
}
@@ -392,11 +392,11 @@ fn make_method_definition(method: &Method, class_name: &str, ctx: &mut Context)
392392
quote! {
393393
#vis fn #method_name( #receiver #(, #params )*, varargs: &[Variant]) #return_decl {
394394
unsafe {
395-
let class_name = StringName::from(#class_name);
396-
let method_name = StringName::from(#method_name_str);
395+
let class_name = OnceString::new(#class_name);
396+
let method_name = OnceString::new(#method_name_str);
397397
let method_bind = sys::interface_fn!(classdb_get_method_bind)(
398-
class_name.leak_string_sys(),
399-
method_name.leak_string_sys(),
398+
class_name.leak_sys(),
399+
method_name.leak_sys(),
400400
#hash
401401
);
402402
let call_fn = sys::interface_fn!(object_method_bind_call);
@@ -419,11 +419,11 @@ fn make_method_definition(method: &Method, class_name: &str, ctx: &mut Context)
419419
quote! {
420420
#vis fn #method_name( #receiver, #( #params ),* ) #return_decl {
421421
unsafe {
422-
let class_name = StringName::from(#class_name);
423-
let method_name = StringName::from(#method_name_str);
422+
let class_name = OnceString::new(#class_name);
423+
let method_name = OnceString::new(#method_name_str);
424424
let method_bind = sys::interface_fn!(classdb_get_method_bind)(
425-
class_name.leak_string_sys(),
426-
method_name.leak_string_sys(),
425+
class_name.leak_sys(),
426+
method_name.leak_sys(),
427427
#hash
428428
);
429429
let call_fn = sys::interface_fn!(object_method_bind_ptrcall);
@@ -461,8 +461,8 @@ pub(crate) fn make_function_definition(
461461
quote! {
462462
pub fn #function_name( #( #params ),* ) #return_decl {
463463
let result = unsafe {
464-
let function_name = StringName::from(#function_name_str);
465-
let call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(function_name.leak_string_sys(), #hash);
464+
let function_name = OnceString::new(#function_name_str);
465+
let call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(function_name.leak_sys(), #hash);
466466
let call_fn = call_fn.unwrap_unchecked();
467467

468468
let args = [

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ impl ClassName {
3535
self.backing.string_sys()
3636
}
3737

38-
#[must_use]
39-
pub fn leak_string_name(self) -> sys::GDNativeStringNamePtr {
40-
self.backing.leak_string_sys()
38+
pub(crate) fn into_once(self) -> OnceString {
39+
OnceString::from_owned(self.backing)
4140
}
4241
}
4342

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66

77
mod class_name;
8+
mod once_string;
89
mod signature;
910

1011
pub use class_name::*;
12+
pub use once_string::*;
1113
pub use signature::*;
1214

1315
use crate::builtin::*;
@@ -66,7 +68,7 @@ impl PropertyInfo {
6668
}
6769

6870
/// Converts to the FFI type. Keep this object allocated while using that!
69-
pub fn sys(&self) -> sys::GDNativePropertyInfo {
71+
pub fn property_sys(&self) -> sys::GDNativePropertyInfo {
7072
use crate::obj::EngineEnum as _;
7173

7274
sys::GDNativePropertyInfo {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::builtin::StringName;
2+
use std::cell::Cell;
3+
use std::mem::ManuallyDrop;
4+
5+
use crate::builtin::meta::PropertyInfo;
6+
use godot_ffi as sys;
7+
8+
/// Utility to safely pass strings to FFI without creating dangling pointers or memory leaks.
9+
///
10+
/// Models a recurring pattern when passing strings to FFI functions:
11+
/// ```no_run
12+
/// # fn some_ffi_function(_ptr: godot_ffi::GDNativeStringNamePtr) {}
13+
/// # use godot_core::builtin::StringName;
14+
/// let s: StringName = todo!();
15+
///
16+
/// // Pass pointer to Godot FFI -- the underlying object must remain valid, because the sys pointer
17+
/// // might point to this object. So we can't use a .leak_sys() function which consumes self.
18+
/// some_ffi_function(s.string_sys());
19+
///
20+
/// // We don't own the string anymore, so we must not destroy it
21+
/// std::mem::forget(s);
22+
///
23+
/// // However, if in fact string_sys() was never invoked (e.g. behind an if),
24+
/// // we now have a memory leak...
25+
/// ```
26+
#[doc(hidden)]
27+
pub struct OnceArg<T: OnceSys> {
28+
obj: ManuallyDrop<T>,
29+
has_ownership: Cell<bool>,
30+
}
31+
32+
#[doc(hidden)]
33+
pub type OnceString = OnceArg<StringName>;
34+
35+
impl OnceString {
36+
pub fn new(rust_string: &str) -> Self {
37+
Self::from_owned(StringName::from(rust_string))
38+
}
39+
}
40+
41+
impl<T: OnceSys> OnceArg<T> {
42+
pub fn from_owned(value: T) -> Self {
43+
Self {
44+
obj: ManuallyDrop::new(value),
45+
has_ownership: Cell::new(true),
46+
}
47+
}
48+
49+
/// Access sys pointer while retaining underlying referred-to object.
50+
///
51+
/// Can only be called once. Leaks memory if unused.
52+
#[must_use]
53+
pub fn leak_sys(&self) -> T::SysPointer {
54+
// Interior mutability because this semantically acts like a consuming `self` function, which doesn't require `mut` either.
55+
56+
let had_ownership = self.has_ownership.replace(false);
57+
assert!(had_ownership, "cannot call leak_sys() more than once");
58+
59+
self.obj.once_sys()
60+
}
61+
}
62+
63+
impl<T: OnceSys> Drop for OnceArg<T> {
64+
fn drop(&mut self) {
65+
// If no one called leak, destroy normally to avoid memory leaks
66+
if self.has_ownership.get() {
67+
unsafe { ManuallyDrop::drop(&mut self.obj) };
68+
}
69+
}
70+
}
71+
72+
// ----------------------------------------------------------------------------------------------------------------------------------------------
73+
74+
#[doc(hidden)]
75+
pub trait OnceSys {
76+
type SysPointer;
77+
78+
fn once_sys(&self) -> Self::SysPointer;
79+
}
80+
81+
impl OnceSys for StringName {
82+
type SysPointer = sys::GDNativeStringNamePtr;
83+
84+
fn once_sys(&self) -> Self::SysPointer {
85+
self.string_sys()
86+
}
87+
}
88+
89+
impl OnceSys for PropertyInfo {
90+
type SysPointer = sys::GDNativePropertyInfo;
91+
92+
fn once_sys(&self) -> Self::SysPointer {
93+
self.property_sys()
94+
}
95+
}

godot-core/src/builtin/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ pub use variant::*;
2929
pub use vector2::*;
3030
pub use vector3::*;
3131
pub use vector4::*;
32+
33+
pub use meta::OnceString;

godot-core/src/builtin/string.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@ impl GodotString {
3232
fn string_sys = sys;
3333
fn write_string_sys = write_sys;
3434
}
35-
36-
// #[doc(hidden)]
37-
// pub fn leak_string_sys(self) -> sys::GDNativeStringPtr {
38-
// let ptr = self.string_sys();
39-
// std::mem::forget(self);
40-
// ptr
41-
// }
4235
}
4336

4437
impl GodotFfi for GodotString {

godot-core/src/builtin/string_name.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,6 @@ impl StringName {
3030
fn string_sys = sys;
3131
fn write_string_sys = write_sys;
3232
}
33-
34-
/// Do not call on temporary objects!
35-
// Important: do not abstract over this with a function taking &str and returning GDNativeStringNamePtr.
36-
// This does not work, because it needs an intermediate StringName object which must stay valid, as long
37-
// as the pointer is in use. The right way to do it is to keep a local StringName (not temporary) around.
38-
#[doc(hidden)]
39-
#[must_use]
40-
pub fn leak_string_sys(&self) -> sys::GDNativeStringNamePtr {
41-
let ptr = self.string_sys();
42-
//std::mem::forget(self);
43-
44-
let boks = Box::new(self.clone());
45-
let ptr = boks.string_sys();
46-
println!("Cloned: '{}' -> '{}'", self, &*boks);
47-
Box::leak(boks);
48-
49-
ptr
50-
}
5133
}
5234

5335
impl GodotFfi for StringName {

godot-core/src/log.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ macro_rules! godot_print {
6969

7070
pub use crate::{godot_error, godot_print, godot_script_error, godot_warn};
7171

72-
use crate::builtin::{StringName, Variant};
72+
use crate::builtin::{OnceString, Variant};
7373
use crate::sys::{self, GodotFfi};
7474

7575
pub fn print(varargs: &[Variant]) {
7676
unsafe {
77-
let method_name = StringName::from("print");
77+
let method_name = OnceString::new("print");
7878
let call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(
79-
method_name.leak_string_sys(),
79+
method_name.leak_sys(),
8080
2648703342i64,
8181
);
8282
let call_fn = call_fn.unwrap_unchecked();

godot-core/src/macros.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,36 @@ macro_rules! gdext_register_method_inner {
127127
// Return value meta-information
128128
let has_return_value: bool = $crate::gdext_is_not_unit!($($RetTy)+);
129129
let return_value_info = Sig::property_info(-1, "");
130-
let mut return_value_info_sys = return_value_info.sys();
130+
let mut return_value_info_sys = return_value_info.property_sys();
131131
let return_value_metadata = Sig::param_metadata(-1);
132132

133133
// Arguments meta-information
134134
let argument_count = NUM_ARGS as u32;
135135
let mut arguments_info: [PropertyInfo; NUM_ARGS] = {
136136
let mut i = -1i32;
137137
[$(
138-
{ i += 1; Sig::property_info(i, stringify!($param)) },
138+
{
139+
i += 1;
140+
let prop = Sig::property_info(i, stringify!($param));
141+
//OnceArg::new(prop)
142+
prop
143+
},
139144
)*]
140145
};
141146
let mut arguments_info_sys: [sys::GDNativePropertyInfo; NUM_ARGS]
142-
= std::array::from_fn(|i| arguments_info[i].sys());
147+
= std::array::from_fn(|i| arguments_info[i].property_sys());
148+
// = std::array::from_fn(|i| arguments_info[i].once_sys());
143149
let mut arguments_metadata: [sys::GDNativeExtensionClassMethodArgumentMetadata; NUM_ARGS]
144150
= std::array::from_fn(|i| Sig::param_metadata(i as i32));
145151

146-
let class_name = StringName::from(stringify!($Class));
147-
let method_name = StringName::from(stringify!($method_name));
152+
let class_name = OnceString::new(stringify!($Class));
153+
let method_name = OnceString::new(stringify!($method_name));
148154

149-
println!("REG {class_name}::{method_name}");
150-
println!(" ret {return_value_info:?}");
155+
// println!("REG {class_name}::{method_name}");
156+
// println!(" ret {return_value_info:?}");
151157

152158
let method_info = sys::GDNativeExtensionClassMethodInfo {
153-
name: method_name.leak_string_sys(),
159+
name: method_name.leak_sys(),
154160
method_userdata: std::ptr::null_mut(),
155161
call_func: Some(varcall_func),
156162
ptrcall_func: Some(ptrcall_func),
@@ -168,7 +174,7 @@ macro_rules! gdext_register_method_inner {
168174
$crate::out!(" Register fn: {}::{}", stringify!($Class), stringify!($method_name));
169175
sys::interface_fn!(classdb_register_extension_class_method)(
170176
sys::get_library(),
171-
class_name.leak_string_sys(),
177+
class_name.leak_sys(),
172178
std::ptr::addr_of!(method_info),
173179
);
174180

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ impl<T: GodotClass> Gd<T> {
331331
where
332332
U: GodotClass,
333333
{
334-
let class_name = ClassName::new::<U>();
335-
let class_tag = interface_fn!(classdb_get_class_tag)(class_name.leak_string_name());
334+
let class_name = ClassName::new::<U>().into_once();
335+
let class_tag = interface_fn!(classdb_get_class_tag)(class_name.leak_sys());
336336
let cast_object_ptr = interface_fn!(object_cast_to)(self.obj_sys(), class_tag);
337337

338338
if cast_object_ptr.is_null() {

godot-core/src/registry.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,17 @@ fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) {
221221
fn register_class_raw(info: ClassRegistrationInfo) {
222222
// First register class...
223223

224-
let class_name = info.class_name;
224+
let class_name = info.class_name.into_once();
225225
let parent_class_name = info
226226
.parent_class_name
227-
.expect("class defined (parent_class_name)");
227+
.expect("class defined (parent_class_name)")
228+
.into_once();
228229

229230
unsafe {
230231
interface_fn!(classdb_register_extension_class)(
231232
sys::get_library(),
232-
class_name.leak_string_name(),
233-
parent_class_name.leak_string_name(),
233+
class_name.leak_sys(),
234+
parent_class_name.leak_sys(),
234235
ptr::addr_of!(info.godot_params),
235236
);
236237
}
@@ -271,13 +272,13 @@ pub mod callbacks {
271272
T: GodotClass,
272273
F: FnOnce(Base<T::Base>) -> T,
273274
{
274-
let class_name = ClassName::new::<T>();
275-
let base_class_name = ClassName::new::<T::Base>();
275+
let class_name = ClassName::new::<T>().into_once();
276+
let base_class_name = ClassName::new::<T::Base>().into_once();
276277

277278
//out!("create callback: {}", class_name.backing);
278279

279280
let base_ptr =
280-
unsafe { interface_fn!(classdb_construct_object)(base_class_name.leak_string_name()) };
281+
unsafe { interface_fn!(classdb_construct_object)(base_class_name.leak_sys()) };
281282
let base = unsafe { Base::from_sys(base_ptr) };
282283

283284
let user_instance = make_user_instance(base);
@@ -287,11 +288,7 @@ pub mod callbacks {
287288

288289
let binding_data_callbacks = crate::storage::nop_instance_callbacks();
289290
unsafe {
290-
interface_fn!(object_set_instance)(
291-
base_ptr,
292-
class_name.leak_string_name(),
293-
instance_ptr,
294-
);
291+
interface_fn!(object_set_instance)(base_ptr, class_name.leak_sys(), instance_ptr);
295292
interface_fn!(object_set_instance_binding)(
296293
base_ptr,
297294
sys::get_library(),

0 commit comments

Comments
 (0)