mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-19 15:10:38 +00:00
Fail static_assert when trying to reference non-referencable types
The previous commit to address #392 triggers a compiler warning about returning a reference to a local variable, which is *not* a false alarm: the following: py::cast<int &>(o) (which happens internally in an overload declaration) really is returning a reference to a local, because the cast operators for the type_caster for numeric types returns a reference to its own member. This commit adds a static_assert to make that a compilation failure rather than returning a reference into about-to-be-freed memory. Incidentally, this is also a fix for #219, which is exactly the same issue: we can't reference numeric primitives that are cast from wrappers around python numeric types.
This commit is contained in:
parent
56f717756b
commit
c03db9bad9
@ -855,10 +855,22 @@ template <typename T> struct move_if_unreferenced<T, typename std::enable_if<
|
|||||||
>::type> : std::true_type {};
|
>::type> : std::true_type {};
|
||||||
template <typename T> using move_never = std::integral_constant<bool, !move_always<T>::value && !move_if_unreferenced<T>::value>;
|
template <typename T> using move_never = std::integral_constant<bool, !move_always<T>::value && !move_if_unreferenced<T>::value>;
|
||||||
|
|
||||||
|
// Detect whether returning a `type` from a cast on type's type_caster is going to result in a
|
||||||
|
// reference or pointer to a local variable of the type_caster. Basically, only
|
||||||
|
// non-reference/pointer `type`s and reference/pointers from a type_caster_generic are safe;
|
||||||
|
// everything else returns a reference/pointer to a local variable.
|
||||||
|
template <typename type> using cast_is_temporary_value_reference = bool_constant<
|
||||||
|
(std::is_reference<type>::value || std::is_pointer<type>::value) &&
|
||||||
|
!std::is_base_of<type_caster_generic, make_caster<type>>::value
|
||||||
|
>;
|
||||||
|
|
||||||
|
|
||||||
NAMESPACE_END(detail)
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
template <typename T> T cast(const handle &handle) {
|
template <typename T> T cast(const handle &handle) {
|
||||||
using type_caster = detail::make_caster<T>;
|
using type_caster = detail::make_caster<T>;
|
||||||
|
static_assert(!detail::cast_is_temporary_value_reference<T>::value,
|
||||||
|
"Unable to cast type to reference: value is local to type caster");
|
||||||
type_caster conv;
|
type_caster conv;
|
||||||
if (!conv.load(handle, true)) {
|
if (!conv.load(handle, true)) {
|
||||||
#if defined(NDEBUG)
|
#if defined(NDEBUG)
|
||||||
|
@ -208,7 +208,9 @@ void init_issues(py::module &m) {
|
|||||||
public:
|
public:
|
||||||
using OverrideTest::OverrideTest;
|
using OverrideTest::OverrideTest;
|
||||||
int int_value() override { PYBIND11_OVERLOAD(int, OverrideTest, int_value); }
|
int int_value() override { PYBIND11_OVERLOAD(int, OverrideTest, int_value); }
|
||||||
int &int_ref() override { PYBIND11_OVERLOAD(int &, OverrideTest, int_ref); }
|
// Not allowed (uncommenting should hit a static_assert failure): we can't get a reference
|
||||||
|
// to a python numeric value, since we only copy values in the numeric type caster:
|
||||||
|
// int &int_ref() override { PYBIND11_OVERLOAD(int &, OverrideTest, int_ref); }
|
||||||
A A_value() override { PYBIND11_OVERLOAD(A, OverrideTest, A_value); }
|
A A_value() override { PYBIND11_OVERLOAD(A, OverrideTest, A_value); }
|
||||||
A &A_ref() override { PYBIND11_OVERLOAD(A &, OverrideTest, A_ref); }
|
A &A_ref() override { PYBIND11_OVERLOAD(A &, OverrideTest, A_ref); }
|
||||||
};
|
};
|
||||||
@ -217,9 +219,10 @@ void init_issues(py::module &m) {
|
|||||||
py::class_<OverrideTest, PyOverrideTest>(m2, "OverrideTest")
|
py::class_<OverrideTest, PyOverrideTest>(m2, "OverrideTest")
|
||||||
.def(py::init<int>())
|
.def(py::init<int>())
|
||||||
.def("int_value", &OverrideTest::int_value)
|
.def("int_value", &OverrideTest::int_value)
|
||||||
.def("int_ref", &OverrideTest::int_ref)
|
// .def("int_ref", &OverrideTest::int_ref)
|
||||||
.def("A_value", &OverrideTest::A_value)
|
.def("A_value", &OverrideTest::A_value)
|
||||||
.def("A_ref", &OverrideTest::A_ref);
|
.def("A_ref", &OverrideTest::A_ref);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MSVC workaround: trying to use a lambda here crashes MSCV
|
// MSVC workaround: trying to use a lambda here crashes MSCV
|
||||||
|
@ -171,9 +171,10 @@ def test_override_ref():
|
|||||||
from pybind11_tests.issues import OverrideTest
|
from pybind11_tests.issues import OverrideTest
|
||||||
o = OverrideTest(42)
|
o = OverrideTest(42)
|
||||||
|
|
||||||
i = o.int_ref()
|
# Not allowed (see associated .cpp comment)
|
||||||
|
#i = o.int_ref()
|
||||||
|
#assert o.int_ref() == 42
|
||||||
assert o.int_value() == 42
|
assert o.int_value() == 42
|
||||||
assert o.int_ref() == 42
|
|
||||||
|
|
||||||
assert o.A_value().value == 99
|
assert o.A_value().value == 99
|
||||||
a = o.A_ref()
|
a = o.A_ref()
|
||||||
|
Loading…
Reference in New Issue
Block a user