diff --git a/CMakeLists.txt b/CMakeLists.txt index a423ae4424..38443da66e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ set(PYBIND11_HEADERS include/pybind11/detail/smart_holder_sfinae_hooks_only.h include/pybind11/detail/smart_holder_type_casters.h include/pybind11/detail/type_caster_base.h + include/pybind11/detail/type_caster_odr_guard.h include/pybind11/detail/typeid.h include/pybind11/attr.h include/pybind11/buffer_info.h diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index d9d8d76e3a..139363c57c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -14,6 +14,7 @@ #include "detail/descr.h" #include "detail/smart_holder_sfinae_hooks_only.h" #include "detail/type_caster_base.h" +#include "detail/type_caster_odr_guard.h" #include "detail/typeid.h" #include "pytypes.h" @@ -44,9 +45,18 @@ class type_caster_for_class_ : public type_caster_base {}; template class type_caster : public type_caster_for_class_ {}; +#ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_ON + +template +using make_caster = type_caster_odr_guard, type_caster>>; + +#else + template using make_caster = type_caster>; +#endif + template struct type_uses_smart_holder_type_caster { static constexpr bool value @@ -56,11 +66,13 @@ struct type_uses_smart_holder_type_caster { // Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T template typename make_caster::template cast_op_type cast_op(make_caster &caster) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) return caster.operator typename make_caster::template cast_op_type(); } template typename make_caster::template cast_op_type::type> cast_op(make_caster &&caster) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) return std::move(caster).operator typename make_caster:: template cast_op_type::type>(); } @@ -80,10 +92,15 @@ class type_caster> { "`operator T &()` or `operator const T &()`"); public: - bool load(handle src, bool convert) { return subcaster.load(src, convert); } + bool load(handle src, bool convert) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(type) + return subcaster.load(src, convert); + } static constexpr auto name = caster_t::name; + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE static handle cast(const std::reference_wrapper &src, return_value_policy policy, handle parent) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(type) // It is definitely wrong to take ownership of this pointer, so mask that rvp if (policy == return_value_policy::take_ownership || policy == return_value_policy::automatic) { @@ -96,12 +113,13 @@ class type_caster> { explicit operator std::reference_wrapper() { return cast_op(subcaster); } }; -#define PYBIND11_TYPE_CASTER(type, py_name) \ +#define PYBIND11_DETAIL_TYPE_CASTER_HEAD(type, py_name) \ protected: \ type value; \ \ public: \ - static constexpr auto name = py_name; \ + static constexpr auto name = py_name; +#define PYBIND11_DETAIL_TYPE_CASTER_TAIL(type) \ template >::value, \ @@ -123,6 +141,22 @@ public: template \ using cast_op_type = ::pybind11::detail::movable_cast_op_type +#ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_ON + +# define PYBIND11_TYPE_CASTER(type, py_name) \ + PYBIND11_DETAIL_TYPE_CASTER_HEAD(type, py_name) \ + static constexpr auto source_file_line \ + = ::pybind11::detail::tu_local_const_name(__FILE__ ":" PYBIND11_TOSTRING(__LINE__)); \ + PYBIND11_DETAIL_TYPE_CASTER_TAIL(type) + +#else + +# define PYBIND11_TYPE_CASTER(type, py_name) \ + PYBIND11_DETAIL_TYPE_CASTER_HEAD(type, py_name) \ + PYBIND11_DETAIL_TYPE_CASTER_TAIL(type) + +#endif + template using is_std_char_type = any_of, /* std::string */ #if defined(PYBIND11_HAS_U8STRING) @@ -315,6 +349,7 @@ class type_caster : public type_caster { using cast_op_type = void *&; explicit operator void *&() { return value; } static constexpr auto name = const_name("capsule"); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE private: void *value = nullptr; @@ -542,6 +577,7 @@ struct type_caster::value>> { public: bool load(handle src, bool convert) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(StringType) if (!src) { return false; } @@ -557,6 +593,7 @@ struct type_caster::value>> { } static handle cast(const CharT *src, return_value_policy policy, handle parent) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(StringType) if (src == nullptr) { return pybind11::none().inc_ref(); } @@ -564,6 +601,7 @@ struct type_caster::value>> { } static handle cast(CharT src, return_value_policy policy, handle parent) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(StringType) if (std::is_same::value) { handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr); if (!s) { @@ -635,6 +673,7 @@ struct type_caster::value>> { } static constexpr auto name = const_name(PYBIND11_STRING_NAME); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE template using cast_op_type = pybind11::detail::cast_op_type<_T>; }; @@ -679,6 +718,7 @@ class tuple_caster { static constexpr auto name = const_name("Tuple[") + concat(make_caster::name...) + const_name("]"); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE template using cast_op_type = type; @@ -851,6 +891,7 @@ struct move_only_holder_caster { return type_caster_base::cast_holder(ptr, std::addressof(src)); } static constexpr auto name = type_caster_base::name; + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE }; #ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT @@ -1035,8 +1076,8 @@ struct return_value_policy_override< }; // Basic python -> C++ casting; throws if casting fails -template -type_caster &load_type(type_caster &conv, const handle &handle) { +template +make_caster &load_type(make_caster &conv, const handle &handle) { static_assert(!detail::is_pyobject::value, "Internal error: type_caster should only be used for C++ types"); if (!conv.load(handle, true)) { @@ -1054,8 +1095,9 @@ type_caster &load_type(type_caster &conv, const handle &ha // Wrapper around the above that also constructs and returns a type_caster template make_caster load_type(const handle &handle) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) make_caster conv; - load_type(conv, handle); + load_type(conv, handle); return conv; } @@ -1091,6 +1133,7 @@ object cast(T &&value, : std::is_lvalue_reference::value ? return_value_policy::copy : return_value_policy::move; } + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) return reinterpret_steal( detail::make_caster::cast(std::forward(value), policy, parent)); } @@ -1191,7 +1234,8 @@ using override_caster_t = conditional_t enable_if_t::value, T> cast_ref(object &&o, make_caster &caster) { - return cast_op(load_type(caster, o)); + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) + return cast_op(load_type(caster, o)); } template enable_if_t::value, T> cast_ref(object &&, @@ -1302,6 +1346,7 @@ struct arg_v : arg { type(type_id()) #endif { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) // Workaround! See: // https://github.com/pybind/pybind11/issues/2336 // https://github.com/pybind/pybind11/pull/2685#issuecomment-731286700 @@ -1532,6 +1577,7 @@ class unpacking_collector { private: template void process(list &args_list, T &&x) { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) auto o = reinterpret_steal( detail::make_caster::cast(std::forward(x), policy, {})); if (!o) { @@ -1676,6 +1722,7 @@ handle type::handle_of() { detail::type_uses_smart_holder_type_caster>::value, "py::type::of only supports the case where T is a registered C++ types."); + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) return detail::get_type_handle(typeid(T), true); } diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index 18adc40b1b..c95f5abb18 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -27,6 +27,7 @@ class type_caster { using cast_op_type = value_and_holder &; explicit operator value_and_holder &() { return *value; } static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE private: value_and_holder *value = nullptr; diff --git a/include/pybind11/detail/smart_holder_type_casters.h b/include/pybind11/detail/smart_holder_type_casters.h index 52b37d591c..95346cd08c 100644 --- a/include/pybind11/detail/smart_holder_type_casters.h +++ b/include/pybind11/detail/smart_holder_type_casters.h @@ -616,6 +616,7 @@ template struct smart_holder_type_caster : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE // static handle cast(T, ...) // is redundant (leads to ambiguous overloads). @@ -777,6 +778,7 @@ template struct smart_holder_type_caster> : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE static handle cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { switch (policy) { @@ -841,6 +843,7 @@ template struct smart_holder_type_caster> : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE static handle cast(const std::shared_ptr &src, return_value_policy policy, handle parent) { @@ -861,6 +864,7 @@ template struct smart_holder_type_caster> : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE static handle cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { if (policy != return_value_policy::automatic @@ -944,6 +948,7 @@ template struct smart_holder_type_caster> : smart_holder_type_caster_load, smart_holder_type_caster_class_hooks { static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE static handle cast(std::unique_ptr &&src, return_value_policy policy, handle parent) { diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index 777fbb7160..556d7afbfd 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -13,6 +13,7 @@ #include "common.h" #include "descr.h" #include "internals.h" +#include "type_caster_odr_guard.h" #include "typeid.h" #include @@ -911,6 +912,7 @@ class type_caster_base : public type_caster_generic { public: static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE type_caster_base() : type_caster_base(typeid(type)) {} explicit type_caster_base(const std::type_info &info) : type_caster_generic(info) {} diff --git a/include/pybind11/detail/type_caster_odr_guard.h b/include/pybind11/detail/type_caster_odr_guard.h new file mode 100644 index 0000000000..ecbc82d4ba --- /dev/null +++ b/include/pybind11/detail/type_caster_odr_guard.h @@ -0,0 +1,176 @@ +// Copyright (c) 2022 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +// The type_caster ODR guard feature requires Translation-Unit-local entities +// (https://en.cppreference.com/w/cpp/language/tu_local), a C++20 feature, but +// all tested C++17 compilers support this feature already. +#if !defined(PYBIND11_TYPE_CASTER_ODR_GUARD_ON) && !defined(PYBIND11_TYPE_CASTER_ODR_GUARD_OFF) \ + && (defined(_MSC_VER) || defined(PYBIND11_CPP17)) +# define PYBIND11_TYPE_CASTER_ODR_GUARD_ON +#endif + +#ifndef PYBIND11_TYPE_CASTER_ODR_GUARD_ON + +# define PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE + +# define PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(...) + +#else + +# if !defined(PYBIND11_CPP20) && defined(__GNUC__) && !defined(__clang__) \ + && !defined(__INTEL_COMPILER) +# pragma GCC diagnostic ignored "-Wsubobject-linkage" +# endif + +# include "common.h" +# include "descr.h" +# include "typeid.h" + +# include +# include +# include +# include +# include +# include +# include +# include + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(detail) + +inline std::unordered_map &type_caster_odr_guard_registry() { + static std::unordered_map reg; + return reg; +} + +inline unsigned &type_caster_odr_violation_detected_counter() { + static unsigned counter = 0; + return counter; +} + +inline bool try_builtin_file_line() { + const char *file = __builtin_FILE(); + unsigned line = __builtin_LINE(); + return file != nullptr && line != 0; // Just something. +} + +inline const char *source_file_line_basename(const char *sfl) { + unsigned i_base = 0; + for (unsigned i = 0; sfl[i] != '\0'; i++) { + if (sfl[i] == '/' || sfl[i] == '\\') { + i_base = i + 1; + } + } + return sfl + i_base; +} + +# ifndef PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_THROW_DISABLED +# define PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_THROW_DISABLED false +# endif + +inline void type_caster_odr_guard_impl(const std::type_info &intrinsic_type_info, + const char *source_file_line, + bool throw_disabled) { + // std::cout cannot be used here: static initialization could be incomplete. +# define PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_PRINTF_OFF +# ifdef PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_PRINTF_ON + std::fprintf(stdout, + "\nTYPE_CASTER_ODR_GUARD_IMPL %s %s\n", + clean_type_id(intrinsic_type_info.name()).c_str(), + source_file_line); + std::fflush(stdout); +# endif + auto ins = type_caster_odr_guard_registry().insert( + {std::type_index(intrinsic_type_info), source_file_line}); + auto reg_iter = ins.first; + auto added = ins.second; + if (!added + && strcmp(source_file_line_basename(reg_iter->second.c_str()), + source_file_line_basename(source_file_line)) + != 0) { + std::string msg("ODR VIOLATION DETECTED: pybind11::detail::type_caster<" + + clean_type_id(intrinsic_type_info.name()) + ">: SourceLocation1=\"" + + reg_iter->second + "\", SourceLocation2=\"" + source_file_line + "\""); + if (throw_disabled) { + std::fprintf(stderr, "\nDISABLED std::system_error: %s\n", msg.c_str()); + std::fflush(stderr); + type_caster_odr_violation_detected_counter()++; + } else { + throw std::system_error(std::make_error_code(std::errc::state_not_recoverable), msg); + } + } +} + +namespace { + +template +struct tu_local_descr : descr { + using descr_t = descr; + using descr_t::descr_t; +}; + +template +constexpr tu_local_descr tu_local_const_name(char const (&text)[N]) { + return tu_local_descr(text); +} +constexpr tu_local_descr<0> tu_local_const_name(char const (&)[1]) { return {}; } + +struct tu_local_no_data_always_false { + explicit operator bool() const noexcept { return false; } +}; + +} // namespace + +# ifndef PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT +# define PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT +# endif + +template +struct get_type_caster_source_file_line { +# ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT + static_assert(TypeCasterType::source_file_line, + "PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE is MISSING: Please add that macro to the " + "TypeCasterType, or undefine PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT"); +# else + static constexpr auto source_file_line = tu_local_const_name("UNAVAILABLE"); +# endif +}; + +template +struct get_type_caster_source_file_line< + TypeCasterType, + enable_if_t::value>> { + static constexpr auto source_file_line = TypeCasterType::source_file_line; +}; + +template +struct type_caster_odr_guard : TypeCasterType { + static tu_local_no_data_always_false translation_unit_local; +}; + +template +tu_local_no_data_always_false + type_caster_odr_guard::translation_unit_local + = []() { + type_caster_odr_guard_impl( + typeid(IntrinsicType), + get_type_caster_source_file_line::source_file_line.text, + PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_THROW_DISABLED); + return tu_local_no_data_always_false(); + }(); + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) + +# define PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE \ + static constexpr auto source_file_line \ + = ::pybind11::detail::tu_local_const_name(__FILE__ ":" PYBIND11_TOSTRING(__LINE__)); + +# define PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(...) \ + if (::pybind11::detail::make_caster<__VA_ARGS__>::translation_unit_local) { \ + } + +#endif diff --git a/include/pybind11/detail/typeid.h b/include/pybind11/detail/typeid.h index 8d99fc0286..a67b52135b 100644 --- a/include/pybind11/detail/typeid.h +++ b/include/pybind11/detail/typeid.h @@ -20,6 +20,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) PYBIND11_NAMESPACE_BEGIN(detail) + /// Erase all occurrences of a substring inline void erase_all(std::string &string, const std::string &search) { for (size_t pos = 0;;) { @@ -46,14 +47,19 @@ PYBIND11_NOINLINE void clean_type_id(std::string &name) { #endif detail::erase_all(name, "pybind11::"); } + +inline std::string clean_type_id(const char *typeid_name) { + std::string name(typeid_name); + detail::clean_type_id(name); + return name; +} + PYBIND11_NAMESPACE_END(detail) /// Return a string representation of a C++ type template static std::string type_id() { - std::string name(typeid(T).name()); - detail::clean_type_id(name); - return name; + return detail::clean_type_id(typeid(T).name()); } PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/include/pybind11/eigen.h b/include/pybind11/eigen.h index beb50266e4..93eb6ee0aa 100644 --- a/include/pybind11/eigen.h +++ b/include/pybind11/eigen.h @@ -392,6 +392,7 @@ struct type_caster::value>> { } static constexpr auto name = props::descriptor; + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE // NOLINTNEXTLINE(google-explicit-constructor) operator Type *() { return &value; } @@ -436,6 +437,7 @@ struct eigen_map_caster { } static constexpr auto name = props::descriptor; + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return // types but not bound arguments). We still provide them (with an explicitly delete) so that @@ -623,6 +625,7 @@ struct type_caster::value>> { } static constexpr auto name = props::descriptor; + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE // Explicitly delete these: support python -> C++ conversion on these (i.e. these can be return // types but not bound arguments). We still provide them (with an explicitly delete) so that diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 382f4bb923..03681f1d18 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -213,8 +213,9 @@ class cpp_function : public function { /* Type casters for the function arguments and return value */ using cast_in = argument_loader; - using cast_out - = make_caster::value, void_type, Return>>; + using make_caster_type_out = conditional_t::value, void_type, Return>; + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(make_caster_type_out) + using cast_out = make_caster; static_assert( expected_num_args( @@ -1854,6 +1855,7 @@ class class_ : public detail::generic_type { auto *ptr = new capture{std::forward(func)}; install_buffer_funcs( [](PyObject *obj, void *ptr) -> buffer_info * { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(type) detail::make_caster caster; if (!caster.load(obj, false)) { return nullptr; @@ -2715,6 +2717,7 @@ void implicitly_convertible() { return nullptr; } set_flag flag_helper(currently_used); + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(InputType) if (!detail::make_caster().load(obj, false)) { return nullptr; } diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index ab30ecac0b..6fe0bea37c 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -344,6 +344,7 @@ struct variant_caster_visitor { template result_type operator()(T &&src) const { + PYBIND11_DETAIL_TYPE_CASTER_ACCESS_TRANSLATION_UNIT_LOCAL(T) return make_caster::cast(std::forward(src), policy, parent); } }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4769ea6776..af57b8c7d7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -170,6 +170,8 @@ set(PYBIND11_TEST_FILES test_stl_binders test_tagbased_polymorphic test_thread + test_type_caster_odr_guard_1 + test_type_caster_odr_guard_2 test_union test_virtual_functions) diff --git a/tests/conftest.py b/tests/conftest.py index e72ec0ef81..d648997345 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ import pytest # Early diagnostic for failed imports -import pybind11_tests # noqa: F401 +import pybind11_tests _long_marker = re.compile(r"([0-9])L") _hexadecimal = re.compile(r"0x[0-9a-fA-F]+") @@ -196,5 +196,15 @@ def gc_collect(): def pytest_configure(): + print( + "C++ Info:", + pybind11_tests.compiler_info, + pybind11_tests.cpp_std, + pybind11_tests.PYBIND11_INTERNALS_ID, + flush=True, + ) + assert ( + pybind11_tests.compiler_info is not None + ), "Please update pybind11_tests.cpp if this assert fails." pytest.suppress = suppress pytest.gc_collect = gc_collect diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 8206000c91..7bcb5e59d3 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -48,6 +48,7 @@ "include/pybind11/detail/smart_holder_sfinae_hooks_only.h", "include/pybind11/detail/smart_holder_type_casters.h", "include/pybind11/detail/type_caster_base.h", + "include/pybind11/detail/type_caster_odr_guard.h", "include/pybind11/detail/typeid.h", } diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 3c04699157..6933dd3289 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -62,9 +62,32 @@ void bind_ConstructorStats(py::module_ &m) { }); } +const char *cpp_std() { + return +#if defined(PYBIND11_CPP20) + "C++20"; +#elif defined(PYBIND11_CPP17) + "C++17"; +#elif defined(PYBIND11_CPP14) + "C++14"; +#else + "C++11"; +#endif +} + PYBIND11_MODULE(pybind11_tests, m) { m.doc() = "pybind11 test module"; +#if defined(_MSC_FULL_VER) + m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER); +#elif defined(__VERSION__) + m.attr("compiler_info") = __VERSION__; +#else + m.attr("compiler_info") = py::none(); +#endif + m.attr("cpp_std") = cpp_std(); + m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID; + bind_ConstructorStats(m); #if defined(PYBIND11_DETAILED_ERROR_MESSAGES) diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index 9c0ec73f68..654e7f7f19 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -28,6 +28,7 @@ template <> class type_caster { public: static constexpr auto name = const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE // Input is unimportant, a new value will always be constructed based on the // cast operator. diff --git a/tests/test_copy_move.cpp b/tests/test_copy_move.cpp index 28c2445644..82e9eec29f 100644 --- a/tests/test_copy_move.cpp +++ b/tests/test_copy_move.cpp @@ -134,6 +134,7 @@ struct type_caster { public: static constexpr auto name = const_name("CopyOnlyInt"); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE bool load(handle src, bool) { value = CopyOnlyInt(src.cast()); return true; diff --git a/tests/test_type_caster_odr_guard_1.cpp b/tests/test_type_caster_odr_guard_1.cpp new file mode 100644 index 0000000000..3cafa4da4d --- /dev/null +++ b/tests/test_type_caster_odr_guard_1.cpp @@ -0,0 +1,67 @@ +#define PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_THROW_DISABLED true +#include "pybind11_tests.h" + +namespace mrc_ns { // minimal real caster + +struct type_mrc { + explicit type_mrc(int v = -9999) : value(v) {} + int value; +}; + +struct minimal_real_caster { + static constexpr auto name = py::detail::const_name(); + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE + + static py::handle + cast(type_mrc const &src, py::return_value_policy /*policy*/, py::handle /*parent*/) { + return py::int_(src.value + 1010).release(); // ODR violation. + } + + // Maximizing simplicity. This will go terribly wrong for other arg types. + template + using cast_op_type = const type_mrc &; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator type_mrc const &() { + static type_mrc obj; + obj.value = 11; // ODR violation. + return obj; + } + + bool load(py::handle src, bool /*convert*/) { + // Only accepts str, but the value is ignored. + return py::isinstance(src); + } +}; + +} // namespace mrc_ns + +namespace pybind11 { +namespace detail { +template <> +struct type_caster : mrc_ns::minimal_real_caster {}; +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_odr_guard_1, m) { + m.def("type_mrc_to_python", []() { return mrc_ns::type_mrc(101); }); + m.def("type_mrc_from_python", [](const mrc_ns::type_mrc &obj) { return obj.value + 100; }); + m.def("type_caster_odr_guard_registry_values", []() { +#ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_ON + py::list values; + for (const auto ®_iter : py::detail::type_caster_odr_guard_registry()) { + values.append(py::str(reg_iter.second)); + } + return values; +#else + return py::none(); +#endif + }); + m.def("type_caster_odr_violation_detected_count", []() { +#ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_ON + return py::detail::type_caster_odr_violation_detected_counter(); +#else + return py::none(); +#endif + }); +} diff --git a/tests/test_type_caster_odr_guard_1.py b/tests/test_type_caster_odr_guard_1.py new file mode 100644 index 0000000000..5274128211 --- /dev/null +++ b/tests/test_type_caster_odr_guard_1.py @@ -0,0 +1,51 @@ +import pytest + +import pybind11_tests +import pybind11_tests.type_caster_odr_guard_1 as m + + +def test_type_mrc_to_python(): + val = m.type_mrc_to_python() + if val == 101 + 2020: + pytest.skip( + "UNEXPECTED: test_type_caster_odr_guard_2.cpp prevailed (to_python)." + ) + else: + assert val == 101 + 1010 + + +def test_type_mrc_from_python(): + val = m.type_mrc_from_python("ignored") + if val == 100 + 22: + pytest.skip( + "UNEXPECTED: test_type_caster_odr_guard_2.cpp prevailed (from_python)." + ) + else: + assert val == 100 + 11 + + +def test_type_caster_odr_registry_values(): + reg_values = m.type_caster_odr_guard_registry_values() + if reg_values is None: + pytest.skip("type_caster_odr_guard_registry_values() is None") + else: + assert "test_type_caster_odr_guard_" in "\n".join(reg_values) + + +def test_type_caster_odr_violation_detected_counter(): + num_violations = m.type_caster_odr_violation_detected_count() + if num_violations is None: + pytest.skip("type_caster_odr_violation_detected_count() is None") + elif num_violations == 0 and ( + # 3.9-dbg (deadsnakes) Valgrind x64: + # This failure is unexplained and the condition here is not completely specific, + # but deemed a good-enough workaround. + pybind11_tests.compiler_info == "9.4.0" + and pybind11_tests.cpp_std == "C++17" + ): + pytest.skip( + "UNEXPECTED: type_caster_odr_violation_detected_count() == 0 (%s %s)" + % (pybind11_tests.compiler_info, pybind11_tests.cpp_std) + ) + else: + assert num_violations == 1 diff --git a/tests/test_type_caster_odr_guard_2.cpp b/tests/test_type_caster_odr_guard_2.cpp new file mode 100644 index 0000000000..126466b7db --- /dev/null +++ b/tests/test_type_caster_odr_guard_2.cpp @@ -0,0 +1,51 @@ +#define PYBIND11_DETAIL_TYPE_CASTER_ODR_GUARD_IMPL_THROW_DISABLED true +#include "pybind11_tests.h" + +namespace mrc_ns { // minimal real caster + +struct type_mrc { + explicit type_mrc(int v = -9999) : value(v) {} + int value; +}; + +struct minimal_real_caster { + static constexpr auto name = py::detail::const_name(); +#ifdef PYBIND11_TYPE_CASTER_ODR_GUARD_STRICT + PYBIND11_TYPE_CASTER_SOURCE_FILE_LINE +#endif + + static py::handle + cast(type_mrc const &src, py::return_value_policy /*policy*/, py::handle /*parent*/) { + return py::int_(src.value + 2020).release(); // ODR violation. + } + + // Maximizing simplicity. This will go terribly wrong for other arg types. + template + using cast_op_type = const type_mrc &; + + // NOLINTNEXTLINE(google-explicit-constructor) + operator type_mrc const &() { + static type_mrc obj; + obj.value = 22; // ODR violation. + return obj; + } + + bool load(py::handle src, bool /*convert*/) { + // Only accepts str, but the value is ignored. + return py::isinstance(src); + } +}; + +} // namespace mrc_ns + +namespace pybind11 { +namespace detail { +template <> +struct type_caster : mrc_ns::minimal_real_caster {}; +} // namespace detail +} // namespace pybind11 + +TEST_SUBMODULE(type_caster_odr_guard_2, m) { + m.def("type_mrc_to_python", []() { return mrc_ns::type_mrc(202); }); + m.def("type_mrc_from_python", [](const mrc_ns::type_mrc &obj) { return obj.value + 200; }); +} diff --git a/tests/test_type_caster_odr_guard_2.py b/tests/test_type_caster_odr_guard_2.py new file mode 100644 index 0000000000..b4f5ef6c41 --- /dev/null +++ b/tests/test_type_caster_odr_guard_2.py @@ -0,0 +1,23 @@ +import pytest + +import pybind11_tests.type_caster_odr_guard_2 as m + + +def test_type_mrc_to_python(): + val = m.type_mrc_to_python() + if val == 202 + 2020: + pytest.skip( + "UNEXPECTED: test_type_caster_odr_guard_2.cpp prevailed (to_python)." + ) + else: + assert val == 202 + 1010 + + +def test_type_mrc_from_python(): + val = m.type_mrc_from_python("ignored") + if val == 200 + 22: + pytest.skip( + "UNEXPECTED: test_type_caster_odr_guard_2.cpp prevailed (from_python)." + ) + else: + assert val == 200 + 11