pybind11
pybind11 copied to clipboard
[WIP] Bake smart_holder functionality into `class_` and `type_caster_base`
Description
WORK IN PROGRESS
Best to ignore this PR until all tests pass.
High-level approach:
- Start with pybind11 master branch.
- Make
smart_holderthe default holder. - Fix until all tests pass, while also adding in all tests from the smart_holder branch. (This is the hard part. Could take a while.)
Suggested changelog entry:
Current state testing with the Google-internal toolchain, using ASAN:
7 failing tests:
//third_party/pybind11/tests:test_copy_move
//third_party/pybind11/tests:test_opaque_types
//third_party/pybind11/tests:test_smart_ptr
//third_party/pybind11/tests:test_stl
//third_party/pybind11/tests:test_stl_binders
//third_party/pybind11/tests:test_tagbased_polymorphic
//third_party/pybind11/tests:test_vector_unique_ptr_member
All other tests run ASAN-clean. For completeness, this is the list of passing tests:
40 passing tests:
//third_party/pybind11/tests:test_async
//third_party/pybind11/tests:test_buffers
//third_party/pybind11/tests:test_builtin_casters
//third_party/pybind11/tests:test_call_policies
//third_party/pybind11/tests:test_callbacks
//third_party/pybind11/tests:test_chrono
//third_party/pybind11/tests:test_class
//third_party/pybind11/tests:test_const_name
//third_party/pybind11/tests:test_constants_and_functions
//third_party/pybind11/tests:test_custom_type_casters
//third_party/pybind11/tests:test_custom_type_setup
//third_party/pybind11/tests:test_docstring_options
//third_party/pybind11/tests:test_eigen_matrix
//third_party/pybind11/tests:test_eigen_tensor
//third_party/pybind11/tests:test_enum
//third_party/pybind11/tests:test_eval
//third_party/pybind11/tests:test_exceptions
//third_party/pybind11/tests:test_factory_constructors
//third_party/pybind11/tests:test_gil_scoped
//third_party/pybind11/tests:test_iostream
//third_party/pybind11/tests:test_kwargs_and_defaults
//third_party/pybind11/tests:test_local_bindings
//third_party/pybind11/tests:test_methods_and_attributes
//third_party/pybind11/tests:test_modules
//third_party/pybind11/tests:test_multiple_inheritance
//third_party/pybind11/tests:test_numpy_array
//third_party/pybind11/tests:test_numpy_dtypes
//third_party/pybind11/tests:test_numpy_vectorize
//third_party/pybind11/tests:test_operator_overloading
//third_party/pybind11/tests:test_pickling
//third_party/pybind11/tests:test_python_multiple_inheritance
//third_party/pybind11/tests:test_pytypes
//third_party/pybind11/tests:test_sequences_and_iterators
//third_party/pybind11/tests:test_thread
//third_party/pybind11/tests:test_type_caster_pyobject_ptr
//third_party/pybind11/tests:test_union
//third_party/pybind11/tests:test_unnamed_namespace_a
//third_party/pybind11/tests:test_unnamed_namespace_b
//third_party/pybind11/tests:test_virtual_functions
//third_party/pybind11/tests:test_wip
Tracking progress:
test_factory_constructors builds and runs successfully WITH ALL BAKEIN_BREAK removed.
This is still the same:
https://github.com/pybind/pybind11/pull/5213#issuecomment-2198362173
Tracking progress (@ commit 66a775eee9b1dff862f5b1af9dd9cdeb423c026c):
All existing tests pass, with just two obvious minor changes to the tests itself:
$ git diff master tests/test_class.cpp tests/test_smart_ptr.py
diff --git a/tests/test_class.cpp b/tests/test_class.cpp
index 9001d86b..e2981aca 100644
--- a/tests/test_class.cpp
+++ b/tests/test_class.cpp
@@ -606,11 +606,15 @@ CHECK_NOALIAS(8);
static_assert(std::is_same<typename DoesntBreak##N::holder_type, \
std::TYPE##_ptr<BreaksBase<(N)>>>::value, \
"DoesntBreak" #N " has wrong holder_type!")
+#define CHECK_SMART_HOLDER(N) \
+ static_assert(std::is_same<typename DoesntBreak##N::holder_type, \
+ pybindit::memory::smart_holder>::value, \
+ "DoesntBreak" #N " has wrong holder_type!")
CHECK_HOLDER(1, unique);
CHECK_HOLDER(2, unique);
CHECK_HOLDER(3, unique);
-CHECK_HOLDER(4, unique);
-CHECK_HOLDER(5, unique);
+CHECK_SMART_HOLDER(4);
+CHECK_SMART_HOLDER(5);
CHECK_HOLDER(6, shared);
CHECK_HOLDER(7, shared);
CHECK_HOLDER(8, shared);
diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py
index bf0ae4ae..0d196199 100644
--- a/tests/test_smart_ptr.py
+++ b/tests/test_smart_ptr.py
@@ -298,6 +298,7 @@ def test_move_only_holder_with_addressof_operator():
def test_smart_ptr_from_default():
+ pytest.skip("BAKEIN_EXPECTED: Failed: DID NOT RAISE <class 'RuntimeError'>")
instance = m.HeldByDefaultHolder()
with pytest.raises(RuntimeError) as excinfo:
m.HeldByDefaultHolder.load_shared_ptr(instance)
Tracking progress (@ commit a332fe8cf4db42435e917a43e82b0775d6a42161):
GHA: all tests pass
ASAN, MSAN testing with Google-internal toolchain: all tests pass
Tracking progress (@ commit 0b7a628a04a11d0c6be4a4e6ed7ea1e9e6d61708):
GHA: all tests pass
ASAN, MSAN testing with Google-internal toolchain: all tests pass
test_class_sh_basic.cpp and test_class_sh_basic.py are identical to the smart_holder branch.
Tracking progress (@ commit 91d40035a2be318f6d33e955c11fe17ac5e1999d):
All smart_holder-specific tests (i.e. tests that only exist on the smart_holder branch, but not on master) were transferred to this PR, except test_exc_namespace_visibility and test_type_caster_odr_guard_[12] (these will not be included here).
Note the BAKEIN_WIP diffs below, which are the only diffs between smart_holder-specific tests.
GHA: all tests pass BUT NOTE that 🐍 3 • GCC 7 • C++17• x64 is disabled
ASAN, MSAN testing with Google-internal toolchain: all tests pass (including test_class_sh_shared_ptr_copy_move.py WITHOUT the diff below).
diff --git a/tests/test_class_sh_disowning.py b/tests/test_class_sh_disowning.py
index 63cdd4ed..195faff9 100644
--- a/tests/test_class_sh_disowning.py
+++ b/tests/test_class_sh_disowning.py
@@ -50,8 +50,10 @@ def test_mixed():
# Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
# both.
is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b))
- assert is_disowned_results.count(True) == 1
- if first_pass:
+ # BAKEIN_WIP: Why is the behavior different?
+ assert is_disowned_results.count(True) == 0
+ # BAKEIN_WIP: Cleanup condition:
+ if first_pass and is_disowned_results.count(True):
first_pass = False
print(
"\nC++ function argument %d is evaluated first."
diff --git a/tests/test_class_sh_property.cpp b/tests/test_class_sh_property.cpp
index 35615cec..a553935b 100644
--- a/tests/test_class_sh_property.cpp
+++ b/tests/test_class_sh_property.cpp
@@ -73,9 +73,9 @@ TEST_SUBMODULE(class_sh_property, m) {
.def_readwrite("m_cptr_readwrite", &Outer::m_cptr)
// .def_readonly("m_uqmp_readonly", &Outer::m_uqmp) // Custom compilation Error.
- .def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp)
+ // BAKEIN_BREAK .def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp)
// .def_readonly("m_uqcp_readonly", &Outer::m_uqcp) // Custom compilation Error.
- .def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp)
+ // BAKEIN_BREAK .def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp)
.def_readwrite("m_shmp_readonly", &Outer::m_shmp)
.def_readwrite("m_shmp_readwrite", &Outer::m_shmp)
diff --git a/tests/test_class_sh_property.py b/tests/test_class_sh_property.py
index 9aeef44e..a2530247 100644
--- a/tests/test_class_sh_property.py
+++ b/tests/test_class_sh_property.py
@@ -16,6 +16,7 @@ def test_valu_getter(m_attr):
outer = m.Outer()
field = getattr(outer, m_attr)
assert field.num == -99
+ pytest.skip("BAKEIN_BREAK: Failed: DID NOT RAISE <class 'ValueError'>")
with pytest.raises(ValueError) as excinfo:
m.DisownOuter(outer)
assert str(excinfo.value) == "Cannot disown use_count != 1 (loaded_as_unique_ptr)."
@@ -83,6 +84,7 @@ def test_ptr(field_type, num_default, outer_type, m_attr, r_kind):
@pytest.mark.parametrize("m_attr_readwrite", ["m_uqmp_readwrite", "m_uqcp_readwrite"])
def test_uqp(m_attr_readwrite):
+ pytest.skip(f"BAKEIN_BREAK: {m_attr_readwrite} does not build")
outer = m.Outer()
assert getattr(outer, m_attr_readwrite) is None
field_orig = m.Field()
@@ -135,6 +137,7 @@ def _proxy_dereference(proxy, xxxattr, *args, **kwargs):
@pytest.mark.parametrize("m_attr", ["m_uqmp", "m_uqcp"])
def test_unique_ptr_field_proxy_poc(m_attr):
m_attr_readwrite = m_attr + "_readwrite"
+ pytest.skip(f"BAKEIN_BREAK: {m_attr_readwrite} does not build")
outer = m.Outer()
field_orig = m.Field()
field_orig.num = 45
diff --git a/tests/test_class_sh_property_non_owning.cpp b/tests/test_class_sh_property_non_owning.cpp
index cd7fc5c6..5591fa96 100644
--- a/tests/test_class_sh_property_non_owning.cpp
+++ b/tests/test_class_sh_property_non_owning.cpp
@@ -60,7 +60,8 @@ TEST_SUBMODULE(class_sh_property_non_owning, m) {
.def_readwrite("core_fld_shared_ptr_rw", &DataField::core_fld_shared_ptr)
.def_readonly("core_fld_raw_ptr_ro", &DataField::core_fld_raw_ptr)
.def_readwrite("core_fld_raw_ptr_rw", &DataField::core_fld_raw_ptr)
- .def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr);
+ // BAKEIN_BREAK .def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr)
+ ;
py::classh<DataFieldsHolder>(m, "DataFieldsHolder")
.def(py::init<std::size_t>())
diff --git a/tests/test_class_sh_property_non_owning.py b/tests/test_class_sh_property_non_owning.py
index 33a9d450..7e38684a 100644
--- a/tests/test_class_sh_property_non_owning.py
+++ b/tests/test_class_sh_property_non_owning.py
@@ -15,7 +15,7 @@ from pybind11_tests import class_sh_property_non_owning as m
("core_fld_shared_ptr_rw", (14, 25)),
("core_fld_raw_ptr_ro", (14, 25)),
("core_fld_raw_ptr_rw", (14, 25)),
- ("core_fld_unique_ptr_rw", (15, 26)),
+ # BAKEIN_BREAK ("core_fld_unique_ptr_rw", (15, 26)),
],
)
def test_core_fld_common(core_fld, expected, persistent_holder):
diff --git a/tests/test_class_sh_shared_ptr_copy_move.py b/tests/test_class_sh_shared_ptr_copy_move.py
index 067bb47d..cfefe9fd 100644
--- a/tests/test_class_sh_shared_ptr_copy_move.py
+++ b/tests/test_class_sh_shared_ptr_copy_move.py
@@ -1,7 +1,13 @@
from __future__ import annotations
+import pytest
+
from pybind11_tests import class_sh_shared_ptr_copy_move as m
+pytest.skip(
+ "BAKEIN_BREAK: Windows fatal exception: access violation", allow_module_level=True
+)
+
def test_shptr_copy():
txt = m.test_ShPtr_copy()[0].get_history()
Keeping track of the
failure for easy future reference:
Sun, 07 Jul 2024 17:26:09 GMT ...
...
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-3.10.1, py-1.7.0, pluggy-0.8.0
C++ Info: 7.5.0 C++17 __pybind11_internals_v4_gcc_libstdcpp_cxxabi1011__ PYBIND11_SIMPLE_GIL_MANAGEMENT=False PYBIND11_NUMPY_1_ONLY=False
rootdir: /__w/pybind11/pybind11/tests, inifile: pytest.ini
collected 1033 items / 1 skipped
../../tests/test_async.py .. [ 0%]
../../tests/test_buffers.py .......................... [ 2%]
../../tests/test_builtin_casters.py .................... [ 4%]
../../tests/test_call_policies.py ........ [ 5%]
../../tests/test_callbacks.py .............s. [ 6%]
../../tests/test_chrono.py ........................................... [ 11%]
../../tests/test_class.py .................................... [ 14%]
../../tests/test_class_sh_basic.py ..................................... [ 18%]
s........ [ 18%]
../../tests/test_class_sh_disowning.py ... [ 19%]
../../tests/test_class_sh_disowning_mi.py ......................... [ 21%]
../../tests/test_class_sh_factory_constructors.py ...... [ 22%]
../../tests/test_class_sh_inheritance.py ....... [ 22%]
../../tests/test_class_sh_mi_thunks.py .......... [ 23%]
../../tests/test_class_sh_trampoline_basic.py ..... [ 24%]
../../tests/test_class_sh_trampoline_self_life_support.py ..... [ 24%]
../../tests/test_class_sh_trampoline_shared_from_this.py .............. [ 26%]
../../tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py ........ [ 27%]
../../tests/test_class_sh_trampoline_unique_ptr.py .. [ 27%]
../../tests/test_class_sh_unique_ptr_custom_deleter.py . [ 27%]
Segmentation fault (core dumped)
make[3]: *** [tests/CMakeFiles/pytest.dir/build.make:74: tests/CMakeFiles/pytest] Error 139
make[2]: *** [CMakeFiles/Makefile2:404: tests/CMakeFiles/pytest.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:411: tests/CMakeFiles/pytest.dir/rule] Error 2
../../tests/test_class_sh_unique_ptr_member.py .make[3]: Leaving directory '/__w/pybind11/pybind11/build'
make[2]: Leaving directory '/__w/pybind11/pybind11/build'
make[1]: Leaving directory '/__w/pybind11/pybind11/build'
make: *** [Makefile:248: pytest] Error 2
Keeping track of the 17 jobs failing before the module-level pytest.skip() in test_class_sh_shared_ptr_copy_move.py was added:
NOTE that [🐍 3 • GCC 7 • C++17• x64] also failed with
Sun, 07 Jul 2024 16:55:30 GMT ...
...
../../tests/test_class_sh_shared_ptr_copy_move.py make[3]: Leaving directory '/__w/pybind11/pybind11/build'
make[2]: Leaving directory '/__w/pybind11/pybind11/build'
Segmentation fault (core dumped)
🐍 3 • Clang 3.7 • C++11 • x64 Process completed with exit code 2. 🐍 3 • Clang 5 • C++14 • x64 Process completed with exit code 2. 🐍 3 • almalinux:8 • x64 Process completed with exit code 2. 🐍 3.8 • ubuntu-20.04 • x64 -DPYBIND11_FINDPYTHON=ON -DCMAKE_CXX_FLAGS="-D_=1" Process completed with exit code 2. 🐍 3 • GCC 8 • C++17• x64 Process completed with exit code 2. 🐍 3.13 • ubuntu-20.04 • x64 Process completed with exit code 2. 🐍 3.9 • MSVC 2022 C++20 • x64 Process completed with exit code 1. 🐍 3 • GCC 7 • C++17• x64 Process completed with exit code 2. 🐍 3.9 • windows-2019 • x64 Process completed with exit code 1. 🐍 3.8 • windows-2022 • x64 Process completed with exit code 1. 🐍 pypy-3.8 • windows-2022 • x64 Process completed with exit code 1. 🐍 3 • windows-latest • mingw64 Process completed with exit code 2. 🐍 3 • ICC latest • x64 Process completed with exit code 2. 🐍 3.9 • ubuntu-20.04 • x64 Process completed with exit code 2. 🐍 3.9 • windows-2022 • x64 Process completed with exit code 1. 🐍 3.8 • macos-13 • x64 Process completed with exit code 2. 🐍 pypy-3.9 • windows-2022 • x64 Process completed with exit code 1.
Regarding commit 127058cb16961526d185b6600511854723651ad5, the key observation leading to that bug fix was this:
< LOOOK DEREGISTER_INSTANCE START(PTR=AAA=, pytype=pybind11_tests.ccccccccccccccccccccc.D, cpptype=pybind11_tests::ccccccccccccccccccccc::D)
---
> LOOOK DEREGISTER_INSTANCE START(PTR=AAA=, pytype=pybind11_tests.ccccccccccccccccccccc.D, cpptype=pybind11_tests::ccccccccccccccccccccc::B)
Note cpptype D (correct) vs B (incorrect).
The correct line is from running test_ ccccccccccccccccccccc with the smart_holder branch, the incorrect line this PR before commit 127058cb16961526d185b6600511854723651ad5.
Milestone reached at commit ed27c37c4e064216aa2ceae64d1fe7d8e1e75a09:
-
Testing via GHA passes:
- All existing tests under master, with the only minor exceptions as noted previously under https://github.com/pybind/pybind11/pull/5213#issuecomment-2204180286.
- All** existing tests under the smart_holder branch, exactly as-is. (** 2 tests were intentionally not transferred to this PR: test_exc_namespace_visibility, test_type_caster_odr_guard_1)
-
Google-global testing, with several minor Google-internal adjustments outside the pybind11 source tree.
NOTE: smart_holder is still the default holder. — This is mainly intended to be a stress test.
The Google-global testing (internal ID: OCL:652909258:BASE:653492046:1721283711718:c0b9e029) identified only 3 use cases that break with smart_holder as the holder. These cases were fixed by explicitly requesting std::unique_ptr as the holder:
- tensorflow/python/framework/experimental/: explicit
std::unique_ptrholder forpy::class_<TapeContext, ...>andpy::class_<TracingContext, ...> - tensorflow/python/client/tf_session_wrapper.cc: explicit
std::unique_ptrholder forpy::class_<TF_Session> - nle/win/rl/pynethack.cc: explicit
std::unique_ptrholder for twopy::class_usingpy::detail::is_new_style_constructor()
Closing, as explained in the PR description.
All checks have passed: 2 skipped and 155 successful checks
CI — https://github.com/pybind/pybind11/actions/runs/10038035526
CI-SH-DEF — https://github.com/pybind/pybind11/actions/runs/10038035539