Skip to content

Commit 6ed2830

Browse files
XuehaiPanclaude
andcommitted
Add re-entrancy detection for internals creation
Prevent re-creation of internals after destruction during interpreter shutdown. If pybind11 code runs after internals have been destroyed, fail early with a clear error message instead of silently creating new empty internals that would cause type lookup failures. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cdefbf3 commit 6ed2830

File tree

1 file changed

+18
-1
lines changed

1 file changed

+18
-1
lines changed

include/pybind11/detail/internals.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,17 +697,29 @@ class internals_pp_manager {
697697
// this could be called without an active interpreter, just use what was cached
698698
if (!tstate || tstate->interp == last_istate_tls()) {
699699
auto tpp = internals_p_tls();
700-
700+
pps_have_created_content_.erase(tpp);
701701
delete tpp;
702702
}
703703
unref();
704704
return;
705705
}
706706
#endif
707+
pps_have_created_content_.erase(internals_singleton_pp_);
707708
delete internals_singleton_pp_;
708709
unref();
709710
}
710711

712+
static void fail_if_internals_recreated(std::unique_ptr<InternalsType> *pp) {
713+
// Prevent re-creation of internals after destruction during interpreter shutdown.
714+
// If pybind11 code (e.g., tp_traverse/tp_clear calling py::cast) runs after internals
715+
// have been destroyed, a new empty internals would be created, causing type lookup
716+
// failures. See https://github.com/pybind/pybind11/pull/5958#discussion_r2717645230.
717+
if (pps_have_created_content_.find(pp) != pps_have_created_content_.end()) {
718+
pybind11_fail("Reentrant call detected while fetching pybind11 internals!");
719+
}
720+
pps_have_created_content_.insert(pp);
721+
}
722+
711723
private:
712724
internals_pp_manager(char const *id, on_fetch_function *on_fetch)
713725
: holder_id_(id), on_fetch_(on_fetch) {}
@@ -748,6 +760,9 @@ class internals_pp_manager {
748760
// Pointer-to-pointer to the singleton internals for the first seen interpreter (may not be the
749761
// main interpreter)
750762
std::unique_ptr<InternalsType> *internals_singleton_pp_ = nullptr;
763+
764+
// Tracks pointer-to-pointers whose internals have been created, to detect re-entrancy.
765+
inline static std::unordered_set<void *> pps_have_created_content_{};
751766
};
752767

753768
// If We loaded the internals through `state_dict`, our `error_already_set`
@@ -788,6 +803,7 @@ PYBIND11_NOINLINE internals &get_internals() {
788803
// Slow path, something needs fetched from the state dict or created
789804
gil_scoped_acquire_simple gil;
790805
error_scope err_scope;
806+
internals_pp_manager<internals>::fail_if_internals_recreated(&internals_ptr);
791807
internals_ptr.reset(new internals());
792808

793809
if (!internals_ptr->instance_base) {
@@ -847,6 +863,7 @@ inline local_internals &get_local_internals() {
847863
auto &internals_ptr = *ppmgr.get_pp();
848864
if (!internals_ptr) {
849865
gil_scoped_acquire_simple gil;
866+
internals_pp_manager<local_internals>::fail_if_internals_recreated(&internals_ptr);
850867
internals_ptr.reset(new local_internals());
851868
}
852869
return *internals_ptr;

0 commit comments

Comments
 (0)