Repository navigation
Conversation
…<T>> arguments optional_caster::load() used a local inner caster, so for T with a by-value caster (Eigen matrices, STL containers, ...) the loaded reference_wrapper pointed into a destroyed caster. Keep the inner caster as a member so it lives as long as the optional caster. Fixes pybind#5637
There was a problem hiding this comment.
codex GPT-6.1-Sol ultra:
Found two regressions at stl.h:565:
-
[P2] Preserve lazy construction for
None. The member constructs the inner caster before checking forNone. Forstd::optional<py::array_t<double>>, this imports NumPy and allocates an array. Without NumPy installed,f(None)now raisesModuleNotFoundError; the baseline accepts it. Reproducer: repro.cpp below. -
[P2] Preserve support for nonmovable custom casters. If the inner caster contains nonmovable state, such as
std::mutex, the optional caster now becomes nonmovable too. Previously validobj.cast<std::optional<Item>>()fails compiling atload_type()’sreturn conv, even whenItemis movable. Confirmed against baseline and PR headers. Reproducer: nonmovable_full.cpp below.
Lazy initialization through movable ownership could address both while preserving the lifetime fix.
… movable Hold the inner caster of optional_caster through std::unique_ptr, created only for non-None values. This keeps the pybind#5637 lifetime fix while restoring lazy construction for None (e.g. std::optional<py::array_t<T>> no longer needs NumPy for None) and movability of the optional caster when the inner caster is not movable (needed by cast<std::optional<T>>()).
|
Thanks @rwgk, both regressions reproduced with the attached files ( Fixed in f116885: the inner caster is now held by |
|
Here's a Fable 5.1 review too: 🤖 AI text below 🤖
Items 1, 2 and 4 are the ones that affect the design of the fix. |
…l STL casters Values loaded as std::reference_wrapper<T>, where T is converted by value, refer into the caster that loaded them. Instead of always holding the optional's inner caster on the heap, a caster now declares this with `static constexpr bool value_refers_to_caster` (std::reference_wrapper<T> over a non-type_caster_generic caster, and the STL casters with such elements), exposed as detail::loaded_value_refers_to_caster<T>. detail::subcaster_store<T> loads elements through a local caster as before when that trait is false (no allocation, casters stay copyable), and keeps the casters in a std::forward_list otherwise (constructed in place, stable across moves, move-only). It is used by optional_caster, variant_caster, list_caster, array_caster, set_caster and map_caster, so e.g. std::vector<std::reference_wrapper<std::string>> and std::variant<int, std::reference_wrapper<Eigen::MatrixXd>> no longer dangle. cast_is_temporary_value_reference now also covers such types, so py::cast<std::optional<std::reference_wrapper<std::string>>>(obj) (whose result would refer into a destroyed temporary caster) fails to compile with the existing static_assert, and PYBIND11_OVERRIDE uses its static caster. optional_caster::load() now empties the value and drops held casters before loading, including for None. The tests track live LiveTracked instances by address and generation id (thread_local), so a reused address is detected.
|
Thanks for the review. Reworked in bf831fe:
The changes to |
Description
Fixes #5637.
Passing a NumPy array to a function that takes
std::optional<std::reference_wrapper<Eigen::MatrixXd>>segfaults. The same happens for anyTconverted by value (Eigen types, strings, STL containers, custom casters), and not only insidestd::optional:std::variant,std::vector/deque/list,std::array,std::setandstd::mapcasters all load each element with a local caster and discard it, so astd::reference_wrapper<T>element refers to a destroyedT.Change
static constexpr bool value_refers_to_caster: the loaded value may refer to data owned by the caster.type_caster<std::reference_wrapper<T>>sets it whenT's caster is nottype_caster_generic-based (i.e. converts by value); the STL casters set it when an element type does. It is read throughdetail::loaded_value_refers_to_caster<T>.detail::subcaster_store<T>loads an element. When the trait is false it uses a local caster exactly as before (no allocation, casters stay copyable). When it is true it keeps the casters in astd::forward_list(constructed in place, so they need not be movable; addresses stable when the outer caster moves; the store is move-only). Used byoptional_caster,variant_caster,list_caster,array_caster,set_caster,map_caster.cast_is_temporary_value_referencealso covers such types, sopy::cast<std::optional<std::reference_wrapper<std::string>>>(obj)(andpy::cast<std::reference_wrapper<std::string>>,std::vector<std::reference_wrapper<std::string>>, ...), whose result would refer into a destroyed temporary caster, now hits the existing static_assert, andPYBIND11_OVERRIDEreturning such a type uses its static caster like it does for references.optional_caster::load()empties the value and drops held casters before loading (also forNone). The inner caster is still only constructed for non-Nonevalues.Testing
test_stl: optional, nestedstd::vector<std::optional<std::reference_wrapper<...>>>, variant, vector, array, set and map with astd::reference_wrapperto a by-value type. The test type tracks live instances by address and generation id, so a reused address is not taken for the original object. With the holding disabled they reportdangling; with the fix they pass. Also a reload test (value thenNone), the non-movable/lazy inner caster test from the previous round, and static_asserts for the trait and caster copyability.std::stringin vector, array and map) under AddressSanitizer: clean with the fix; heap-use-after-free without it.py::castof these types was checked to fail to compile (no compile-fail tests in the suite, so it is covered by static_asserts on the trait).Suggested changelog entry:
std::reference_wrapper<T>to a type converted by value (e.g. Eigen matrices,std::string) insidestd::optional,std::variantor an STL container.py::castto such types (including plainstd::reference_wrapper<T>), which returned a dangling reference, is now a compile-time error.