Skip to content

Commit f537093

Browse files
Fail on passing py::object with wrong Python type to py::object subclass using PYBIND11_OBJECT macro (#2349)
* Fail on passing py::object with wrong Python type to py::object subclass using PYBIND11_OBJECT macro * Split off test_non_converting_constructors from test_constructors * Fix test_as_type, as py::type constructor now throws an error itself if the argument is not a type * Replace tp_name access by pybind11::detail::get_fully_qualified_tp_name * Move forward-declaration of get_fully_qualified_tp_name to detail/common.h * Don't add the builtins module name in get_fully_qualified_tp_name for PyPy * Add PYBIND11_BUILTINS_MODULE macro, and use it in get_fully_qualified_tp_name
1 parent 2a2f522 commit f537093

File tree

8 files changed

+49
-16
lines changed

8 files changed

+49
-16
lines changed

include/pybind11/cast.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@
3939
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4040
PYBIND11_NAMESPACE_BEGIN(detail)
4141

42-
// Forward-declaration; see detail/class.h
43-
std::string get_fully_qualified_tp_name(PyTypeObject*);
44-
4542
/// A life support system for temporary objects created by `type_caster::load()`.
4643
/// Adding a patient will keep it alive up until the enclosing function returns.
4744
class loader_life_support {

include/pybind11/detail/class.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ inline std::string get_fully_qualified_tp_name(PyTypeObject *type) {
2828
#if !defined(PYPY_VERSION)
2929
return type->tp_name;
3030
#else
31-
return handle((PyObject *) type).attr("__module__").cast<std::string>() + "." + type->tp_name;
31+
auto module_name = handle((PyObject *) type).attr("__module__").cast<std::string>();
32+
if (module_name == PYBIND11_BUILTINS_MODULE)
33+
return type->tp_name;
34+
else
35+
return std::move(module_name) + "." + type->tp_name;
3236
#endif
3337
}
3438

include/pybind11/detail/common.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
#define PYBIND11_STR_TYPE ::pybind11::str
183183
#define PYBIND11_BOOL_ATTR "__bool__"
184184
#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_bool)
185+
#define PYBIND11_BUILTINS_MODULE "builtins"
185186
// Providing a separate declaration to make Clang's -Wmissing-prototypes happy.
186187
// See comment for PYBIND11_MODULE below for why this is marked "maybe unused".
187188
#define PYBIND11_PLUGIN_IMPL(name) \
@@ -209,6 +210,7 @@
209210
#define PYBIND11_STR_TYPE ::pybind11::bytes
210211
#define PYBIND11_BOOL_ATTR "__nonzero__"
211212
#define PYBIND11_NB_BOOL(ptr) ((ptr)->nb_nonzero)
213+
#define PYBIND11_BUILTINS_MODULE "__builtin__"
212214
// Providing a separate PyInit decl to make Clang's -Wmissing-prototypes happy.
213215
// See comment for PYBIND11_MODULE below for why this is marked "maybe unused".
214216
#define PYBIND11_PLUGIN_IMPL(name) \
@@ -853,8 +855,8 @@ class any_container {
853855
const std::vector<T> *operator->() const { return &v; }
854856
};
855857

856-
PYBIND11_NAMESPACE_END(detail)
857-
858-
858+
// Forward-declaration; see detail/class.h
859+
std::string get_fully_qualified_tp_name(PyTypeObject*);
859860

861+
PYBIND11_NAMESPACE_END(detail)
860862
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/pytypes.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,11 +812,18 @@ PYBIND11_NAMESPACE_END(detail)
812812
: Parent(check_(o) ? o.release().ptr() : ConvertFun(o.ptr()), stolen_t{}) \
813813
{ if (!m_ptr) throw error_already_set(); }
814814

815+
#define PYBIND11_OBJECT_CHECK_FAILED(Name, o) \
816+
type_error("Object of type '" + \
817+
pybind11::detail::get_fully_qualified_tp_name(Py_TYPE(o.ptr())) + \
818+
"' is not an instance of '" #Name "'")
819+
815820
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
816821
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \
817822
/* This is deliberately not 'explicit' to allow implicit conversion from object: */ \
818-
Name(const object &o) : Parent(o) { } \
819-
Name(object &&o) : Parent(std::move(o)) { }
823+
Name(const object &o) : Parent(o) \
824+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); } \
825+
Name(object &&o) : Parent(std::move(o)) \
826+
{ if (o && !check_(o)) throw PYBIND11_OBJECT_CHECK_FAILED(Name, o); }
820827

821828
#define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \
822829
PYBIND11_OBJECT(Name, Parent, CheckFun) \

tests/test_class.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,7 @@ TEST_SUBMODULE(class_, m) {
157157
});
158158

159159
m.def("as_type", [](py::object ob) {
160-
auto tp = py::type(ob);
161-
if (py::isinstance<py::type>(ob))
162-
return tp;
163-
else
164-
throw std::runtime_error("Invalid type");
160+
return py::type(ob);
165161
});
166162

167163
// test_mismatched_holder

tests/test_class.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ def test_type_of_py_nodelete():
5959
def test_as_type_py():
6060
assert m.as_type(int) == int
6161

62-
with pytest.raises(RuntimeError):
62+
with pytest.raises(TypeError):
6363
assert m.as_type(1) == int
6464

65-
with pytest.raises(RuntimeError):
65+
with pytest.raises(TypeError):
6666
assert m.as_type(m.DerivedClass1()) == m.DerivedClass1
6767

6868

tests/test_pytypes.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,19 @@ TEST_SUBMODULE(pytypes, m) {
243243

244244
m.def("convert_to_pybind11_str", [](py::object o) { return py::str(o); });
245245

246+
m.def("nonconverting_constructor", [](std::string type, py::object value) -> py::object {
247+
if (type == "bytes") {
248+
return py::bytes(value);
249+
}
250+
else if (type == "none") {
251+
return py::none(value);
252+
}
253+
else if (type == "ellipsis") {
254+
return py::ellipsis(value);
255+
}
256+
throw std::runtime_error("Invalid type");
257+
});
258+
246259
m.def("get_implicit_casting", []() {
247260
py::dict d;
248261
d["char*_i1"] = "abc";

tests/test_pytypes.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,20 @@ def test_constructors():
245245
assert noconv2[k] is expected[k]
246246

247247

248+
def test_non_converting_constructors():
249+
non_converting_test_cases = [
250+
("bytes", range(10)),
251+
("none", 42),
252+
("ellipsis", 42),
253+
]
254+
for t, v in non_converting_test_cases:
255+
with pytest.raises(TypeError) as excinfo:
256+
m.nonconverting_constructor(t, v)
257+
expected_error = "Object of type '{}' is not an instance of '{}'".format(
258+
type(v).__name__, t)
259+
assert str(excinfo.value) == expected_error
260+
261+
248262
def test_pybind11_str_raw_str():
249263
# specifically to exercise pybind11::str::raw_str
250264
cvt = m.convert_to_pybind11_str

0 commit comments

Comments
 (0)