Skip to content

Commit 30f6c3b

Browse files
committed
Fix indirect loading of Eigen::Ref
Put the caster's temporary array on life support to ensure correct lifetime when it's being used as a subcaster.
1 parent af2dda3 commit 30f6c3b

File tree

3 files changed

+30
-0
lines changed

3 files changed

+30
-0
lines changed

include/pybind11/eigen.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ struct type_caster<
462462
if (!fits || !fits.template stride_compatible<props>())
463463
return false;
464464
copy_or_ref = std::move(copy);
465+
loader_life_support::add_patient(copy_or_ref);
465466
}
466467

467468
ref.reset();

tests/test_eigen.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "pybind11_tests.h"
1111
#include "constructor_stats.h"
1212
#include <pybind11/eigen.h>
13+
#include <pybind11/stl.h>
1314
#include <Eigen/Cholesky>
1415

1516
using MatrixXdR = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
@@ -298,4 +299,17 @@ test_initializer eigen([](py::module &m) {
298299
.def(py::init<>())
299300
.def_readonly("a", &CustomOperatorNew::a)
300301
.def_readonly("b", &CustomOperatorNew::b);
302+
303+
// test_eigen_ref_life_support
304+
// In case of a failure (the caster's temp array does not live long enough), creating
305+
// a new array (np.ones(10)) increases the chances that the temp array will be garbage
306+
// collected and/or that its memory will be overridden with different values.
307+
m.def("get_elem_direct", [](Eigen::Ref<const Eigen::VectorXd> v) {
308+
py::module::import("numpy").attr("ones")(10);
309+
return v(5);
310+
});
311+
m.def("get_elem_indirect", [](std::vector<Eigen::Ref<const Eigen::VectorXd>> v) {
312+
py::module::import("numpy").attr("ones")(10);
313+
return v[0](5);
314+
});
301315
});

tests/test_eigen.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,21 @@ def test_nocopy_wrapper():
602602
', flags.c_contiguous' in str(excinfo.value))
603603

604604

605+
def test_eigen_ref_life_support():
606+
"""Ensure the lifetime of temporary arrays created by the `Ref` caster
607+
608+
The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to
609+
happen both for directs casts (just the array) or indirectly (e.g. list of arrays).
610+
"""
611+
from pybind11_tests import get_elem_direct, get_elem_indirect
612+
613+
a = np.full(shape=10, fill_value=8, dtype=np.int8)
614+
assert get_elem_direct(a) == 8
615+
616+
list_of_a = [a]
617+
assert get_elem_indirect(list_of_a) == 8
618+
619+
605620
def test_special_matrix_objects():
606621
from pybind11_tests import incr_diag, symmetric_upper, symmetric_lower
607622

0 commit comments

Comments
 (0)