Skip to content

Commit e57dd47

Browse files
Fix various minor memory leaks in the tests (found by Valgrind in #2746) (#2758)
* Fix leak in the test_copy_move::test_move_fallback * Fix leaking PyMethodDef in test_class::test_implicit_conversion_life_support * Plumb leak in test_buffer, occuring when a mutable buffer is requested for a read-only object, and enable test_buffer.py * Fix weird return_value_policy::reference in test_stl_binders, and enable those tests * Cleanup nodelete holder objects in test_smart_ptr, and enable those tests
1 parent e612043 commit e57dd47

File tree

8 files changed

+68
-26
lines changed

8 files changed

+68
-26
lines changed

include/pybind11/detail/class.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,12 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
550550
}
551551
std::memset(view, 0, sizeof(Py_buffer));
552552
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
553+
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
554+
delete info;
555+
// view->obj = nullptr; // Was just memset to 0, so not necessary
556+
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage");
557+
return -1;
558+
}
553559
view->obj = obj;
554560
view->ndim = 1;
555561
view->internal = info;
@@ -559,12 +565,6 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
559565
for (auto s : info->shape)
560566
view->len *= s;
561567
view->readonly = info->readonly;
562-
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
563-
if (view)
564-
view->obj = nullptr;
565-
PyErr_SetString(PyExc_BufferError, "Writable buffer requested for readonly storage");
566-
return -1;
567-
}
568568
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
569569
view->format = const_cast<char *>(info->format.c_str());
570570
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {

tests/test_buffers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ def test_readonly_buffer():
9292
view = memoryview(buf)
9393
assert view[0] == b"d" if env.PY2 else 0x64
9494
assert view.readonly
95+
with pytest.raises(TypeError):
96+
view[0] = b"\0" if env.PY2 else 0
9597

9698

9799
def test_selective_readonly_buffer():

tests/test_class.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ TEST_SUBMODULE(class_, m) {
231231
};
232232

233233
auto def = new PyMethodDef{"f", f, METH_VARARGS, nullptr};
234-
return py::reinterpret_steal<py::object>(PyCFunction_NewEx(def, nullptr, m.ptr()));
234+
py::capsule def_capsule(def, [](void *ptr) { delete reinterpret_cast<PyMethodDef *>(ptr); });
235+
return py::reinterpret_steal<py::object>(PyCFunction_NewEx(def, def_capsule.ptr(), m.ptr()));
235236
}());
236237

237238
// test_operator_new_delete

tests/test_copy_move.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ TEST_SUBMODULE(copy_move_policies, m) {
214214
};
215215
py::class_<MoveIssue2>(m, "MoveIssue2").def(py::init<int>()).def_readwrite("value", &MoveIssue2::v);
216216

217-
m.def("get_moveissue1", [](int i) { return new MoveIssue1(i); }, py::return_value_policy::move);
217+
// #2742: Don't expect ownership of raw pointer to `new`ed object to be transferred with `py::return_value_policy::move`
218+
m.def("get_moveissue1", [](int i) { return std::unique_ptr<MoveIssue1>(new MoveIssue1(i)); }, py::return_value_policy::move);
218219
m.def("get_moveissue2", [](int i) { return MoveIssue2(i); }, py::return_value_policy::move);
219220
}

tests/test_copy_move.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def test_private_op_new():
119119
def test_move_fallback():
120120
"""#389: rvp::move should fall-through to copy on non-movable objects"""
121121

122-
m2 = m.get_moveissue2(2)
123-
assert m2.value == 2
124122
m1 = m.get_moveissue1(1)
125123
assert m1.value == 1
124+
m2 = m.get_moveissue2(2)
125+
assert m2.value == 2

tests/test_smart_ptr.cpp

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,33 +176,63 @@ TEST_SUBMODULE(smart_ptr, m) {
176176

177177
// test_unique_nodelete
178178
// Object with a private destructor
179+
class MyObject4;
180+
static std::unordered_set<MyObject4 *> myobject4_instances;
179181
class MyObject4 {
180182
public:
181-
MyObject4(int value) : value{value} { print_created(this); }
183+
MyObject4(int value) : value{value} {
184+
print_created(this);
185+
myobject4_instances.insert(this);
186+
}
182187
int value;
188+
189+
static void cleanupAllInstances() {
190+
auto tmp = std::move(myobject4_instances);
191+
myobject4_instances.clear();
192+
for (auto o : tmp)
193+
delete o;
194+
}
183195
private:
184-
~MyObject4() { print_destroyed(this); }
196+
~MyObject4() {
197+
myobject4_instances.erase(this);
198+
print_destroyed(this);
199+
}
185200
};
186201
py::class_<MyObject4, std::unique_ptr<MyObject4, py::nodelete>>(m, "MyObject4")
187202
.def(py::init<int>())
188-
.def_readwrite("value", &MyObject4::value);
203+
.def_readwrite("value", &MyObject4::value)
204+
.def_static("cleanup_all_instances", &MyObject4::cleanupAllInstances);
189205

190206
// test_unique_deleter
191207
// Object with std::unique_ptr<T, D> where D is not matching the base class
192208
// Object with a protected destructor
209+
class MyObject4a;
210+
static std::unordered_set<MyObject4a *> myobject4a_instances;
193211
class MyObject4a {
194212
public:
195213
MyObject4a(int i) {
196214
value = i;
197215
print_created(this);
216+
myobject4a_instances.insert(this);
198217
};
199218
int value;
219+
220+
static void cleanupAllInstances() {
221+
auto tmp = std::move(myobject4a_instances);
222+
myobject4a_instances.clear();
223+
for (auto o : tmp)
224+
delete o;
225+
}
200226
protected:
201-
virtual ~MyObject4a() { print_destroyed(this); }
227+
virtual ~MyObject4a() {
228+
myobject4a_instances.erase(this);
229+
print_destroyed(this);
230+
}
202231
};
203232
py::class_<MyObject4a, std::unique_ptr<MyObject4a, py::nodelete>>(m, "MyObject4a")
204233
.def(py::init<int>())
205-
.def_readwrite("value", &MyObject4a::value);
234+
.def_readwrite("value", &MyObject4a::value)
235+
.def_static("cleanup_all_instances", &MyObject4a::cleanupAllInstances);
206236

207237
// Object derived but with public destructor and no Deleter in default holder
208238
class MyObject4b : public MyObject4a {

tests/test_smart_ptr.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ def test_unique_nodelete():
125125
cstats = ConstructorStats.get(m.MyObject4)
126126
assert cstats.alive() == 1
127127
del o
128-
assert cstats.alive() == 1 # Leak, but that's intentional
128+
assert cstats.alive() == 1
129+
m.MyObject4.cleanup_all_instances()
130+
assert cstats.alive() == 0
129131

130132

131133
def test_unique_nodelete4a():
@@ -134,19 +136,25 @@ def test_unique_nodelete4a():
134136
cstats = ConstructorStats.get(m.MyObject4a)
135137
assert cstats.alive() == 1
136138
del o
137-
assert cstats.alive() == 1 # Leak, but that's intentional
139+
assert cstats.alive() == 1
140+
m.MyObject4a.cleanup_all_instances()
141+
assert cstats.alive() == 0
138142

139143

140144
def test_unique_deleter():
145+
m.MyObject4a(0)
141146
o = m.MyObject4b(23)
142147
assert o.value == 23
143148
cstats4a = ConstructorStats.get(m.MyObject4a)
144-
assert cstats4a.alive() == 2 # Two because of previous test
149+
assert cstats4a.alive() == 2
145150
cstats4b = ConstructorStats.get(m.MyObject4b)
146151
assert cstats4b.alive() == 1
147152
del o
148-
assert cstats4a.alive() == 1 # Should now only be one leftover from previous test
153+
assert cstats4a.alive() == 1 # Should now only be one leftover
149154
assert cstats4b.alive() == 0 # Should be deleted
155+
m.MyObject4a.cleanup_all_instances()
156+
assert cstats4a.alive() == 0
157+
assert cstats4b.alive() == 0
150158

151159

152160
def test_large_holder():

tests/test_stl_binders.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ TEST_SUBMODULE(stl_binders, m) {
8686

8787
// test_noncopyable_containers
8888
py::bind_vector<std::vector<E_nc>>(m, "VectorENC");
89-
m.def("get_vnc", &one_to_n<std::vector<E_nc>>, py::return_value_policy::reference);
89+
m.def("get_vnc", &one_to_n<std::vector<E_nc>>);
9090
py::bind_vector<std::deque<E_nc>>(m, "DequeENC");
91-
m.def("get_dnc", &one_to_n<std::deque<E_nc>>, py::return_value_policy::reference);
91+
m.def("get_dnc", &one_to_n<std::deque<E_nc>>);
9292
py::bind_map<std::map<int, E_nc>>(m, "MapENC");
93-
m.def("get_mnc", &times_ten<std::map<int, E_nc>>, py::return_value_policy::reference);
93+
m.def("get_mnc", &times_ten<std::map<int, E_nc>>);
9494
py::bind_map<std::unordered_map<int, E_nc>>(m, "UmapENC");
95-
m.def("get_umnc", &times_ten<std::unordered_map<int, E_nc>>, py::return_value_policy::reference);
95+
m.def("get_umnc", &times_ten<std::unordered_map<int, E_nc>>);
9696
// Issue #1885: binding nested std::map<X, Container<E>> with E non-copyable
9797
py::bind_map<std::map<int, std::vector<E_nc>>>(m, "MapVecENC");
9898
m.def("get_nvnc", [](int n)
@@ -102,11 +102,11 @@ TEST_SUBMODULE(stl_binders, m) {
102102
for (int j = 1; j <= n; j++)
103103
(*m)[i].emplace_back(j);
104104
return m;
105-
}, py::return_value_policy::reference);
105+
});
106106
py::bind_map<std::map<int, std::map<int, E_nc>>>(m, "MapMapENC");
107-
m.def("get_nmnc", &times_hundred<std::map<int, std::map<int, E_nc>>>, py::return_value_policy::reference);
107+
m.def("get_nmnc", &times_hundred<std::map<int, std::map<int, E_nc>>>);
108108
py::bind_map<std::unordered_map<int, std::unordered_map<int, E_nc>>>(m, "UmapUmapENC");
109-
m.def("get_numnc", &times_hundred<std::unordered_map<int, std::unordered_map<int, E_nc>>>, py::return_value_policy::reference);
109+
m.def("get_numnc", &times_hundred<std::unordered_map<int, std::unordered_map<int, E_nc>>>);
110110

111111
// test_vector_buffer
112112
py::bind_vector<std::vector<unsigned char>>(m, "VectorUChar", py::buffer_protocol());

0 commit comments

Comments
 (0)