Merge branch 'master' into henryiii-patch-3

This commit is contained in:
Henry Schreiner
2026-01-21 17:58:09 -05:00
committed by GitHub
74 changed files with 2197 additions and 607 deletions

View File

@@ -48,7 +48,10 @@ Checks: |
readability-misplaced-array-index,
readability-non-const-parameter,
readability-qualified-auto,
readability-redundant-casting,
readability-redundant-function-ptr-dereference,
readability-redundant-inline-specifier,
readability-redundant-member-init,
readability-redundant-smartptr-get,
readability-redundant-string-cstr,
readability-simplify-subscript-expr,

View File

@@ -165,7 +165,7 @@ The valid options are:
* Use `cmake --build build -j12` to build with 12 cores (for example).
* Use `-G` and the name of a generator to use something different. `cmake
--help` lists the generators available.
- On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give
- On Unix, setting `CMAKE_GENERATOR=Ninja` in your environment will give
you automatic multithreading on all your CMake projects!
* Open the `CMakeLists.txt` with QtCreator to generate for that IDE.
* You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file

View File

@@ -4,12 +4,8 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
interval: "monthly"
groups:
actions:
patterns:
- "*"
ignore:
- dependency-name: actions/checkout
versions:
- "<5"

View File

@@ -88,7 +88,7 @@ jobs:
cmake-args: -DCMAKE_CXX_STANDARD=20 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
- runs-on: ubuntu-latest
python-version: '3.14'
cmake-args: -DCMAKE_CXX_STANDARD=14 -DCMAKE_CXX_FLAGS="-DPYBIND11_HAS_SUBINTERPRETER_SUPPORT=0"
cmake-args: -DCMAKE_CXX_STANDARD=14
- runs-on: ubuntu-latest
python-version: 'pypy-3.10'
cmake-args: -DCMAKE_CXX_STANDARD=14
@@ -229,6 +229,7 @@ jobs:
run: cmake --build . --target pytest
- name: Compiled tests
timeout-minutes: 3
run: cmake --build . --target cpptest
- name: Interface test
@@ -296,7 +297,7 @@ jobs:
- name: Valgrind cache
if: matrix.valgrind
uses: actions/cache@v4
uses: actions/cache@v5
id: cache-valgrind
with:
path: valgrind
@@ -334,6 +335,7 @@ jobs:
run: cmake --build --preset default --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build --preset default --target cpptest
- name: Visibility test
@@ -393,6 +395,7 @@ jobs:
run: cmake --build build --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build build --target cpptest
- name: Interface test
@@ -470,10 +473,10 @@ jobs:
# Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds
ubuntu-nvhpc7:
ubuntu-nvhpc:
if: github.event.pull_request.draft == false
runs-on: ubuntu-22.04
name: "🐍 3 • NVHPC 23.5 • C++17 • x64"
runs-on: ubuntu-24.04
name: "🐍 3 • NVHPC 25.11 • C++17 • x64"
timeout-minutes: 90
env:
@@ -482,6 +485,11 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Clean out unused stuff to save space
run: |
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL
sudo apt-get clean
- name: Add NVHPC Repo
run: |
echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \
@@ -489,10 +497,11 @@ jobs:
- name: Install 🐍 3 & NVHPC
run: |
sudo apt-get update -y && \
sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \
sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \
sudo apt-get update -y
sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy
sudo apt-get install -y --no-install-recommends nvhpc-25-11
sudo rm -rf /var/lib/apt/lists/*
apt-cache depends nvhpc-25-11
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pytest
@@ -502,7 +511,7 @@ jobs:
shell: bash
run: |
source /etc/profile.d/modules.sh
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/25.11
cmake -S . -B build -DDOWNLOAD_CATCH=ON \
-DCMAKE_CXX_STANDARD=17 \
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
@@ -510,12 +519,13 @@ jobs:
-DPYBIND11_TEST_FILTER="test_smart_ptr.cpp"
- name: Build
run: cmake --build build -j 2 --verbose
run: cmake --build build -j $(nproc) --verbose
- name: Python tests
run: cmake --build build --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build build --target cpptest
- name: Interface test
@@ -570,6 +580,7 @@ jobs:
run: cmake --build build --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build build --target cpptest
- name: Interface test
@@ -652,6 +663,7 @@ jobs:
cmake --build build-11 --target check
- name: C++ tests C++11
timeout-minutes: 3
run: |
set +e; source /opt/intel/oneapi/setvars.sh; set -e
cmake --build build-11 --target cpptest
@@ -689,6 +701,7 @@ jobs:
cmake --build build-17 --target check
- name: C++ tests C++17
timeout-minutes: 3
run: |
set +e; source /opt/intel/oneapi/setvars.sh; set -e
cmake --build build-17 --target cpptest
@@ -760,6 +773,7 @@ jobs:
run: cmake --build build --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build build --target cpptest
- name: Interface test
@@ -778,7 +792,8 @@ jobs:
timeout-minutes: 90
steps:
- uses: actions/checkout@v1 # v1 is required to run inside docker
# v1 required for i386/debian container; pinned to SHA to prevent dependabot updates
- uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9 # v1
- name: Install requirements
run: |
@@ -1000,6 +1015,7 @@ jobs:
run: cmake --build build --target pytest
- name: C++20 tests
timeout-minutes: 3
run: cmake --build build --target cpptest -j 2
- name: Interface test C++20
@@ -1076,6 +1092,7 @@ jobs:
run: cmake --build build --target pytest -j 2
- name: C++11 tests
timeout-minutes: 3
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2
- name: Interface test C++11
@@ -1100,6 +1117,7 @@ jobs:
run: cmake --build build2 --target pytest -j 2
- name: C++14 tests
timeout-minutes: 3
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2
- name: Interface test C++14
@@ -1124,6 +1142,7 @@ jobs:
run: cmake --build build3 --target pytest -j 2
- name: C++17 tests
timeout-minutes: 3
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target cpptest -j 2
- name: Interface test C++17
@@ -1153,7 +1172,7 @@ jobs:
uses: actions/checkout@v6
- name: Set up Clang
uses: egor-tensin/setup-clang@v1
uses: egor-tensin/setup-clang@v2
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v6
@@ -1195,6 +1214,7 @@ jobs:
run: cmake --build . --target pytest -j 2
- name: C++ tests
timeout-minutes: 3
run: cmake --build . --target cpptest -j 2
- name: Interface test
@@ -1205,3 +1225,136 @@ jobs:
- name: Clean directory
run: git clean -fdx
# Clang with MSVC/Windows SDK toolchain + python.org CPython (Windows ARM)
windows_arm_clang_msvc:
if: github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
os: [windows-11-arm]
python: ['3.13']
runs-on: "${{ matrix.os }}"
timeout-minutes: 90
name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-msvc"
steps:
- name: Show env
run: env
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python ${{ matrix.python }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
architecture: arm64
- name: Run pip installs
run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt
- name: Configure CMake
run: >
cmake -G Ninja -S . -B .
-DPYBIND11_WERROR=OFF
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=20
-DPython_ROOT_DIR="$env:Python_ROOT_DIR"
- name: Build
run: cmake --build . -j 2
- name: Python tests
run: cmake --build . --target pytest -j 2
- name: C++ tests
timeout-minutes: 3
run: cmake --build . --target cpptest -j 2
- name: Interface test
run: cmake --build . --target test_cmake_build -j 2
- name: Visibility test
run: cmake --build . --target test_cross_module_rtti -j 2
# Clang in MSYS2/MinGW-w64 CLANGARM64 toolchain + MSYS2 Python (Windows ARM)
windows_arm_clang_msys2:
if: github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
os: [windows-11-arm]
runs-on: "${{ matrix.os }}"
timeout-minutes: 90
name: "${{ matrix.os }} • clang-msys2"
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v6
with:
submodules: true
- uses: msys2/setup-msys2@v2
with:
msystem: CLANGARM64
update: true
install: |
mingw-w64-clang-aarch64-cmake
mingw-w64-clang-aarch64-clang
mingw-w64-clang-aarch64-ninja
mingw-w64-clang-aarch64-python-pip
mingw-w64-clang-aarch64-python-pytest
mingw-w64-clang-aarch64-python-numpy
- name: Debug info
run: |
clang++ --version
cmake --version
ninja --version
python --version
- name: Run pip installs
run: |
python -m pip install --upgrade pip
python -m pip install -r tests/requirements.txt
- name: Configure CMake
run: >-
cmake -S . -B build
-DPYBIND11_WERROR=OFF
-DDOWNLOAD_CATCH=ON
-DDOWNLOAD_EIGEN=ON
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=20
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
- name: Build
run: cmake --build build -j 2
- name: Python tests
run: cmake --build build --target pytest -j 2
- name: C++ tests
timeout-minutes: 3
run: PYTHONHOME=/clangarm64 PYTHONPATH=/clangarm64 cmake --build build --target cpptest -j 2
- name: Interface test
run: cmake --build build --target test_cmake_build -j 2
- name: Visibility test
run: cmake --build build --target test_cross_module_rtti -j 2

View File

@@ -39,10 +39,10 @@ jobs:
cmake: "3.15"
- runs-on: macos-14
cmake: "4.0"
cmake: "4.2"
- runs-on: windows-latest
cmake: "4.0"
cmake: "4.2"
name: 🐍 3.11 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
runs-on: ${{ matrix.runs-on }}

View File

@@ -33,7 +33,7 @@ jobs:
nox -s build
nox -s build_global
- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@v6
with:
name: Packages
path: dist/*
@@ -44,7 +44,7 @@ jobs:
needs: [build_wheel]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
with:
name: Packages
path: dist
@@ -53,7 +53,7 @@ jobs:
run: ls -lha dist/*.whl
- name: Upload wheel to Anaconda Cloud as nightly
uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2
uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
with:
artifacts_path: dist
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}

View File

@@ -72,13 +72,13 @@ jobs:
run: twine check dist/*
- name: Save standard package
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: standard
path: dist/pybind11-*
- name: Save global package
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: global
path: dist/*global-*
@@ -100,7 +100,7 @@ jobs:
steps:
# Downloads all to directories matching the artifact names
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
- name: Generate artifact attestation for sdist and wheel
uses: actions/attest-build-provenance@v3

View File

@@ -83,6 +83,7 @@ jobs:
run: cmake --build build --target pytest
- name: C++ tests
timeout-minutes: 3
run: cmake --build build --target cpptest
- name: Interface test

View File

@@ -66,6 +66,7 @@ jobs:
run: cmake --build build11 --target pytest -j 2
- name: C++11 tests
timeout-minutes: 3
run: cmake --build build11 --target cpptest -j 2
- name: Interface test C++11
@@ -87,6 +88,7 @@ jobs:
run: cmake --build build17 --target pytest
- name: C++17 tests
timeout-minutes: 3
run: cmake --build build17 --target cpptest
# Third build - C++17 mode with unstable ABI

View File

@@ -25,14 +25,14 @@ repos:
# Clang format the codebase automatically
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v21.1.6"
rev: "v21.1.8"
hooks:
- id: clang-format
types_or: [c++, c, cuda]
# Ruff, the Python auto-correcting linter/formatter written in Rust
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.7
rev: v0.14.10
hooks:
- id: ruff-check
args: ["--fix", "--show-fixes"]
@@ -40,7 +40,7 @@ repos:
# Check static types with mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.19.0"
rev: "v1.19.1"
hooks:
- id: mypy
args: []
@@ -115,9 +115,18 @@ repos:
rev: "v2.4.1"
hooks:
- id: codespell
exclude: ".supp$"
exclude: "(.supp|^pyproject.toml)$"
args: ["-x.codespell-ignore-lines", "-Lccompiler,intstruct"]
# Also check spelling
# Use mirror because pre-commit autoupdate confuses tags in the upstream repo.
# See https://github.com/crate-ci/typos/issues/390
- repo: https://github.com/adhtruong/mirrors-typos
rev: "v1.41.0"
hooks:
- id: typos
args: []
# Check for common shell mistakes
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: "v0.11.0.1"
@@ -142,7 +151,7 @@ repos:
# Check schemas on some of our YAML files
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.35.0
rev: 0.36.0
hooks:
- id: check-readthedocs
- id: check-github-workflows

View File

@@ -10,7 +10,7 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27")
cmake_policy(GET CMP0148 _pybind11_cmp0148)
endif()
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
if(_pybind11_cmp0148)
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})

View File

@@ -18,7 +18,7 @@ information, see :doc:`/compiling`.
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example)
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
@@ -302,7 +302,7 @@ Activating a Sub-interpreter
Once a sub-interpreter is created, you can "activate" it on a thread (and
acquire its GIL) by creating a :class:`subinterpreter_scoped_activate`
instance and passing it the sub-intepreter to be activated. The function
instance and passing it the sub-interpreter to be activated. The function
will acquire the sub-interpreter's GIL and make the sub-interpreter the
current active interpreter on the current thread for the lifetime of the
instance. When the :class:`subinterpreter_scoped_activate` instance goes out
@@ -492,4 +492,8 @@ Best Practices for sub-interpreter safety
So you must still consider the thread safety of your C++ code. Remember, in Python 3.12
sub-interpreters must be destroyed on the same thread that they were created on.
- When using sub-interpreters in free-threaded python builds, note that creating and destroying
sub-interpreters may initiate a "stop-the-world". Be sure to detach long-running C++ threads
from Python thread state (similar to releasing the GIL) to avoid deadlocks.
- Familiarize yourself with :ref:`misc_concurrency`.

View File

@@ -13,6 +13,161 @@ Changes will be added here periodically from the "Suggested changelog
entry" block in pull request descriptions.
## Version 3.0.2 (release date TBD)
Bug fixes:
- MSVC 19.16 and earlier were blocked from using `std::launder` due to internal compiler errors.
[#5968](https://github.com/pybind/pybind11/pull/5968)
- Internals destructors were updated to check the owning interpreter before clearing Python objects.
[#5965](https://github.com/pybind/pybind11/pull/5965)
- pybind11 internals were updated to be deallocated during (sub-)interpreter shutdown to avoid memory leaks.
[#5958](https://github.com/pybind/pybind11/pull/5958)
- Fixed ambiguous `str(handle)` construction for `object`-derived types like `kwargs` or `dict` by templatizing the constructor with SFINAE.
[#5949](https://github.com/pybind/pybind11/pull/5949)
- Fixed concurrency consistency for `internals_pp_manager` under multiple-interpreters.
[#5947](https://github.com/pybind/pybind11/pull/5947)
- Fixed MSVC LNK2001 in C++20 builds when /GL (whole program optimization) is enabled.
[#5939](https://github.com/pybind/pybind11/pull/5939)
- Added per-interpreter storage for `gil_safe_call_once_and_store` to make it safe under multi-interpreters.
[#5933](https://github.com/pybind/pybind11/pull/5933)
- A workaround for a GCC `-Warray-bounds` false positive in `argument_vector` was added.
[#5908](https://github.com/pybind/pybind11/pull/5908)
- Corrected a mistake where support for `__index__` was added, but the type hints did not reflect acceptance of `SupportsIndex` objects. Also fixed a long-standing bug: the complex-caster did not accept `__index__` in `convert` mode.
[#5891](https://github.com/pybind/pybind11/pull/5891)
- Fixed `*args/**kwargs` return types. Added type hinting to `py::make_tuple`.
[#5881](https://github.com/pybind/pybind11/pull/5881)
- Fixed compiler error in `type_caster_generic` when casting a `T` implicitly convertible from `T*`.
[#5873](https://github.com/pybind/pybind11/pull/5873)
- Updated `py::native_enum` bindings to unregister enum types on destruction, preventing a use-after-free when returning a destroyed enum instance.
[#5871](https://github.com/pybind/pybind11/pull/5871)
- Fixed undefined behavior that occurred when importing pybind11 modules from non-main threads created by C API modules or embedded python interpreters.
[#5870](https://github.com/pybind/pybind11/pull/5870)
- Fixed dangling pointer in `internals::registered_types_cpp_fast`.
[#5867](https://github.com/pybind/pybind11/pull/5867)
- Added support for `std::shared_ptr<T>` when loading module-local or conduit types from other modules.
[#5862](https://github.com/pybind/pybind11/pull/5862)
- Fixed thread-safety issues if types were concurrently registered while `get_local_type_info()` was called in free threaded Python.
[#5856](https://github.com/pybind/pybind11/pull/5856)
- Fixed py::float_ casting and py::int_ and py::float_ type hints.
[#5839](https://github.com/pybind/pybind11/pull/5839)
- Fixed two `smart_holder` bugs in `shared_ptr` and `unique_ptr` adoption with multiple/virtual inheritance:
- `shared_ptr` to-Python caster was updated to register the correct subobject pointer (fixes #5786).
- `unique_ptr` adoption was updated to own the proper object start while aliasing subobject pointers for registration, which fixed MSVC crashes during destruction.
[#5836](https://github.com/pybind/pybind11/pull/5836)
- Constrained `accessor::operator=` templates to avoid obscuring special members.
[#5832](https://github.com/pybind/pybind11/pull/5832)
- Fixed crash that can occur when finalizers acquire and release the GIL.
[#5828](https://github.com/pybind/pybind11/pull/5828)
- Fixed compiler detection in `pybind11/detail/pybind11_namespace_macros.h` for clang-cl on Windows, to address warning suppression macros.
[#5816](https://github.com/pybind/pybind11/pull/5816)
- Fixed compatibility with CMake policy CMP0190 by not always requiring a Python interpreter when cross-compiling.
[#5829](https://github.com/pybind/pybind11/pull/5829)
- Added a static assertion to disallow `keep_alive` and `call_guard` on properties.
[#5533](https://github.com/pybind/pybind11/pull/5533)
Internal:
- CMake policy limit was set to 4.1.
[#5944](https://github.com/pybind/pybind11/pull/5944)
- Improved performance of function calls between Python and C++ by switching to the "vectorcall" calling protocol.
[#5948](https://github.com/pybind/pybind11/pull/5948)
- Many C-style casts were replaced with C++-style casts.
[#5930](https://github.com/pybind/pybind11/pull/5930)
- Added `cast_sources` abstraction to `type_caster_generic`.
[#5866](https://github.com/pybind/pybind11/pull/5866)
- Improved the performance of from-Python conversions of legacy pybind11 enum objects bound by `py::enum_`.
[#5860](https://github.com/pybind/pybind11/pull/5860)
- Reduced size overhead by deduplicating functions' readable signatures and type information.
[#5857](https://github.com/pybind/pybind11/pull/5857)
- Used new Python 3.14 C APIs when available.
[#5854](https://github.com/pybind/pybind11/pull/5854)
- Improved performance of function dispatch and type casting by porting two-level type info lookup strategy from nanobind.
[#5842](https://github.com/pybind/pybind11/pull/5842)
- Updated `.gitignore` to exclude `__pycache__/` directories.
[#5838](https://github.com/pybind/pybind11/pull/5838)
- Changed internals to use `thread_local` instead of `thread_specific_storage` for increased performance.
[#5834](https://github.com/pybind/pybind11/pull/5834)
- Reduced function call overhead by using thread_local for loader_life_support when possible.
[#5830](https://github.com/pybind/pybind11/pull/5830)
- Removed heap allocation for the C++ argument array when dispatching functions with 6 or fewer arguments.
[#5824](https://github.com/pybind/pybind11/pull/5824)
Documentation:
- Fixed docstring for `long double` complex types to use `numpy.clongdouble` instead of the deprecated `numpy.longcomplex` (removed in NumPy 2.0).
[#5952](https://github.com/pybind/pybind11/pull/5952)
- The "Supported compilers" and "Supported platforms" sections in the main `README.rst` were replaced with a new "Supported platforms & compilers" section that points to the CI test matrix as the living source of truth.
[#5910](https://github.com/pybind/pybind11/pull/5910)
- Fixed documentation formatting.
[#5903](https://github.com/pybind/pybind11/pull/5903)
- Updated upgrade notes for `py::native_enum`.
[#5885](https://github.com/pybind/pybind11/pull/5885)
- Clarified in the docs to what extent bindings are global.
[#5859](https://github.com/pybind/pybind11/pull/5859)
Tests:
- Calls to `env.deprecated_call()` were replaced with direct calls to `pytest.deprecated_call()`.
[#5893](https://github.com/pybind/pybind11/pull/5893)
- Updated pytest configuration to use `log_level` instead of `log_cli_level`.
[#5890](https://github.com/pybind/pybind11/pull/5890)
CI:
- Added CI tests for windows-11-arm with clang/MSVC (currently python 3.13), windows-11-arm with clang/mingw (currently python 3.12).
[#5932](https://github.com/pybind/pybind11/pull/5932)
- These clang-tidy rules were added: `readability-redundant-casting`, `readability-redundant-inline-specifier`, `readability-redundant-member-init`
[#5924](https://github.com/pybind/pybind11/pull/5924)
- Replaced deprecated macos-13 runners with macos-15-intel in CI.
[#5916](https://github.com/pybind/pybind11/pull/5916)
- Restored `runs-on: windows-latest` in CI.
[#5835](https://github.com/pybind/pybind11/pull/5835)
## Version 3.0.1 (August 22, 2025)
Bug fixes:
@@ -167,7 +322,7 @@ New Features:
[#5665](https://github.com/pybind/pybind11/pull/5665) and consolidate code
[#5670](https://github.com/pybind/pybind11/pull/5670).
- Added API in `pybind11/subinterpreter.h` for embedding sub-intepreters (requires Python 3.12+).
- Added API in `pybind11/subinterpreter.h` for embedding sub-interpreters (requires Python 3.12+).
[#5666](https://github.com/pybind/pybind11/pull/5666)
- `py::native_enum` was added, for conversions between Python's native
@@ -1213,7 +1368,7 @@ Performance and style:
- Optimize Eigen sparse matrix casting by removing unnecessary
temporary. [#4064](https://github.com/pybind/pybind11/pull/4064)
- Avoid potential implicit copy/assignment constructors causing double
free in `strdup_gaurd`.
free in `strdup_guard`.
[#3905](https://github.com/pybind/pybind11/pull/3905)
- Enable clang-tidy checks `misc-definitions-in-headers`,
`modernize-loop-convert`, and `modernize-use-nullptr`.

View File

@@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example LANGUAGES CXX)
set(PYBIND11_FINDPYTHON ON)
@@ -447,7 +447,7 @@ See the `Config file`_ docstring for details of relevant CMake variables.
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED)
@@ -492,7 +492,7 @@ FindPython, pybind11 will detect this and use the existing targets instead:
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example LANGUAGES CXX)
find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED)
@@ -570,7 +570,7 @@ You can use these targets to build complex applications. For example, the
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
@@ -628,7 +628,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
.. code-block:: cmake
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(example LANGUAGES CXX)
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)

View File

@@ -23,8 +23,6 @@ idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
importlib-metadata==8.7.0
# via sphinx
jinja2==3.1.6
# via
# myst-parser
@@ -85,7 +83,5 @@ sphinxcontrib-serializinghtml==1.1.10
# via sphinx
sphinxcontrib-svg2pdfconverter==1.2.2
# via -r requirements.in
urllib3==2.5.0
urllib3==2.6.3
# via requests
zipp==3.23.0
# via importlib-metadata

View File

@@ -373,7 +373,7 @@ struct type_record {
+ (base_has_unique_ptr_holder ? "does not" : "does"));
}
bases.append((PyObject *) base_info->type);
bases.append(reinterpret_cast<PyObject *>(base_info->type));
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
dynamic_attr |= base_info->type->tp_dictoffset != 0;
@@ -721,7 +721,9 @@ template <typename... Extra,
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs);
return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs;
return named == 0
|| (self + named + static_cast<size_t>(has_args) + static_cast<size_t>(has_kwargs))
== nargs;
}
PYBIND11_NAMESPACE_END(detail)

View File

@@ -66,10 +66,11 @@ struct buffer_info {
bool readonly = false)
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) {
if (ndim != static_cast<ssize_t>(shape.size())
|| ndim != static_cast<ssize_t>(strides.size())) {
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
}
for (size_t i = 0; i < (size_t) ndim; ++i) {
for (size_t i = 0; i < static_cast<size_t>(ndim); ++i) {
size *= shape[i];
}
}
@@ -195,7 +196,7 @@ struct compare_buffer_info {
template <typename T>
struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
static bool compare(const buffer_info &b) {
return (size_t) b.itemsize == sizeof(T)
return static_cast<size_t>(b.itemsize) == sizeof(T)
&& (b.format == format_descriptor<T>::value
|| ((sizeof(T) == sizeof(long))
&& b.format == (std::is_unsigned<T>::value ? "L" : "l"))

View File

@@ -394,7 +394,8 @@ public:
}
/* Check if this is a C++ type */
const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr());
const auto &bases
= all_type_info(reinterpret_cast<PyTypeObject *>(type::handle_of(h).ptr()));
if (bases.size() == 1) { // Only allowing loading from a single-value type
value = values_and_holders(reinterpret_cast<instance *>(h.ptr())).begin()->value_ptr();
return true;
@@ -475,7 +476,7 @@ public:
private:
// Test if an object is a NumPy boolean (without fetching the type).
static inline bool is_numpy_bool(handle object) {
static bool is_numpy_bool(handle object) {
const char *type_name = Py_TYPE(object.ptr())->tp_name;
// Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support
return std::strcmp("numpy.bool", type_name) == 0
@@ -541,7 +542,7 @@ struct string_caster {
const auto *buffer
= reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
size_t length = static_cast<size_t>(PYBIND11_BYTES_SIZE(utfNbytes.ptr())) / sizeof(CharT);
// Skip BOM for UTF-16/32
if (UTF_N > 8) {
buffer++;
@@ -2077,8 +2078,7 @@ using is_pos_only = std::is_same<intrinsic_t<T>, pos_only>;
// forward declaration (definition in attr.h)
struct function_record;
/// (Inline size chosen mostly arbitrarily; 6 should pad function_call out to two cache lines
/// (16 pointers) in size.)
/// Inline size chosen mostly arbitrarily.
constexpr std::size_t arg_vector_small_size = 6;
/// Internal data associated with a single function call
@@ -2190,86 +2190,121 @@ private:
std::tuple<make_caster<Args>...> argcasters;
};
/// Helper class which collects only positional arguments for a Python function call.
/// A fancier version below can collect any argument, but this one is optimal for simple calls.
template <return_value_policy policy>
class simple_collector {
public:
template <typename... Ts>
explicit simple_collector(Ts &&...values)
: m_args(pybind11::make_tuple<policy>(std::forward<Ts>(values)...)) {}
const tuple &args() const & { return m_args; }
dict kwargs() const { return {}; }
tuple args() && { return std::move(m_args); }
/// Call a Python function and pass the collected arguments
object call(PyObject *ptr) const {
PyObject *result = PyObject_CallObject(ptr, m_args.ptr());
if (!result) {
throw error_already_set();
}
return reinterpret_steal<object>(result);
}
private:
tuple m_args;
};
// [workaround(intel)] Separate function required here
// We need to put this into a separate function because the Intel compiler
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
// (tested with ICC 2021.1 Beta 20200827).
template <typename... Args>
constexpr bool args_has_keyword_or_ds() {
return any_of<is_keyword_or_ds<Args>...>::value;
}
/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
template <return_value_policy policy>
class unpacking_collector {
public:
template <typename... Ts>
explicit unpacking_collector(Ts &&...values) {
// Tuples aren't (easily) resizable so a list is needed for collection,
// but the actual function call strictly requires a tuple.
auto args_list = list();
using expander = int[];
(void) expander{0, (process(args_list, std::forward<Ts>(values)), 0)...};
explicit unpacking_collector(Ts &&...values)
: m_names(reinterpret_steal<tuple>(
handle())) // initialize to null to avoid useless allocation of 0-length tuple
{
/*
Python can sometimes utilize an extra space before the arguments to prepend `self`.
This is important enough that there is a special flag for it:
PY_VECTORCALL_ARGUMENTS_OFFSET.
All we have to do is allocate an extra space at the beginning of this array, and set the
flag. Note that the extra space is not passed directly in to vectorcall.
*/
m_args.reserve(sizeof...(values) + 1);
m_args.push_back_null();
m_args = std::move(args_list);
if (args_has_keyword_or_ds<Ts...>()) {
list names_list;
// collect_arguments guarantees this can't be constructed with kwargs before the last
// positional so we don't need to worry about Ts... being in anything but normal python
// order.
using expander = int[];
(void) expander{0, (process(names_list, std::forward<Ts>(values)), 0)...};
m_names = reinterpret_steal<tuple>(PyList_AsTuple(names_list.ptr()));
} else {
auto not_used
= reinterpret_steal<list>(handle()); // initialize as null (to avoid an allocation)
using expander = int[];
(void) expander{0, (process(not_used, std::forward<Ts>(values)), 0)...};
}
}
const tuple &args() const & { return m_args; }
const dict &kwargs() const & { return m_kwargs; }
tuple args() && { return std::move(m_args); }
dict kwargs() && { return std::move(m_kwargs); }
/// Call a Python function and pass the collected arguments
object call(PyObject *ptr) const {
PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr());
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
if (m_names) {
nargs -= m_names.size();
}
PyObject *result = _PyObject_Vectorcall(
ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
if (!result) {
throw error_already_set();
}
return reinterpret_steal<object>(result);
}
tuple args() const {
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
if (m_names) {
nargs -= m_names.size();
}
tuple val(nargs);
for (size_t i = 0; i < nargs; ++i) {
// +1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
val[i] = reinterpret_borrow<object>(m_args[i + 1]);
}
return val;
}
dict kwargs() const {
dict val;
if (m_names) {
size_t offset = m_args.size() - m_names.size();
for (size_t i = 0; i < m_names.size(); ++i, ++offset) {
val[m_names[i]] = reinterpret_borrow<object>(m_args[offset]);
}
}
return val;
}
private:
// normal argument, possibly needing conversion
template <typename T>
void process(list &args_list, T &&x) {
auto o = reinterpret_steal<object>(
detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
if (!o) {
void process(list & /*names_list*/, T &&x) {
handle h = detail::make_caster<T>::cast(std::forward<T>(x), policy, {});
if (!h) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1));
#else
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1),
type_id<T>());
#endif
}
args_list.append(std::move(o));
m_args.push_back_steal(h.ptr()); // cast returns a new reference
}
void process(list &args_list, detail::args_proxy ap) {
// * unpacking
void process(list & /*names_list*/, detail::args_proxy ap) {
if (!ap) {
return;
}
for (auto a : ap) {
args_list.append(a);
m_args.push_back_borrow(a.ptr());
}
}
void process(list & /*args_list*/, arg_v a) {
// named argument
// NOLINTNEXTLINE(performance-unnecessary-value-param)
void process(list &names_list, arg_v a) {
assert(names_list);
if (!a.name) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
nameless_argument_error();
@@ -2277,7 +2312,8 @@ private:
nameless_argument_error(a.type);
#endif
}
if (m_kwargs.contains(a.name)) {
auto name = str(a.name);
if (names_list.contains(name)) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
multiple_values_error();
#else
@@ -2291,22 +2327,27 @@ private:
throw cast_error_unable_to_convert_call_arg(a.name, a.type);
#endif
}
m_kwargs[a.name] = std::move(a.value);
names_list.append(std::move(name));
m_args.push_back_borrow(a.value.ptr());
}
void process(list & /*args_list*/, detail::kwargs_proxy kp) {
// ** unpacking
void process(list &names_list, detail::kwargs_proxy kp) {
if (!kp) {
return;
}
for (auto k : reinterpret_borrow<dict>(kp)) {
if (m_kwargs.contains(k.first)) {
assert(names_list);
for (auto &&k : reinterpret_borrow<dict>(kp)) {
auto name = str(k.first);
if (names_list.contains(name)) {
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
multiple_values_error();
#else
multiple_values_error(str(k.first));
multiple_values_error(name);
#endif
}
m_kwargs[k.first] = k.second;
names_list.append(std::move(name));
m_args.push_back_borrow(k.second.ptr());
}
}
@@ -2332,39 +2373,20 @@ private:
}
private:
tuple m_args;
dict m_kwargs;
ref_small_vector<arg_vector_small_size> m_args;
tuple m_names;
};
// [workaround(intel)] Separate function required here
// We need to put this into a separate function because the Intel compiler
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
// (tested with ICC 2021.1 Beta 20200827).
template <typename... Args>
constexpr bool args_are_all_positional() {
return all_of<is_positional<Args>...>::value;
}
/// Collect only positional arguments for a Python function call
template <return_value_policy policy,
typename... Args,
typename = enable_if_t<args_are_all_positional<Args...>()>>
simple_collector<policy> collect_arguments(Args &&...args) {
return simple_collector<policy>(std::forward<Args>(args)...);
}
/// Collect all arguments, including keywords and unpacking (only instantiated when needed)
template <return_value_policy policy,
typename... Args,
typename = enable_if_t<!args_are_all_positional<Args...>()>>
/// Collect all arguments, including keywords and unpacking
template <return_value_policy policy, typename... Args>
unpacking_collector<policy> collect_arguments(Args &&...args) {
// Following argument order rules for generalized unpacking according to PEP 448
static_assert(constexpr_last<is_positional, Args...>()
< constexpr_first<is_keyword_or_ds, Args...>()
&& constexpr_last<is_s_unpacking, Args...>()
< constexpr_first<is_ds_unpacking, Args...>(),
"Invalid function call: positional args must precede keywords and ** unpacking; "
"* unpacking must precede ** unpacking");
static_assert(
constexpr_last<is_positional, Args...>() < constexpr_first<is_keyword_or_ds, Args...>(),
"Invalid function call: positional args must precede keywords and */** unpacking;");
static_assert(constexpr_last<is_s_unpacking, Args...>()
< constexpr_first<is_ds_unpacking, Args...>(),
"Invalid function call: * unpacking must precede ** unpacking");
return unpacking_collector<policy>(std::forward<Args>(args)...);
}

View File

@@ -66,24 +66,23 @@ union inline_array_or_vector {
inline_array iarray;
heap_vector hvector;
static_assert(std::is_trivially_move_constructible<ArrayT>::value,
"ArrayT must be trivially move constructible");
static_assert(std::is_trivially_destructible<ArrayT>::value,
"ArrayT must be trivially destructible");
inline_array_or_vector() : iarray() {}
~inline_array_or_vector() {
if (!is_inline()) {
if (is_inline()) {
iarray.~inline_array();
} else {
hvector.~heap_vector();
}
}
// Disable copy ctor and assignment.
inline_array_or_vector(const inline_array_or_vector &) = delete;
inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
if (rhs.is_inline()) {
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
new (&iarray) inline_array(std::move(rhs.iarray));
} else {
new (&hvector) heap_vector(std::move(rhs.hvector));
}
@@ -95,17 +94,16 @@ union inline_array_or_vector {
return *this;
}
if (rhs.is_inline()) {
if (!is_inline()) {
hvector.~heap_vector();
}
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
if (is_inline()) {
iarray.~inline_array();
} else {
if (is_inline()) {
new (&hvector) heap_vector(std::move(rhs.hvector));
} else {
hvector = std::move(rhs.hvector);
}
hvector.~heap_vector();
}
if (rhs.is_inline()) {
new (&iarray) inline_array(std::move(rhs.iarray));
} else {
new (&hvector) heap_vector(std::move(rhs.hvector));
}
return *this;
}
@@ -126,18 +124,16 @@ union inline_array_or_vector {
}
};
// small_vector-like container to avoid heap allocation for N or fewer
// arguments.
template <std::size_t N>
struct argument_vector {
template <typename T, std::size_t InlineSize>
struct small_vector {
public:
argument_vector() = default;
small_vector() = default;
// Disable copy ctor and assignment.
argument_vector(const argument_vector &) = delete;
argument_vector &operator=(const argument_vector &) = delete;
argument_vector(argument_vector &&) noexcept = default;
argument_vector &operator=(argument_vector &&) noexcept = default;
small_vector(const small_vector &) = delete;
small_vector &operator=(const small_vector &) = delete;
small_vector(small_vector &&) noexcept = default;
small_vector &operator=(small_vector &&) noexcept = default;
std::size_t size() const {
if (is_inline()) {
@@ -146,7 +142,14 @@ public:
return m_repr.hvector.vec.size();
}
handle &operator[](std::size_t idx) {
T const *data() const {
if (is_inline()) {
return m_repr.iarray.arr.data();
}
return m_repr.hvector.vec.data();
}
T &operator[](std::size_t idx) {
assert(idx < size());
if (is_inline()) {
return m_repr.iarray.arr[idx];
@@ -154,7 +157,7 @@ public:
return m_repr.hvector.vec[idx];
}
handle operator[](std::size_t idx) const {
T const &operator[](std::size_t idx) const {
assert(idx < size());
if (is_inline()) {
return m_repr.iarray.arr[idx];
@@ -162,28 +165,28 @@ public:
return m_repr.hvector.vec[idx];
}
void push_back(handle x) {
void push_back(const T &x) { emplace_back(x); }
void push_back(T &&x) { emplace_back(std::move(x)); }
template <typename... Args>
void emplace_back(Args &&...x) {
if (is_inline()) {
auto &ha = m_repr.iarray;
if (ha.size == N) {
move_to_heap_vector_with_reserved_size(N + 1);
push_back_slow_path(x);
if (ha.size == InlineSize) {
move_to_heap_vector_with_reserved_size(InlineSize + 1);
m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
} else {
ha.arr[ha.size++] = x;
ha.arr[ha.size++] = T(std::forward<Args>(x)...);
}
} else {
push_back_slow_path(x);
m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
}
}
template <typename Arg>
void emplace_back(Arg &&x) {
push_back(handle(x));
}
void reserve(std::size_t sz) {
if (is_inline()) {
if (sz > N) {
if (sz > InlineSize) {
move_to_heap_vector_with_reserved_size(sz);
}
} else {
@@ -192,7 +195,7 @@ public:
}
private:
using repr_type = inline_array_or_vector<handle, N>;
using repr_type = inline_array_or_vector<T, InlineSize>;
repr_type m_repr;
PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
@@ -201,37 +204,38 @@ private:
using heap_vector = typename repr_type::heap_vector;
heap_vector hv;
hv.vec.reserve(reserved_size);
std::copy(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
static_assert(std::is_nothrow_move_constructible<T>::value,
"this conversion is not exception safe");
static_assert(std::is_nothrow_move_constructible<heap_vector>::value,
"this conversion is not exception safe");
std::move(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
new (&m_repr.hvector) heap_vector(std::move(hv));
}
PYBIND11_NOINLINE void push_back_slow_path(handle x) { m_repr.hvector.vec.push_back(x); }
PYBIND11_NOINLINE void reserve_slow_path(std::size_t sz) { m_repr.hvector.vec.reserve(sz); }
bool is_inline() const { return m_repr.is_inline(); }
};
// small_vector-like container to avoid heap allocation for N or fewer
// arguments.
// Container to avoid heap allocation for kRequestedInlineSize or fewer booleans.
template <std::size_t kRequestedInlineSize>
struct args_convert_vector {
struct small_vector<bool, kRequestedInlineSize> {
private:
public:
args_convert_vector() = default;
small_vector() = default;
// Disable copy ctor and assignment.
args_convert_vector(const args_convert_vector &) = delete;
args_convert_vector &operator=(const args_convert_vector &) = delete;
args_convert_vector(args_convert_vector &&) noexcept = default;
args_convert_vector &operator=(args_convert_vector &&) noexcept = default;
small_vector(const small_vector &) = delete;
small_vector &operator=(const small_vector &) = delete;
small_vector(small_vector &&) noexcept = default;
small_vector &operator=(small_vector &&) noexcept = default;
args_convert_vector(std::size_t count, bool value) {
small_vector(std::size_t count, bool value) {
if (count > kInlineSize) {
new (&m_repr.hvector) typename repr_type::heap_vector(count, value);
} else {
auto &inline_arr = m_repr.iarray;
inline_arr.arr.fill(value ? std::size_t(-1) : 0);
inline_arr.arr.fill(value ? static_cast<std::size_t>(-1) : 0);
inline_arr.size = static_cast<decltype(inline_arr.size)>(count);
}
}
@@ -273,9 +277,9 @@ public:
assert(wbi.word < kWords);
assert(wbi.bit < kBitsPerWord);
if (b) {
ha.arr[wbi.word] |= (std::size_t(1) << wbi.bit);
ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
} else {
ha.arr[wbi.word] &= ~(std::size_t(1) << wbi.bit);
ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
}
assert(operator[](ha.size - 1) == b);
}
@@ -284,7 +288,24 @@ public:
}
}
void swap(args_convert_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
void set(std::size_t idx, bool value = true) {
if (is_inline()) {
auto &ha = m_repr.iarray;
assert(ha.size < kInlineSize);
const auto wbi = word_and_bit_index(idx);
assert(wbi.word < kWords);
assert(wbi.bit < kBitsPerWord);
if (value) {
ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
} else {
ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
}
} else {
m_repr.hvector.vec[idx] = value;
}
}
void swap(small_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
private:
struct WordAndBitIndex {
@@ -300,7 +321,7 @@ private:
const auto wbi = word_and_bit_index(idx);
assert(wbi.word < kWords);
assert(wbi.bit < kBitsPerWord);
return m_repr.iarray.arr[wbi.word] & (std::size_t(1) << wbi.bit);
return m_repr.iarray.arr[wbi.word] & (static_cast<std::size_t>(1) << wbi.bit);
}
PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
@@ -326,5 +347,71 @@ private:
bool is_inline() const { return m_repr.is_inline(); }
};
// Container to avoid heap allocation for N or fewer arguments.
template <size_t N>
using argument_vector = small_vector<handle, N>;
// Container to avoid heap allocation for N or fewer booleans.
template <size_t N>
using args_convert_vector = small_vector<bool, N>;
/// A small_vector of PyObject* that holds references and releases them on destruction.
/// This provides explicit ownership semantics without relying on py::object's
/// destructor, and avoids the need for reinterpret_cast when passing to vectorcall.
template <std::size_t InlineSize>
class ref_small_vector {
public:
ref_small_vector() = default;
~ref_small_vector() {
for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
Py_XDECREF(m_ptrs[i]);
}
}
// Disable copy (prevent accidental double-decref)
ref_small_vector(const ref_small_vector &) = delete;
ref_small_vector &operator=(const ref_small_vector &) = delete;
// Move is allowed
ref_small_vector(ref_small_vector &&other) noexcept : m_ptrs(std::move(other.m_ptrs)) {
// other.m_ptrs is now empty, so its destructor won't decref anything
}
ref_small_vector &operator=(ref_small_vector &&other) noexcept {
if (this != &other) {
// Decref our current contents
for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
Py_XDECREF(m_ptrs[i]);
}
m_ptrs = std::move(other.m_ptrs);
}
return *this;
}
/// Add a pointer, taking ownership (no incref, will decref on destruction)
void push_back_steal(PyObject *p) { m_ptrs.push_back(p); }
/// Add a pointer, borrowing (increfs now, will decref on destruction)
void push_back_borrow(PyObject *p) {
Py_XINCREF(p);
m_ptrs.push_back(p);
}
/// Add a null pointer (for PY_VECTORCALL_ARGUMENTS_OFFSET slot)
void push_back_null() { m_ptrs.push_back(nullptr); }
void reserve(std::size_t sz) { m_ptrs.reserve(sz); }
std::size_t size() const { return m_ptrs.size(); }
PyObject *operator[](std::size_t idx) const { return m_ptrs[idx]; }
PyObject *const *data() const { return m_ptrs.data(); }
private:
small_vector<PyObject *, InlineSize> m_ptrs;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -71,7 +71,7 @@ inline PyTypeObject *make_static_property_type() {
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
if (!heap_type) {
pybind11_fail("make_static_property_type(): error allocating type!");
}
@@ -98,7 +98,7 @@ inline PyTypeObject *make_static_property_type() {
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
return type;
@@ -207,7 +207,7 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
/// Cleanup the type-info for a pybind11-registered type.
extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
with_internals([obj](internals &internals) {
with_internals_if_internals([obj](internals &internals) {
auto *type = (PyTypeObject *) obj;
// A pybind11-registered type will:
@@ -265,7 +265,7 @@ inline PyTypeObject *make_default_metaclass() {
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
if (!heap_type) {
pybind11_fail("make_default_metaclass(): error allocating metaclass!");
}
@@ -291,7 +291,7 @@ inline PyTypeObject *make_default_metaclass() {
pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
return type;
@@ -306,7 +306,7 @@ inline void traverse_offset_bases(void *valueptr,
instance *self,
bool (*f)(void * /*parentptr*/, instance * /*self*/)) {
for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
if (auto *parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
if (auto *parent_tinfo = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()))) {
for (auto &c : parent_tinfo->implicit_casts) {
if (c.first == tinfo->cpptype) {
auto *parentptr = c.second(valueptr);
@@ -530,7 +530,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
if (!heap_type) {
pybind11_fail("make_object_base_type(): error allocating type!");
}
@@ -557,11 +557,11 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string());
}
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
return (PyObject *) heap_type;
return reinterpret_cast<PyObject *>(heap_type);
}
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
@@ -746,7 +746,7 @@ inline PyObject *make_new_python_type(const type_record &rec) {
/* Allocate memory for docstring (Python will free this later on) */
size_t size = std::strlen(rec.doc) + 1;
#if PY_VERSION_HEX >= 0x030D0000
tp_doc = (char *) PyMem_MALLOC(size);
tp_doc = static_cast<char *>(PyMem_MALLOC(size));
#else
tp_doc = (char *) PyObject_MALLOC(size);
#endif
@@ -761,10 +761,10 @@ inline PyObject *make_new_python_type(const type_record &rec) {
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto *metaclass
= rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() : internals.default_metaclass;
auto *metaclass = rec.metaclass.ptr() ? reinterpret_cast<PyTypeObject *>(rec.metaclass.ptr())
: internals.default_metaclass;
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
if (!heap_type) {
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
}
@@ -777,7 +777,7 @@ inline PyObject *make_new_python_type(const type_record &rec) {
auto *type = &heap_type->ht_type;
type->tp_name = full_name;
type->tp_doc = tp_doc;
type->tp_base = type_incref((PyTypeObject *) base);
type->tp_base = type_incref(reinterpret_cast<PyTypeObject *>(base));
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
if (!bases.empty()) {
type->tp_bases = bases.release().ptr();
@@ -818,18 +818,18 @@ inline PyObject *make_new_python_type(const type_record &rec) {
/* Register type with the parent scope */
if (rec.scope) {
setattr(rec.scope, rec.name, (PyObject *) type);
setattr(rec.scope, rec.name, reinterpret_cast<PyObject *>(type));
} else {
Py_INCREF(type); // Keep it alive forever (reference leak)
}
if (module_) { // Needed by pydoc
setattr((PyObject *) type, "__module__", module_);
setattr(reinterpret_cast<PyObject *>(type), "__module__", module_);
}
PYBIND11_SET_OLDPY_QUALNAME(type, qualname);
return (PyObject *) type;
return reinterpret_cast<PyObject *>(type);
}
PYBIND11_NAMESPACE_END(detail)

View File

@@ -87,7 +87,7 @@
# endif
#endif
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914))
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1920)) // See PR #5968
# define PYBIND11_STD_LAUNDER std::launder
# define PYBIND11_HAS_STD_LAUNDER 1
#else
@@ -441,12 +441,11 @@ Note that this is run once for each (sub-)interpreter the module is imported int
possibly concurrently. The PyModuleDef is allowed to be static, but the PyObject* resulting from
PyModuleDef_Init should be treated like any other PyObject (so not shared across interpreters).
*/
#define PYBIND11_MODULE_PYINIT(name, pre_init, ...) \
#define PYBIND11_MODULE_PYINIT(name, ...) \
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
PYBIND11_PLUGIN_IMPL(name) { \
PYBIND11_CHECK_PYTHON_VERSION \
pre_init; \
PYBIND11_ENSURE_INTERNALS_READY \
pybind11::detail::ensure_internals(); \
static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots( \
&PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
@@ -465,6 +464,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \
try { \
pybind11::detail::ensure_internals(); \
auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \
if (!pybind11::detail::get_cached_module(m.attr("__spec__").attr("name"))) { \
PYBIND11_CONCAT(pybind11_init_, name)(m); \
@@ -518,8 +518,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
\endrst */
#define PYBIND11_MODULE(name, variable, ...) \
PYBIND11_MODULE_PYINIT( \
name, (pybind11::detail::get_num_interpreters_seen() += 1), ##__VA_ARGS__) \
PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__) \
PYBIND11_MODULE_EXEC(name, variable)
// pop gnu-zero-variadic-macro-arguments
@@ -590,14 +589,10 @@ enum class return_value_policy : uint8_t {
PYBIND11_NAMESPACE_BEGIN(detail)
inline static constexpr int log2(size_t n, int k = 0) {
return (n <= 1) ? k : log2(n >> 1, k + 1);
}
static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); }
// Returns the size as a multiple of sizeof(void *), rounded up.
inline static constexpr size_t size_in_ptrs(size_t s) {
return 1 + ((s - 1) >> log2(sizeof(void *)));
}
static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); }
/**
* The space to allocate for simple layout instance holders (see below) in multiple of the size of

View File

@@ -21,13 +21,13 @@ inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) {
return bool(internals.registered_types_py.find(type_obj)
!= internals.registered_types_py.end());
#else
return bool(type_obj->tp_new == pybind11_object_new);
return (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));
return ((descr != nullptr) && PyInstanceMethod_Check(descr));
}
inline object try_get_cpp_conduit_method(PyObject *obj) {

View File

@@ -126,7 +126,7 @@ inline bool is_function_record_PyObject(PyObject *obj) {
inline function_record *function_record_ptr_from_PyObject(PyObject *obj) {
if (is_function_record_PyObject(obj)) {
return ((detail::function_record_PyObject *) obj)->cpp_func_rec;
return (reinterpret_cast<detail::function_record_PyObject *>(obj))->cpp_func_rec;
}
return nullptr;
}
@@ -137,7 +137,7 @@ inline object function_record_PyObject_New() {
throw error_already_set();
}
py_func_rec->cpp_func_rec = nullptr; // For clarity/purity. Redundant in practice.
return reinterpret_steal<object>((PyObject *) py_func_rec);
return reinterpret_steal<object>(reinterpret_cast<PyObject *>(py_func_rec));
}
PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)

View File

@@ -476,9 +476,9 @@ void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias)
return;
}
// Our tests never run into an unset dict, but being careful here for now (see #5658)
auto dict = getattr((PyObject *) v_h.inst, "__dict__", none());
auto dict = getattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", none());
if (dict.is_none()) {
setattr((PyObject *) v_h.inst, "__dict__", d);
setattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", d);
} else {
// Keep the original object dict and just update it
if (PyDict_Update(dict.ptr(), d.ptr()) < 0) {

View File

@@ -103,7 +103,7 @@ public:
// However, in GraalPy (as of v24.2 or older), TSS is implemented by Java and this call
// requires a living Python interpreter.
#ifdef GRAALVM_PYTHON
if (!Py_IsInitialized() || _Py_IsFinalizing()) {
if (Py_IsInitialized() == 0 || _Py_IsFinalizing() != 0) {
return;
}
#endif
@@ -143,6 +143,38 @@ inline PyTypeObject *make_default_metaclass();
inline PyObject *make_object_base_type(PyTypeObject *metaclass);
inline void translate_exception(std::exception_ptr p);
inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
return PyThreadState_GET();
#elif PY_VERSION_HEX < 0x030D0000
return _PyThreadState_UncheckedGet();
#else
return PyThreadState_GetUnchecked();
#endif
}
inline PyInterpreterState *get_interpreter_state_unchecked() {
auto *tstate = get_thread_state_unchecked();
return tstate ? tstate->interp : nullptr;
}
inline object get_python_state_dict() {
object state_dict;
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else
auto *istate = get_interpreter_state_unchecked();
if (istate) {
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
}
#endif
if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
throw error_already_set();
}
return state_dict;
}
// Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly
// other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module
// even when `A` is the same, non-hidden-visibility type (e.g. from a common include). Under
@@ -186,7 +218,7 @@ template <typename value_type>
using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;
struct override_hash {
inline size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
size_t value = std::hash<const void *>()(v.first);
value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
return value;
@@ -285,11 +317,8 @@ struct internals {
internals()
: static_property_type(make_static_property_type()),
default_metaclass(make_default_metaclass()) {
default_metaclass(make_default_metaclass()), istate(get_interpreter_state_unchecked()) {
tstate.set(nullptr); // See PR #5870
PyThreadState *cur_tstate = PyThreadState_Get();
istate = cur_tstate->interp;
registered_exception_translators.push_front(&translate_exception);
#ifdef Py_GIL_DISABLED
// Scale proportional to the number of cores. 2x is a heuristic to reduce contention.
@@ -308,7 +337,19 @@ struct internals {
internals(internals &&other) = delete;
internals &operator=(const internals &other) = delete;
internals &operator=(internals &&other) = delete;
~internals() = default;
~internals() {
// Normally this destructor runs during interpreter finalization and it may DECREF things.
// In odd finalization scenarios it might end up running after the interpreter has
// completely shut down, In that case, we should not decref these objects because pymalloc
// is gone. This also applies across sub-interpreters, we should only DECREF when the
// original owning interpreter is active.
auto *cur_istate = get_interpreter_state_unchecked();
if (cur_istate && cur_istate == istate) {
Py_CLEAR(instance_base);
Py_CLEAR(default_metaclass);
Py_CLEAR(static_property_type);
}
}
};
// the internals struct (above) is shared between all the modules. local_internals are only
@@ -318,6 +359,8 @@ struct internals {
// impact any other modules, because the only things accessing the local internals is the
// module that contains them.
struct local_internals {
local_internals() : istate(get_interpreter_state_unchecked()) {}
// It should be safe to use fast_type_map here because this entire
// data structure is scoped to our single module, and thus a single
// DSO and single instance of type_info for any particular type.
@@ -325,6 +368,19 @@ struct local_internals {
std::forward_list<ExceptionTranslator> registered_exception_translators;
PyTypeObject *function_record_py_type = nullptr;
PyInterpreterState *istate = nullptr;
~local_internals() {
// Normally this destructor runs during interpreter finalization and it may DECREF things.
// In odd finalization scenarios it might end up running after the interpreter has
// completely shut down, In that case, we should not decref these objects because pymalloc
// is gone. This also applies across sub-interpreters, we should only DECREF when the
// original owning interpreter is active.
auto *cur_istate = get_interpreter_state_unchecked();
if (cur_istate && cur_istate == istate) {
Py_CLEAR(function_record_py_type);
}
}
};
enum class holder_enum_t : uint8_t {
@@ -408,21 +464,12 @@ struct native_enum_record {
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
return PyThreadState_GET();
#elif PY_VERSION_HEX < 0x030D0000
return _PyThreadState_UncheckedGet();
#else
return PyThreadState_GetUnchecked();
#endif
}
/// We use this counter to figure out if there are or have been multiple subinterpreters active at
/// any point. This must never decrease while any interpreter may be running in any thread!
inline std::atomic<int> &get_num_interpreters_seen() {
static std::atomic<int> counter(0);
return counter;
/// We use this to figure out if there are or have been multiple subinterpreters active at any
/// point. This must never go from true to false while any interpreter may be running in any
/// thread!
inline std::atomic_bool &has_seen_non_main_interpreter() {
static std::atomic_bool multi(false);
return multi;
}
template <class T,
@@ -529,25 +576,85 @@ inline void translate_local_exception(std::exception_ptr p) {
}
#endif
inline object get_python_state_dict() {
object state_dict;
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else
# if PY_VERSION_HEX < 0x03090000
PyInterpreterState *istate = _PyInterpreterState_Get();
# else
PyInterpreterState *istate = PyInterpreterState_Get();
# endif
if (istate) {
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
// Get or create per-storage capsule in the current interpreter's state dict.
// - The storage is interpreter-dependent: different interpreters will have different storage.
// This is important when using multiple-interpreters, to avoid sharing unshareable objects
// between interpreters.
// - There is one storage per `key` in an interpreter and it is accessible between all extensions
// in the same interpreter.
// - The life span of the storage is tied to the interpreter: it will be kept alive until the
// interpreter shuts down.
//
// Use test-and-set pattern with `PyDict_SetDefault` for thread-safe concurrent access.
// WARNING: There can be multiple threads creating the storage at the same time, while only one
// will succeed in inserting its capsule into the dict. Therefore, the deleter will be
// used to clean up the storage of the unused capsules.
//
// Returns: pair of (pointer to storage, bool indicating if newly created).
// The bool follows std::map::insert convention: true = created, false = existed.
template <typename Payload>
std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
void (*dtor)(PyObject *) = nullptr) {
error_scope err_scope; // preserve any existing Python error states
auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
PyObject *capsule_obj = nullptr;
bool created = false;
// Try to get existing storage (fast path).
capsule_obj = dict_getitemstring(state_dict.ptr(), key);
if (capsule_obj == nullptr) {
if (PyErr_Occurred()) {
throw error_already_set();
}
// Storage doesn't exist yet, create a new one.
// Use unique_ptr for exception safety: if capsule creation throws, the storage is
// automatically deleted.
auto storage_ptr = std::unique_ptr<Payload>(new Payload{});
auto new_capsule
= capsule(storage_ptr.get(),
// The destructor will be called when the capsule is GC'ed.
// If the insert below fails (entry already in the dict), then this
// destructor will be called on the newly created capsule at the end of this
// function, and we want to just release this memory.
/*destructor=*/[](void *v) { delete static_cast<Payload *>(v); });
// At this point, the capsule object is created successfully.
// Release the unique_ptr and let the capsule object own the storage to avoid double-free.
(void) storage_ptr.release();
// Use `PyDict_SetDefault` for atomic test-and-set:
// - If key doesn't exist, inserts our capsule and returns it.
// - If key exists (another thread inserted first), returns the existing value.
// This is thread-safe because `PyDict_SetDefault` will hold a lock on the dict.
//
// NOTE: Here we use `PyDict_SetDefault` instead of `PyDict_SetDefaultRef` because the
// capsule is kept alive until interpreter shutdown, so we do not need to handle
// incref and decref here.
capsule_obj = dict_setdefaultstring(state_dict.ptr(), key, new_capsule.ptr());
if (capsule_obj == nullptr) {
throw error_already_set();
}
created = (capsule_obj == new_capsule.ptr());
// - If key already existed, our `new_capsule` is not inserted, it will be destructed when
// going out of scope here, and will call the destructor set above.
// - Otherwise, our `new_capsule` is now in the dict, and it owns the storage and the state
// dict will incref it. We need to set the caller's destructor on it, which will be
// called when the interpreter shuts down.
if (created && dtor) {
if (PyCapsule_SetDestructor(capsule_obj, dtor) < 0) {
throw error_already_set();
}
}
}
#endif
if (!state_dict) {
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
// Get the storage pointer from the capsule.
void *raw_ptr = PyCapsule_GetPointer(capsule_obj, /*name=*/nullptr);
if (!raw_ptr) {
raise_from(PyExc_SystemError,
"pybind11::detail::atomic_get_or_create_in_state_dict() FAILED");
throw error_already_set();
}
return state_dict;
return std::pair<Payload *, bool>(static_cast<Payload *>(raw_ptr), created);
}
template <typename InternalsType>
@@ -555,7 +662,7 @@ class internals_pp_manager {
public:
using on_fetch_function = void(InternalsType *);
inline static internals_pp_manager &get_instance(char const *id, on_fetch_function *on_fetch) {
static internals_pp_manager &get_instance(char const *id, on_fetch_function *on_fetch) {
static internals_pp_manager instance(id, on_fetch);
return instance;
}
@@ -564,7 +671,7 @@ public:
/// acquire the GIL. Will never return nullptr.
std::unique_ptr<InternalsType> *get_pp() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
if (has_seen_non_main_interpreter()) {
// Whenever the interpreter changes on the current thread we need to invalidate the
// internals_pp so that it can be pulled from the interpreter's state dict. That is
// slow, so we use the current PyThreadState to check if it is necessary.
@@ -590,7 +697,7 @@ public:
/// Drop all the references we're currently holding.
void unref() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
if (has_seen_non_main_interpreter()) {
last_istate_tls() = nullptr;
internals_p_tls() = nullptr;
return;
@@ -601,14 +708,13 @@ public:
void destroy() {
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (get_num_interpreters_seen() > 1) {
if (has_seen_non_main_interpreter()) {
auto *tstate = get_thread_state_unchecked();
// this could be called without an active interpreter, just use what was cached
if (!tstate || tstate->interp == last_istate_tls()) {
auto tpp = internals_p_tls();
if (tpp) {
delete tpp;
}
delete tpp;
}
unref();
return;
@@ -622,27 +728,32 @@ private:
internals_pp_manager(char const *id, on_fetch_function *on_fetch)
: holder_id_(id), on_fetch_(on_fetch) {}
static void internals_shutdown(PyObject *capsule) {
auto *pp = static_cast<std::unique_ptr<InternalsType> *>(
PyCapsule_GetPointer(capsule, nullptr));
if (pp) {
pp->reset();
}
// We reset the unique_ptr's contents but cannot delete the unique_ptr itself here.
// The pp_manager in this module (and possibly other modules sharing internals) holds
// a raw pointer to this unique_ptr, and that pointer would dangle if we deleted it now.
//
// For pybind11-owned interpreters (via embed.h or subinterpreter.h), destroy() is
// called after Py_Finalize/Py_EndInterpreter completes, which safely deletes the
// unique_ptr. For interpreters not owned by pybind11 (e.g., a pybind11 extension
// loaded into an external interpreter), destroy() is never called and the unique_ptr
// shell (8 bytes, not its contents) is leaked.
// (See PR #5958 for ideas to eliminate this leak.)
}
std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
error_scope err_scope;
dict state_dict = get_python_state_dict();
auto internals_obj
= reinterpret_steal<object>(dict_getitemstringref(state_dict.ptr(), holder_id_));
std::unique_ptr<InternalsType> *pp = nullptr;
if (internals_obj) {
void *raw_ptr = PyCapsule_GetPointer(internals_obj.ptr(), /*name=*/nullptr);
if (!raw_ptr) {
raise_from(PyExc_SystemError,
"pybind11::detail::internals_pp_manager::get_pp_from_dict() FAILED");
throw error_already_set();
}
pp = reinterpret_cast<std::unique_ptr<InternalsType> *>(raw_ptr);
if (on_fetch_ && pp) {
on_fetch_(pp->get());
}
} else {
pp = new std::unique_ptr<InternalsType>;
// NOLINTNEXTLINE(bugprone-casting-through-void)
state_dict[holder_id_] = capsule(reinterpret_cast<void *>(pp));
auto result = atomic_get_or_create_in_state_dict<std::unique_ptr<InternalsType>>(
holder_id_, &internals_shutdown);
auto *pp = result.first;
bool created = result.second;
// Only call on_fetch_ when fetching existing internals, not when creating new ones.
if (!created && on_fetch_ && pp) {
on_fetch_(pp->get());
}
return pp;
}
@@ -661,6 +772,8 @@ private:
char const *holder_id_ = nullptr;
on_fetch_function *on_fetch_ = nullptr;
// Pointer-to-pointer to the singleton internals for the first seen interpreter (may not be the
// main interpreter)
std::unique_ptr<InternalsType> *internals_singleton_pp_;
};
@@ -713,6 +826,16 @@ PYBIND11_NOINLINE internals &get_internals() {
return *internals_ptr;
}
inline void ensure_internals() {
pybind11::detail::get_internals_pp_manager().unref();
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
if (PyInterpreterState_Get() != PyInterpreterState_Main()) {
has_seen_non_main_interpreter() = true;
}
#endif
pybind11::detail::get_internals();
}
inline internals_pp_manager<local_internals> &get_local_internals_pp_manager() {
// Use the address of this static itself as part of the key, so that the value is uniquely tied
// to where the module is loaded in memory
@@ -745,6 +868,17 @@ inline auto with_internals(const F &cb) -> decltype(cb(get_internals())) {
return cb(internals);
}
template <typename F>
inline void with_internals_if_internals(const F &cb) {
auto &ppmgr = get_internals_pp_manager();
auto &internals_ptr = *ppmgr.get_pp();
if (internals_ptr) {
auto &internals = *internals_ptr;
PYBIND11_LOCK_INTERNALS(internals);
cb(internals);
}
}
template <typename F>
inline auto with_exception_translators(const F &cb)
-> decltype(cb(get_internals().registered_exception_translators,

View File

@@ -125,7 +125,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
assert(bases.empty());
std::vector<PyTypeObject *> check;
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
check.push_back((PyTypeObject *) parent.ptr());
check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
}
auto const &type_dict = get_internals().registered_types_py;
for (size_t i = 0; i < check.size(); i++) {
@@ -168,7 +168,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
i--;
}
for (handle parent : reinterpret_borrow<tuple>(type->tp_bases)) {
check.push_back((PyTypeObject *) parent.ptr());
check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
}
}
}
@@ -286,7 +286,7 @@ PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp,
PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) {
detail::type_info *type_info = get_type_info(tp, throw_if_missing);
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
return handle(type_info ? (reinterpret_cast<PyObject *>(type_info->type)) : nullptr);
}
inline bool try_incref(PyObject *obj) {
@@ -506,7 +506,7 @@ PYBIND11_NOINLINE void instance::allocate_layout() {
// efficient for small allocations like the one we're doing here;
// for larger allocations they are just wrappers around malloc.
// TODO: is this still true for pure Python 3.6?
nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *));
nonsimple.values_and_holders = static_cast<void **>(PyMem_Calloc(space, sizeof(void *)));
if (!nonsimple.values_and_holders) {
throw std::bad_alloc();
}
@@ -537,7 +537,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
for (auto it = range.first; it != range.second; ++it) {
for (const auto &vh : values_and_holders(it->second)) {
if (vh.type == type) {
return handle((PyObject *) it->second);
return handle(reinterpret_cast<PyObject *>(it->second));
}
}
}
@@ -1700,7 +1700,7 @@ inline std::string quote_cpp_type_name(const std::string &cpp_type_name) {
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
if (auto *type_data = get_type_info(ti)) {
handle th((PyObject *) type_data->type);
handle th(reinterpret_cast<PyObject *>(type_data->type));
return th.attr("__module__").cast<std::string>() + '.'
+ th.attr("__qualname__").cast<std::string>();
}

View File

@@ -54,7 +54,8 @@ struct value_and_holder {
} else if (v) {
inst->nonsimple.status[index] |= instance::status_holder_constructed;
} else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed;
inst->nonsimple.status[index]
&= static_cast<std::uint8_t>(~instance::status_holder_constructed);
}
}
bool instance_registered() const {
@@ -69,7 +70,8 @@ struct value_and_holder {
} else if (v) {
inst->nonsimple.status[index] |= instance::status_instance_registered;
} else {
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered;
inst->nonsimple.status[index]
&= static_cast<std::uint8_t>(~instance::status_instance_registered);
}
}
};

View File

@@ -58,7 +58,7 @@
PYBIND11_WARNING_PUSH
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
#define PYBIND11_EMBEDDED_MODULE(name, variable, ...) \
PYBIND11_MODULE_PYINIT(name, {}, ##__VA_ARGS__) \
PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__) \
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(PyInit_, name)); \
PYBIND11_MODULE_EXEC(name, variable)
@@ -202,7 +202,7 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
#endif
// There is exactly one interpreter alive currently.
detail::get_num_interpreters_seen() = 1;
detail::has_seen_non_main_interpreter() = false;
}
/** \rst
@@ -242,12 +242,12 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
\endrst */
inline void finalize_interpreter() {
// get rid of any thread-local interpreter cache that currently exists
if (detail::get_num_interpreters_seen() > 1) {
if (detail::has_seen_non_main_interpreter()) {
detail::get_internals_pp_manager().unref();
detail::get_local_internals_pp_manager().unref();
// We know there can be no other interpreter alive now, so we can lower the count
detail::get_num_interpreters_seen() = 1;
// We know there can be no other interpreter alive now
detail::has_seen_non_main_interpreter() = false;
}
// Re-fetch the internals pointer-to-pointer (but not the internals itself, which might not
@@ -265,8 +265,8 @@ inline void finalize_interpreter() {
// avoid undefined behaviors when initializing another interpreter
detail::get_local_internals_pp_manager().destroy();
// We know there is no interpreter alive now, so we can reset the count
detail::get_num_interpreters_seen() = 0;
// We know there is no interpreter alive now, so we can reset the multi-flag
detail::has_seen_non_main_interpreter() = false;
}
/** \rst

View File

@@ -3,17 +3,31 @@
#pragma once
#include "detail/common.h"
#include "detail/internals.h"
#include "gil.h"
#include <cassert>
#include <mutex>
#ifdef Py_GIL_DISABLED
#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
# include <atomic>
#endif
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
# include <cstdint>
# include <memory>
# include <string>
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
using atomic_bool = std::atomic_bool;
#else
using atomic_bool = bool;
#endif
PYBIND11_NAMESPACE_END(detail)
// Use the `gil_safe_call_once_and_store` class below instead of the naive
//
// static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE!
@@ -48,12 +62,23 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
// functions, which is usually the case.
//
// For in-depth background, see docs/advanced/deadlock.md
#ifndef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
// Subinterpreter support is disabled.
// In this case, we can store the result globally, because there is only a single interpreter.
//
// The life span of the stored result is the entire process lifetime. It is leaked on process
// termination to avoid destructor calls after the Python interpreter was finalized.
template <typename T>
class gil_safe_call_once_and_store {
public:
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
//
// NOTE: The second parameter (finalize callback) is intentionally unused when subinterpreter
// support is disabled. In that case, storage is process-global and intentionally leaked to
// avoid calling destructors after the Python interpreter has been finalized.
template <typename Callable>
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
void (*)(T &) /*unused*/ = nullptr) {
if (!is_initialized_) { // This read is guarded by the GIL.
// Multiple threads may enter here, because the GIL is released in the next line and
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
@@ -74,29 +99,175 @@ public:
T &get_stored() {
assert(is_initialized_);
PYBIND11_WARNING_PUSH
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
# if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
// Needed for gcc 4.8.5
PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
#endif
# endif
return *reinterpret_cast<T *>(storage_);
PYBIND11_WARNING_POP
}
constexpr gil_safe_call_once_and_store() = default;
// The instance is a global static, so its destructor runs when the process
// is terminating. Therefore, do nothing here because the Python interpreter
// may have been finalized already.
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
// Disable copy and move operations.
gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
private:
// The global static storage (per-process) when subinterpreter support is disabled.
alignas(T) char storage_[sizeof(T)] = {};
std::once_flag once_flag_ = {};
#ifdef Py_GIL_DISABLED
std::atomic_bool
#else
bool
#endif
is_initialized_{false};
std::once_flag once_flag_;
// The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
// but the latter does not have the triviality properties of former,
// therefore `std::optional` is not a viable alternative here.
detail::atomic_bool is_initialized_{false};
};
#else
// Subinterpreter support is enabled.
// In this case, we should store the result per-interpreter instead of globally, because each
// subinterpreter has its own separate state. The cached result may not shareable across
// interpreters (e.g., imported modules and their members).
PYBIND11_NAMESPACE_BEGIN(detail)
template <typename T>
struct call_once_storage {
alignas(T) char storage[sizeof(T)] = {};
std::once_flag once_flag;
void (*finalize)(T &) = nullptr;
std::atomic_bool is_initialized{false};
call_once_storage() = default;
~call_once_storage() {
if (is_initialized) {
if (finalize != nullptr) {
finalize(*reinterpret_cast<T *>(storage));
} else {
reinterpret_cast<T *>(storage)->~T();
}
}
}
call_once_storage(const call_once_storage &) = delete;
call_once_storage(call_once_storage &&) = delete;
call_once_storage &operator=(const call_once_storage &) = delete;
call_once_storage &operator=(call_once_storage &&) = delete;
};
PYBIND11_NAMESPACE_END(detail)
// Prefix for storage keys in the interpreter state dict.
# define PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX PYBIND11_INTERNALS_ID "_call_once_storage__"
// The life span of the stored result is the entire interpreter lifetime. An additional
// `finalize_fn` can be provided to clean up the stored result when the interpreter is destroyed.
template <typename T>
class gil_safe_call_once_and_store {
public:
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
template <typename Callable>
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
void (*finalize_fn)(T &) = nullptr) {
if (!is_last_storage_valid()) {
// Multiple threads may enter here, because the GIL is released in the next line and
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
gil_scoped_release gil_rel; // Needed to establish lock ordering.
// There can be multiple threads going through here.
storage_type *value = nullptr;
{
gil_scoped_acquire gil_acq; // Restore lock ordering.
// This function is thread-safe under free-threading.
value = get_or_create_storage_in_state_dict();
}
assert(value != nullptr);
std::call_once(value->once_flag, [&] {
// Only one thread will ever enter here.
gil_scoped_acquire gil_acq;
// fn may release, but will reacquire, the GIL.
::new (value->storage) T(fn());
value->finalize = finalize_fn;
value->is_initialized = true;
last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
is_initialized_by_at_least_one_interpreter_ = true;
});
// All threads will observe `is_initialized_by_at_least_one_interpreter_` as true here.
}
// Intentionally not returning `T &` to ensure the calling code is self-documenting.
return *this;
}
// This must only be called after `call_once_and_store_result()` was called.
T &get_stored() {
T *result = last_storage_ptr_;
if (!is_last_storage_valid()) {
gil_scoped_acquire gil_acq;
auto *value = get_or_create_storage_in_state_dict();
result = last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
}
assert(result != nullptr);
return *result;
}
constexpr gil_safe_call_once_and_store() = default;
// The instance is a global static, so its destructor runs when the process
// is terminating. Therefore, do nothing here because the Python interpreter
// may have been finalized already.
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
// Disable copy and move operations because the memory address is used as key.
gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
private:
using storage_type = detail::call_once_storage<T>;
// Indicator of fast path for single-interpreter case.
bool is_last_storage_valid() const {
return is_initialized_by_at_least_one_interpreter_
&& !detail::has_seen_non_main_interpreter();
}
// Get the unique key for this storage instance in the interpreter's state dict.
// The return type should not be `py::str` because PyObject is interpreter-dependent.
std::string get_storage_key() const {
// The instance is expected to be global static, so using its address as unique identifier.
// The typical usage is like:
//
// PYBIND11_CONSTINIT static gil_safe_call_once_and_store<T> storage;
//
return PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX
+ std::to_string(reinterpret_cast<std::uintptr_t>(this));
}
// Get or create per-storage capsule in the current interpreter's state dict.
// The storage is interpreter-dependent and will not be shared across interpreters.
storage_type *get_or_create_storage_in_state_dict() {
return detail::atomic_get_or_create_in_state_dict<storage_type>(get_storage_key().c_str())
.first;
}
// No storage needed when subinterpreter support is enabled.
// The actual storage is stored in the per-interpreter state dict via
// `get_or_create_storage_in_state_dict()`.
// Fast local cache to avoid repeated lookups when there are no multiple interpreters.
// This is only valid if there is a single interpreter. Otherwise, it is not used.
// WARNING: We cannot use thread local cache similar to `internals_pp_manager::internals_p_tls`
// because the thread local storage cannot be explicitly invalidated when interpreters
// are destroyed (unlike `internals_pp_manager` which has explicit hooks for that).
T *last_storage_ptr_ = nullptr;
// This flag is true if the value has been initialized by any interpreter (may not be the
// current one).
detail::atomic_bool is_initialized_by_at_least_one_interpreter_{false};
};
#endif
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

View File

@@ -416,7 +416,7 @@ struct npy_format_descriptor_name<T, enable_if_t<is_complex<T>::value>> {
|| std::is_same<typename T::value_type, const double>::value
> (const_name("numpy.complex")
+ const_name<sizeof(typename T::value_type) * 16>(),
const_name("numpy.longcomplex"));
const_name("numpy.clongdouble"));
};
template <typename T>
@@ -1860,7 +1860,7 @@ public:
using value_type = container_type::value_type;
using size_type = container_type::size_type;
common_iterator() : m_strides() {}
common_iterator() = default;
common_iterator(void *ptr, const container_type &strides, const container_type &shape)
: p_ptr(reinterpret_cast<char *>(ptr)), m_strides(strides.size()) {

View File

@@ -43,6 +43,12 @@ PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
# include <cxxabi.h>
#endif
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606
# define PYBIND11_MAYBE_CONSTEXPR constexpr
#else
# define PYBIND11_MAYBE_CONSTEXPR
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
@@ -159,7 +165,7 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
pybind11_fail("Internal error while parsing type signature (1)");
}
if (auto *tinfo = detail::get_type_info(*t)) {
handle th((PyObject *) tinfo->type);
handle th(reinterpret_cast<PyObject *>(tinfo->type));
signature += th.attr("__module__").cast<std::string>() + "."
+ th.attr("__qualname__").cast<std::string>();
} else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
@@ -642,7 +648,7 @@ protected:
rec->signature = guarded_strdup(signature.c_str());
rec->args.shrink_to_fit();
rec->nargs = (std::uint16_t) args;
rec->nargs = static_cast<std::uint16_t>(args);
if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) {
rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr());
@@ -678,10 +684,10 @@ protected:
rec->def->ml_name = rec->name;
rec->def->ml_meth
= reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(dispatcher));
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
rec->def->ml_flags = METH_FASTCALL | METH_KEYWORDS;
object py_func_rec = detail::function_record_PyObject_New();
((detail::function_record_PyObject *) py_func_rec.ptr())->cpp_func_rec
(reinterpret_cast<detail::function_record_PyObject *>(py_func_rec.ptr()))->cpp_func_rec
= unique_rec.release();
guarded_strdup.release();
@@ -715,8 +721,8 @@ protected:
// chain.
chain_start = rec;
rec->next = chain;
auto *py_func_rec
= (detail::function_record_PyObject *) PyCFunction_GET_SELF(m_ptr);
auto *py_func_rec = reinterpret_cast<detail::function_record_PyObject *>(
PyCFunction_GET_SELF(m_ptr));
py_func_rec->cpp_func_rec = unique_rec.release();
guarded_strdup.release();
} else {
@@ -776,7 +782,7 @@ protected:
}
}
auto *func = (PyCFunctionObject *) m_ptr;
auto *func = reinterpret_cast<PyCFunctionObject *>(m_ptr);
// Install docstring if it's non-empty (when at least one option is enabled)
auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
@@ -811,9 +817,9 @@ protected:
// so they cannot be freed. Once the function has been created, they can.
// Check `make_function_record` for more details.
if (free_strings) {
std::free((char *) rec->name);
std::free((char *) rec->doc);
std::free((char *) rec->signature);
std::free(rec->name);
std::free(rec->doc);
std::free(rec->signature);
for (auto &arg : rec->args) {
std::free(const_cast<char *>(arg.name));
std::free(const_cast<char *>(arg.descr));
@@ -841,7 +847,8 @@ protected:
}
/// Main dispatch logic for calls to functions bound using pybind11
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
static PyObject *
dispatcher(PyObject *self, PyObject *const *args_in_arr, size_t nargsf, PyObject *kwnames_in) {
using namespace detail;
const function_record *overloads = function_record_ptr_from_PyObject(self);
assert(overloads != nullptr);
@@ -851,9 +858,9 @@ protected:
/* Need to know how many arguments + keyword arguments there are to pick the right
overload */
const auto n_args_in = (size_t) PyTuple_GET_SIZE(args_in);
const auto n_args_in = static_cast<size_t>(PyVectorcall_NARGS(nargsf));
handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr,
handle parent = n_args_in > 0 ? args_in_arr[0] : nullptr,
result = PYBIND11_TRY_NEXT_OVERLOAD;
auto self_value_and_holder = value_and_holder();
@@ -865,7 +872,8 @@ protected:
return nullptr;
}
auto *const tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
auto *const tinfo
= get_type_info(reinterpret_cast<PyTypeObject *>(overloads->scope.ptr()));
auto *const pi = reinterpret_cast<instance *>(parent.ptr());
self_value_and_holder = pi->get_value_and_holder(tinfo, true);
@@ -941,7 +949,7 @@ protected:
self_value_and_holder.type->dealloc(self_value_and_holder);
}
call.init_self = PyTuple_GET_ITEM(args_in, 0);
call.init_self = args_in_arr[0];
call.args.emplace_back(reinterpret_cast<PyObject *>(&self_value_and_holder));
call.args_convert.push_back(false);
++args_copied;
@@ -952,17 +960,24 @@ protected:
for (; args_copied < args_to_copy; ++args_copied) {
const argument_record *arg_rec
= args_copied < func.args.size() ? &func.args[args_copied] : nullptr;
if (kwargs_in && arg_rec && arg_rec->name
&& dict_getitemstring(kwargs_in, arg_rec->name)) {
/* if the argument is listed in the call site's kwargs, but the argument is
also fulfilled positionally, then the call can't match this overload. for
example, the call site is: foo(0, key=1) but our overload is foo(key:int) then
this call can't be for us, because it would be invalid.
*/
if (kwnames_in && arg_rec && arg_rec->name
&& keyword_index(kwnames_in, arg_rec->name) >= 0) {
bad_arg = true;
break;
}
handle arg(PyTuple_GET_ITEM(args_in, args_copied));
handle arg(args_in_arr[args_copied]);
if (arg_rec && !arg_rec->none && arg.is_none()) {
bad_arg = true;
break;
}
call.args.push_back(arg);
call.args_convert.push_back(arg_rec ? arg_rec->convert : true);
}
@@ -974,20 +989,12 @@ protected:
// to copy the rest into a py::args argument.
size_t positional_args_copied = args_copied;
// We'll need to copy this if we steal some kwargs for defaults
dict kwargs = reinterpret_borrow<dict>(kwargs_in);
// 1.5. Fill in any missing pos_only args from defaults if they exist
if (args_copied < func.nargs_pos_only) {
for (; args_copied < func.nargs_pos_only; ++args_copied) {
const auto &arg_rec = func.args[args_copied];
handle value;
if (arg_rec.value) {
value = arg_rec.value;
}
if (value) {
call.args.push_back(value);
call.args.push_back(arg_rec.value);
call.args_convert.push_back(arg_rec.convert);
} else {
break;
@@ -1000,46 +1007,42 @@ protected:
}
// 2. Check kwargs and, failing that, defaults that may help complete the list
small_vector<bool, arg_vector_small_size> used_kwargs(
kwnames_in ? static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)) : 0, false);
size_t used_kwargs_count = 0;
if (args_copied < num_args) {
bool copied_kwargs = false;
for (; args_copied < num_args; ++args_copied) {
const auto &arg_rec = func.args[args_copied];
handle value;
if (kwargs_in && arg_rec.name) {
value = dict_getitemstring(kwargs.ptr(), arg_rec.name);
if (kwnames_in && arg_rec.name) {
ssize_t i = keyword_index(kwnames_in, arg_rec.name);
if (i >= 0) {
value = args_in_arr[n_args_in + static_cast<size_t>(i)];
used_kwargs.set(static_cast<size_t>(i), true);
used_kwargs_count++;
}
}
if (value) {
// Consume a kwargs value
if (!copied_kwargs) {
kwargs = reinterpret_steal<dict>(PyDict_Copy(kwargs.ptr()));
copied_kwargs = true;
}
if (PyDict_DelItemString(kwargs.ptr(), arg_rec.name) == -1) {
throw error_already_set();
}
} else if (arg_rec.value) {
if (!value) {
value = arg_rec.value;
if (!value) {
break;
}
}
if (!arg_rec.none && value.is_none()) {
break;
}
if (value) {
// If we're at the py::args index then first insert a stub for it to be
// replaced later
if (func.has_args && call.args.size() == func.nargs_pos) {
call.args.push_back(none());
}
call.args.push_back(value);
call.args_convert.push_back(arg_rec.convert);
} else {
break;
// If we're at the py::args index then first insert a stub for it to be
// replaced later
if (func.has_args && call.args.size() == func.nargs_pos) {
call.args.push_back(none());
}
call.args.push_back(value);
call.args_convert.push_back(arg_rec.convert);
}
if (args_copied < num_args) {
@@ -1049,47 +1052,50 @@ protected:
}
// 3. Check everything was consumed (unless we have a kwargs arg)
if (kwargs && !kwargs.empty() && !func.has_kwargs) {
if (!func.has_kwargs && used_kwargs_count < used_kwargs.size()) {
continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
}
// 4a. If we have a py::args argument, create a new tuple with leftovers
if (func.has_args) {
tuple extra_args;
if (args_to_copy == 0) {
// We didn't copy out any position arguments from the args_in tuple, so we
// can reuse it directly without copying:
extra_args = reinterpret_borrow<tuple>(args_in);
} else if (positional_args_copied >= n_args_in) {
extra_args = tuple(0);
if (positional_args_copied >= n_args_in) {
call.args_ref = tuple(0);
} else {
size_t args_size = n_args_in - positional_args_copied;
extra_args = tuple(args_size);
tuple extra_args(args_size);
for (size_t i = 0; i < args_size; ++i) {
extra_args[i] = PyTuple_GET_ITEM(args_in, positional_args_copied + i);
extra_args[i] = args_in_arr[positional_args_copied + i];
}
call.args_ref = std::move(extra_args);
}
if (call.args.size() <= func.nargs_pos) {
call.args.push_back(extra_args);
call.args.push_back(call.args_ref);
} else {
call.args[func.nargs_pos] = extra_args;
call.args[func.nargs_pos] = call.args_ref;
}
call.args_convert.push_back(false);
call.args_ref = std::move(extra_args);
}
// 4b. If we have a py::kwargs, pass on any remaining kwargs
if (func.has_kwargs) {
if (!kwargs.ptr()) {
kwargs = dict(); // If we didn't get one, send an empty one
dict kwargs;
for (size_t i = 0; i < used_kwargs.size(); ++i) {
if (!used_kwargs[i]) {
// Cast values into handles before indexing into kwargs to ensure
// well-defined evaluation order (MSVC C4866).
handle arg_in_arr = args_in_arr[n_args_in + i],
kwname = PyTuple_GET_ITEM(kwnames_in, i);
kwargs[kwname] = arg_in_arr;
}
}
call.args.push_back(kwargs);
call.args_convert.push_back(false);
call.kwargs_ref = std::move(kwargs);
}
// 5. Put everything in a vector. Not technically step 5, we've been building it
// in `call.args` all along.
// 5. Put everything in a vector. Not technically step 5, we've been building it
// in `call.args` all along.
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) {
pybind11_fail("Internal error: function call dispatcher inserted wrong number "
@@ -1220,40 +1226,37 @@ protected:
msg += '\n';
}
msg += "\nInvoked with: ";
auto args_ = reinterpret_borrow<tuple>(args_in);
bool some_args = false;
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < n_args_in; ++ti) {
if (!some_args) {
some_args = true;
} else {
msg += ", ";
}
try {
msg += pybind11::repr(args_[ti]);
msg += pybind11::repr(args_in_arr[ti]);
} catch (const error_already_set &) {
msg += "<repr raised Error>";
}
}
if (kwargs_in) {
auto kwargs = reinterpret_borrow<dict>(kwargs_in);
if (!kwargs.empty()) {
if (some_args) {
msg += "; ";
if (kwnames_in && PyTuple_GET_SIZE(kwnames_in) > 0) {
if (some_args) {
msg += "; ";
}
msg += "kwargs: ";
bool first = true;
for (size_t i = 0; i < static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)); ++i) {
if (first) {
first = false;
} else {
msg += ", ";
}
msg += "kwargs: ";
bool first = true;
for (const auto &kwarg : kwargs) {
if (first) {
first = false;
} else {
msg += ", ";
}
msg += pybind11::str("{}=").format(kwarg.first);
try {
msg += pybind11::repr(kwarg.second);
} catch (const error_already_set &) {
msg += "<repr raised Error>";
}
msg += reinterpret_borrow<pybind11::str>(PyTuple_GET_ITEM(kwnames_in, i));
msg += '=';
try {
msg += pybind11::repr(args_in_arr[n_args_in + i]);
} catch (const error_already_set &) {
msg += "<repr raised Error>";
}
}
}
@@ -1288,6 +1291,28 @@ protected:
}
return result.ptr();
}
static ssize_t keyword_index(PyObject *haystack, char const *needle) {
/* kwargs is usually very small (<= 5 entries). The arg strings are typically interned.
* CPython itself implements the search this way, first comparing all pointers ... which is
* cheap and will work if the strings are interned. If it fails, then it falls back to a
* second lexicographic check. This is wildly expensive for huge argument lists, but those
* are incredibly rare so we optimize for the vastly common case of just a couple of args.
*/
auto n = PyTuple_GET_SIZE(haystack);
auto s = reinterpret_steal<pybind11::str>(PyUnicode_InternFromString(needle));
for (ssize_t i = 0; i < n; ++i) {
if (PyTuple_GET_ITEM(haystack, i) == s.ptr()) {
return i;
}
}
for (ssize_t i = 0; i < n; ++i) {
if (PyUnicode_Compare(PyTuple_GET_ITEM(haystack, i), s.ptr()) == 0) {
return i;
}
}
return -1;
}
};
PYBIND11_NAMESPACE_BEGIN(detail)
@@ -1296,7 +1321,7 @@ PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
// This implementation needs the definition of `class cpp_function`.
inline void tp_dealloc_impl(PyObject *self) {
auto *py_func_rec = (function_record_PyObject *) self;
auto *py_func_rec = reinterpret_cast<function_record_PyObject *>(self);
cpp_function::destruct(py_func_rec->cpp_func_rec);
py_func_rec->cpp_func_rec = nullptr;
}
@@ -1669,7 +1694,7 @@ protected:
/* Register supplemental type information in C++ dict */
auto *tinfo = new detail::type_info();
tinfo->type = (PyTypeObject *) m_ptr;
tinfo->type = reinterpret_cast<PyTypeObject *>(m_ptr);
tinfo->cpptype = rec.type;
tinfo->type_size = rec.type_size;
tinfo->type_align = rec.type_align;
@@ -1704,7 +1729,7 @@ protected:
PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds")
PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread")
#endif
internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo};
internals.registered_types_py[reinterpret_cast<PyTypeObject *>(m_ptr)] = {tinfo};
PYBIND11_WARNING_POP
});
@@ -1712,7 +1737,8 @@ protected:
mark_parents_nonsimple(tinfo->type);
tinfo->simple_ancestors = false;
} else if (rec.bases.size() == 1) {
auto *parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr());
auto *parent_tinfo
= get_type_info(reinterpret_cast<PyTypeObject *>(rec.bases[0].ptr()));
assert(parent_tinfo != nullptr);
bool parent_simple_ancestors = parent_tinfo->simple_ancestors;
tinfo->simple_ancestors = parent_simple_ancestors;
@@ -1731,17 +1757,17 @@ protected:
void mark_parents_nonsimple(PyTypeObject *value) {
auto t = reinterpret_borrow<tuple>(value->tp_bases);
for (handle h : t) {
auto *tinfo2 = get_type_info((PyTypeObject *) h.ptr());
auto *tinfo2 = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()));
if (tinfo2) {
tinfo2->simple_type = false;
}
mark_parents_nonsimple((PyTypeObject *) h.ptr());
mark_parents_nonsimple(reinterpret_cast<PyTypeObject *>(h.ptr()));
}
}
void install_buffer_funcs(buffer_info *(*get_buffer)(PyObject *, void *),
void *get_buffer_data) {
auto *type = (PyHeapTypeObject *) m_ptr;
auto *type = reinterpret_cast<PyHeapTypeObject *>(m_ptr);
auto *tinfo = detail::get_type_info(&type->ht_type);
if (!type->ht_type.tp_as_buffer) {
@@ -1763,8 +1789,8 @@ protected:
const auto is_static = (rec_func != nullptr) && !(rec_func->is_method && rec_func->scope);
const auto has_doc = (rec_func != nullptr) && (rec_func->doc != nullptr)
&& pybind11::options::show_user_defined_docstrings();
auto property = handle(
(PyObject *) (is_static ? get_internals().static_property_type : &PyProperty_Type));
auto property = handle(reinterpret_cast<PyObject *>(
is_static ? get_internals().static_property_type : &PyProperty_Type));
attr(name) = property(fget.ptr() ? fget : none(),
fset.ptr() ? fset : none(),
/*deleter*/ none(),
@@ -2486,8 +2512,7 @@ private:
static void init_holder_from_existing(const detail::value_and_holder &v_h,
const holder_type *holder_ptr,
std::true_type /*is_copy_constructible*/) {
new (std::addressof(v_h.holder<holder_type>()))
holder_type(*reinterpret_cast<const holder_type *>(holder_ptr));
new (std::addressof(v_h.holder<holder_type>())) holder_type(*holder_ptr);
}
static void init_holder_from_existing(const detail::value_and_holder &v_h,
@@ -2699,8 +2724,9 @@ struct enum_base {
PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) {
m_base.attr("__entries") = dict();
auto property = handle((PyObject *) &PyProperty_Type);
auto static_property = handle((PyObject *) get_internals().static_property_type);
auto property = handle(reinterpret_cast<PyObject *>(&PyProperty_Type));
auto static_property
= handle(reinterpret_cast<PyObject *>(get_internals().static_property_type));
m_base.attr("__repr__") = cpp_function(
[](const object &arg) -> str {
@@ -2731,7 +2757,7 @@ struct enum_base {
[](handle arg) -> std::string {
std::string docstring;
dict entries = arg.attr("__entries");
if (((PyTypeObject *) arg.ptr())->tp_doc) {
if ((reinterpret_cast<PyTypeObject *>(arg.ptr()))->tp_doc) {
docstring += std::string(
reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
docstring += "\n\n";
@@ -2857,7 +2883,7 @@ struct enum_base {
dict entries = m_base.attr("__entries");
str name(name_);
if (entries.contains(name)) {
std::string type_name = (std::string) str(m_base.attr("__name__"));
std::string type_name = std::string(str(m_base.attr("__name__")));
throw value_error(std::move(type_name) + ": element \"" + std::string(name_)
+ "\" already exists!");
}
@@ -3052,7 +3078,7 @@ all_type_info_get_cache(PyTypeObject *type) {
if (res.second) {
// New cache entry created; set up a weak reference to automatically remove it if the type
// gets destroyed:
weakref((PyObject *) type, cpp_function([type](handle wr) {
weakref(reinterpret_cast<PyObject *>(type), cpp_function([type](handle wr) {
with_internals([type](internals &internals) {
internals.registered_types_py.erase(type);
@@ -3293,7 +3319,7 @@ void implicitly_convertible() {
}
tuple args(1);
args[0] = obj;
PyObject *result = PyObject_Call((PyObject *) type, args.ptr(), nullptr);
PyObject *result = PyObject_Call(reinterpret_cast<PyObject *>(type), args.ptr(), nullptr);
if (result == nullptr) {
PyErr_Clear();
}
@@ -3509,7 +3535,7 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char *
if (frame != nullptr) {
PyCodeObject *f_code = PyFrame_GetCode(frame);
// f_code is guaranteed to not be NULL
if ((std::string) str(f_code->co_name) == name && f_code->co_argcount > 0) {
if (std::string(str(f_code->co_name)) == name && f_code->co_argcount > 0) {
# if PY_VERSION_HEX >= 0x030d0000
PyObject *locals = PyEval_GetFrameLocals();
# else
@@ -3604,13 +3630,15 @@ function get_override(const T *this_ptr, const char *name) {
auto o = override(__VA_ARGS__); \
PYBIND11_WARNING_PUSH \
PYBIND11_WARNING_DISABLE_MSVC(4127) \
if (pybind11::detail::cast_is_temporary_value_reference<ret_type>::value \
if PYBIND11_MAYBE_CONSTEXPR ( \
pybind11::detail::cast_is_temporary_value_reference<ret_type>::value \
&& !pybind11::detail::is_same_ignoring_cvref<ret_type, PyObject *>::value) { \
static pybind11::detail::override_caster_t<ret_type> caster; \
return pybind11::detail::cast_ref<ret_type>(std::move(o), caster); \
} else { \
return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
} \
PYBIND11_WARNING_POP \
return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
} \
} while (false)

View File

@@ -547,8 +547,13 @@ struct error_fetch_and_normalize {
// The presence of __notes__ is likely due to exception normalization
// errors, although that is not necessarily true, therefore insert a
// hint only:
if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) {
const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__");
if (has_notes == 1) {
m_lazy_error_string += "[WITH __notes__]";
} else if (has_notes == -1) {
// Ignore secondary errors when probing for __notes__ to avoid leaking a
// spurious exception while still reporting the original error.
PyErr_Clear();
}
#else
// PyErr_NormalizeException() may change the exception type if there are cascading
@@ -861,7 +866,7 @@ bool isinstance(handle obj) {
}
template <>
inline bool isinstance<handle>(handle) = delete;
bool isinstance<handle>(handle) = delete;
template <>
inline bool isinstance<object>(handle obj) {
return obj.ptr() != nullptr;
@@ -992,9 +997,11 @@ inline PyObject *dict_getitem(PyObject *v, PyObject *key) {
return rv;
}
// PyDict_GetItemStringRef was added in Python 3.13.0a1.
// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
inline PyObject *dict_getitemstringref(PyObject *v, const char *key) {
#if PY_VERSION_HEX >= 0x030D0000
PyObject *rv;
#if PY_VERSION_HEX >= 0x030D00A1
PyObject *rv = nullptr;
if (PyDict_GetItemStringRef(v, key, &rv) < 0) {
throw error_already_set();
}
@@ -1009,6 +1016,46 @@ inline PyObject *dict_getitemstringref(PyObject *v, const char *key) {
#endif
}
inline PyObject *dict_setdefaultstring(PyObject *v, const char *key, PyObject *defaultobj) {
PyObject *kv = PyUnicode_FromString(key);
if (kv == nullptr) {
throw error_already_set();
}
PyObject *rv = PyDict_SetDefault(v, kv, defaultobj);
Py_DECREF(kv);
if (rv == nullptr) {
throw error_already_set();
}
return rv;
}
// PyDict_SetDefaultRef was added in Python 3.13.0a4.
// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
inline PyObject *dict_setdefaultstringref(PyObject *v, const char *key, PyObject *defaultobj) {
#if PY_VERSION_HEX >= 0x030D00A4
PyObject *kv = PyUnicode_FromString(key);
if (kv == nullptr) {
throw error_already_set();
}
PyObject *rv = nullptr;
if (PyDict_SetDefaultRef(v, kv, defaultobj, &rv) < 0) {
Py_DECREF(kv);
throw error_already_set();
}
Py_DECREF(kv);
return rv;
#else
PyObject *rv = dict_setdefaultstring(v, key, defaultobj);
if (rv == nullptr || PyErr_Occurred()) {
throw error_already_set();
}
Py_XINCREF(rv);
return rv;
#endif
}
// Helper aliases/functions to support implicit casting of values given to python
// accessors/methods. When given a pyobject, this simply returns the pyobject as-is; for other C++
// type, the value goes through pybind11::cast(obj) to convert it to an `object`.
@@ -1555,7 +1602,7 @@ private:
}
private:
object value = {};
object value;
};
class type : public object {
@@ -1563,7 +1610,9 @@ public:
PYBIND11_OBJECT(type, object, PyType_Check)
/// Return a type handle from a handle or an object
static handle handle_of(handle h) { return handle((PyObject *) Py_TYPE(h.ptr())); }
static handle handle_of(handle h) {
return handle(reinterpret_cast<PyObject *>(Py_TYPE(h.ptr())));
}
/// Return a type object from a handle or an object
static type of(handle h) { return type(type::handle_of(h), borrowed_t{}); }
@@ -1641,7 +1690,13 @@ public:
Return a string representation of the object. This is analogous to
the ``str()`` function in Python.
\endrst */
explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) {
// Templatized to avoid ambiguity with str(const object&) for object-derived types.
template <typename T,
detail::enable_if_t<!std::is_base_of<object, detail::remove_cvref_t<T>>::value
&& std::is_constructible<handle, T>::value,
int>
= 0>
explicit str(T &&h) : object(raw_str(handle(std::forward<T>(h)).ptr()), stolen_t{}) {
if (!m_ptr) {
throw error_already_set();
}
@@ -1661,7 +1716,7 @@ public:
if (PyBytes_AsStringAndSize(temp.ptr(), &buffer, &length) != 0) {
throw error_already_set();
}
return std::string(buffer, (size_t) length);
return std::string(buffer, static_cast<size_t>(length));
}
template <typename... Args>
@@ -1861,10 +1916,12 @@ template <typename Unsigned>
Unsigned as_unsigned(PyObject *o) {
if (sizeof(Unsigned) <= sizeof(unsigned long)) {
unsigned long v = PyLong_AsUnsignedLong(o);
return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
return v == static_cast<unsigned long>(-1) && PyErr_Occurred() ? (Unsigned) -1
: (Unsigned) v;
}
unsigned long long v = PyLong_AsUnsignedLongLong(o);
return v == (unsigned long long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
return v == static_cast<unsigned long long>(-1) && PyErr_Occurred() ? (Unsigned) -1
: (Unsigned) v;
}
PYBIND11_NAMESPACE_END(detail)
@@ -1908,21 +1965,21 @@ public:
PYBIND11_OBJECT_CVT(float_, object, PyFloat_Check, PyNumber_Float)
// Allow implicit conversion from float/double:
// NOLINTNEXTLINE(google-explicit-constructor)
float_(float value) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
float_(float value) : object(PyFloat_FromDouble(static_cast<double>(value)), stolen_t{}) {
if (!m_ptr) {
pybind11_fail("Could not allocate float object!");
}
}
// NOLINTNEXTLINE(google-explicit-constructor)
float_(double value = .0) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
float_(double value = .0) : object(PyFloat_FromDouble(value), stolen_t{}) {
if (!m_ptr) {
pybind11_fail("Could not allocate float object!");
}
}
// NOLINTNEXTLINE(google-explicit-constructor)
operator float() const { return (float) PyFloat_AsDouble(m_ptr); }
operator float() const { return static_cast<float>(PyFloat_AsDouble(m_ptr)); }
// NOLINTNEXTLINE(google-explicit-constructor)
operator double() const { return (double) PyFloat_AsDouble(m_ptr); }
operator double() const { return PyFloat_AsDouble(m_ptr); }
};
class weakref : public object {
@@ -2122,7 +2179,7 @@ public:
pybind11_fail("Could not allocate tuple object!");
}
}
size_t size() const { return (size_t) PyTuple_Size(m_ptr); }
size_t size() const { return static_cast<size_t>(PyTuple_Size(m_ptr)); }
bool empty() const { return size() == 0; }
detail::tuple_accessor operator[](size_t index) const { return {*this, index}; }
template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
@@ -2156,7 +2213,7 @@ public:
typename collector = detail::deferred_t<detail::unpacking_collector<>, Args...>>
explicit dict(Args &&...args) : dict(collector(std::forward<Args>(args)...).kwargs()) {}
size_t size() const { return (size_t) PyDict_Size(m_ptr); }
size_t size() const { return static_cast<size_t>(PyDict_Size(m_ptr)); }
bool empty() const { return size() == 0; }
detail::dict_iterator begin() const { return {*this, 0}; }
detail::dict_iterator end() const { return {}; }
@@ -2176,7 +2233,8 @@ private:
if (PyDict_Check(op)) {
return handle(op).inc_ref().ptr();
}
return PyObject_CallFunctionObjArgs((PyObject *) &PyDict_Type, op, nullptr);
return PyObject_CallFunctionObjArgs(
reinterpret_cast<PyObject *>(&PyDict_Type), op, nullptr);
}
};
@@ -2188,7 +2246,7 @@ public:
if (result == -1) {
throw error_already_set();
}
return (size_t) result;
return static_cast<size_t>(result);
}
bool empty() const { return size() == 0; }
detail::sequence_accessor operator[](size_t index) const { return {*this, index}; }
@@ -2211,7 +2269,7 @@ public:
pybind11_fail("Could not allocate list object!");
}
}
size_t size() const { return (size_t) PyList_Size(m_ptr); }
size_t size() const { return static_cast<size_t>(PyList_Size(m_ptr)); }
bool empty() const { return size() == 0; }
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
@@ -2497,7 +2555,7 @@ inline size_t len(handle h) {
if (result < 0) {
throw error_already_set();
}
return (size_t) result;
return static_cast<size_t>(result);
}
/// Get the length hint of a Python object.
@@ -2510,7 +2568,7 @@ inline size_t len_hint(handle h) {
PyErr_Clear();
return 0;
}
return (size_t) result;
return static_cast<size_t>(result);
}
inline str repr(handle h) {

View File

@@ -244,7 +244,7 @@ void vector_modifiers(
}
auto *seq = new Vector();
seq->reserve((size_t) slicelength);
seq->reserve(slicelength);
for (size_t i = 0; i < slicelength; ++i) {
seq->push_back(v[start]);

View File

@@ -20,15 +20,6 @@
#endif
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
inline PyInterpreterState *get_interpreter_state_unchecked() {
auto cur_tstate = get_thread_state_unchecked();
if (cur_tstate)
return cur_tstate->interp;
else
return nullptr;
}
PYBIND11_NAMESPACE_END(detail)
class subinterpreter;
@@ -76,7 +67,7 @@ public:
/// Create a new subinterpreter with the specified configuration
/// @note This function acquires (and then releases) the main interpreter GIL, but the main
/// interpreter and its GIL are not required to be held prior to calling this function.
static inline subinterpreter create(PyInterpreterConfig const &cfg) {
static subinterpreter create(PyInterpreterConfig const &cfg) {
error_scope err_scope;
subinterpreter result;
@@ -84,7 +75,7 @@ public:
// we must hold the main GIL in order to create a subinterpreter
subinterpreter_scoped_activate main_guard(main());
auto prev_tstate = PyThreadState_Get();
auto *prev_tstate = PyThreadState_Get();
PyStatus status;
@@ -103,13 +94,13 @@ public:
}
// this doesn't raise a normal Python exception, it provides an exit() status code.
if (PyStatus_Exception(status)) {
if (PyStatus_Exception(status) != 0) {
pybind11_fail("failed to create new sub-interpreter");
}
// upon success, the new interpreter is activated in this thread
result.istate_ = result.creation_tstate_->interp;
detail::get_num_interpreters_seen() += 1; // there are now many interpreters
detail::has_seen_non_main_interpreter() = true;
detail::get_internals(); // initialize internals.tstate, amongst other things...
// In 3.13+ this state should be deleted right away, and the memory will be reused for
@@ -128,7 +119,7 @@ public:
/// Calls create() with a default configuration of an isolated interpreter that disallows fork,
/// exec, and Python threads.
static inline subinterpreter create() {
static subinterpreter create() {
// same as the default config in the python docs
PyInterpreterConfig cfg;
std::memset(&cfg, 0, sizeof(cfg));
@@ -144,8 +135,8 @@ public:
return;
}
PyThreadState *destroy_tstate;
PyThreadState *old_tstate;
PyThreadState *destroy_tstate = nullptr;
PyThreadState *old_tstate = nullptr;
// Python 3.12 requires us to keep the original PyThreadState alive until we are ready to
// destroy the interpreter. We prefer to use that to destroy the interpreter.
@@ -173,7 +164,7 @@ public:
old_tstate = PyThreadState_Swap(destroy_tstate);
#endif
bool switch_back = old_tstate && old_tstate->interp != istate_;
bool switch_back = (old_tstate != nullptr) && old_tstate->interp != istate_;
// Internals always exists in the subinterpreter, this class enforces it when it creates
// the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
@@ -190,8 +181,9 @@ public:
detail::get_local_internals_pp_manager().destroy();
// switch back to the old tstate and old GIL (if there was one)
if (switch_back)
if (switch_back) {
PyThreadState_Swap(old_tstate);
}
}
/// Get a handle to the main interpreter that can be used with subinterpreter_scoped_activate
@@ -214,11 +206,11 @@ public:
/// Get the numerical identifier for the sub-interpreter
int64_t id() const {
if (istate_ != nullptr)
if (istate_ != nullptr) {
return PyInterpreterState_GetID(istate_);
else
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
// here.
}
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
// here.
}
/// Get the interpreter's state dict. This interpreter's GIL must be held before calling!

View File

@@ -38,7 +38,7 @@ requires-python = ">=3.8"
[project.urls]
Homepage = "https://github.com/pybind/pybind11"
Documentation = "https://pybind11.readthedocs.io/"
"Bug Tracker" = "https://github.com/pybind/pybind11/issues"
"Issue Tracker" = "https://github.com/pybind/pybind11/issues"
Discussions = "https://github.com/pybind/pybind11/discussions"
Changelog = "https://pybind11.readthedocs.io/en/latest/changelog.html"
Chat = "https://gitter.im/pybind/Lobby"
@@ -152,6 +152,8 @@ extend-select = [
"C4", # flake8-comprehensions
"EM", # flake8-errmsg
"ICN", # flake8-import-conventions
"ISC", # flake8-implicit-str-concat
"PERF", # Perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
@@ -182,3 +184,26 @@ isort.required-imports = ["from __future__ import annotations"]
[tool.repo-review]
ignore = ["PP"]
[tool.typos]
files.extend-exclude = ["/cpython"]
[tool.typos.default.extend-identifiers]
ser_no = "ser_no"
SerNo = "SerNo"
StrLits = "StrLits"
[tool.typos.default.extend-words]
nd = "nd"
valu = "valu"
fo = "fo"
quater = "quater"
optin = "optin"
othr = "othr"
#[tool.typos.type.cpp.extend-words]
setp = "setp"
ot = "ot"
[tool.typos.type.json.extend-words]
ba = "ba"

View File

@@ -5,7 +5,7 @@
# All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
# Filter out items; print an optional message if any items filtered. This ignores extensions.
#
@@ -545,7 +545,9 @@ source_group(
FILES ${PYBIND11_HEADERS})
# Make sure pytest is found or produce a warning
pybind11_find_import(pytest VERSION 3.1)
if(NOT CMAKE_CROSSCOMPILING)
pybind11_find_import(pytest VERSION 3.1)
endif()
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# This is not used later in the build, so it's okay to regenerate each time.
@@ -578,24 +580,46 @@ add_custom_target(
USES_TERMINAL)
if(NOT PYBIND11_CUDA_TESTS)
# This module doesn't get mixed with other test modules because those aren't subinterpreter safe.
pybind11_add_module(mod_per_interpreter_gil THIN_LTO mod_per_interpreter_gil.cpp)
pybind11_add_module(mod_shared_interpreter_gil THIN_LTO mod_shared_interpreter_gil.cpp)
set_target_properties(mod_per_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"$<1:${CMAKE_CURRENT_BINARY_DIR}>")
set_target_properties(mod_shared_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"$<1:${CMAKE_CURRENT_BINARY_DIR}>")
if(PYBIND11_TEST_SMART_HOLDER)
target_compile_definitions(
mod_per_interpreter_gil
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
)
target_compile_definitions(
mod_shared_interpreter_gil
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
)
set(PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES
mod_per_interpreter_gil mod_shared_interpreter_gil mod_per_interpreter_gil_with_singleton)
# These modules don't get mixed with other test modules because those aren't subinterpreter safe.
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
pybind11_add_module("${mod}" THIN_LTO "${mod}.cpp")
pybind11_enable_warnings("${mod}")
endforeach()
# Put the built modules next to `pybind11_tests.so` so that the test scripts can find them.
get_target_property(pybind11_tests_output_directory pybind11_tests LIBRARY_OUTPUT_DIRECTORY)
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${pybind11_tests_output_directory}")
# Also set config-specific output directories for multi-configuration generators (MSVC)
if(DEFINED CMAKE_CONFIGURATION_TYPES)
foreach(config ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${config} config)
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
"${pybind11_tests_output_directory}")
endforeach()
endif()
endforeach()
if(SKBUILD)
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
install(TARGETS "${mod}" LIBRARY DESTINATION .)
endforeach()
endif()
add_dependencies(pytest mod_per_interpreter_gil mod_shared_interpreter_gil)
if(PYBIND11_TEST_SMART_HOLDER)
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
target_compile_definitions(
"${mod}"
PUBLIC
-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
endforeach()
endif()
add_dependencies(pytest ${PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES})
endif()
if(PYBIND11_TEST_OVERRIDE)

View File

@@ -304,10 +304,10 @@ def backport_typehints() -> Callable[[SanitizedString], SanitizedString]:
if sys.version_info < (3, 10):
d["typing_extensions.TypeGuard"] = "typing.TypeGuard"
def backport(sanatized_string: SanitizedString) -> SanitizedString:
def backport(sanitized_string: SanitizedString) -> SanitizedString:
for old, new in d.items():
sanatized_string.string = sanatized_string.string.replace(old, new)
sanitized_string.string = sanitized_string.string.replace(old, new)
return sanatized_string
return sanitized_string
return backport

View File

@@ -5,9 +5,11 @@ import sys
import sysconfig
ANDROID = sys.platform.startswith("android")
IOS = sys.platform.startswith("ios")
LINUX = sys.platform.startswith("linux")
MACOS = sys.platform.startswith("darwin")
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
FREEBSD = sys.platform.startswith("freebsd")
CPYTHON = platform.python_implementation() == "CPython"
PYPY = platform.python_implementation() == "PyPy"

View File

@@ -0,0 +1,147 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>
namespace py = pybind11;
#ifdef PYBIND11_HAS_NATIVE_ENUM
# include <pybind11/native_enum.h>
#endif
namespace pybind11_tests {
namespace mod_per_interpreter_gil_with_singleton {
// A singleton class that holds references to certain Python objects
// This singleton is per-interpreter using gil_safe_call_once_and_store
class MySingleton {
public:
MySingleton() = default;
~MySingleton() = default;
MySingleton(const MySingleton &) = delete;
MySingleton &operator=(const MySingleton &) = delete;
MySingleton(MySingleton &&) = default;
MySingleton &operator=(MySingleton &&) = default;
static MySingleton &get_instance() {
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<MySingleton> storage;
return storage
.call_once_and_store_result([]() -> MySingleton {
MySingleton instance{};
auto emplace = [&instance](const py::handle &obj) -> void {
obj.inc_ref(); // Ensure the object is not GC'd while interpreter is alive
instance.objects.emplace_back(obj);
};
// Example objects to store in the singleton
emplace(py::type::handle_of(py::none())); // static type
emplace(py::type::handle_of(py::tuple())); // static type
emplace(py::type::handle_of(py::list())); // static type
emplace(py::type::handle_of(py::dict())); // static type
emplace(py::module_::import("collections").attr("OrderedDict")); // static type
emplace(py::module_::import("collections").attr("defaultdict")); // heap type
emplace(py::module_::import("collections").attr("deque")); // heap type
assert(instance.objects.size() == 7);
return instance;
})
.get_stored();
}
std::vector<py::handle> &get_objects() { return objects; }
static void init() {
// Ensure the singleton is created
auto &instance = get_instance();
(void) instance; // suppress unused variable warning
assert(instance.objects.size() == 7);
// Register cleanup at interpreter exit
py::module_::import("atexit").attr("register")(py::cpp_function(&MySingleton::clear));
}
static void clear() {
auto &instance = get_instance();
(void) instance; // suppress unused variable warning
assert(instance.objects.size() == 7);
for (const auto &obj : instance.objects) {
obj.dec_ref();
}
instance.objects.clear();
}
private:
std::vector<py::handle> objects;
};
class MyClass {
public:
explicit MyClass(py::ssize_t v) : value(v) {}
py::ssize_t get_value() const { return value; }
private:
py::ssize_t value;
};
class MyGlobalError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class MyLocalError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
enum class MyEnum : int {
ONE = 1,
TWO = 2,
THREE = 3,
};
} // namespace mod_per_interpreter_gil_with_singleton
} // namespace pybind11_tests
PYBIND11_MODULE(mod_per_interpreter_gil_with_singleton,
m,
py::mod_gil_not_used(),
py::multiple_interpreters::per_interpreter_gil()) {
using namespace pybind11_tests::mod_per_interpreter_gil_with_singleton;
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = true;
#else
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = false;
#endif
MySingleton::init();
// Ensure py::multiple_interpreters::per_interpreter_gil() works with singletons using
// py::gil_safe_call_once_and_store
m.def(
"get_objects_in_singleton",
[]() -> std::vector<py::handle> { return MySingleton::get_instance().get_objects(); },
"Get the list of objects stored in the singleton");
// Ensure py::multiple_interpreters::per_interpreter_gil() works with class bindings
py::class_<MyClass>(m, "MyClass")
.def(py::init<py::ssize_t>())
.def("get_value", &MyClass::get_value);
// Ensure py::multiple_interpreters::per_interpreter_gil() works with global exceptions
py::register_exception<MyGlobalError>(m, "MyGlobalError");
// Ensure py::multiple_interpreters::per_interpreter_gil() works with local exceptions
py::register_local_exception<MyLocalError>(m, "MyLocalError");
#ifdef PYBIND11_HAS_NATIVE_ENUM
// Ensure py::multiple_interpreters::per_interpreter_gil() works with native_enum
py::native_enum<MyEnum>(m, "MyEnum", "enum.IntEnum")
.value("ONE", MyEnum::ONE)
.value("TWO", MyEnum::TWO)
.value("THREE", MyEnum::THREE)
.finalize();
#else
py::enum_<MyEnum>(m, "MyEnum")
.value("ONE", MyEnum::ONE)
.value("TWO", MyEnum::TWO)
.value("THREE", MyEnum::THREE);
#endif
}

View File

@@ -15,6 +15,8 @@ target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Ca
add_custom_target(
test_pure_cpp
COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
)
add_dependencies(check test_pure_cpp)

View File

@@ -25,7 +25,9 @@ PYBIND11_FINDPYTHON = true
[tool.cibuildwheel]
test-sources = ["tests", "pyproject.toml"]
test-command = "python -m pytest -o timeout=0 -p no:cacheprovider tests"
test-command = "python -m pytest -v -o timeout=120 -p no:cacheprovider tests"
# Pyodide doesn't have signal.setitimer, so pytest-timeout can't work with timeout > 0
pyodide.test-command = "python -m pytest -v -o timeout=0 -p no:cacheprovider tests"
environment.PIP_ONLY_BINARY = "numpy,scipy"
environment.PIP_PREFER_BINARY = "1"
android.environment.ANDROID_API_LEVEL = "24" # Needed to include libc++ in the wheel.

View File

@@ -7,8 +7,10 @@ numpy~=2.2.0; python_version=="3.10" and platform_python_implementation=="PyPy"
numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux"
numpy~=1.21.5; platform_python_implementation=="CPython" and python_version>="3.8" and python_version<"3.10"
numpy~=1.22.2; platform_python_implementation=="CPython" and python_version=="3.10"
numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13"
numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13"
numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13" and platform_machine!="ARM64"
numpy>=2.3.0; platform_python_implementation=="CPython" and python_version>="3.11" and platform_machine=="ARM64"
numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13" and platform_machine!="ARM64"
numpy==2.4.0; platform_python_implementation=="CPython" and python_version>="3.14"
pytest>=6
pytest-timeout
scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10"

View File

@@ -367,7 +367,7 @@ TEST_SUBMODULE(builtin_casters, m) {
m.def("complex_noconvert", [](std::complex<float> x) { return x; }, py::arg{}.noconvert());
// test int vs. long (Python 2)
m.def("int_cast", []() { return (int) 42; });
m.def("int_cast", []() { return 42; });
m.def("long_cast", []() { return (long) 42; });
m.def("longlong_cast", []() { return ULLONG_MAX; });

View File

@@ -112,8 +112,8 @@ def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected):
pass_f(obj)
assert str(exc_info.value) == (
"Missing value for wrapped C++ type"
+ " `pybind11_tests::class_sh_basic::atyp`:"
+ " Python instance was disowned."
" `pybind11_tests::class_sh_basic::atyp`:"
" Python instance was disowned."
)

View File

@@ -36,9 +36,9 @@ struct PySpBase : SpBase, py::trampoline_self_life_support {
struct SpBaseTester {
std::shared_ptr<SpBase> get_object() const { return m_obj; }
void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
bool is_base_used() { return m_obj->is_base_used(); }
bool has_instance() { return (bool) m_obj; }
bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
bool is_base_used() const { return m_obj->is_base_used(); }
bool has_instance() const { return (bool) m_obj; }
bool has_python_instance() const { return m_obj && m_obj->has_python_instance(); }
void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
std::shared_ptr<SpBase> m_obj;
};

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_installed_embed CXX)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_installed_function CXX)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_installed_target CXX)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_subdirectory_embed CXX)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_subdirectory_function CXX)

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
project(test_subdirectory_target CXX)

View File

@@ -60,7 +60,9 @@ add_custom_target(
test_cross_module_rtti
COMMAND "$<TARGET_FILE:test_cross_module_rtti_main>"
DEPENDS test_cross_module_rtti_main
WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>")
WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>"
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
)
set_target_properties(test_cross_module_rtti_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}")

View File

@@ -48,7 +48,7 @@ def test_docstring_options():
assert not m.DocstringTestFoo.__doc__
assert not m.DocstringTestFoo.value_prop.__doc__
# Check existig behaviour of enum docstings
# Check existing behaviour of enum docstings
assert (
m.DocstringTestEnum1.__doc__
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"

View File

@@ -76,9 +76,9 @@ def test_cross_module_exceptions(msg):
# TODO: FIXME
@pytest.mark.xfail(
"(env.MACOS and env.PYPY) or env.ANDROID",
"(env.MACOS and env.PYPY) or env.ANDROID or env.FREEBSD",
raises=RuntimeError,
reason="See Issue #2847, PR #2999, PR #4324",
reason="See Issue #2847, PR #2999, PR #4324, PR #5925",
strict=not env.PYPY, # PR 5569
)
def test_cross_module_exception_translator():

View File

@@ -73,7 +73,7 @@ public:
// Inheritance test
class TestFactory4 : public TestFactory3 {
public:
TestFactory4() : TestFactory3() { print_default_created(this); }
TestFactory4() { print_default_created(this); }
explicit TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
~TestFactory4() override { print_destroyed(this); }
};

View File

@@ -288,11 +288,7 @@ def test_redirect_both(capfd):
def test_threading():
with m.ostream_redirect(stdout=True, stderr=False):
# start some threads
threads = []
# start some threads
for _j in range(20):
threads.append(m.TestThread())
threads = [m.TestThread() for _j in range(20)]
# give the threads some time to fail
threads[0].sleep()

View File

@@ -458,7 +458,8 @@ def test_args_refcount():
assert refcount(myval) == expected
exp3 = refcount(myval, myval, myval)
assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
# if we have to create a new tuple internally, then it will hold an extra reference for each item in it.
assert m.args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3)
assert refcount(myval) == expected
# This function takes the first arg as a `py::object` and the rest as a `py::args`. Unlike the

View File

@@ -3,10 +3,18 @@ from __future__ import annotations
import contextlib
import os
import pickle
import subprocess
import sys
import textwrap
import pytest
import env
import pybind11_tests
if env.IOS:
pytest.skip("Subinterpreters not supported on iOS", allow_module_level=True)
# 3.14.0b3+, though sys.implementation.supports_isolated_interpreters is being added in b4
# Can be simplified when we drop support for the first three betas
CONCURRENT_INTERPRETERS_SUPPORT = (
@@ -82,21 +90,23 @@ def get_interpreters(*, modern: bool):
def test_independent_subinterpreters():
"""Makes sure the internals object differs across independent subinterpreters"""
sys.path.append(".")
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
run_string, create = get_interpreters(modern=True)
m = pytest.importorskip("mod_per_interpreter_gil")
import mod_per_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
code = """
import mod_per_interpreter_gil as m
import pickle
with open(pipeo, 'wb') as f:
pickle.dump(m.internals_at(), f)
"""
code = textwrap.dedent(
"""
import mod_per_interpreter_gil as m
import pickle
with open(pipeo, 'wb') as f:
pickle.dump(m.internals_at(), f)
"""
).strip()
with create() as interp1, create() as interp2:
try:
@@ -131,20 +141,22 @@ with open(pipeo, 'wb') as f:
def test_independent_subinterpreters_modern():
"""Makes sure the internals object differs across independent subinterpreters. Modern (3.14+) syntax."""
sys.path.append(".")
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
m = pytest.importorskip("mod_per_interpreter_gil")
import mod_per_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
from concurrent import interpreters
code = """
import mod_per_interpreter_gil as m
code = textwrap.dedent(
"""
import mod_per_interpreter_gil as m
values.put_nowait(m.internals_at())
"""
values.put_nowait(m.internals_at())
"""
).strip()
with contextlib.closing(interpreters.create()) as interp1, contextlib.closing(
interpreters.create()
@@ -175,21 +187,23 @@ values.put_nowait(m.internals_at())
def test_dependent_subinterpreters():
"""Makes sure the internals object differs across subinterpreters"""
sys.path.append(".")
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
run_string, create = get_interpreters(modern=False)
m = pytest.importorskip("mod_shared_interpreter_gil")
import mod_shared_interpreter_gil as m
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
pytest.skip("Does not have subinterpreter support compiled in")
code = """
import mod_shared_interpreter_gil as m
import pickle
with open(pipeo, 'wb') as f:
pickle.dump(m.internals_at(), f)
"""
code = textwrap.dedent(
"""
import mod_shared_interpreter_gil as m
import pickle
with open(pipeo, 'wb') as f:
pickle.dump(m.internals_at(), f)
"""
).strip()
with create("legacy") as interp1:
pipei, pipeo = os.pipe()
@@ -198,3 +212,244 @@ with open(pipeo, 'wb') as f:
res1 = pickle.load(f)
assert res1 != m.internals_at(), "internals should differ from main interpreter"
PREAMBLE_CODE = textwrap.dedent(
f"""
def test():
import sys
sys.path.insert(0, {os.path.dirname(pybind11_tests.__file__)!r})
import collections
import mod_per_interpreter_gil_with_singleton as m
objects = m.get_objects_in_singleton()
expected = [
type(None), # static type: shared between interpreters
tuple, # static type: shared between interpreters
list, # static type: shared between interpreters
dict, # static type: shared between interpreters
collections.OrderedDict, # static type: shared between interpreters
collections.defaultdict, # heap type: dynamically created per interpreter
collections.deque, # heap type: dynamically created per interpreter
]
# Check that we have the expected objects. Avoid IndexError by checking lengths first.
assert len(objects) == len(expected), (
f"Expected {{expected!r}} ({{len(expected)}}), got {{objects!r}} ({{len(objects)}})."
)
# The first ones are static types shared between interpreters.
assert objects[:-2] == expected[:-2], (
f"Expected static objects {{expected[:-2]!r}}, got {{objects[:-2]!r}}."
)
# The last two are heap types created per-interpreter.
# The expected objects are dynamically imported from `collections`.
assert objects[-2:] == expected[-2:], (
f"Expected heap objects {{expected[-2:]!r}}, got {{objects[-2:]!r}}."
)
assert hasattr(m, 'MyClass'), "Module missing MyClass"
assert hasattr(m, 'MyGlobalError'), "Module missing MyGlobalError"
assert hasattr(m, 'MyLocalError'), "Module missing MyLocalError"
assert hasattr(m, 'MyEnum'), "Module missing MyEnum"
"""
).lstrip()
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
def test_import_module_with_singleton_per_interpreter():
"""Tests that a singleton storing Python objects works correctly per-interpreter"""
from concurrent import interpreters
code = f"{PREAMBLE_CODE.strip()}\n\ntest()\n"
with contextlib.closing(interpreters.create()) as interp:
interp.exec(code)
def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
"""Runs the given code in a subprocess."""
code = textwrap.dedent(code).strip()
try:
for _ in range(rerun): # run flakily failing test multiple times
subprocess.check_output(
[sys.executable, "-c", code],
cwd=os.getcwd(),
stderr=subprocess.STDOUT,
text=True,
)
except subprocess.CalledProcessError as ex:
raise RuntimeError(
f"Subprocess failed with exit code {ex.returncode}.\n\n"
f"Code:\n"
f"```python\n"
f"{code}\n"
f"```\n\n"
f"Output:\n"
f"{ex.output}"
) from None
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
def test_import_in_subinterpreter_after_main():
"""Tests that importing a module in a subinterpreter after the main interpreter works correctly"""
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
test()
interp = None
with contextlib.closing(interpreters.create()) as interp:
interp.call(test)
del interp
for _ in range(5):
gc.collect()
"""
)
)
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import contextlib
import gc
import random
from concurrent import interpreters
test()
interps = interp = None
with contextlib.ExitStack() as stack:
interps = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range(8)
]
random.shuffle(interps)
for interp in interps:
interp.call(test)
del interps, interp, stack
for _ in range(5):
gc.collect()
"""
)
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
def test_import_in_subinterpreter_before_main():
"""Tests that importing a module in a subinterpreter before the main interpreter works correctly"""
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
interp = None
with contextlib.closing(interpreters.create()) as interp:
interp.call(test)
test()
del interp
for _ in range(5):
gc.collect()
"""
)
)
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
interps = interp = None
with contextlib.ExitStack() as stack:
interps = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range(8)
]
for interp in interps:
interp.call(test)
test()
del interps, interp, stack
for _ in range(5):
gc.collect()
"""
)
)
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import contextlib
import gc
from concurrent import interpreters
interps = interp = None
with contextlib.ExitStack() as stack:
interps = [
stack.enter_context(contextlib.closing(interpreters.create()))
for _ in range(8)
]
for interp in interps:
interp.call(test)
test()
del interps, interp, stack
for _ in range(5):
gc.collect()
"""
)
)
@pytest.mark.skipif(
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
)
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
def test_import_in_subinterpreter_concurrently():
"""Tests that importing a module in multiple subinterpreters concurrently works correctly"""
check_script_success_in_subprocess(
PREAMBLE_CODE
+ textwrap.dedent(
"""
import gc
from concurrent.futures import InterpreterPoolExecutor, as_completed
futures = future = None
with InterpreterPoolExecutor(max_workers=16) as executor:
futures = [executor.submit(test) for _ in range(32)]
for future in as_completed(futures):
future.result()
del futures, future, executor
for _ in range(5):
gc.collect()
"""
)
)

View File

@@ -17,8 +17,8 @@
// Size / dtype checks.
struct DtypeCheck {
py::dtype numpy{};
py::dtype pybind11{};
py::dtype numpy;
py::dtype pybind11;
};
template <typename T>
@@ -43,11 +43,11 @@ std::vector<DtypeCheck> get_concrete_dtype_checks() {
}
struct DtypeSizeCheck {
std::string name{};
std::string name;
int size_cpp{};
int size_numpy{};
// For debugging.
py::dtype dtype{};
py::dtype dtype;
};
template <typename T>

View File

@@ -1211,4 +1211,6 @@ TEST_SUBMODULE(pytypes, m) {
m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs<RealNumber> {
return py::isinstance<RealNumber>(x);
});
m.def("const_kwargs_ref_to_str", [](const py::kwargs &kwargs) { return py::str(kwargs); });
}

View File

@@ -1367,3 +1367,8 @@ def test_arg_return_type_hints(doc, backport_typehints):
backport_typehints(doc(m.check_type_guard))
== "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]"
)
def test_const_kwargs_ref_to_str():
assert m.const_kwargs_ref_to_str() == "{}"
assert m.const_kwargs_ref_to_str(a=1) == "{'a': 1}"

View File

@@ -556,7 +556,7 @@ TEST_SUBMODULE(sequences_and_iterators, m) {
});
m.def("count_nonzeros", [](const py::dict &d) {
return std::count_if(d.begin(), d.end(), [](std::pair<py::handle, py::handle> p) {
return std::count_if(d.begin(), d.end(), [](const std::pair<py::handle, py::handle> &p) {
return p.second.cast<int>() != 0;
});
});

View File

@@ -220,7 +220,7 @@ struct SharedPtrRef {
~A() { print_destroyed(this); }
};
A value = {};
A value;
std::shared_ptr<A> shared = std::make_shared<A>();
};
@@ -228,13 +228,14 @@ struct SharedPtrRef {
struct SharedFromThisRef {
struct B : std::enable_shared_from_this<B> {
B() { print_created(this); }
// NOLINTNEXTLINE(bugprone-copy-constructor-init)
// NOLINTNEXTLINE(bugprone-copy-constructor-init, readability-redundant-member-init)
B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
// NOLINTNEXTLINE(readability-redundant-member-init)
B(B &&) noexcept : std::enable_shared_from_this<B>() { print_move_created(this); }
~B() { print_destroyed(this); }
};
B value = {};
B value;
std::shared_ptr<B> shared = std::make_shared<B>();
};

View File

@@ -55,7 +55,7 @@ template <class Map>
Map *times_ten(int n) {
auto *m = new Map();
for (int i = 1; i <= n; i++) {
m->emplace(int(i), E_nc(10 * i));
m->emplace(i, E_nc(10 * i));
}
return m;
}
@@ -65,7 +65,7 @@ NestMap *times_hundred(int n) {
auto *m = new NestMap();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
(*m)[i].emplace(int(j * 10), E_nc(100 * j));
(*m)[i].emplace(j * 10, E_nc(100 * j));
}
}
return m;

View File

@@ -258,7 +258,7 @@ def test_noncopyable_containers():
assert nvnc[i][j].value == j + 1
# Note: maps do not have .values()
for _, v in nvnc.items():
for v in nvnc.values():
for i, j in enumerate(v, start=1):
assert j.value == i
@@ -269,7 +269,7 @@ def test_noncopyable_containers():
assert nmnc[i][j].value == 10 * j
vsum = 0
for _, v_o in nmnc.items():
for v_o in nmnc.values():
for k_i, v_i in v_o.items():
assert v_i.value == 10 * k_i
vsum += v_i.value
@@ -283,7 +283,7 @@ def test_noncopyable_containers():
assert numnc[i][j].value == 10 * j
vsum = 0
for _, v_o in numnc.items():
for v_o in numnc.values():
for k_i, v_i in v_o.items():
assert v_i.value == 10 * k_i
vsum += v_i.value

View File

@@ -5,7 +5,10 @@ import pytest
from pybind11_tests import unnamed_namespace_a as m
from pybind11_tests import unnamed_namespace_b as mb
XFAIL_CONDITION = "not m.defined_WIN32_or__WIN32 and (m.defined___clang__ or m.defined__LIBCPP_VERSION)"
XFAIL_CONDITION = (
"m.defined__LIBCPP_VERSION or "
"(not m.defined_WIN32_or__WIN32 and m.defined___clang__)"
)
XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319"

View File

@@ -47,7 +47,9 @@ add_custom_target(
cpptest
COMMAND "$<TARGET_FILE:test_with_catch>"
DEPENDS test_with_catch
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
)
pybind11_add_module(external_module THIN_LTO external_module.cpp)
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY

View File

@@ -3,6 +3,17 @@
#include <pybind11/embed.h>
#include <chrono>
#include <csignal>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <sstream>
#ifndef _WIN32
# include <unistd.h>
#endif
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
PYBIND11_WARNING_DISABLE_MSVC(4996)
@@ -13,11 +24,126 @@ PYBIND11_WARNING_DISABLE_MSVC(4996)
#endif
#define CATCH_CONFIG_RUNNER
#define CATCH_CONFIG_DEFAULT_REPORTER "progress"
#include "catch_skip.h"
#include <catch.hpp>
namespace py = pybind11;
// Simple progress reporter that prints a line per test case.
namespace {
class ProgressReporter : public Catch::StreamingReporterBase<ProgressReporter> {
public:
using StreamingReporterBase<ProgressReporter>::StreamingReporterBase;
static std::string getDescription() { return "Simple progress reporter (one line per test)"; }
void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
print_python_version_once();
auto &os = Catch::cout();
os << "[ RUN ] " << testInfo.name << '\n';
os.flush();
}
void testCaseEnded(Catch::TestCaseStats const &stats) override {
bool failed = stats.totals.assertions.failed > 0;
auto &os = Catch::cout();
os << (failed ? "[ FAILED ] " : "[ OK ] ") << stats.testInfo.name << '\n';
os.flush();
}
void noMatchingTestCases(std::string const &spec) override {
auto &os = Catch::cout();
os << "[ NO TEST ] no matching test cases for spec: " << spec << '\n';
os.flush();
}
void reportInvalidArguments(std::string const &arg) override {
auto &os = Catch::cout();
os << "[ ERROR ] invalid Catch2 arguments: " << arg << '\n';
os.flush();
}
void assertionStarting(Catch::AssertionInfo const &) override {}
bool assertionEnded(Catch::AssertionStats const &) override { return false; }
void testRunEnded(Catch::TestRunStats const &stats) override {
auto &os = Catch::cout();
auto passed = stats.totals.testCases.passed;
auto failed = stats.totals.testCases.failed;
auto total = passed + failed;
auto assertions = stats.totals.assertions.passed + stats.totals.assertions.failed;
if (failed == 0) {
os << "[ PASSED ] " << total << " test cases, " << assertions << " assertions.\n";
} else {
os << "[ FAILED ] " << failed << " of " << total << " test cases, " << assertions
<< " assertions.\n";
}
os.flush();
}
private:
void print_python_version_once() {
if (printed_) {
return;
}
printed_ = true;
auto &os = Catch::cout();
os << "[ PYTHON ] " << Py_GetVersion() << '\n';
os.flush();
}
bool printed_ = false;
};
} // namespace
CATCH_REGISTER_REPORTER("progress", ProgressReporter)
namespace {
std::string get_utc_timestamp() {
auto now = std::chrono::system_clock::now();
auto time_t_now = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::tm utc_tm{};
#if defined(_WIN32)
gmtime_s(&utc_tm, &time_t_now);
#else
gmtime_r(&time_t_now, &utc_tm);
#endif
std::ostringstream oss;
oss << std::put_time(&utc_tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3)
<< ms.count() << 'Z';
return oss.str();
}
#ifndef _WIN32
// Signal handler to print a message when the process is terminated.
// Uses only async-signal-safe functions.
void termination_signal_handler(int sig) {
const char *msg = "[ SIGNAL ] Process received SIGTERM\n";
// write() is async-signal-safe, unlike std::cout
ssize_t written = write(STDOUT_FILENO, msg, strlen(msg));
(void) written; // suppress "unused variable" warnings
// Re-raise with default handler to get proper exit status
std::signal(sig, SIG_DFL);
std::raise(sig);
}
#endif
} // namespace
int main(int argc, char *argv[]) {
#ifndef _WIN32
std::signal(SIGTERM, termination_signal_handler);
#endif
// Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number:
std::string updated_pythonpath("pybind11_test_with_catch_PYTHONPATH_2099743835476552");
const char *preexisting_pythonpath = getenv("PYTHONPATH");
@@ -35,9 +161,15 @@ int main(int argc, char *argv[]) {
setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1);
#endif
std::cout << "[ STARTING ] " << get_utc_timestamp() << '\n';
std::cout.flush();
py::scoped_interpreter guard{};
auto result = Catch::Session().run(argc, argv);
std::cout << "[ DONE ] " << get_utc_timestamp() << " (result " << result << ")\n";
std::cout.flush();
return result < 0xff ? result : 0xff;
}

View File

@@ -0,0 +1,16 @@
// Macro to skip a test at runtime with a visible message.
// Catch2 v2 doesn't have native skip support (v3 does with SKIP()).
// The test will count as "passed" in totals, but the output clearly shows it was skipped.
#pragma once
#include <catch.hpp>
#define PYBIND11_CATCH2_SKIP_IF(condition, reason) \
do { \
if (condition) { \
Catch::cout() << "[ SKIPPED ] " << (reason) << '\n'; \
Catch::cout().flush(); \
return; \
} \
} while (0)

View File

@@ -1,11 +1,14 @@
#include <pybind11/embed.h>
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
# include <pybind11/gil_safe_call_once.h>
# include <pybind11/subinterpreter.h>
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
PYBIND11_WARNING_DISABLE_MSVC(4996)
# include "catch_skip.h"
# include <catch.hpp>
# include <cstdlib>
# include <fstream>
@@ -28,7 +31,7 @@ void unsafe_reset_internals_for_single_interpreter() {
py::detail::get_local_internals_pp_manager().unref();
// we know there are no other interpreters, so we can lower this. SUPER DANGEROUS
py::detail::get_num_interpreters_seen() = 1;
py::detail::has_seen_non_main_interpreter() = false;
// now we unref the static global singleton internals
py::detail::get_internals_pp_manager().unref();
@@ -39,6 +42,30 @@ void unsafe_reset_internals_for_single_interpreter() {
py::detail::get_local_internals();
}
py::object &get_dict_type_object() {
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
return storage
.call_once_and_store_result(
[]() -> py::object { return py::module_::import("builtins").attr("dict"); })
.get_stored();
}
py::object &get_ordered_dict_type_object() {
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
return storage
.call_once_and_store_result(
[]() -> py::object { return py::module_::import("collections").attr("OrderedDict"); })
.get_stored();
}
py::object &get_default_dict_type_object() {
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
return storage
.call_once_and_store_result(
[]() -> py::object { return py::module_::import("collections").attr("defaultdict"); })
.get_stored();
}
TEST_CASE("Single Subinterpreter") {
unsafe_reset_internals_for_single_interpreter();
@@ -104,14 +131,21 @@ TEST_CASE("Move Subinterpreter") {
py::module_::import("external_module");
}
std::thread([&]() {
auto t = std::thread([&]() {
// Use it again
{
py::subinterpreter_scoped_activate activate(*sub);
py::module_::import("external_module");
}
sub.reset();
}).join();
});
// on 3.14.1+ destructing a sub-interpreter does a stop-the-world. we need to detach our
// thread state in order for that to be possible.
{
py::gil_scoped_release nogil;
t.join();
}
REQUIRE(!sub);
@@ -299,6 +333,103 @@ TEST_CASE("Multiple Subinterpreters") {
unsafe_reset_internals_for_single_interpreter();
}
// Test that gil_safe_call_once_and_store provides per-interpreter storage.
// Without the per-interpreter storage fix, the subinterpreter would see the value
// cached by the main interpreter, which is invalid (different interpreter's object).
TEST_CASE("gil_safe_call_once_and_store per-interpreter isolation") {
unsafe_reset_internals_for_single_interpreter();
// This static simulates a typical usage pattern where a module caches
// an imported object using gil_safe_call_once_and_store.
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
// Get the interpreter ID in the main interpreter
auto main_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
// Store a value in the main interpreter - we'll store the interpreter ID as a Python int
auto &main_value = storage
.call_once_and_store_result([]() {
return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
})
.get_stored();
REQUIRE(main_value.cast<int64_t>() == main_interp_id);
py::object dict_type = get_dict_type_object();
py::object ordered_dict_type = get_ordered_dict_type_object();
py::object default_dict_type = get_default_dict_type_object();
int64_t sub_interp_id = -1;
int64_t sub_cached_value = -1;
bool sub_default_dict_type_destroyed = false;
// Create a subinterpreter and check that it gets its own storage
{
py::scoped_subinterpreter ssi;
sub_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
REQUIRE(sub_interp_id != main_interp_id);
// Access the same static storage from the subinterpreter.
// With per-interpreter storage, this should call the lambda again
// and cache a NEW value for this interpreter.
// Without per-interpreter storage, this would return main's cached value.
auto &sub_value
= storage
.call_once_and_store_result([]() {
return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
})
.get_stored();
sub_cached_value = sub_value.cast<int64_t>();
// The cached value should be the SUBINTERPRETER's ID, not the main interpreter's.
// This would fail without per-interpreter storage.
REQUIRE(sub_cached_value == sub_interp_id);
REQUIRE(sub_cached_value != main_interp_id);
py::object sub_dict_type = get_dict_type_object();
py::object sub_ordered_dict_type = get_ordered_dict_type_object();
py::object sub_default_dict_type = get_default_dict_type_object();
// Verify that the subinterpreter has its own cached type objects.
// For static types, they should be the same object across interpreters.
// See also: https://docs.python.org/3/c-api/typeobj.html#static-types
REQUIRE(sub_dict_type.is(dict_type)); // dict is a static type
REQUIRE(sub_ordered_dict_type.is(ordered_dict_type)); // OrderedDict is a static type
// For heap types, they are dynamically created per-interpreter.
// See also: https://docs.python.org/3/c-api/typeobj.html#heap-types
REQUIRE_FALSE(sub_default_dict_type.is(default_dict_type)); // defaultdict is a heap type
// Set up a weakref callback to detect when the subinterpreter's cached default_dict_type
// is destroyed so the gil_safe_call_once_and_store storage is not leaked when the
// subinterpreter is shutdown.
(void) py::weakref(sub_default_dict_type,
py::cpp_function([&](py::handle weakref) -> void {
sub_default_dict_type_destroyed = true;
weakref.dec_ref();
}))
.release();
}
// Back in main interpreter, verify main's value is unchanged
auto &main_value_after = storage.get_stored();
REQUIRE(main_value_after.cast<int64_t>() == main_interp_id);
// Verify that the types cached in main are unchanged
py::object dict_type_after = get_dict_type_object();
py::object ordered_dict_type_after = get_ordered_dict_type_object();
py::object default_dict_type_after = get_default_dict_type_object();
REQUIRE(dict_type_after.is(dict_type));
REQUIRE(ordered_dict_type_after.is(ordered_dict_type));
REQUIRE(default_dict_type_after.is(default_dict_type));
// Verify that the subinterpreter's cached default_dict_type was destroyed
REQUIRE(sub_default_dict_type_destroyed);
unsafe_reset_internals_for_single_interpreter();
}
# ifdef Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
TEST_CASE("Per-Subinterpreter GIL") {
auto main_int

View File

@@ -41,7 +41,18 @@ set(pybind11_INCLUDE_DIRS
"${pybind11_INCLUDE_DIR}"
CACHE INTERNAL "Include directory for pybind11 (Python not requested)")
if(CMAKE_CROSSCOMPILING AND PYBIND11_USE_CROSSCOMPILING)
# CMP0190 prohibits calling FindPython with both Interpreter and Development components
# when cross-compiling, unless the CMAKE_CROSSCOMPILING_EMULATOR variable is defined.
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.1")
cmake_policy(GET CMP0190 _pybind11_cmp0190)
if(_pybind11_cmp0190 STREQUAL "NEW")
set(PYBIND11_USE_CROSSCOMPILING "ON")
endif()
endif()
if(CMAKE_CROSSCOMPILING
AND PYBIND11_USE_CROSSCOMPILING
AND NOT DEFINED CMAKE_CROSSCOMPILING_EMULATOR)
set(_PYBIND11_CROSSCOMPILING
ON
CACHE INTERNAL "")

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
function(pybind11_guess_python_module_extension python)
@@ -14,15 +14,23 @@ function(pybind11_guess_python_module_extension python)
STRING
"Extension suffix for Python extension modules (Initialized from SETUPTOOLS_EXT_SUFFIX)")
endif()
# The final extension depends on the system
set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(_PY_BUILD_EXTENSION ".pyd")
endif()
# If running under scikit-build-core, use the SKBUILD_SOABI variable:
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED SKBUILD_SOABI)
message(STATUS "Determining Python extension suffix based on SKBUILD_SOABI: ${SKBUILD_SOABI}")
set(PYTHON_MODULE_EXT_SUFFIX ".${SKBUILD_SOABI}${_PY_BUILD_EXTENSION}")
endif()
# If that didn't work, use the Python_SOABI variable:
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED ${python}_SOABI)
message(
STATUS "Determining Python extension suffix based on ${python}_SOABI: ${${python}_SOABI}")
# The final extension depends on the system
set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(_PY_BUILD_EXTENSION ".pyd")
endif()
# If the SOABI already has an extension, use it as the full suffix
# (used for debug versions of Python on Windows)
if(${python}_SOABI MATCHES "\\.")
@@ -43,9 +51,9 @@ function(pybind11_guess_python_module_extension python)
# If we could not deduce the extension suffix, unset the results:
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX)
unset(PYTHON_MODULE_DEBUG_POSTFIX PARENT_SCOPE)
unset(PYTHON_MODULE_EXTENSION PARENT_SCOPE)
unset(PYTHON_IS_DEBUG PARENT_SCOPE)
unset(PYTHON_MODULE_DEBUG_POSTFIX CACHE)
unset(PYTHON_MODULE_EXTENSION CACHE)
unset(PYTHON_IS_DEBUG CACHE)
return()
endif()
@@ -75,12 +83,12 @@ function(pybind11_guess_python_module_extension python)
# Return results
set(PYTHON_MODULE_DEBUG_POSTFIX
"${_PYTHON_MODULE_DEBUG_POSTFIX}"
PARENT_SCOPE)
CACHE INTERNAL "")
set(PYTHON_MODULE_EXTENSION
"${_PYTHON_MODULE_EXTENSION}"
PARENT_SCOPE)
CACHE INTERNAL "")
set(PYTHON_IS_DEBUG
"${_PYTHON_IS_DEBUG}"
PARENT_SCOPE)
CACHE INTERNAL "")
endfunction()

View File

@@ -106,18 +106,7 @@ if(PYBIND11_MASTER_PROJECT)
endif()
endif()
if(NOT _PYBIND11_CROSSCOMPILING)
# If a user finds Python, they may forget to include the Interpreter component
# and the following two steps require it. It is highly recommended by CMake
# when finding development libraries anyway, so we will require it.
if(NOT DEFINED ${_Python}_EXECUTABLE)
message(
FATAL_ERROR
"${_Python} was found without the Interpreter component. Pybind11 requires this component."
)
endif()
if(NOT _PYBIND11_CROSSCOMPILING AND DEFINED ${_Python}_EXECUTABLE)
if(DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST AND NOT ${_Python}_EXECUTABLE STREQUAL
PYBIND11_PYTHON_EXECUTABLE_LAST)
# Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed
@@ -190,15 +179,15 @@ else()
include("${CMAKE_CURRENT_LIST_DIR}/pybind11GuessPythonExtSuffix.cmake")
pybind11_guess_python_module_extension("${_Python}")
endif()
# When cross-compiling, we cannot query the Python interpreter, so we require
# the user to set these variables explicitly.
if(NOT DEFINED PYTHON_IS_DEBUG
OR NOT DEFINED PYTHON_MODULE_EXTENSION
OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
message(
FATAL_ERROR
"When cross-compiling, you should set the PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX \
variables appropriately before loading pybind11 (e.g. in your CMake toolchain file)")
"A Python interpreter was not found, or you are cross-compiling, and the "
"PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX "
"variables could not be guessed. Set these variables appropriately before "
"loading pybind11 (e.g. in your CMake toolchain file)")
endif()
endif()
@@ -248,10 +237,7 @@ if(TARGET ${_Python}::Module)
# files.
get_target_property(module_target_type ${_Python}::Module TYPE)
if(ANDROID AND module_target_type STREQUAL INTERFACE_LIBRARY)
set_property(
TARGET ${_Python}::Module
APPEND
PROPERTY INTERFACE_LINK_LIBRARIES "${${_Python}_LIBRARIES}")
target_link_libraries(${_Python}::Module INTERFACE ${${_Python}_LIBRARIES})
endif()
set_property(

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.15...4.0)
cmake_minimum_required(VERSION 3.15...4.2)
# Tests for pybind11_guess_python_module_extension
# Run using `cmake -P tools/test-pybind11GuessPythonExtSuffix.cmake`
@@ -87,6 +87,30 @@ unset(PYTHON_MODULE_EXT_SUFFIX)
unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
unset(ENV{SETUPTOOLS_EXT_SUFFIX})
# Check the priority of the possible suffix sources.
set(ENV{SETUPTOOLS_EXT_SUFFIX} ".from-setuptools.pyd")
set(SKBUILD_SOABI "from-skbuild")
set(Python3_SOABI "from-python3")
pybind11_guess_python_module_extension("Python3")
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-setuptools.pyd")
unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
unset(ENV{SETUPTOOLS_EXT_SUFFIX})
pybind11_guess_python_module_extension("Python3")
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-skbuild.pyd")
unset(SKBUILD_SOABI)
pybind11_guess_python_module_extension("Python3")
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-python3.pyd")
set(Python3_SOABI "")
pybind11_guess_python_module_extension("Python3")
expect_streq("${PYTHON_MODULE_EXTENSION}" ".pyd")
unset(Python3_SOABI)
pybind11_guess_python_module_extension("Python3")
expect_streq("${PYTHON_MODULE_EXTENSION}" "")
# macOS
set(CMAKE_SYSTEM_NAME "Darwin")
set(CMAKE_SHARED_MODULE_SUFFIX ".so")