Repository navigation
Keep the ShadowNode vtable compatible after the asLayoutable change - #59652
hryhoriiK97 wants to merge 1 commit into
Conversation
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>
| static_cast<const LayoutableShadowNode&>(sourceShadowNode) | ||
| .layoutMetrics_) {} | ||
| .layoutMetrics_) { | ||
| traits_.set(ShadowNodeTraits::Trait::LayoutableKind); |
There was a problem hiding this comment.
This should go in BaseTraits
|
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 |
|
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. |
|
@javache makes sense, thanks. Without that case there's no real gain over asLayoutable(), so I'll close this one 👍 |
Summary
#59290 added a virtual
asLayoutable()toShadowNode, right aftercompleteClone. 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 itsappendChild: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 theShadowNodevtable is the same as before Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics #59290.LayoutableKind, is set by bothLayoutableShadowNodeconstructors. They live in the core library, so subclasses built against older headers get the trait too.ShadowViewchecks the trait: with it, astatic_cast; without it, empty layout metrics, asasLayoutable()returnednullptrfor such nodes.There is no behaviour or performance change compared with main: every node gets the same layout metrics as with
asLayoutable(), still without adynamic_cast. Only the vtable change is undone.Changelog:
[GENERAL] [FIXED] - Keep the
ShadowNodevtable layout compatible with native code built against older headersTest Plan:
react_codegen_AppSpecs, incl.RNTReportFullyDrawnView) compiled against the pre-Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics #59290 headers, the rest against main.ConcreteViewShadowNodesubclass, dumped with-fdump-vtable-layouts: identical to the one before Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics #59290.yarn fantom, full suite, with the tester rebuilt from this change: 3316 tests pass. The 60 failures are all snapshot printing in my checkout's Jest setup (React elements printed as plain objects instead of JSX), including Fantom's ownexpect-itest.js; none is about layout.🤖 Generated with Claude Code