Skip to content

Conversation

rwgk
Copy link
Collaborator

@rwgk rwgk commented Sep 14, 2025

Description

Closes #5786.

This PR fixes two independent bugs in the smart_holder machinery, both exposed by new tests involving virtual inheritance.

1. Fix in pybind11/detail/type_caster_base.h

The return-value caster for std::shared_ptr<T> was incorrectly using src.get() (the most-derived pointer) instead of st.first (the adjusted subobject pointer appropriate for the registered tinfo).
On MSVC with virtual inheritance, this mismatch led to passing an Animal* (see below) to register_instance when binding Tiger, which caused the virtual-base offset walker to dereference a bogus vbptr.
This is the bug reported in #5786. Using st.first ensures the correct subobject is registered.

2. Fix in pybind11/detail/struct_smart_holder.h

smart_holder::from_unique_ptr was storing the subobject pointer as the owned resource in its control block. Under MSVC, destroying through that misaligned pointer caused NX faults in the virtual destructor path.
The fix mirrors the shared_ptr path: always own the real T* object start with the deleter, and, if needed, create an aliasing shared_ptr<void> at the subobject pointer for registration/identity.

Tests

  • The original Animal–Cat–Tiger reproducer from [BUG]: Using smart_holder in virtual inheritance relationship error #5786 is kept as a targeted regression test. It fails reliably only on MSVC, which is why it is important to keep it.
  • A new Diamond virtual-inheritance test (test_class_sh_mi_thunks) was added. This was designed to also fail on Linux/Clang/GCC without the fix (not just on MSVC). Indeed, the diamond case fails across all ci.yml jobs and several tests-cibw.yml jobs pre-fix, and passes after fix (see comment).
  • The diamond test was trivially extended to exercise smart_holder_from_unique_ptr, which immediately exposed the second bug. That test produced 20 MSVC failures pre-fix (comment), and passes after the fix.
  • A pseudo-test test_virtual_base_at_offset_0 is included to make it explicit when a compiler/layout places the virtual base at offset 0. This isn’t a true skip, but a way to document in the pytest summary when the test can’t exercise the MI/VI code path. In practice, this skip has never been triggered in any GitHub Actions job.
  • Debug-only asserts remain in pybind11/detail/class.h. They were invaluable during debugging and experimentation, have no impact on non-debug builds, and may be useful again if MI/VI edge cases resurface.

Context

The changes here are in the same spirit as PR #4380, which fixed earlier oversights in smart_holder MI handling. As noted there, MI use cases are uncommon in downstream projects (and even discouraged in some organizations), so coverage has historically been thin. The new tests give us much better confidence that both shared_ptr and unique_ptr adoption paths are now robust under virtual and multiple inheritance.


Full ChatGPT conversation guiding the work on the fixes (very long and involved): https://chatgpt.com/share/68c7950a-61b8-8008-8678-53b96643ce7e

A lot of the credit for the fixes goes to ChatGPT 5 Pro

Suggested changelog entry:

Fixed two smart_holder bugs in shared_ptr and unique_ptr adoption with multiple/virtual inheritance:

```
/__w/pybind11/pybind11/tests/test_class_sh_mi_thunks.cpp:44:13: error: prefer using 'override' or (rarely) 'final' instead of 'virtual' [modernize-use-override,-warnings-as-errors]
   44 |     virtual ~Left() = default;
      |     ~~~~~~~ ^
      |                     override
/__w/pybind11/pybind11/tests/test_class_sh_mi_thunks.cpp:48:13: error: prefer using 'override' or (rarely) 'final' instead of 'virtual' [modernize-use-override,-warnings-as-errors]
   48 |     virtual ~Right() = default;
      |     ~~~~~~~ ^
      |                      override
```
@rwgk
Copy link
Collaborator Author

rwgk commented Sep 14, 2025

CI results @ commit 5f77da3:

66 failing, 2 skipped, 17 successful checks

CI: https://github.com/pybind/pybind11/actions/runs/17707681525?pr=5836

gh_pr_checks_2025-09-14+002918.txt

@rwgk
Copy link
Collaborator Author

rwgk commented Sep 14, 2025

CI results @ commit 3620ceb:

20 failing, 2 skipped, 63 successful checks

CI: https://github.com/pybind/pybind11/actions/runs/17713020523?pr=5836

gh_pr_checks_2025-09-14+091113.txt

rwgk added 10 commits September 14, 2025 09:16
ChatGPT:

* shared_ptr’s ctor can throw (control-block alloc). Using get() keeps unique_ptr owning the memory if that happens, so no leak.

* Only after the shared_ptr is successfully constructed do you release(), transferring ownership exactly once.
…ampoline code (which often uses the term "alias", too)
```
/__w/pybind11/pybind11/tests/test_class_sh_mi_thunks.cpp:67:5: error: 'auto ptr' can be declared as 'auto *ptr' [readability-qualified-auto,-warnings-as-errors]
   67 |     auto ptr = new Diamond;
      |     ^~~~
      |     auto *
```
@rwgk rwgk changed the title [WIP] Add diamond virtual-inheritance test case Fix smart_holder multiple/virtual inheritance bugs in shared_ptr and unique_ptr to-Python conversions Sep 15, 2025
@rwgk
Copy link
Collaborator Author

rwgk commented Sep 15, 2025

@henryiii This PR is ready for review.

@MannixYang If you get a chance to try this out, please report back.

@MannixYang
Copy link

@rwgk Thank you very much indeed. Yes, I will give it a try. It should be within the next two days.

@MannixYang
Copy link

@rwgk Great! This is very useful. My project can now run. Thank you again.

@rwgk
Copy link
Collaborator Author

rwgk commented Sep 15, 2025

@rwgk Great! This is very useful. My project can now run. Thank you again.

Thanks a lot for confirming!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG]: Using smart_holder in virtual inheritance relationship error
2 participants