pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

[BUG]: 'terminate called after throwing an instance of' in `def_buffer` method

Open stellaraccident opened this issue 4 years ago • 11 comments

Required prerequisites

  • [X] Make sure you've read the documentation. Your issue may be addressed there.
  • [X] Search the issue tracker and Discussions to verify that this hasn't already been reported. +1 or comment there if it has.
  • [X] Consider asking first in the Gitter chat room or in a Discussion.

Problem description

It seems that if I throw an exception from a def_buffer method, this is not translated properly to a Python exception and instead terminates the program with something like:

terminate called after throwing an instance of 'std::invalid_argument'
  what():  unimplemented array format.
Aborted

Reproducible example code

I believe the following minimal repro causes this but I have not yet built it separately/verified outside of my project.

struct Foobar;
py::class_<Foobar>(m, "Foobar")
  .def(py::init<>())
  .def_buffer([](Foobar &) {
    throw std::invalid_argument("boom");
  });
import numpy as np
import my_module
np.array(my_module.Foobar())

stellaraccident avatar Oct 06 '21 23:10 stellaraccident

We probably should add bindings to BufferError as well as that might make more sense to raise an error here.

Skylion007 avatar Oct 09 '21 17:10 Skylion007

terminate called after throwing an instance of 'std::invalid_argument'

Did someone already analyze why this happens? The usual suspects:

  • Exception thrown from a destructor?
  • Exception thrown while a previous exception is still unwinding?

(https://en.cppreference.com/w/cpp/error/terminate has a lot more details.)

Maybe if we understand the code path, we can think of a way to avoid the std::terminate?

rwgk avatar Oct 10 '21 18:10 rwgk

The bf_getbuffer handler installed by pybind11 just calls the user-defined function without catching and translating any exceptions.

https://github.com/pybind/pybind11/blob/f791dc8648e1f6ec33f402d679b6b116a76d4e1b/include/pybind11/detail/class.h#L551

I have seen quite a few cases in pybind11 where c++ exceptions are allowed to propagate out of Python C API callbacks, though I think most of those cases are internal errors that aren't too likely to occur.

jbms avatar Oct 20 '21 06:10 jbms

@jbms I suspect that this was the cause. Also, just to nit pick the error thrown really should be a BufferError since we have exception bindings for it. ;)

Skylion007 avatar Oct 20 '21 16:10 Skylion007

The exception is thrown here outside the scope of any exception_translators and within a new: https://github.com/pybind/pybind11/blob/e7c9753f1d35061d137ac3ce561e94a7407e5583/include/pybind11/pybind11.h#L1460

Skylion007 avatar Oct 31 '21 16:10 Skylion007

This is apparently a duplicate of https://github.com/pybind/pybind11/issues/2764#issue-776844461

Skylion007 avatar Nov 01 '21 18:11 Skylion007

Do you think

try {
  return new buffer_info(((capture *) ptr)->func(caster));
 } catch (...) {
  // translate exception
  return nullptr;
}

will solve this?

rwgk avatar Nov 01 '21 18:11 rwgk

It would be nice to factor out the exception translation logic from cpp_function so that it can be used in other places within pybind11, such as here, and also by user code directly when needed.

jbms avatar Nov 01 '21 18:11 jbms

It would be nice to factor out the exception translation logic from cpp_function

I think that's a great idea. @Skylion007, @henryiii, WDYT?

rwgk avatar Nov 01 '21 18:11 rwgk

That definitely does sound useful.

Skylion007 avatar Nov 01 '21 19:11 Skylion007