Skip to content

Commit 6b5eb1f

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

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-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: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
};
1515
use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python};
1616
use std::cell::{Cell, UnsafeCell};
17+
use std::convert::TryInto;
1718
use std::fmt;
1819
use std::mem::ManuallyDrop;
1920
use std::ops::{Deref, DerefMut};
@@ -104,34 +105,81 @@ pub struct PyCell<T: PyClass> {
104105
pub(crate) struct PyCellContents<T: PyClass> {
105106
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
106107
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()
109108
pub(crate) dict: T::Dict,
110109
pub(crate) weakref: T::WeakRef,
111110
}
112111

113112
impl<T: PyClass> PyCell<T> {
114113
/// Get the offset of the dictionary from the start of the struct in bytes.
115114
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
116-
pub(crate) fn dict_offset() -> Option<usize> {
115+
pub(crate) fn dict_offset() -> Option<ffi::Py_ssize_t> {
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 contents_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents) };
125+
let dict_ptr = unsafe { std::ptr::addr_of!((*contents_ptr).dict) };
126+
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
127+
};
128+
#[cfg(not(addr_of))]
129+
let offset = {
130+
// With std::ptr::addr_of - need to take references to PyCell to measure offsets;
131+
// make a zero-initialised "fake" one so that referencing it is not UB.
132+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
133+
unsafe {
134+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
135+
}
136+
let cell = unsafe { cell.assume_init() };
137+
let dict_ptr = &cell.contents.dict;
138+
let offset = unsafe {
139+
(dict_ptr as *const _ as *const u8).offset_from(&cell as *const _ as *const u8)
140+
};
141+
// This isn't a valid cell, so ensure no Drop code runs etc.
142+
std::mem::forget(cell);
143+
offset
144+
};
145+
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
125146
}
126147
}
127148

128149
/// Get the offset of the weakref list from the start of the struct in bytes.
129150
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
130-
pub(crate) fn weakref_offset() -> Option<usize> {
151+
pub(crate) fn weakref_offset() -> Option<ffi::Py_ssize_t> {
131152
if T::WeakRef::IS_DUMMY {
132153
None
133154
} else {
134-
Some(std::mem::size_of::<Self>() - std::mem::size_of::<T::WeakRef>())
155+
#[cfg(addr_of)]
156+
let offset = {
157+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
158+
let cell = std::mem::MaybeUninit::<Self>::uninit();
159+
let base_ptr = cell.as_ptr();
160+
let contents_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents) };
161+
let weaklist_ptr = unsafe { std::ptr::addr_of!((*contents_ptr).weakref) };
162+
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
163+
};
164+
#[cfg(not(addr_of))]
165+
let offset = {
166+
// With std::ptr::addr_of - need to take references to PyCell to measure offsets;
167+
// make a zero-initialised "fake" one so that referencing it is not UB.
168+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
169+
unsafe {
170+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
171+
}
172+
let cell = unsafe { cell.assume_init() };
173+
let weaklist_ptr = &cell.contents.weakref;
174+
let offset = unsafe {
175+
(weaklist_ptr as *const _ as *const u8)
176+
.offset_from(&cell as *const _ as *const u8)
177+
};
178+
// This isn't a valid cell, so ensure no Drop code runs etc.
179+
std::mem::forget(cell);
180+
offset
181+
};
182+
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
135183
}
136184
}
137185
}

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)