mirror of
https://github.com/pocketpy/pocketpy
synced 2026-03-22 05:00:17 +00:00
Merge 02bd5c82a16acb0b8677cb007ffbaa37f074f7e9 into a2f16e5f1f5fcc3b3d2cc1bdacfc3a027dfe2d76
This commit is contained in:
commit
440f0c2c8b
@ -70,6 +70,7 @@ args_proxy interface<Derived>::operator* () const {
|
||||
template <typename Derived>
|
||||
template <return_value_policy policy, typename... Args>
|
||||
object interface<Derived>::operator() (Args&&... args) const {
|
||||
py_StackRef pc = py_peek(0); // Save checkpoint BEFORE push
|
||||
py_push(ptr());
|
||||
py_pushnil();
|
||||
|
||||
@ -108,7 +109,16 @@ object interface<Derived>::operator() (Args&&... args) const {
|
||||
|
||||
(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();
|
||||
}
|
||||
|
||||
@ -410,4 +410,57 @@ TEST_F(PYBIND11_TEST, overload_cast) {
|
||||
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user