test_potentially_slicing_shared_ptr.cpp,py (for smart_holder only)

This commit is contained in:
Ralf W. Grosse-Kunstleve
2025-04-26 23:58:29 -07:00
parent 3b4b28cd46
commit 56d23dc478
6 changed files with 106 additions and 240 deletions

View File

@@ -130,11 +130,9 @@ set(PYBIND11_TEST_FILES
test_class_sh_trampoline_shared_from_this test_class_sh_trampoline_shared_from_this
test_class_sh_trampoline_shared_ptr_cpp_arg test_class_sh_trampoline_shared_ptr_cpp_arg
test_class_sh_trampoline_unique_ptr test_class_sh_trampoline_unique_ptr
test_class_sh_trampoline_weak_ptr
test_class_sh_unique_ptr_custom_deleter test_class_sh_unique_ptr_custom_deleter
test_class_sh_unique_ptr_member test_class_sh_unique_ptr_member
test_class_sh_virtual_py_cpp_mix test_class_sh_virtual_py_cpp_mix
test_class_sp_trampoline_weak_ptr
test_const_name test_const_name
test_constants_and_functions test_constants_and_functions
test_copy_move test_copy_move
@@ -163,6 +161,7 @@ set(PYBIND11_TEST_FILES
test_opaque_types test_opaque_types
test_operator_overloading test_operator_overloading
test_pickling test_pickling
test_potentially_slicing_shared_ptr
test_python_multiple_inheritance test_python_multiple_inheritance
test_pytypes test_pytypes
test_sequences_and_iterators test_sequences_and_iterators

View File

@@ -1,66 +0,0 @@
// Copyright (c) 2025 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "pybind11_tests.h"
#include <memory>
namespace pybind11_tests {
namespace class_sh_trampoline_weak_ptr {
struct VirtBase {
virtual ~VirtBase() = default;
virtual int get_code() { return 100; }
};
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 {
void set_wp(const std::shared_ptr<VirtBase> &sp) { wp = sp; }
int get_code() {
auto sp = wp.lock();
if (!sp) {
return -999;
}
return sp->get_code();
}
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
using namespace pybind11_tests::class_sh_trampoline_weak_ptr;
TEST_SUBMODULE(class_sh_trampoline_weak_ptr, m) {
py::classh<VirtBase, PyVirtBase>(m, "VirtBase")
.def(py::init<>())
.def("get_code", &VirtBase::get_code);
py::classh<WpOwner>(m, "WpOwner")
.def(py::init<>())
.def("set_wp",
[](WpOwner &self, py::handle obj) {
self.set_wp(py::potentially_slicing_shared_ptr<VirtBase>(obj));
})
.def("get_code", &WpOwner::get_code);
m.def("pass_through_sp_VirtBase", pass_through_sp_VirtBase);
}

View File

@@ -1,53 +0,0 @@
from __future__ import annotations
import gc
import pytest
import env
import pybind11_tests.class_sh_trampoline_weak_ptr as m
class PyDrvd(m.VirtBase):
def get_code(self):
return 200
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
def test_weak_ptr_owner(vtype, expected_code):
wpo = m.WpOwner()
assert wpo.get_code() == -999
obj = vtype()
assert obj.get_code() == expected_code
wpo.set_wp(obj)
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)
def test_potentially_slicing_shared_ptr_not_convertible_error():
wpo = m.WpOwner()
with pytest.raises(Exception) as excinfo:
wpo.set_wp("")
assert str(excinfo.value) == (
'"str" object is not convertible to std::shared_ptr<T>'
" (with T = pybind11_tests::class_sh_trampoline_weak_ptr::VirtBase)"
)

View File

@@ -1,86 +0,0 @@
from __future__ import annotations
import gc
import pytest
import env
import pybind11_tests.class_sp_trampoline_weak_ptr as m
class PyDrvd(m.VirtBase):
def get_code(self):
return 200
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
def test_with_wp_owner(vtype, expected_code):
wpo = m.WpOwner()
assert wpo.get_code() == -999
obj = vtype()
assert obj.get_code() == expected_code
wpo.set_wp(obj)
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_with_sp_owner(vtype, expected_code):
spo = m.SpOwner()
assert spo.get_code() == -888
obj = vtype()
assert obj.get_code() == expected_code
spo.set_sp(obj)
assert spo.get_code() == 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)])
def test_with_sp_and_wp_owners(vtype, expected_code):
spo = m.SpOwner()
wpo = m.WpOwner()
obj = vtype()
spo.set_sp(obj)
wpo.set_wp(obj)
assert spo.get_code() == expected_code
assert wpo.get_code() == expected_code
del obj
if env.PYPY or env.GRAALPY:
pytest.skip("Cannot reliably trigger GC")
# Inheritance slicing (issue #1333)
assert spo.get_code() == 100
assert wpo.get_code() == 100
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)

View File

@@ -7,7 +7,7 @@
#include <memory> #include <memory>
namespace pybind11_tests { namespace pybind11_tests {
namespace class_sp_trampoline_weak_ptr { namespace potentially_slicing_shared_ptr {
struct VirtBase { struct VirtBase {
virtual ~VirtBase() = default; virtual ~VirtBase() = default;
@@ -17,12 +17,28 @@ struct VirtBase {
struct PyVirtBase : VirtBase, py::trampoline_self_life_support { struct PyVirtBase : VirtBase, py::trampoline_self_life_support {
using VirtBase::VirtBase; using VirtBase::VirtBase;
int get_code() override { PYBIND11_OVERRIDE(int, VirtBase, get_code); } int get_code() override { PYBIND11_OVERRIDE(int, VirtBase, get_code); }
};
~PyVirtBase() override { std::shared_ptr<VirtBase> rtrn_obj_cast_shared_ptr(py::handle obj) {
fflush(stderr); return obj.cast<std::shared_ptr<VirtBase>>();
printf("\nLOOOK ~PyVirtBase()\n"); }
fflush(stdout);
std::shared_ptr<VirtBase> rtrn_potentially_slicing_shared_ptr(py::handle obj) {
return py::potentially_slicing_shared_ptr<VirtBase>(obj);
}
struct SpOwner {
void set_sp(const std::shared_ptr<VirtBase> &sp_) { sp = sp_; }
int get_code() {
if (!sp) {
return -888;
}
return sp->get_code();
} }
private:
std::shared_ptr<VirtBase> sp;
}; };
struct WpOwner { struct WpOwner {
@@ -40,43 +56,30 @@ private:
std::weak_ptr<VirtBase> wp; std::weak_ptr<VirtBase> wp;
}; };
struct SpOwner { } // namespace potentially_slicing_shared_ptr
void set_sp(const std::shared_ptr<VirtBase> &sp_) { sp = sp_; }
int get_code() {
if (!sp) {
return -888;
}
return sp->get_code();
}
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 } // namespace pybind11_tests
using namespace pybind11_tests::class_sp_trampoline_weak_ptr; using namespace pybind11_tests::potentially_slicing_shared_ptr;
TEST_SUBMODULE(class_sp_trampoline_weak_ptr, m) { TEST_SUBMODULE(potentially_slicing_shared_ptr, m) {
py::class_<VirtBase, std::shared_ptr<VirtBase>, PyVirtBase>(m, "VirtBase") py::classh<VirtBase, PyVirtBase>(m, "VirtBase")
.def(py::init<>()) .def(py::init<>())
.def("get_code", &VirtBase::get_code); .def("get_code", &VirtBase::get_code);
py::class_<WpOwner>(m, "WpOwner") m.def("rtrn_obj_cast_shared_ptr", rtrn_obj_cast_shared_ptr);
.def(py::init<>()) m.def("rtrn_potentially_slicing_shared_ptr", rtrn_potentially_slicing_shared_ptr);
.def("set_wp", &WpOwner::set_wp)
.def("get_code", &WpOwner::get_code);
py::class_<SpOwner>(m, "SpOwner") py::classh<SpOwner>(m, "SpOwner")
.def(py::init<>()) .def(py::init<>())
.def("set_sp", &SpOwner::set_sp) .def("set_sp", &SpOwner::set_sp)
.def("get_code", &SpOwner::get_code); .def("get_code", &SpOwner::get_code);
m.def("pass_through_sp_VirtBase", pass_through_sp_VirtBase); py::classh<WpOwner>(m, "WpOwner")
.def(py::init<>())
.def("set_wp", &WpOwner::set_wp)
.def("set_wp_potentially_slicing",
[](WpOwner &self, py::handle obj) {
self.set_wp(py::potentially_slicing_shared_ptr<VirtBase>(obj));
})
.def("get_code", &WpOwner::get_code);
} }

View File

@@ -0,0 +1,69 @@
from __future__ import annotations
import gc
import weakref
import pytest
import env
import pybind11_tests.potentially_slicing_shared_ptr as m
class PyDrvd(m.VirtBase):
def get_code(self):
return 200
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
@pytest.mark.parametrize(
"rtrn_meth", ["rtrn_obj_cast_shared_ptr", "rtrn_potentially_slicing_shared_ptr"]
)
def test_rtrn_obj_cast_shared_ptr(vtype, rtrn_meth, expected_code):
obj = vtype()
ptr = getattr(m, rtrn_meth)(obj)
assert ptr.get_code() == expected_code
objref = weakref.ref(obj)
del obj
gc.collect()
assert ptr.get_code() == expected_code # the ptr Python object keeps obj alive
assert objref() is not None
del ptr
gc.collect()
assert objref() is None
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
def test_with_sp_owner(vtype, expected_code):
spo = m.SpOwner()
assert spo.get_code() == -888
obj = vtype()
assert obj.get_code() == expected_code
spo.set_sp(obj)
assert spo.get_code() == expected_code
del obj
gc.collect()
assert spo.get_code() == expected_code
@pytest.mark.parametrize(("vtype", "expected_code"), [(m.VirtBase, 100), (PyDrvd, 200)])
@pytest.mark.parametrize("set_meth", ["set_wp", "set_wp_potentially_slicing"])
def test_with_wp_owner(vtype, set_meth, expected_code):
wpo = m.WpOwner()
assert wpo.get_code() == -999
obj = vtype()
assert obj.get_code() == expected_code
getattr(wpo, set_meth)(obj)
if vtype is m.VirtBase or set_meth == "set_wp_potentially_slicing":
assert wpo.get_code() == expected_code
else:
assert wpo.get_code() == -999 # see PR #5624
del obj
gc.collect()
if not (env.PYPY or env.GRAALPY):
assert wpo.get_code() == -999