Skip to content

fix: Clean sibling storage after old-style init - #6180

Merged
rwgk merged 1 commit into
pybind:masterfrom
rwgk:fix-old-style-init-sibling-cleanup
Sep 17, 2026
Merged

rwgk merged 1 commit into
pybind:masterfrom
rwgk:fix-old-style-init-sibling-cleanup

Conversation

@rwgk

@rwgk rwgk commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #6157 and its post-merge review.

When a constructor chain contains a deprecated old-style placement-new callback, old_style_init_active intentionally permits lazy allocation across the whole Python instance. In a Python multiple-inheritance layout, reentrant conversion can therefore allocate an unconstructed sibling-base slot. The existing scope cleanup tracked only the constructor self slot, leaving sibling storage published after dispatch; a later ordinary load could treat it as a live C++ object and invoke undefined behavior.

This change snapshots all empty value slots when the compatibility window opens and cleans every such slot that acquired storage without constructing a holder. Pre-existing slots and successfully constructed slots are retained. A regression covers a successful old-style base constructor that exposes a new-style sibling during argument conversion, then verifies that the sibling is rejected after dispatch and remains normally constructible.

The scope is intentionally narrow:

  • Pure new-style constructor chains are unchanged; the scope remains a no-op for them.
  • The cleanup affects only old-style and mixed old-/new-style chains.
  • Reentrant, nested, cross-base, and concurrent access during the deprecated compatibility window remains unsafe and documented as such.

Although this is a pre-existing edge case confined to deprecated machinery, the fix is small and localized. More importantly, it completes the boundary established by #6157: the temporary old-style compatibility exception may remain unsafe while active, but it should not leave an unconstructed pointer that poisons later ordinary loads. The upgrade-guide wording is adjusted to describe the actual overload-chain boundary.

Suggested changelog entry:

  • Clean up unconstructed sibling-base storage allocated during deprecated old-style or mixed constructor dispatch, preventing later loads from treating it as a live C++ object.

📚 Documentation preview 📚: https://pybind11--6180.org.readthedocs.build/

@rwgk
rwgk marked this pull request as ready for review September 16, 2026 18:16
@rwgk

rwgk commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

@espressolee for visibility

@espressolee espressolee left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Measured at 95d54b0e against 63121f7b, CPython 3.14.0rc1t, macOS arm64, two reproducers built from both include trees (type_caster_base.h the only differing file).

The reported hole is closed on both paths — the failing old-style __init__ and the successful one:

                                        63121f7b        95d54b0e
class C(A, B), init raises            SIGSEGV 5/5     ValueError 5/5
class PPCC(PC, CppDrvd), init ok      SIGSEGV 3/3     ValueError 3/3

I tried to make the wider cleanup free something live. The closest case is the related-base shape where load_impl routes CppBase& to the CppDrvd slot: after a successful old-style __init__, 95d54b0e leaves the instance uninitialized, so the call is a silent no-op. Worth knowing, but not a regression — at 63121f7b the same sequence leaves a CppBase in a CppDrvd slot, and calling a CppDrvd method is SIGBUS; here it raises and teardown is clean. Both shas already abort that shape under assert (register_instance_impl, class.h:348), so it is out of contract either way.

Reviewed with Claude Code assistance.

@rwgk

rwgk commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @espressolee!

@rwgk
rwgk merged commit 296d5d1 into pybind:master Sep 17, 2026
90 of 100 checks passed
@github-actions github-actions Bot added the needs changelog Possibly needs a changelog entry label Sep 17, 2026
@rwgk
rwgk deleted the fix-old-style-init-sibling-cleanup branch September 17, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs changelog Possibly needs a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants