Commit e5a43bd
Pass a top-level JS null TurboModule arg to ObjC as nil, not NSNull
Summary:
Changelog: [iOS][Fixed] - Pass a top-level JS `null` TurboModule argument to Objective-C as `nil` instead of `NSNull` when `enableModuleArgumentNSNullConversionIOS` is enabled
When `enableModuleArgumentNSNullConversionIOS` is on, `convertJSIValueToObjCObject`
maps a JS `null` to `(id)kCFNull`. That is the intended behaviour for nulls *nested*
inside arrays and dictionaries, but a `null` in **argument position** must still reach
Objective-C as `nil` — `NSNull` is truthy and does not respond to the selectors the
receiver expects, so leaking it crashes the callee.
The guard that enforced this lived three branches deep in
`ObjCTurboModule::setInvocationArg`, reachable only when all of the following held:
- `objCArgType == encode(id)`, and
- `getArgumentTypeName(...)` returned non-nil, and
- `RCTConvert` responded to a selector named after that type.
`getArgumentTypeName` resolves the argument type by scanning for `__rct_export__`-prefixed
selectors, which the compiler only emits for methods declared with `RCT_EXPORT_METHOD`.
Any TurboModule method without that macro — or with an `id`-typed argument, since
`[RCTConvert respondsToSelector:selector(id:)]` is `NO` — silently skipped the guard and
received `NSNull`.
This diff hoists the check to immediately after the conversion, so it applies to every
argument regardless of the method's `__rct_export__` metadata, its ObjC type encoding, or
whether an `RCTConvert` converter exists. Returning without calling `setArgument:` leaves
the `NSInvocation` slot zeroed, i.e. `nil` — identical to what the old guard did.
Behaviour is unchanged when the flag is off: the check short-circuits on the flag. Nested
`NSNull` inside arrays and dictionaries is untouched, as asserted by the existing
`testInvokeTurboModuleWithNull` case.
The pre-existing check inside the `RCTConvert` branch is left in place. It is now
effectively unreachable — `objCArg == kCFNull` is the only way `convertedObjCArg` can be
`kCFNull`, because every `RCTConvert` converter either returns a non-`kCFNull` input
unchanged or builds a new object — but it costs nothing and keeps the diff narrow.
Differential Revision: D1179605771 parent c6b137c commit e5a43bd
2 files changed
Lines changed: 53 additions & 0 deletions
File tree
- packages/react-native/ReactCommon/react/nativemodule/core
- iostests
- platform/ios/ReactCommon
Lines changed: 43 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
| |||
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
33 | 43 | | |
34 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
35 | 53 | | |
36 | 54 | | |
37 | 55 | | |
| |||
122 | 140 | | |
123 | 141 | | |
124 | 142 | | |
| 143 | + | |
| 144 | + | |
125 | 145 | | |
126 | 146 | | |
127 | 147 | | |
| |||
159 | 179 | | |
160 | 180 | | |
161 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
162 | 205 | | |
163 | 206 | | |
164 | 207 | | |
| |||
Lines changed: 10 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
727 | 727 | | |
728 | 728 | | |
729 | 729 | | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
730 | 740 | | |
731 | 741 | | |
732 | 742 | | |
| |||
0 commit comments