Skip to content

Commit 56c1edb

Browse files
jagermanwjakob
authored andcommitted
Don't add duplicate patients
This fixes #1251 (patient vector grows without bounds) for the 2.2.2 branch by checking that the vector doesn't already have the given patient. This is a little less elegant than the same fix for `master` (which changes the patients `vector` to an `unordered_set`), but that requires an internals layout change, which this approach avoids.
1 parent 20d6d1d commit 56c1edb

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

include/pybind11/detail/class.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,13 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject
289289
inline void add_patient(PyObject *nurse, PyObject *patient) {
290290
auto &internals = get_internals();
291291
auto instance = reinterpret_cast<detail::instance *>(nurse);
292+
auto &current_patients = internals.patients[nurse];
292293
instance->has_patients = true;
294+
for (auto &p : current_patients)
295+
if (p == patient)
296+
return;
293297
Py_INCREF(patient);
294-
internals.patients[nurse].push_back(patient);
298+
current_patients.push_back(patient);
295299
}
296300

297301
inline void clear_patients(PyObject *self) {

tests/test_call_policies.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
#include "pybind11_tests.h"
11+
#include "constructor_stats.h"
1112

1213
struct CustomGuard {
1314
static bool enabled;
@@ -59,6 +60,21 @@ TEST_SUBMODULE(call_policies, m) {
5960
.def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
6061
.def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>());
6162

63+
// test_keep_alive_single
64+
m.def("add_patient", [](py::object /*nurse*/, py::object /*patient*/) { }, py::keep_alive<1, 2>());
65+
m.def("get_patients", [](py::object nurse) {
66+
py::list patients;
67+
for (PyObject *p : pybind11::detail::get_internals().patients[nurse.ptr()])
68+
patients.append(py::reinterpret_borrow<py::object>(p));
69+
return patients;
70+
});
71+
m.def("refcount", [](py::handle h) {
72+
#ifdef PYPY_VERSION
73+
ConstructorStats::gc(); // PyPy doesn't update ref counts until GC occurs
74+
#endif
75+
return h.ref_count();
76+
});
77+
6278
#if !defined(PYPY_VERSION)
6379
// test_alive_gc
6480
class ParentGC : public Parent {

tests/test_call_policies.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
from pybind11_tests import call_policies as m
3-
from pybind11_tests import ConstructorStats
3+
from pybind11_tests import ConstructorStats, UserType
44

55

66
def test_keep_alive_argument(capture):
@@ -69,6 +69,35 @@ def test_keep_alive_return_value(capture):
6969
"""
7070

7171

72+
def test_keep_alive_single():
73+
"""Issue #1251 - patients are stored multiple times when given to the same nurse"""
74+
75+
nurse, p1, p2 = UserType(), UserType(), UserType()
76+
b = m.refcount(nurse)
77+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b, b]
78+
m.add_patient(nurse, p1)
79+
assert m.get_patients(nurse) == [p1, ]
80+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b]
81+
m.add_patient(nurse, p1)
82+
assert m.get_patients(nurse) == [p1, ]
83+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b]
84+
m.add_patient(nurse, p1)
85+
assert m.get_patients(nurse) == [p1, ]
86+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b]
87+
m.add_patient(nurse, p2)
88+
assert m.get_patients(nurse) == [p1, p2]
89+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1]
90+
m.add_patient(nurse, p2)
91+
assert m.get_patients(nurse) == [p1, p2]
92+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1]
93+
m.add_patient(nurse, p2)
94+
m.add_patient(nurse, p1)
95+
assert m.get_patients(nurse) == [p1, p2]
96+
assert [m.refcount(nurse), m.refcount(p1), m.refcount(p2)] == [b, b + 1, b + 1]
97+
del nurse
98+
assert [m.refcount(p1), m.refcount(p2)] == [b, b]
99+
100+
72101
# https://bitbucket.org/pypy/pypy/issues/2447
73102
@pytest.unsupported_on_pypy
74103
def test_alive_gc(capture):

0 commit comments

Comments
 (0)