@@ -381,34 +381,61 @@ impl<T: GodotClass> RawGd<T> {
381381 }
382382 }
383383
384- /// Verify that the object is non-null and alive. In paranoid mode, additionally verify that it is of type `T` or derived.
384+ /// Validates object for use in `RawGd`/`Gd` methods (not FFI boundary calls).
385+ ///
386+ /// This is used for Rust-side object operations like `bind()`, `clone()`, `ffi_cast()`, etc.
387+ /// For FFI boundary calls (generated engine methods), validation happens in signature.rs instead.
388+ ///
389+ /// # Panics
390+ /// If validation fails.
391+ #[ cfg( safeguards_balanced) ]
385392 pub ( crate ) fn check_rtti ( & self , method_name : & ' static str ) {
386- #[ cfg( safeguards_balanced) ]
387- {
388- let call_ctx = CallContext :: gd :: < T > ( method_name) ;
389- #[ cfg( safeguards_strict) ]
390- self . check_dynamic_type ( & call_ctx) ;
391- let instance_id = unsafe { self . instance_id_unchecked ( ) . unwrap_unchecked ( ) } ;
393+ let call_ctx = CallContext :: gd :: < T > ( method_name) ;
392394
393- classes:: ensure_object_alive ( instance_id, self . obj_sys ( ) , & call_ctx) ;
394- }
395+ // Type check (strict only).
396+ #[ cfg( safeguards_strict) ]
397+ self . check_dynamic_type ( & call_ctx) ;
398+
399+ // SAFETY: check_rtti() is only called for non-null pointers.
400+ let instance_id = unsafe { self . instance_id_unchecked ( ) . unwrap_unchecked ( ) } ;
401+
402+ // Liveness check (balanced + strict).
403+ classes:: ensure_object_alive ( instance_id, self . obj_sys ( ) , & call_ctx) ;
395404 }
396405
397- /// Checks only type, not alive-ness. Used in Gd<T> in case of `free()`.
406+ #[ cfg( not( safeguards_balanced) ) ]
407+ pub ( crate ) fn check_rtti ( & self , _method_name : & ' static str ) {
408+ // Disengaged level: no-op.
409+ }
410+
411+ /// Checks only type, not liveness.
412+ ///
413+ /// Used in specific scenarios like `Gd<T>::free()` where we need type validation
414+ /// but the object may already be dead. This is an internal helper for `check_rtti()`.
398415 #[ cfg( safeguards_strict) ]
416+ #[ inline]
399417 pub ( crate ) fn check_dynamic_type ( & self , call_ctx : & CallContext < ' static > ) {
400- debug_assert ! (
418+ assert ! (
401419 !self . is_null( ) ,
402- "{call_ctx}: cannot call method on null object" ,
420+ "internal bug: {call_ctx}: cannot call method on null object" ,
403421 ) ;
404422
405423 let rtti = self . cached_rtti . as_ref ( ) ;
406424
407- // SAFETY: code surrounding RawGd<T> ensures that `self` is non-null; above is just a sanity check against internal bugs .
425+ // SAFETY: RawGd non-null (checked above) .
408426 let rtti = unsafe { rtti. unwrap_unchecked ( ) } ;
409427 rtti. check_type :: < T > ( ) ;
410428 }
411429
430+ /// Creates a validated object for FFI boundary crossing.
431+ ///
432+ /// This is a convenience wrapper around [`ValidatedObject::validate()`].
433+ #[ doc( hidden) ]
434+ #[ inline]
435+ pub fn validated_object ( & self ) -> ValidatedObject {
436+ ValidatedObject :: validate ( self )
437+ }
438+
412439 // Not pub(super) because used by godot::meta::args::ObjectArg.
413440 pub ( crate ) fn obj_sys ( & self ) -> sys:: GDExtensionObjectPtr {
414441 self . obj as sys:: GDExtensionObjectPtr
@@ -428,7 +455,7 @@ where
428455{
429456 /// Hands out a guard for a shared borrow, through which the user instance can be read.
430457 ///
431- /// See [`crate::obj:: Gd::bind()`] for a more in depth explanation.
458+ /// See [`Gd::bind()`] for a more in depth explanation.
432459 // Note: possible names: write/read, hold/hold_mut, r/w, r/rw, ...
433460 pub ( crate ) fn bind ( & self ) -> GdRef < ' _ , T > {
434461 self . check_rtti ( "bind" ) ;
@@ -437,7 +464,7 @@ where
437464
438465 /// Hands out a guard for an exclusive borrow, through which the user instance can be read and written.
439466 ///
440- /// See [`crate::obj:: Gd::bind_mut()`] for a more in depth explanation.
467+ /// See [`Gd::bind_mut()`] for a more in depth explanation.
441468 pub ( crate ) fn bind_mut ( & mut self ) -> GdMut < ' _ , T > {
442469 self . check_rtti ( "bind_mut" ) ;
443470 GdMut :: from_guard ( self . storage ( ) . unwrap ( ) . get_mut ( ) )
@@ -758,6 +785,44 @@ impl<T: GodotClass> fmt::Debug for RawGd<T> {
758785 }
759786}
760787
788+ // ----------------------------------------------------------------------------------------------------------------------------------------------
789+ // Type-state for already-validated objects before an engine API call
790+
791+ /// Type-state object pointers that have been validated for engine API calls.
792+ ///
793+ /// Can be passed to [`Signature`](meta::signature::Signature) methods. Performs the following checks depending on safeguard level:
794+ /// - `disengaged`: no validation.
795+ /// - `balanced`: liveness check only.
796+ /// - `strict`: liveness + type inheritance check.
797+ #[ doc( hidden) ]
798+ pub struct ValidatedObject {
799+ object_ptr : sys:: GDExtensionObjectPtr ,
800+ }
801+
802+ impl ValidatedObject {
803+ /// Validates a `RawGd<T>` according to the type's invariants (depending on safeguard level).
804+ ///
805+ /// # Panics
806+ /// If validation fails.
807+ #[ doc( hidden) ]
808+ #[ inline]
809+ pub fn validate < T : GodotClass > ( raw_gd : & RawGd < T > ) -> Self {
810+ raw_gd. check_rtti ( "validated_object" ) ;
811+
812+ Self {
813+ object_ptr : raw_gd. obj_sys ( ) ,
814+ }
815+ }
816+
817+ /// Extracts the object pointer from an `Option<ValidatedObject>`.
818+ ///
819+ /// Returns null pointer for `None` (static methods), or the validated pointer for `Some`.
820+ #[ inline]
821+ pub fn object_ptr ( opt : Option < & Self > ) -> sys:: GDExtensionObjectPtr {
822+ opt. map ( |v| v. object_ptr ) . unwrap_or ( ptr:: null_mut ( ) )
823+ }
824+ }
825+
761826// ----------------------------------------------------------------------------------------------------------------------------------------------
762827// Reusable functions, also shared with Gd, Variant, ObjectArg.
763828
0 commit comments