From 0676b21da2827b827ee7cac89e9b27c4aa3c005e Mon Sep 17 00:00:00 2001 From: Jason Matthew Suhari Date: Fri, 20 Mar 2026 13:22:25 +0800 Subject: [PATCH] fix: save stack checkpoint before pushing args in operator() (#479) * fix: capture stack checkpoint before pushing args in operator() (#469) * test: add regression test for operator() python error propagation (#469) --- include/pybind11/internal/function.h | 9 ++++++++- include/pybind11/tests/error.cpp | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/pybind11/internal/function.h b/include/pybind11/internal/function.h index 2c84e756..c1c69dff 100644 --- a/include/pybind11/internal/function.h +++ b/include/pybind11/internal/function.h @@ -70,6 +70,7 @@ args_proxy interface::operator* () const { template template object interface::operator() (Args&&... args) const { + py_StackRef p0 = py_peek(0); // checkpoint before pushing so py_clearexc can safely rewind py_push(ptr()); py_pushnil(); @@ -108,7 +109,13 @@ object interface::operator() (Args&&... args) const { (foreach(std::forward(args)), ...); - raise_call(argc, kwargsc); + if(!py_vectorcall(argc, kwargsc)) { + py_matchexc(tp_Exception); + object e = object::from_ret(); + auto what = py_formatexc(); + py_clearexc(p0); + throw python_error(what, std::move(e)); + } return object::from_ret(); } diff --git a/include/pybind11/tests/error.cpp b/include/pybind11/tests/error.cpp index 8f945b23..b93a4d11 100644 --- a/include/pybind11/tests/error.cpp +++ b/include/pybind11/tests/error.cpp @@ -79,3 +79,18 @@ TEST_F(PYBIND11_TEST, exception_cpp_to_python) { TEST_EXCEPTION(attribute_error, AttributeError); TEST_EXCEPTION(runtime_error, RuntimeError); } + +// Regression test: operator() must throw python_error instead of crashing when Python raises (#469) +TEST_F(PYBIND11_TEST, operator_call_propagates_python_error) { + py::exec("def f(x):\n raise ValueError('intentional error')"); + py::object fn = py::eval("f"); + + bool caught = false; + try { + fn(py::int_(1)); + } catch(py::python_error& e) { + caught = true; + EXPECT_TRUE(e.match(tp_ValueError)); + } + EXPECT_TRUE(caught); +}