Skip to content

fix: Fix: EXC_BAD_ACCESS in RCTInlineMessageNative.updateProps on iOS Fabric#581

Merged
mahmoud-elmorabea merged 3 commits into
mainfrom
fix-inapp-view-props
Apr 23, 2026
Merged

fix: Fix: EXC_BAD_ACCESS in RCTInlineMessageNative.updateProps on iOS Fabric#581
mahmoud-elmorabea merged 3 commits into
mainfrom
fix-inapp-view-props

Conversation

@mahmoud-elmorabea
Copy link
Copy Markdown
Contributor

@mahmoud-elmorabea mahmoud-elmorabea commented Apr 21, 2026

Problem

Users on iOS with React Native New Architecture (Fabric) hit EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) inside -[RCTInlineMessageNative updateProps:oldProps:] when InlineInAppMessageView is mounted. 13 users reported
it in production, see this report. Stack:

EXC_BAD_ACCESS: KERN_INVALID_ADDRESS at 0x41c0000000021

Stacktrace:
  -[RCTMountingManager initiateTransaction:]
  -[RCTMountingManager performTransaction:]
  facebook::react::TelemetryController::pullTransaction
  -[RCTInlineMessageNative updateProps:oldProps:]
  _platform_memcmp

Hypotheses

H1 — oldProps parameter can be nullptr (the reporter's suggestion).
On Insert mutations, Fabric calls updateProps:props oldProps:nullptr ([RCTMountingManager.mm](https://gitlab.123xe.vn/opensource/react-native/-/blob/7f0a4f72b445f45a317ccff9a90cfe53ffa4493e/React/Fabric/Mounting/RCTMountingManager.mm#L69)). Any subclass dereferencing the oldProps parameter without a null guard would crash on first mount.

That's not likely the problem because our updateProps: implementation did not touch the oldProps parameter; it casted the _props ivar instead. A null guard on _props would always pass, as _props is never null at this lifecycle stage.

H2 — _props has the wrong runtime type on first mount (our diagnosis, most likely).
In the original init override, we never installed a concrete default for _props. During [super init], the chain reaches RCTViewComponentView.initWithFrame:, which assigns: _props = ViewShadowNode::defaultSharedProps();

This is a std::shared_ptr, not InlineMessageNativeProps. On the first updateProps: call, static_pointer_cast(_props) reinterprets that pointer. oldViewProps.elementId then attempts to read a std::string 24 bytes past the end of the ViewProps allocation. If those bytes happen to form a malformed std::string with a garbage data pointer, the subsequent std::string::operator!= forwards to _platform_memcmp on invalid memory.

Reproduction attempt

Was unable to do reproduce 🔴
Built a local test screen that mounts InlineInAppMessageView under a changing React key to force repeated fresh allocations. The crash did not reproduce.

Solution

1. Install a concrete _props default

Replaced the init override with initWithFrame: (the UIView designated initializer) and assigned the concrete default before creating the Swift bridge:

From the first updateProps: onward, _props has the correct runtime type, the static_pointer_cast is well-defined, and RCTViewComponentView's debug RCTAssert at updateProps: is satisfied.

2. Diff against the oldProps parameter with a null guard

Rewrote updateProps: to cast the oldProps parameter (the value Fabric passes us) rather than the _props ivar. When Fabric calls with oldProps == nullptr (first mount), the override applies all props unconditionally; on subsequent calls it diffs oldViewProps.elementId against the new value exactly as before.

Why this fix?

_props = <Subclass>ShadowNode::defaultSharedProps() inside initWithFrame: is the canonical Fabric component-view initializer. Every first-party RN Fabric component view follows it. In the repo bundled with this
example app (React/Fabric/Mounting/ComponentViews/)

The same base class enforces the invariant in RCTViewComponentView.mm:217-223, asserting in debug builds that subclass _props is not the plain ViewProps — i.e. exactly the rule the previous init override was violating.

Does Android have the same problem?

Not affected, Android's InlineInAppMessageViewManager is a SimpleViewManager with per-prop @ReactProp setters (setElementId(view, value)). There is no C++ Props struct, no _props ivar, and no updateProps(oldProps, newProps) hook — so the class of bug described in H2 is structurally impossible on the Android side.


Note

Medium Risk
Touches iOS Fabric component-view initialization and updateProps diffing logic; while aimed at preventing a production crash, it could affect prop updates/lifecycle behavior for inline in-app messages. Lockfile-only dependency bumps are low risk but may alter local build/tooling behavior.

Overview
Fixes a crash in the iOS Fabric inline message component by switching RCTInlineMessageNative to initialize via initWithFrame: and setting _props to InlineMessageNativeShadowNode::defaultSharedProps() before the first updateProps.

Updates updateProps to not cast/diff against the _props ivar; it now null-guards oldProps (which can be nullptr on first mount) and applies elementId on first mount, diffing only on subsequent updates. Also refreshes the example app’s Ruby/Node lockfiles with minor dependency updates (e.g., fastlane, react-native/metro, lint/tooling packages).

Reviewed by Cursor Bugbot for commit 632706c. Bugbot is set up for automated code reviews on this repo. Configure here.

@mahmoud-elmorabea mahmoud-elmorabea self-assigned this Apr 21, 2026
@mahmoud-elmorabea mahmoud-elmorabea requested a review from a team as a code owner April 21, 2026 12:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.


  • iOS FCM: 581.3.0 (29616091)
  • Android APN: 581.3.0 (29616091)
  • iOS APN: 581.3.0 (29616091)

Copy link
Copy Markdown
Contributor

@hollyschilling-cio hollyschilling-cio left a comment

Choose a reason for hiding this comment

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

My C++ is pretty rusty, but the Objective C portion looks good and this should fix the bug referenced.

NSAssert(self.bridge != nil, @"Bridge is nil when %@", context);
}

- (instancetype)init {
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.

Non-blocking: would it be worth keeping -init and funneling it into -initWithFrame:CGRectZero (or extracting a shared setup helper)? I think the current change is correct, but making the _props and bridge setup explicit on every init path might remove reliance on UIKit’s init -> initWithFrame: forwarding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Better safe than sorry 😂 .. added!

@mahmoud-elmorabea mahmoud-elmorabea merged commit e93e8df into main Apr 23, 2026
21 of 23 checks passed
@mahmoud-elmorabea mahmoud-elmorabea deleted the fix-inapp-view-props branch April 23, 2026 23:15
github-actions Bot pushed a commit that referenced this pull request Apr 23, 2026
## [6.4.1](6.4.0...6.4.1) (2026-04-23)

### Bug Fixes

* Fix: EXC_BAD_ACCESS in RCTInlineMessageNative.updateProps on iOS Fabric ([#581](#581)) ([e93e8df](e93e8df))
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.

4 participants