Skip to content

Commit 6e7043c

Browse files
committed
Detect dead objects before RawGd construction (avoids UB)
1 parent e51bcd7 commit 6e7043c

File tree

2 files changed

+19
-26
lines changed

2 files changed

+19
-26
lines changed

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88
use crate::builtin::{
99
GString, StringName, VariantArray, VariantDispatch, VariantOperator, VariantType,
1010
};
11-
use crate::classes;
1211
use crate::meta::error::ConvertError;
1312
use crate::meta::{arg_into_ref, ArrayElement, AsArg, FromGodot, ToGodot};
14-
use crate::obj::Gd;
1513
use godot_ffi as sys;
1614
use std::{fmt, ptr};
1715
use sys::{ffi_methods, interface_fn, GodotFfi};
@@ -232,20 +230,17 @@ impl Variant {
232230
unsafe { interface_fn!(variant_booleanize)(self.var_sys()) != 0 }
233231
}
234232

235-
/* If we need to detect dead objects in the future, this is an alternative to try_to().
233+
/// Assuming that this is of type `OBJECT`, checks whether the object is dead.
234+
///
235+
/// Does not check again that the variant has type `OBJECT`.
236+
pub(crate) fn is_object_dead(&self) -> bool {
237+
debug_assert_eq!(self.get_type(), VariantType::OBJECT);
236238

237-
/// # Panics
238-
/// In Debug mode, if this variant holds an object that has been freed.
239-
fn ensure_alive_if_object(&self) {
240-
// There is no dedicated API, however to_string() returns a magic variant for dead objects.
241-
// This may of course fail if a string repr matches this exactly, but it's very unlikely.
242-
debug_assert_ne!(
243-
self.to_string(),
244-
"<Freed Object>",
245-
"Variant holds pointer to a dead object; all operations on it are invalid"
246-
)
239+
!crate::gen::utilities::is_instance_valid(self)
240+
241+
// In case there are ever problems with this approach, alternative implementation:
242+
// self.stringify() == "<Freed Object>".into()
247243
}
248-
*/
249244

250245
// Conversions from/to Godot C++ `Variant*` pointers
251246
ffi_methods! {

godot-core/src/obj/raw_gd.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ impl<T: GodotClass> RawGd<T> {
5353
} else {
5454
let raw_id = unsafe { interface_fn!(object_get_instance_id)(obj) };
5555

56-
// During Variant -> RawGd conversion, it can happen that the variant contains a dead object.
57-
// It's not quite clear how Godot detects instance_id == 0 here though, since the Variant holds the object as bytes in an array,
58-
// and there is no lookup performed.
59-
let Some(instance_id) = InstanceId::try_from_u64(raw_id) else {
60-
return Self::null();
61-
};
56+
// This happened originally during Variant -> RawGd conversion, but at this point it's too late to detect, and UB has already
57+
// occurred (the Variant holds the object pointer as bytes in an array, which becomes dangling the moment the actual object dies).
58+
let instance_id = InstanceId::try_from_u64(raw_id)
59+
.expect("null instance ID when constructing object; this very likely causes UB");
6260

6361
// TODO(bromeon): this should query dynamic type of object, which can be different from T (upcast, FromGodot, etc).
6462
// See comment in ObjectRtti.
@@ -593,6 +591,12 @@ impl<T: GodotClass> GodotFfiVariant for RawGd<T> {
593591
.into_error(variant.clone()));
594592
}
595593

594+
// Check for dead objects *before* converting. Godot doesn't care if the objects are still alive, and hitting
595+
// RawGd::from_obj_sys_weak() is too late and causes UB.
596+
if variant.is_object_dead() {
597+
return Err(FromVariantError::DeadObject.into_error(variant.clone()));
598+
}
599+
596600
let raw = unsafe {
597601
// Uses RawGd<Object> and not Self, because Godot still allows illegal conversions. We thus check with manual casting later on.
598602
// See https://github.com/godot-rust/gdext/issues/158.
@@ -603,12 +607,6 @@ impl<T: GodotClass> GodotFfiVariant for RawGd<T> {
603607
})
604608
};
605609

606-
// Explicitly handle case where object is dead. See RawGd::from_obj_sys_weak() for some edge cases and considerations.
607-
if raw.is_null() {
608-
// Passing `raw` is not useful, it would just print "null" for the value.
609-
return Err(FromVariantError::DeadObject.into_error(variant.clone()));
610-
}
611-
612610
raw.with_inc_refcount().owned_cast().map_err(|raw| {
613611
FromVariantError::WrongClass {
614612
expected: T::class_name(),

0 commit comments

Comments
 (0)