-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: the types for return_value_policy_override in optional_caster #3376
fix: the types for return_value_policy_override in optional_caster #3376
Conversation
`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<T>` 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 pybind#3330
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Thanks for the thorough testing!
@@ -245,17 +245,17 @@ template <typename Key, typename Value, typename Hash, typename Equal, typename | |||
: map_caster<std::unordered_map<Key, Value, Hash, Equal, Alloc>, Key, Value> { }; | |||
|
|||
// This type caster is intended to be used for std::optional and std::experimental::optional | |||
template<typename T> struct optional_caster { | |||
using value_conv = make_caster<typename T::value_type>; | |||
template<typename Type, typename Value = typename Type::value_type> struct optional_caster { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super important, but this would read much nicer with ValueType
(and wouldn't even need extra line breaks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using the same naming convention for template parameters that's used for list_caster
and array_caster
. I agree ValueType
is a better name, though. Would you prefer that I change it or keep it consistent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine as is. Thanks for the explanation!
tests/test_stl.cpp
Outdated
OptionalProperties() : value(EnumType::k1) {} | ||
~OptionalProperties() { | ||
// Reset value to detect use-after-destruction. | ||
// This is set to a specific value rather than NULL to ensure that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than NULL
std::nullopt (to be precise)?
The specific value chosen happens to be 0
in memory. Is that sufficiently reliable for detecting use-after-destruction? (Vs. some random non-zero value.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I used the generic nullopt
instead of std::nullopt
because the OptionalImpl
type may not be std::optional
.
tests/test_stl.cpp
Outdated
#endif | ||
|
||
// test_refsensitive_optional | ||
m.attr("has_refsensitive_optional") = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually needed/used? (copy-paste oversight)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed
c50c854
to
2412ad0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the only test failure is a standard flake. I'm happy once @rwgk's comments are resolved.
The one CI failure is: I've not seen that one before, but it's safe to ignore I think. I'll go ahead merge this now. |
Hi @ryancahoon-zoox, when testing this PR with the smart_holder branch and
|
@ryancahoon-zoox never mind, I think I just got it, it needs a tweak on the smart_holder branch: |
Glad my tests are already being useful :-) I'm not exactly sure what changed - I didn't get so far as reading the Boost source. I just wrote a binding for a class similar to My very tenuous intuition is that it's either some optimization involving move semantics, or else in the process of standardizing |
Description
return_value_policy_override
was not being applied correctly inoptional_caster
in two ways:is_lvalue_reference
condition referencedT
, which was theoptional<T>
type parameter from the class, when it should have usedT_
,which was the parameter to the
cast
function.T_
can potentially be areference type, but
T
will never be.return_value_policy_override
should beT::value_type
, notT
. This matches the way that the other STL containertype casters work.
The result of these issues was that a method/property definition which used a
reference
orreference_internal
return value policy would create a Pythonvalue 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 thelibstdc++
implementation of
std::optional
. The issue (that the override toreturn_value_policy::move
is never being applied) is present for allimplementations, it just seems like that somehow doesn't result in problems for
the some implementation of
optional
. This change includes a regression typewith 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
andT_
having very similar names. This also changes the typevariables in
optional_caster
to use slightly more descriptive names, whichalso more closely follow the naming convention used by the other STL casters.
Fixes #3330. Closes #3376.
Suggested changelog entry: