Skip to content

Commit 4c5aee9

Browse files
committed
pycell: fix calculation of dictoffset on 32-bit Windows
1 parent 16e6f78 commit 4c5aee9

File tree

4 files changed

+73
-15
lines changed

4 files changed

+73
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2121
- Fix regression in 0.14.0 rejecting usage of `#[doc(hidden)]` on structs and functions annotated with PyO3 macros. [#1722](https://github.com/PyO3/pyo3/pull/1722)
2222
- Fix regression in 0.14.0 leading to incorrect code coverage being computed for `#[pyfunction]`s. [#1726](https://github.com/PyO3/pyo3/pull/1726)
2323
- Fix incorrect FFI definition of `Py_Buffer` on PyPy. [#1737](https://github.com/PyO3/pyo3/pull/1737)
24+
- Fix incorrect calculation of `dictoffset` on 32-bit Windows. [#1475](https://github.com/PyO3/pyo3/pull/1475)
2425

2526
## [0.14.1] - 2021-07-04
2627

build.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,18 @@ fn configure_pyo3() -> Result<()> {
192192
)?;
193193
interpreter_config.emit_pyo3_cfgs();
194194

195+
let rustc_minor_version = rustc_minor_version().unwrap_or(0);
196+
195197
// Enable use of const generics on Rust 1.51 and greater
196-
if rustc_minor_version().unwrap_or(0) >= 51 {
198+
if rustc_minor_version >= 51 {
197199
println!("cargo:rustc-cfg=min_const_generics");
198200
}
199201

202+
// Enable use of std::ptr::addr_of! on Rust 1.51 and greater
203+
if rustc_minor_version >= 51 {
204+
println!("cargo:rustc-cfg=addr_of");
205+
}
206+
200207
Ok(())
201208
}
202209

src/pycell.rs

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,34 +104,84 @@ pub struct PyCell<T: PyClass> {
104104
pub(crate) struct PyCellContents<T: PyClass> {
105105
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
106106
pub(crate) thread_checker: T::ThreadChecker,
107-
// DO NOT CHANGE THE ORDER OF THESE FIELDS WITHOUT CHANGING PyCell::dict_offset()
108-
// AND PyCell::weakref_offset()
109107
pub(crate) dict: T::Dict,
110108
pub(crate) weakref: T::WeakRef,
111109
}
112110

113111
impl<T: PyClass> PyCell<T> {
114112
/// Get the offset of the dictionary from the start of the struct in bytes.
115113
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
116-
pub(crate) fn dict_offset() -> Option<usize> {
114+
pub(crate) fn dict_offset() -> Option<ffi::Py_ssize_t> {
115+
use std::convert::TryInto;
117116
if T::Dict::IS_DUMMY {
118117
None
119118
} else {
120-
Some(
121-
std::mem::size_of::<Self>()
122-
- std::mem::size_of::<T::Dict>()
123-
- std::mem::size_of::<T::WeakRef>(),
124-
)
119+
#[cfg(addr_of)]
120+
let offset = {
121+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
122+
let cell = std::mem::MaybeUninit::<Self>::uninit();
123+
let base_ptr = cell.as_ptr();
124+
let dict_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.dict) };
125+
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
126+
};
127+
#[cfg(not(addr_of))]
128+
let offset = {
129+
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
130+
// make a zero-initialised "fake" one so that referencing it is not UB.
131+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
132+
unsafe {
133+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
134+
}
135+
let cell = unsafe { cell.assume_init() };
136+
let dict_ptr = &cell.contents.dict;
137+
// offset_from wasn't stabilised until 1.47, so we also have to work around
138+
// that...
139+
let offset = (dict_ptr as *const _ as usize) - (&cell as *const _ as usize);
140+
// This isn't a valid cell, so ensure no Drop code runs etc.
141+
std::mem::forget(cell);
142+
offset
143+
};
144+
// Py_ssize_t may not be equal to isize on all platforms
145+
#[allow(clippy::useless_conversion)]
146+
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
125147
}
126148
}
127149

128150
/// Get the offset of the weakref list from the start of the struct in bytes.
129151
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
130-
pub(crate) fn weakref_offset() -> Option<usize> {
152+
pub(crate) fn weakref_offset() -> Option<ffi::Py_ssize_t> {
153+
use std::convert::TryInto;
131154
if T::WeakRef::IS_DUMMY {
132155
None
133156
} else {
134-
Some(std::mem::size_of::<Self>() - std::mem::size_of::<T::WeakRef>())
157+
#[cfg(addr_of)]
158+
let offset = {
159+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
160+
let cell = std::mem::MaybeUninit::<Self>::uninit();
161+
let base_ptr = cell.as_ptr();
162+
let weaklist_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents.weakref) };
163+
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
164+
};
165+
#[cfg(not(addr_of))]
166+
let offset = {
167+
// No std::ptr::addr_of - need to take references to PyCell to measure offsets;
168+
// make a zero-initialised "fake" one so that referencing it is not UB.
169+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
170+
unsafe {
171+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
172+
}
173+
let cell = unsafe { cell.assume_init() };
174+
let weaklist_ptr = &cell.contents.weakref;
175+
// offset_from wasn't stabilised until 1.47, so we also have to work around
176+
// that...
177+
let offset = (weaklist_ptr as *const _ as usize) - (&cell as *const _ as usize);
178+
// This isn't a valid cell, so ensure no Drop code runs etc.
179+
std::mem::forget(cell);
180+
offset
181+
};
182+
// Py_ssize_t may not be equal to isize on all platforms
183+
#[allow(clippy::useless_conversion)]
184+
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
135185
}
136186
}
137187
}

src/pyclass.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ fn tp_init_additional<T: PyClass>(type_object: *mut ffi::PyTypeObject) {
174174
// __dict__ support
175175
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
176176
unsafe {
177-
(*type_object).tp_dictoffset = dict_offset as ffi::Py_ssize_t;
177+
(*type_object).tp_dictoffset = dict_offset;
178178
}
179179
}
180180
// weakref support
181181
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
182182
unsafe {
183-
(*type_object).tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
183+
(*type_object).tp_weaklistoffset = weakref_offset;
184184
}
185185
}
186186
}
@@ -233,11 +233,11 @@ fn py_class_method_defs(
233233
#[cfg(Py_3_9)]
234234
fn py_class_members<T: PyClass>() -> Vec<ffi::structmember::PyMemberDef> {
235235
#[inline(always)]
236-
fn offset_def(name: &'static str, offset: usize) -> ffi::structmember::PyMemberDef {
236+
fn offset_def(name: &'static str, offset: ffi::Py_ssize_t) -> ffi::structmember::PyMemberDef {
237237
ffi::structmember::PyMemberDef {
238238
name: name.as_ptr() as _,
239239
type_code: ffi::structmember::T_PYSSIZET,
240-
offset: offset as _,
240+
offset,
241241
flags: ffi::structmember::READONLY,
242242
doc: std::ptr::null_mut(),
243243
}

0 commit comments

Comments
 (0)