Skip to content

Commit

Permalink
Merge pull request #1060 from sebpuetz/memoffset
Browse files Browse the repository at this point in the history
Calculate offsets for weakreflist and dict in PyCell.
  • Loading branch information
kngwyu committed Jul 21, 2020
2 parents 4cf0db0 + 4563e00 commit b05eb48
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed
- Conversion from types with an `__index__` method to Rust BigInts. [#1027](https://github.com/PyO3/pyo3/pull/1027)
- Fix segfault with #[pyclass(dict, unsendable)] [#1058](https://github.com/PyO3/pyo3/pull/1058)
- Don't rely on the order of structmembers to compute offsets in PyCell. Related to
[#1058](https://github.com/PyO3/pyo3/pull/1058). [#1059](https://github.com/PyO3/pyo3/pull/1059)

## [0.11.1] - 2020-06-30
### Added
Expand Down
24 changes: 23 additions & 1 deletion src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use crate::type_object::{PyBorrowFlagLayout, PyLayout, PySizedLayout, PyTypeInfo
use crate::types::PyAny;
use crate::{ffi, FromPy, PyErr, PyNativeType, PyObject, PyResult, Python};
use std::cell::{Cell, UnsafeCell};
use std::fmt;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::{fmt, mem};

/// Base layout of PyCell.
/// This is necessary for sharing BorrowFlag between parents and children.
Expand Down Expand Up @@ -162,10 +162,32 @@ impl<T: PyClass> PyCellInner<T> {
pub struct PyCell<T: PyClass> {
inner: PyCellInner<T>,
thread_checker: T::ThreadChecker,
// DO NOT CHANGE THE ORDER OF THESE FIELDS WITHOUT CHANGING PyCell::dict_offset()
// AND PyCell::weakref_offset()
dict: T::Dict,
weakref: T::WeakRef,
}

impl<T: PyClass> PyCell<T> {
/// Get the offset of the dictionary from the start of the struct in bytes.
pub(crate) fn dict_offset() -> Option<usize> {
if T::Dict::IS_DUMMY {
None
} else {
Some(mem::size_of::<Self>() - mem::size_of::<T::Dict>() - mem::size_of::<T::WeakRef>())
}
}

/// Get the offset of the weakref list from the start of the struct in bytes.
pub(crate) fn weakref_offset() -> Option<usize> {
if T::WeakRef::IS_DUMMY {
None
} else {
Some(mem::size_of::<Self>() - mem::size_of::<T::WeakRef>())
}
}
}

unsafe impl<T: PyClass> PyNativeType for PyCell<T> {}

impl<T: PyClass> PyCell<T> {
Expand Down
14 changes: 5 additions & 9 deletions src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,18 +137,14 @@ where
// type size
type_object.tp_basicsize = std::mem::size_of::<T::Layout>() as ffi::Py_ssize_t;

let mut offset = type_object.tp_basicsize;

// __dict__ support
if let Some(dict_offset) = T::Dict::OFFSET {
offset += dict_offset as ffi::Py_ssize_t;
type_object.tp_dictoffset = offset;
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t;
}

// weakref support
if let Some(weakref_offset) = T::WeakRef::OFFSET {
offset += weakref_offset as ffi::Py_ssize_t;
type_object.tp_weaklistoffset = offset;
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
}

// GC support
Expand Down Expand Up @@ -206,7 +202,7 @@ where
// properties
let mut props = py_class_properties::<T>();

if T::Dict::OFFSET.is_some() {
if !T::Dict::IS_DUMMY {
props.push(ffi::PyGetSetDef_DICT);
}
if !props.is_empty() {
Expand Down
10 changes: 4 additions & 6 deletions src/pyclass_slots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
//! Mainly used by our proc-macro codes.
use crate::{ffi, Python};

const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _;

/// Represents `__dict__` field for `#[pyclass]`.
pub trait PyClassDict {
const OFFSET: Option<isize> = None;
const IS_DUMMY: bool = true;
fn new() -> Self;
unsafe fn clear_dict(&mut self, _py: Python) {}
private_decl! {}
}

/// Represents `__weakref__` field for `#[pyclass]`.
pub trait PyClassWeakRef {
const OFFSET: Option<isize> = None;
const IS_DUMMY: bool = true;
fn new() -> Self;
unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python) {}
private_decl! {}
Expand Down Expand Up @@ -45,7 +43,7 @@ pub struct PyClassDictSlot(*mut ffi::PyObject);

impl PyClassDict for PyClassDictSlot {
private_impl! {}
const OFFSET: Option<isize> = Some(-POINTER_SIZE);
const IS_DUMMY: bool = false;
fn new() -> Self {
Self(std::ptr::null_mut())
}
Expand All @@ -64,7 +62,7 @@ pub struct PyClassWeakRefSlot(*mut ffi::PyObject);

impl PyClassWeakRef for PyClassWeakRefSlot {
private_impl! {}
const OFFSET: Option<isize> = Some(-POINTER_SIZE);
const IS_DUMMY: bool = false;
fn new() -> Self {
Self(std::ptr::null_mut())
}
Expand Down
24 changes: 24 additions & 0 deletions tests/test_unsendable_dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,27 @@ fn test_unsendable_dict() {
let inst = Py::new(py, UnsendableDictClass {}).unwrap();
py_run!(py, inst, "assert inst.__dict__ == {}");
}

#[pyclass(dict, unsendable, weakref)]
struct UnsendableDictClassWithWeakRef {}

#[pymethods]
impl UnsendableDictClassWithWeakRef {
#[new]
fn new() -> Self {
Self {}
}
}

#[test]
fn test_unsendable_dict_with_weakref() {
let gil = Python::acquire_gil();
let py = gil.python();
let inst = Py::new(py, UnsendableDictClassWithWeakRef {}).unwrap();
py_run!(py, inst, "assert inst.__dict__ == {}");
py_run!(
py,
inst,
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
);
}

0 comments on commit b05eb48

Please sign in to comment.