Skip to content

Commit 9f7b3f7

Browse files
addl unit tests for PR #3970 (#3977)
* Add test_perf_accessors (to be merged into test_pytypes). * Python < 3.8 f-string compatibility * Use thread_local in inc_ref_counter() * Intentional breakage, brute-force way to quickly find out how many platforms reach the PYBIND11_HANDLE_REF_DEBUG code, with and without threads. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove Intentional breakage * Drop perf test, move inc_refs tests to test_pytypes * Fold in PR #3970 with `#ifdef`s * Complete test coverage for all newly added code. * Condense new unit tests via a simple local helper macro. * Remove PYBIND11_PR3970 define. See #3977 (comment) * Move static keyword first (fixes silly oversight). Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b24c5ed commit 9f7b3f7

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

include/pybind11/pytypes.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ class object_api : public pyobject_tag {
180180

181181
PYBIND11_NAMESPACE_END(detail)
182182

183+
#if !defined(PYBIND11_HANDLE_REF_DEBUG) && !defined(NDEBUG)
184+
# define PYBIND11_HANDLE_REF_DEBUG
185+
#endif
186+
183187
/** \rst
184188
Holds a reference to a Python object (no reference counting)
185189
@@ -209,6 +213,9 @@ class handle : public detail::object_api<handle> {
209213
this function automatically. Returns a reference to itself.
210214
\endrst */
211215
const handle &inc_ref() const & {
216+
#ifdef PYBIND11_HANDLE_REF_DEBUG
217+
inc_ref_counter(1);
218+
#endif
212219
Py_XINCREF(m_ptr);
213220
return *this;
214221
}
@@ -244,6 +251,18 @@ class handle : public detail::object_api<handle> {
244251

245252
protected:
246253
PyObject *m_ptr = nullptr;
254+
255+
#ifdef PYBIND11_HANDLE_REF_DEBUG
256+
private:
257+
static std::size_t inc_ref_counter(std::size_t add) {
258+
thread_local std::size_t counter = 0;
259+
counter += add;
260+
return counter;
261+
}
262+
263+
public:
264+
static std::size_t inc_ref_counter() { return inc_ref_counter(0); }
265+
#endif
247266
};
248267

249268
/** \rst

tests/test_pytypes.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,55 @@ TEST_SUBMODULE(pytypes, m) {
297297
return d;
298298
});
299299

300+
m.def("accessor_moves", []() { // See PR #3970
301+
py::list return_list;
302+
#ifdef PYBIND11_HANDLE_REF_DEBUG
303+
py::int_ py_int_0(0);
304+
py::int_ py_int_42(42);
305+
py::str py_str_count("count");
306+
307+
auto tup = py::make_tuple(0);
308+
309+
py::sequence seq(tup);
310+
311+
py::list lst;
312+
lst.append(0);
313+
314+
# define PYBIND11_LOCAL_DEF(...) \
315+
{ \
316+
std::size_t inc_refs = py::handle::inc_ref_counter(); \
317+
__VA_ARGS__; \
318+
inc_refs = py::handle::inc_ref_counter() - inc_refs; \
319+
return_list.append(inc_refs); \
320+
}
321+
322+
PYBIND11_LOCAL_DEF(tup[py_int_0]) // l-value (to have a control)
323+
PYBIND11_LOCAL_DEF(tup[py::int_(0)]) // r-value
324+
325+
PYBIND11_LOCAL_DEF(tup.attr(py_str_count)) // l-value
326+
PYBIND11_LOCAL_DEF(tup.attr(py::str("count"))) // r-value
327+
328+
PYBIND11_LOCAL_DEF(seq[py_int_0]) // l-value
329+
PYBIND11_LOCAL_DEF(seq[py::int_(0)]) // r-value
330+
331+
PYBIND11_LOCAL_DEF(seq.attr(py_str_count)) // l-value
332+
PYBIND11_LOCAL_DEF(seq.attr(py::str("count"))) // r-value
333+
334+
PYBIND11_LOCAL_DEF(lst[py_int_0]) // l-value
335+
PYBIND11_LOCAL_DEF(lst[py::int_(0)]) // r-value
336+
337+
PYBIND11_LOCAL_DEF(lst.attr(py_str_count)) // l-value
338+
PYBIND11_LOCAL_DEF(lst.attr(py::str("count"))) // r-value
339+
340+
auto lst_acc = lst[py::int_(0)];
341+
lst_acc = py::int_(42); // Detaches lst_acc from lst.
342+
PYBIND11_LOCAL_DEF(lst_acc = py_int_42) // l-value
343+
PYBIND11_LOCAL_DEF(lst_acc = py::int_(42)) // r-value
344+
# undef PYBIND11_LOCAL_DEF
345+
#endif
346+
return return_list;
347+
});
348+
300349
// test_constructors
301350
m.def("default_constructors", []() {
302351
return py::dict("bytes"_a = py::bytes(),

tests/test_pytypes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,15 @@ def func(self, x, *args):
317317
assert d["var"] == 99
318318

319319

320+
def test_accessor_moves():
321+
inc_refs = m.accessor_moves()
322+
if inc_refs:
323+
# To be changed in PR #3970: [1, 0, 1, 0, ...]
324+
assert inc_refs == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
325+
else:
326+
pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG")
327+
328+
320329
def test_constructors():
321330
"""C++ default and converting constructors are equivalent to type calls in Python"""
322331
types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set]

0 commit comments

Comments
 (0)