pybind / pybind/pybind11

[BUG]: Exception thrown from C++ library function could not be correctly converted to the registered class with clang-19 compiler

Open
#5,773 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

Required prerequisites
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!

test_exception.zip

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.