Skip to content

Commit b850836

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

File tree

4 files changed

+71
-13
lines changed

4 files changed

+71
-13
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 & 8 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};
@@ -113,25 +114,74 @@ pub(crate) struct PyCellContents<T: PyClass> {
113114
impl<T: PyClass> PyCell<T> {
114115
/// Get the offset of the dictionary from the start of the struct in bytes.
115116
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
116-
pub(crate) fn dict_offset() -> Option<usize> {
117+
pub(crate) fn dict_offset() -> Option<ffi::Py_ssize_t> {
117118
if T::Dict::IS_DUMMY {
118119
None
119120
} else {
120-
Some(
121-
std::mem::size_of::<Self>()
122-
- std::mem::size_of::<T::Dict>()
123-
- std::mem::size_of::<T::WeakRef>(),
124-
)
121+
#[cfg(addr_of)]
122+
let offset = {
123+
// With std::ptr::addr_of - can measure offset using uninit memory without UB.
124+
let cell = std::mem::MaybeUninit::<Self>::uninit();
125+
let base_ptr = cell.as_ptr();
126+
let contents_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents) };
127+
let dict_ptr = unsafe { std::ptr::addr_of!((*contents_ptr).dict) };
128+
unsafe { (dict_ptr as *const u8).offset_from(base_ptr as *const u8) }
129+
};
130+
#[cfg(not(addr_of))]
131+
let offset = {
132+
// With std::ptr::addr_of - need to take references to PyCell to measure offsets;
133+
// make a zero-initialised "fake" one so that referencing it is not UB.
134+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
135+
unsafe {
136+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
137+
}
138+
let cell = unsafe { cell.assume_init() };
139+
let dict_ptr = &cell.contents.dict;
140+
let offset = unsafe {
141+
(dict_ptr as *const _ as *const u8).offset_from(&cell as *const _ as *const u8)
142+
};
143+
// This isn't a valid cell, so ensure no Drop code runs etc.
144+
std::mem::forget(cell);
145+
offset
146+
};
147+
Some(offset.try_into().expect("offset should fit in Py_ssize_t"))
125148
}
126149
}
127150

128151
/// Get the offset of the weakref list from the start of the struct in bytes.
129152
#[cfg(not(all(Py_LIMITED_API, not(Py_3_9))))]
130-
pub(crate) fn weakref_offset() -> Option<usize> {
153+
pub(crate) fn weakref_offset() -> Option<ffi::Py_ssize_t> {
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 contents_ptr = unsafe { std::ptr::addr_of!((*base_ptr).contents) };
163+
let weaklist_ptr = unsafe { std::ptr::addr_of!((*contents_ptr).weakref) };
164+
unsafe { (weaklist_ptr as *const u8).offset_from(base_ptr as *const u8) }
165+
};
166+
#[cfg(not(addr_of))]
167+
let offset = {
168+
// With std::ptr::addr_of - need to take references to PyCell to measure offsets;
169+
// make a zero-initialised "fake" one so that referencing it is not UB.
170+
let mut cell = std::mem::MaybeUninit::<Self>::uninit();
171+
unsafe {
172+
std::ptr::write_bytes(cell.as_mut_ptr(), 0, 1);
173+
}
174+
let cell = unsafe { cell.assume_init() };
175+
let weaklist_ptr = &cell.contents.weakref;
176+
let offset = unsafe {
177+
(weaklist_ptr as *const _ as *const u8)
178+
.offset_from(&cell as *const _ as *const u8)
179+
};
180+
// This isn't a valid cell, so ensure no Drop code runs etc.
181+
std::mem::forget(cell);
182+
offset
183+
};
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)