From c2d3e220bd1bfcf664e29d95ac2ed774fd6723f4 Mon Sep 17 00:00:00 2001 From: Ryan Cahoon Date: Mon, 25 Oct 2021 19:04:45 -0700 Subject: [PATCH] fix: the types for return_value_policy_override in optional_caster (#3376) * fix: the types for return_value_policy_override in optional_caster `return_value_policy_override` was not being applied correctly in `optional_caster` in two ways: - The `is_lvalue_reference` condition referenced `T`, which was the `optional` type parameter from the class, when it should have used `T_`, which was the parameter to the `cast` function. `T_` can potentially be a reference type, but `T` will never be. - The type parameter passed to `return_value_policy_override` should be `T::value_type`, not `T`. This matches the way that the other STL container type casters work. The result of these issues was that a method/property definition which used a `reference` or `reference_internal` return value policy would create a Python value that's bound by reference to a temporary C++ object, resulting in undefined behavior. For reasons that I was not able to figure out fully, it seems like this causes problems when using old versions of `boost::optional`, but not with recent versions of `boost::optional` or the `libstdc++` implementation of `std::optional`. The issue (that the override to `return_value_policy::move` is never being applied) is present for all implementations, it just seems like that somehow doesn't result in problems for the some implementation of `optional`. This change includes a regression type with a custom optional-like type which was able to reproduce the issue. Part of the issue with using the wrong types may have stemmed from the type variables `T` and `T_` having very similar names. This also changes the type variables in `optional_caster` to use slightly more descriptive names, which also more closely follow the naming convention used by the other STL casters. Fixes #3330 * Fix clang-tidy complaints * Add missing NOLINT * Apply a couple more fixes * fix: support GCC 4.8 * tests: avoid warning about unknown compiler for compilers missing C++17 * Remove unneeded test module attribute * Change test enum to have more unique int values Co-authored-by: Aaron Gokaslan Co-authored-by: Henry Schreiner --- include/pybind11/stl.h | 16 ++-- tests/CMakeLists.txt | 6 +- tests/test_stl.cpp | 189 ++++++++++++++++++++++++++++++++++++++++- tests/test_stl.py | 67 +++++++++++++++ 4 files changed, 265 insertions(+), 13 deletions(-) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 2c017b4f..3608d298 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -245,17 +245,17 @@ template , Key, Value> { }; // This type caster is intended to be used for std::optional and std::experimental::optional -template struct optional_caster { - using value_conv = make_caster; +template struct optional_caster { + using value_conv = make_caster; - template - static handle cast(T_ &&src, return_value_policy policy, handle parent) { + template + static handle cast(T &&src, return_value_policy policy, handle parent) { if (!src) return none().inc_ref(); if (!std::is_lvalue_reference::value) { - policy = return_value_policy_override::policy(policy); + policy = return_value_policy_override::policy(policy); } - return value_conv::cast(*std::forward(src), policy, parent); + return value_conv::cast(*std::forward(src), policy, parent); } bool load(handle src, bool convert) { @@ -269,11 +269,11 @@ template struct optional_caster { if (!inner_caster.load(src, convert)) return false; - value.emplace(cast_op(std::move(inner_caster))); + value.emplace(cast_op(std::move(inner_caster))); return true; } - PYBIND11_TYPE_CASTER(T, _("Optional[") + value_conv::name + _("]")); + PYBIND11_TYPE_CASTER(Type, _("Optional[") + value_conv::name + _("]")); }; #if defined(PYBIND11_HAS_OPTIONAL) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d4e7b71e..e1b18dd7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -256,7 +256,9 @@ if(Boost_FOUND) endif() # Check if we need to add -lstdc++fs or -lc++fs or nothing -if(MSVC) +if(DEFINED CMAKE_CXX_STANDARD AND CMAKE_CXX_STANDARD LESS 17) + set(STD_FS_NO_LIB_NEEDED TRUE) +elseif(MSVC) set(STD_FS_NO_LIB_NEEDED TRUE) else() file( @@ -286,7 +288,7 @@ elseif(${STD_FS_NEEDS_CXXFS}) elseif(${STD_FS_NO_LIB_NEEDED}) set(STD_FS_LIB "") else() - message(WARNING "Unknown compiler - not passing -lstdc++fs") + message(WARNING "Unknown C++17 compiler - not passing -lstdc++fs") set(STD_FS_LIB "") endif() diff --git a/tests/test_stl.cpp b/tests/test_stl.cpp index 7e3363c5..bc5c6553 100644 --- a/tests/test_stl.cpp +++ b/tests/test_stl.cpp @@ -19,6 +19,18 @@ #include #include +#if defined(PYBIND11_TEST_BOOST) +#include + +namespace pybind11 { namespace detail { +template +struct type_caster> : optional_caster> {}; + +template <> +struct type_caster : void_caster {}; +}} // namespace pybind11::detail +#endif + // Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 #if defined(PYBIND11_HAS_VARIANT) using std::variant; @@ -59,7 +71,8 @@ namespace std { template