diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c34a566..9b485195 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -312,11 +312,6 @@ jobs: fail-fast: false matrix: clang: - - 3.6 - - 3.7 - - 3.9 - - 7 - - 9 - dev std: - 11 @@ -325,8 +320,6 @@ jobs: include: - clang: 5 std: 14 - - clang: 10 - std: 17 - clang: 11 std: 20 - clang: 12 @@ -504,10 +497,6 @@ jobs: fail-fast: false matrix: include: - - { gcc: 7, std: 11 } - - { gcc: 7, std: 17 } - - { gcc: 8, std: 14 } - - { gcc: 8, std: 17 } - { gcc: 9, std: 20 } - { gcc: 10, std: 17 } - { gcc: 10, std: 20 } @@ -728,9 +717,9 @@ jobs: # This tests an "install" with the CMake tools install-classic: - name: "🐍 3.7 β€’ Debian β€’ x86 β€’ Install" + name: "🐍 3.9 β€’ Debian β€’ x86 β€’ Install" runs-on: ubuntu-latest - container: i386/debian:buster + container: i386/debian:bullseye steps: - uses: actions/checkout@v1 # v1 is required to run inside docker @@ -810,7 +799,6 @@ jobs: fail-fast: false matrix: python: - - '3.7' - '3.8' - '3.9' - '3.10' @@ -828,8 +816,6 @@ jobs: args: -DCMAKE_CXX_STANDARD=20 - python: '3.8' args: -DCMAKE_CXX_STANDARD=17 - - python: '3.7' - args: -DCMAKE_CXX_STANDARD=14 name: "🐍 ${{ matrix.python }} β€’ MSVC 2019 β€’ x86 ${{ matrix.args }}" diff --git a/.github/workflows/ci_sh_def.yml b/.github/workflows/ci_sh_def.yml index f37f7516..68a73985 100644 --- a/.github/workflows/ci_sh_def.yml +++ b/.github/workflows/ci_sh_def.yml @@ -329,11 +329,6 @@ jobs: fail-fast: false matrix: clang: - - 3.6 - - 3.7 - - 3.9 - - 7 - - 9 - dev std: - 11 @@ -342,8 +337,6 @@ jobs: include: - clang: 5 std: 14 - - clang: 10 - std: 17 - clang: 11 std: 20 - clang: 12 @@ -522,10 +515,6 @@ jobs: fail-fast: false matrix: include: - - { gcc: 7, std: 11 } - - { gcc: 7, std: 17 } - - { gcc: 8, std: 14 } - - { gcc: 8, std: 17 } - { gcc: 9, std: 20 } - { gcc: 10, std: 17 } - { gcc: 10, std: 20 } @@ -751,9 +740,9 @@ jobs: # This tests an "install" with the CMake tools install-classic: - name: "🐍 3.7 β€’ Debian β€’ x86 β€’ Install" + name: "🐍 3.9 β€’ Debian β€’ x86 β€’ Install" runs-on: ubuntu-latest - container: i386/debian:buster + container: i386/debian:bullseye steps: - uses: actions/checkout@v1 # v1 is required to run inside docker @@ -834,7 +823,6 @@ jobs: fail-fast: false matrix: python: - - '3.7' - '3.8' - '3.9' - '3.10' @@ -852,8 +840,6 @@ jobs: args: -DCMAKE_CXX_STANDARD=20 - python: '3.8' args: -DCMAKE_CXX_STANDARD=17 - - python: '3.7' - args: -DCMAKE_CXX_STANDARD=14 name: "🐍 ${{ matrix.python }} β€’ MSVC 2019 β€’ x86 ${{ matrix.args }}" diff --git a/.github/workflows/ci_sh_def.yml.patch b/.github/workflows/ci_sh_def.yml.patch index 456ca736..ecfd7639 100644 --- a/.github/workflows/ci_sh_def.yml.patch +++ b/.github/workflows/ci_sh_def.yml.patch @@ -1,5 +1,5 @@ ---- ci.yml 2024-07-29 11:18:11.967568957 -0700 -+++ ci_sh_def.yml 2024-07-29 11:18:42.087538968 -0700 +--- ci.yml 2024-07-30 11:20:28.997003056 -0700 ++++ ci_sh_def.yml 2024-07-30 11:21:39.724969167 -0700 @@ -1,4 +1,16 @@ -name: CI +# PLEASE KEEP THIS GROUP OF FILES IN SYNC AT ALL TIMES: @@ -67,7 +67,7 @@ - name: Build run: cmake --build build -j 2 -@@ -364,6 +381,7 @@ +@@ -357,6 +374,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -75,7 +75,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -393,7 +411,7 @@ +@@ -386,7 +404,7 @@ run: apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y cmake git python3-dev python3-pytest python3-numpy - name: Configure @@ -84,7 +84,7 @@ - name: Build run: cmake --build build -j2 --verbose -@@ -481,7 +499,7 @@ +@@ -474,7 +492,7 @@ cmake -S . -B build -DDOWNLOAD_CATCH=ON \ -DCMAKE_CXX_STANDARD=17 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ @@ -93,7 +93,7 @@ -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - name: Build -@@ -537,6 +555,7 @@ +@@ -526,6 +544,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -101,7 +101,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -559,6 +578,7 @@ +@@ -548,6 +567,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DCMAKE_CXX_STANDARD=${{ matrix.std }} @@ -109,7 +109,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" -@@ -608,6 +628,7 @@ +@@ -597,6 +617,7 @@ -DDOWNLOAD_CATCH=ON \ -DDOWNLOAD_EIGEN=OFF \ -DCMAKE_CXX_STANDARD=11 \ @@ -117,7 +117,7 @@ -DCMAKE_CXX_COMPILER=$(which icpc) \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -@@ -640,6 +661,7 @@ +@@ -629,6 +650,7 @@ -DDOWNLOAD_CATCH=ON \ -DDOWNLOAD_EIGEN=OFF \ -DCMAKE_CXX_STANDARD=17 \ @@ -125,7 +125,7 @@ -DCMAKE_CXX_COMPILER=$(which icpc) \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") -@@ -711,6 +733,7 @@ +@@ -700,6 +722,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -133,7 +133,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -761,6 +784,7 @@ +@@ -750,6 +773,7 @@ cmake ../pybind11-tests -DDOWNLOAD_CATCH=ON -DPYBIND11_WERROR=ON @@ -141,7 +141,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") working-directory: /build-tests -@@ -864,6 +888,7 @@ +@@ -850,6 +874,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -149,7 +149,7 @@ ${{ matrix.args }} - name: Build C++11 run: cmake --build build -j 2 -@@ -918,6 +943,7 @@ +@@ -904,6 +929,7 @@ -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON @@ -157,7 +157,7 @@ ${{ matrix.args }} - name: Build C++11 run: cmake --build build --config Debug -j 2 -@@ -960,6 +986,7 @@ +@@ -946,6 +972,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -165,7 +165,7 @@ - name: Build C++20 run: cmake --build build -j 2 -@@ -980,6 +1007,7 @@ +@@ -966,6 +993,7 @@ -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=20 @@ -173,7 +173,7 @@ "-DPYBIND11_TEST_OVERRIDE=test_call_policies.cpp;test_gil_scoped.cpp;test_thread.cpp" - name: Build C++20 - Exercise cmake -DPYBIND11_TEST_OVERRIDE -@@ -1032,6 +1060,7 @@ +@@ -1018,6 +1046,7 @@ run: >- cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") @@ -181,7 +181,7 @@ -S . -B build - name: Build C++11 -@@ -1053,6 +1082,7 @@ +@@ -1039,6 +1068,7 @@ run: >- cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") @@ -189,7 +189,7 @@ -S . -B build2 - name: Build C++14 -@@ -1074,6 +1104,7 @@ +@@ -1060,6 +1090,7 @@ run: >- cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") @@ -197,7 +197,7 @@ -S . -B build3 - name: Build C++17 -@@ -1141,6 +1172,7 @@ +@@ -1127,6 +1158,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 @@ -205,7 +205,7 @@ - name: Build run: cmake --build . -j 2 -@@ -1206,6 +1238,7 @@ +@@ -1192,6 +1224,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 @@ -213,7 +213,7 @@ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build -@@ -1229,6 +1262,7 @@ +@@ -1215,6 +1248,7 @@ -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17 diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index ac3900c3..8242db7b 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -40,22 +40,22 @@ jobs: - runs-on: macos-13 arch: x64 - cmake: "3.7" + cmake: "3.8" - runs-on: windows-2019 arch: x64 # x86 compilers seem to be missing on 2019 image cmake: "3.18" - name: 🐍 3.7 β€’ CMake ${{ matrix.cmake }} β€’ ${{ matrix.runs-on }} + name: 🐍 3.8 β€’ CMake ${{ matrix.cmake }} β€’ ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v4 - - name: Setup Python 3.7 + - name: Setup Python 3.8 uses: actions/setup-python@v5 with: - python-version: 3.7 + python-version: 3.8 architecture: ${{ matrix.arch }} - name: Prepare env diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml index 4f12e81c..cbd7f5d5 100644 --- a/.github/workflows/emscripten.yaml +++ b/.github/workflows/emscripten.yaml @@ -20,7 +20,7 @@ jobs: submodules: true fetch-depth: 0 - - uses: pypa/cibuildwheel@v2.19 + - uses: pypa/cibuildwheel@v2.20 env: PYODIDE_BUILD_EXPORTS: whole_archive CFLAGS: -fexceptions diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8961a777..4f92f83a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.5.6 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -40,7 +40,7 @@ repos: # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.10.1" + rev: "v1.11.1" hooks: - id: mypy args: [] @@ -144,14 +144,14 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.2.4" + rev: "v3.2.6" hooks: - id: pylint files: ^pybind11 # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.6 + rev: 0.29.1 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/CMakeLists.txt b/CMakeLists.txt index fb82639d..f55c0cb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,11 +156,11 @@ set(PYBIND11_HEADERS include/pybind11/detail/internals.h include/pybind11/detail/native_enum_data.h include/pybind11/detail/smart_holder_poc.h - include/pybind11/detail/smart_holder_sfinae_hooks_only.h - include/pybind11/detail/smart_holder_type_casters.h + include/pybind11/detail/try_as_void_ptr_capsule_get_pointer.h include/pybind11/detail/type_caster_base.h include/pybind11/detail/type_caster_odr_guard.h include/pybind11/detail/typeid.h + include/pybind11/detail/using_smart_holder.h include/pybind11/detail/value_and_holder.h include/pybind11/attr.h include/pybind11/buffer_info.h diff --git a/README.rst b/README.rst index 49c62dfe..f554ae9d 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,7 @@ dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~4K -lines of code and depend on Python (3.7+, or PyPy) and the C++ +lines of code and depend on Python (3.8+, or PyPy) and the C++ standard library. This compact implementation was possible thanks to some C++11 language features (specifically: tuples, lambda functions and variadic templates). Since its creation, this library has grown beyond @@ -112,7 +112,7 @@ Goodies In addition to the core functionality, pybind11 provides some extra goodies: -- Python 3.7+, and PyPy3 7.3 are supported with an implementation-agnostic +- Python 3.8+, and PyPy3 7.3 are supported with an implementation-agnostic interface (pybind11 2.9 was the last version to support Python 2 and 3.5). - It is possible to bind C++11 lambda functions with captured diff --git a/README_smart_holder.rst b/README_smart_holder.rst index 7943d5c6..05b1ab56 100644 --- a/README_smart_holder.rst +++ b/README_smart_holder.rst @@ -6,58 +6,71 @@ pybind11 β€” smart_holder branch Overview ======== -- The smart_holder git branch is a strict superset of the master - branch. Everything that works on master is expected to work exactly the same - with the smart_holder branch. - -- **Smart-pointer interoperability** (``std::unique_ptr``, ``std::shared_ptr``) - is implemented as an **add-on**. - -- The add-on also supports - * passing a Python object back to C++ via ``std::unique_ptr``, safely - **disowning** the Python object. - * safely passing `"trampoline" - `_ - objects (objects with C++ virtual function overrides implemented in - Python) via ``std::unique_ptr`` or ``std::shared_ptr`` back to C++: - associated Python objects are automatically kept alive for the lifetime - of the smart-pointer. - -- The smart_holder branch can be used in two modes: - * **Conservative mode**: ``py::class_`` works exactly as on master. - ``py::classh`` uses ``py::smart_holder``. - * **Progressive mode**: ``py::class_`` uses ``py::smart_holder`` - (i.e. ``py::smart_holder`` is the default holder). +- The smart_holder branch is a strict superset of the pybind11 master branch. + Everything that works with the master branch is expected to work exactly the + same with the smart_holder branch. + +- Activating the smart_holder functionality for a given C++ type ``T`` is as + easy as changing ``py::class_`` to ``py::classh`` in client code. + +- The ``py::classh`` functionality includes + + * support for **two-way** Python/C++ conversions for both + ``std::unique_ptr`` and ``std::shared_ptr`` **simultaneously**. + β€” In contrast, ``py::class_`` only supports one-way C++-to-Python + conversions for ``std::unique_ptr``, or alternatively two-way + Python/C++ conversions for ``std::shared_ptr``, which then excludes + the one-way C++-to-Python ``std::unique_ptr`` conversions (this + manifests itself through undefined runtime behavior). + + * passing a Python object back to C++ via ``std::unique_ptr``, safely + **disowning** the Python object. + + * safely passing `"trampoline" + `_ + objects (objects with C++ virtual function overrides implemented in + Python) via ``std::unique_ptr`` or ``std::shared_ptr`` back to C++: + associated Python objects are automatically kept alive for the lifetime + of the smart-pointer. + +Note: As of `PR #5257 `_ +the smart_holder functionality is fully baked into pybind11. +Prior to PR #5257 the smart_holder implementation was an "add-on", which made +it necessary to use a ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. This macro +still exists for backward compatibility, but is now a no-op. The trade-off +for this convenience is that the ``PYBIND11_INTERNALS_VERSION`` needed to be +changed. Consequently, Python extension modules built with the smart_holder +branch no longer interoperate with extension modules built with the pybind11 +master branch. If cross-extension-module interoperability is required, all +extension modules involved must be built with the smart_holder branch. +β€” Probably, most extension modules do not require cross-extension-module +interoperability, but exceptions to this are quite common. What is fundamentally different? -------------------------------- - Classic pybind11 has the concept of "smart-pointer is holder". - Interoperability between smart-pointers is completely missing. For - example, when using ``std::shared_ptr`` as holder, ``return``-ing - a ``std::unique_ptr`` leads to undefined runtime behavior - (`#1138 `_). A - `systematic analysis is here `_. + Interoperability between smart-pointers is completely missing. For example, + with ``py::class_>``, ``return``-ing a + ``std::unique_ptr`` leads to undefined runtime behavior + (`#1138 `_). + A `systematic analysis can be found here + `_. - ``py::smart_holder`` has a richer concept in comparison, with well-defined - runtime behavior. The holder "knows" about both ``std::unique_ptr`` and - ``std::shared_ptr`` and how they interoperate. - -- Caveat (#HelpAppreciated): currently the ``smart_holder`` branch does - not have a well-lit path for including interoperability with custom - smart-pointers. It is expected to be a fairly obvious extension of the - ``smart_holder`` implementation, but will depend on the exact specifications - of each custom smart-pointer type (generalizations are very likely possible). + runtime behavior in all situations. ``py::smart_holder`` "knows" about both + ``std::unique_ptr`` and ``std::shared_ptr``, and how they interoperate. What motivated the development of the smart_holder code? -------------------------------------------------------- -- Necessity is the mother. The bigger context is the ongoing retooling of - `PyCLIF `_, to use pybind11 underneath - instead of directly targeting the Python C API. Essentially, the smart_holder - branch is porting established PyCLIF functionality into pybind11. +- The original context was retooling of `PyCLIF + `_, to use pybind11 underneath, + instead of directly targeting the Python C API. Essentially the smart_holder + branch is porting established PyCLIF functionality into pybind11. (However, + this work also led to bug fixes in PyCLIF.) Installation @@ -72,225 +85,6 @@ Currently ``git clone`` is the only option. We do not have released packages. Everything else is exactly identical to using the default (master) branch. -Conservative or Progressive mode? -================================= - -It depends. To a first approximation, for a stand-alone, new project, the -Progressive mode will be easiest to use. For larger projects or projects -that integrate with third-party pybind11-based projects, the Conservative -mode may be more practical, at least initially, although it comes with the -disadvantage of having to use the ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. - - -Conservative mode ------------------ - -Here is a minimal example for wrapping a C++ type with ``py::smart_holder`` as -holder: - -.. code-block:: cpp - - #include - - struct Foo {}; - - PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo) - - PYBIND11_MODULE(example_bindings, m) { - namespace py = pybind11; - py::classh(m, "Foo"); - } - -There are three small differences compared to Classic pybind11: - -- ``#include `` is used instead of - ``#include ``. - -- The ``PYBIND11_SMART_HOLDER_TYPE_CASTERS(Foo)`` macro is needed. - β€” NOTE: This macro needs to be in the global namespace. - -- ``py::classh`` is used instead of ``py::class_``. - -To the 2nd bullet point, the ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro -needs to appear in all translation units with pybind11 bindings that involve -Python⇄C++ conversions for ``Foo``. This is the biggest inconvenience of the -Conservative mode. Practically, at a larger scale it is best to work with a -pair of ``.h`` and ``.cpp`` files for the bindings code, with the macros in -the ``.h`` files. - -To the 3rd bullet point, ``py::classh`` is simply a shortcut for -``py::class_``. The shortcut makes it possible to -switch to using ``py::smart_holder`` without disturbing the indentation of -existing code. - -When migrating code that uses ``py::class_>`` -there are two alternatives. The first one is to use ``py::classh``: - -.. code-block:: diff - - - py::class_>(m, "Bar"); - + py::classh(m, "Bar"); - -This is clean and simple, but makes it difficult to fall back to Classic -mode if needed. The second alternative is to replace ``std::shared_ptr`` -with ``PYBIND11_SH_AVL(Bar)``: - -.. code-block:: diff - - - py::class_>(m, "Bar"); - + py::class_(m, "Bar"); - -The ``PYBIND11_SH_AVL`` macro substitutes ``py::smart_holder`` -in Conservative mode, or ``std::shared_ptr`` in Classic mode. -See tests/test_classh_mock.cpp for an example. Note that the macro is also -designed to not disturb the indentation of existing code. - - -Progressive mode ----------------- - -To work in Progressive mode: - -- Add ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` to the compilation commands. - -- Remove or replace (see below) ``std::shared_ptr<...>`` holders. - -- Only if custom smart-pointers are used: the - ``PYBIND11_TYPE_CASTER_BASE_HOLDER`` macro is needed (see - tests/test_smart_ptr.cpp for examples). - -Overall this is probably easier to work with than the Conservative mode, but - -- the macro inconvenience is shifted from ``py::smart_holder`` to custom - smart-pointer holders (which are probably much more rare). - -- it will not interoperate with other extensions built against master or - stable, or extensions built in Conservative mode (see the cross-module - compatibility section below). - -When migrating code that uses ``py::class_>`` there -are the same alternatives as for the Conservative mode (see previous section). -An additional alternative is to use the ``PYBIND11_SH_DEF(...)`` macro: - -.. code-block:: diff - - - py::class_>(m, "Bar"); - + py::class_(m, "Bar"); - -The ``PYBIND11_SH_DEF`` macro substitutes ``py::smart_holder`` only in -Progressive mode, or ``std::shared_ptr`` in Classic or Conservative -mode. See tests/test_classh_mock.cpp for an example. Note that the -``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro is never needed in combination -with the ``PYBIND11_SH_DEF`` macro, which is an advantage compared to the -``PYBIND11_SH_AVL`` macro. Please review tests/test_classh_mock.cpp for a -concise overview of all available options. - - -Transition from Classic to Progressive mode -------------------------------------------- - -This still has to be tried out more in practice, but in small-scale situations -it may be feasible to switch directly to Progressive mode in a break-fix -fashion. In large-scale situations it seems more likely that an incremental -approach is needed, which could mean incrementally converting ``py::class_`` -to ``py::classh`` and using the family of related macros, then flip the switch -to Progressive mode, and convert ``py::classh`` back to ``py:class_`` combined -with removal of the macros if desired (at that point it will work equivalently -either way). It may be smart to delay the final cleanup step until all -third-party projects of interest have made the switch, because then the code -will continue to work in all modes. - - -Using py::smart_holder but with fallback to Classic pybind11 ------------------------------------------------------------- - -For situations in which compatibility with Classic pybind11 -(without smart_holder) is needed for some period of time, fallback -to Classic mode can be enabled by copying the ``BOILERPLATE`` code -block from tests/test_classh_mock.cpp. This code block provides mock -implementations of ``py::classh`` and the family of related macros -(e.g. ``PYBIND11_SMART_HOLDER_TYPE_CASTERS``). - - -Classic / Conservative / Progressive cross-module compatibility ---------------------------------------------------------------- - -Currently there are essentially three modes for building a pybind11 extension -module: - -- Classic: pybind11 stable (e.g. v2.6.2) or current master branch. - -- Conservative: pybind11 smart_holder branch. - -- Progressive: pybind11 smart_holder branch with - ``-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT``. - -In environments that mix extension modules built with different modes, -this is the compatibility matrix for ``py::class_``-wrapped types: - -.. list-table:: Compatibility matrix - :widths: auto - :header-rows: 2 - - * - - - - - - - Module 2 - - - * - - - - - Classic - - Conservative - - Progressive - * - - - **Classic** - - full - - one-and-a-half-way - - isolated - * - **Module 1** - - **Conservative** - - one-and-a-half-way - - full - - isolated - * - - - **Progressive** - - isolated - - isolated - - full - -Mixing Classic+Progressive or Conservative+Progressive is very easy to -understand: the extension modules are essentially completely isolated from -each other. This is in fact just the same as using pybind11 versions with -differing `"internals version" -`_ -in the past. While this is easy to understand, there is also no incremental -transition path between Classic and Progressive. - -The Conservative mode enables incremental transitions, but at the cost of -more complexity. Types wrapped in a Classic module are fully compatible with -a Conservative module. However, a type wrapped in a Conservative module is -compatible with a Classic module only if ``py::smart_holder`` is **not** used -(for that type). A type wrapped with ``py::smart_holder`` is incompatible with -a Classic module. This is an important pitfall to keep in mind: attempts to use -``py::smart_holder``-wrapped types in a Classic module will lead to undefined -runtime behavior, such as a SEGFAULT. This is a more general flavor of the -long-standing issue `#1138 `_, -often referred to as "holder mismatch". It is important to note that the -pybind11 smart_holder branch solves the smart-pointer interoperability issue, -but not the more general holder mismatch issue. β€” Unfortunately the existing -pybind11 internals do not track holder runtime type information, therefore -the holder mismatch issue cannot be solved in a fashion that would allow -an incremental transition, which is the whole point of the Conservative -mode. Please proceed with caution. (See `PR #2644 -`_ for background, which is -labeled with "abi break".) - -Another pitfall worth pointing out specifically, although it follows -from the previous: mixing base and derived classes between Classic and -Conservative modules means that neither the base nor the derived class can -use ``py::smart_holder``. - - Trampolines and std::unique_ptr ------------------------------- @@ -307,37 +101,10 @@ inherit from ``py::trampoline_self_life_support``, for example: ... }; -This is the only difference compared to Classic pybind11. A fairly +This is the only difference compared to classic pybind11. A fairly minimal but complete example is tests/test_class_sh_trampoline_unique_ptr.cpp. -Ideas for the long-term ------------------------ - -The macros are clearly an inconvenience in many situations. Highly -speculative: to avoid the need for the macros, a potential approach would -be to combine the Classic implementation (``type_caster_base``) with -the ``smart_holder_type_caster``, but this will probably be very messy and -not great as a long-term solution. The ``type_caster_base`` code is very -complex already. A more maintainable approach long-term could be to work -out and document a smart_holder-based solution for custom smart-pointers -in pybind11 version ``N``, then purge ``type_caster_base`` in version -``N+1``. #HelpAppreciated. - - -Testing of PRs against the smart_holder branch ----------------------------------------------- - -In the pybind11 GitHub Actions, PRs against the smart_holder branch are -automatically tested in both modes (Conservative, Progressive), with the -only difference that ``PYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` is defined -for Progressive mode testing. - -For interactive testing, the ``PYBIND11_USE_SMART_HOLDER_AS_DEFAULT`` -define needs to be manually added to the cmake command. See -.github/workflows/ci_sh.yml for examples. - - Related links ============= @@ -353,4 +120,4 @@ Related links * Small `slide deck `_ presented in meeting with pybind11 maintainers on Feb 22, 2021. Slides 5 - and 6 show performance comparisons. + and 6 show performance comparisons. (These are outdated but probably not far off.) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 01a490b7..1e221072 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -826,8 +826,7 @@ An instance can now be pickled as follows: always use the latest available version. Beware: failure to follow these instructions will cause important pybind11 memory allocation routines to be skipped during unpickling, which will likely lead to memory corruption - and/or segmentation faults. Python defaults to version 3 (Python 3-3.7) and - version 4 for Python 3.8+. + and/or segmentation faults. .. seealso:: diff --git a/docs/advanced/exceptions.rst b/docs/advanced/exceptions.rst index e20f42b5..8f0e9c93 100644 --- a/docs/advanced/exceptions.rst +++ b/docs/advanced/exceptions.rst @@ -368,8 +368,7 @@ Should they throw or fail to catch any exceptions in their call graph, the C++ runtime calls ``std::terminate()`` to abort immediately. Similarly, Python exceptions raised in a class's ``__del__`` method do not -propagate, but are logged by Python as an unraisable error. In Python 3.8+, a -`system hook is triggered +propagate, but ``sys.unraisablehook()`` `is triggered `_ and an auditing event is logged. diff --git a/docs/compiling.rst b/docs/compiling.rst index 0b7c178b..0c788335 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -25,7 +25,7 @@ A Python extension module can be created with just a few lines of code: find_package(pybind11 CONFIG REQUIRED) pybind11_add_module(example example.cpp) - install(TARGET example DESTINATION .) + install(TARGETS example DESTINATION .) (You use the ``add_subdirectory`` instead, see the example in :ref:`cmake`.) In this example, the code is located in a file named :file:`example.cpp`. Either @@ -426,7 +426,7 @@ with ``PYTHON_EXECUTABLE``. For example: .. code-block:: bash - cmake -DPYBIND11_PYTHON_VERSION=3.7 .. + cmake -DPYBIND11_PYTHON_VERSION=3.8 .. # Another method: cmake -DPYTHON_EXECUTABLE=/path/to/python .. @@ -493,7 +493,7 @@ existing targets instead: cmake_minimum_required(VERSION 3.15...3.22) project(example LANGUAGES CXX) - find_package(Python 3.7 COMPONENTS Interpreter Development REQUIRED) + find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED) find_package(pybind11 CONFIG REQUIRED) # or add_subdirectory(pybind11) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 7d4a2216..6a2d35a8 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -349,6 +349,10 @@ struct type_record { /// Solves pybind/pybind11#1446 bool release_gil_before_calling_cpp_dtor : 1; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + holder_enum_t holder_enum_v = holder_enum_t::undefined; +#endif + PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) { auto *base_info = detail::get_type_info(base, false); if (!base_info) { diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4c97515f..bfc64869 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -13,7 +13,6 @@ #include "detail/common.h" #include "detail/descr.h" #include "detail/native_enum_data.h" -#include "detail/smart_holder_sfinae_hooks_only.h" #include "detail/type_caster_base.h" #include "detail/type_caster_odr_guard.h" #include "detail/typeid.h" @@ -31,23 +30,14 @@ #include #include -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -# include "detail/smart_holder_type_casters.h" -#endif - PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -template -class type_caster_for_class_ : public type_caster_base {}; -#endif - template -class type_caster : public type_caster_for_class_ {}; +class type_caster : public type_caster_base {}; #if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD) @@ -93,7 +83,7 @@ class type_caster_enum_type { if (native_enum) { return native_enum(static_cast(src)).release(); } - return type_caster_for_class_::cast( + return type_caster_base::cast( std::forward(src), // Fixes https://github.com/pybind/pybind11/pull/3643#issuecomment-1022987818: return_value_policy::copy, @@ -115,7 +105,7 @@ class type_caster_enum_type { return true; } if (!pybind11_enum_) { - pybind11_enum_.reset(new type_caster_for_class_()); + pybind11_enum_.reset(new type_caster_base()); } return pybind11_enum_->load(src, convert); } @@ -140,7 +130,7 @@ class type_caster_enum_type { } private: - std::unique_ptr> pybind11_enum_; + std::unique_ptr> pybind11_enum_; EnumType value; }; @@ -157,27 +147,6 @@ template class type_caster::value>> : public type_caster_enum_type {}; -template -struct type_uses_smart_holder_type_caster { - static constexpr bool value - = std::is_base_of>::value -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - || type_uses_type_caster_enum_type::value -#endif - ; -}; - -template -struct type_caster_classh_enum_aware : type_caster {}; - -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -template -struct type_caster_classh_enum_aware< - EnumType, - detail::enable_if_t::value>> - : type_caster_for_class_ {}; -#endif - template ::value, int> = 0> bool isinstance_native_enum_impl(handle obj, const std::type_info &tp) { handle native_enum = global_internals_native_enum_type_map_get_item(tp); @@ -308,7 +277,7 @@ struct type_caster::value && !is_std_char_t } else { handle src_or_index = src; // PyPy: 7.3.7's 3.8 does not implement PyLong_*'s __index__ calls. -#if PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) +#if defined(PYPY_VERSION) object index; if (!PYBIND11_LONG_CHECK(src.ptr())) { // So: index_check(src.ptr()) index = reinterpret_steal(PyNumber_Index(src.ptr())); @@ -911,6 +880,7 @@ struct holder_helper { static auto get(const T &p) -> decltype(p.get()) { return p.get(); } }; +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to shared_ptr specialization. /// Type caster for holder types like std::shared_ptr, etc. /// The SFINAE hook is provided to help work around the current lack of support /// for smart-pointer interoperability. Please consider it an implementation @@ -988,16 +958,169 @@ struct copyable_holder_caster : public type_caster_base { } static bool try_direct_conversions(handle) { return false; } + static bool try_as_void_ptr_capsule(handle) { return false; } holder_type holder; }; -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled : std::true_type {}; + +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor copyable_holder_caster to reduce code duplication. +template +struct copyable_holder_caster< + type, + std::shared_ptr, + enable_if_t::value>> + : public type_caster_base { +public: + using base = type_caster_base; + static_assert(std::is_base_of>::value, + "Holder classes are only supported for custom types"); + using base::base; + using base::cast; + using base::typeinfo; + using base::value; + + bool load(handle src, bool convert) { + return base::template load_impl>>( + src, convert); + } + + explicit operator std::shared_ptr *() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + pybind11_fail("Passing `std::shared_ptr *` from Python to C++ is not supported " + "(inherently unsafe)."); + } + return std::addressof(shared_ptr_holder); + } + + explicit operator std::shared_ptr &() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + if (from_direct_conversion) { + throw cast_error("Unowned pointer from direct conversion cannot be converted to a " + "std::shared_ptr."); + } + if (from_as_void_ptr_capsule) { + throw cast_error("Unowned pointer from a void pointer capsule cannot be converted " + "to a std::shared_ptr."); + } + shared_ptr_holder = sh_load_helper.load_as_shared_ptr(value); + } + return shared_ptr_holder; + } + + static handle + cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { + const auto *ptr = src.get(); + auto st = type_caster_base::src_and_type(ptr); + if (st.second == nullptr) { + return handle(); // no type info: error will be set already + } + if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { + return smart_holder_type_caster_support::smart_holder_from_shared_ptr( + src, policy, parent, st); + } + return type_caster_base::cast_holder(ptr, &src); + } + + // This function will succeed even if the `responsible_parent` does not own the + // wrapped C++ object directly. + // It is the responsibility of the caller to ensure that the `responsible_parent` + // has a `keep_alive` relationship with the owner of the wrapped C++ object, or + // that the wrapped C++ object lives for the duration of the process. + static std::shared_ptr shared_ptr_with_responsible_parent(handle responsible_parent) { + copyable_holder_caster loader; + loader.load(responsible_parent, /*convert=*/false); + assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder); + return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent); + } + +protected: + friend class type_caster_generic; + void check_holder_compat() { + if (typeinfo->default_holder) { + throw cast_error("Unable to load a custom holder type from a default-holder instance"); + } + } + + void load_value(value_and_holder &&v_h) { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = v_h; + value = sh_load_helper.get_void_ptr_or_nullptr(); + return; + } + if (v_h.holder_constructed()) { + value = v_h.value_ptr(); + shared_ptr_holder = v_h.template holder>(); + return; + } + throw cast_error("Unable to cast from non-held to held instance (T& to Holder) " +# if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) + "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for " + "type information)"); +# else + "of type '" + + type_id>() + "''"); +# endif + } + + template , + detail::enable_if_t::value, int> = 0> + bool try_implicit_casts(handle, bool) { + return false; + } + + template , + detail::enable_if_t::value, int> = 0> + bool try_implicit_casts(handle src, bool convert) { + for (auto &cast : typeinfo->implicit_casts) { + copyable_holder_caster sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; + } else { + shared_ptr_holder + = std::shared_ptr(sub_caster.shared_ptr_holder, (type *) value); + } + return true; + } + } + return false; + } + + bool try_direct_conversions(handle src) { + if (type_caster_base::try_direct_conversions(src)) { + from_direct_conversion = true; + return true; + } + return false; + } + + bool try_as_void_ptr_capsule(handle src) { + if (type_caster_base::try_as_void_ptr_capsule(src)) { + from_as_void_ptr_capsule = true; + return true; + } + return false; + } + + std::shared_ptr shared_ptr_holder; + smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl + bool from_direct_conversion = false; + bool from_as_void_ptr_capsule = false; +}; + +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + /// Specialize for the common std::shared_ptr, so users don't need to template class type_caster> : public copyable_holder_caster> {}; -#endif +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to unique_ptr specialization. /// Type caster for holder types like std::unique_ptr. /// Please consider the SFINAE hook an implementation detail, as explained /// in the comment for the copyable_holder_caster. @@ -1013,11 +1136,139 @@ struct move_only_holder_caster { static constexpr auto name = type_caster_base::name; }; -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled : std::true_type {}; + +// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor move_only_holder_caster to reduce code duplication. +template +struct move_only_holder_caster< + type, + std::unique_ptr, + enable_if_t::value>> + : public type_caster_base { +public: + using base = type_caster_base; + static_assert(std::is_base_of>::value, + "Holder classes are only supported for custom types"); + using base::base; + using base::cast; + using base::typeinfo; + using base::value; + + static handle + cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { + auto *ptr = src.get(); + auto st = type_caster_base::src_and_type(ptr); + if (st.second == nullptr) { + return handle(); // no type info: error will be set already + } + if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) { + return smart_holder_type_caster_support::smart_holder_from_unique_ptr( + std::move(src), policy, parent, st); + } + return type_caster_generic::cast(st.first, + return_value_policy::take_ownership, + {}, + st.second, + nullptr, + nullptr, + std::addressof(src)); + } + + static handle + cast(const std::unique_ptr &src, return_value_policy policy, handle parent) { + if (!src) { + return none().release(); + } + if (policy == return_value_policy::automatic) { + policy = return_value_policy::reference_internal; + } + if (policy != return_value_policy::reference_internal) { + throw cast_error("Invalid return_value_policy for unique_ptr&"); + } + return type_caster_base::cast(src.get(), policy, parent); + } + + bool load(handle src, bool convert) { + return base::template load_impl< + move_only_holder_caster>>(src, convert); + } + + void load_value(value_and_holder &&v_h) { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = v_h; + sh_load_helper.loaded_v_h.type = typeinfo; + value = sh_load_helper.get_void_ptr_or_nullptr(); + return; + } + pybind11_fail( + "Passing `std::unique_ptr` from Python to C++ requires `py::classh` (with T = " + + clean_type_id(typeinfo->cpptype->name()) + ")"); + } + + template + using cast_op_type = std::unique_ptr; + + explicit operator std::unique_ptr() { + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + if (from_direct_conversion) { + throw cast_error("Unowned pointer from direct conversion cannot be converted to a " + "std::unique_ptr."); + } + if (from_as_void_ptr_capsule) { + throw cast_error("Unowned pointer from a void pointer capsule cannot be converted " + "to a std::unique_ptr."); + } + return sh_load_helper.template load_as_unique_ptr(value); + } + pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); + } + + bool try_implicit_casts(handle src, bool convert) { + for (auto &cast : typeinfo->implicit_casts) { + move_only_holder_caster sub_caster(*cast.first); + if (sub_caster.load(src, convert)) { + value = cast.second(sub_caster.value); + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h; + } else { + pybind11_fail("Expected to be UNREACHABLE: " __FILE__ + ":" PYBIND11_TOSTRING(__LINE__)); + } + return true; + } + } + return false; + } + + bool try_direct_conversions(handle src) { + if (type_caster_base::try_direct_conversions(src)) { + from_direct_conversion = true; + return true; + } + return false; + } + + bool try_as_void_ptr_capsule(handle src) { + if (type_caster_base::try_as_void_ptr_capsule(src)) { + from_as_void_ptr_capsule = true; + return true; + } + return false; + } + + smart_holder_type_caster_support::load_helper> sh_load_helper; // Const2Mutbl + bool from_direct_conversion = false; + bool from_as_void_ptr_capsule = false; +}; + +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + template class type_caster> : public move_only_holder_caster> {}; -#endif template using type_caster_holder = conditional_t::value, @@ -1046,10 +1297,16 @@ struct always_construct_holder { template struct is_holder_type : std::is_base_of, detail::type_caster> {}; -// Specialization for always-supported unique_ptr holders: + +// Specializations for always-supported holders: template struct is_holder_type> : std::true_type {}; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT +template +struct is_holder_type : std::true_type {}; +#endif + #ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888 // This leads to compilation errors if a specialization is missing. @@ -1278,11 +1535,10 @@ using move_never = none_of, move_if_unreferenced>; // non-reference/pointer `type`s and reference/pointers from a type_caster_generic are safe; // everything else returns a reference/pointer to a local variable. template -using cast_is_temporary_value_reference = bool_constant< - (std::is_reference::value || std::is_pointer::value) - && !std::is_base_of>::value - && !std::is_base_of>::value - && !std::is_same, void>::value>; +using cast_is_temporary_value_reference + = bool_constant<(std::is_reference::value || std::is_pointer::value) + && !std::is_base_of>::value + && !std::is_same, void>::value>; // When a value returned from a C++ function is being cast back to Python, we almost always want to // force `policy = move`, regardless of the return value policy the function/method was declared @@ -1295,9 +1551,7 @@ struct return_value_policy_override { template struct return_value_policy_override< Return, - detail::enable_if_t>::value - || type_uses_smart_holder_type_caster>::value, - void>> { + detail::enable_if_t>::value, void>> { static return_value_policy policy(return_value_policy p) { return !std::is_lvalue_reference::value && !std::is_pointer::value && p != return_value_policy::_clif_automatic @@ -1737,7 +1991,7 @@ struct kw_only {}; /// \ingroup annotations /// Annotation indicating that all previous arguments are positional-only; the is the equivalent of -/// an unnamed '/' argument (in Python 3.8) +/// an unnamed '/' argument struct pos_only {}; PYBIND11_NAMESPACE_BEGIN(detail) @@ -2131,10 +2385,8 @@ PYBIND11_NAMESPACE_END(detail) template handle type::handle_of() { - static_assert( - detail::any_of>, - detail::type_uses_smart_holder_type_caster>::value, - "py::type::of only supports the case where T is a registered C++ types."); + static_assert(std::is_base_of>::value, + "py::type::of only supports the case where T is a registered C++ types."); return detail::get_type_handle(typeid(T), true); } @@ -2143,7 +2395,7 @@ handle type::handle_of() { PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) \ namespace detail { \ template <> \ - class type_caster<__VA_ARGS__> : public type_caster_for_class_<__VA_ARGS__> {}; \ + class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> {}; \ } \ PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h index 1a0d1693..c0bac7fd 100644 --- a/include/pybind11/detail/class.h +++ b/include/pybind11/detail/class.h @@ -520,19 +520,9 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) { type->tp_free(self); -#if PY_VERSION_HEX < 0x03080000 - // `type->tp_dealloc != pybind11_object_dealloc` means that we're being called - // as part of a derived type's dealloc, in which case we're not allowed to decref - // the type here. For cross-module compatibility, we shouldn't compare directly - // with `pybind11_object_dealloc`, but with the common one stashed in internals. - auto pybind11_object_type = (PyTypeObject *) get_internals().instance_base; - if (type->tp_dealloc == pybind11_object_type->tp_dealloc) - Py_DECREF(type); -#else // This was not needed before Python 3.8 (Python issue 35810) // https://github.com/pybind/pybind11/issues/1946 Py_DECREF(type); -#endif } std::string error_string(); diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 7a512a6f..17cfd5a9 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -9,13 +9,13 @@ #pragma once -#define PYBIND11_VERSION_MAJOR 2 -#define PYBIND11_VERSION_MINOR 14 +#define PYBIND11_VERSION_MAJOR 3 +#define PYBIND11_VERSION_MINOR 0 #define PYBIND11_VERSION_PATCH 0.dev1 // Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html // Additional convention: 0xD = dev -#define PYBIND11_VERSION_HEX 0x020E00D1 +#define PYBIND11_VERSION_HEX 0x030000D1 // Define some generic pybind11 helper macros for warning management. // @@ -272,8 +272,8 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) #endif #include -#if PY_VERSION_HEX < 0x03070000 -# error "PYTHON < 3.7 IS UNSUPPORTED. pybind11 v2.12 was the last to support Python 3.6." +#if PY_VERSION_HEX < 0x03080000 +# error "PYTHON < 3.8 IS UNSUPPORTED. pybind11 v2.13 was the last to support Python 3.7." #endif #include #include diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 4dbba903..bc92e1e1 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -10,7 +10,7 @@ #pragma once #include "class.h" -#include "smart_holder_sfinae_hooks_only.h" +#include "using_smart_holder.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) @@ -159,9 +159,7 @@ void construct(value_and_holder &v_h, Alias *alias_ptr, bool) { // holder. This also handles types like std::shared_ptr and std::unique_ptr where T is a // derived type (through those holder's implicit conversion from derived class holder // constructors). -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, Holder holder, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = holder_helper>::get(holder); @@ -203,10 +201,18 @@ void construct(value_and_holder &v_h, Alias &&result, bool) { v_h.value_ptr() = new Alias(std::move(result)); } +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +template +smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr &&unq_ptr, + bool void_cast_raw_ptr) { + void *void_ptr = void_cast_raw_ptr ? static_cast(unq_ptr.get()) : nullptr; + return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); +} + template >, - detail::enable_if_t>::value, int> - = 0> + detail::enable_if_t>::value, int> = 0> void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = unq_ptr.get(); @@ -220,7 +226,7 @@ void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, // trampoline Python object alive. For types that don't inherit from enable_shared_from_this // it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary // to also inspect the type. - auto smhldr = type_caster>::smart_holder_from_unique_ptr( + auto smhldr = init_smart_holder_from_unique_ptr( std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias(ptr)); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); @@ -228,22 +234,19 @@ void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, template >, - detail::enable_if_t>::value, int> - = 0> + detail::enable_if_t>::value, int> = 0> void construct(value_and_holder &v_h, std::unique_ptr, D> &&unq_ptr, bool /*need_alias*/) { auto *ptr = unq_ptr.get(); no_nullptr(ptr); - auto smhldr = type_caster>::smart_holder_from_unique_ptr( - std::move(unq_ptr), /*void_cast_raw_ptr*/ true); + auto smhldr + = init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool need_alias) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias); auto *ptr = shd_ptr.get(); @@ -252,24 +255,24 @@ void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, boo throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee " "is not an alias instance"); } - auto smhldr = type_caster>::smart_holder_from_shared_ptr(shd_ptr); + auto smhldr = smart_holder::from_shared_ptr(shd_ptr); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } -template >::value, int> - = 0> +template >::value, int> = 0> void construct(value_and_holder &v_h, std::shared_ptr> &&shd_ptr, bool /*need_alias*/) { auto *ptr = shd_ptr.get(); no_nullptr(ptr); - auto smhldr = type_caster>::smart_holder_from_shared_ptr(shd_ptr); + auto smhldr = smart_holder::from_shared_ptr(shd_ptr); v_h.value_ptr() = ptr; v_h.type->init_instance(v_h.inst, &smhldr); } +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + // Implementing class for py::init<...>() template struct constructor { diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index b4da3ced..332e8563 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -16,7 +16,6 @@ #endif #include "../pytypes.h" -#include "smart_holder_sfinae_hooks_only.h" #include #include @@ -44,7 +43,9 @@ # define PYBIND11_INTERNALS_VERSION 6 #endif #ifndef PYBIND11_INTERNALS_VERSION -# if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) +# if PYBIND11_VERSION_MAJOR >= 3 +# define PYBIND11_INTERNALS_VERSION 6 +# elif PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER) // Version bump for Python 3.12+, before first 3.12 beta release. // Version bump for MSVC piggy-backed on PR #4779. See comments there. # define PYBIND11_INTERNALS_VERSION 5 @@ -248,6 +249,20 @@ struct internals { } }; +#if PYBIND11_INTERNALS_VERSION >= 6 + +# define PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +enum class holder_enum_t : uint8_t { + undefined, + std_unique_ptr, // Default, lacking interop with std::shared_ptr. + std_shared_ptr, // Lacking interop with std::unique_ptr. + smart_holder, // Full std::unique_ptr / std::shared_ptr interop. + custom_holder, +}; + +#endif + /// Additional type information which does not fit into the PyTypeObject. /// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`. struct type_info { @@ -271,9 +286,14 @@ struct type_info { /* True if there is no multiple inheritance in this type's inheritance tree */ bool simple_ancestors : 1; /* for base vs derived holder_type checks */ + // SMART_HOLDER_BAKEIN_FOLLOW_ON: Remove default_holder member here and + // produce better error messages in the places where it is currently used. bool default_holder : 1; /* true if this is a type registered with py::module_local */ bool module_local : 1; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + holder_enum_t holder_enum_v = holder_enum_t::undefined; +#endif }; /// On MSVC, debug and release builds are not ABI-compatible! @@ -333,25 +353,15 @@ struct type_info { # define PYBIND11_INTERNALS_KIND "" #endif -/// See README_smart_holder.rst: -/// Classic / Conservative / Progressive cross-module compatibility -#ifndef PYBIND11_INTERNALS_SH_DEF -# if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) -# define PYBIND11_INTERNALS_SH_DEF "_sh_def" -# else -# define PYBIND11_INTERNALS_SH_DEF "" -# endif -#endif - #define PYBIND11_INTERNALS_ID \ "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE PYBIND11_INTERNALS_SH_DEF "__" + PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \ + PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" #define PYBIND11_MODULE_LOCAL_ID \ "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE PYBIND11_INTERNALS_SH_DEF "__" + PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB \ + PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. @@ -469,7 +479,7 @@ inline void translate_local_exception(std::exception_ptr p) { inline object get_python_state_dict() { object state_dict; -#if PYBIND11_INTERNALS_VERSION <= 4 || PY_VERSION_HEX < 0x03080000 || defined(PYPY_VERSION) +#if PYBIND11_INTERNALS_VERSION <= 4 || defined(PYPY_VERSION) state_dict = reinterpret_borrow(PyEval_GetBuiltins()); #else # if PY_VERSION_HEX < 0x03090000 diff --git a/include/pybind11/detail/smart_holder_sfinae_hooks_only.h b/include/pybind11/detail/smart_holder_sfinae_hooks_only.h deleted file mode 100644 index 5114607f..00000000 --- a/include/pybind11/detail/smart_holder_sfinae_hooks_only.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2021 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. - -#pragma once - -#include "common.h" - -#include -#include - -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -// #define PYBIND11_USE_SMART_HOLDER_AS_DEFAULT -// Currently the main purpose of this switch is to enable non-intrusive comprehensive testing. If -// and when `smart_holder` will actually become the released default is currently open. In the -// meantime, the full functionality is easily available by using `py::classh`, which is just a -// handy shortcut for `py::class_` (see `pybind11/smart_holder.h`). Classes -// wrapped in this way are fully compatible with everything existing. -#endif - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) -PYBIND11_NAMESPACE_BEGIN(detail) - -constexpr bool smart_holder_is_default_holder_type = -#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - true; -#else - false; -#endif - -template -struct is_smart_holder_type : std::false_type {}; - -// Tag to be used as base class, inspected by type_uses_smart_holder_type_caster test. -struct smart_holder_type_caster_base_tag {}; - -template -struct type_uses_smart_holder_type_caster; - -// Simple helpers that may eventually be a better fit for another header file: - -template -struct is_std_unique_ptr : std::false_type {}; -template -struct is_std_unique_ptr> : std::true_type {}; - -template -struct is_std_shared_ptr : std::false_type {}; -template -struct is_std_shared_ptr> : std::true_type {}; - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h deleted file mode 100644 index d138613f..00000000 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ /dev/null @@ -1,1162 +0,0 @@ -// Copyright (c) 2021 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. - -#pragma once - -#include "../gil.h" -#include "../pytypes.h" -#include "../trampoline_self_life_support.h" -#include "common.h" -#include "descr.h" -#include "dynamic_raw_ptr_cast_if_possible.h" -#include "internals.h" -#include "smart_holder_poc.h" -#include "smart_holder_sfinae_hooks_only.h" -#include "type_caster_base.h" -#include "typeid.h" - -#include -#include -#include -#include -#include -#include -#include - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -using pybindit::memory::smart_holder; - -PYBIND11_NAMESPACE_BEGIN(detail) - -template <> -struct is_smart_holder_type : std::true_type {}; - -// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. -inline void register_instance(instance *self, void *valptr, const type_info *tinfo); -inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); -extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); - -// Replace all occurrences of substrings in a string. -inline void replace_all(std::string &str, const std::string &from, const std::string &to) { - if (str.empty()) { - return; - } - size_t pos = 0; - while ((pos = str.find(from, pos)) != std::string::npos) { - str.replace(pos, from.length(), to); - pos += to.length(); - } -} - -inline bool type_is_pybind11_class_(PyTypeObject *type_obj) { -#if defined(PYPY_VERSION) - auto &internals = get_internals(); - return bool(internals.registered_types_py.find(type_obj) - != internals.registered_types_py.end()); -#else - return bool(type_obj->tp_new == pybind11_object_new); -#endif -} - -inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) { - PyObject *descr = _PyType_Lookup(type_obj, attr_name); - return bool((descr != nullptr) && PyInstanceMethod_Check(descr)); -} - -inline object try_get_as_capsule_method(PyObject *obj, PyObject *attr_name) { - if (PyType_Check(obj)) { - return object(); - } - PyTypeObject *type_obj = Py_TYPE(obj); - bool known_callable = false; - if (type_is_pybind11_class_(type_obj)) { - if (!is_instance_method_of_type(type_obj, attr_name)) { - return object(); - } - known_callable = true; - } - PyObject *method = PyObject_GetAttr(obj, attr_name); - if (method == nullptr) { - PyErr_Clear(); - return object(); - } - if (!known_callable && PyCallable_Check(method) == 0) { - Py_DECREF(method); - return object(); - } - return reinterpret_steal(method); -} - -inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_name) { - std::string suffix = clean_type_id(typeid_name); - replace_all(suffix, "::", "_"); // Convert `a::b::c` to `a_b_c`. - replace_all(suffix, "*", ""); - object as_capsule_method = try_get_as_capsule_method(src.ptr(), str("as_" + suffix).ptr()); - if (as_capsule_method) { - object void_ptr_capsule = as_capsule_method(); - if (isinstance(void_ptr_capsule)) { - return reinterpret_borrow(void_ptr_capsule).get_pointer(); - } - } - return nullptr; -} - -// The modified_type_caster_generic_load_impl could replace type_caster_generic::load_impl but not -// vice versa. The main difference is that the original code only propagates a reference to the -// held value, while the modified implementation propagates value_and_holder. -class modified_type_caster_generic_load_impl { -public: - PYBIND11_NOINLINE explicit modified_type_caster_generic_load_impl( - const std::type_info &type_info) - : typeinfo(get_type_info(type_info)), cpptype(&type_info) {} - - explicit modified_type_caster_generic_load_impl(const type_info *typeinfo = nullptr) - : typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) {} - - bool load(handle src, bool convert) { - return load_impl(src, convert); - } - - // Base methods for generic caster; there are overridden in copyable_holder_caster - void load_value_and_holder(value_and_holder &&v_h) { - if (!v_h.holder_constructed()) { - // This is needed for old-style __init__. - // type_caster_generic::load_value BEGIN - auto *&vptr = v_h.value_ptr(); - // Lazy allocation for unallocated values: - if (vptr == nullptr) { - // Lazy allocation for unallocated values: - const auto *type = v_h.type ? v_h.type : typeinfo; - if (type->operator_new) { - vptr = type->operator_new(type->type_size); - } else { -#if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912) - if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { - vptr = ::operator new(type->type_size, std::align_val_t(type->type_align)); - } else { - vptr = ::operator new(type->type_size); - } -#else - vptr = ::operator new(type->type_size); -#endif - } - } - // type_caster_generic::load_value END - } - loaded_v_h = v_h; - loaded_v_h.type = typeinfo; - } - - bool try_implicit_casts(handle src, bool convert) { - for (const auto &cast : typeinfo->implicit_casts) { - modified_type_caster_generic_load_impl sub_caster(*cast.first); - if (sub_caster.load(src, convert)) { - if (loaded_v_h_cpptype != nullptr) { - pybind11_fail("smart_holder_type_casters: try_implicit_casts failure."); - } - loaded_v_h = sub_caster.loaded_v_h; - loaded_v_h_cpptype = cast.first; - // the sub_caster is being discarded, so steal its vector - implicit_casts = std::move(sub_caster.implicit_casts); - implicit_casts.emplace_back(cast.second); - return true; - } - } - return false; - } - - bool try_direct_conversions(handle src) { - for (auto &converter : *typeinfo->direct_conversions) { - if (converter(src.ptr(), unowned_void_ptr_from_direct_conversion)) { - return true; - } - } - return false; - } - - bool try_as_void_ptr_capsule(handle src) { - unowned_void_ptr_from_void_ptr_capsule - = try_as_void_ptr_capsule_get_pointer(src, cpptype->name()); - if (unowned_void_ptr_from_void_ptr_capsule != nullptr) { - return true; - } - return false; - } - - PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) { - std::unique_ptr loader( - new modified_type_caster_generic_load_impl(ti)); - if (loader->load(src, false)) { - // Trick to work with the existing pybind11 internals. - // The void pointer is immediately captured in a new unique_ptr in - // try_load_foreign_module_local. If this assumption is violated sanitizers - // will most likely flag a leak (verified to be the case with ASAN). - return static_cast(loader.release()); - } - return nullptr; - } - - /// Try to load with foreign typeinfo, if available. Used when there is no - /// native typeinfo, or when the native one wasn't able to produce a value. - PYBIND11_NOINLINE bool try_load_foreign_module_local(handle src) { - constexpr auto *local_key = PYBIND11_MODULE_LOCAL_ID; - const auto pytype = type::handle_of(src); - if (!hasattr(pytype, local_key)) { - return false; - } - - type_info *foreign_typeinfo = reinterpret_borrow(getattr(pytype, local_key)); - // Only consider this foreign loader if actually foreign and is a loader of the correct cpp - // type - if (foreign_typeinfo->module_local_load == &local_load - || (cpptype && !same_type(*cpptype, *foreign_typeinfo->cpptype))) { - return false; - } - - void *foreign_loader_void_ptr - = foreign_typeinfo->module_local_load(src.ptr(), foreign_typeinfo); - if (foreign_loader_void_ptr != nullptr) { - auto foreign_loader = std::unique_ptr( - static_cast(foreign_loader_void_ptr)); - // Magic number intentionally hard-coded for simplicity and maximum robustness. - if (foreign_loader->local_load_safety_guard != 1887406645) { - pybind11_fail("smart_holder_type_casters: Unexpected local_load_safety_guard," - " possibly due to py::class_ holder mixup."); - } - if (loaded_v_h_cpptype != nullptr) { - pybind11_fail("smart_holder_type_casters: try_load_foreign_module_local failure."); - } - loaded_v_h = foreign_loader->loaded_v_h; - loaded_v_h_cpptype = foreign_loader->loaded_v_h_cpptype; - // SMART_HOLDER_WIP: should this be a copy or move? - implicit_casts = foreign_loader->implicit_casts; - return true; - } - return false; - } - - // Implementation of `load`; this takes the type of `this` so that it can dispatch the relevant - // bits of code between here and copyable_holder_caster where the two classes need different - // logic (without having to resort to virtual inheritance). - template - PYBIND11_NOINLINE bool load_impl(handle src, bool convert) { - if (!src) { - return false; - } - if (!typeinfo) { - return try_load_foreign_module_local(src); - } - - auto &this_ = static_cast(*this); - - PyTypeObject *srctype = Py_TYPE(src.ptr()); - - // Case 1: If src is an exact type match for the target type then we can reinterpret_cast - // the instance's value pointer to the target type: - if (srctype == typeinfo->type) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder()); - return true; - } - // Case 2: We have a derived class - if (PyType_IsSubtype(srctype, typeinfo->type)) { - const auto &bases = all_type_info(srctype); // subtype bases - bool no_cpp_mi = typeinfo->simple_type; - - // Case 2a: the python type is a Python-inherited derived class that inherits from just - // one simple (no MI) pybind11 class, or is an exact match, so the C++ instance is of - // the right type and we can use reinterpret_cast. - // (This is essentially the same as case 2b, but because not using multiple inheritance - // is extremely common, we handle it specially to avoid the loop iterator and type - // pointer lookup overhead) - if (bases.size() == 1 && (no_cpp_mi || bases.front()->type == typeinfo->type)) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder()); - loaded_v_h_cpptype = bases.front()->cpptype; - reinterpret_cast_deemed_ok = true; - return true; - } - // Case 2b: the python type inherits from multiple C++ bases. Check the bases to see - // if we can find an exact match (or, for a simple C++ type, an inherited match); if - // so, we can safely reinterpret_cast to the relevant pointer. - if (bases.size() > 1) { - for (auto *base : bases) { - if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type) - : base->type == typeinfo->type) { - this_.load_value_and_holder( - reinterpret_cast(src.ptr())->get_value_and_holder(base)); - loaded_v_h_cpptype = base->cpptype; - reinterpret_cast_deemed_ok = true; - return true; - } - } - } - - // Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type - // match in the registered bases, above, so try implicit casting (needed for proper C++ - // casting when MI is involved). - if (this_.try_implicit_casts(src, convert)) { - return true; - } - } - - // Perform an implicit conversion - if (convert) { - for (const auto &converter : typeinfo->implicit_conversions) { - auto temp = reinterpret_steal(converter(src.ptr(), typeinfo->type)); - if (load_impl(temp, false)) { - loader_life_support::add_patient(temp); - return true; - } - } - if (this_.try_direct_conversions(src)) { - return true; - } - } - - // Failed to match local typeinfo. Try again with global. - if (typeinfo->module_local) { - if (auto *gtype = get_global_type_info(*typeinfo->cpptype)) { - typeinfo = gtype; - return load(src, false); - } - } - - // Global typeinfo has precedence over foreign module_local - if (try_load_foreign_module_local(src)) { - return true; - } - - if (src.is_none()) { - // Defer accepting None to other overloads (if we aren't in convert mode): - if (!convert) { - return false; - } - loaded_v_h = value_and_holder(); - return true; - } - if (convert && cpptype && try_as_void_ptr_capsule(src)) { - return true; - } - return false; - } - - const type_info *typeinfo = nullptr; - const std::type_info *cpptype = nullptr; - void *unowned_void_ptr_from_direct_conversion = nullptr; - void *unowned_void_ptr_from_void_ptr_capsule = nullptr; - const std::type_info *loaded_v_h_cpptype = nullptr; - std::vector implicit_casts; - value_and_holder loaded_v_h; - bool reinterpret_cast_deemed_ok = false; - // Magic number intentionally hard-coded, to guard against class_ holder mixups. - // Ideally type_caster_generic would have a similar guard, but this requires a change there. - // SMART_HOLDER_WIP: If it is decided that this guard is useful long term, potentially - // set/reset this value in ctor/dtor, mark volatile. - std::size_t local_load_safety_guard = 1887406645; // 32-bit compatible value for portability. -}; - -struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag { - static decltype(&modified_type_caster_generic_load_impl::local_load) - get_local_load_function_ptr() { - return &modified_type_caster_generic_load_impl::local_load; - } - - using holder_type = pybindit::memory::smart_holder; - - template - static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) { - return false; - } - - // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy - // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already, - // it is reused, irrespective of the return_value_policy in effect. - // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a - // direct base of WrappedType. - template - static bool try_initialization_using_shared_from_this( - holder_type *uninitialized_location, - WrappedType *value_ptr_w_t, - const std::enable_shared_from_this *) { - auto shd_ptr = std::dynamic_pointer_cast( - detail::try_get_shared_from_this(value_ptr_w_t)); - if (!shd_ptr) { - return false; - } - // Note: inst->owned ignored. - new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr)); - return true; - } - - template - static void init_instance_for_type(detail::instance *inst, const void *holder_const_void_ptr) { - // Need for const_cast is a consequence of the type_info::init_instance type: - // void (*init_instance)(instance *, const void *); - auto *holder_void_ptr = const_cast(holder_const_void_ptr); - - auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(WrappedType))); - if (!v_h.instance_registered()) { - register_instance(inst, v_h.value_ptr(), v_h.type); - v_h.set_instance_registered(); - } - auto *uninitialized_location = std::addressof(v_h.holder()); - auto *value_ptr_w_t = v_h.value_ptr(); - bool pointee_depends_on_holder_owner - = dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; - if (holder_void_ptr) { - // Note: inst->owned ignored. - auto *holder_ptr = static_cast(holder_void_ptr); - new (uninitialized_location) holder_type(std::move(*holder_ptr)); - } else if (!try_initialization_using_shared_from_this( - uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { - if (inst->owned) { - new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( - value_ptr_w_t, /*void_cast_raw_ptr*/ pointee_depends_on_holder_owner)); - } else { - new (uninitialized_location) - holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); - } - } - v_h.holder().pointee_depends_on_holder_owner - = pointee_depends_on_holder_owner; - v_h.set_holder_constructed(); - } - - template - static smart_holder smart_holder_from_unique_ptr(std::unique_ptr &&unq_ptr, - bool void_cast_raw_ptr) { - void *void_ptr = void_cast_raw_ptr ? static_cast(unq_ptr.get()) : nullptr; - return pybindit::memory::smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr); - } - - template - static smart_holder smart_holder_from_shared_ptr(std::shared_ptr shd_ptr) { - return pybindit::memory::smart_holder::from_shared_ptr(shd_ptr); - } -}; - -struct shared_ptr_parent_life_support { - PyObject *parent; - explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { - Py_INCREF(parent); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(parent); - } -}; - -struct shared_ptr_trampoline_self_life_support { - PyObject *self; - explicit shared_ptr_trampoline_self_life_support(instance *inst) - : self{reinterpret_cast(inst)} { - gil_scoped_acquire gil; - Py_INCREF(self); - } - // NOLINTNEXTLINE(readability-make-member-function-const) - void operator()(void *) { - gil_scoped_acquire gil; - Py_DECREF(self); - } -}; - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - return std::unique_ptr(raw_ptr); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template ::value, int>::type = 0> -inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { - if (deleter == nullptr) { - pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" - " instance available to return."); - } - return std::unique_ptr(raw_ptr, std::move(*deleter)); -} - -template -struct smart_holder_type_caster_load { - using holder_type = pybindit::memory::smart_holder; - - bool load(handle src, bool convert) { - static_assert(type_uses_smart_holder_type_caster::value, "Internal consistency error."); - load_impl = modified_type_caster_generic_load_impl(typeid(T)); - if (!load_impl.load(src, convert)) { - return false; - } - return true; - } - - T *loaded_as_raw_ptr_unowned() const { - void *void_ptr = load_impl.unowned_void_ptr_from_void_ptr_capsule; - if (void_ptr == nullptr) { - void_ptr = load_impl.unowned_void_ptr_from_direct_conversion; - } - if (void_ptr == nullptr) { - if (have_holder()) { - throw_if_uninitialized_or_disowned_holder(typeid(T)); - void_ptr = holder().template as_raw_ptr_unowned(); - } else if (load_impl.loaded_v_h.vh != nullptr) { - void_ptr = load_impl.loaded_v_h.value_ptr(); - } - if (void_ptr == nullptr) { - return nullptr; - } - } - return convert_type(void_ptr); - } - - T &loaded_as_lvalue_ref() const { - T *raw_ptr = loaded_as_raw_ptr_unowned(); - if (raw_ptr == nullptr) { - throw reference_cast_error(); - } - return *raw_ptr; - } - - std::shared_ptr make_shared_ptr_with_responsible_parent(handle parent) const { - return std::shared_ptr(loaded_as_raw_ptr_unowned(), - shared_ptr_parent_life_support(parent.ptr())); - } - - std::shared_ptr loaded_as_shared_ptr(handle responsible_parent = nullptr) const { - if (load_impl.unowned_void_ptr_from_void_ptr_capsule) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(responsible_parent); - } - throw cast_error("Unowned pointer from a void pointer capsule cannot be converted to a" - " std::shared_ptr."); - } - if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(responsible_parent); - } - throw cast_error("Unowned pointer from direct conversion cannot be converted to a" - " std::shared_ptr."); - } - if (!have_holder()) { - return nullptr; - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - holder_type &hld = holder(); - hld.ensure_is_not_disowned("loaded_as_shared_ptr"); - if (hld.vptr_is_using_noop_deleter) { - if (responsible_parent) { - return make_shared_ptr_with_responsible_parent(responsible_parent); - } - throw std::runtime_error("Non-owning holder (loaded_as_shared_ptr)."); - } - auto *void_raw_ptr = hld.template as_raw_ptr_unowned(); - auto *type_raw_ptr = convert_type(void_raw_ptr); - if (hld.pointee_depends_on_holder_owner) { - auto *vptr_gd_ptr = std::get_deleter(hld.vptr); - if (vptr_gd_ptr != nullptr) { - std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); - if (released_ptr) { - return std::shared_ptr(released_ptr, type_raw_ptr); - } - std::shared_ptr to_be_released( - type_raw_ptr, - shared_ptr_trampoline_self_life_support(load_impl.loaded_v_h.inst)); - vptr_gd_ptr->released_ptr = to_be_released; - return to_be_released; - } - auto *sptsls_ptr = std::get_deleter(hld.vptr); - if (sptsls_ptr != nullptr) { - // This code is reachable only if there are multiple registered_instances for the - // same pointee. - if (reinterpret_cast(load_impl.loaded_v_h.inst) == sptsls_ptr->self) { - pybind11_fail("smart_holder_type_casters loaded_as_shared_ptr failure: " - "load_impl.loaded_v_h.inst == sptsls_ptr->self"); - } - } - if (sptsls_ptr != nullptr - || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { - return std::shared_ptr( - type_raw_ptr, - shared_ptr_trampoline_self_life_support(load_impl.loaded_v_h.inst)); - } - if (hld.vptr_is_external_shared_ptr) { - pybind11_fail("smart_holder_type_casters loaded_as_shared_ptr failure: not " - "implemented: trampoline-self-life-support for external shared_ptr " - "to type inheriting from std::enable_shared_from_this."); - } - pybind11_fail("smart_holder_type_casters: loaded_as_shared_ptr failure: internal " - "inconsistency."); - } - std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); - return std::shared_ptr(void_shd_ptr, type_raw_ptr); - } - - template - std::unique_ptr loaded_as_unique_ptr(const char *context = "loaded_as_unique_ptr") { - if (load_impl.unowned_void_ptr_from_void_ptr_capsule) { - throw cast_error("Unowned pointer from a void pointer capsule cannot be converted to a" - " std::unique_ptr."); - } - if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr) { - throw cast_error("Unowned pointer from direct conversion cannot be converted to a" - " std::unique_ptr."); - } - if (!have_holder()) { - return unique_with_deleter(nullptr, std::unique_ptr()); - } - throw_if_uninitialized_or_disowned_holder(typeid(T)); - throw_if_instance_is_currently_owned_by_shared_ptr(); - holder().ensure_is_not_disowned(context); - holder().template ensure_compatible_rtti_uqp_del(context); - holder().ensure_use_count_1(context); - auto raw_void_ptr = holder().template as_raw_ptr_unowned(); - - void *value_void_ptr = load_impl.loaded_v_h.value_ptr(); - if (value_void_ptr != raw_void_ptr) { - pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:" - " value_void_ptr != raw_void_ptr"); - } - - // SMART_HOLDER_WIP: MISSING: Safety checks for type conversions - // (T must be polymorphic or meet certain other conditions). - T *raw_type_ptr = convert_type(raw_void_ptr); - - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); - if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { - throw value_error("Alias class (also known as trampoline) does not inherit from " - "py::trampoline_self_life_support, therefore the ownership of this " - "instance cannot safely be transferred to C++."); - } - - // Temporary variable to store the extracted deleter in. - std::unique_ptr extracted_deleter; - - auto *gd = std::get_deleter(holder().vptr); - if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. - // In smart_holder_poc, a custom deleter is always stored in a guarded delete. - // The guarded delete's std::function actually points at the - // custom_deleter type, so we can verify it is of the custom deleter type and - // finally extract its deleter. - using custom_deleter_D = pybindit::memory::custom_deleter; - const auto &custom_deleter_ptr = gd->del_fun.template target(); - assert(custom_deleter_ptr != nullptr); - // Now that we have confirmed the type of the deleter matches the desired return - // value we can extract the function. - extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); - } - - // Critical transfer-of-ownership section. This must stay together. - if (self_life_support != nullptr) { - holder().disown(); - } else { - holder().release_ownership(); - } - auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); - if (self_life_support != nullptr) { - self_life_support->activate_life_support(load_impl.loaded_v_h); - } else { - load_impl.loaded_v_h.value_ptr() = nullptr; - deregister_instance( - load_impl.loaded_v_h.inst, value_void_ptr, load_impl.loaded_v_h.type); - } - // Critical section end. - - return result; - } - - // This function will succeed even if the `responsible_parent` does not own the - // wrapped C++ object directly. - // It is the responsibility of the caller to ensure that the `responsible_parent` - // has a `keep_alive` relationship with the owner of the wrapped C++ object, or - // that the wrapped C++ object lives for the duration of the process. - static std::shared_ptr shared_ptr_from_python(handle responsible_parent) { - smart_holder_type_caster_load loader; - loader.load(responsible_parent, false); - return loader.loaded_as_shared_ptr(responsible_parent); - } - -private: - modified_type_caster_generic_load_impl load_impl; - - bool have_holder() const { - return load_impl.loaded_v_h.vh != nullptr && load_impl.loaded_v_h.holder_constructed(); - } - - holder_type &holder() const { return load_impl.loaded_v_h.holder(); } - - // have_holder() must be true or this function will fail. - void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { - static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; - if (!holder().is_populated) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance is uninitialized."); - } - if (!holder().has_pointee()) { - throw value_error(missing_value_msg + clean_type_id(typeid_name) - + "`: Python instance was disowned."); - } - } - - void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { - throw_if_uninitialized_or_disowned_holder(type_info.name()); - } - - // have_holder() must be true or this function will fail. - void throw_if_instance_is_currently_owned_by_shared_ptr() const { - auto vptr_gd_ptr = std::get_deleter(holder().vptr); - if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { - throw value_error("Python instance is currently owned by a std::shared_ptr."); - } - } - - T *convert_type(void *void_ptr) const { - if (void_ptr != nullptr && load_impl.loaded_v_h_cpptype != nullptr - && !load_impl.reinterpret_cast_deemed_ok && !load_impl.implicit_casts.empty()) { - for (auto implicit_cast : load_impl.implicit_casts) { - void_ptr = implicit_cast(void_ptr); - } - } - return static_cast(void_ptr); - } -}; - -// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. -struct make_constructor : private type_caster_base { // Any type, nothing special about int. - using type_caster_base::Constructor; - using type_caster_base::make_copy_constructor; - using type_caster_base::make_move_constructor; -}; - -template -struct smart_holder_type_caster : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - // static handle cast(T, ...) - // is redundant (leads to ambiguous overloads). - - static handle cast(T &&src, return_value_policy /*policy*/, handle parent) { - // type_caster_base BEGIN - return cast(std::addressof(src), return_value_policy::move, parent); - // type_caster_base END - } - - static handle cast(T const &src, return_value_policy policy, handle parent) { - // type_caster_base BEGIN - if (policy == return_value_policy::automatic - || policy == return_value_policy::automatic_reference - || policy == return_value_policy::_clif_automatic) { - policy = return_value_policy::copy; - } - return cast(std::addressof(src), policy, parent); - // type_caster_base END - } - - static handle cast(T &src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::_clif_automatic) { - if (is_move_constructible::value) { - policy = return_value_policy::move; - } else { - policy = return_value_policy::automatic; - } - } - return cast(const_cast(src), policy, parent); // Mutbl2Const - } - - static handle cast(T const *src, return_value_policy policy, handle parent) { - auto st = type_caster_base::src_and_type(src); - if (policy == return_value_policy::_clif_automatic) { - policy = return_value_policy::copy; - } - return cast_const_raw_ptr( // Originally type_caster_generic::cast. - st.first, - policy, - parent, - st.second, - make_constructor::make_copy_constructor(src), - make_constructor::make_move_constructor(src)); - } - - static handle cast(T *src, return_value_policy policy, handle parent) { - if (policy == return_value_policy::_clif_automatic) { - if (parent) { - policy = return_value_policy::reference_internal; - } else { - policy = return_value_policy::reference; - } - } - return cast(const_cast(src), policy, parent); // Mutbl2Const - } - - template - using cast_op_type = conditional_t< - std::is_same, T const *>::value, - T const *, - conditional_t, T *>::value, - T *, - conditional_t::value, T const &, T &>>>; - - // The const operators here prove that the existing type_caster mechanism already supports - // const-correctness. However, fully implementing const-correctness inside this type_caster - // is still a major project. - // NOLINTNEXTLINE(google-explicit-constructor) - operator T const &() const { - return const_cast(this)->loaded_as_lvalue_ref(); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T const *() const { - return const_cast(this)->loaded_as_raw_ptr_unowned(); - } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T &() { return this->loaded_as_lvalue_ref(); } - // NOLINTNEXTLINE(google-explicit-constructor) - operator T *() { return this->loaded_as_raw_ptr_unowned(); } - - // Originally type_caster_generic::cast. - PYBIND11_NOINLINE static handle cast_const_raw_ptr(const void *_src, - return_value_policy policy, - handle parent, - const detail::type_info *tinfo, - void *(*copy_constructor)(const void *), - void *(*move_constructor)(const void *), - const void *existing_holder = nullptr) { - if (!tinfo) { // no type info: error will be set already - return handle(); - } - - void *src = const_cast(_src); - if (src == nullptr) { - return none().release(); - } - - if (handle existing_inst = find_registered_python_instance(src, tinfo)) { - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *wrapper = reinterpret_cast(inst.ptr()); - wrapper->owned = false; - void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); - - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::take_ownership: - valueptr = src; - wrapper->owned = true; - break; - - case return_value_policy::automatic_reference: - case return_value_policy::reference: - valueptr = src; - wrapper->owned = false; - break; - - case return_value_policy::copy: - if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("return_value_policy = copy, but type is " - "non-copyable! (#define PYBIND11_DETAILED_ERROR_MESSAGES or " - "compile in debug mode for details)"); -#else - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = copy, but type " + type_name - + " is non-copyable!"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::move: - if (move_constructor) { - valueptr = move_constructor(src); - } else if (copy_constructor) { - valueptr = copy_constructor(src); - } else { -#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error("return_value_policy = move, but type is neither " - "movable nor copyable! " - "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in " - "debug mode for details)"); -#else - std::string type_name(tinfo->cpptype->name()); - detail::clean_type_id(type_name); - throw cast_error("return_value_policy = move, but type " + type_name - + " is neither movable nor copyable!"); -#endif - } - wrapper->owned = true; - break; - - case return_value_policy::reference_internal: - valueptr = src; - wrapper->owned = false; - keep_alive_impl(inst, parent); - break; - - default: - throw cast_error("unhandled return_value_policy: should not happen!"); - } - - tinfo->init_instance(wrapper, existing_holder); - - return inst.release(); - } -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { - switch (policy) { - case return_value_policy::automatic: - case return_value_policy::automatic_reference: - break; - case return_value_policy::take_ownership: - throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); - break; - case return_value_policy::copy: - case return_value_policy::move: - break; - case return_value_policy::reference: - throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); - break; - case return_value_policy::reference_internal: - case return_value_policy::_return_as_bytes: - case return_value_policy::_clif_automatic: - break; - } - if (!src) { - return none().release(); - } - - auto src_raw_ptr = src.get(); - auto st = type_caster_base::src_and_type(src_raw_ptr); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - - void *src_raw_void_ptr = static_cast(src_raw_ptr); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. - // SMART_HOLDER_WIP: MISSING: keep_alive. - return existing_inst; - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - auto smhldr = pybindit::memory::smart_holder::from_shared_ptr( - std::shared_ptr(src, const_cast(st.first))); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); - } - - template - using cast_op_type = std::shared_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::shared_ptr() { return this->loaded_as_shared_ptr(); } -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle - cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { - return smart_holder_type_caster>::cast( - std::const_pointer_cast(src), // Const2Mutbl - policy, - parent); - } - - template - using cast_op_type = std::shared_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::shared_ptr() { return this->loaded_as_shared_ptr(); } // Mutbl2Const -}; - -template -struct smart_holder_type_caster> : smart_holder_type_caster_load, - smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { - if (policy != return_value_policy::automatic - && policy != return_value_policy::automatic_reference - && policy != return_value_policy::reference_internal - && policy != return_value_policy::move - && policy != return_value_policy::_clif_automatic) { - // SMART_HOLDER_WIP: IMPROVABLE: Error message. - throw cast_error("Invalid return_value_policy for unique_ptr."); - } - if (!src) { - return none().release(); - } - - auto st = type_caster_base::src_and_type(src.get()); - if (st.second == nullptr) { - return handle(); // no type info: error will be set already - } - - void *src_raw_void_ptr = const_cast(st.first); - const detail::type_info *tinfo = st.second; - if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { - auto *self_life_support - = dynamic_raw_ptr_cast_if_possible(src.get()); - if (self_life_support != nullptr) { - value_and_holder &v_h = self_life_support->v_h; - if (v_h.inst != nullptr && v_h.vh != nullptr) { - auto &holder = v_h.holder(); - if (!holder.is_disowned) { - pybind11_fail("smart_holder_type_casters: unexpected " - "smart_holder.is_disowned failure."); - } - // Critical transfer-of-ownership section. This must stay together. - self_life_support->deactivate_life_support(); - holder.reclaim_disowned(); - (void) src.release(); - // Critical section end. - return existing_inst; - } - } - throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); - } - - auto inst = reinterpret_steal(make_new_instance(tinfo->type)); - auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); - inst_raw_ptr->owned = true; - void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); - valueptr = src_raw_void_ptr; - - if (static_cast(src.get()) == src_raw_void_ptr) { - // This is a multiple-inheritance situation that is incompatible with the current - // shared_from_this handling (see PR #3023). - // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? - src_raw_void_ptr = nullptr; - } - auto smhldr - = pybindit::memory::smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); - tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); - - if (policy == return_value_policy::reference_internal) { - keep_alive_impl(inst, parent); - } - - return inst.release(); - } - static handle - cast(const std::unique_ptr &src, return_value_policy policy, handle parent) { - if (!src) { - return none().release(); - } - if (policy == return_value_policy::automatic) { - policy = return_value_policy::reference_internal; - } - if (policy != return_value_policy::reference_internal) { - throw cast_error("Invalid return_value_policy for unique_ptr&"); - } - return smart_holder_type_caster::cast(src.get(), policy, parent); - } - - template - using cast_op_type = std::unique_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::unique_ptr() { return this->template loaded_as_unique_ptr(); } -}; - -template -struct smart_holder_type_caster> - : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { - static constexpr auto name = const_name(); - - static handle - cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { - return smart_holder_type_caster>::cast( - std::unique_ptr(const_cast(src.release()), - std::move(src.get_deleter())), // Const2Mutbl - policy, - parent); - } - - template - using cast_op_type = std::unique_ptr; - - // NOLINTNEXTLINE(google-explicit-constructor) - operator std::unique_ptr() { return this->template loaded_as_unique_ptr(); } -}; - -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - -# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) \ - namespace pybind11 { \ - namespace detail { \ - template <> \ - class type_caster<__VA_ARGS__> : public smart_holder_type_caster<__VA_ARGS__> {}; \ - template <> \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template <> \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - template \ - class type_caster> \ - : public smart_holder_type_caster> {}; \ - } \ - } -#else - -# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) - -template -class type_caster_for_class_ : public smart_holder_type_caster {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -template -class type_caster_for_class_> - : public smart_holder_type_caster> {}; - -#endif - -PYBIND11_NAMESPACE_END(detail) -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/try_as_void_ptr_capsule_get_pointer.h b/include/pybind11/detail/try_as_void_ptr_capsule_get_pointer.h new file mode 100644 index 00000000..e2d3cbf6 --- /dev/null +++ b/include/pybind11/detail/try_as_void_ptr_capsule_get_pointer.h @@ -0,0 +1,90 @@ +// Copyright (c) 2024 The pybind Community. + +// **WARNING:** +// The `as_void_ptr_capsule` feature is needed for PyCLIF-SWIG interoperability +// in the Google-internal environment, but the current implementation is lacking +// any safety checks. + +#pragma once + +#include "../pytypes.h" +#include "common.h" +#include "typeid.h" + +#include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +// Replace all occurrences of substrings in a string. +inline void replace_all(std::string &str, const std::string &from, const std::string &to) { + if (str.empty()) { + return; + } + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } +} + +// Forward declaration needed here: Refactoring opportunity. +extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *); + +inline bool type_is_pybind11_class_(PyTypeObject *type_obj) { +#if defined(PYPY_VERSION) + auto &internals = get_internals(); + return bool(internals.registered_types_py.find(type_obj) + != internals.registered_types_py.end()); +#else + return bool(type_obj->tp_new == pybind11_object_new); +#endif +} + +inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) { + PyObject *descr = _PyType_Lookup(type_obj, attr_name); + return bool((descr != nullptr) && PyInstanceMethod_Check(descr)); +} + +inline object try_get_as_capsule_method(PyObject *obj, PyObject *attr_name) { + if (PyType_Check(obj)) { + return object(); + } + PyTypeObject *type_obj = Py_TYPE(obj); + bool known_callable = false; + if (type_is_pybind11_class_(type_obj)) { + if (!is_instance_method_of_type(type_obj, attr_name)) { + return object(); + } + known_callable = true; + } + PyObject *method = PyObject_GetAttr(obj, attr_name); + if (method == nullptr) { + PyErr_Clear(); + return object(); + } + if (!known_callable && PyCallable_Check(method) == 0) { + Py_DECREF(method); + return object(); + } + return reinterpret_steal(method); +} + +inline void *try_as_void_ptr_capsule_get_pointer(handle src, const char *typeid_name) { + std::string suffix = clean_type_id(typeid_name); + replace_all(suffix, "::", "_"); // Convert `a::b::c` to `a_b_c`. + replace_all(suffix, "*", ""); + object as_capsule_method = try_get_as_capsule_method(src.ptr(), str("as_" + suffix).ptr()); + if (as_capsule_method) { + object void_ptr_capsule = as_capsule_method(); + if (isinstance(void_ptr_capsule)) { + return reinterpret_borrow(void_ptr_capsule).get_pointer(); + } + } + return nullptr; +} + +#define PYBIND11_HAS_TRY_AS_VOID_PTR_CAPSULE_GET_POINTER + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index edd1de59..6177218c 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -9,11 +9,16 @@ #pragma once +#include "../gil.h" #include "../pytypes.h" +#include "../trampoline_self_life_support.h" #include "common.h" #include "descr.h" +#include "dynamic_raw_ptr_cast_if_possible.h" #include "internals.h" +#include "try_as_void_ptr_capsule_get_pointer.h" #include "typeid.h" +#include "using_smart_holder.h" #include "value_and_holder.h" #include @@ -468,6 +473,361 @@ inline PyThreadState *get_thread_state_unchecked() { void keep_alive_impl(handle nurse, handle patient); inline PyObject *make_new_instance(PyTypeObject *type); +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code. +inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo); + +PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support) + +struct value_and_holder_helper { + value_and_holder loaded_v_h; + + bool have_holder() const { + return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed(); + } + + smart_holder &holder() const { return loaded_v_h.holder(); } + + void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const { + static const std::string missing_value_msg = "Missing value for wrapped C++ type `"; + if (!holder().is_populated) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance is uninitialized."); + } + if (!holder().has_pointee()) { + throw value_error(missing_value_msg + clean_type_id(typeid_name) + + "`: Python instance was disowned."); + } + } + + void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const { + throw_if_uninitialized_or_disowned_holder(type_info.name()); + } + + // have_holder() must be true or this function will fail. + void throw_if_instance_is_currently_owned_by_shared_ptr() const { + auto *vptr_gd_ptr = std::get_deleter(holder().vptr); + if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) { + throw value_error("Python instance is currently owned by a std::shared_ptr."); + } + } + + void *get_void_ptr_or_nullptr() const { + if (have_holder()) { + auto &hld = holder(); + if (hld.is_populated && hld.has_pointee()) { + return hld.template as_raw_ptr_unowned(); + } + } + return nullptr; + } +}; + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + if (policy == return_value_policy::copy) { + throw cast_error("return_value_policy::copy is invalid for unique_ptr."); + } + if (!src) { + return none().release(); + } + void *src_raw_void_ptr = const_cast(st.first); + assert(st.second != nullptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(src.get()); + if (self_life_support != nullptr) { + value_and_holder &v_h = self_life_support->v_h; + if (v_h.inst != nullptr && v_h.vh != nullptr) { + auto &holder = v_h.holder(); + if (!holder.is_disowned) { + pybind11_fail("smart_holder_from_unique_ptr: unexpected " + "smart_holder.is_disowned failure."); + } + // Critical transfer-of-ownership section. This must stay together. + self_life_support->deactivate_life_support(); + holder.reclaim_disowned(); + (void) src.release(); + // Critical section end. + return existing_inst; + } + } + throw cast_error("Invalid unique_ptr: another instance owns this pointer already."); + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + if (static_cast(src.get()) == src_raw_void_ptr) { + // This is a multiple-inheritance situation that is incompatible with the current + // shared_from_this handling (see PR #3023). + // SMART_HOLDER_WIP: IMPROVABLE: Is there a better solution? + src_raw_void_ptr = nullptr; + } + auto smhldr = smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_unique_ptr(std::unique_ptr &&src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_unique_ptr( + std::unique_ptr(const_cast(src.release()), + std::move(src.get_deleter())), // Const2Mutbl + policy, + parent, + st); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + switch (policy) { + case return_value_policy::automatic: + case return_value_policy::automatic_reference: + break; + case return_value_policy::take_ownership: + throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership)."); + case return_value_policy::copy: + case return_value_policy::move: + break; + case return_value_policy::reference: + throw cast_error("Invalid return_value_policy for shared_ptr (reference)."); + case return_value_policy::reference_internal: + case return_value_policy::_return_as_bytes: + case return_value_policy::_clif_automatic: + break; + } + if (!src) { + return none().release(); + } + + auto src_raw_ptr = src.get(); + assert(st.second != nullptr); + void *src_raw_void_ptr = static_cast(src_raw_ptr); + const detail::type_info *tinfo = st.second; + if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) { + // SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder. + // SMART_HOLDER_WIP: MISSING: keep_alive. + return existing_inst; + } + + auto inst = reinterpret_steal(make_new_instance(tinfo->type)); + auto *inst_raw_ptr = reinterpret_cast(inst.ptr()); + inst_raw_ptr->owned = true; + void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr(); + valueptr = src_raw_void_ptr; + + auto smhldr + = smart_holder::from_shared_ptr(std::shared_ptr(src, const_cast(st.first))); + tinfo->init_instance(inst_raw_ptr, static_cast(&smhldr)); + + if (policy == return_value_policy::reference_internal) { + keep_alive_impl(inst, parent); + } + + return inst.release(); +} + +template +handle smart_holder_from_shared_ptr(const std::shared_ptr &src, + return_value_policy policy, + handle parent, + const std::pair &st) { + return smart_holder_from_shared_ptr(std::const_pointer_cast(src), // Const2Mutbl + policy, + parent, + st); +} + +struct shared_ptr_parent_life_support { + PyObject *parent; + explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} { + Py_INCREF(parent); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(parent); + } +}; + +struct shared_ptr_trampoline_self_life_support { + PyObject *self; + explicit shared_ptr_trampoline_self_life_support(instance *inst) + : self{reinterpret_cast(inst)} { + gil_scoped_acquire gil; + Py_INCREF(self); + } + // NOLINTNEXTLINE(readability-make-member-function-const) + void operator()(void *) { + gil_scoped_acquire gil; + Py_DECREF(self); + } +}; + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + return std::unique_ptr(raw_ptr); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template ::value, int>::type = 0> +inline std::unique_ptr unique_with_deleter(T *raw_ptr, std::unique_ptr &&deleter) { + if (deleter == nullptr) { + pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no" + " instance available to return."); + } + return std::unique_ptr(raw_ptr, std::move(*deleter)); +} + +template +struct load_helper : value_and_holder_helper { + static std::shared_ptr make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) { + return std::shared_ptr(raw_ptr, shared_ptr_parent_life_support(parent.ptr())); + } + + std::shared_ptr load_as_shared_ptr(void *void_raw_ptr, + handle responsible_parent = nullptr) const { + if (!have_holder()) { + return nullptr; + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + smart_holder &hld = holder(); + hld.ensure_is_not_disowned("load_as_shared_ptr"); + if (hld.vptr_is_using_noop_deleter) { + if (responsible_parent) { + return make_shared_ptr_with_responsible_parent(static_cast(void_raw_ptr), + responsible_parent); + } + throw std::runtime_error("Non-owning holder (load_as_shared_ptr)."); + } + auto *type_raw_ptr = static_cast(void_raw_ptr); + if (hld.pointee_depends_on_holder_owner) { + auto *vptr_gd_ptr = std::get_deleter(hld.vptr); + if (vptr_gd_ptr != nullptr) { + std::shared_ptr released_ptr = vptr_gd_ptr->released_ptr.lock(); + if (released_ptr) { + return std::shared_ptr(released_ptr, type_raw_ptr); + } + std::shared_ptr to_be_released( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + vptr_gd_ptr->released_ptr = to_be_released; + return to_be_released; + } + auto *sptsls_ptr = std::get_deleter(hld.vptr); + if (sptsls_ptr != nullptr) { + // This code is reachable only if there are multiple registered_instances for the + // same pointee. + if (reinterpret_cast(loaded_v_h.inst) == sptsls_ptr->self) { + pybind11_fail("smart_holder_type_caster_support load_as_shared_ptr failure: " + "loaded_v_h.inst == sptsls_ptr->self"); + } + } + if (sptsls_ptr != nullptr + || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) { + return std::shared_ptr( + type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); + } + if (hld.vptr_is_external_shared_ptr) { + pybind11_fail("smart_holder_type_casters load_as_shared_ptr failure: not " + "implemented: trampoline-self-life-support for external shared_ptr " + "to type inheriting from std::enable_shared_from_this."); + } + pybind11_fail( + "smart_holder_type_casters: load_as_shared_ptr failure: internal inconsistency."); + } + std::shared_ptr void_shd_ptr = hld.template as_shared_ptr(); + return std::shared_ptr(void_shd_ptr, type_raw_ptr); + } + + template + std::unique_ptr load_as_unique_ptr(void *raw_void_ptr, + const char *context = "load_as_unique_ptr") { + if (!have_holder()) { + return unique_with_deleter(nullptr, std::unique_ptr()); + } + throw_if_uninitialized_or_disowned_holder(typeid(T)); + throw_if_instance_is_currently_owned_by_shared_ptr(); + holder().ensure_is_not_disowned(context); + holder().template ensure_compatible_rtti_uqp_del(context); + holder().ensure_use_count_1(context); + + T *raw_type_ptr = static_cast(raw_void_ptr); + + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible(raw_type_ptr); + if (self_life_support == nullptr && holder().pointee_depends_on_holder_owner) { + throw value_error("Alias class (also known as trampoline) does not inherit from " + "py::trampoline_self_life_support, therefore the ownership of this " + "instance cannot safely be transferred to C++."); + } + + // Temporary variable to store the extracted deleter in. + std::unique_ptr extracted_deleter; + + auto *gd = std::get_deleter(holder().vptr); + if (gd && gd->use_del_fun) { // Note the ensure_compatible_rtti_uqp_del() call above. + // In smart_holder_poc, a custom deleter is always stored in a guarded delete. + // The guarded delete's std::function actually points at the + // custom_deleter type, so we can verify it is of the custom deleter type and + // finally extract its deleter. + using custom_deleter_D = pybindit::memory::custom_deleter; + const auto &custom_deleter_ptr = gd->del_fun.template target(); + assert(custom_deleter_ptr != nullptr); + // Now that we have confirmed the type of the deleter matches the desired return + // value we can extract the function. + extracted_deleter = std::unique_ptr(new D(std::move(custom_deleter_ptr->deleter))); + } + + // Critical transfer-of-ownership section. This must stay together. + if (self_life_support != nullptr) { + holder().disown(); + } else { + holder().release_ownership(); + } + auto result = unique_with_deleter(raw_type_ptr, std::move(extracted_deleter)); + if (self_life_support != nullptr) { + self_life_support->activate_life_support(loaded_v_h); + } else { + void *value_void_ptr = loaded_v_h.value_ptr(); + loaded_v_h.value_ptr() = nullptr; + deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type); + } + // Critical section end. + + return result; + } +}; + +PYBIND11_NAMESPACE_END(smart_holder_type_caster_support) + +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + class type_caster_generic { public: PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info) @@ -577,6 +937,17 @@ class type_caster_generic { // Base methods for generic caster; there are overridden in copyable_holder_caster void load_value(value_and_holder &&v_h) { +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + smart_holder_type_caster_support::value_and_holder_helper v_h_helper; + v_h_helper.loaded_v_h = v_h; + if (v_h_helper.have_holder()) { + v_h_helper.throw_if_uninitialized_or_disowned_holder(cpptype->name()); + value = v_h_helper.holder().template as_raw_ptr_unowned(); + return; + } + } +#endif auto *&vptr = v_h.value_ptr(); // Lazy allocation for unallocated values: if (vptr == nullptr) { @@ -615,6 +986,21 @@ class type_caster_generic { } return false; } + bool try_as_void_ptr_capsule(handle src) { +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + // The `as_void_ptr_capsule` feature is needed for PyCLIF-SWIG interoperability + // in the Google-internal environment, but the current implementation is lacking + // any safety checks. To lower the risk potential, the feature is activated + // only if the smart_holder is used (PyCLIF-pybind11 uses `classh`). + if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { + value = try_as_void_ptr_capsule_get_pointer(src, cpptype->name()); + if (value != nullptr) { + return true; + } + } +#endif + return false; + } void check_holder_compat() {} PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) { @@ -746,6 +1132,10 @@ class type_caster_generic { return true; } + if (convert && cpptype && this_.try_as_void_ptr_capsule(src)) { + return true; + } + return false; } @@ -1055,6 +1445,17 @@ class type_caster_base : public type_caster_generic { return cast(std::addressof(src), policy, parent); } + static handle cast(itype &src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::_clif_automatic) { + if (is_move_constructible::value) { + policy = return_value_policy::move; + } else { + policy = return_value_policy::automatic; + } + } + return cast(const_cast(src), policy, parent); // Mutbl2Const + } + static handle cast(itype &&src, return_value_policy, handle parent) { return cast(std::addressof(src), return_value_policy::move, parent); } @@ -1086,6 +1487,9 @@ class type_caster_base : public type_caster_generic { static handle cast(const itype *src, return_value_policy policy, handle parent) { auto st = src_and_type(src); + if (policy == return_value_policy::_clif_automatic) { + policy = return_value_policy::copy; + } return type_caster_generic::cast(st.first, policy, parent, @@ -1094,6 +1498,17 @@ class type_caster_base : public type_caster_generic { make_move_constructor(src)); } + static handle cast(itype *src, return_value_policy policy, handle parent) { + if (policy == return_value_policy::_clif_automatic) { + if (parent) { + policy = return_value_policy::reference_internal; + } else { + policy = return_value_policy::reference; + } + } + return cast(const_cast(src), policy, parent); // Mutbl2Const + } + static handle cast_holder(const itype *src, const void *holder) { auto st = src_and_type(src); return type_caster_generic::cast(st.first, diff --git a/include/pybind11/detail/using_smart_holder.h b/include/pybind11/detail/using_smart_holder.h new file mode 100644 index 00000000..3d046320 --- /dev/null +++ b/include/pybind11/detail/using_smart_holder.h @@ -0,0 +1,33 @@ +// Copyright (c) 2024 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. + +#pragma once + +#include "common.h" +#include "internals.h" + +#include + +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT +# include "smart_holder_poc.h" +#endif + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT +using pybindit::memory::smart_holder; +#endif + +PYBIND11_NAMESPACE_BEGIN(detail) + +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT +template +using is_smart_holder = std::is_same; +#else +template +struct is_smart_holder : std::false_type {}; +#endif + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/embed.h b/include/pybind11/embed.h index 9d29eb82..0af77703 100644 --- a/include/pybind11/embed.h +++ b/include/pybind11/embed.h @@ -104,23 +104,13 @@ inline void initialize_interpreter_pre_pyconfig(bool init_signal_handlers, detail::precheck_interpreter(); Py_InitializeEx(init_signal_handlers ? 1 : 0); - // Before it was special-cased in python 3.8, passing an empty or null argv - // caused a segfault, so we have to reimplement the special case ourselves. - bool special_case = (argv == nullptr || argc <= 0); - - const char *const empty_argv[]{"\0"}; - const char *const *safe_argv = special_case ? empty_argv : argv; - if (special_case) { - argc = 1; - } - auto argv_size = static_cast(argc); // SetArgv* on python 3 takes wchar_t, so we have to convert. std::unique_ptr widened_argv(new wchar_t *[argv_size]); std::vector> widened_argv_entries; widened_argv_entries.reserve(argv_size); for (size_t ii = 0; ii < argv_size; ++ii) { - widened_argv_entries.emplace_back(detail::widen_chars(safe_argv[ii])); + widened_argv_entries.emplace_back(detail::widen_chars(argv[ii])); if (!widened_argv_entries.back()) { // A null here indicates a character-encoding failure or the python // interpreter out of memory. Give up. diff --git a/include/pybind11/eval.h b/include/pybind11/eval.h index bd5f981f..74d9b96b 100644 --- a/include/pybind11/eval.h +++ b/include/pybind11/eval.h @@ -19,7 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) inline void ensure_builtins_in_globals(object &global) { -#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x03080000 +#if defined(PYPY_VERSION) // Running exec and eval adds `builtins` module under `__builtins__` key to // globals if not yet present. Python 3.8 made PyRun_String behave // similarly. Let's also do that for older versions, for consistency. This diff --git a/include/pybind11/gil.h b/include/pybind11/gil.h index 6b0edaee..88881049 100644 --- a/include/pybind11/gil.h +++ b/include/pybind11/gil.h @@ -147,9 +147,7 @@ class gil_scoped_release { // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) tstate = PyEval_SaveThread(); if (disassoc) { - // Python >= 3.7 can remove this, it's an int before 3.7 - // NOLINTNEXTLINE(readability-qualified-auto) - auto key = internals.tstate; + auto key = internals.tstate; // NOLINT(readability-qualified-auto) PYBIND11_TLS_DELETE_VALUE(key); } } @@ -173,9 +171,7 @@ class gil_scoped_release { PyEval_RestoreThread(tstate); } if (disassoc) { - // Python >= 3.7 can remove this, it's an int before 3.7 - // NOLINTNEXTLINE(readability-qualified-auto) - auto key = detail::get_internals().tstate; + auto key = detail::get_internals().tstate; // NOLINT(readability-qualified-auto) PYBIND11_TLS_REPLACE_VALUE(key, tstate); } } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 32f82fe5..d25dbbc3 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -11,10 +11,11 @@ #pragma once #include "detail/class.h" +#include "detail/dynamic_raw_ptr_cast_if_possible.h" #include "detail/function_record_pyobject.h" #include "detail/init.h" #include "detail/native_enum_data.h" -#include "detail/smart_holder_sfinae_hooks_only.h" +#include "detail/using_smart_holder.h" #include "attr.h" #include "gil.h" #include "gil_safe_call_once.h" @@ -1433,8 +1434,7 @@ class generic_type : public object { public: PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) protected: - void initialize(const type_record &rec, - void *(*type_caster_module_local_load)(PyObject *, const type_info *) ) { + void initialize(const type_record &rec) { if (rec.scope && hasattr(rec.scope, "__dict__") && rec.scope.attr("__dict__").contains(rec.name)) { pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) @@ -1463,6 +1463,9 @@ class generic_type : public object { tinfo->simple_ancestors = true; tinfo->default_holder = rec.default_holder; tinfo->module_local = rec.module_local; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + tinfo->holder_enum_v = rec.holder_enum_v; +#endif with_internals([&](internals &internals) { auto tindex = std::type_index(*rec.type); @@ -1489,7 +1492,7 @@ class generic_type : public object { if (rec.module_local) { // Stash the local typeinfo and loader so that external modules can access it. - tinfo->module_local_load = type_caster_module_local_load; + tinfo->module_local_load = &type_caster_generic::local_load; setattr(m_ptr, PYBIND11_MODULE_LOCAL_ID, capsule(tinfo)); } } @@ -1625,49 +1628,7 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)( return pmf; } -#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT - -template -using default_holder_type = std::unique_ptr; - -# ifndef PYBIND11_SH_AVL -# define PYBIND11_SH_AVL(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if AVaiLable" -// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation -// of existing code. -# endif - -# define PYBIND11_SH_DEF(...) std::shared_ptr<__VA_ARGS__> // "Smart_Holder if DEFault" -// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation -// of existing code. - -# define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...) - -#else - -template -using default_holder_type = smart_holder; - -# ifndef PYBIND11_SH_AVL -# define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable" -// -------- std::shared_ptr(...) -- same length by design, to not disturb the indentation -// of existing code. -# endif - -# define PYBIND11_SH_DEF(...) ::pybind11::smart_holder // "Smart_Holder if DEFault" - -// This define could be hidden away inside detail/smart_holder_type_casters.h, but is kept here -// for clarity. -# define PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...) \ - namespace pybind11 { \ - namespace detail { \ - template <> \ - class type_caster : public type_caster_base {}; \ - template <> \ - class type_caster<__VA_ARGS__> : public type_caster_holder {}; \ - } \ - } - -#endif +PYBIND11_NAMESPACE_BEGIN(detail) // Helper for the property_cpp_function static member functions below. // The only purpose of these functions is to support .def_readonly & .def_readwrite. @@ -1676,8 +1637,7 @@ using default_holder_type = smart_holder; // against accidents. As a side-effect, it also explains why the syntactical overhead for // perfect forwarding is not needed. template -using must_be_member_function_pointer - = detail::enable_if_t::value, int>; +using must_be_member_function_pointer = enable_if_t::value, int>; // Note that property_cpp_function is intentionally in the main pybind11 namespace, // because user-defined specializations could be useful. @@ -1686,9 +1646,9 @@ using must_be_member_function_pointer // getter and setter functions. // WARNING: This classic implementation can lead to dangling pointers for raw pointer members. // See test_ptr() in tests/test_class_sh_property.py -// This implementation works as-is (and safely) for smart_holder std::shared_ptr members. -template -struct property_cpp_function { +// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members. +template +struct property_cpp_function_classic { template = 0> static cpp_function readonly(PM pm, const handle &hdl) { return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl)); @@ -1705,31 +1665,54 @@ struct property_cpp_function { } }; -// smart_holder specializations for raw pointer members. +PYBIND11_NAMESPACE_END(detail) + +template +struct property_cpp_function : detail::property_cpp_function_classic {}; + +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +struct both_t_and_d_use_type_caster_base : std::false_type {}; + +// `T` is assumed to be equivalent to `intrinsic_t`. +// `D` is may or may not be equivalent to `intrinsic_t`. +template +struct both_t_and_d_use_type_caster_base< + T, + D, + enable_if_t, type_caster>, + std::is_base_of>, make_caster>>::value>> + : std::true_type {}; + +// Specialization for raw pointer members, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // WARNING: Like the classic implementation, this implementation can lead to dangling pointers. // See test_ptr() in tests/test_class_sh_property.py // However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach: // https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 // This prevents disowning of the Python object owning the raw pointer member. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::type_uses_smart_holder_type_caster, - std::is_pointer>::value>> { - +struct property_cpp_function_sh_raw_ptr_member { using drp = typename std::remove_pointer::type; template = 0> static cpp_function readonly(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - D ptr = (*c_sp).*pm; - return std::shared_ptr(c_sp, ptr); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + D ptr = (*c_sp).*pm; + return std::shared_ptr(c_sp, ptr); + }, + is_method(hdl)); + } + return property_cpp_function_classic::readonly(pm, hdl); } template = 0> @@ -1739,81 +1722,95 @@ struct property_cpp_function< template = 0> static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, D value) { c.*pm = std::forward(value); }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function([pm](T &c, D value) { c.*pm = std::forward(std::move(value)); }, + is_method(hdl)); + } + return property_cpp_function_classic::write(pm, hdl); } }; -// smart_holder specializations for members held by-value. +// Specialization for members held by-value, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // The read functions return a shared_ptr to the member, emulating the PyCLIF approach: // https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233 // This prevents disowning of the Python object owning the member. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::type_uses_smart_holder_type_caster, - detail::none_of, - detail::is_std_unique_ptr, - detail::is_std_shared_ptr>>::value>> { - +struct property_cpp_function_sh_member_held_by_value { template = 0> static cpp_function readonly(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr::type> { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return std::shared_ptr::type>(c_sp, &(c_sp.get()->*pm)); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr::type> { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return std::shared_ptr::type>(c_sp, + &(c_sp.get()->*pm)); + }, + is_method(hdl)); + } + return property_cpp_function_classic::readonly(pm, hdl); } template = 0> static cpp_function read(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> std::shared_ptr { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return std::shared_ptr(c_sp, &(c_sp.get()->*pm)); - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> std::shared_ptr { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return std::shared_ptr(c_sp, &(c_sp.get()->*pm)); + }, + is_method(hdl)); + } + return property_cpp_function_classic::read(pm, hdl); } template = 0> static cpp_function write(PM pm, const handle &hdl) { - return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl)); + } + return property_cpp_function_classic::write(pm, hdl); } }; -// smart_holder specializations for std::unique_ptr members. +// Specialization for std::unique_ptr members, using smart_holder if that is the class_ holder, +// or falling back to the classic implementation if not. // read disowns the member unique_ptr. // write disowns the passed Python object. // readonly is disabled (static_assert) because there is no safe & intuitive way to make the member // accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning // the unique_ptr member is deemed highly prone to misunderstandings. template -struct property_cpp_function< - T, - D, - detail::enable_if_t, - detail::is_std_unique_ptr, - detail::type_uses_smart_holder_type_caster>::value>> { - +struct property_cpp_function_sh_unique_ptr_member { template = 0> static cpp_function readonly(PM, const handle &) { - static_assert(!detail::is_std_unique_ptr::value, + static_assert(!is_instantiation::value, "def_readonly cannot be used for std::unique_ptr members."); return cpp_function{}; // Unreachable. } template = 0> static cpp_function read(PM pm, const handle &hdl) { - return cpp_function( - [pm](handle c_hdl) -> D { - std::shared_ptr c_sp = detail::type_caster::shared_ptr_from_python(c_hdl); - return D{std::move(c_sp.get()->*pm)}; - }, - is_method(hdl)); + type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true); + if (tinfo->holder_enum_v == holder_enum_t::smart_holder) { + return cpp_function( + [pm](handle c_hdl) -> D { + std::shared_ptr c_sp + = type_caster>::shared_ptr_with_responsible_parent( + c_hdl); + return D{std::move(c_sp.get()->*pm)}; + }, + is_method(hdl)); + } + return property_cpp_function_classic::read(pm, hdl); } template = 0> @@ -1822,18 +1819,63 @@ struct property_cpp_function< } }; +PYBIND11_NAMESPACE_END(detail) + +template +struct property_cpp_function< + T, + D, + detail::enable_if_t, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_raw_ptr_member {}; + +template +struct property_cpp_function, + std::is_array, + detail::is_instantiation, + detail::is_instantiation>, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_member_held_by_value {}; + +template +struct property_cpp_function< + T, + D, + detail::enable_if_t, + detail::both_t_and_d_use_type_caster_base>::value>> + : detail::property_cpp_function_sh_unique_ptr_member {}; + +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +#if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) \ + && defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT) +// NOTE: THIS IS MEANT FOR STRESS-TESTING ONLY! +// As of PR #5257, for production use, there is no longer a strong reason to make +// smart_holder the default holder: +// Simply use `py::classh` (see below) instead of `py::class_` as needed. +// Running the pybind11 unit tests with smart_holder as the default holder is to ensure +// that `py::smart_holder` / `py::classh` is backward-compatible with all pre-existing +// functionality. +# define PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT +template +using default_holder_type = smart_holder; +#else +template +using default_holder_type = std::unique_ptr; +#endif + template class class_ : public detail::generic_type { + template + using is_holder = detail::is_holder_type; template using is_subtype = detail::is_strict_base_of; template using is_base = detail::is_strict_base_of; - template - using is_holder - = detail::any_of, - detail::all_of>, - detail::negation>, - detail::type_uses_smart_holder_type_caster>>; // struct instead of using here to help MSVC: template struct is_valid_class_option : detail::any_of, is_subtype, is_base> {}; @@ -1865,42 +1907,6 @@ class class_ : public detail::generic_type { none_of...>::value), "Error: multiple inheritance bases must be specified via class_ template options"); - static constexpr bool holder_is_smart_holder - = detail::is_smart_holder_type::value; - static constexpr bool wrapped_type_uses_smart_holder_type_caster - = detail::type_uses_smart_holder_type_caster::value; - static constexpr bool type_caster_type_is_type_caster_base_subtype - = std::is_base_of, detail::type_caster>::value; - // Necessary conditions, but not strict. - static_assert(!(detail::is_instantiation::value - && wrapped_type_uses_smart_holder_type_caster), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, std::unique_ptr)?"); - static_assert(!(detail::is_instantiation::value - && wrapped_type_uses_smart_holder_type_caster), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, std::shared_ptr)?"); - static_assert(!(holder_is_smart_holder && type_caster_type_is_type_caster_base_subtype), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)?"); -#ifdef PYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX - // Strict conditions cannot be enforced universally at the moment (PR #2836). - static_assert(holder_is_smart_holder - == (wrapped_type_uses_smart_holder_type_caster - || (detail::type_uses_type_caster_enum_type::value - && detail::smart_holder_is_default_holder_type)), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)" - " or collision with custom py::detail::type_caster?"); - static_assert(!holder_is_smart_holder - == (type_caster_type_is_type_caster_base_subtype - || (detail::type_uses_type_caster_enum_type::value - && !detail::smart_holder_is_default_holder_type)), - "py::class_ holder vs type_caster mismatch:" - " missing PYBIND11_TYPE_CASTER_BASE_HOLDER(T, ...)" - " or collision with custom py::detail::type_caster?"); -#endif - type_record record; record.scope = scope; record.name = name; @@ -1913,6 +1919,18 @@ class class_ : public detail::generic_type { // A more fitting name would be uses_unique_ptr_holder. record.default_holder = detail::is_instantiation::value; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + if (detail::is_instantiation::value) { + record.holder_enum_v = detail::holder_enum_t::std_unique_ptr; + } else if (detail::is_instantiation::value) { + record.holder_enum_v = detail::holder_enum_t::std_shared_ptr; + } else if (std::is_same::value) { + record.holder_enum_v = detail::holder_enum_t::smart_holder; + } else { + record.holder_enum_v = detail::holder_enum_t::custom_holder; + } +#endif + set_operator_new(&record); /* Register base classes specified via template arguments to class_, if any */ @@ -1927,7 +1945,7 @@ class class_ : public detail::generic_type { record.dealloc = dealloc_without_manipulating_gil; } - generic_type_initialize(record); + generic_type::initialize(record); if (has_alias) { with_internals([&](internals &internals) { @@ -2193,19 +2211,6 @@ class class_ : public detail::generic_type { } private: - template ::value, int> = 0> - void generic_type_initialize(const detail::type_record &record) { - generic_type::initialize(record, &detail::type_caster_generic::local_load); - } - - template ::value, int> = 0> - void generic_type_initialize(const detail::type_record &record) { - generic_type::initialize( - record, detail::type_caster_classh_enum_aware::get_local_load_function_ptr()); - } - /// Initialize holder object, variant 1: object derives from enable_shared_from_this template static void init_holder(detail::instance *inst, @@ -2259,8 +2264,8 @@ class class_ : public detail::generic_type { /// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes /// an optional pointer to an existing holder to use; if not specified and the instance is /// `.owned`, a new holder will be constructed to manage the value pointer. - template ::value, int> = 0> + template ::value, int> = 0> static void init_instance(detail::instance *inst, const void *holder_ptr) { auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); if (!v_h.instance_registered()) { @@ -2270,14 +2275,70 @@ class class_ : public detail::generic_type { init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr()); } - template ::value, int> = 0> - static void init_instance(detail::instance *inst, const void *holder_ptr) { - detail::type_caster_classh_enum_aware::template init_instance_for_type( - inst, holder_ptr); +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + + template + static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) { + return false; + } + + // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy + // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already, + // it is reused, irrespective of the return_value_policy in effect. + // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a + // direct base of WrappedType. + template + static bool try_initialization_using_shared_from_this( + holder_type *uninitialized_location, + WrappedType *value_ptr_w_t, + const std::enable_shared_from_this *) { + auto shd_ptr = std::dynamic_pointer_cast( + detail::try_get_shared_from_this(value_ptr_w_t)); + if (!shd_ptr) { + return false; + } + // Note: inst->owned ignored. + new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr)); + return true; + } + + template ::value, int> = 0> + static void init_instance(detail::instance *inst, const void *holder_const_void_ptr) { + // Need for const_cast is a consequence of the type_info::init_instance type: + // void (*init_instance)(instance *, const void *); + auto *holder_void_ptr = const_cast(holder_const_void_ptr); + + auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type))); + if (!v_h.instance_registered()) { + register_instance(inst, v_h.value_ptr(), v_h.type); + v_h.set_instance_registered(); + } + auto *uninitialized_location = std::addressof(v_h.holder()); + auto *value_ptr_w_t = v_h.value_ptr(); + bool pointee_depends_on_holder_owner + = detail::dynamic_raw_ptr_cast_if_possible(value_ptr_w_t) != nullptr; + if (holder_void_ptr) { + // Note: inst->owned ignored. + auto *holder_ptr = static_cast(holder_void_ptr); + new (uninitialized_location) holder_type(std::move(*holder_ptr)); + } else if (!try_initialization_using_shared_from_this( + uninitialized_location, value_ptr_w_t, value_ptr_w_t)) { + if (inst->owned) { + new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership( + value_ptr_w_t, /*void_cast_raw_ptr*/ pointee_depends_on_holder_owner)); + } else { + new (uninitialized_location) + holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t)); + } + } + v_h.holder().pointee_depends_on_holder_owner + = pointee_depends_on_holder_owner; + v_h.set_holder_constructed(); } +#endif // PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + // Deallocates an instance; via holder, if constructed; otherwise via operator delete. // NOTE: The Python error indicator needs to cleared BEFORE this function is called. // This is because we could be deallocating while cleaning up after a Python exception. @@ -2336,6 +2397,18 @@ class class_ : public detail::generic_type { } }; +#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT + +// Supports easier switching between py::class_ and py::class_: +// users can simply replace the `_` in `class_` with `h` or vice versa. +template +class classh : public class_ { +public: + using class_::class_; +}; + +#endif + /// Binds an existing constructor taking arguments Args... template detail::initimpl::constructor init() { diff --git a/include/pybind11/smart_holder.h b/include/pybind11/smart_holder.h index 03ec4bb8..5f568a55 100644 --- a/include/pybind11/smart_holder.h +++ b/include/pybind11/smart_holder.h @@ -1,29 +1,14 @@ -// Copyright (c) 2021 The Pybind Development Team. +// Copyright (c) 2021-2024 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. #pragma once #include "pybind11.h" -#include "detail/common.h" -#include "detail/smart_holder_type_casters.h" -#undef PYBIND11_SH_AVL // Undoing #define in pybind11.h - -#define PYBIND11_SH_AVL(...) ::pybind11::smart_holder // "Smart_Holder if AVaiLable" -// ---- std::shared_ptr(...) -- same length by design, to not disturb the indentation -// of existing code. - -PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) - -// Supports easier switching between py::class_ and py::class_: -// users can simply replace the `_` in `class_` with `h` or vice versa. -// Note though that the PYBIND11_SMART_HOLDER_TYPE_CASTERS(T) macro also needs to be -// added (for `classh`) or commented out (when falling back to `class_`). -template -class classh : public class_ { -public: - using class_::class_; -}; - -PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) +// Legacy macros introduced with smart_holder_type_casters implementation in 2021. +// Deprecated. +#define PYBIND11_TYPE_CASTER_BASE_HOLDER(...) +#define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...) +#define PYBIND11_SH_AVL(...) // "Smart_Holder if AVaiLable" +#define PYBIND11_SH_DEF(...) // "Smart_Holder if DEFault" diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index c228b164..76dbb913 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -591,21 +591,23 @@ struct visit_helper { template struct variant_caster; +PYBIND11_WARNING_PUSH +#if defined(__MINGW32__) +PYBIND11_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") +#endif + template