diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 1eed9b712a0..8ced185eb9a 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2867,16 +2867,14 @@ where T::Base: core::fmt::Debug, { /// Converts this reference to the base type (ownership transfer). - /// # Safety - /// T and T::Base must have compatible layouts in size_of::() bytes. #[inline] #[must_use] pub fn into_base(self) -> PyRef { - let obj: PyObjectRef = self.into(); - match obj.downcast() { - Ok(base_ref) => base_ref, - Err(_) => unsafe { core::hint::unreachable_unchecked() }, - } + // PySubclass establishes the physical prefix; Python may have changed + // the object's class or MRO since this typed reference was constructed. + let ptr = self.ptr.cast(); + core::mem::forget(self); + PyRef { ptr } } #[inline] #[must_use] @@ -2897,9 +2895,8 @@ impl Py { /// Converts `&Py` to `&Py`. #[inline] pub fn to_base(&self) -> &Py { - debug_assert!(self.as_object().downcast_ref::().is_some()); - // SAFETY: T is #[repr(transparent)] over T::Base, - // so Py and Py have the same layout. + // SAFETY: PySubclass guarantees a valid base prefix with matching + // payload offsets and compatible object alignment. unsafe { &*(self as *const Self as *const Py) } } @@ -3245,6 +3242,37 @@ pub(crate) fn init_type_hierarchy() -> BootstrapTypeHierarchy { mod tests { use super::*; + #[test] + fn native_base_conversions_ignore_python_mro() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let class = vm.ctx.new_class( + None, + "HiddenOSErrorBase", + vm.ctx.exceptions.os_error.to_owned(), + Default::default(), + ); + let marker: PyObjectRef = vm.ctx.new_int(42).into(); + let error = vm.new_os_subtype_error(class.clone(), None, marker.clone()); + // A custom MRO can omit native bases without changing the allocation. + *class.mro.write() = vec![class.clone(), vm.ctx.types.object_type.to_owned()]; + assert!( + !error + .as_object() + .downcastable::() + ); + + assert!(error.to_base().to_base().get_arg(0).unwrap().is(&marker)); + assert!( + error + .into_base() + .into_base() + .get_arg(0) + .unwrap() + .is(&marker) + ); + }); + } + #[test] fn native_type_basicsize_includes_payload_padding() { use crate::class::PyClassDef;