From fb10d3b4dc01d3fc7d6b9f13b4e6f676a8772974 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 9 Mar 2025 20:21:53 -0700 Subject: [PATCH] Add test_native_enum_double_finalize(), test_native_enum_value_after_finalize() --- include/pybind11/detail/native_enum_data.h | 21 ++++++++++++++++----- include/pybind11/native_enum.h | 7 ++++--- tests/test_native_enum.cpp | 17 ++++++++++++++++- tests/test_native_enum.py | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/include/pybind11/detail/native_enum_data.h b/include/pybind11/detail/native_enum_data.h index d65a80fcf7..c3eb4f4a00 100644 --- a/include/pybind11/detail/native_enum_data.h +++ b/include/pybind11/detail/native_enum_data.h @@ -10,6 +10,7 @@ #include "common.h" #include "internals.h" +#include #include #include @@ -30,8 +31,18 @@ class native_enum_data { native_enum_data(const native_enum_data &) = delete; native_enum_data &operator=(const native_enum_data &) = delete; - void disarm_correct_use_check() const { correct_use_check = false; } - void arm_correct_use_check() const { correct_use_check = true; } + void disarm_finalize_check(const char *error_context) const { + if (!finalize_needed) { + pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded + + "\"): " + error_context); + } + finalize_needed = false; + } + + void arm_finalize_check() const { + assert(!finalize_needed); + finalize_needed = true; + } // This is a separate public function only to enable easy unit testing. std::string missing_finalize_error_message() const { @@ -42,14 +53,14 @@ class native_enum_data { #if !defined(NDEBUG) // This dtor cannot easily be unit tested because it terminates the process. ~native_enum_data() { - if (correct_use_check) { + if (finalize_needed) { pybind11_fail(missing_finalize_error_message()); } } #endif private: - mutable bool correct_use_check{false}; + mutable bool finalize_needed{false}; public: object parent_scope; @@ -87,7 +98,7 @@ global_internals_native_enum_type_map_contains(const std::type_index &enum_type_ } inline void native_enum_data::finalize() { - disarm_correct_use_check(); + disarm_finalize_check("DOUBLE finalize"); if (hasattr(parent_scope, enum_name)) { pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded + "\"): an object with that name is already defined"); diff --git a/include/pybind11/native_enum.h b/include/pybind11/native_enum.h index 8f44a66c5c..6ea4aa51f1 100644 --- a/include/pybind11/native_enum.h +++ b/include/pybind11/native_enum.h @@ -38,7 +38,7 @@ class native_enum : public detail::native_enum_data { pybind11_fail("pybind11::native_enum<...>(\"" + enum_name_encoded + "\") is already registered!"); } - arm_correct_use_check(); + arm_finalize_check(); } /// Export enumeration entries into the parent scope @@ -49,12 +49,13 @@ class native_enum : public detail::native_enum_data { /// Add an enumeration entry native_enum &value(char const *name, Type value, const char *doc = nullptr) { - disarm_correct_use_check(); + // Disarm for the case that the native_enum_data dtor runs during exception unwinding. + disarm_finalize_check("value after finalize"); members.append(make_tuple(name, static_cast(value))); if (doc) { docs.append(make_tuple(name, doc)); } - arm_correct_use_check(); + arm_finalize_check(); // There was no exception. return *this; } diff --git a/tests/test_native_enum.cpp b/tests/test_native_enum.cpp index 96030454db..403598fa8a 100644 --- a/tests/test_native_enum.cpp +++ b/tests/test_native_enum.cpp @@ -135,7 +135,6 @@ TEST_SUBMODULE(native_enum, m) { m.def("native_enum_data_missing_finalize_error_message", [](const char *enum_name) { py::detail::native_enum_data data( py::none(), enum_name, std::type_index(typeid(void)), false); - data.disarm_correct_use_check(); return data.missing_finalize_error_message(); }); @@ -144,6 +143,22 @@ TEST_SUBMODULE(native_enum, m) { py::native_enum{py::none(), malformed_utf8, py::native_enum_kind::IntEnum}; }); + m.def("native_enum_double_finalize", [](py::module_ &m) { + enum fake { x }; + py::native_enum ne( + m, "fake_native_enum_double_finalize", py::native_enum_kind::IntEnum); + ne.finalize(); + ne.finalize(); + }); + + m.def("native_enum_value_after_finalize", [](py::module_ &m) { + enum fake { x }; + py::native_enum ne( + m, "fake_native_enum_value_after_finalize", py::native_enum_kind::IntEnum); + ne.finalize(); + ne.value("x", fake::x); + }); + m.def("native_enum_value_malformed_utf8", [](const char *malformed_utf8) { enum fake { x }; py::native_enum(py::none(), "fake", py::native_enum_kind::IntEnum) diff --git a/tests/test_native_enum.py b/tests/test_native_enum.py index f4d92c0f68..1ad51a6fcb 100644 --- a/tests/test_native_enum.py +++ b/tests/test_native_enum.py @@ -161,6 +161,24 @@ def test_native_enum_malformed_utf8(func): func(malformed_utf8) +def test_native_enum_double_finalize(): + with pytest.raises(RuntimeError) as excinfo: + m.native_enum_double_finalize(m) + assert ( + str(excinfo.value) + == 'pybind11::native_enum<...>("fake_native_enum_double_finalize"): DOUBLE finalize' + ) + + +def test_native_enum_value_after_finalize(): + with pytest.raises(RuntimeError) as excinfo: + m.native_enum_value_after_finalize(m) + assert ( + str(excinfo.value) + == 'pybind11::native_enum<...>("fake_native_enum_value_after_finalize"): value after finalize' + ) + + def test_double_registration_native_enum(): with pytest.raises(RuntimeError) as excinfo: m.double_registration_native_enum(m)