From 26cc8cddf98cfefcfa35fba58da08587febdf926 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Wed, 3 Apr 2024 23:00:03 -0700 Subject: [PATCH] Do not attempt to call `Class.forName` in OSS (#43816) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43816 We should not attempt to load generated classes by the Annotation processor in OSS because we simply don't run it, so those classes will fail to load. I'm short-circuiting the logic here. This is sustainability work as we got a report for this in OSS a while ago and I never got the time to work on it. Changelog: [Internal] [Changed] - Do not attempt to call Class.forName in OSS Reviewed By: rshest Differential Revision: D55693479 fbshipit-source-id: 3ec84e2c7940011b48f354058b5099b46065166d --- .../ReactAndroid/api/ReactAndroid.api | 6 ++ .../facebook/react/CoreModulesPackage.java | 78 ++++++++------ .../com/facebook/react/DebugCorePackage.java | 48 +++++---- .../com/facebook/react/common/ClassFinder.kt | 30 ++++++ .../react/runtime/CoreReactPackage.java | 68 +++++++----- .../react/shell/MainReactPackage.java | 101 ++++++++++-------- 6 files changed, 201 insertions(+), 130 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ClassFinder.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 2c9d99290b4..cc97c622074 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1666,6 +1666,12 @@ public class com/facebook/react/bridge/queue/ReactQueueConfigurationSpec$Builder public fun setNativeModulesQueueThreadSpec (Lcom/facebook/react/bridge/queue/MessageQueueThreadSpec;)Lcom/facebook/react/bridge/queue/ReactQueueConfigurationSpec$Builder; } +public final class com/facebook/react/common/ClassFinder { + public static final field INSTANCE Lcom/facebook/react/common/ClassFinder; + public static final fun canLoadClassesFromAnnotationProcessors ()Z + public static final fun findClass (Ljava/lang/String;)Ljava/lang/Class; +} + public class com/facebook/react/common/ClearableSynchronizedPool : androidx/core/util/Pools$Pool { public fun (I)V public fun acquire ()Ljava/lang/Object; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index ae1bb0131fd..fde2e75a951 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMarker; +import com.facebook.react.common.ClassFinder; import com.facebook.react.devsupport.LogBoxModule; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; @@ -76,48 +77,21 @@ class CoreModulesPackage extends TurboReactPackage implements ReactPackageLogger /** * This method is overridden, since OSS does not run the annotation processor to generate {@link - * CoreModulesPackage$$ReactModuleInfoProvider} class. Here we check if it exists. If it does not - * exist, we generate one manually in {@link - * CoreModulesPackage#getReactModuleInfoByInitialization()} and return that instead. + * CoreModulesPackage$$ReactModuleInfoProvider} class. Here we check if it exists with the method + * {@link canLoadClassesFromAnnotationProcessors}. If it does not exist, we generate one manually + * in {@link CoreModulesPackage#getReactModuleInfoByInitialization()} and return that instead. */ @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { + if (!ClassFinder.canLoadClassesFromAnnotationProcessors()) { + return fallbackForMissingClass(); + } try { Class reactModuleInfoProviderClass = - Class.forName("com.facebook.react.CoreModulesPackage$$ReactModuleInfoProvider"); + ClassFinder.findClass("com.facebook.react.CoreModulesPackage$$ReactModuleInfoProvider"); return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance(); } catch (ClassNotFoundException e) { - // In OSS case, the annotation processor does not run. We fall back on creating this byhand - Class[] moduleList = - new Class[] { - AndroidInfoModule.class, - DeviceEventManagerModule.class, - DeviceInfoModule.class, - DevSettingsModule.class, - ExceptionsManagerModule.class, - LogBoxModule.class, - HeadlessJsTaskSupportModule.class, - SourceCodeModule.class, - TimingModule.class, - UIManagerModule.class, - }; - - final Map reactModuleInfoMap = new HashMap<>(); - for (Class moduleClass : moduleList) { - ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); - - reactModuleInfoMap.put( - reactModule.name(), - new ReactModuleInfo( - reactModule.name(), - moduleClass.getName(), - reactModule.canOverrideExistingModule(), - reactModule.needsEagerInit(), - reactModule.isCxxModule(), - ReactModuleInfo.classIsTurboModule(moduleClass))); - } - - return () -> reactModuleInfoMap; + return fallbackForMissingClass(); } catch (InstantiationException e) { throw new RuntimeException( "No ReactModuleInfoProvider for CoreModulesPackage$$ReactModuleInfoProvider", e); @@ -127,6 +101,40 @@ class CoreModulesPackage extends TurboReactPackage implements ReactPackageLogger } } + private ReactModuleInfoProvider fallbackForMissingClass() { + // In OSS case, the annotation processor does not run. We fall back on creating this byhand + Class[] moduleList = + new Class[] { + AndroidInfoModule.class, + DeviceEventManagerModule.class, + DeviceInfoModule.class, + DevSettingsModule.class, + ExceptionsManagerModule.class, + LogBoxModule.class, + HeadlessJsTaskSupportModule.class, + SourceCodeModule.class, + TimingModule.class, + UIManagerModule.class, + }; + + final Map reactModuleInfoMap = new HashMap<>(); + for (Class moduleClass : moduleList) { + ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); + + reactModuleInfoMap.put( + reactModule.name(), + new ReactModuleInfo( + reactModule.name(), + moduleClass.getName(), + reactModule.canOverrideExistingModule(), + reactModule.needsEagerInit(), + reactModule.isCxxModule(), + ReactModuleInfo.classIsTurboModule(moduleClass))); + } + + return () -> reactModuleInfoMap; + } + @Override public NativeModule getModule(String name, ReactApplicationContext reactContext) { switch (name) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java index 6a4ba82d25f..67d569cfbb3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java @@ -11,6 +11,7 @@ import androidx.annotation.Nullable; import com.facebook.react.bridge.ModuleSpec; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.common.ClassFinder; import com.facebook.react.devsupport.JSCHeapCapture; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; @@ -53,30 +54,15 @@ public class DebugCorePackage extends TurboReactPackage implements ViewManagerOn @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { + if (!ClassFinder.canLoadClassesFromAnnotationProcessors()) { + return fallbackForMissingClass(); + } try { Class reactModuleInfoProviderClass = - Class.forName("com.facebook.react.DebugCorePackage$$ReactModuleInfoProvider"); + ClassFinder.findClass("com.facebook.react.DebugCorePackage$$ReactModuleInfoProvider"); return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance(); } catch (ClassNotFoundException e) { - // In OSS case, the annotation processor does not run. We fall back on creating this by hand - Class[] moduleList = new Class[] {JSCHeapCapture.class}; - - final Map reactModuleInfoMap = new HashMap<>(); - for (Class moduleClass : moduleList) { - ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); - - reactModuleInfoMap.put( - reactModule.name(), - new ReactModuleInfo( - reactModule.name(), - moduleClass.getName(), - reactModule.canOverrideExistingModule(), - reactModule.needsEagerInit(), - reactModule.isCxxModule(), - ReactModuleInfo.classIsTurboModule(moduleClass))); - } - - return () -> reactModuleInfoMap; + return fallbackForMissingClass(); } catch (InstantiationException e) { throw new RuntimeException( "No ReactModuleInfoProvider for DebugCorePackage$$ReactModuleInfoProvider", e); @@ -86,6 +72,28 @@ public class DebugCorePackage extends TurboReactPackage implements ViewManagerOn } } + private ReactModuleInfoProvider fallbackForMissingClass() { + // In OSS case, the annotation processor does not run. We fall back on creating this by hand + Class[] moduleList = new Class[] {JSCHeapCapture.class}; + + final Map reactModuleInfoMap = new HashMap<>(); + for (Class moduleClass : moduleList) { + ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); + + reactModuleInfoMap.put( + reactModule.name(), + new ReactModuleInfo( + reactModule.name(), + moduleClass.getName(), + reactModule.canOverrideExistingModule(), + reactModule.needsEagerInit(), + reactModule.isCxxModule(), + ReactModuleInfo.classIsTurboModule(moduleClass))); + } + + return () -> reactModuleInfoMap; + } + private static void appendMap( Map map, String name, Provider provider) { map.put(name, ModuleSpec.viewManagerSpec(provider)); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ClassFinder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ClassFinder.kt new file mode 100644 index 00000000000..33ed77837dc --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/ClassFinder.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.common + +import com.facebook.react.BuildConfig +import kotlin.jvm.Throws + +public object ClassFinder { + + /** + * We don't run the ModuleInfoProvider Annotation Processor in OSS, so there is no need to attempt + * to call Class.forName() as we know for sure that those classes won't be there. + */ + @JvmStatic + public fun canLoadClassesFromAnnotationProcessors(): Boolean = BuildConfig.IS_INTERNAL_BUILD + + @JvmStatic + @Throws(ClassNotFoundException::class) + public fun findClass(className: String): Class<*>? { + if (canLoadClassesFromAnnotationProcessors().not()) { + return null + } + return Class.forName(className) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/CoreReactPackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/CoreReactPackage.java index deae9de2334..387aa433787 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/CoreReactPackage.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/CoreReactPackage.java @@ -8,10 +8,12 @@ package com.facebook.react.runtime; import androidx.annotation.Nullable; +import com.facebook.infer.annotation.Assertions; import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.common.ClassFinder; import com.facebook.react.devsupport.LogBoxModule; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.module.annotations.ReactModule; @@ -74,38 +76,16 @@ class CoreReactPackage extends TurboReactPackage { @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { + if (!ClassFinder.canLoadClassesFromAnnotationProcessors()) { + return fallbackForMissingClass(); + } try { Class reactModuleInfoProviderClass = - Class.forName(CoreReactPackage.class.getName() + "$$ReactModuleInfoProvider"); - return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance(); + ClassFinder.findClass(CoreReactPackage.class.getName() + "$$ReactModuleInfoProvider"); + return (ReactModuleInfoProvider) + Assertions.assertNotNull(reactModuleInfoProviderClass).newInstance(); } catch (ClassNotFoundException e) { - // In OSS case, the annotation processor does not run. We fall back on creating this byhand - Class[] moduleList = - new Class[] { - AndroidInfoModule.class, - DeviceInfoModule.class, - SourceCodeModule.class, - DevSettingsModule.class, - DeviceEventManagerModule.class, - LogBoxModule.class, - ExceptionsManagerModule.class, - }; - final Map reactModuleInfoMap = new HashMap<>(); - for (Class moduleClass : moduleList) { - ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); - if (reactModule != null) { - reactModuleInfoMap.put( - reactModule.name(), - new ReactModuleInfo( - reactModule.name(), - moduleClass.getName(), - reactModule.canOverrideExistingModule(), - reactModule.needsEagerInit(), - reactModule.isCxxModule(), - ReactModuleInfo.classIsTurboModule(moduleClass))); - } - } - return () -> reactModuleInfoMap; + return fallbackForMissingClass(); } catch (InstantiationException e) { throw new RuntimeException( "No ReactModuleInfoProvider for " @@ -120,4 +100,34 @@ class CoreReactPackage extends TurboReactPackage { e); } } + + private ReactModuleInfoProvider fallbackForMissingClass() { + // In OSS case, the annotation processor does not run. We fall back on creating this byhand + Class[] moduleList = + new Class[] { + AndroidInfoModule.class, + DeviceInfoModule.class, + SourceCodeModule.class, + DevSettingsModule.class, + DeviceEventManagerModule.class, + LogBoxModule.class, + ExceptionsManagerModule.class, + }; + final Map reactModuleInfoMap = new HashMap<>(); + for (Class moduleClass : moduleList) { + ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); + if (reactModule != null) { + reactModuleInfoMap.put( + reactModule.name(), + new ReactModuleInfo( + reactModule.name(), + moduleClass.getName(), + reactModule.canOverrideExistingModule(), + reactModule.needsEagerInit(), + reactModule.isCxxModule(), + ReactModuleInfo.classIsTurboModule(moduleClass))); + } + } + return () -> reactModuleInfoMap; + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 8c9ef62150e..cd4b581cfbb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -15,6 +15,7 @@ import com.facebook.react.animated.NativeAnimatedModule; import com.facebook.react.bridge.ModuleSpec; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.common.ClassFinder; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; import com.facebook.react.module.model.ReactModuleInfo; @@ -252,56 +253,16 @@ public class MainReactPackage extends TurboReactPackage implements ViewManagerOn @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { + if (!ClassFinder.canLoadClassesFromAnnotationProcessors()) { + return fallbackForMissingClass(); + } try { Class reactModuleInfoProviderClass = - Class.forName("com.facebook.react.shell.MainReactPackage$$ReactModuleInfoProvider"); + ClassFinder.findClass( + "com.facebook.react.shell.MainReactPackage$$ReactModuleInfoProvider"); return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance(); } catch (ClassNotFoundException e) { - // In the OSS case, the annotation processor does not run. We fall back to creating this by - // hand - Class[] moduleList = - new Class[] { - AccessibilityInfoModule.class, - AppearanceModule.class, - AppStateModule.class, - BlobModule.class, - DevLoadingModule.class, - FileReaderModule.class, - ClipboardModule.class, - DialogModule.class, - FrescoModule.class, - I18nManagerModule.class, - ImageLoaderModule.class, - ImageStoreManager.class, - IntentModule.class, - NativeAnimatedModule.class, - NetworkingModule.class, - PermissionsModule.class, - DevToolsSettingsManagerModule.class, - ShareModule.class, - StatusBarModule.class, - SoundManagerModule.class, - ToastModule.class, - VibrationModule.class, - WebSocketModule.class - }; - - final Map reactModuleInfoMap = new HashMap<>(); - for (Class moduleClass : moduleList) { - ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); - if (reactModule != null) { - reactModuleInfoMap.put( - reactModule.name(), - new ReactModuleInfo( - reactModule.name(), - moduleClass.getName(), - reactModule.canOverrideExistingModule(), - reactModule.needsEagerInit(), - reactModule.isCxxModule(), - ReactModuleInfo.classIsTurboModule(moduleClass))); - } - } - return () -> reactModuleInfoMap; + return fallbackForMissingClass(); } catch (InstantiationException e) { throw new RuntimeException( "No ReactModuleInfoProvider for" @@ -314,4 +275,52 @@ public class MainReactPackage extends TurboReactPackage implements ViewManagerOn e); } } + + private ReactModuleInfoProvider fallbackForMissingClass() { + // In the OSS case, the annotation processor does not run. + // We fall back to creating this by hand + Class[] moduleList = + new Class[] { + AccessibilityInfoModule.class, + AppearanceModule.class, + AppStateModule.class, + BlobModule.class, + DevLoadingModule.class, + FileReaderModule.class, + ClipboardModule.class, + DialogModule.class, + FrescoModule.class, + I18nManagerModule.class, + ImageLoaderModule.class, + ImageStoreManager.class, + IntentModule.class, + NativeAnimatedModule.class, + NetworkingModule.class, + PermissionsModule.class, + DevToolsSettingsManagerModule.class, + ShareModule.class, + StatusBarModule.class, + SoundManagerModule.class, + ToastModule.class, + VibrationModule.class, + WebSocketModule.class + }; + + final Map reactModuleInfoMap = new HashMap<>(); + for (Class moduleClass : moduleList) { + ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class); + if (reactModule != null) { + reactModuleInfoMap.put( + reactModule.name(), + new ReactModuleInfo( + reactModule.name(), + moduleClass.getName(), + reactModule.canOverrideExistingModule(), + reactModule.needsEagerInit(), + reactModule.isCxxModule(), + ReactModuleInfo.classIsTurboModule(moduleClass))); + } + } + return () -> reactModuleInfoMap; + } }