[BUG]: Exception thrown from C++ library function could not be correctly converted to the registered class with clang-19 compiler
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 18k
- Forks
- 2.3k
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 10
Description
Required prerequisites
- Make sure you've read the documentation. Your issue may be addressed there.
- Search the issue tracker and Discussions to verify that this hasn't already been reported. +1 or comment there if it has.
- Consider asking first in the Gitter chat room or in a Discussion.
What version (or hash if on master) of pybind11 are you using?
3.0.0
Problem description
Recently I have encountered some issue while working with the pybind11 exception binding. The exception defined in C++ and registered by pybind11 could not be caught as the defined exception class, but only as its parent exception class (e.g., RuntimeError).
This issue raises while I am using LLVM/clang 19.1.5 compiler (along with libc++), but GCC 14.1.0 compiler works fine. The testing platform is CentOS 7. For a minimal example, please see the attachment.
Minimal example
In the minimal example (see attachment), an exception class is defined in C++, inheriting from standard std::runtime_error:
class PyTestException: public std::runtime_error
{
public:
using runtime_error::runtime_error;
};
A function simply throwing this exception is also defined:
void throw_pyexcept()
{
throw PyTestException("Exception from C++");
}
The exception class and the function throw_pyexcept make up a shared library called libpyexcept.so.
Then, in pyexcept_test.cpp, the pybind11 binding to the exception is registered, the function defined above is also bound. Meanwhile, another binding function that simply throw the exception using lambda expression is also defined.
PYBIND11_MODULE(pyexcept_test, m) {
pybind11::register_exception<PyTestException>(m, "PyTestException", PyExc_RuntimeError);
m.def("throw_pyexcept", &throw_pyexcept);
m.def("throw_pyexcept_from_lambda", [](){throw PyTestException("Exception from lambda");});
}
After installing this package to local Python environment, the following test script is executed:
from pyexcept_test.pyexcept_test import PyTestException, throw_pyexcept, throw_pyexcept_from_lambda
print('Testing throw_pyexcept')
try:
throw_pyexcept()
except PyTestException:
print('PyTestException')
except RuntimeError:
print('RuntimeError')
except Exception:
print('Exception')
print('Testing throw_pyexcept_from_lambda')
try:
throw_pyexcept_from_lambda()
except PyTestException:
print('PyTestException')
except RuntimeError:
print('RuntimeError')
except Exception:
print('Exception')
print('Finished')
The expected output of this script should be like:
Testing throw_pyexcept
PyTestException
Testing throw_pyexcept_from_lambda
PyTestException
Finished
Because the thrown exception is exactly PyTestException. It is exactly this case if GCC 14.1.0 compiler is used. However, when LLVM Clang 19.1.5 compiler is used, the result is different:
Testing throw_pyexcept
RuntimeError
Testing throw_pyexcept_from_lambda
PyTestException
Finished
The PyTestException thrown by the throw_pyexcept C++ function defined in the shared library is caught as RuntimeError, not PyTestException, while the one thrown by lambda expression is correctly caught.
More information
I have also tested that if the throw_pyexcept function was defined in a static library (not shared), the exception could be correctly caught.
I wonder if there was any wrong made in my code, or with my testing environment.
Any suggestions are welcomed.
Thank you in advance for your time!
Reproducible example code
Please see the attachment for the reproducible minimal example code.
Is this a regression? Put the last known working version here if it is.
Not a regression
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the attached minimal example, especially pyexcept_test.cpp, libpyexcept.so, and the Python test script. Reproduce the difference between clang-19.1.5 with libc++ and GCC 14.1.0, comparing the shared-library and lambda cases around register_exception. Done means identifying the shared-library conversion problem and covering the corrected behavior with a regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100