Skip to content

Commit 0803a55

Browse files
Convert native base references without consulting the Python class (#9090)
PyRef::into_base and Py::to_base cast the pointer directly instead of going through a class-based downcast. into_base no longer reaches unreachable_unchecked when the object's MRO omits the native base, and to_base drops the equivalent debug assertion. Extracted from #8963. Assisted-by: Codex:gpt-6 Assisted-by: Claude:claude-opus-5-5 Co-authored-by: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com>
1 parent 7c3c7a7 commit 0803a55

1 file changed

Lines changed: 38 additions & 10 deletions

File tree

‎crates/vm/src/object/core.rs‎

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2904,16 +2904,14 @@ where
29042904
T::Base: core::fmt::Debug,
29052905
{
29062906
/// Converts this reference to the base type (ownership transfer).
2907-
/// # Safety
2908-
/// T and T::Base must have compatible layouts in size_of::<T::Base>() bytes.
29092907
#[inline]
29102908
#[must_use]
29112909
pub fn into_base(self) -> PyRef<T::Base> {
2912-
let obj: PyObjectRef = self.into();
2913-
match obj.downcast() {
2914-
Ok(base_ref) => base_ref,
2915-
Err(_) => unsafe { core::hint::unreachable_unchecked() },
2916-
}
2910+
// PySubclass establishes the physical prefix; Python may have changed
2911+
// the object's class or MRO since this typed reference was constructed.
2912+
let ptr = self.ptr.cast();
2913+
core::mem::forget(self);
2914+
PyRef { ptr }
29172915
}
29182916
#[inline]
29192917
#[must_use]
@@ -2934,9 +2932,8 @@ impl<T: crate::class::PySubclass> Py<T> {
29342932
/// Converts `&Py<T>` to `&Py<T::Base>`.
29352933
#[inline]
29362934
pub fn to_base(&self) -> &Py<T::Base> {
2937-
debug_assert!(self.as_object().downcast_ref::<T::Base>().is_some());
2938-
// SAFETY: T is #[repr(transparent)] over T::Base,
2939-
// so Py<T> and Py<T::Base> have the same layout.
2935+
// SAFETY: PySubclass guarantees a valid base prefix with matching
2936+
// payload offsets and compatible object alignment.
29402937
unsafe { &*(self as *const Self as *const Py<T::Base>) }
29412938
}
29422939

@@ -3410,6 +3407,37 @@ mod tests {
34103407
}
34113408
}
34123409

3410+
#[test]
3411+
fn native_base_conversions_ignore_python_mro() {
3412+
crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
3413+
let class = vm.ctx.new_class(
3414+
None,
3415+
"HiddenOSErrorBase",
3416+
vm.ctx.exceptions.os_error.to_owned(),
3417+
Default::default(),
3418+
);
3419+
let marker: PyObjectRef = vm.ctx.new_int(42).into();
3420+
let error = vm.new_os_subtype_error(class.clone(), None, marker.clone());
3421+
// A custom MRO can omit native bases without changing the allocation.
3422+
*class.mro.write() = vec![class.clone(), vm.ctx.types.object_type.to_owned()];
3423+
assert!(
3424+
!error
3425+
.as_object()
3426+
.downcastable::<crate::exceptions::types::PyException>()
3427+
);
3428+
3429+
assert!(error.to_base().to_base().get_arg(0).unwrap().is(&marker));
3430+
assert!(
3431+
error
3432+
.into_base()
3433+
.into_base()
3434+
.get_arg(0)
3435+
.unwrap()
3436+
.is(&marker)
3437+
);
3438+
});
3439+
}
3440+
34133441
#[test]
34143442
fn native_type_basicsize_includes_payload_padding() {
34153443
use crate::class::PyClassDef;

0 commit comments

Comments
 (0)