Merge 02bd5c82a16acb0b8677cb007ffbaa37f074f7e9 into a2f16e5f1f5fcc3b3d2cc1bdacfc3a027dfe2d76

This commit is contained in:
Abhijit Singh 2026-03-18 13:40:45 +05:30 committed by GitHub
commit 440f0c2c8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 1 deletions

View File

@ -70,6 +70,7 @@ args_proxy interface<Derived>::operator* () const {
template <typename Derived> template <typename Derived>
template <return_value_policy policy, typename... Args> template <return_value_policy policy, typename... Args>
object interface<Derived>::operator() (Args&&... args) const { object interface<Derived>::operator() (Args&&... args) const {
py_StackRef pc = py_peek(0); // Save checkpoint BEFORE push
py_push(ptr()); py_push(ptr());
py_pushnil(); py_pushnil();
@ -108,7 +109,16 @@ object interface<Derived>::operator() (Args&&... args) const {
(foreach(std::forward<Args>(args)), ...); (foreach(std::forward<Args>(args)), ...);
raise_call<py_vectorcall>(argc, kwargsc); // Don't use raise_call here - it saves checkpoint AFTER push, which causes
// an assertion failure in py_clearexc when py_vectorcall fails (because
// py_vectorcall pops 2+argc items on both success and failure).
// Instead, we save checkpoint BEFORE push and handle error explicitly.
if(!py_vectorcall(argc, kwargsc)) {
char* what = py_formatexc();
object e = object::from_ret();
py_clearexc(pc); // pc is now at or below current sp, assertion safe
throw python_error(what, std::move(e));
}
return object::from_ret(); return object::from_ret();
} }

View File

@ -410,4 +410,57 @@ TEST_F(PYBIND11_TEST, overload_cast) {
EXPECT_EVAL_EQ("X().add(1, 2, 3)", 6); EXPECT_EVAL_EQ("X().add(1, 2, 3)", 6);
} }
// Test for GitHub issue: calling Python function that raises exception
// from C++ via py::object::operator() should not crash with assertion failure
// in py_clearexc. The bug was that raise_call saved the stack checkpoint
// AFTER pushing callable + nil + args, but py_vectorcall pops 2+argc items
// on both success and failure, causing the checkpoint to be below the
// current stack pointer when py_clearexc is called.
TEST_F(PYBIND11_TEST, vectorcall_exception) {
auto m = py::module::__main__();
// Define a Python function that raises an exception
py::exec(R"(
def raise_error(msg):
raise ValueError(msg)
)");
// Get the function object
py::object raise_error = m.attr("raise_error");
// Call the function from C++ - this should catch the exception
// without crashing due to assertion failure in py_clearexc
try {
raise_error("test error");
FAIL() << "Expected py::python_error to be thrown";
} catch (py::python_error& e) {
// Expected: we should catch the exception
EXPECT_TRUE(e.match(tp_ValueError));
}
// Test with arguments
try {
py::str msg("error with arg");
raise_error(msg);
FAIL() << "Expected py::python_error to be thrown";
} catch (py::python_error& e) {
// Expected: we should catch the exception
EXPECT_TRUE(e.match(tp_ValueError));
}
// Test multiple arguments
py::exec(R"(
def raise_error2(a, b, c):
raise RuntimeError(f'{a}-{b}-{c}')
)");
py::object raise_error2 = m.attr("raise_error2");
try {
raise_error2(1, 2, 3);
FAIL() << "Expected py::python_error to be thrown";
} catch (py::python_error& e) {
EXPECT_TRUE(e.match(tp_RuntimeError));
}
}
} // namespace } // namespace