mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Properly translate C++ exception to Python exception when creating Python buffer from wrapped object (#5324)
* Add test for throwing def_buffer case * Translate C++ -> Python exceptions in buffer creation This required a little refactoring to extract exception translation to a separate method * Fix code formatting * Fix "unused parameter" warning * Refactor per review * style: pre-commit fixes * Address review comments * Address review comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
570d323bb6
commit
9e6a67d572
@ -155,6 +155,7 @@ set(PYBIND11_HEADERS
|
||||
include/pybind11/detail/type_caster_base.h
|
||||
include/pybind11/detail/typeid.h
|
||||
include/pybind11/detail/value_and_holder.h
|
||||
include/pybind11/detail/exception_translation.h
|
||||
include/pybind11/attr.h
|
||||
include/pybind11/buffer_info.h
|
||||
include/pybind11/cast.h
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <pybind11/attr.h>
|
||||
#include <pybind11/options.h>
|
||||
|
||||
#include "exception_translation.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
@ -591,7 +593,18 @@ extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int fla
|
||||
return -1;
|
||||
}
|
||||
std::memset(view, 0, sizeof(Py_buffer));
|
||||
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||
buffer_info *info = nullptr;
|
||||
try {
|
||||
info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||
} catch (...) {
|
||||
try_translate_exceptions();
|
||||
raise_from(PyExc_BufferError, "Error getting buffer");
|
||||
return -1;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
pybind11_fail("FATAL UNEXPECTED SITUATION: tinfo->get_buffer() returned nullptr.");
|
||||
}
|
||||
|
||||
if ((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE && info->readonly) {
|
||||
delete info;
|
||||
// view->obj = nullptr; // Was just memset to 0, so not necessary
|
||||
|
71
include/pybind11/detail/exception_translation.h
Normal file
71
include/pybind11/detail/exception_translation.h
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
pybind11/detail/exception_translation.h: means to translate C++ exceptions to Python exceptions
|
||||
|
||||
Copyright (c) 2024 The Pybind Development Team.
|
||||
|
||||
All rights reserved. Use of this source code is governed by a
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "internals.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
// Apply all the extensions translators from a list
|
||||
// Return true if one of the translators completed without raising an exception
|
||||
// itself. Return of false indicates that if there are other translators
|
||||
// available, they should be tried.
|
||||
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators) {
|
||||
auto last_exception = std::current_exception();
|
||||
|
||||
for (auto &translator : translators) {
|
||||
try {
|
||||
translator(last_exception);
|
||||
return true;
|
||||
} catch (...) {
|
||||
last_exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void try_translate_exceptions() {
|
||||
/* When an exception is caught, give each registered exception
|
||||
translator a chance to translate it to a Python exception. First
|
||||
all module-local translators will be tried in reverse order of
|
||||
registration. If none of the module-locale translators handle
|
||||
the exception (or there are no module-locale translators) then
|
||||
the global translators will be tried, also in reverse order of
|
||||
registration.
|
||||
|
||||
A translator may choose to do one of the following:
|
||||
|
||||
- catch the exception and call py::set_error()
|
||||
to set a standard (or custom) Python exception, or
|
||||
- do nothing and let the exception fall through to the next translator, or
|
||||
- delegate translation to the next translator by throwing a new type of exception.
|
||||
*/
|
||||
|
||||
bool handled = with_internals([&](internals &internals) {
|
||||
auto &local_exception_translators = get_local_internals().registered_exception_translators;
|
||||
if (detail::apply_exception_translators(local_exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
auto &exception_translators = internals.registered_exception_translators;
|
||||
if (detail::apply_exception_translators(exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!handled) {
|
||||
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
|
||||
}
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
@ -9,8 +9,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "detail/class.h"
|
||||
#include "detail/exception_translation.h"
|
||||
#include "detail/init.h"
|
||||
#include "attr.h"
|
||||
#include "gil.h"
|
||||
@ -95,24 +95,6 @@ inline std::string replace_newlines_and_squash(const char *text) {
|
||||
return result.substr(str_begin, str_range);
|
||||
}
|
||||
|
||||
// Apply all the extensions translators from a list
|
||||
// Return true if one of the translators completed without raising an exception
|
||||
// itself. Return of false indicates that if there are other translators
|
||||
// available, they should be tried.
|
||||
inline bool apply_exception_translators(std::forward_list<ExceptionTranslator> &translators) {
|
||||
auto last_exception = std::current_exception();
|
||||
|
||||
for (auto &translator : translators) {
|
||||
try {
|
||||
translator(last_exception);
|
||||
return true;
|
||||
} catch (...) {
|
||||
last_exception = std::current_exception();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define PYBIND11_COMPAT_STRDUP _strdup
|
||||
#else
|
||||
@ -1038,40 +1020,7 @@ protected:
|
||||
throw;
|
||||
#endif
|
||||
} catch (...) {
|
||||
/* When an exception is caught, give each registered exception
|
||||
translator a chance to translate it to a Python exception. First
|
||||
all module-local translators will be tried in reverse order of
|
||||
registration. If none of the module-locale translators handle
|
||||
the exception (or there are no module-locale translators) then
|
||||
the global translators will be tried, also in reverse order of
|
||||
registration.
|
||||
|
||||
A translator may choose to do one of the following:
|
||||
|
||||
- catch the exception and call py::set_error()
|
||||
to set a standard (or custom) Python exception, or
|
||||
- do nothing and let the exception fall through to the next translator, or
|
||||
- delegate translation to the next translator by throwing a new type of exception.
|
||||
*/
|
||||
|
||||
bool handled = with_internals([&](internals &internals) {
|
||||
auto &local_exception_translators
|
||||
= get_local_internals().registered_exception_translators;
|
||||
if (detail::apply_exception_translators(local_exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
auto &exception_translators = internals.registered_exception_translators;
|
||||
if (detail::apply_exception_translators(exception_translators)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (handled) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
set_error(PyExc_SystemError, "Exception escaped from default exception translator!");
|
||||
try_translate_exceptions();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ detail_headers = {
|
||||
"include/pybind11/detail/type_caster_base.h",
|
||||
"include/pybind11/detail/typeid.h",
|
||||
"include/pybind11/detail/value_and_holder.h",
|
||||
"include/pybind11/detail/exception_translation.h",
|
||||
}
|
||||
|
||||
eigen_headers = {
|
||||
|
@ -167,6 +167,18 @@ TEST_SUBMODULE(buffers, m) {
|
||||
sizeof(float)});
|
||||
});
|
||||
|
||||
class BrokenMatrix : public Matrix {
|
||||
public:
|
||||
BrokenMatrix(py::ssize_t rows, py::ssize_t cols) : Matrix(rows, cols) {}
|
||||
void throw_runtime_error() { throw std::runtime_error("See PR #5324 for context."); }
|
||||
};
|
||||
py::class_<BrokenMatrix>(m, "BrokenMatrix", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t, py::ssize_t>())
|
||||
.def_buffer([](BrokenMatrix &m) {
|
||||
m.throw_runtime_error();
|
||||
return py::buffer_info();
|
||||
});
|
||||
|
||||
// test_inherited_protocol
|
||||
class SquareMatrix : public Matrix {
|
||||
public:
|
||||
|
@ -228,3 +228,10 @@ def test_buffer_docstring():
|
||||
m.get_buffer_info.__doc__.strip()
|
||||
== "get_buffer_info(arg0: Buffer) -> pybind11_tests.buffers.buffer_info"
|
||||
)
|
||||
|
||||
|
||||
def test_buffer_exception():
|
||||
with pytest.raises(BufferError, match="Error getting buffer") as excinfo:
|
||||
memoryview(m.BrokenMatrix(1, 1))
|
||||
assert isinstance(excinfo.value.__cause__, RuntimeError)
|
||||
assert "for context" in str(excinfo.value.__cause__)
|
||||
|
Loading…
Reference in New Issue
Block a user