Skip to content

Commit a20bb85

Browse files
authored
Merge pull request #841 from godot-rust/bugfix/export-node-only-in-node
Validate that Nodes can only be exported from Node-derived classes
2 parents 76b2d99 + 38b7ed0 commit a20bb85

File tree

4 files changed

+51
-9
lines changed

4 files changed

+51
-9
lines changed

godot-core/src/obj/gd.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::builtin::{Callable, NodePath, StringName, Variant};
1616
use crate::global::PropertyHint;
1717
use crate::meta::error::{ConvertError, FromFfiError};
1818
use crate::meta::{
19-
ArrayElement, CallContext, FromGodot, GodotConvert, GodotType, PropertyHintInfo, ToGodot,
19+
ArrayElement, CallContext, ClassName, FromGodot, GodotConvert, GodotType, PropertyHintInfo,
20+
ToGodot,
2021
};
2122
use crate::obj::{
2223
bounds, cap, Bounds, EngineEnum, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId,
@@ -823,6 +824,11 @@ where
823824

824825
PropertyHintInfo { hint, hint_string }
825826
}
827+
828+
#[doc(hidden)]
829+
fn as_node_class() -> Option<ClassName> {
830+
T::inherits::<classes::Node>().then(|| T::class_name())
831+
}
826832
}
827833

828834
// Trait impls Property, Export and TypeStringHint for Option<Gd<T>> are covered by blanket impl for Option<T>

godot-core/src/registry/godot_register_wrappers.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,34 @@ use crate::builtin::StringName;
1111
use crate::global::PropertyUsageFlags;
1212
use crate::meta::{ClassName, GodotConvert, GodotType, PropertyHintInfo, PropertyInfo};
1313
use crate::obj::GodotClass;
14-
use crate::registry::property::Var;
15-
use crate::sys;
14+
use crate::registry::property::{Export, Var};
15+
use crate::{classes, sys};
1616
use godot_ffi::GodotFfi;
1717

18-
pub fn register_var_or_export<C: GodotClass, T: Var>(
18+
/// Same as [`register_var()`], but statically verifies the `Export` trait (again) and the fact that nodes can only be exported from nodes.
19+
pub fn register_export<C: GodotClass, T: Export>(
20+
property_name: &str,
21+
getter_name: &str,
22+
setter_name: &str,
23+
hint_info: PropertyHintInfo,
24+
usage: PropertyUsageFlags,
25+
) {
26+
// Note: if the user manually specifies `hint`, `hint_string` or `usage` keys, and thus is routed to `register_var()` instead,
27+
// they can bypass this validation.
28+
if !C::inherits::<classes::Node>() {
29+
if let Some(class) = T::as_node_class() {
30+
panic!(
31+
"#[export] for Gd<{t}>: nodes can only be exported in Node-derived classes, but the current class is {c}.",
32+
t = class,
33+
c = C::class_name()
34+
);
35+
}
36+
}
37+
38+
register_var::<C, T>(property_name, getter_name, setter_name, hint_info, usage);
39+
}
40+
41+
pub fn register_var<C: GodotClass, T: Var>(
1942
property_name: &str,
2043
getter_name: &str,
2144
setter_name: &str,

godot-core/src/registry/property.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
1010
use godot_ffi as sys;
1111

12-
use crate::meta::{FromGodot, GodotConvert, GodotType, PropertyHintInfo, ToGodot};
12+
use crate::meta::{ClassName, FromGodot, GodotConvert, GodotType, PropertyHintInfo, ToGodot};
1313

1414
// ----------------------------------------------------------------------------------------------------------------------------------------------
1515
// Trait definitions
@@ -55,6 +55,14 @@ pub trait Export: Var {
5555
fn export_hint() -> PropertyHintInfo {
5656
<Self as Var>::var_hint()
5757
}
58+
59+
/// If this is a class inheriting `Node`, returns the `ClassName`; otherwise `None`.
60+
///
61+
/// Only overridden for `Gd<T>`, to detect erroneous exports of `Node` inside a `Resource` class.
62+
#[allow(clippy::wrong_self_convention)]
63+
fn as_node_class() -> Option<ClassName> {
64+
None
65+
}
5866
}
5967

6068
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-macros/src/class/data_models/property.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,19 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
7272
mut usage_flags,
7373
} = var;
7474

75-
let mut export_hint = None;
75+
let export_hint;
76+
let registration_fn;
7677

7778
if let Some(export) = export {
78-
export_hint = export.to_export_hint();
79-
8079
if usage_flags.is_inferred() {
8180
usage_flags = UsageFlags::InferredExport;
8281
}
82+
83+
export_hint = export.to_export_hint();
84+
registration_fn = quote! { register_export };
85+
} else {
86+
export_hint = None;
87+
registration_fn = quote! { register_var };
8388
}
8489

8590
let usage_flags = match usage_flags {
@@ -140,7 +145,7 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
140145
);
141146

142147
export_tokens.push(quote! {
143-
::godot::register::private::register_var_or_export::<#class_name, #field_type>(
148+
::godot::register::private::#registration_fn::<#class_name, #field_type>(
144149
#field_name,
145150
#getter_name,
146151
#setter_name,

0 commit comments

Comments
 (0)