Skip to content

Keep the ShadowNode vtable compatible after the asLayoutable change - #59652

Closed
hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:fabric-layoutable-trait-abi
Closed

hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:fabric-layoutable-trait-abi

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Contributor

Summary

#59290 added a virtual asLayoutable() to ShadowNode, right after completeClone. Every virtual method declared after it (appendChild, replaceChild, layoutTree, measureContent, layout, ...) moved one vtable slot down.

Native code built against the old headers, for example a Fabric component from a prebuilt library, still has the old vtable layout, so a call from the core can land on a different method. In my repro, the core asks such a component for asLayoutable() and the call lands in its appendChild:

SIGSEGV
ShadowNode::appendChild
YogaLayoutableShadowNode::appendChild
ShadowView::ShadowView
FabricMountingManager::maybePreallocateShadowNode
UIManager::createNode

This may also explain a report we got from an app on a nightly: the JS bundle loads, nothing crashes and nothing is logged, but the first screen never appears. We have not confirmed that yet.

This PR keeps the speed-up from #59290 without the vtable change:

  • asLayoutable() is removed, so the ShadowNode vtable is the same as before Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics #59290.
  • A new trait, LayoutableKind, is set by both LayoutableShadowNode constructors. They live in the core library, so subclasses built against older headers get the trait too.
  • ShadowView checks the trait: with it, a static_cast; without it, empty layout metrics, as asLayoutable() returned nullptr for such nodes.

There is no behaviour or performance change compared with main: every node gets the same layout metrics as with asLayoutable(), still without a dynamic_cast. Only the vtable change is undone.

Changelog:

[GENERAL] [FIXED] - Keep the ShadowNode vtable layout compatible with native code built against older headers

Test Plan:

🤖 Generated with Claude Code

react#59290 added a virtual asLayoutable() to ShadowNode after completeClone,
which moved every later virtual method by one vtable slot. Native code
built against older headers (for example a prebuilt Fabric component)
then calls the wrong methods: appendChild lands on asLayoutable, and so on.

Replace the virtual method with a LayoutableKind trait set by the
LayoutableShadowNode constructors. ShadowView checks the trait and uses a
static_cast, so it still avoids the dynamic_cast, as asLayoutable() did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Oct 29, 2026
static_cast<const LayoutableShadowNode&>(sourceShadowNode)
.layoutMetrics_) {}
.layoutMetrics_) {
traits_.set(ShadowNodeTraits::Trait::LayoutableKind);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should go in BaseTraits

@javache

javache commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

React Native is not ABI stable - it's incorrect to assume you can existing binaries with newer versions of the framework.

@hryhoriiK97

Copy link
Copy Markdown
Contributor Author

React Native is not ABI stable - it's incorrect to assume you can existing binaries with newer versions of the framework.

@javache , thanks for the review! For context, I found this because code built against the headers from before #59290 crashes on main: the asLayoutable() call lands in appendChild and it ends in a SIGSEGV. That's also why I set the trait in the constructor and not in BaseTraits: BaseTraits is inline in the header, so code built against older headers would never set it. What do you think we should do here?

@hryhoriiK97
hryhoriiK97 requested a review from javache October 30, 2026 11:38
@javache

javache commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Code being built against previous headers is an unsupported use-case, and I wouldn't land this change just for the sake of that.

If you think modelling this as BaseTraits instead of asLayoutable, that's something we can merge, although I think it should go in the header too.

@hryhoriiK97

Copy link
Copy Markdown
Contributor Author

@javache makes sense, thanks. Without that case there's no real gain over asLayoutable(), so I'll close this one 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants