Skip to content

Commit acc0cca

Browse files
YevheniiKotyrlometa-codesync[bot]
authored andcommitted
Skip third-party components whose class is not linked instead of crashing at launch (#59067)
Summary: `RCTThirdPartyComponentsProvider.mm` builds its component map from a dictionary literal of `NSClassFromString` results. When a component's library is not linked into the target, such as an app extension or App Clip that leaves a library out (#56469), or a macOS app whose dependencies include an iOS-only component library (react-native-macos generates the same file), `NSClassFromString` returns nil and the literal throws at launch: ``` *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]' ``` The provider now maps component names to class names and registers the classes the runtime has, the way `RCTModuleProviders.mm` already handles a module provider that cannot be found. A component left out this way falls back to `<UnimplementedView>` if it is rendered, as an unknown component does. ## Changelog: [IOS] [FIXED] - Skip third-party components whose class is not linked instead of crashing at launch Pull Request resolved: #59067 Test Plan: - `generate-artifacts-executor-test.js`: the provider snapshots and the crawl case expect the name-to-class-name map. In Linux (WSL) the file's 52 tests and 24 snapshots pass; its suite-level `fs.rmdirSync(…, {recursive: true})` cleanup fails under Node 26, on `main` too. - The generated `RCTThirdPartyComponentsProvider.mm` for two component libraries, compiled into a binary that links only one of their classes (clang++, Foundation, macOS): - `main`: `NSInvalidArgumentException … attempt to insert nil object from objects[1]` - this change: `registered components: LinkedComponent` - `yarn flow`: no errors; eslint and prettier on the changed files: clean. Reviewed By: christophpurrer Differential Revision: D125236957 Pulled By: cipolleschi fbshipit-source-id: 55126e43352aad7275298978dceaa744a163f966
1 parent c89f412 commit acc0cca

4 files changed

Lines changed: 50 additions & 8 deletions

File tree

‎packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap‎

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,24 @@ exports[`execute test-app "ReactCodegen/RCTThirdPartyComponentsProvider.mm" shou
328328
static dispatch_once_t nativeComponentsToken;
329329
330330
dispatch_once(&nativeComponentsToken, ^{
331-
thirdPartyComponents = @{
332-
@\\"TestAppComponent\\": NSClassFromString(@\\"RCTTestAppComponent\\"), // test-app
333-
@\\"TestLibraryComponent\\": NSClassFromString(@\\"RCTTestLibraryComponent\\"), // test-library
331+
NSDictionary<NSString *, NSString *> * componentMapping = @{
332+
@\\"TestAppComponent\\": @\\"RCTTestAppComponent\\", // test-app
333+
@\\"TestLibraryComponent\\": @\\"RCTTestLibraryComponent\\", // test-library
334334
};
335+
336+
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count];
337+
338+
for (NSString *key in componentMapping) {
339+
// Nil for a library this target does not link (an app extension, macOS).
340+
Class klass = NSClassFromString(componentMapping[key]);
341+
if (!klass) {
342+
continue;
343+
}
344+
345+
[dict setObject:klass forKey:key];
346+
}
347+
348+
thirdPartyComponents = dict;
335349
});
336350
337351
return thirdPartyComponents;
@@ -822,9 +836,23 @@ exports[`execute test-app-legacy "ReactCodegen/RCTThirdPartyComponentsProvider.m
822836
static dispatch_once_t nativeComponentsToken;
823837
824838
dispatch_once(&nativeComponentsToken, ^{
825-
thirdPartyComponents = @{
826-
@\\"TestAppDeprecatedComponent\\": NSClassFromString(@\\"RCTTestAppDeprecatedComponentClass\\"), // test-app-legacy
839+
NSDictionary<NSString *, NSString *> * componentMapping = @{
840+
@\\"TestAppDeprecatedComponent\\": @\\"RCTTestAppDeprecatedComponentClass\\", // test-app-legacy
827841
};
842+
843+
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count];
844+
845+
for (NSString *key in componentMapping) {
846+
// Nil for a library this target does not link (an app extension, macOS).
847+
Class klass = NSClassFromString(componentMapping[key]);
848+
if (!klass) {
849+
continue;
850+
}
851+
852+
[dict setObject:klass forKey:key];
853+
}
854+
855+
thirdPartyComponents = dict;
828856
});
829857
830858
return thirdPartyComponents;

‎packages/react-native/scripts/codegen/__tests__/generate-artifacts-executor-test.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ describe('generateRCTThirdPartyComponents', () => {
297297
'utf8',
298298
);
299299
expect(generatedFile).toContain(
300-
'@"ExampleComponent": NSClassFromString(@"RCTExampleComponent"), // component-library',
300+
'@"ExampleComponent": @"RCTExampleComponent", // component-library',
301301
);
302302
} finally {
303303
fs.rmSync(libraryPath, {recursive: true, force: true});

‎packages/react-native/scripts/codegen/generate-artifacts-executor/generateRCTThirdPartyComponents.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ function generateRCTThirdPartyComponents(
139139
.flatMap(library => {
140140
const components = componentsInLibraries[library];
141141
return components.map(({componentName, className}) => {
142-
return `\t\t@"${componentName}": NSClassFromString(@"${className}"), // ${library}`;
142+
return `\t\t@"${componentName}": @"${className}", // ${library}`;
143143
});
144144
})
145145
.join('\n');

‎packages/react-native/scripts/codegen/templates/RCTThirdPartyComponentsProviderMM.template‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,23 @@
1919
static dispatch_once_t nativeComponentsToken;
2020

2121
dispatch_once(&nativeComponentsToken, ^{
22-
thirdPartyComponents = @{
22+
NSDictionary<NSString *, NSString *> * componentMapping = @{
2323
{thirdPartyComponentsMapping}
2424
};
25+
26+
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:componentMapping.count];
27+
28+
for (NSString *key in componentMapping) {
29+
// Nil for a library this target does not link (an app extension, macOS).
30+
Class klass = NSClassFromString(componentMapping[key]);
31+
if (!klass) {
32+
continue;
33+
}
34+
35+
[dict setObject:klass forKey:key];
36+
}
37+
38+
thirdPartyComponents = dict;
2539
});
2640

2741
return thirdPartyComponents;

0 commit comments

Comments
 (0)