Skip to content

Commit c557f9a

Browse files
authored
[smart_holder] type_caster ODR guard (#4022)
* Insert type_caster_odr_guard<> (an empty struct to start with). * Add odr_guard_registry() used in type_caster_odr_guard() default constructor. * Add minimal_real_caster (from PR #3862) to test_async, test_buffers * VERY MESSY SNAPSHOT of WIP, this was the starting point for cl/454658864, which has more changes on top. * Restore original test_async, test_buffers from current smart_holder HEAD * Copy from cl/454991845 snapshot Jun 14, 5:08 PM * Cleanup of tests. Systematically insert `if (make_caster<T>::translation_unit_local) {` * Small simplification of odr_guard_impl() * WIP * Add PYBIND11_SOURCE_FILE_LINE macro. * Replace PYBIND11_TYPE_CASTER_UNIQUE_IDENTIFIER with PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE, baked into PYBIND11_TYPE_CASTER macro. * Add more PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL; resolves "unused" warning when compiling test_custom_type_casters.cpp * load_type fixes & follow-on cleanup * Strip ./ from source_file_line * Add new tests to CMakeLists.txt, disable PYBIND11_WERROR * Replace C++17 syntax. Compiles with Debian clang 13 C++11 mode, but fails to link. Trying GitHub Actions anyway to see if there are any platforms that support https://en.cppreference.com/w/cpp/language/tu_local before C++20. Note that Debian clang 13 C++17 works locally. * Show C++ version along with ODR VIOLATION DETECTED message. * Add source_file_line_basename() * Introduce PYBIND11_TYPE_CASTER_ODR_GUARD_ON (but not set automatically). * Minor cleanup. * Set PYBIND11_TYPE_CASTER_ODR_GUARD_ON automatically. * Resolve clang-tidy error. * Compatibility with old compilers. * Fix off-by-one in source_file_line_basename() * Report PYBIND11_INTERNALS_ID & C++ Version from pytest_configure() * Restore use of PYBIND11_WERROR * Move cpp_version_in_use() from cast.h to pybind11_tests.cpp * define PYBIND11_DETAIL_ODR_GUARD_IMPL_THROW_DISABLED true in test_odr_guard_1,2.cpp * IWYU cleanup of detail/type_caster_odr_guard.h * Replace `throw err;` to resolve clang-tidy error. * Add new header filename to CMakeLists.txt, test_files.py * Experiment: Try any C++17 compiler. * Fix ifdef for pragma GCC diagnostic. * type_caster_odr_guard_impl() cleanup * Move type_caster_odr_guard to type_caster_odr_guard.h * Rename test_odr_guard* to test_type_caster_odr_guard* * Remove comments that are (now) more distracting than helpful. * Mark tu_local_no_data_always_false operator bool as explicit (clang-tidy). See also: https://stackoverflow.com/questions/39995573/when-can-i-use-explicit-operator-bool-without-a-cast * New PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT option (current on by default). * Add test_type_caster_odr_registry_values(), test_type_caster_odr_violation_detected_counter() * Report UNEXPECTED: test_type_caster_odr_guard_2.cpp prevailed (but do not fail). * Apply clang-tidy suggestion. * Attempt to handle valgrind behavior. * Another attempt to handle valgrind behavior. * Yet another attempt to handle valgrind behavior. * Trying a new direction: show compiler info & std for UNEXPECTED: type_caster_odr_violation_detected_count() == 0 * compiler_info MSVC fix. num_violations == 0 condition. * assert pybind11_tests.compiler_info is not None * Introduce `make_caster_intrinsic<T>`, to be able to undo the 2 changes from `load_type` to `load_type<T>`. This is to avoid breaking 2 `pybind11::detail::load_type()` calls found in the wild (Google global testing). One of the breakages in the wild was: https://github.com/google/tensorstore/blob/0f0f6007670a3588093acd9df77cce423e0de805/python/tensorstore/subscript_method.h#L61 * Add test for stl.h / stl_bind.h mix. Manually verified that the ODR guard detects the ODR violation: ``` C++ Info: Debian Clang 13.0.1 C++17 __pybind11_internals_v4_clang_libstdcpp_cxxabi1002_sh_def__ =========================================================== test session starts ============================================================ platform linux -- Python 3.9.12, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3 ... ================================================================= FAILURES ================================================================= _____________________________________________ test_type_caster_odr_violation_detected_counter ______________________________________________ def test_type_caster_odr_violation_detected_counter(): ... else: > assert num_violations == 1 E assert 2 == 1 E +2 E -1 num_violations = 2 test_type_caster_odr_guard_1.py:51: AssertionError ========================================================= short test summary info ========================================================== FAILED test_type_caster_odr_guard_1.py::test_type_caster_odr_violation_detected_counter - assert 2 == 1 ======================================================= 1 failed, 5 passed in 0.08s ======================================================== ``` * Eliminate need for `PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL` macro. Copying code first developed by @amauryfa. I tried this at an earlier stage, but by itself this was insufficient. In the meantime I added in the TU-local mechanisms: trying again. Passes local testing: ``` DISABLED std::system_error: ODR VIOLATION DETECTED: pybind11::detail::type_caster<mrc_ns::type_mrc>: SourceLocation1="/usr/local/google/home/rwgk/forked/pybind11/tests/test_type_caster_odr_guard_1.cpp:18", SourceLocation2="/usr/local/google/home/rwgk/forked/pybind11/tests/test_type_caster_odr_guard_2.cpp:19" C++ Info: Debian Clang 13.0.1 C++17 __pybind11_internals_v4_clang_libstdcpp_cxxabi1002_sh_def__ =========================================================== test session starts ============================================================ platform linux -- Python 3.9.12, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3 cachedir: .pytest_cache rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini collected 6 items test_type_caster_odr_guard_1.py::test_type_mrc_to_python PASSED test_type_caster_odr_guard_1.py::test_type_mrc_from_python PASSED test_type_caster_odr_guard_1.py::test_type_caster_odr_registry_values PASSED test_type_caster_odr_guard_1.py::test_type_caster_odr_violation_detected_counter PASSED test_type_caster_odr_guard_2.py::test_type_mrc_to_python PASSED test_type_caster_odr_guard_2.py::test_type_mrc_from_python PASSED ============================================================ 6 passed in 0.01s ============================================================= ``` * tu_local_descr with src_loc experiment * clang-tidy suggested fixes * Use source_file_line_from_sloc in type_caster_odr_guard_registry * Disable type_caster ODR guard for __INTEL_COMPILER (see comment). Also turn off printf. * Add missing include (discovered via google-internal testing). * Work `scr_loc` into `descr` * Use `TypeCasterType::name.sloc` instead of `source_file_line.sloc` Manual re-verification: ``` +++ b/tests/test_type_caster_odr_guard_2.cpp - // m.def("pass_vector_type_mrc", mrc_ns::pass_vector_type_mrc); + m.def("pass_vector_type_mrc", mrc_ns::pass_vector_type_mrc); ``` ``` > assert num_violations == 1 E assert 2 == 1 num_violations = 2 test_type_caster_odr_guard_1.py:51: AssertionError ``` * Fix small oversight (src_loc::here() -> src_loc{nullptr, 0}). * Remove PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL macro completely. * Remove PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE macro completely. Some small extra cleanup. * Minor tweaks looking at the PR with a fresh eye. * src_loc comments * Add new test_descr_src_loc & and fix descr.h `concat()` `src_loc` bug discovered while working on the test. * Some more work on source code comments. * Fully document the ODR violations in the ODR guard itself and introduce `PYBIND11_TYPE_CASTER_ODR_GUARD_ON_IF_AVAILABLE` * Update comment (incl. mention of deadsnakes known to not work as intended). * Use no-destructor idiom for type_caster_odr_guard_registry, as suggested by @laramiel * Fix clang-tidy error: 'auto reg' can be declared as 'auto *reg' [readability-qualified-auto,-warnings-as-errors] * WIP * Revert "WIP" (tu_local_no_data_always_false_base experiment). This reverts commit 31e8ac5. * Change `PYBIND11_TYPE_CASTER_ODR_GUARD_ON` to `PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD`, based on a suggestion by @rainwoodman * Improved `#if` determining `PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD`, based on suggestion by @laramiel * Make `descr::sloc` `const`, as suggested by @rainwoodman * Rename macro to `PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_DEBUG`, as suggested by @laramiel * Tweak comments some more (add "white hat hacker" analogy). * Bring back `PYBIND11_CPP17` in determining `PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD`, to hopefully resolve most if not all of the many CI failures (89 failing, 32 successful: https://github.com/pybind/pybind11/runs/7430295771). * Try another workaround for `__has_builtin`-related breakages (https://github.com/pybind/pybind11/runs/7430720321). * Remove `defined(__has_builtin)` and subconditions. * Update "known to not work" expectation in test and comment. * `pytest.skip` `num_violations == 0` only `#ifdef __NO_INLINE__` (irrespective of the compiler) * Systematically change all new `#ifdef` to `#if defined` (review suggestion). * Bring back MSVC comment that got lost while experimenting.
1 parent 0ec9e31 commit c557f9a

12 files changed

+708
-29
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ set(PYBIND11_HEADERS
115115
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
116116
include/pybind11/detail/smart_holder_type_casters.h
117117
include/pybind11/detail/type_caster_base.h
118+
include/pybind11/detail/type_caster_odr_guard.h
118119
include/pybind11/detail/typeid.h
119120
include/pybind11/attr.h
120121
include/pybind11/buffer_info.h

include/pybind11/cast.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "detail/descr.h"
1515
#include "detail/smart_holder_sfinae_hooks_only.h"
1616
#include "detail/type_caster_base.h"
17+
#include "detail/type_caster_odr_guard.h"
1718
#include "detail/typeid.h"
1819
#include "pytypes.h"
1920

@@ -44,8 +45,20 @@ class type_caster_for_class_ : public type_caster_base<T> {};
4445
template <typename type, typename SFINAE = void>
4546
class type_caster : public type_caster_for_class_<type> {};
4647

48+
#if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD)
49+
50+
template <typename type>
51+
using make_caster_for_intrinsic = type_caster_odr_guard<type, type_caster<type>>;
52+
53+
#else
54+
4755
template <typename type>
48-
using make_caster = type_caster<intrinsic_t<type>>;
56+
using make_caster_for_intrinsic = type_caster<type>;
57+
58+
#endif
59+
60+
template <typename type>
61+
using make_caster = make_caster_for_intrinsic<intrinsic_t<type>>;
4962

5063
template <typename T>
5164
struct type_uses_smart_holder_type_caster {
@@ -1035,8 +1048,8 @@ struct return_value_policy_override<
10351048
};
10361049

10371050
// Basic python -> C++ casting; throws if casting fails
1038-
template <typename T, typename SFINAE>
1039-
type_caster<T, SFINAE> &load_type(type_caster<T, SFINAE> &conv, const handle &handle) {
1051+
template <typename T>
1052+
make_caster_for_intrinsic<T> &load_type(make_caster_for_intrinsic<T> &conv, const handle &handle) {
10401053
static_assert(!detail::is_pyobject<T>::value,
10411054
"Internal error: type_caster should only be used for C++ types");
10421055
if (!conv.load(handle, true)) {

include/pybind11/detail/descr.h

Lines changed: 127 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Copyright (c) 2022 The Pybind Development Team.
2+
// All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
14
/*
25
pybind11/detail/descr.h: Helper type for concatenating type signatures at compile time
36
@@ -20,21 +23,102 @@ PYBIND11_NAMESPACE_BEGIN(detail)
2023
# define PYBIND11_DESCR_CONSTEXPR const
2124
#endif
2225

26+
// struct src_loc below is to support type_caster_odr_guard.h
27+
// (see https://github.com/pybind/pybind11/pull/4022).
28+
// The ODR guard creates ODR violations itself (see WARNING below & in type_caster_odr_guard.h),
29+
// but is currently the only tool available.
30+
// The ODR is useful to know *for sure* what is safe and what is not, but that is only a
31+
// subset of what actually works in practice, in a specific environment. The implementation
32+
// here exploits the gray area (similar to a white hat hacker).
33+
// The dedicated test_type_caster_odr_guard_1, test_type_caster_odr_guard_2 pair of unit tests
34+
// passes reliably on almost all platforms that meet the compiler requirements (C++17, C++20),
35+
// except one (gcc 9.4.0 debug build).
36+
// In the pybind11 unit tests we want to test the ODR guard in as many environments as possible,
37+
// but it is NOT recommended to enable the guard in regular builds, production, or
38+
// debug. The guard is meant to be used similar to a sanitizer, to check for type_caster ODR
39+
// violations in binaries that are otherwise already fully tested and assumed to be healthy.
40+
//
41+
// * MSVC 2017 does not support __builtin_FILE(), __builtin_LINE().
42+
// * Intel 2021.6.0.20220226 (g++ 9.4 mode) __builtin_LINE() is unreliable
43+
// (line numbers vary between translation units).
44+
#if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD_IF_AVAILABLE) \
45+
&& !defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD) && defined(PYBIND11_CPP17) \
46+
&& !defined(__INTEL_COMPILER) \
47+
&& (!defined(_MSC_VER) || _MSC_VER >= 1920) // MSVC 2019 or newer.
48+
# define PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD
49+
#endif
50+
51+
#if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD)
52+
53+
// Not using std::source_location because:
54+
// 1. "It is unspecified whether the copy/move constructors and the copy/move
55+
// assignment operators of source_location are trivial and/or constexpr."
56+
// (https://en.cppreference.com/w/cpp/utility/source_location).
57+
// 2. A matching no-op stub is needed (below) to avoid code duplication.
58+
struct src_loc {
59+
const char *file;
60+
unsigned line;
61+
62+
constexpr src_loc(const char *file, unsigned line) : file(file), line(line) {}
63+
64+
static constexpr src_loc here(const char *file = __builtin_FILE(),
65+
unsigned line = __builtin_LINE()) {
66+
return src_loc(file, line);
67+
}
68+
69+
constexpr src_loc if_known_or(const src_loc &other) const {
70+
if (file != nullptr) {
71+
return *this;
72+
}
73+
return other;
74+
}
75+
};
76+
77+
#else
78+
79+
// No-op stub, to avoid code duplication, expected to be optimized out completely.
80+
struct src_loc {
81+
constexpr src_loc(const char *, unsigned) {}
82+
83+
static constexpr src_loc here(const char * = nullptr, unsigned = 0) {
84+
return src_loc(nullptr, 0);
85+
}
86+
87+
constexpr src_loc if_known_or(const src_loc &) const { return *this; }
88+
};
89+
90+
#endif
91+
92+
#if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD)
93+
namespace { // WARNING: This creates an ODR violation in the ODR guard itself,
94+
// but we do not have any alternative at the moment.
95+
// The ODR violation here is a difference in constexpr between multiple TUs.
96+
// All definitions have the same data layout, the only difference is the
97+
// text const char* pointee (the pointees are identical in value),
98+
// src_loc const char* file pointee (the pointees are different in value),
99+
// src_loc unsigned line value.
100+
// See also: Comment above; WARNING in type_caster_odr_guard.h
101+
#endif
102+
23103
/* Concatenate type signatures at compile time */
24104
template <size_t N, typename... Ts>
25105
struct descr {
26106
char text[N + 1]{'\0'};
107+
const src_loc sloc;
27108

28-
constexpr descr() = default;
109+
explicit constexpr descr(src_loc sloc) : sloc(sloc) {}
29110
// NOLINTNEXTLINE(google-explicit-constructor)
30-
constexpr descr(char const (&s)[N + 1]) : descr(s, make_index_sequence<N>()) {}
111+
constexpr descr(char const (&s)[N + 1], src_loc sloc = src_loc::here())
112+
: descr(s, make_index_sequence<N>(), sloc) {}
31113

32114
template <size_t... Is>
33-
constexpr descr(char const (&s)[N + 1], index_sequence<Is...>) : text{s[Is]..., '\0'} {}
115+
constexpr descr(char const (&s)[N + 1], index_sequence<Is...>, src_loc sloc = src_loc::here())
116+
: text{s[Is]..., '\0'}, sloc(sloc) {}
34117

35118
template <typename... Chars>
36119
// NOLINTNEXTLINE(google-explicit-constructor)
37-
constexpr descr(char c, Chars... cs) : text{c, static_cast<char>(cs)..., '\0'} {}
120+
constexpr descr(src_loc sloc, char c, Chars... cs)
121+
: text{c, static_cast<char>(cs)..., '\0'}, sloc(sloc) {}
38122

39123
static constexpr std::array<const std::type_info *, sizeof...(Ts) + 1> types() {
40124
return {{&typeid(Ts)..., nullptr}};
@@ -47,7 +131,8 @@ constexpr descr<N1 + N2, Ts1..., Ts2...> plus_impl(const descr<N1, Ts1...> &a,
47131
index_sequence<Is1...>,
48132
index_sequence<Is2...>) {
49133
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(b);
50-
return {a.text[Is1]..., b.text[Is2]...};
134+
return descr<N1 + N2, Ts1..., Ts2...>{
135+
a.sloc.if_known_or(b.sloc), a.text[Is1]..., b.text[Is2]...};
51136
}
52137

53138
template <size_t N1, size_t N2, typename... Ts1, typename... Ts2>
@@ -57,27 +142,33 @@ constexpr descr<N1 + N2, Ts1..., Ts2...> operator+(const descr<N1, Ts1...> &a,
57142
}
58143

59144
template <size_t N>
60-
constexpr descr<N - 1> const_name(char const (&text)[N]) {
61-
return descr<N - 1>(text);
145+
constexpr descr<N - 1> const_name(char const (&text)[N], src_loc sloc = src_loc::here()) {
146+
return descr<N - 1>(text, sloc);
147+
}
148+
constexpr descr<0> const_name(char const (&)[1], src_loc sloc = src_loc::here()) {
149+
return descr<0>(sloc);
62150
}
63-
constexpr descr<0> const_name(char const (&)[1]) { return {}; }
64151

65152
template <size_t Rem, size_t... Digits>
66153
struct int_to_str : int_to_str<Rem / 10, Rem % 10, Digits...> {};
67154
template <size_t... Digits>
68155
struct int_to_str<0, Digits...> {
69156
// WARNING: This only works with C++17 or higher.
70-
static constexpr auto digits = descr<sizeof...(Digits)>(('0' + Digits)...);
157+
// src_loc not tracked (not needed in this situation, at least at the moment).
158+
static constexpr auto digits
159+
= descr<sizeof...(Digits)>(src_loc{nullptr, 0}, ('0' + Digits)...);
71160
};
72161

73162
// Ternary description (like std::conditional)
74163
template <bool B, size_t N1, size_t N2>
75-
constexpr enable_if_t<B, descr<N1 - 1>> const_name(char const (&text1)[N1], char const (&)[N2]) {
76-
return const_name(text1);
164+
constexpr enable_if_t<B, descr<N1 - 1>>
165+
const_name(char const (&text1)[N1], char const (&)[N2], src_loc sloc = src_loc::here()) {
166+
return const_name(text1, sloc);
77167
}
78168
template <bool B, size_t N1, size_t N2>
79-
constexpr enable_if_t<!B, descr<N2 - 1>> const_name(char const (&)[N1], char const (&text2)[N2]) {
80-
return const_name(text2);
169+
constexpr enable_if_t<!B, descr<N2 - 1>>
170+
const_name(char const (&)[N1], char const (&text2)[N2], src_loc sloc = src_loc::here()) {
171+
return const_name(text2, sloc);
81172
}
82173

83174
template <bool B, typename T1, typename T2>
@@ -91,12 +182,13 @@ constexpr enable_if_t<!B, T2> const_name(const T1 &, const T2 &d) {
91182

92183
template <size_t Size>
93184
auto constexpr const_name() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
185+
// src_loc not tracked (not needed in this situation, at least at the moment).
94186
return int_to_str<Size / 10, Size % 10>::digits;
95187
}
96188

97189
template <typename Type>
98-
constexpr descr<1, Type> const_name() {
99-
return {'%'};
190+
constexpr descr<1, Type> const_name(src_loc sloc = src_loc::here()) {
191+
return {sloc, '%'};
100192
}
101193

102194
// If "_" is defined as a macro, py::detail::_ cannot be provided.
@@ -106,16 +198,18 @@ constexpr descr<1, Type> const_name() {
106198
#ifndef _
107199
# define PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY
108200
template <size_t N>
109-
constexpr descr<N - 1> _(char const (&text)[N]) {
110-
return const_name<N>(text);
201+
constexpr descr<N - 1> _(char const (&text)[N], src_loc sloc = src_loc::here()) {
202+
return const_name<N>(text, sloc);
111203
}
112204
template <bool B, size_t N1, size_t N2>
113-
constexpr enable_if_t<B, descr<N1 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
114-
return const_name<B, N1, N2>(text1, text2);
205+
constexpr enable_if_t<B, descr<N1 - 1>>
206+
_(char const (&text1)[N1], char const (&text2)[N2], src_loc sloc = src_loc::here()) {
207+
return const_name<B, N1, N2>(text1, text2, sloc);
115208
}
116209
template <bool B, size_t N1, size_t N2>
117-
constexpr enable_if_t<!B, descr<N2 - 1>> _(char const (&text1)[N1], char const (&text2)[N2]) {
118-
return const_name<B, N1, N2>(text1, text2);
210+
constexpr enable_if_t<!B, descr<N2 - 1>>
211+
_(char const (&text1)[N1], char const (&text2)[N2], src_loc sloc = src_loc::here()) {
212+
return const_name<B, N1, N2>(text1, text2, sloc);
119213
}
120214
template <bool B, typename T1, typename T2>
121215
constexpr enable_if_t<B, T1> _(const T1 &d1, const T2 &d2) {
@@ -128,15 +222,16 @@ constexpr enable_if_t<!B, T2> _(const T1 &d1, const T2 &d2) {
128222

129223
template <size_t Size>
130224
auto constexpr _() -> remove_cv_t<decltype(int_to_str<Size / 10, Size % 10>::digits)> {
225+
// src_loc not tracked (not needed in this situation, at least at the moment).
131226
return const_name<Size>();
132227
}
133228
template <typename Type>
134-
constexpr descr<1, Type> _() {
135-
return const_name<Type>();
229+
constexpr descr<1, Type> _(src_loc sloc = src_loc::here()) {
230+
return const_name<Type>(sloc);
136231
}
137232
#endif // #ifndef _
138233

139-
constexpr descr<0> concat() { return {}; }
234+
constexpr descr<0> concat(src_loc sloc = src_loc::here()) { return descr<0>{sloc}; }
140235

141236
template <size_t N, typename... Ts>
142237
constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
@@ -146,13 +241,19 @@ constexpr descr<N, Ts...> concat(const descr<N, Ts...> &descr) {
146241
template <size_t N, typename... Ts, typename... Args>
147242
constexpr auto concat(const descr<N, Ts...> &d, const Args &...args)
148243
-> decltype(std::declval<descr<N + 2, Ts...>>() + concat(args...)) {
149-
return d + const_name(", ") + concat(args...);
244+
// Ensure that src_loc of existing descr is used.
245+
return d + const_name(", ", src_loc{nullptr, 0}) + concat(args...);
150246
}
151247

152248
template <size_t N, typename... Ts>
153249
constexpr descr<N + 2, Ts...> type_descr(const descr<N, Ts...> &descr) {
154-
return const_name("{") + descr + const_name("}");
250+
// Ensure that src_loc of existing descr is used.
251+
return const_name("{", src_loc{nullptr, 0}) + descr + const_name("}");
155252
}
156253

254+
#if defined(PYBIND11_ENABLE_TYPE_CASTER_ODR_GUARD)
255+
} // namespace
256+
#endif
257+
157258
PYBIND11_NAMESPACE_END(detail)
158259
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

0 commit comments

Comments
 (0)