Skip to content

Commit 44596bc

Browse files
hawkinspSkylion007
andauthored
Fix exception handling when pybind11::weakref() fails. (#3739)
* Clear Python error state if pybind11::weakref() fails. The weakref() constructor calls pybind11_fail() without clearing any Python interpreter error state. If a client catches the C++ exception thrown by pybind11_fail(), the Python interpreter will be left in an error state. * Add test case for failing to create weakref * Add Debug asserts for pybind11 fail * Make error handling more pythonic * Does this fix PyPy? * Adapt test to PyPy differences * Simplify test to remove redundancy Co-authored-by: Aaron Gokaslan <[email protected]>
1 parent 009ffc3 commit 44596bc

File tree

3 files changed

+32
-1
lines changed

3 files changed

+32
-1
lines changed

include/pybind11/detail/common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,9 +936,11 @@ PYBIND11_RUNTIME_EXCEPTION(cast_error, PyExc_RuntimeError) /// Thrown when pybin
936936
PYBIND11_RUNTIME_EXCEPTION(reference_cast_error, PyExc_RuntimeError) /// Used internally
937937

938938
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const char *reason) {
939+
assert(!PyErr_Occurred());
939940
throw std::runtime_error(reason);
940941
}
941942
[[noreturn]] PYBIND11_NOINLINE void pybind11_fail(const std::string &reason) {
943+
assert(!PyErr_Occurred());
942944
throw std::runtime_error(reason);
943945
}
944946

include/pybind11/pytypes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,9 @@ class weakref : public object {
15091509
explicit weakref(handle obj, handle callback = {})
15101510
: object(PyWeakref_NewRef(obj.ptr(), callback.ptr()), stolen_t{}) {
15111511
if (!m_ptr) {
1512+
if (PyErr_Occurred()) {
1513+
throw error_already_set();
1514+
}
15121515
pybind11_fail("Could not allocate weak reference!");
15131516
}
15141517
}

tests/test_pytypes.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import contextlib
12
import sys
23

34
import pytest
45

5-
import env # noqa: F401
6+
import env
67
from pybind11_tests import debug_enabled
78
from pybind11_tests import pytypes as m
89

@@ -583,6 +584,31 @@ def callback(wr):
583584
assert callback_called
584585

585586

587+
@pytest.mark.parametrize(
588+
"create_weakref, has_callback",
589+
[
590+
(m.weakref_from_handle, False),
591+
(m.weakref_from_object, False),
592+
(m.weakref_from_handle_and_function, True),
593+
(m.weakref_from_object_and_function, True),
594+
],
595+
)
596+
def test_weakref_err(create_weakref, has_callback):
597+
class C:
598+
__slots__ = []
599+
600+
def callback(_):
601+
pass
602+
603+
ob = C()
604+
# Should raise TypeError on CPython
605+
with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext():
606+
if has_callback:
607+
_ = create_weakref(ob, callback)
608+
else:
609+
_ = create_weakref(ob)
610+
611+
586612
def test_cpp_iterators():
587613
assert m.tuple_iterator() == 12
588614
assert m.dict_iterator() == 305 + 711

0 commit comments

Comments
 (0)