Skip to content

Commit 426b218

Browse files
committed
wrap PyClassObjectContents to prevent it from leaking outside pyo3
1 parent ccce96d commit 426b218

12 files changed

+92
-58
lines changed

src/impl_/coroutine.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
use crate::{
77
coroutine::{cancel::ThrowCallback, Coroutine},
88
instance::Bound,
9-
pycell::impl_::{InternalPyClassObjectLayout, PyClassBorrowChecker},
9+
pycell::impl_::{PyClassBorrowChecker, PyClassObjectLayout},
1010
pyclass::boolean_struct::False,
1111
types::{PyAnyMethods, PyString},
1212
IntoPyObject, Py, PyAny, PyClass, PyErr, PyResult, Python,

src/impl_/pycell.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Externally-accessible implementation of pycell
22
pub use crate::pycell::impl_::{
3-
GetBorrowChecker, PyClassMutability, PyClassObjectBase, PyClassObjectLayout,
3+
GetBorrowChecker, PyClassMutability, PyClassObjectBase, PyClassObjectBaseLayout,
44
PyStaticClassObject, PyVariableClassObject, PyVariableClassObjectBase,
55
};

src/impl_/pyclass.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use crate::{
66
ffi,
77
impl_::{
88
freelist::FreeList,
9-
pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
9+
pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectBaseLayout},
1010
pyclass_init::PyObjectInit,
1111
pymethods::{PyGetterDef, PyMethodDefType},
1212
},
13-
pycell::{impl_::InternalPyClassObjectLayout, PyBorrowError},
13+
pycell::{impl_::PyClassObjectLayout, PyBorrowError},
1414
types::{any::PyAnyMethods, PyBool},
1515
Borrowed, BoundObject, Py, PyAny, PyClass, PyErr, PyRef, PyResult, PyTypeInfo, Python,
1616
};
@@ -170,7 +170,7 @@ pub trait PyClassImpl: Sized + 'static {
170170
const IS_SEQUENCE: bool = false;
171171

172172
/// Description of how this class is laid out in memory
173-
type Layout: InternalPyClassObjectLayout<Self>;
173+
type Layout: PyClassObjectLayout<Self>;
174174

175175
/// Base class
176176
type BaseType: PyTypeInfo + PyClassBaseType;
@@ -1137,7 +1137,7 @@ impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
11371137
)
11381138
)]
11391139
pub trait PyClassBaseType: Sized {
1140-
type LayoutAsBase: PyClassObjectLayout<Self>;
1140+
type LayoutAsBase: PyClassObjectBaseLayout<Self>;
11411141
type BaseNativeType;
11421142
type Initializer: PyObjectInit<Self>;
11431143
type PyClassMutability: PyClassMutability;
@@ -1549,7 +1549,7 @@ where
15491549
let class_ptr = obj.cast::<<ClassT as PyClassImpl>::Layout>();
15501550
// Safety: the object `obj` must have the layout `ClassT::Layout`
15511551
let class_obj = unsafe { &mut *class_ptr };
1552-
let contents = class_obj.contents_mut() as *mut PyClassObjectContents<ClassT>;
1552+
let contents = (&mut class_obj.contents_mut().0) as *mut PyClassObjectContents<ClassT>;
15531553
(contents.cast::<u8>(), offset)
15541554
}
15551555
#[cfg(not(Py_3_12))]

src/impl_/pyclass_init.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
use crate::ffi_ptr_ext::FfiPtrExt;
33
use crate::impl_::pyclass::PyClassImpl;
44
use crate::internal::get_slot::TP_ALLOC;
5-
use crate::pycell::impl_::{InternalPyClassObjectLayout, PyClassObjectContents};
5+
use crate::pycell::impl_::{
6+
PyClassObjectContents, PyClassObjectLayout, WrappedPyClassObjectContents,
7+
};
68
use crate::types::PyType;
79
use crate::{ffi, Borrowed, PyErr, PyResult, Python};
810
use crate::{ffi::PyTypeObject, sealed::Sealed, type_object::PyTypeInfo};
911
use std::marker::PhantomData;
1012

1113
pub unsafe fn initialize_with_default<T: PyClassImpl + Default>(obj: *mut ffi::PyObject) {
1214
let contents = T::Layout::contents_uninitialised(obj);
13-
(*contents).write(PyClassObjectContents::new(T::default()));
15+
(*contents).write(WrappedPyClassObjectContents(PyClassObjectContents::new(
16+
T::default(),
17+
)));
1418
}
1519

1620
/// Initializer for Python types.

src/impl_/pymethods.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use crate::exceptions::PyStopAsyncIteration;
22
use crate::gil::LockGIL;
33
use crate::impl_::callback::IntoPyCallbackOutput;
44
use crate::impl_::panic::PanicTrap;
5-
use crate::impl_::pycell::PyClassObjectLayout;
5+
use crate::impl_::pycell::PyClassObjectBaseLayout;
66
use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE};
7-
use crate::pycell::impl_::{InternalPyClassObjectLayout, PyClassBorrowChecker as _};
7+
use crate::pycell::impl_::{PyClassBorrowChecker as _, PyClassObjectLayout};
88
use crate::pycell::{PyBorrowError, PyBorrowMutError};
99
use crate::pyclass::boolean_struct::False;
1010
use crate::types::any::PyAnyMethods;
@@ -310,8 +310,8 @@ where
310310
if class_object.check_threadsafe().is_ok()
311311
// ... and we cannot traverse a type which might be being mutated by a Rust thread
312312
&& class_object.borrow_checker().try_borrow().is_ok() {
313-
struct TraverseGuard<'a, U: PyClassImpl, V: InternalPyClassObjectLayout<U>>(&'a V, PhantomData<U>);
314-
impl<U: PyClassImpl, V: InternalPyClassObjectLayout<U>> Drop for TraverseGuard<'_, U, V> {
313+
struct TraverseGuard<'a, U: PyClassImpl, V: PyClassObjectLayout<U>>(&'a V, PhantomData<U>);
314+
impl<U: PyClassImpl, V: PyClassObjectLayout<U>> Drop for TraverseGuard<'_, U, V> {
315315
fn drop(&mut self) {
316316
self.0.borrow_checker().release_borrow()
317317
}
@@ -320,7 +320,7 @@ where
320320
// `.try_borrow()` above created a borrow, we need to release it when we're done
321321
// traversing the object. This allows us to read `instance` safely.
322322
let _guard = TraverseGuard(class_object, PhantomData);
323-
let instance = &*class_object.contents().value.get();
323+
let instance = &*class_object.contents().0.value.get();
324324

325325
let visit = PyVisit { visit, arg, _guard: PhantomData };
326326

src/instance.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::conversion::IntoPyObject;
22
use crate::err::{self, PyErr, PyResult};
33
use crate::impl_::pyclass::PyClassImpl;
44
use crate::internal_tricks::ptr_from_ref;
5-
use crate::pycell::impl_::InternalPyClassObjectLayout;
5+
use crate::pycell::impl_::PyClassObjectLayout;
66
use crate::pycell::{PyBorrowError, PyBorrowMutError};
77
use crate::pyclass::boolean_struct::{False, True};
88
use crate::types::{any::PyAnyMethods, string::PyStringMethods, typeobject::PyTypeMethods};

src/pycell.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ use std::mem::ManuallyDrop;
208208
use std::ops::{Deref, DerefMut};
209209

210210
pub(crate) mod impl_;
211-
use impl_::{InternalPyClassObjectLayout, PyClassBorrowChecker, PyClassObjectLayout};
211+
use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout};
212212

213213
/// A wrapper type for an immutably borrowed value from a [`Bound<'py, T>`].
214214
///

src/pycell/impl_.rs

+62-34
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,13 @@ pub trait GetBorrowChecker<T: PyClassImpl> {
187187

188188
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for MutableClass {
189189
fn borrow_checker(class_object: &T::Layout) -> &BorrowChecker {
190-
&class_object.contents().borrow_checker
190+
&class_object.contents().0.borrow_checker
191191
}
192192
}
193193

194194
impl<T: PyClassImpl<PyClassMutability = Self>> GetBorrowChecker<T> for ImmutableClass {
195195
fn borrow_checker(class_object: &T::Layout) -> &EmptySlot {
196-
&class_object.contents().borrow_checker
196+
&class_object.contents().0.borrow_checker
197197
}
198198
}
199199

@@ -226,7 +226,7 @@ pub struct PyVariableClassObjectBase {
226226

227227
unsafe impl<T> PyLayout<T> for PyVariableClassObjectBase {}
228228

229-
impl<T: PyTypeInfo> PyClassObjectLayout<T> for PyVariableClassObjectBase {
229+
impl<T: PyTypeInfo> PyClassObjectBaseLayout<T> for PyVariableClassObjectBase {
230230
fn ensure_threadsafe(&self) {}
231231
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
232232
Ok(())
@@ -237,7 +237,7 @@ impl<T: PyTypeInfo> PyClassObjectLayout<T> for PyVariableClassObjectBase {
237237
}
238238

239239
#[doc(hidden)]
240-
pub trait PyClassObjectLayout<T>: PyLayout<T> {
240+
pub trait PyClassObjectBaseLayout<T>: PyLayout<T> {
241241
fn ensure_threadsafe(&self);
242242
fn check_threadsafe(&self) -> Result<(), PyBorrowError>;
243243
/// Implementation of tp_dealloc.
@@ -247,6 +247,30 @@ pub trait PyClassObjectLayout<T>: PyLayout<T> {
247247
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject);
248248
}
249249

250+
/// Allow [PyClassObjectLayout] to have public visibility without leaking the structure of [PyClassObjectContents].
251+
#[doc(hidden)]
252+
#[repr(transparent)]
253+
pub struct WrappedPyClassObjectContents<T: PyClassImpl>(pub(crate) PyClassObjectContents<T>);
254+
255+
impl<'a, T: PyClassImpl> From<&'a PyClassObjectContents<T>>
256+
for &'a WrappedPyClassObjectContents<T>
257+
{
258+
fn from(value: &'a PyClassObjectContents<T>) -> &'a WrappedPyClassObjectContents<T> {
259+
// Safety: Wrapped struct must use repr(transparent)
260+
unsafe { std::mem::transmute(value) }
261+
}
262+
}
263+
264+
impl<'a, T: PyClassImpl> From<&'a mut PyClassObjectContents<T>>
265+
for &'a mut WrappedPyClassObjectContents<T>
266+
{
267+
fn from(value: &'a mut PyClassObjectContents<T>) -> &'a mut WrappedPyClassObjectContents<T> {
268+
// Safety: Wrapped struct must use repr(transparent)
269+
unsafe { std::mem::transmute(value) }
270+
}
271+
}
272+
273+
/// Functionality required for creating and managing the memory associated with a pyclass annotated struct.
250274
#[doc(hidden)]
251275
#[cfg_attr(
252276
all(diagnostic_namespace),
@@ -256,18 +280,18 @@ pub trait PyClassObjectLayout<T>: PyLayout<T> {
256280
note = "the python version being built against influences which layouts are valid",
257281
)
258282
)]
259-
pub trait InternalPyClassObjectLayout<T: PyClassImpl>: PyClassObjectLayout<T> {
283+
pub trait PyClassObjectLayout<T: PyClassImpl>: PyClassObjectBaseLayout<T> {
260284
/// Obtain a pointer to the contents of an uninitialized PyObject of this type
261285
/// Safety: the provided object must have the layout that the implementation is expecting
262286
unsafe fn contents_uninitialised(
263287
obj: *mut ffi::PyObject,
264-
) -> *mut MaybeUninit<PyClassObjectContents<T>>;
288+
) -> *mut MaybeUninit<WrappedPyClassObjectContents<T>>;
265289

266290
fn get_ptr(&self) -> *mut T;
267291

268-
fn contents(&self) -> &PyClassObjectContents<T>;
292+
fn contents(&self) -> &WrappedPyClassObjectContents<T>;
269293

270-
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T>;
294+
fn contents_mut(&mut self) -> &mut WrappedPyClassObjectContents<T>;
271295

272296
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase;
273297

@@ -287,7 +311,7 @@ pub trait InternalPyClassObjectLayout<T: PyClassImpl>: PyClassObjectLayout<T> {
287311
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker;
288312
}
289313

290-
impl<T, U> PyClassObjectLayout<T> for PyClassObjectBase<U>
314+
impl<T, U> PyClassObjectBaseLayout<T> for PyClassObjectBase<U>
291315
where
292316
U: PySizedLayout<T>,
293317
T: PyTypeInfo,
@@ -301,6 +325,10 @@ where
301325
}
302326
}
303327

328+
/// Implementation of tp_dealloc.
329+
/// # Safety
330+
/// - obj must be a valid pointer to an instance of the type at `type_ptr` or a subclass.
331+
/// - obj must not be used after this call (as it will be freed).
304332
unsafe fn tp_dealloc(py: Python<'_>, obj: *mut ffi::PyObject, type_ptr: *mut ffi::PyTypeObject) {
305333
// FIXME: there is potentially subtle issues here if the base is overwritten
306334
// at runtime? To be investigated.
@@ -376,14 +404,14 @@ pub struct PyStaticClassObject<T: PyClassImpl> {
376404
contents: PyClassObjectContents<T>,
377405
}
378406

379-
impl<T: PyClassImpl> InternalPyClassObjectLayout<T> for PyStaticClassObject<T> {
407+
impl<T: PyClassImpl> PyClassObjectLayout<T> for PyStaticClassObject<T> {
380408
unsafe fn contents_uninitialised(
381409
obj: *mut ffi::PyObject,
382-
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
410+
) -> *mut MaybeUninit<WrappedPyClassObjectContents<T>> {
383411
#[repr(C)]
384412
struct PartiallyInitializedClassObject<T: PyClassImpl> {
385413
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
386-
contents: MaybeUninit<PyClassObjectContents<T>>,
414+
contents: MaybeUninit<WrappedPyClassObjectContents<T>>,
387415
}
388416
let obj: *mut PartiallyInitializedClassObject<T> = obj.cast();
389417
addr_of_mut!((*obj).contents)
@@ -397,12 +425,12 @@ impl<T: PyClassImpl> InternalPyClassObjectLayout<T> for PyStaticClassObject<T> {
397425
&self.ob_base
398426
}
399427

400-
fn contents(&self) -> &PyClassObjectContents<T> {
401-
&self.contents
428+
fn contents(&self) -> &WrappedPyClassObjectContents<T> {
429+
(&self.contents).into()
402430
}
403431

404-
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
405-
&mut self.contents
432+
fn contents_mut(&mut self) -> &mut WrappedPyClassObjectContents<T> {
433+
(&mut self.contents).into()
406434
}
407435

408436
/// used to set PyType_Spec::basicsize
@@ -453,9 +481,9 @@ impl<T: PyClassImpl> InternalPyClassObjectLayout<T> for PyStaticClassObject<T> {
453481
unsafe impl<T: PyClassImpl> PyLayout<T> for PyStaticClassObject<T> {}
454482
impl<T: PyClass> PySizedLayout<T> for PyStaticClassObject<T> {}
455483

456-
impl<T: PyClassImpl> PyClassObjectLayout<T> for PyStaticClassObject<T>
484+
impl<T: PyClassImpl> PyClassObjectBaseLayout<T> for PyStaticClassObject<T>
457485
where
458-
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectLayout<T::BaseType>,
486+
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
459487
{
460488
fn ensure_threadsafe(&self) {
461489
self.contents.thread_checker.ensure();
@@ -470,7 +498,7 @@ where
470498
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
471499
// Safety: Python only calls tp_dealloc when no references to the object remain.
472500
let class_object = &mut *(slf.cast::<T::Layout>());
473-
class_object.contents_mut().dealloc(py, slf);
501+
class_object.contents_mut().0.dealloc(py, slf);
474502
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf)
475503
}
476504
}
@@ -482,41 +510,41 @@ pub struct PyVariableClassObject<T: PyClassImpl> {
482510

483511
impl<T: PyClassImpl> PyVariableClassObject<T> {
484512
#[cfg(Py_3_12)]
485-
fn get_contents_of_obj(obj: *mut ffi::PyObject) -> *mut PyClassObjectContents<T> {
513+
fn get_contents_of_obj(obj: *mut ffi::PyObject) -> *mut WrappedPyClassObjectContents<T> {
486514
// https://peps.python.org/pep-0697/
487515
let type_obj = unsafe { ffi::Py_TYPE(obj) };
488516
let pointer = unsafe { ffi::PyObject_GetTypeData(obj, type_obj) };
489-
pointer as *mut PyClassObjectContents<T>
517+
pointer as *mut WrappedPyClassObjectContents<T>
490518
}
491519

492520
#[cfg(Py_3_12)]
493-
fn get_contents_ptr(&self) -> *mut PyClassObjectContents<T> {
521+
fn get_contents_ptr(&self) -> *mut WrappedPyClassObjectContents<T> {
494522
Self::get_contents_of_obj(self as *const PyVariableClassObject<T> as *mut ffi::PyObject)
495523
}
496524
}
497525

498526
#[cfg(Py_3_12)]
499-
impl<T: PyClassImpl> InternalPyClassObjectLayout<T> for PyVariableClassObject<T> {
527+
impl<T: PyClassImpl> PyClassObjectLayout<T> for PyVariableClassObject<T> {
500528
unsafe fn contents_uninitialised(
501529
obj: *mut ffi::PyObject,
502-
) -> *mut MaybeUninit<PyClassObjectContents<T>> {
503-
Self::get_contents_of_obj(obj) as *mut MaybeUninit<PyClassObjectContents<T>>
530+
) -> *mut MaybeUninit<WrappedPyClassObjectContents<T>> {
531+
Self::get_contents_of_obj(obj) as *mut MaybeUninit<WrappedPyClassObjectContents<T>>
504532
}
505533

506534
fn get_ptr(&self) -> *mut T {
507-
self.contents().value.get()
535+
self.contents().0.value.get()
508536
}
509537

510538
fn ob_base(&self) -> &<T::BaseType as PyClassBaseType>::LayoutAsBase {
511539
&self.ob_base
512540
}
513541

514-
fn contents(&self) -> &PyClassObjectContents<T> {
515-
unsafe { (self.get_contents_ptr() as *const PyClassObjectContents<T>).as_ref() }
542+
fn contents(&self) -> &WrappedPyClassObjectContents<T> {
543+
unsafe { self.get_contents_ptr().cast_const().as_ref() }
516544
.expect("should be able to cast PyClassObjectContents pointer")
517545
}
518546

519-
fn contents_mut(&mut self) -> &mut PyClassObjectContents<T> {
547+
fn contents_mut(&mut self) -> &mut WrappedPyClassObjectContents<T> {
520548
unsafe { self.get_contents_ptr().as_mut() }
521549
.expect("should be able to cast PyClassObjectContents pointer")
522550
}
@@ -560,24 +588,24 @@ impl<T: PyClassImpl> InternalPyClassObjectLayout<T> for PyVariableClassObject<T>
560588
unsafe impl<T: PyClassImpl> PyLayout<T> for PyVariableClassObject<T> {}
561589

562590
#[cfg(Py_3_12)]
563-
impl<T: PyClassImpl> PyClassObjectLayout<T> for PyVariableClassObject<T>
591+
impl<T: PyClassImpl> PyClassObjectBaseLayout<T> for PyVariableClassObject<T>
564592
where
565-
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectLayout<T::BaseType>,
593+
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyClassObjectBaseLayout<T::BaseType>,
566594
{
567595
fn ensure_threadsafe(&self) {
568-
self.contents().thread_checker.ensure();
596+
self.contents().0.thread_checker.ensure();
569597
self.ob_base.ensure_threadsafe();
570598
}
571599
fn check_threadsafe(&self) -> Result<(), PyBorrowError> {
572-
if !self.contents().thread_checker.check() {
600+
if !self.contents().0.thread_checker.check() {
573601
return Err(PyBorrowError { _private: () });
574602
}
575603
self.ob_base.check_threadsafe()
576604
}
577605
unsafe fn tp_dealloc(py: Python<'_>, slf: *mut ffi::PyObject) {
578606
// Safety: Python only calls tp_dealloc when no references to the object remain.
579607
let class_object = &mut *(slf.cast::<T::Layout>());
580-
class_object.contents_mut().dealloc(py, slf);
608+
class_object.contents_mut().0.dealloc(py, slf);
581609
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(py, slf)
582610
}
583611
}

src/pyclass/create_type_object.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
trampoline::trampoline,
1212
},
1313
internal_tricks::ptr_from_ref,
14-
pycell::impl_::InternalPyClassObjectLayout,
14+
pycell::impl_::PyClassObjectLayout,
1515
types::{typeobject::PyTypeMethods, PyType},
1616
Py, PyClass, PyResult, PyTypeInfo, Python,
1717
};

0 commit comments

Comments
 (0)