Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ jobs:
python-version: '3.14t'
cmake-args: -DCMAKE_CXX_STANDARD=17 -DPYBIND11_TEST_SMART_HOLDER=ON
- runs-on: ubuntu-latest
python-version: 'pypy-3.11-v7.3.23'
python-version: 'pypy-3.11-v8.0.0'
cmake-args: -DCMAKE_CXX_STANDARD=17
- runs-on: ubuntu-latest
python-version: 'pypy-3.12-v8.0.0'
cmake-args: -DCMAKE_CXX_STANDARD=20
- runs-on: ubuntu-latest
python-version: 'graalpy-25.0'
cmake-args: -DCMAKE_CXX_STANDARD=20
Expand Down Expand Up @@ -138,7 +141,7 @@ jobs:
python-version: '3.15'
cmake-args: -DCMAKE_CXX_STANDARD=17
- runs-on: macos-latest
python-version: 'pypy-3.11-v7.3.23'
python-version: 'pypy-3.12-v8.0.0'
- runs-on: macos-latest
python-version: 'graalpy-25.0'
- runs-on: macos-latest
Expand Down Expand Up @@ -172,6 +175,9 @@ jobs:
- runs-on: windows-latest
python-version: 'pypy3.11'
cmake-args: -DCMAKE_CXX_STANDARD=20
- runs-on: windows-latest
python-version: 'pypy3.12'
cmake-args: -DCMAKE_CXX_STANDARD=17
# The setup-python action currently doesn't have graalpy for windows
# See https://github.com/actions/setup-python/pull/880

Expand Down
17 changes: 14 additions & 3 deletions include/pybind11/detail/class-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,17 @@ PYBIND11_INLINE void clear_patients(PyObject *self) {
}
}

PYBIND11_INLINE PyObject **instance_dict_ptr(PyObject *self) {
#if defined(PYPY_VERSION)
// PyPy 3.12's _PyObject_GetDictPtr() aborts if called from tp_dealloc.
auto offset = Py_TYPE(self)->tp_dictoffset;
return offset > 0 ? reinterpret_cast<PyObject **>(reinterpret_cast<char *>(self) + offset)
: nullptr;
#else
return _PyObject_GetDictPtr(self);
#endif
}

PYBIND11_INLINE void clear_instance(PyObject *self) {
auto *instance = reinterpret_cast<detail::instance *>(self);

Expand Down Expand Up @@ -436,7 +447,7 @@ PYBIND11_INLINE void clear_instance(PyObject *self) {
PyObject_ClearWeakRefs(self);
}

PyObject **dict_ptr = _PyObject_GetDictPtr(self);
PyObject **dict_ptr = instance_dict_ptr(self);
if (dict_ptr) {
Py_CLEAR(*dict_ptr);
}
Expand Down Expand Up @@ -525,7 +536,7 @@ extern "C" PYBIND11_INLINE int pybind11_traverse(PyObject *self, visitproc visit
return ret;
}
#else
PyObject *&dict = *_PyObject_GetDictPtr(self);
PyObject *&dict = *instance_dict_ptr(self);
Py_VISIT(dict);
#endif
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
Expand All @@ -537,7 +548,7 @@ extern "C" PYBIND11_INLINE int pybind11_clear(PyObject *self) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject_ClearManagedDict(self);
#else
PyObject *&dict = *_PyObject_GetDictPtr(self);
PyObject *&dict = *instance_dict_ptr(self);
Py_CLEAR(dict);
#endif
return 0;
Expand Down
3 changes: 3 additions & 0 deletions include/pybind11/detail/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ void add_patient(PyObject *nurse, PyObject *patient);

void clear_patients(PyObject *self);

/// Returns the address of the instance `__dict__` slot, or nullptr if there is none.
PyObject **instance_dict_ptr(PyObject *self);

/// Clears all internal data from the instance and removes it from registered instances in
/// preparation for deallocation.
void clear_instance(PyObject *self);
Expand Down
13 changes: 13 additions & 0 deletions include/pybind11/pytypes-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,21 @@ PYBIND11_INLINE memoryview memoryview::from_buffer(void *ptr,
view.itemsize = itemsize;
view.format = const_cast<char *>(format);
view.ndim = static_cast<int>(ndim);
#if defined(PYPY_VERSION)
// PyPy (at least up to 8.0.0) only copies shape and strides that point to its inline arrays.
if (ndim > PyBUF_MAX_NDIM) {
pybind11_fail("memoryview: too many dimensions");
}
for (size_t i = 0; i < ndim; ++i) {
view._shape[i] = (*shape)[i];
view._strides[i] = (*strides)[i];
}
view.shape = view._shape;
view.strides = view._strides;
#else
view.shape = shape->data();
view.strides = strides->data();
#endif
view.suboffsets = nullptr;
view.internal = nullptr;
PyObject *obj = PyMemoryView_FromBuffer(&view);
Expand Down
2 changes: 2 additions & 0 deletions tests/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,8 @@ class PyNonFinalFinalChild(m.IsNonFinalFinal):
def test_exception_rvalue_abort():
with pytest.raises(RuntimeError):
m.PyPrintDestructor().throw_something()
# The destructor calls Python; on PyPy, a later GC at an arbitrary point can abort
pytest.gc_collect()


# https://github.com/pybind/pybind11/issues/1568
Expand Down
6 changes: 5 additions & 1 deletion tests/test_multiple_inheritance.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,13 @@ def test_mi_static_properties():
def test_mi_dynamic_attributes():
"""Mixing bases with and without dynamic attribute support"""

for d in (m.VanillaDictMix1(), m.VanillaDictMix2()):
for cls in (m.VanillaDictMix1, m.VanillaDictMix2):
d = cls()
d.dynamic = 1
assert d.dynamic == 1
del d
# The dict must be cleared without crashing (PyPy 3.12)
pytest.gc_collect()


def test_mi_unaligned_base():
Expand Down
Loading