-
Notifications
You must be signed in to change notification settings - Fork 211
Description
On the Godot side, Object::set_script immediately frees the script instance, causing self to point to garbage for the rest of the function. Any operations on self is consequently UB because of the invalid state. This is problematic because it's expected that owner is an unsafe pointer, but not self, which should be a safe reference.
set_script is incidentally marked unsafe, but only because Object is not reference counted, so it may still come as a surprise to users who call it, because the unsafe keyword may be used so liberally that it no longer makes users alert.
Take this modified hello_world example that showcase the problem:
#[macro_use]
extern crate gdnative;
#[derive(gdnative::NativeClass)]
#[inherit(gdnative::Node)]
struct HelloWorld(i64);
#[gdnative::methods]
impl HelloWorld {
fn _init(_owner: gdnative::Node) -> Self {
HelloWorld(42)
}
#[export]
fn _ready(&self, mut owner: gdnative::Node) {
unsafe {
owner.set_script(None);
}
assert_eq!(42, self.0, "self should not be holding garbage");
godot_print!("hello, world.")
}
}
fn init(handle: gdnative::init::InitHandle) {
handle.add_class::<HelloWorld>();
}
godot_gdnative_init!();
godot_nativescript_init!(init);
godot_gdnative_terminate!();Output:
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
left: `42`,
right: `944####680`: self should not be holding garbage', examples/hello_world/src/lib.rs:19:9
Dealing with the problem
As there are more ways that owner can be freed (along with its script instance), it might be best to replace the userdata Box with an Rc (or even Arc. I'm not sure if it's possible for Godot to free an Object from another thread) to prevent UB even if the Godot side called the destructor midway.