Skip to content

Commit

Permalink
Add test_native_enum_double_finalize(), test_native_enum_value_after_…
Browse files Browse the repository at this point in the history
…finalize()
  • Loading branch information
rwgk committed Mar 10, 2025
1 parent 0da489d commit fb10d3b
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 9 deletions.
21 changes: 16 additions & 5 deletions include/pybind11/detail/native_enum_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "common.h"
#include "internals.h"

#include <cassert>
#include <string>
#include <typeindex>

Expand All @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
7 changes: 4 additions & 3 deletions include/pybind11/native_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Underlying>(value)));
if (doc) {
docs.append(make_tuple(name, doc));
}
arm_correct_use_check();
arm_finalize_check(); // There was no exception.
return *this;
}

Expand Down
17 changes: 16 additions & 1 deletion tests/test_native_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand All @@ -144,6 +143,22 @@ TEST_SUBMODULE(native_enum, m) {
py::native_enum<fake>{py::none(), malformed_utf8, py::native_enum_kind::IntEnum};
});

m.def("native_enum_double_finalize", [](py::module_ &m) {
enum fake { x };
py::native_enum<fake> 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<fake> 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<fake>(py::none(), "fake", py::native_enum_kind::IntEnum)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_native_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit fb10d3b

Please sign in to comment.