Modify py::trampoline_self_life_support semantics: if trampoline class does not inherit from this class, preserve established Inheritance Slicing behavior.

rwgk reached this point with the help of ChatGPT:

* https://chatgpt.com/share/68056498-7d94-8008-8ff0-232e2aba451c

The only production code change in this commit is:

```
diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h
index d4f9a41e..f3d45301 100644
--- a/include/pybind11/detail/type_caster_base.h
+++ b/include/pybind11/detail/type_caster_base.h
@@ -776,6 +776,14 @@ struct load_helper : value_and_holder_helper {
                 if (released_ptr) {
                     return std::shared_ptr<T>(released_ptr, type_raw_ptr);
                 }
+                auto *self_life_support
+                    = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(type_raw_ptr);
+                if (self_life_support == nullptr) {
+                    std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>();
+                    std::shared_ptr<T> to_be_released(void_shd_ptr, type_raw_ptr);
+                    vptr_gd_ptr->released_ptr = to_be_released;
+                    return to_be_released;
+                }
                 std::shared_ptr<T> to_be_released(
                     type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
                 vptr_gd_ptr->released_ptr = to_be_released;
```
This commit is contained in:
Ralf W. Grosse-Kunstleve
2025-04-20 14:14:47 -07:00
parent 1c0b700fdc
commit 4638e017b6
7 changed files with 76 additions and 6 deletions

View File

@@ -776,6 +776,14 @@ struct load_helper : value_and_holder_helper {
if (released_ptr) {
return std::shared_ptr<T>(released_ptr, type_raw_ptr);
}
auto *self_life_support
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(type_raw_ptr);
if (self_life_support == nullptr) {
std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>();
std::shared_ptr<T> to_be_released(void_shd_ptr, type_raw_ptr);
vptr_gd_ptr->released_ptr = to_be_released;
return to_be_released;
}
std::shared_ptr<T> to_be_released(
type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
vptr_gd_ptr->released_ptr = to_be_released;

View File

@@ -3416,14 +3416,23 @@ PYBIND11_NAMESPACE_END(detail)
template <class T>
function get_override(const T *this_ptr, const char *name) {
auto *tinfo = detail::get_type_info(typeid(T));
fflush(stderr);
printf("\nLOOOK get_override tinfo truthy = %s\n", tinfo ? "YES" : "NO");
fflush(stdout);
return tinfo ? detail::get_type_override(this_ptr, tinfo, name) : function();
}
#define PYBIND11_OVERRIDE_IMPL(ret_type, cname, name, ...) \
do { \
pybind11::gil_scoped_acquire gil; \
fflush(stderr); \
printf("\nLOOOK BEFORE static_cast<const cname *>(this)\n"); \
fflush(stdout); \
pybind11::function override \
= pybind11::get_override(static_cast<const cname *>(this), name); \
fflush(stderr); \
printf("\nLOOOK AFTER static_cast<const cname *>(this)\n"); \
fflush(stdout); \
if (override) { \
auto o = override(__VA_ARGS__); \
PYBIND11_WARNING_PUSH \

View File

@@ -28,7 +28,7 @@ struct SpBase {
std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
struct PySpBase : SpBase {
struct PySpBase : SpBase, py::trampoline_self_life_support {
using SpBase::SpBase;
bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
};

View File

@@ -14,9 +14,15 @@ struct VirtBase {
virtual int get_code() { return 100; }
};
struct PyVirtBase : VirtBase, py::trampoline_self_life_support {
struct PyVirtBase : VirtBase /*, py::trampoline_self_life_support */ {
using VirtBase::VirtBase;
int get_code() override { PYBIND11_OVERRIDE(int, VirtBase, get_code); }
~PyVirtBase() override {
fflush(stderr);
printf("\nLOOOK ~PyVirtBase()\n");
fflush(stdout);
}
};
struct WpOwner {
@@ -34,6 +40,10 @@ private:
std::weak_ptr<VirtBase> wp;
};
std::shared_ptr<VirtBase> pass_through_sp_VirtBase(const std::shared_ptr<VirtBase> &sp) {
return sp;
}
} // namespace class_sh_trampoline_weak_ptr
} // namespace pybind11_tests
@@ -48,4 +58,6 @@ TEST_SUBMODULE(class_sh_trampoline_weak_ptr, m) {
.def(py::init<>())
.def("set_wp", &WpOwner::set_wp)
.def("get_code", &WpOwner::get_code);
m.def("pass_through_sp_VirtBase", pass_through_sp_VirtBase);
}

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import gc
import pytest
import env
@@ -20,12 +22,22 @@ def test_weak_ptr_owner(vtype, expected_code):
assert obj.get_code() == expected_code
wpo.set_wp(obj)
if vtype is m.VirtBase:
assert wpo.get_code() == expected_code
else:
assert wpo.get_code() == -999 # THIS NEEDS FIXING (issue #5623)
assert wpo.get_code() == expected_code
del obj
if env.PYPY or env.GRAALPY:
pytest.skip("Cannot reliably trigger GC")
assert wpo.get_code() == -999
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
def test_pass_through_sp_VirtBase(vtype, expected_code):
obj = vtype()
ptr = m.pass_through_sp_VirtBase(obj)
print("\nLOOOK BEFORE del obj", flush=True)
del obj
print("\nLOOOK AFTER del obj", flush=True)
gc.collect()
print("\nLOOOK AFTER gc.collect()", flush=True)
assert ptr.get_code() == expected_code
print("\nLOOOK AFTER ptr.get_code()", flush=True)

View File

@@ -17,6 +17,12 @@ struct VirtBase {
struct PyVirtBase : VirtBase, py::trampoline_self_life_support {
using VirtBase::VirtBase;
int get_code() override { PYBIND11_OVERRIDE(int, VirtBase, get_code); }
~PyVirtBase() override {
fflush(stderr);
printf("\nLOOOK ~PyVirtBase()\n");
fflush(stdout);
}
};
struct WpOwner {
@@ -48,6 +54,10 @@ private:
std::shared_ptr<VirtBase> sp;
};
std::shared_ptr<VirtBase> pass_through_sp_VirtBase(const std::shared_ptr<VirtBase> &sp) {
return sp;
}
} // namespace class_sp_trampoline_weak_ptr
} // namespace pybind11_tests
@@ -67,4 +77,6 @@ TEST_SUBMODULE(class_sp_trampoline_weak_ptr, m) {
.def(py::init<>())
.def("set_sp", &SpOwner::set_sp)
.def("get_code", &SpOwner::get_code);
m.def("pass_through_sp_VirtBase", pass_through_sp_VirtBase);
}

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import gc
import pytest
import env
@@ -42,7 +44,9 @@ def test_with_sp_owner(vtype, expected_code):
del obj
if env.PYPY or env.GRAALPY:
pytest.skip("Cannot reliably trigger GC")
print("\nLOOOK BEFORE spo.get_code() AFTER del obj", flush=True)
assert spo.get_code() == 100 # Inheritance slicing (issue #1333)
print("\nLOOOK AFTER spo.get_code() AFTER del obj", flush=True)
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
@@ -67,3 +71,16 @@ def test_with_sp_and_wp_owners(vtype, expected_code):
del spo
assert wpo.get_code() == -999
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
def test_pass_through_sp_VirtBase(vtype, expected_code):
obj = vtype()
ptr = m.pass_through_sp_VirtBase(obj)
print("\nLOOOK BEFORE del obj", flush=True)
del obj
print("\nLOOOK AFTER del obj", flush=True)
gc.collect()
print("\nLOOOK AFTER gc.collect()", flush=True)
assert ptr.get_code() == expected_code
print("\nLOOOK AFTER ptr.get_code()", flush=True)