Skip to content

Commit 5b37916

Browse files
cyyeverrwgk
andauthored
Apply clang-tidy fixes to subinterpreter support code (#5929)
* Fix PyObject_HasAttrString return value Signed-off-by: cyy <[email protected]> * Tidy unchecked files Signed-off-by: cyy <[email protected]> * [skip ci] Handle PyObject_HasAttrString error when probing __notes__ PyObject_HasAttrString may return -1 to signal an error and set a Python exception. The previous logic only checked for "!= 0", which meant that the error path was treated the same as "attribute exists", causing two problems: misreporting the presence of __notes__ and leaving a spurious exception pending. The earlier PR tightened the condition to "== 1" so that only a successful lookup marks the error string as [WITH __notes__], but it still left the -1 case unhandled. In the context of error_fetch_and_normalize, we are already dealing with an active exception and only want to best-effort detect whether normalization attached any __notes__. If the attribute probe itself fails, we do not want that secondary failure to affect later C-API calls or the error we ultimately report. This change stores the PyObject_HasAttrString return value, treats "== 1" as "has __notes__", and explicitly calls PyErr_Clear() when it returns -1. That way, we avoid leaking a secondary error while still preserving the original exception information and hinting [WITH __notes__] only when we can determine it reliably. * Run clang-tidy with -DPYBIND11_HAS_SUBINTERPRETER_SUPPORT * [skip ci] Revert "Run clang-tidy with -DPYBIND11_HAS_SUBINTERPRETER_SUPPORT" This reverts commit bb6e751. --------- Signed-off-by: cyy <[email protected]> Co-authored-by: Ralf W. Grosse-Kunstleve <[email protected]>
1 parent 1006933 commit 5b37916

File tree

2 files changed

+23
-17
lines changed

2 files changed

+23
-17
lines changed

include/pybind11/pytypes.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,13 @@ struct error_fetch_and_normalize {
547547
// The presence of __notes__ is likely due to exception normalization
548548
// errors, although that is not necessarily true, therefore insert a
549549
// hint only:
550-
if (PyObject_HasAttrString(m_value.ptr(), "__notes__") != 0) {
550+
const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__");
551+
if (has_notes == 1) {
551552
m_lazy_error_string += "[WITH __notes__]";
553+
} else if (has_notes == -1) {
554+
// Ignore secondary errors when probing for __notes__ to avoid leaking a
555+
// spurious exception while still reporting the original error.
556+
PyErr_Clear();
552557
}
553558
#else
554559
// PyErr_NormalizeException() may change the exception type if there are cascading

include/pybind11/subinterpreter.h

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
2323
PYBIND11_NAMESPACE_BEGIN(detail)
2424
inline PyInterpreterState *get_interpreter_state_unchecked() {
25-
auto cur_tstate = get_thread_state_unchecked();
26-
if (cur_tstate)
25+
auto *cur_tstate = get_thread_state_unchecked();
26+
if (cur_tstate) {
2727
return cur_tstate->interp;
28-
else
29-
return nullptr;
28+
}
29+
return nullptr;
3030
}
3131
PYBIND11_NAMESPACE_END(detail)
3232

@@ -76,15 +76,15 @@ class subinterpreter {
7676
/// Create a new subinterpreter with the specified configuration
7777
/// @note This function acquires (and then releases) the main interpreter GIL, but the main
7878
/// interpreter and its GIL are not required to be held prior to calling this function.
79-
static inline subinterpreter create(PyInterpreterConfig const &cfg) {
79+
static subinterpreter create(PyInterpreterConfig const &cfg) {
8080

8181
error_scope err_scope;
8282
subinterpreter result;
8383
{
8484
// we must hold the main GIL in order to create a subinterpreter
8585
subinterpreter_scoped_activate main_guard(main());
8686

87-
auto prev_tstate = PyThreadState_Get();
87+
auto *prev_tstate = PyThreadState_Get();
8888

8989
PyStatus status;
9090

@@ -103,7 +103,7 @@ class subinterpreter {
103103
}
104104

105105
// this doesn't raise a normal Python exception, it provides an exit() status code.
106-
if (PyStatus_Exception(status)) {
106+
if (PyStatus_Exception(status) != 0) {
107107
pybind11_fail("failed to create new sub-interpreter");
108108
}
109109

@@ -128,7 +128,7 @@ class subinterpreter {
128128

129129
/// Calls create() with a default configuration of an isolated interpreter that disallows fork,
130130
/// exec, and Python threads.
131-
static inline subinterpreter create() {
131+
static subinterpreter create() {
132132
// same as the default config in the python docs
133133
PyInterpreterConfig cfg;
134134
std::memset(&cfg, 0, sizeof(cfg));
@@ -144,8 +144,8 @@ class subinterpreter {
144144
return;
145145
}
146146

147-
PyThreadState *destroy_tstate;
148-
PyThreadState *old_tstate;
147+
PyThreadState *destroy_tstate = nullptr;
148+
PyThreadState *old_tstate = nullptr;
149149

150150
// Python 3.12 requires us to keep the original PyThreadState alive until we are ready to
151151
// destroy the interpreter. We prefer to use that to destroy the interpreter.
@@ -173,7 +173,7 @@ class subinterpreter {
173173
old_tstate = PyThreadState_Swap(destroy_tstate);
174174
#endif
175175

176-
bool switch_back = old_tstate && old_tstate->interp != istate_;
176+
bool switch_back = (old_tstate != nullptr) && old_tstate->interp != istate_;
177177

178178
// Internals always exists in the subinterpreter, this class enforces it when it creates
179179
// the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
@@ -190,8 +190,9 @@ class subinterpreter {
190190
detail::get_local_internals_pp_manager().destroy();
191191

192192
// switch back to the old tstate and old GIL (if there was one)
193-
if (switch_back)
193+
if (switch_back) {
194194
PyThreadState_Swap(old_tstate);
195+
}
195196
}
196197

197198
/// Get a handle to the main interpreter that can be used with subinterpreter_scoped_activate
@@ -214,11 +215,11 @@ class subinterpreter {
214215

215216
/// Get the numerical identifier for the sub-interpreter
216217
int64_t id() const {
217-
if (istate_ != nullptr)
218+
if (istate_ != nullptr) {
218219
return PyInterpreterState_GetID(istate_);
219-
else
220-
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
221-
// here.
220+
}
221+
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
222+
// here.
222223
}
223224

224225
/// Get the interpreter's state dict. This interpreter's GIL must be held before calling!

0 commit comments

Comments
 (0)