Skip to content

Commit 2e6d06d

Browse files
committed
Merge branch 'master' into sh_merge_master
2 parents 85209ea + a90e2af commit 2e6d06d

File tree

14 files changed

+362
-178
lines changed

14 files changed

+362
-178
lines changed

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,24 @@ jobs:
6666
# Inject a couple Windows 2019 runs
6767
- runs-on: windows-2019
6868
python: '3.9'
69+
# Inject a few runs with different runtime libraries
70+
- runs-on: windows-2022
71+
python: '3.9'
72+
args: >
73+
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
74+
- runs-on: windows-2022
75+
python: '3.10'
76+
args: >
77+
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL
78+
# This needs a python built with MTd
79+
# - runs-on: windows-2022
80+
# python: '3.11'
81+
# args: >
82+
# -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug
83+
- runs-on: windows-2022
84+
python: '3.12'
85+
args: >
86+
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL
6987
# Extra ubuntu latest job
7088
- runs-on: ubuntu-latest
7189
python: '3.11'

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ set(PYBIND11_HEADERS
146146
include/pybind11/chrono.h
147147
include/pybind11/common.h
148148
include/pybind11/complex.h
149+
include/pybind11/conduit/pybind11_conduit_v1.h
150+
include/pybind11/conduit/pybind11_platform_abi_id.h
151+
include/pybind11/conduit/wrap_include_python_h.h
149152
include/pybind11/options.h
150153
include/pybind11/eigen.h
151154
include/pybind11/eigen/common.h

include/pybind11/conduit/README.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
NOTE
2+
----
3+
4+
The C++ code here
5+
6+
** only depends on <Python.h> **
7+
8+
and nothing else.
9+
10+
DO NOT ADD CODE WITH OTHER EXTERNAL DEPENDENCIES TO THIS DIRECTORY.
11+
12+
Read on:
13+
14+
pybind11_conduit_v1.h — Type-safe interoperability between different
15+
independent Python/C++ bindings systems.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) 2024 The pybind Community.
2+
3+
/* The pybind11_conduit_v1 feature enables type-safe interoperability between
4+
5+
* different independent Python/C++ bindings systems,
6+
7+
* including pybind11 versions with different PYBIND11_INTERNALS_VERSION's.
8+
9+
The naming of the feature is a bit misleading:
10+
11+
* The feature is in no way tied to pybind11 internals.
12+
13+
* It just happens to originate from pybind11 and currently still lives there.
14+
15+
* The only external dependency is <Python.h>.
16+
17+
The implementation is a VERY light-weight dependency. It is designed to be
18+
compatible with any ISO C++11 (or higher) compiler, and does NOT require
19+
C++ Exception Handling to be enabled.
20+
21+
Please see https://github.com/pybind/pybind11/pull/5296 for more background.
22+
23+
The implementation involves a
24+
25+
def _pybind11_conduit_v1_(
26+
self,
27+
pybind11_platform_abi_id: bytes,
28+
cpp_type_info_capsule: capsule,
29+
pointer_kind: bytes) -> capsule
30+
31+
method that is meant to be added to Python objects wrapping C++ objects
32+
(e.g. pybind11::class_-wrapped types).
33+
34+
The design of the _pybind11_conduit_v1_ feature provides two layers of
35+
protection against C++ ABI mismatches:
36+
37+
* The first and most important layer is that the pybind11_platform_abi_id's
38+
must match between extensions. — This will never be perfect, but is the same
39+
pragmatic approach used in pybind11 since 2017
40+
(https://github.com/pybind/pybind11/commit/96997a4b9d4ec3d389a570604394af5d5eee2557,
41+
PYBIND11_INTERNALS_ID).
42+
43+
* The second layer is that the typeid(std::type_info).name()'s must match
44+
between extensions.
45+
46+
The implementation below (which is shorter than this comment!), serves as a
47+
battle-tested specification. The main API is this one function:
48+
49+
auto *cpp_pointer = pybind11_conduit_v1::get_type_pointer_ephemeral<YourType>(py_obj);
50+
51+
It is meant to be a minimalistic reference implementation, intentionally
52+
without comprehensive error reporting. It is expected that major bindings
53+
systems will roll their own, compatible implementations, potentially with
54+
system-specific error reporting. The essential specifications all bindings
55+
systems need to agree on are merely:
56+
57+
* PYBIND11_PLATFORM_ABI_ID (const char* literal).
58+
59+
* The cpp_type_info capsule (see below: a void *ptr and a const char *name).
60+
61+
* The cpp_conduit capsule (see below: a void *ptr and a const char *name).
62+
63+
* "raw_pointer_ephemeral" means: the lifetime of the pointer is the lifetime
64+
of the py_obj.
65+
66+
*/
67+
68+
// THIS MUST STAY AT THE TOP!
69+
#include "pybind11_platform_abi_id.h"
70+
71+
#include <Python.h>
72+
#include <typeinfo>
73+
74+
namespace pybind11_conduit_v1 {
75+
76+
inline void *get_raw_pointer_ephemeral(PyObject *py_obj, const std::type_info *cpp_type_info) {
77+
PyObject *cpp_type_info_capsule
78+
= PyCapsule_New(const_cast<void *>(static_cast<const void *>(cpp_type_info)),
79+
typeid(std::type_info).name(),
80+
nullptr);
81+
if (cpp_type_info_capsule == nullptr) {
82+
return nullptr;
83+
}
84+
PyObject *cpp_conduit = PyObject_CallMethod(py_obj,
85+
"_pybind11_conduit_v1_",
86+
"yOy",
87+
PYBIND11_PLATFORM_ABI_ID,
88+
cpp_type_info_capsule,
89+
"raw_pointer_ephemeral");
90+
Py_DECREF(cpp_type_info_capsule);
91+
if (cpp_conduit == nullptr) {
92+
return nullptr;
93+
}
94+
void *raw_ptr = PyCapsule_GetPointer(cpp_conduit, cpp_type_info->name());
95+
Py_DECREF(cpp_conduit);
96+
if (PyErr_Occurred()) {
97+
return nullptr;
98+
}
99+
return raw_ptr;
100+
}
101+
102+
template <typename T>
103+
T *get_type_pointer_ephemeral(PyObject *py_obj) {
104+
void *raw_ptr = get_raw_pointer_ephemeral(py_obj, &typeid(T));
105+
if (raw_ptr == nullptr) {
106+
return nullptr;
107+
}
108+
return static_cast<T *>(raw_ptr);
109+
}
110+
111+
} // namespace pybind11_conduit_v1
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#pragma once
2+
3+
// Copyright (c) 2024 The pybind Community.
4+
5+
// To maximize reusability:
6+
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
7+
8+
#include "wrap_include_python_h.h"
9+
10+
// Implementation details. DO NOT USE ELSEWHERE. (Unfortunately we cannot #undef them.)
11+
// This is duplicated here to maximize portability.
12+
#define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x
13+
#define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x)
14+
15+
// On MSVC, debug and release builds are not ABI-compatible!
16+
#if defined(_MSC_VER) && defined(_DEBUG)
17+
# define PYBIND11_BUILD_TYPE "_debug"
18+
#else
19+
# define PYBIND11_BUILD_TYPE ""
20+
#endif
21+
22+
// Let's assume that different compilers are ABI-incompatible.
23+
// A user can manually set this string if they know their
24+
// compiler is compatible.
25+
#ifndef PYBIND11_COMPILER_TYPE
26+
# if defined(_MSC_VER)
27+
# define PYBIND11_COMPILER_TYPE "_msvc"
28+
# elif defined(__INTEL_COMPILER)
29+
# define PYBIND11_COMPILER_TYPE "_icc"
30+
# elif defined(__clang__)
31+
# define PYBIND11_COMPILER_TYPE "_clang"
32+
# elif defined(__PGI)
33+
# define PYBIND11_COMPILER_TYPE "_pgi"
34+
# elif defined(__MINGW32__)
35+
# define PYBIND11_COMPILER_TYPE "_mingw"
36+
# elif defined(__CYGWIN__)
37+
# define PYBIND11_COMPILER_TYPE "_gcc_cygwin"
38+
# elif defined(__GNUC__)
39+
# define PYBIND11_COMPILER_TYPE "_gcc"
40+
# else
41+
# define PYBIND11_COMPILER_TYPE "_unknown"
42+
# endif
43+
#endif
44+
45+
// Also standard libs
46+
#ifndef PYBIND11_STDLIB
47+
# if defined(_LIBCPP_VERSION)
48+
# define PYBIND11_STDLIB "_libcpp"
49+
# elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
50+
# define PYBIND11_STDLIB "_libstdcpp"
51+
# else
52+
# define PYBIND11_STDLIB ""
53+
# endif
54+
#endif
55+
56+
#ifndef PYBIND11_BUILD_ABI
57+
# if defined(__GXX_ABI_VERSION) // Linux/OSX.
58+
# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(__GXX_ABI_VERSION)
59+
# elif defined(_MSC_VER) // See PR #4953.
60+
# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd.
61+
# if (_MSC_VER) / 100 == 19
62+
# define PYBIND11_BUILD_ABI "_md_mscver19"
63+
# else
64+
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
65+
# endif
66+
# elif defined(_MT) // Corresponding to CL command line options /MT or /MTd.
67+
# define PYBIND11_BUILD_ABI "_mt_mscver" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_MSC_VER)
68+
# else
69+
# if (_MSC_VER) / 100 == 19
70+
# define PYBIND11_BUILD_ABI "_none_mscver19"
71+
# else
72+
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
73+
# endif
74+
# endif
75+
# elif defined(__NVCOMPILER) // NVHPC (PGI-based).
76+
# define PYBIND11_BUILD_ABI "" // TODO: What should be here, to prevent UB?
77+
# else
78+
# error "Unknown platform or compiler: PLEASE REVISE THIS CODE."
79+
# endif
80+
#endif
81+
82+
#ifndef PYBIND11_INTERNALS_KIND
83+
# define PYBIND11_INTERNALS_KIND ""
84+
#endif
85+
86+
#define PYBIND11_PLATFORM_ABI_ID \
87+
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
88+
PYBIND11_BUILD_TYPE
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#pragma once
2+
3+
// Copyright (c) 2024 The pybind Community.
4+
5+
// STRONG REQUIREMENT:
6+
// This header is a wrapper around `#include <Python.h>`, therefore it
7+
// MUST BE INCLUDED BEFORE ANY STANDARD HEADERS are included.
8+
// See also:
9+
// https://docs.python.org/3/c-api/intro.html#include-files
10+
// Quoting from there:
11+
// Note: Since Python may define some pre-processor definitions which affect
12+
// the standard headers on some systems, you must include Python.h before
13+
// any standard headers are included.
14+
15+
// To maximize reusability:
16+
// DO NOT ADD CODE THAT REQUIRES C++ EXCEPTION HANDLING.
17+
18+
// Disable linking to pythonX_d.lib on Windows in debug mode.
19+
#if defined(_MSC_VER) && defined(_DEBUG) && !defined(Py_DEBUG)
20+
// Workaround for a VS 2022 issue.
21+
// See https://github.com/pybind/pybind11/pull/3497 for full context.
22+
// NOTE: This workaround knowingly violates the Python.h include order
23+
// requirement (see above).
24+
# include <yvals.h>
25+
# if _MSVC_STL_VERSION >= 143
26+
# include <crtdefs.h>
27+
# endif
28+
# define PYBIND11_DEBUG_MARKER
29+
# undef _DEBUG
30+
#endif
31+
32+
// Don't let Python.h #define (v)snprintf as macro because they are implemented
33+
// properly in Visual Studio since 2015.
34+
#if defined(_MSC_VER)
35+
# define HAVE_SNPRINTF 1
36+
#endif
37+
38+
#if defined(_MSC_VER)
39+
# pragma warning(push)
40+
# pragma warning(disable : 4505)
41+
// C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed
42+
#endif
43+
44+
#include <Python.h>
45+
#include <frameobject.h>
46+
#include <pythread.h>
47+
48+
#if defined(_MSC_VER)
49+
# pragma warning(pop)
50+
#endif
51+
52+
#if defined(PYBIND11_DEBUG_MARKER)
53+
# define _DEBUG
54+
# undef PYBIND11_DEBUG_MARKER
55+
#endif
56+
57+
// Python #defines overrides on all sorts of core functions, which
58+
// tends to wreak havok in C++ codebases that expect these to work
59+
// like regular functions (potentially with several overloads).
60+
#if defined(isalnum)
61+
# undef isalnum
62+
# undef isalpha
63+
# undef islower
64+
# undef isspace
65+
# undef isupper
66+
# undef tolower
67+
# undef toupper
68+
#endif
69+
70+
#if defined(copysign)
71+
# undef copysign
72+
#endif

include/pybind11/detail/class.h

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,9 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
585585
return -1;
586586
}
587587
std::memset(view, 0, sizeof(Py_buffer));
588-
buffer_info *info = nullptr;
588+
std::unique_ptr<buffer_info> info = nullptr;
589589
try {
590-
info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
590+
info.reset(tinfo->get_buffer(obj, tinfo->get_buffer_data));
591591
} catch (...) {
592592
try_translate_exceptions();
593593
raise_from(PyExc_BufferError, "Error getting buffer");
@@ -598,17 +598,13 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
598598
}
599599

600600
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
601-
delete info;
602601
// view->obj = nullptr; // Was just memset to 0, so not necessary
603602
set_error(PyExc_BufferError, "Writable buffer requested for readonly storage");
604603
return -1;
605604
}
606605

607606
// Fill in all the information, and then downgrade as requested by the caller, or raise an
608607
// error if that's not possible.
609-
view->obj = obj;
610-
view->internal = info;
611-
view->buf = info->ptr;
612608
view->itemsize = info->itemsize;
613609
view->len = view->itemsize;
614610
for (auto s : info->shape) {
@@ -626,23 +622,20 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
626622
if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS) {
627623
if (PyBuffer_IsContiguous(view, 'C') == 0) {
628624
std::memset(view, 0, sizeof(Py_buffer));
629-
delete info;
630625
set_error(PyExc_BufferError,
631626
"C-contiguous buffer requested for discontiguous storage");
632627
return -1;
633628
}
634629
} else if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) {
635630
if (PyBuffer_IsContiguous(view, 'F') == 0) {
636631
std::memset(view, 0, sizeof(Py_buffer));
637-
delete info;
638632
set_error(PyExc_BufferError,
639633
"Fortran-contiguous buffer requested for discontiguous storage");
640634
return -1;
641635
}
642636
} else if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS) {
643637
if (PyBuffer_IsContiguous(view, 'A') == 0) {
644638
std::memset(view, 0, sizeof(Py_buffer));
645-
delete info;
646639
set_error(PyExc_BufferError, "Contiguous buffer requested for discontiguous storage");
647640
return -1;
648641
}
@@ -652,7 +645,6 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
652645
// https://docs.python.org/3/c-api/buffer.html#contiguity-requests
653646
if (PyBuffer_IsContiguous(view, 'C') == 0) {
654647
std::memset(view, 0, sizeof(Py_buffer));
655-
delete info;
656648
set_error(PyExc_BufferError,
657649
"C-contiguous buffer requested for discontiguous storage");
658650
return -1;
@@ -667,6 +659,11 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
667659
}
668660
}
669661

662+
// Set these after all checks so they don't leak out into the caller, and can be automatically
663+
// cleaned up on error.
664+
view->buf = info->ptr;
665+
view->internal = info.release();
666+
view->obj = obj;
670667
Py_INCREF(view->obj);
671668
return 0;
672669
}

0 commit comments

Comments
 (0)