Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -819,14 +819,19 @@ class tuple_caster {

static constexpr bool load_impl(const sequence &, bool, index_sequence<>) { return true; }

handle load_item(const sequence &seq, size_t index) {
items[index] = accessor_policies::sequence_item::get(seq, index);
return items[index];
}

template <size_t... Is>
bool load_impl(const sequence &seq, bool convert, index_sequence<Is...>) {
#ifdef __cpp_fold_expressions
if ((... || !std::get<Is>(subcasters).load(seq[Is], convert))) {
if ((... || !std::get<Is>(subcasters).load(load_item(seq, Is), convert))) {
return false;
}
#else
for (bool r : {std::get<Is>(subcasters).load(seq[Is], convert)...}) {
for (bool r : {std::get<Is>(subcasters).load(load_item(seq, Is), convert)...}) {
if (!r) {
return false;
}
Expand Down Expand Up @@ -859,6 +864,8 @@ class tuple_caster {
}

Tuple<make_caster<Ts>...> subcasters;
// A subcaster can refer to its item, and the sequence does not necessarily keep it alive.
std::array<object, size> items;
};

template <typename T1, typename T2>
Expand Down
19 changes: 19 additions & 0 deletions tests/test_builtin_casters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "pybind11_tests.h"

#include <set>

struct ConstRefCasted {
int tag;
};
Expand Down Expand Up @@ -256,6 +258,23 @@ TEST_SUBMODULE(builtin_casters, m) {
},
"Return a triple in reversed order");
m.def("empty_tuple", []() { return std::tuple<>(); });

// test_tuple_items_created_on_the_fly
// The copy constructor records if the source instance was alive.
struct TupleItem {
explicit TupleItem(int) { alive().insert(this); }
TupleItem(const TupleItem &other) : source_was_alive{alive().count(&other) != 0} {}
~TupleItem() { alive().erase(this); }
static std::set<const TupleItem *> &alive() {
static std::set<const TupleItem *> instances;
return instances;
}
bool source_was_alive = true;
};
py::class_<TupleItem>(m, "TupleItem").def(py::init<int>());
m.def("pair_item_sources_were_alive", [](const std::pair<TupleItem, TupleItem> &input) {
return std::make_pair(input.first.source_was_alive, input.second.source_was_alive);
});
static std::pair<RValueCaster, RValueCaster> lvpair;
static std::tuple<RValueCaster, RValueCaster, RValueCaster> lvtuple;
static std::pair<RValueCaster, std::tuple<RValueCaster, std::pair<RValueCaster, RValueCaster>>>
Expand Down
13 changes: 13 additions & 0 deletions tests/test_builtin_casters.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,19 @@ def test_tuple(doc):
assert m.int_string_pair() == (2, "items")


def test_tuple_items_created_on_the_fly():
"""Each item is kept alive until its value was copied (no use-after-free)."""

class OnTheFly:
def __len__(self):
return 2

def __getitem__(self, index):
return m.TupleItem(index) # The sequence does not retain the item.

assert m.pair_item_sources_were_alive(OnTheFly()) == (True, True)


def test_builtins_cast_return_none():
"""Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
assert m.return_none_string() is None
Expand Down
Loading