make implicit conversions non-reentrant (fixes #1035) (#1037)
diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst
index 8926fa9..7bcd038 100644
--- a/docs/advanced/classes.rst
+++ b/docs/advanced/classes.rst
@@ -585,6 +585,10 @@
Implicit conversions from ``A`` to ``B`` only work when ``B`` is a custom
data type that is exposed to Python via pybind11.
+ To prevent runaway recursion, implicit conversions are non-reentrant: an
+ implicit conversion invoked as part of another implicit conversion of the
+ same type (i.e. from ``A`` to ``B``) will fail.
+
.. _static_properties:
Static properties
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 9ed5036..16e3fdc 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -1536,7 +1536,16 @@
}
template <typename InputType, typename OutputType> void implicitly_convertible() {
+ struct set_flag {
+ bool &flag;
+ set_flag(bool &flag) : flag(flag) { flag = true; }
+ ~set_flag() { flag = false; }
+ };
auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * {
+ static bool currently_used = false;
+ if (currently_used) // implicit conversions are non-reentrant
+ return nullptr;
+ set_flag flag_helper(currently_used);
if (!detail::make_caster<InputType>().load(obj, false))
return nullptr;
tuple args(1);
diff --git a/tests/test_class.cpp b/tests/test_class.cpp
index 842991a..2221906 100644
--- a/tests/test_class.cpp
+++ b/tests/test_class.cpp
@@ -291,6 +291,17 @@
.def(py::init<int, const std::string &>())
.def_readwrite("field1", &BraceInitialization::field1)
.def_readwrite("field2", &BraceInitialization::field2);
+
+ // test_reentrant_implicit_conversion_failure
+ // #1035: issue with runaway reentrant implicit conversion
+ struct BogusImplicitConversion {
+ BogusImplicitConversion(const BogusImplicitConversion &) { }
+ };
+
+ py::class_<BogusImplicitConversion>(m, "BogusImplicitConversion")
+ .def(py::init<const BogusImplicitConversion &>());
+
+ py::implicitly_convertible<int, BogusImplicitConversion>();
}
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };
diff --git a/tests/test_class.py b/tests/test_class.py
index ac2a3c0..412d679 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -223,3 +223,13 @@
assert refcount_1 == refcount_3
assert refcount_2 > refcount_1
+
+
+def test_reentrant_implicit_conversion_failure(msg):
+ # ensure that there is no runaway reentrant implicit conversion (#1035)
+ with pytest.raises(TypeError) as excinfo:
+ m.BogusImplicitConversion(0)
+ assert msg(excinfo.value) == '''__init__(): incompatible constructor arguments. The following argument types are supported:
+ 1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion)
+
+Invoked with: 0'''