Skip to content

Commit 3d2a7a0

Browse files
bors[bot]Bromeon
andauthored
Merge #844
844: `Rid` and APIs accepting it are now unsafe r=Bromeon a=Bromeon Fixes #836. Makes all GDNative API methods accepting at least one `Rid` parameter `unsafe`. Overall changes in `Rid`: * Remove `operator_less()` * Rename `is_valid()` -> `is_occupied()` * `get_id()` is now `unsafe` + null-checked (if the RID is dangling, Godot dereferences an invalid pointer) * Fix `PartialOrd` impl * Docs Co-authored-by: Jan Haller <[email protected]>
2 parents 2d2476a + f252d57 commit 3d2a7a0

File tree

5 files changed

+109
-55
lines changed

5 files changed

+109
-55
lines changed

bindings_generator/src/api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl GodotArgument {
324324
}
325325
}
326326

327-
#[derive(Clone)]
327+
#[derive(Clone, Eq, PartialEq)]
328328
pub enum Ty {
329329
Void,
330330
String,

bindings_generator/src/methods.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,20 +344,19 @@ pub(crate) fn generate_methods(
344344
let icall_name = method_sig.function_name();
345345
let icall = format_ident!("{}", icall_name);
346346

347-
icalls.insert(icall_name.clone(), method_sig);
348-
349-
let rusty_name = rust_safe_name(rusty_method_name);
350-
351347
let maybe_unsafe: TokenStream;
352348
let maybe_unsafe_reason: &str;
353-
if UNSAFE_OBJECT_METHODS.contains(&(&class.name, method_name)) {
349+
if let Some(unsafe_reason) = unsafe_reason(class, method_name, &method_sig) {
354350
maybe_unsafe = quote! { unsafe };
355-
maybe_unsafe_reason = "\n# Safety\nThis function bypasses Rust's static type checks \
356-
(aliasing, thread boundaries, calls to free(), ...).";
351+
maybe_unsafe_reason = unsafe_reason;
357352
} else {
358353
maybe_unsafe = TokenStream::default();
359354
maybe_unsafe_reason = "";
360-
};
355+
}
356+
357+
icalls.insert(icall_name.clone(), method_sig);
358+
359+
let rusty_name = rust_safe_name(rusty_method_name);
361360

362361
let method_bind_fetch = {
363362
let method_table = format_ident!("{}MethodTable", class.name);
@@ -394,6 +393,27 @@ pub(crate) fn generate_methods(
394393
result
395394
}
396395

396+
/// Returns a message as to why this method would be unsafe; or None if the method is safe
397+
fn unsafe_reason(
398+
class: &GodotClass,
399+
method_name: &str,
400+
method_sig: &MethodSig,
401+
) -> Option<&'static str> {
402+
if UNSAFE_OBJECT_METHODS.contains(&(&class.name, method_name)) {
403+
Some(
404+
"\n# Safety\
405+
\nThis function bypasses Rust's static type checks (aliasing, thread boundaries, calls to free(), ...).",
406+
)
407+
} else if method_sig.arguments.contains(&Ty::Rid) {
408+
Some(
409+
"\n# Safety\
410+
\nThis function has parameters of type `Rid` (resource ID). \
411+
RIDs are untyped and interpreted as raw pointers by the engine, so passing an incorrect RID can cause UB.")
412+
} else {
413+
None
414+
}
415+
}
416+
397417
fn ret_recover(ty: &Ty, icall_ty: IcallType) -> TokenStream {
398418
match icall_ty {
399419
IcallType::Ptr => ty.to_return_post(),

gdnative-core/src/core_types/rid.rs

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,57 @@
11
use crate::private::get_api;
22
use crate::sys;
33
use std::cmp::Ordering;
4-
use std::cmp::{Eq, PartialEq};
54
use std::mem::transmute;
65

7-
/// The RID type is used to access the unique integer ID of a resource.
8-
/// They are opaque, so they do not grant access to the associated resource by themselves.
6+
// Note: for safety design, consult:
7+
// * https://github.com/godotengine/godot/blob/3.x/core/rid.h
8+
// * https://github.com/godotengine/godot/blob/3.x/modules/gdnative/include/gdnative/rid.h
9+
10+
/// A RID ("resource ID") is an opaque handle that refers to a Godot `Resource`.
11+
///
12+
/// RIDs do not grant access to the resource itself. Instead, they can be used in lower-level resource APIs
13+
/// such as the [servers]. See also [Godot API docs for `RID`][docs].
14+
///
15+
/// Note that RIDs are highly unsafe to work with (especially with a Release build of Godot):
16+
/// * They are untyped, i.e. Godot does not recognize if they represent the correct resource type.
17+
/// * The internal handle is interpreted as a raw pointer by Godot, meaning that passing an invalid or wrongly
18+
/// typed RID is instant undefined behavior.
19+
///
20+
/// For this reason, GDNative methods accepting `Rid` parameters are marked `unsafe`.
21+
///
22+
/// [servers]: https://docs.godotengine.org/en/stable/tutorials/optimization/using_servers.html
23+
/// [docs]: https://docs.godotengine.org/en/stable/classes/class_rid.html
924
#[derive(Copy, Clone, Debug)]
1025
pub struct Rid(pub(crate) sys::godot_rid);
1126

1227
impl Rid {
28+
/// Creates an empty, invalid RID.
1329
#[inline]
1430
pub fn new() -> Self {
1531
Rid::default()
1632
}
1733

34+
/// Returns the ID of the referenced resource.
35+
///
36+
/// # Panics
37+
/// When this instance is empty, i.e. `self.is_occupied()` is false.
38+
///
39+
/// # Safety
40+
/// RIDs are untyped and interpreted as raw pointers by the engine.
41+
/// If this method is called on an invalid resource ID, the behavior is undefined.
42+
/// This can happen when the resource behind the RID is no longer alive.
1843
#[inline]
19-
pub fn get_id(self) -> i32 {
20-
unsafe { (get_api().godot_rid_get_id)(&self.0) }
21-
}
22-
23-
#[inline]
24-
pub fn operator_less(self, b: Rid) -> bool {
25-
unsafe { (get_api().godot_rid_operator_less)(&self.0, &b.0) }
44+
pub unsafe fn get_id(self) -> i32 {
45+
assert!(self.is_occupied());
46+
(get_api().godot_rid_get_id)(&self.0)
2647
}
2748

49+
/// Check if this RID is non-empty. This does **not** mean it's valid or safe to use!
50+
///
51+
/// This simply checks if the handle has not been initialized with the empty default.
52+
/// It does not give any indication about whether it points to a valid resource.
2853
#[inline]
29-
pub fn is_valid(self) -> bool {
54+
pub fn is_occupied(self) -> bool {
3055
self.to_u64() != 0
3156
}
3257

@@ -73,14 +98,12 @@ impl_basic_traits_as_sys! {
7398
impl PartialOrd for Rid {
7499
#[inline]
75100
fn partial_cmp(&self, other: &Rid) -> Option<Ordering> {
76-
unsafe {
77-
let native = (get_api().godot_rid_operator_less)(&self.0, &other.0);
78-
79-
if native {
80-
Some(Ordering::Less)
81-
} else {
82-
Some(Ordering::Greater)
83-
}
101+
if PartialEq::eq(self, other) {
102+
Some(Ordering::Equal)
103+
} else if unsafe { (get_api().godot_rid_operator_less)(self.sys(), other.sys()) } {
104+
Some(Ordering::Less)
105+
} else {
106+
Some(Ordering::Greater)
84107
}
85108
}
86109
}

gdnative-core/src/export/mod.rs

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Functionality for user-defined types exported to the engine (native scripts)
1+
//! Functionality for user-defined types exported to the engine (native scripts).
22
//!
33
//! NativeScript allows users to have their own scripts in a native language (in this case Rust).
44
//! It is _not_ the same as GDNative, the native interface to call into Godot.
@@ -8,32 +8,7 @@
88
//! If you are looking for how to manage Godot core types or classes (objects), check
99
//! out the [`core_types`][crate::core_types] and [`object`][crate::object] modules, respectively.
1010
//!
11-
//! ## Init and exit hooks
12-
//!
13-
//! Three endpoints are automatically invoked by the engine during startup and shutdown:
14-
//!
15-
//! * [`godot_gdnative_init`],
16-
//! * [`godot_nativescript_init`],
17-
//! * [`godot_gdnative_terminate`],
18-
//!
19-
//! All three must be present. To quickly define all three endpoints using the default names,
20-
//! use [`godot_init`].
21-
//!
22-
//! ## Registering script classes
23-
//!
24-
//! [`InitHandle`] is the registry of all your exported symbols.
25-
//! To register script classes, call [`InitHandle::add_class()`] or [`InitHandle::add_tool_class()`]
26-
//! in your [`godot_nativescript_init`] or [`godot_init`] callback:
27-
//!
28-
//! ```ignore
29-
//! use gdnative::prelude::*;
30-
//!
31-
//! fn init(handle: InitHandle) {
32-
//! handle.add_class::<HelloWorld>();
33-
//! }
34-
//!
35-
//! godot_init!(init);
36-
//! ```
11+
//! To handle initialization and shutdown of godot-rust, take a look at the [`init`][crate::init] module.
3712
//!
3813
//! For full examples, see [`examples`](https://github.com/godot-rust/godot-rust/tree/master/examples)
3914
//! in the godot-rust repository.

gdnative-core/src/init/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,40 @@
11
//! Global initialization and termination of the library.
2+
//!
3+
//! This module provides all the plumbing required for global initialization and shutdown of godot-rust.
4+
//!
5+
//! ## Init and exit hooks
6+
//!
7+
//! Three endpoints are automatically invoked by the engine during startup and shutdown:
8+
//!
9+
//! * [`godot_gdnative_init`],
10+
//! * [`godot_nativescript_init`],
11+
//! * [`godot_gdnative_terminate`],
12+
//!
13+
//! All three must be present. To quickly define all three endpoints using the default names,
14+
//! use [`godot_init`].
15+
//!
16+
//! ## Registering script classes
17+
//!
18+
//! [`InitHandle`] is the registry of all your exported symbols.
19+
//! To register script classes, call [`InitHandle::add_class()`] or [`InitHandle::add_tool_class()`]
20+
//! in your `godot_nativescript_init` or `godot_init` callback:
21+
//!
22+
//! ```no_run
23+
//! use gdnative::prelude::*;
24+
//!
25+
//! #[derive(NativeClass)]
26+
//! # #[no_constructor]
27+
//! struct HelloWorld { /* ... */ }
28+
//!
29+
//! #[methods]
30+
//! impl HelloWorld { /* ... */ }
31+
//!
32+
//! fn init(handle: InitHandle) {
33+
//! handle.add_class::<HelloWorld>();
34+
//! }
35+
//!
36+
//! godot_init!(init);
37+
//! ```
238
339
mod info;
440
mod init_handle;

0 commit comments

Comments
 (0)