Skip to content

Commit 9d4b4df

Browse files
wangxf123456rwgkpre-commit-ci[bot]
authored
Fixes issue #3788 (#3796)
* Reproducer for #3788 Expected to build & run as-is. Uncommenting reproduces the infinite recursion. * Moving try_as_void_ptr_capsule() to the end of load_impl() * Moving new test into the existing test_class_sh_void_ptr_capsule * Experiment * Remove comments and simplify the test cases. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent bcc241f commit 9d4b4df

File tree

3 files changed

+31
-29
lines changed

3 files changed

+31
-29
lines changed

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,6 @@ class modified_type_caster_generic_load_impl {
200200
if (!src) {
201201
return false;
202202
}
203-
if (cpptype && try_as_void_ptr_capsule(src)) {
204-
return true;
205-
}
206203
if (!typeinfo) {
207204
return try_load_foreign_module_local(src);
208205
}
@@ -295,7 +292,9 @@ class modified_type_caster_generic_load_impl {
295292
loaded_v_h = value_and_holder();
296293
return true;
297294
}
298-
295+
if (convert && cpptype && try_as_void_ptr_capsule(src)) {
296+
return true;
297+
}
299298
return false;
300299
}
301300

tests/test_class_sh_void_ptr_capsule.cpp

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@
77
namespace pybind11_tests {
88
namespace class_sh_void_ptr_capsule {
99

10-
// Without the helper we will run into a type_caster::load recursion.
11-
// This is because whenever the type_caster::load is called, it checks
12-
// whether the object defines an `as_` method that returns the void pointer
13-
// capsule. If yes, it calls the method. But in the following testcases, those
14-
// `as_` methods are defined with pybind11, which implicitly takes the object
15-
// itself as the first parameter. Therefore calling those methods causes loading
16-
// the object again, which causes infinite recursion.
17-
// This test is unusual in the sense that the void pointer capsules are meant to
18-
// be provided by objects wrapped with systems other than pybind11
19-
// (i.e. having to avoid the recursion is an artificial problem, not the norm).
20-
// Conveniently, the helper also serves to keep track of `capsule_generated`.
10+
// Conveniently, the helper serves to keep track of `capsule_generated`.
2111
struct HelperBase {
2212
HelperBase() = default;
2313
HelperBase(const HelperBase &) = delete;
@@ -65,6 +55,12 @@ struct AsAnotherObject : public HelperBase {
6555
}
6656
};
6757

58+
// https://github.com/pybind/pybind11/issues/3788
59+
struct TypeWithGetattr {
60+
TypeWithGetattr() = default;
61+
int get_42() const { return 42; }
62+
};
63+
6864
int get_from_valid_capsule(const Valid *c) { return c->get(); }
6965

7066
int get_from_shared_ptr_valid_capsule(const std::shared_ptr<Valid> &c) { return c->get(); }
@@ -83,6 +79,7 @@ PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::Va
8379
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::NoConversion)
8480
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::NoCapsuleReturned)
8581
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::AsAnotherObject)
82+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_void_ptr_capsule::TypeWithGetattr)
8683

8784
TEST_SUBMODULE(class_sh_void_ptr_capsule, m) {
8885
using namespace pybind11_tests::class_sh_void_ptr_capsule;
@@ -94,10 +91,8 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) {
9491

9592
py::classh<Valid, HelperBase>(m, "Valid")
9693
.def(py::init<>())
97-
.def("as_pybind11_tests_class_sh_void_ptr_capsule_Valid", [](HelperBase *self) {
98-
auto *obj = dynamic_cast<Valid *>(self);
99-
assert(obj != nullptr);
100-
PyObject *capsule = obj->as_pybind11_tests_class_sh_void_ptr_capsule_Valid();
94+
.def("as_pybind11_tests_class_sh_void_ptr_capsule_Valid", [](Valid &self) {
95+
PyObject *capsule = self.as_pybind11_tests_class_sh_void_ptr_capsule_Valid();
10196
return pybind11::reinterpret_steal<py::capsule>(capsule);
10297
});
10398

@@ -106,20 +101,16 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) {
106101
py::classh<NoCapsuleReturned, HelperBase>(m, "NoCapsuleReturned")
107102
.def(py::init<>())
108103
.def("as_pybind11_tests_class_sh_void_ptr_capsule_NoCapsuleReturned",
109-
[](HelperBase *self) {
110-
auto *obj = dynamic_cast<NoCapsuleReturned *>(self);
111-
assert(obj != nullptr);
104+
[](NoCapsuleReturned &self) {
112105
PyObject *capsule
113-
= obj->as_pybind11_tests_class_sh_void_ptr_capsule_NoCapsuleReturned();
106+
= self.as_pybind11_tests_class_sh_void_ptr_capsule_NoCapsuleReturned();
114107
return pybind11::reinterpret_steal<py::capsule>(capsule);
115108
});
116109

117110
py::classh<AsAnotherObject, HelperBase>(m, "AsAnotherObject")
118111
.def(py::init<>())
119-
.def("as_pybind11_tests_class_sh_void_ptr_capsule_Valid", [](HelperBase *self) {
120-
auto *obj = dynamic_cast<AsAnotherObject *>(self);
121-
assert(obj != nullptr);
122-
PyObject *capsule = obj->as_pybind11_tests_class_sh_void_ptr_capsule_Valid();
112+
.def("as_pybind11_tests_class_sh_void_ptr_capsule_Valid", [](AsAnotherObject &self) {
113+
PyObject *capsule = self.as_pybind11_tests_class_sh_void_ptr_capsule_Valid();
123114
return pybind11::reinterpret_steal<py::capsule>(capsule);
124115
});
125116

@@ -128,4 +119,10 @@ TEST_SUBMODULE(class_sh_void_ptr_capsule, m) {
128119
m.def("get_from_unique_ptr_valid_capsule", &get_from_unique_ptr_valid_capsule);
129120
m.def("get_from_no_conversion_capsule", &get_from_no_conversion_capsule);
130121
m.def("get_from_no_capsule_returned", &get_from_no_capsule_returned);
122+
123+
py::classh<TypeWithGetattr>(m, "TypeWithGetattr")
124+
.def(py::init<>())
125+
.def("get_42", &TypeWithGetattr::get_42)
126+
.def("__getattr__",
127+
[](TypeWithGetattr &, const std::string &key) { return "GetAttr: " + key; });
131128
}

tests/test_class_sh_void_ptr_capsule.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
@pytest.mark.parametrize(
77
"ctor, caller, expected, capsule_generated",
88
[
9-
(m.Valid, m.get_from_valid_capsule, 101, True),
9+
(m.Valid, m.get_from_valid_capsule, 101, False),
1010
(m.NoConversion, m.get_from_no_conversion_capsule, 102, False),
11-
(m.NoCapsuleReturned, m.get_from_no_capsule_returned, 103, True),
11+
(m.NoCapsuleReturned, m.get_from_no_capsule_returned, 103, False),
1212
(m.AsAnotherObject, m.get_from_valid_capsule, 104, True),
1313
],
1414
)
@@ -31,3 +31,9 @@ def test_as_void_ptr_capsule_unsupported(ctor, caller, pointer_type, capsule_gen
3131
caller(obj)
3232
assert pointer_type in str(excinfo.value)
3333
assert obj.capsule_generated == capsule_generated
34+
35+
36+
def test_type_with_getattr():
37+
obj = m.TypeWithGetattr()
38+
assert obj.get_42() == 42
39+
assert obj.something == "GetAttr: something"

0 commit comments

Comments
 (0)