-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Added test case for visibility of common symbols across shared libraries #5700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Should this be passing? Maybe you need to add it to a few of the ci jobs? |
tests/test_visibility/CMakeLists.txt
Outdated
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" | ||
OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" | ||
OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy") | ||
message(STATUS "Skipping embed test on PyPy or GraalPy") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message(STATUS "Skipping embed test on PyPy or GraalPy") | |
message(STATUS "Skipping visibility test on PyPy or GraalPy") |
Hi, yes it should not pass at the moment. I assumed that everything that "check" depended on would run automatically |
Perfect, now can you add the fix? |
It only fails under macOS, interesting. I'm still pretty worried about exporting on all platforms, but just exporting under macOS seems fine. |
If it needs exporting, I'd export it on all platforms. The settings might be different, but if a symbol is needed, I think it is best to ensure it is always available. |
I'd be very uncomfortable doing that on all platforms, in particular on Windows, without fully understanding what exactly happens if the modules are not ABI compatible. — Developing that full understanding is probably hard (could take days of effort). Linux, macOS, Windows are all different in both fundamental and subtle ways. To the best of my understanding: Exporting symbols is akin to piercing a hole into the ABI isolation layers. |
Could you try setting (Also, while you are at it, feel free to replace "ON" with "hidden" in tools/pybind11Config.cmake.in:97 - you don't have to, but that was a mistake in the docs!) |
@rwgk I was also surprised that it didn't fail on with other clang builds. After some digging in libc++ it seems there are 3 different implementations for type_info equal. The same file explains the differences Apple arm64 uses the third option. And of note here is:
hence guarded_delete will get a unique and different id in each module. Which aligns with what we have seen here. |
@rwgk I think you might be overly careful here. We only need to export the guarded_delete, and as long as that class stays ABI compatible I don't think it in it self would be problem. What maybe? could be a problem is when mixing a version with an exported guarded_delete and one without having it exported. @henryiii |
Maybe we should pull out the macro into a new |
Up to you guys, but I've generally been faring best with: If it's not broken (Linux, Windows), don't "fix" it. — You're definitely playing with ABI compatibility fire. Will it burn someone or not, idk. |
AFACT, this is only "working" other places due to an implementation detail. If an implementation of |
This is what we have:
Note:
We only have this for
It seems to be exactly what's needed to fix the failing tests. To not add a new twist, I recommend sticking to that approach. Please give me a moment for trying out a prototype fix here. |
@@ -58,6 +58,18 @@ High-level aspects: | |||
#include <typeinfo> | |||
#include <utility> | |||
|
|||
// IMPORTANT: This code block must stay BELOW the #include <stdexcept> above. | |||
#if !defined(PYBIND11_EXPORT_GUARDED_DELETE) | |||
# if defined(_LIBCPP_EXCEPTION) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this have to do with exceptions? Copy-paste error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's intentional at the moment, for the purpose of experimenting, mimicking this existing logic:
pybind11/include/pybind11/detail/common.h
Lines 235 to 244 in 57e27c1
// For libc++, the exceptions should be exported, | |
// otherwise, the exception translation would be incorrect. | |
// IMPORTANT: This code block must stay BELOW the #include <exception> above (see PR #5390). | |
#if !defined(PYBIND11_EXPORT_EXCEPTION) | |
# if defined(_LIBCPP_EXCEPTION) | |
# define PYBIND11_EXPORT_EXCEPTION PYBIND11_EXPORT | |
# else | |
# define PYBIND11_EXPORT_EXCEPTION | |
# endif | |
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests pass.
I believe this is the right direction: conservative / in keeping with existing code.
Now, ideally we'd have a centralized decision that applies to both PYBIND11_EXPORT_EXCEPTION
and PYBIND11_EXPORT_GUARDED_DELETE
. I don't know what I'd call the central macro though, in large part because I only have very sketchy ideas what's going on under the hood. Something along the lines of: libc++ doesn't handle cross-dynamic-library type_info
very well?
It's also unfortunate that we need to #include <exception>
or #include <stdexcept>
in order to make that decision. Which we shouldn't do before #include <pybind11/conduit/wrap_include_python_h.h>
... UGH.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it's still an implementation detail - libc++ has three implementations; one of them requires this exported, so I think the correct choice is to export it, rather than relying on this implementation detail. What happens if libstdc++ decides to switch to a similar implementation? What happens if you use libc++ on Windows?
Test pass because this is exactly the same as before; this is always true when we compile against libc++; we do not support disabling exceptions in pybind11. We don't have a Windows libc++ compile. All our macOS builds use libc++, (I'm pretty sure) all our Linux builds use libstdc++.
We can't include anything from the stdlib before Python.h, that is completely unsupported by CPython. But this isn't changing the includes, though?
And __libcpp_version
would be the standard way to detect libc++, I think.
|
See here: https://chatgpt.com/share/683636ac-48dc-8008-9cf4-0860d29a4f44 Be careful though, I've seen ChatGPT go wrong even if referenced source code: in that case it misunderstood the source code. But of course, possibly it is correct here? |
That's what it seems like. I'm surprised pytest would enable the GIL to collect tests, but it's reporting as enabled during collection (not during the actual test runs). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new test suite to verify that shared libraries export common symbols correctly, ensuring shared_ptr
deleters match across modules.
- Adds a standalone visibility test under
tests/test_visibility
with CMake integration and CI hooks - Exports the
guarded_delete
struct instruct_smart_holder.h
to give it default visibility - Updates CI workflows to invoke the new
test_visibility
target on all build configurations
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
tests/test_visibility/test_visibility.cpp | New Catch-based tests for simple vs. alias-backed Python bindings |
tests/test_visibility/lib.h | Header defining lib::Base and lib::Foo with export macros |
tests/test_visibility/lib.cpp | Implementation of Base and Foo |
tests/test_visibility/bindings.cpp | Pybind11 bindings (typo in class registration) |
tests/test_visibility/catch.cpp | Custom Catch runner with embedded Python interpreter |
tests/test_visibility/CMakeLists.txt | CMake rules to build and link the visibility test suite |
tests/CMakeLists.txt | Added test_visibility subdirectory to main test tree |
include/pybind11/detail/struct_smart_holder.h | Added PYBIND11_EXPORT_GUARDED_DELETE to guarded_delete for visibility |
.github/workflows/ci.yml | Added test_visibility invocations across all CI job steps |
Comments suppressed due to low confidence (3)
tests/test_visibility/test_visibility.cpp:27
- [nitpick] The variable name
holder
is ambiguous—consider renaming it tofoo_holder
orfoo_instance
for clarity.
auto holder = bindings.attr("get_foo")(1, 2);
tests/test_visibility/test_visibility.cpp:40
- [nitpick] The variable name
holder2
is ambiguous—consider renaming it tobar_holder
orbar_instance
for clarity.
auto holder2 = main.attr("get_bar")(1, 2);
tests/test_visibility/bindings.cpp:12
- Typo in class registration:
pybind11::classh
should bepybind11::class_
to compile correctly.
pybind11::classh<lib::Base, BaseTrampoline>(m, "Base")
@rwgk about
I think that from a conceptual point of view any class that is part of the public interface should generally be exported. Considering what is standard practice for exporting symbols it seems a bit excessive to use PYBIND11_EXPORT_GUARDED_DELETE and PYBIND11_EXPORT_EXCEPTION when PYBIND11_EXPORT would be fine and inline with what is generally used. When reading PYBIND11_EXPORT_GUARDED_DELETE and PYBIND11_EXPORT_EXCEPTION I assume that there are something more complicated going on then just regular PYBIND11_EXPORT which I find misleading and confusing. |
// IMPORTANT: This code block must stay BELOW the #include <stdexcept> above. | ||
#if !defined(PYBIND11_EXPORT_GUARDED_DELETE) | ||
# if defined(_LIBCPP_EXCEPTION) | ||
# if defined(WIN32) || defined(_WIN32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if one uses libc++ on windows?
Here is a long chat specific to this PR: TL;DR: I believe the "right thing" here is to move these into
|
I mean if this was not a header only library (why is it header only?), I would do all that in one TU and it would be fine. Also I don't think that guarded_delete is really abi safe without the export either. Unless it has the same exact definition in all TUs it will break the one definition rule anyway. To deal with that you would need to version the symbol, like putting it in a v1 namespace and update that on every change to the class. Edit: My thinking about the guarded_delete here was with respect to the C++ abstract machine spec. But that is not that relevant here, since the semantics here are all vendor defined and not specified in the c++ standard. |
It is header only due to simplicity in building. Today I'd much rather have an optional pre-compile step (similar to CLI11 and Catch2 these days); nanobind has a required one and people haven't had issues with it AFAIK. Someone almost contributed an option like that, but a lot other development was going on at the same time and they left their job before it was ready to put in. |
@petersteneteg — I believe this statement should be revised or removed, as it gives a misleading impression. pybind11 deliberately uses multiple layers of ABI isolation — including namespace visibility attributes, Carefully controlled sharing is only enabled via the |
@rwgk: You are most likely right, and I have very limited understand of how the internals handle this. And I am fine with either solution. |
@rwgk: Thinking more about the fact the a lot of this behavior is implementation defined, I think it makes sense to be conservative in where to apply the fix, as you suggested. |
Once it's ready, we can try to get it in. I'll probably go ahead and make an RC without it, though. |
I don't think I'll get to it before the weekend. This could take me a couple of weekends to complete. It's not a regression, so making the 3.0.0 release sounds fine to me. The main trouble is that we'll have to bump the internals version again. The conduit feature will help, but I think a fraction of use cases will still be affected by the internals version bump (what @rhaschke found). |
So is the plan to do a minimal fix like this for 3.0, then move things to internals and bump the internals for 3.1? I was rather hoping after a forced internals bump in 3.0 we'd not have to bump it for a while, but we can do that. |
That would seem OK to me.
Me too. Unfortunately I cannot jump on this immediately. The options I see:
On the back of my mind, details for this PR:
|
Description
See issue #5696
Suggested changelog entry:
📚 Documentation preview 📚: https://pybind11--5700.org.readthedocs.build/