Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions crates/vm/src/object/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T::Base>() bytes.
#[inline]
#[must_use]
pub fn into_base(self) -> PyRef<T::Base> {
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]
Expand All @@ -2897,9 +2895,8 @@ impl<T: crate::class::PySubclass> Py<T> {
/// Converts `&Py<T>` to `&Py<T::Base>`.
#[inline]
pub fn to_base(&self) -> &Py<T::Base> {
debug_assert!(self.as_object().downcast_ref::<T::Base>().is_some());
// SAFETY: T is #[repr(transparent)] over T::Base,
// so Py<T> and Py<T::Base> 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<T::Base>) }
}

Expand Down Expand Up @@ -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::<crate::exceptions::types::PyException>()
);

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;
Expand Down
Loading