Skip to content

Commit 528c0c2

Browse files
committed
test: add embedding test for py::enum_ across interpreter restart (gh-5976)
py::enum_ is the primary trigger for gh-5976 because its constructor creates properties via def_property_static / def_property_readonly_static, which call process_attributes::init on already-initialized function records. Yet none of the existing embedding tests used py::enum_ at all. Add an PYBIND11_EMBEDDED_MODULE with py::enum_ and a test case that imports it, finalize/reinitializes the interpreter, and re-imports it. This exercises the def_property_static code path that was fixed in the preceding commit. Note: on Python 3.14.2 (and likely 3.12+), tp_dealloc_impl is not called during Py_FinalizeEx for function record PyObjects — they simply leak because types are effectively immortalized. As a result, this test cannot trigger the original free()-on-string-literal crash on this Python version. However, it remains valuable as a regression guard: on Python builds where finalization does clean up function records (or if CPython changes this behavior), the test would catch the crash. It also verifies that py::enum_ survives interpreter restart correctly, which was previously untested. Made-with: Cursor
1 parent 3b62426 commit 528c0c2

1 file changed

Lines changed: 23 additions & 0 deletions

File tree

tests/test_with_catch/test_interpreter.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
8484
.def("func", &test_override_cache_helper::func);
8585
}
8686

87+
enum class SomeEnum { value1, value2 };
88+
89+
PYBIND11_EMBEDDED_MODULE(enum_module, m, py::multiple_interpreters::per_interpreter_gil()) {
90+
py::enum_<SomeEnum>(m, "SomeEnum")
91+
.value("value1", SomeEnum::value1)
92+
.value("value2", SomeEnum::value2);
93+
}
94+
8795
PYBIND11_EMBEDDED_MODULE(throw_exception, ) { throw std::runtime_error("C++ Error"); }
8896

8997
PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
@@ -343,6 +351,21 @@ TEST_CASE("Restart the interpreter") {
343351
REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
344352
}
345353

354+
TEST_CASE("Enum module survives restart") {
355+
// Regression test for gh-5976: py::enum_ uses def_property_static, which
356+
// calls process_attributes::init after initialize_generic's strdup loop,
357+
// leaving arg names as string literals. Without the fix, destruct() would
358+
// call free() on those literals during interpreter finalization.
359+
auto enum_mod = py::module_::import("enum_module");
360+
REQUIRE(enum_mod.attr("SomeEnum").attr("value1").attr("name").cast<std::string>() == "value1");
361+
362+
py::finalize_interpreter();
363+
py::initialize_interpreter();
364+
365+
enum_mod = py::module_::import("enum_module");
366+
REQUIRE(enum_mod.attr("SomeEnum").attr("value2").attr("name").cast<std::string>() == "value2");
367+
}
368+
346369
TEST_CASE("Execution frame") {
347370
// When the interpreter is embedded, there is no execution frame, but `py::exec`
348371
// should still function by using reasonable globals: `__main__.__dict__`.

0 commit comments

Comments
 (0)