first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
react_native_path = ".."
Pod::Spec.new do |s|
s.name = "React-Fabric"
s.version = version
s.summary = "Fabric for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("dummyFile.cpp", "")
s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_Fabric")
s.dependency "React-jsiexecutor"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-jsi"
s.dependency "React-logger"
s.dependency "React-Core"
s.dependency "React-debug"
s.dependency "React-featureflags"
s.dependency "React-runtimescheduler"
s.dependency "React-cxxreact"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.subspec "animated" do |ss|
ss.source_files = podspec_sources("react/renderer/animated/**/*.{m,mm,cpp,h}", "react/renderer/animated/**/*.{h}")
ss.exclude_files = "react/renderer/animated/tests"
ss.header_dir = "react/renderer/animated"
end
s.subspec "animations" do |ss|
ss.source_files = podspec_sources("react/renderer/animations/**/*.{m,mm,cpp,h}", "react/renderer/animations/**/*.{h}")
ss.exclude_files = "react/renderer/animations/tests"
ss.header_dir = "react/renderer/animations"
end
s.subspec "animationbackend" do |ss|
ss.source_files = podspec_sources("react/renderer/animationbackend/**/*.{m,mm,cpp,h}", "react/renderer/animationbackend/**/*.{h}")
ss.header_dir = "react/renderer/animationbackend"
end
s.subspec "attributedstring" do |ss|
ss.source_files = podspec_sources("react/renderer/attributedstring/**/*.{m,mm,cpp,h}", "react/renderer/attributedstring/**/*.{h}")
ss.exclude_files = "react/renderer/attributedstring/tests"
ss.header_dir = "react/renderer/attributedstring"
end
s.subspec "bridging" do |ss|
ss.source_files = podspec_sources("react/renderer/bridging/**/*.{m,mm,cpp,h}", "react/renderer/bridging/**/*.{h}")
ss.exclude_files = "react/renderer/bridging/tests"
ss.header_dir = "react/renderer/bridging"
end
s.subspec "core" do |ss|
header_search_path = [
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_TARGET_SRCROOT)\""
]
if ENV['USE_FRAMEWORKS']
header_search_path = header_search_path + [
"\"$(PODS_TARGET_SRCROOT)/react/renderer/textlayoutmanager/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/scrollview/platform/cxx\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/text/platform/cxx\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/textinput/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/view/platform/cxx\"",
]
end
ss.source_files = podspec_sources("react/renderer/core/**/*.{m,mm,cpp,h}", "react/renderer/core/**/*.{h}")
ss.exclude_files = "react/renderer/core/tests"
ss.header_dir = "react/renderer/core"
ss.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => header_search_path.join(" ")
}
end
s.subspec "componentregistry" do |ss|
ss.source_files = podspec_sources("react/renderer/componentregistry/*.{m,mm,cpp,h}", "react/renderer/componentregistry/*.{h}")
ss.header_dir = "react/renderer/componentregistry"
end
s.subspec "componentregistrynative" do |ss|
ss.source_files = podspec_sources("react/renderer/componentregistry/native/**/*.{m,mm,cpp,h}", "react/renderer/componentregistry/native/**/*.{h}")
ss.header_dir = "react/renderer/componentregistry/native"
end
s.subspec "components" do |ss|
ss.subspec "root" do |sss|
sss.source_files = podspec_sources("react/renderer/components/root/**/*.{m,mm,cpp,h}", "react/renderer/components/root/**/*.{h}")
sss.exclude_files = "react/renderer/components/root/tests"
sss.header_dir = "react/renderer/components/root"
end
ss.subspec "view" do |sss|
sss.dependency "React-renderercss"
sss.dependency "Yoga"
sss.source_files = podspec_sources(["react/renderer/components/view/*.{m,mm,cpp,h}", "react/renderer/components/view/platform/cxx/**/*.{m,mm,cpp,h}"], ["react/renderer/components/view/*.{h}", "react/renderer/components/view/platform/cxx/**/*.{h}"])
sss.header_dir = "react/renderer/components/view"
end
ss.subspec "scrollview" do |sss|
sss.source_files = podspec_sources("react/renderer/components/scrollview/**/*.{m,mm,cpp,h}", "react/renderer/components/scrollview/**/*.{h}")
sss.header_dir = "react/renderer/components/scrollview"
sss.exclude_files = "react/renderer/components/scrollview/tests", "react/renderer/components/scrollview/platform/android"
end
ss.subspec "legacyviewmanagerinterop" do |sss|
sss.source_files = podspec_sources("react/renderer/components/legacyviewmanagerinterop/**/*.{m,mm,cpp,h}", "react/renderer/components/legacyviewmanagerinterop/**/*.{h}")
sss.exclude_files = "react/renderer/components/legacyviewmanagerinterop/tests"
sss.header_dir = "react/renderer/components/legacyviewmanagerinterop"
end
end
s.subspec "dom" do |ss|
ss.dependency "React-graphics"
ss.source_files = podspec_sources("react/renderer/dom/**/*.{m,mm,cpp,h}", "react/renderer/dom/**/*.{h}")
ss.exclude_files = "react/renderer/dom/tests"
ss.header_dir = "react/renderer/dom"
end
s.subspec "scheduler" do |ss|
ss.source_files = podspec_sources("react/renderer/scheduler/**/*.{m,mm,cpp,h}", "react/renderer/scheduler/**/*.h")
ss.header_dir = "react/renderer/scheduler"
ss.dependency "React-performancecdpmetrics"
ss.dependency "React-performancetimeline"
ss.dependency "React-Fabric/observers/events"
end
s.subspec "imagemanager" do |ss|
ss.source_files = podspec_sources("react/renderer/imagemanager/*.{m,mm,cpp,h}", "react/renderer/imagemanager/*.h")
ss.header_dir = "react/renderer/imagemanager"
end
s.subspec "mounting" do |ss|
ss.source_files = podspec_sources("react/renderer/mounting/**/*.{m,mm,cpp,h}", "react/renderer/mounting/**/*.h")
ss.exclude_files = "react/renderer/mounting/tests"
ss.header_dir = "react/renderer/mounting"
end
s.subspec "observers" do |ss|
ss.subspec "events" do |sss|
sss.source_files = podspec_sources("react/renderer/observers/events/**/*.{m,mm,cpp,h}", "react/renderer/observers/events/**/*.h")
sss.exclude_files = "react/renderer/observers/events/tests"
sss.header_dir = "react/renderer/observers/events"
end
ss.subspec "intersection" do |sss|
sss.source_files = podspec_sources("react/renderer/observers/intersection/**/*.{m,mm,cpp,h}", "react/renderer/observers/intersection/**/*.h")
sss.exclude_files = "react/renderer/observers/intersection/tests"
sss.header_dir = "react/renderer/observers/intersection"
end
end
s.subspec "templateprocessor" do |ss|
ss.source_files = podspec_sources("react/renderer/templateprocessor/**/*.{m,mm,cpp,h}", "react/renderer/templateprocessor/**/*.h")
ss.exclude_files = "react/renderer/templateprocessor/tests"
ss.header_dir = "react/renderer/templateprocessor"
end
s.subspec "telemetry" do |ss|
ss.source_files = podspec_sources("react/renderer/telemetry/**/*.{m,mm,cpp,h}", "react/renderer/telemetry/**/*.h")
ss.exclude_files = "react/renderer/telemetry/tests"
ss.header_dir = "react/renderer/telemetry"
end
s.subspec "consistency" do |ss|
ss.source_files = podspec_sources("react/renderer/consistency/**/*.{m,mm,cpp,h}", "react/renderer/consistency/**/*.h")
ss.header_dir = "react/renderer/consistency"
end
s.subspec "uimanager" do |ss|
ss.subspec "consistency" do |sss|
sss.source_files = podspec_sources("react/renderer/uimanager/consistency/*.{m,mm,cpp,h}", "react/renderer/uimanager/consistency/*.h")
sss.header_dir = "react/renderer/uimanager/consistency"
end
ss.dependency "React-rendererconsistency"
ss.source_files = podspec_sources("react/renderer/uimanager/*.{m,mm,cpp,h}", "react/renderer/uimanager/*.h")
ss.header_dir = "react/renderer/uimanager"
end
s.subspec "leakchecker" do |ss|
ss.source_files = podspec_sources("react/renderer/leakchecker/**/*.{cpp,h}", "react/renderer/leakchecker/**/*.h")
ss.exclude_files = "react/renderer/leakchecker/tests"
ss.header_dir = "react/renderer/leakchecker"
ss.pod_target_xcconfig = { "GCC_WARN_PEDANTIC" => "YES" }
end
end

View File

@@ -0,0 +1,176 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
react_native_path = ".."
header_search_path = [
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
"\"$(PODS_TARGET_SRCROOT)\"",
]
if ENV['USE_FRAMEWORKS']
header_search_path = header_search_path + [
"\"$(PODS_TARGET_SRCROOT)/react/renderer/textlayoutmanager/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/scrollview/platform/cxx\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/textinput/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/text/platform/cxx\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/view/platform/cxx\"",
]
end
Pod::Spec.new do |s|
s.name = "React-FabricComponents"
s.version = version
s.summary = "Fabric Components for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("dummyFile.cpp", "")
s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES",
"HEADER_SEARCH_PATHS" => header_search_path.join(" "),
}
resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_FabricComponents")
s.dependency "React-jsiexecutor"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
s.dependency "React-jsi"
s.dependency "React-logger"
s.dependency "React-Core"
s.dependency "React-debug"
s.dependency "React-featureflags"
s.dependency "React-utils"
s.dependency "React-runtimescheduler"
s.dependency "React-cxxreact"
s.dependency "Yoga"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-Fabric", :additional_framework_paths => [
"react/renderer/components/scrollview/platform/cxx",
"react/renderer/components/view/platform/cxx",
"react/renderer/imagemanager/platform/ios"
])
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.subspec "components" do |ss|
ss.subspec "inputaccessory" do |sss|
sss.source_files = podspec_sources("react/renderer/components/inputaccessory/**/*.{m,mm,cpp,h}", "react/renderer/components/inputaccessory/**/*.h")
sss.exclude_files = "react/renderer/components/inputaccessory/tests"
sss.header_dir = "react/renderer/components/inputaccessory"
end
ss.subspec "modal" do |sss|
sss.source_files = podspec_sources("react/renderer/components/modal/*.{m,mm,cpp,h}", "react/renderer/components/modal/*.h")
sss.exclude_files = "react/renderer/components/modal/tests"
sss.header_dir = "react/renderer/components/modal"
end
ss.subspec "safeareaview" do |sss|
sss.source_files = podspec_sources("react/renderer/components/safeareaview/**/*.{m,mm,cpp,h}", "react/renderer/components/safeareaview/**/*.h")
# Exclude tests to avoid conflicts with the react-native-safe-area-context package
sss.exclude_files = "react/renderer/components/safeareaview/tests"
sss.header_dir = "react/renderer/components/safeareaview"
end
ss.subspec "scrollview" do |sss|
sss.source_files = podspec_sources(["react/renderer/components/scrollview/*.{m,mm,cpp,h}",
"react/renderer/components/scrollview/platform/cxx/**/*.{m,mm,cpp,h}"],
["react/renderer/components/scrollview/*.h",
"react/renderer/components/scrollview/platform/cxx/**/*.h"])
sss.exclude_files = "react/renderer/components/scrollview/tests"
sss.header_dir = "react/renderer/components/scrollview"
end
ss.subspec "text" do |sss|
sss.source_files = podspec_sources(["react/renderer/components/text/*.{m,mm,cpp,h}",
"react/renderer/components/text/platform/cxx/**/*.{m,mm,cpp,h}"],
["react/renderer/components/text/*.h",
"react/renderer/components/text/platform/cxx/**/*.h"])
sss.header_dir = "react/renderer/components/text"
end
ss.subspec "iostextinput" do |sss|
sss.source_files = podspec_sources(["react/renderer/components/textinput/*.{m,mm,cpp,h}",
"react/renderer/components/textinput/platform/ios/**/*.{m,mm,cpp,h}"],
["react/renderer/components/textinput/*.h",
"react/renderer/components/textinput/platform/ios/**/*.h"])
sss.header_dir = "react/renderer/components/iostextinput"
end
ss.subspec "switch" do |sss|
sss.source_files = podspec_sources(
["react/renderer/components/switch/iosswitch/**/*.{m,mm,cpp,h}"],
["react/renderer/components/switch/iosswitch/**/*.h"])
sss.exclude_files = "react/renderer/components/switch/iosswitch/**/MacOS*.{m,mm,cpp,h}"
sss.header_dir = "react/renderer/components/switch/"
end
ss.subspec "textinput" do |sss|
sss.source_files = podspec_sources("react/renderer/components/textinput/*.{m,mm,cpp,h}", "react/renderer/components/textinput/**/*.h")
sss.header_dir = "react/renderer/components/textinput"
end
ss.subspec "unimplementedview" do |sss|
sss.source_files = podspec_sources("react/renderer/components/unimplementedview/**/*.{m,mm,cpp,h}", "react/renderer/components/unimplementedview/**/*.h")
sss.exclude_files = "react/renderer/components/unimplementedview/tests"
sss.header_dir = "react/renderer/components/unimplementedview"
end
ss.subspec "virtualview" do |sss|
sss.source_files = "react/renderer/components/virtualview/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/virtualview/tests"
sss.header_dir = "react/renderer/components/virtualview"
end
ss.subspec "virtualviewexperimental" do |sss|
sss.source_files = "react/renderer/components/virtualviewexperimental/**/*.{m,mm,cpp,h}"
sss.exclude_files = "react/renderer/components/virtualviewexperimental/tests"
sss.header_dir = "react/renderer/components/virtualviewexperimental"
end
# Legacy header paths for backwards compat
ss.subspec "rncore" do |sss|
sss.source_files = podspec_sources("react/renderer/components/rncore/**/*.h", "react/renderer/components/rncore/**/*.h")
sss.header_dir = "react/renderer/components/rncore"
end
end
s.subspec "textlayoutmanager" do |ss|
ss.dependency "React-Fabric"
ss.source_files = podspec_sources(["react/renderer/textlayoutmanager/platform/ios/**/*.{m,mm,cpp,h}",
"react/renderer/textlayoutmanager/*.{m,mm,cpp,h}"],
["react/renderer/textlayoutmanager/platform/ios/**/*.h",
"react/renderer/textlayoutmanager/*.{h}"])
ss.exclude_files = "react/renderer/textlayoutmanager/tests",
"react/renderer/textlayoutmanager/platform/android",
"react/renderer/textlayoutmanager/platform/cxx"
ss.header_dir = "react/renderer/textlayoutmanager"
end
end

View File

@@ -0,0 +1,79 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
react_native_path = ".."
header_search_path = [
"\"$(PODS_TARGET_SRCROOT)/ReactCommon\"",
"\"$(PODS_ROOT)/Headers/Private/Yoga\"",
]
if ENV['USE_FRAMEWORKS']
header_search_path = header_search_path + [
"\"$(PODS_TARGET_SRCROOT)\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/textlayoutmanager/platform/ios\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/text/platform/cxx\"",
"\"$(PODS_TARGET_SRCROOT)/react/renderer/components/textinput/platform/ios\"",
# "\"$(PODS_CONFIGURATION_BUILD_DIR)/ReactCodegen/ReactCodegen.framework/Headers\"",
]
end
Pod::Spec.new do |s|
s.name = "React-FabricImage"
s.version = version
s.summary = "Image Component for Fabric for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("react/renderer/components/image/**/*.{m,mm,cpp,h}", "react/renderer/components/image/**/*.h")
s.exclude_files = "react/renderer/components/image/tests"
s.header_dir = "react/renderer/components/image"
s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_path.join(" ")
}
resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_FabricImage")
s.dependency "React-jsiexecutor", version
s.dependency "RCTRequired", version
s.dependency "RCTTypeSafety", version
s.dependency "React-jsi"
s.dependency "React-logger"
s.dependency "React-featureflags"
s.dependency "React-utils"
s.dependency "Yoga"
add_dependency(s, "React-ImageManager", :additional_framework_paths => [
"react/renderer/components/view/platform/cxx",
"react/renderer/imagemanager/platform/ios",
])
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core")
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-Fabric", :additional_framework_paths => [
"react/renderer/components/view/platform/cxx",
"react/renderer/imagemanager/platform/ios"
])
add_dependency(s, "React-rendererdebug")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,40 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-Mapbuffer"
s.version = version
s.summary = "-"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("react/renderer/mapbuffer/*.{cpp,h}", "react/renderer/mapbuffer/*.h")
s.exclude_files = "react/renderer/mapbuffer/tests"
s.public_header_files = 'react/renderer/mapbuffer/*.h'
s.header_dir = "react/renderer/mapbuffer"
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => ["\"$(PODS_TARGET_SRCROOT)\""], "USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard() }
resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_Mapbuffer")
add_dependency(s, "React-debug")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,72 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "ReactCommon"
s.module_name = "ReactCommon"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\"",
"USE_HEADERMAP" => "YES",
"DEFINES_MODULE" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: './')
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
# TODO (T48588859): Restructure this target to align with dir structure: "react/nativemodule/..."
# Note: Update this only when ready to minimize breaking changes.
s.subspec "turbomodule" do |ss|
ss.dependency "React-callinvoker", version
ss.dependency "React-perflogger", version
ss.dependency "React-cxxreact", version
ss.dependency "React-jsi", version
ss.dependency "React-logger", version
if use_hermes()
ss.dependency "hermes-engine"
end
ss.subspec "bridging" do |sss|
sss.dependency "React-jsi", version
sss.source_files = podspec_sources("react/bridging/**/*.{cpp,h}", "react/bridging/**/*.h")
sss.exclude_files = "react/bridging/tests"
sss.header_dir = "react/bridging"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\"" }
if use_hermes()
sss.dependency "hermes-engine"
end
end
ss.subspec "core" do |sss|
sss.source_files = podspec_sources("react/nativemodule/core/ReactCommon/**/*.{cpp,h}", "react/nativemodule/core/ReactCommon/**/*.h")
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_featureflags.framework/Headers\" \"$(PODS_CONFIGURATION_BUILD_DIR)/React-utils/React_utils.framework/Headers\"" }
sss.dependency "React-debug", version
sss.dependency "React-featureflags", version
sss.dependency "React-utils", version
end
end
end

View File

@@ -0,0 +1,15 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
add_library(callinvoker INTERFACE)
target_include_directories(callinvoker INTERFACE .)
target_compile_reactnative_options(callinvoker INTERFACE)
target_compile_options(callinvoker INTERFACE -Wpedantic)

View File

@@ -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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-callinvoker"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
s.header_dir = "ReactCommon"
end

View File

@@ -0,0 +1,61 @@
/*
* 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.
*/
#pragma once
#include "SchedulerPriority.h"
#include <functional>
#include <string>
namespace facebook::jsi {
class Runtime;
}
namespace facebook::react {
using CallFunc = std::function<void(jsi::Runtime &)>;
/**
* An interface for a generic native-to-JS call invoker. See BridgeJSCallInvoker
* for an implementation.
*/
class CallInvoker {
public:
virtual void invokeAsync(CallFunc &&func) noexcept = 0;
virtual void invokeAsync(SchedulerPriority /*priority*/, CallFunc &&func) noexcept
{
// When call with priority is not implemented, fall back to a regular async
// execution
invokeAsync(std::move(func));
}
virtual void invokeSync(CallFunc &&func) = 0;
// Backward compatibility only, prefer the CallFunc methods instead
virtual void invokeAsync(std::function<void()> &&func) noexcept
{
invokeAsync([func = std::move(func)](jsi::Runtime &) { func(); });
}
virtual void invokeSync(std::function<void()> &&func)
{
invokeSync([func = std::move(func)](jsi::Runtime &) { func(); });
}
virtual ~CallInvoker() = default;
};
using NativeMethodCallFunc = std::function<void()>;
class NativeMethodCallInvoker {
public:
virtual void invokeAsync(const std::string &methodName, NativeMethodCallFunc &&func) noexcept = 0;
virtual void invokeSync(const std::string &methodName, NativeMethodCallFunc &&func) = 0;
virtual ~NativeMethodCallInvoker() = default;
};
} // namespace facebook::react

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
#pragma once
namespace facebook::react {
enum class SchedulerPriority : int {
ImmediatePriority = 1,
UserBlockingPriority = 2,
NormalPriority = 3,
LowPriority = 4,
IdlePriority = 5,
};
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <list>
namespace facebook::react {
class TestCallInvoker : public CallInvoker {
public:
explicit TestCallInvoker(facebook::jsi::Runtime &runtime) : runtime_(runtime) {}
void invokeAsync(CallFunc &&func) noexcept override
{
queue_.push_back(std::move(func));
}
void invokeSync(CallFunc &&func) override
{
func(runtime_);
}
void flushQueue()
{
while (!queue_.empty()) {
queue_.front()(runtime_);
queue_.pop_front();
runtime_.drainMicrotasks(); // Run microtasks every cycle.
}
}
size_t queueSize()
{
return queue_.size();
}
private:
facebook::jsi::Runtime &runtime_;
std::list<CallFunc> queue_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,24 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
# This function can be used to define a dependency for Android, with optional
# fallback for cross platform.
#
# Usage:
# react_native_android_selector(jsijni jsijni jsi)
# react_native_android_selector(fabricjni fabricjni "")
# target_link_librarues(target_name ${jsijni} ${fabricjni})
function(react_native_android_selector output_var name fallback)
if(ANDROID)
set(${output_var} ${name})
else()
set(${output_var} ${fallback})
endif()
set(${output_var} ${${output_var}} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,35 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
# This CMake file exposes the React Native Flags that all the libraries should use when
# compiling a module that will end up inside libreactnative.so
SET(reactnative_FLAGS
-Wall
-Werror
-fexceptions
-frtti
-std=c++20
-DFOLLY_NO_CONFIG=1
-DLOG_TAG=\"ReactNative\"
)
# This function can be used to configure the reactnative flags for a specific target in
# a convenient way. The usage is:
#
# target_compile_reactnative_options(target_name scope)
#
# scope is either PUBLIC, PRIVATE or INTERFACE
function(target_compile_reactnative_options target_name scope)
target_compile_options(${target_name} ${scope} ${reactnative_FLAGS})
# TODO T228344694 improve this so that it works for all platforms
if(ANDROID)
target_compile_definitions(${target_name} ${scope} RN_SERIALIZABLE_STATE)
endif()
endfunction()

View File

@@ -0,0 +1,29 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB react_cxxreact_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_cxxreact OBJECT ${react_cxxreact_SRC})
target_include_directories(react_cxxreact PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_cxxreact
boost
callinvoker
folly_runtime
glog
jsi
jsinspector
logger
reactperflogger
runtimeexecutor
react_debug)
target_compile_reactnative_options(react_cxxreact PRIVATE)
target_compile_options(react_cxxreact PRIVATE -Wno-unused-lambda-capture)

View File

@@ -0,0 +1,221 @@
/*
* 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.
*/
#pragma once
#include <functional>
#include <map>
#include <tuple>
#include <vector>
#include <folly/dynamic.h>
namespace facebook::react {
class Instance;
} // namespace facebook::react
namespace facebook::xplat::module {
/**
* Base class for Catalyst native modules whose implementations are
* written in C++. Native methods are represented by instances of the
* Method struct. Generally, a derived class will manage an instance
* which represents the data for the module, and non-Catalyst-specific
* methods can be wrapped in lambdas which convert between
* folly::dynamic and native C++ objects. The Callback arguments will
* pass through to js functions passed to the analogous javascript
* methods. At most two callbacks will be converted. Results should
* be passed to the first callback, and errors to the second callback.
* Exceptions thrown by a method will be converted to platform
* exceptions, and handled however they are handled on that platform.
* (TODO mhorowitz #7128529: this exception behavior is not yet
* implemented.)
*
* There are two sets of constructors here. The first set initializes
* a Method using a name and anything convertible to a std::function.
* This is most useful for registering a lambda as a RN method. There
* are overloads to support functions which take no arguments,
* arguments only, and zero, one, or two callbacks.
*
* The second set of methods is similar, but instead of taking a
* function, takes the method name, an object, and a pointer to a
* method on that object.
*/
class CxxModule {
class AsyncTagType {};
class SyncTagType {};
public:
using Provider = std::function<std::unique_ptr<CxxModule>()>;
using Callback = std::function<void(std::vector<folly::dynamic>)>;
constexpr static AsyncTagType AsyncTag = AsyncTagType();
constexpr static SyncTagType SyncTag = SyncTagType();
struct Method {
std::string name;
size_t callbacks;
bool isPromise;
std::function<void(folly::dynamic, Callback, Callback)> func;
std::function<folly::dynamic(folly::dynamic)> syncFunc;
const char *getType()
{
assert(func || syncFunc);
return func ? (isPromise ? "promise" : "async") : "sync";
}
// std::function/lambda ctors
Method(std::string aname, std::function<void()> &&afunc)
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(std::move(afunc)))
{
}
Method(std::string aname, std::function<void(folly::dynamic)> &&afunc)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
func(std::bind(std::move(afunc), std::placeholders::_1))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback)> &&afunc)
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(std::move(afunc), std::placeholders::_1, std::placeholders::_2))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback, Callback)> &&afunc)
: name(std::move(aname)), callbacks(2), isPromise(true), func(std::move(afunc))
{
}
Method(std::string aname, std::function<void(folly::dynamic, Callback, Callback)> &&afunc, AsyncTagType /*unused*/)
: name(std::move(aname)), callbacks(2), isPromise(false), func(std::move(afunc))
{
}
// method pointer ctors
template <typename T>
Method(std::string aname, T *t, void (T::*method)())
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(method, t))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic))
: name(std::move(aname)), callbacks(0), isPromise(false), func(std::bind(method, t, std::placeholders::_1))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback))
: name(std::move(aname)),
callbacks(1),
isPromise(false),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback, Callback))
: name(std::move(aname)),
callbacks(2),
isPromise(true),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{
}
template <typename T>
Method(std::string aname, T *t, void (T::*method)(folly::dynamic, Callback, Callback), AsyncTagType /*unused*/)
: name(std::move(aname)),
callbacks(2),
isPromise(false),
func(std::bind(method, t, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))
{
}
// sync std::function/lambda ctors
// Overloads for functions returning void give ambiguity errors.
// I am not sure if this is a runtime/compiler bug, or a
// limitation I do not understand.
Method(std::string aname, std::function<folly::dynamic()> &&afunc, SyncTagType /*unused*/)
: name(std::move(aname)),
callbacks(0),
isPromise(false),
syncFunc([afunc = std::move(afunc)](const folly::dynamic &) { return afunc(); })
{
}
Method(std::string aname, std::function<folly::dynamic(folly::dynamic)> &&afunc, SyncTagType /*unused*/)
: name(std::move(aname)), callbacks(0), isPromise(false), syncFunc(std::move(afunc))
{
}
};
/**
* This may block, if necessary to complete cleanup before the
* object is destroyed.
*/
virtual ~CxxModule() = default;
/**
* @return the name of this module. This will be the name used to {@code
* require()} this module from javascript.
*/
virtual std::string getName() = 0;
/**
* Each entry in the map will be exported as a property to JS. The
* key is the property name, and the value can be anything.
*/
virtual auto getConstants() -> std::map<std::string, folly::dynamic>
{
return {};
};
/**
* @return a list of methods this module exports to JS.
*/
virtual auto getMethods() -> std::vector<Method> = 0;
/**
* Called during the construction of CxxNativeModule.
*/
void setInstance(std::weak_ptr<react::Instance> instance)
{
instance_ = instance;
}
/**
* @return a weak_ptr to the current instance of the bridge.
* When used with CxxNativeModule, this gives Cxx modules access to functions
* such as `callJSFunction`, allowing them to communicate back to JS outside
* of the regular callbacks.
*/
std::weak_ptr<react::Instance> getInstance()
{
return instance_;
}
private:
std::weak_ptr<react::Instance> instance_;
};
} // namespace facebook::xplat::module

View File

@@ -0,0 +1,258 @@
/*
* 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.
*/
#include "CxxNativeModule.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include "Instance.h"
#include <folly/json.h>
#include <glog/logging.h>
#include <iterator>
#include "JsArgumentHelpers.h"
#include "MessageQueueThread.h"
#include "TraceSection.h"
#include <logger/react_native_log.h>
using facebook::xplat::module::CxxModule;
namespace facebook::react {
std::function<void(folly::dynamic)> makeCallback(
std::weak_ptr<Instance> instance,
const folly::dynamic& callbackId) {
if (!callbackId.isNumber()) {
throw std::invalid_argument("Expected callback(s) as final argument");
}
auto id = callbackId.asInt();
return [winstance = std::move(instance), id](folly::dynamic args) {
if (auto instance = winstance.lock()) {
instance->callJSCallback(id, std::move(args));
}
};
}
namespace {
/**
* CxxModule::Callback accepts a vector<dynamic>, makeCallback returns
* a callback that accepts a dynamic, adapt the second into the first.
* TODO: Callback types should be made equal (preferably
* function<void(dynamic)>) to avoid the extra copy and indirect call.
*/
CxxModule::Callback convertCallback(
std::function<void(folly::dynamic)> callback) {
return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
// after unpinning folly, can use folly::dynamic::array_range
folly::dynamic obj = folly::dynamic::array;
for (auto& arg : args) {
obj.push_back(std::move(arg));
}
callback(std::move(obj));
};
}
} // namespace
bool CxxNativeModule::shouldWarnOnUse_ = false;
void CxxNativeModule::setShouldWarnOnUse(bool value) {
shouldWarnOnUse_ = value;
}
void CxxNativeModule::emitWarnIfWarnOnUsage(
const std::string& method_name,
const std::string& module_name) {
if (shouldWarnOnUse_) {
std::string message = "Calling " + method_name +
" on Cxx NativeModule (name = \"" + module_name + "\").";
react_native_log_warn(message.c_str());
}
}
std::string CxxNativeModule::getName() {
return name_;
}
std::string CxxNativeModule::getSyncMethodName(unsigned int reactMethodId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(reactMethodId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
return methods_[reactMethodId].name;
}
std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
lazyInit();
std::vector<MethodDescriptor> descs;
descs.reserve(methods_.size());
for (auto& method : methods_) {
descs.emplace_back(method.name, method.getType());
}
return descs;
}
folly::dynamic CxxNativeModule::getConstants() {
lazyInit();
if (!module_) {
return nullptr;
}
emitWarnIfWarnOnUsage("getConstants()", getName());
folly::dynamic constants = folly::dynamic::object();
for (auto& pair : module_->getConstants()) {
constants.insert(std::move(pair.first), std::move(pair.second));
}
return constants;
}
void CxxNativeModule::invoke(
unsigned int reactMethodId,
folly::dynamic&& params,
int callId) {
if (reactMethodId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(reactMethodId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
if (!params.isArray()) {
throw std::invalid_argument(
std::string("Method parameters should be array, but are ") +
params.typeName());
}
CxxModule::Callback first;
CxxModule::Callback second;
const auto& method = methods_[reactMethodId];
if (!method.func) {
throw std::runtime_error(
"Method " + method.name + " is synchronous but invoked asynchronously");
}
emitWarnIfWarnOnUsage(method.name, getName());
if (params.size() < method.callbacks) {
throw std::invalid_argument(
"Expected " + std::to_string(method.callbacks) +
" callbacks, but only " + std::to_string(params.size()) +
" parameters provided");
}
if (method.callbacks == 1) {
first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
} else if (method.callbacks == 2) {
first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
second =
convertCallback(makeCallback(instance_, params[params.size() - 1]));
}
params.resize(params.size() - method.callbacks);
// I've got a few flawed options here. I can let the C++ exception
// propagate, and the registry will log/convert them to java exceptions.
// This lets all the java and red box handling work ok, but the only info I
// can capture about the C++ exception is the what() string, not the stack.
// I can std::terminate() the app. This causes the full, accurate C++
// stack trace to be added to logcat by debuggerd. The java state is lost,
// but in practice, the java stack is always the same in this case since
// the javascript stack is not visible, and the crash is unfriendly to js
// developers, but crucial to C++ developers. The what() value is also
// lost. Finally, I can catch, log the java stack, then rethrow the C++
// exception. In this case I get java and C++ stack data, but the C++
// stack is as of the rethrow, not the original throw, both the C++ and
// java stacks always look the same.
//
// I am going with option 2, since that seems like the most useful
// choice. It would be nice to be able to get what() and the C++
// stack. I'm told that will be possible in the future. TODO
// mhorowitz #7128529: convert C++ exceptions to Java
const auto& moduleName = name_;
TraceSection s(
"CxxMethodCallQueue", "module", moduleName, "method", method.name);
messageQueueThread_->runOnQueue([method,
moduleName,
params = std::move(params),
first,
second,
callId]() {
#ifdef WITH_FBSYSTRACE
if (callId != -1) {
fbsystrace_end_async_flow(TRACE_TAG_REACT, "native", callId);
}
#else
(void)(callId);
#endif
TraceSection s(
"CxxMethodCallDispatch", "module", moduleName, "method", method.name);
try {
method.func(params, first, second);
} catch (const facebook::xplat::JsArgumentException&) {
throw;
} catch (std::exception& e) {
LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
<< " failed: " << e.what();
std::terminate();
} catch (std::string& error) {
LOG(ERROR) << "std::string. Method call " << method.name.c_str()
<< " failed: " << error.c_str();
std::terminate();
} catch (...) {
LOG(ERROR) << "Method call " << method.name.c_str()
<< " failed. unknown error";
std::terminate();
}
});
}
MethodCallResult CxxNativeModule::callSerializableNativeHook(
unsigned int hookId,
folly::dynamic&& args) {
if (hookId >= methods_.size()) {
throw std::invalid_argument(
"methodId " + std::to_string(hookId) + " out of range [0.." +
std::to_string(methods_.size()) + "]");
}
const auto& method = methods_[hookId];
if (!method.syncFunc) {
throw std::runtime_error(
"Method " + method.name + " is asynchronous but invoked synchronously");
}
emitWarnIfWarnOnUsage(method.name, getName());
return method.syncFunc(std::move(args));
}
void CxxNativeModule::lazyInit() {
if (module_ || !provider_) {
return;
}
// TODO 17216751: providers should never return null modules
module_ = provider_();
provider_ = nullptr;
if (module_) {
module_->setInstance(instance_);
methods_ = module_->getMethods();
}
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,66 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cxxreact/CxxModule.h>
#include <cxxreact/NativeModule.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class Instance;
class MessageQueueThread;
typedef void (*WarnOnUsageLogger)(std::string message);
[[deprecated("This API will be removed along with the legacy architecture.")]]
std::function<void(folly::dynamic)> makeCallback(std::weak_ptr<Instance> instance, const folly::dynamic &callbackId);
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] CxxNativeModule
: public NativeModule {
public:
CxxNativeModule(
std::weak_ptr<Instance> instance,
std::string name,
xplat::module::CxxModule::Provider provider,
std::shared_ptr<MessageQueueThread> messageQueueThread)
: instance_(instance), name_(std::move(name)), provider_(provider), messageQueueThread_(messageQueueThread)
{
}
std::string getName() override;
std::string getSyncMethodName(unsigned int methodId) override;
std::vector<MethodDescriptor> getMethods() override;
folly::dynamic getConstants() override;
void invoke(unsigned int reactMethodId, folly::dynamic &&params, int callId) override;
MethodCallResult callSerializableNativeHook(unsigned int hookId, folly::dynamic &&args) override;
static void setShouldWarnOnUse(bool value);
private:
void lazyInit();
std::weak_ptr<Instance> instance_;
std::string name_;
xplat::module::CxxModule::Provider provider_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
std::unique_ptr<xplat::module::CxxModule> module_;
std::vector<xplat::module::CxxModule::Method> methods_;
void emitWarnIfWarnOnUsage(const std::string &method_name, const std::string &module_name);
static bool shouldWarnOnUse_;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,42 @@
/*
* 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.
*/
#pragma once
#include <jsi/jsi.h>
namespace facebook::react {
inline static void handleJSError(jsi::Runtime &runtime, const jsi::JSError &error, bool isFatal)
{
auto errorUtils = runtime.global().getProperty(runtime, "ErrorUtils");
if (errorUtils.isUndefined() || !errorUtils.isObject() ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportFatalError") ||
!errorUtils.getObject(runtime).hasProperty(runtime, "reportError")) {
// ErrorUtils was not set up. This probably means the bundle didn't
// load properly.
throw jsi::JSError(
runtime,
"ErrorUtils is not set up properly. Something probably went wrong trying to load the JS bundle. Trying to report error " +
error.getMessage(),
error.getStack());
}
// TODO(janzer): Rewrite this function to return the processed error
// instead of just reporting it through the native module
if (isFatal) {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(runtime, "reportFatalError");
func.call(runtime, error.value());
} else {
auto func = errorUtils.asObject(runtime).getPropertyAsFunction(runtime, "reportError");
func.call(runtime, error.value());
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,372 @@
/*
* 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.
*/
#include "Instance.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include "ErrorUtils.h"
#include "JSBigString.h"
#include "JSBundleType.h"
#include "JSExecutor.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "NativeToJsBridge.h"
#include "RAMBundleRegistry.h"
#include "RecoverableError.h"
#include "TraceSection.h"
#include <cxxreact/JSIndexedRAMBundle.h>
#include <folly/json.h>
#include <react/debug/react_native_assert.h>
#include <glog/logging.h>
#include <condition_variable>
#include <exception>
#include <memory>
#include <mutex>
#include <string>
namespace facebook::react {
Instance::~Instance() {
if (nativeToJsBridge_) {
nativeToJsBridge_->destroy();
}
}
void Instance::unregisterFromInspector() {
if (inspectorTarget_ != nullptr) {
assert(runtimeInspectorTarget_);
inspectorTarget_->unregisterRuntime(*runtimeInspectorTarget_);
assert(parentInspectorTarget_);
parentInspectorTarget_->unregisterInstance(*inspectorTarget_);
parentInspectorTarget_ = nullptr;
inspectorTarget_ = nullptr;
}
}
void Instance::initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::HostTarget* parentInspectorTarget) {
callback_ = std::move(callback);
moduleRegistry_ = std::move(moduleRegistry);
parentInspectorTarget_ = parentInspectorTarget;
jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
jsef.get(), moduleRegistry_, jsQueue, callback_);
// If a parent inspector HostTarget is provided, perform inspector
// initialization synchronously.
if (parentInspectorTarget_ != nullptr) {
auto inspectorExecutor = parentInspectorTarget_->executorFromThis();
std::mutex inspectorInitializedMutex;
std::condition_variable inspectorInitializedCv;
bool inspectorInitialized = false;
// Schedule work on the inspector thread. NOTE: We expect this callback
// to always execute, given the invariant that `initializeBridge` (this
// method) completes before `unregisterFromInspector` is called.
// - On iOS, instance creation and invalidation both run on the main
// queue (`RCTCxxBridge::start,invalidate` use `RCTAssertMainQueue`).
// - On Android, `ReactContext` must be initialized with a constructed
// `CatalystInstance` (in which `Instance::initializeBridge` has
// completed) before `destroy` can be called.
inspectorExecutor([this,
&inspectorInitialized,
&inspectorInitializedMutex,
&inspectorInitializedCv](
jsinspector_modern::HostTarget& hostTarget) {
// NOTE: By passing *this, we strongly assume the Instance will still
// be alive by the time this executes.
// - On iOS, instance creation is done synchronously
// (`RCTCxxBridge::_initializeBridgeLocked`).
// - On Android, we explicitly wait for instance creation before
// destruction (`ReactInstanceManager::mReactContextLock`).
inspectorTarget_ = &hostTarget.registerInstance(*this);
RuntimeExecutor runtimeExecutorIfJsi = getRuntimeExecutor();
runtimeInspectorTarget_ = &inspectorTarget_->registerRuntime(
nativeToJsBridge_->getInspectorTargetDelegate(),
runtimeExecutorIfJsi ? runtimeExecutorIfJsi : [](auto) {});
// Signal that initialization is complete
{
std::lock_guard lock(inspectorInitializedMutex);
inspectorInitialized = true;
}
inspectorInitializedCv.notify_one();
});
// Wait for the initialization work to complete
{
std::unique_lock lock(inspectorInitializedMutex);
inspectorInitializedCv.wait(
lock, [&inspectorInitialized] { return inspectorInitialized; });
}
}
// Initialize the JavaScript runtime after we've initialized the inspector
nativeToJsBridge_->initializeRuntime();
// After NativeToJsBridge is created, the jsi::Runtime should exist. Also,
// the JS message queue thread exists. So, it's safe to schedule all queued
// up JS calls.
jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);
std::scoped_lock lock(m_syncMutex);
m_syncReady = true;
m_syncCV.notify_all();
});
CHECK(nativeToJsBridge_);
}
void Instance::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL) {
callback_->incrementPendingJSCalls();
TraceSection s("Instance::loadBundle", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(
std::move(bundleRegistry),
std::move(startupScript),
std::move(sourceURL));
}
void Instance::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL) {
std::unique_lock<std::mutex> lock(m_syncMutex);
m_syncCV.wait(lock, [this] { return m_syncReady; });
TraceSection s("Instance::loadBundleSync", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundleSync(
std::move(bundleRegistry),
std::move(startupScript),
std::move(sourceURL));
}
void Instance::setSourceURL(std::string sourceURL) {
callback_->incrementPendingJSCalls();
TraceSection s("Instance::setSourceURL", "sourceURL", sourceURL);
nativeToJsBridge_->loadBundle(nullptr, nullptr, std::move(sourceURL));
}
void Instance::loadScriptFromString(
std::unique_ptr<const JSBigString> string,
std::string sourceURL,
bool loadSynchronously) {
TraceSection s("Instance::loadScriptFromString", "sourceURL", sourceURL);
if (loadSynchronously) {
loadBundleSync(nullptr, std::move(string), std::move(sourceURL));
} else {
loadBundle(nullptr, std::move(string), std::move(sourceURL));
}
}
void Instance::loadRAMBundleFromString(
std::unique_ptr<const JSBigString> script,
const std::string& sourceURL) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(std::move(script));
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::singleBundleRegistry(std::move(bundle));
loadRAMBundle(std::move(registry), std::move(startupScript), sourceURL, true);
}
void Instance::loadRAMBundleFromFile(
const std::string& sourcePath,
const std::string& sourceURL,
bool loadSynchronously) {
auto bundle = std::make_unique<JSIndexedRAMBundle>(sourcePath.c_str());
auto startupScript = bundle->getStartupCode();
auto registry = RAMBundleRegistry::multipleBundlesRegistry(
std::move(bundle), JSIndexedRAMBundle::buildFactory());
loadRAMBundle(
std::move(registry),
std::move(startupScript),
sourceURL,
loadSynchronously);
}
void Instance::loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously) {
if (loadSynchronously) {
loadBundleSync(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
} else {
loadBundle(
std::move(bundleRegistry),
std::move(startupScript),
std::move(startupScriptSourceURL));
}
}
void Instance::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
nativeToJsBridge_->setGlobalVariable(
std::move(propName), std::move(jsonValue));
}
void* Instance::getJavaScriptContext() {
return nativeToJsBridge_ ? nativeToJsBridge_->getJavaScriptContext()
: nullptr;
}
bool Instance::isInspectable() {
return nativeToJsBridge_ ? nativeToJsBridge_->isInspectable() : false;
}
bool Instance::isBatchActive() {
return nativeToJsBridge_ ? nativeToJsBridge_->isBatchActive() : false;
}
void Instance::callJSFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& params) {
callback_->incrementPendingJSCalls();
nativeToJsBridge_->callFunction(
std::move(module), std::move(method), std::move(params));
}
void Instance::callJSCallback(uint64_t callbackId, folly::dynamic&& params) {
TraceSection s("Instance::callJSCallback");
callback_->incrementPendingJSCalls();
nativeToJsBridge_->invokeCallback((double)callbackId, std::move(params));
}
void Instance::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
nativeToJsBridge_->registerBundle(bundleId, bundlePath);
}
const ModuleRegistry& Instance::getModuleRegistry() const {
return *moduleRegistry_;
}
ModuleRegistry& Instance::getModuleRegistry() {
return *moduleRegistry_;
}
void Instance::handleMemoryPressure(int pressureLevel) {
if (nativeToJsBridge_) {
// This class resets `nativeToJsBridge_` only in the destructor,
// hence a race is not possible there.
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
}
}
std::shared_ptr<CallInvoker> Instance::getJSCallInvoker() {
return std::static_pointer_cast<CallInvoker>(jsCallInvoker_);
}
RuntimeExecutor Instance::getRuntimeExecutor() {
// HACK: RuntimeExecutor is not compatible with non-JSIExecutor, we return
// a null callback, which the caller should handle.
if (getJavaScriptContext() == nullptr) {
return nullptr;
}
std::weak_ptr<NativeToJsBridge> weakNativeToJsBridge = nativeToJsBridge_;
return [weakNativeToJsBridge](
std::function<void(jsi::Runtime & runtime)>&& callback) {
if (auto strongNativeToJsBridge = weakNativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[callback = std::move(callback)](JSExecutor* executor) {
// Assumes the underlying executor is a JSIExecutor
auto* runtime =
static_cast<jsi::Runtime*>(executor->getJavaScriptContext());
try {
react_native_assert(runtime != nullptr);
callback(*runtime);
executor->flush();
} catch (jsi::JSError& originalError) {
handleJSError(*runtime, originalError, true);
}
});
}
};
}
std::shared_ptr<NativeMethodCallInvoker>
Instance::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) {
return nativeToJsBridge_->getDecoratedNativeMethodCallInvoker(
nativeMethodCallInvoker);
}
void Instance::JSCallInvoker::setNativeToJsBridgeAndFlushCalls(
std::weak_ptr<NativeToJsBridge> nativeToJsBridge) {
std::scoped_lock guard(m_mutex);
m_shouldBuffer = false;
m_nativeToJsBridge = nativeToJsBridge;
while (!m_workBuffer.empty()) {
scheduleAsync(std::move(m_workBuffer.front()));
m_workBuffer.pop_front();
}
}
void Instance::JSCallInvoker::invokeSync(CallFunc&& /*work*/) {
// TODO: Replace JS Callinvoker with RuntimeExecutor.
throw std::runtime_error(
"Synchronous native -> JS calls are currently not supported.");
}
void Instance::JSCallInvoker::invokeAsync(CallFunc&& work) noexcept {
std::scoped_lock guard(m_mutex);
/**
* Why is is necessary to queue up async work?
*
* 1. TurboModuleManager must be created synchronously after the Instance,
* before we load the source code. This is when the NativeModule system
* is initialized. RCTDevLoadingView shows bundle download progress.
* 2. TurboModuleManager requires a JS CallInvoker.
* 3. The JS CallInvoker requires the NativeToJsBridge, which is created on
* the JS thread in Instance::initializeBridge.
*
* Therefore, although we don't call invokeAsync before the JS bundle is
* executed, this buffering is implemented anyways to ensure that work
* isn't discarded.
*/
if (m_shouldBuffer) {
m_workBuffer.push_back(std::move(work));
return;
}
scheduleAsync(std::move(work));
}
void Instance::JSCallInvoker::scheduleAsync(CallFunc&& work) noexcept {
if (auto strongNativeToJsBridge = m_nativeToJsBridge.lock()) {
strongNativeToJsBridge->runOnExecutorQueue(
[work = std::move(work)](JSExecutor* executor) {
auto* runtime = (jsi::Runtime*)executor->getJavaScriptContext();
work(*runtime);
executor->flush();
});
}
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,176 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/NativeToJsBridge.h>
#include <jsinspector-modern/ReactCdp.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace folly {
struct dynamic;
}
namespace facebook::react {
class JSBigString;
class JSExecutorFactory;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
struct [[deprecated("This API will be removed along with the legacy architecture.")]] InstanceCallback {
virtual ~InstanceCallback() = default;
virtual void onBatchComplete() {}
virtual void incrementPendingJSCalls() {}
virtual void decrementPendingJSCalls() {}
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] Instance
: private jsinspector_modern::InstanceTargetDelegate {
public:
~Instance() override;
void initializeBridge(
std::unique_ptr<InstanceCallback> callback,
std::shared_ptr<JSExecutorFactory> jsef,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry,
jsinspector_modern::HostTarget *inspectorTarget = nullptr);
void initializeRuntime();
void setSourceURL(std::string sourceURL);
void loadScriptFromString(std::unique_ptr<const JSBigString> string, std::string sourceURL, bool loadSynchronously);
void loadRAMBundleFromString(std::unique_ptr<const JSBigString> script, const std::string &sourceURL);
void loadRAMBundleFromFile(const std::string &sourcePath, const std::string &sourceURL, bool loadSynchronously);
void loadRAMBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL,
bool loadSynchronously);
bool supportsProfiling();
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void callJSFunction(std::string &&module, std::string &&method, folly::dynamic &&params);
void callJSCallback(uint64_t callbackId, folly::dynamic &&params);
// This method is experimental, and may be modified or removed.
void registerBundle(uint32_t bundleId, const std::string &bundlePath);
const ModuleRegistry &getModuleRegistry() const;
ModuleRegistry &getModuleRegistry();
void handleMemoryPressure(int pressureLevel);
/**
* JS CallInvoker is used by TurboModules to schedule work on the JS thread.
*
* Why is the bridge creating JS CallInvoker?
*
* - After every Native -> JS call in the TurboModule system, the bridge
* needs to flush all queued NativeModule method calls. The bridge must
* also dispatch onBatchComplete if the queue of NativeModule method calls
* was not empty.
*/
std::shared_ptr<CallInvoker> getJSCallInvoker();
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*
* Why is the bridge decorating NativeMethodCallInvoker?
*
* - The bridge must be informed of all TurboModule async method calls. Why?
* When all queued NativeModule method calls are flushed by a call from
* Native -> JS, if that queue was non-zero in size, JsToNativeBridge
* dispatches onBatchComplete. When we turn our NativeModules to
* TurboModules, there will be less and less pending NativeModule method
* calls, so onBatchComplete will not fire as often. Therefore, the bridge
* needs to know how many TurboModule async method calls have been completed
* since the last time the bridge was flushed. If this number is non-zero,
* we fire onBatchComplete.
*
* Why can't we just create and return a new NativeMethodCallInvoker?
*
* - On Android, we have one NativeModule thread. That thread is created and
* managed outside of NativeToJsBridge. On iOS, we have one MethodQueue per
* module. Those MethodQueues are also created and managed outside of
* NativeToJsBridge. Therefore, we need to pass in a
* NativeMethodCallInvoker that schedules work on the respective thread.
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker);
/**
* RuntimeExecutor is used by Fabric to access the jsi::Runtime.
*/
RuntimeExecutor getRuntimeExecutor();
/**
* Unregisters the instance from the inspector. This method must be called
* on the main (non-JS) thread, AFTER \c initializeBridge has completed.
*/
void unregisterFromInspector();
private:
void callNativeModules(folly::dynamic &&calls, bool isEndOfBatch);
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL);
std::shared_ptr<InstanceCallback> callback_;
std::shared_ptr<NativeToJsBridge> nativeToJsBridge_;
std::shared_ptr<ModuleRegistry> moduleRegistry_;
std::mutex m_syncMutex;
std::condition_variable m_syncCV;
bool m_syncReady = false;
class JSCallInvoker : public CallInvoker {
private:
std::weak_ptr<NativeToJsBridge> m_nativeToJsBridge;
std::mutex m_mutex;
bool m_shouldBuffer = true;
std::list<CallFunc> m_workBuffer;
void scheduleAsync(CallFunc &&work) noexcept;
public:
void setNativeToJsBridgeAndFlushCalls(std::weak_ptr<NativeToJsBridge> nativeToJsBridge);
void invokeAsync(CallFunc &&work) noexcept override;
void invokeSync(CallFunc &&work) override;
};
std::shared_ptr<JSCallInvoker> jsCallInvoker_ = std::make_shared<JSCallInvoker>();
jsinspector_modern::HostTarget *parentInspectorTarget_{nullptr};
jsinspector_modern::InstanceTarget *inspectorTarget_{nullptr};
jsinspector_modern::RuntimeTarget *runtimeInspectorTarget_{nullptr};
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,117 @@
/*
* 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.
*/
#include "JSBigString.h"
#include <glog/logging.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/SysMman.h>
#include <folly/portability/SysStat.h>
#include <folly/portability/Unistd.h>
#include <cstring>
#include <memory>
namespace facebook::react {
JSBigFileString::JSBigFileString(int fd, size_t size, off_t offset /*= 0*/)
: m_fd{-1}, m_data{nullptr} {
m_fd = dup(fd);
if (m_fd == -1) {
const char* message =
"JSBigFileString::JSBigFileString - Could not duplicate file descriptor";
LOG(ERROR) << message;
throw std::runtime_error(message);
}
// Offsets given to mmap must be page aligned. We abstract away that
// restriction by sending a page aligned offset to mmap, and keeping track
// of the offset within the page that we must alter the mmap pointer by to
// get the final desired offset.
if (offset != 0) {
const static auto ps = sysconf(_SC_PAGESIZE);
auto d = lldiv(offset, ps);
m_mapOff = static_cast<off_t>(d.quot) * ps;
m_pageOff = static_cast<off_t>(d.rem);
m_size = size + m_pageOff;
} else {
m_mapOff = 0;
m_pageOff = 0;
m_size = size;
}
}
JSBigFileString::~JSBigFileString() {
if (m_data != nullptr) {
munmap((void*)m_data, m_size);
}
folly::fileops::close(m_fd);
}
const char* JSBigFileString::c_str() const {
if (m_size == 0) {
return "";
}
if (m_data == nullptr) {
m_data = (const char*)mmap(
nullptr, m_size, PROT_READ, MAP_PRIVATE, m_fd, m_mapOff);
CHECK(m_data != MAP_FAILED)
<< " fd: " << m_fd << " size: " << m_size << " offset: " << m_mapOff
<< " error: " << std::strerror(errno);
}
static const size_t kMinPageSize = 4096;
CHECK(!(reinterpret_cast<uintptr_t>(m_data) & (kMinPageSize - 1)))
<< "mmap address misaligned, likely corrupted"
<< " m_data: " << (const void*)m_data;
CHECK(m_pageOff <= m_size)
<< "offset impossibly large, likely corrupted"
<< " m_pageOff: " << m_pageOff << " m_size: " << m_size;
return m_data + m_pageOff;
}
size_t JSBigFileString::size() const {
// Ensure mapping has been initialized.
c_str();
return m_size - m_pageOff;
}
int JSBigFileString::fd() const {
return m_fd;
}
std::unique_ptr<const JSBigFileString> JSBigFileString::fromPath(
const std::string& sourceURL) {
int fd = folly::fileops::open(sourceURL.c_str(), O_RDONLY);
if (fd == -1) {
const std::string message =
std::string("JSBigFileString::fromPath - Could not open file: ") +
sourceURL;
LOG(ERROR) << message;
throw std::runtime_error(message.c_str());
}
struct stat fileInfo{};
int res = ::fstat(fd, &fileInfo);
if (res == -1) {
const std::string message =
"JSBigFileString::fromPath - fstat on bundle failed: " + sourceURL;
LOG(ERROR) << message;
folly::fileops::close(fd);
throw std::runtime_error(message.c_str());
}
auto ptr = std::make_unique<const JSBigFileString>(fd, fileInfo.st_size);
CHECK(folly::fileops::close(fd) == 0);
return ptr;
}
} // namespace facebook::react

View File

@@ -0,0 +1,144 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <string>
#ifndef RN_EXPORT
#ifdef _MSC_VER
#define RN_EXPORT
#else
#define RN_EXPORT __attribute__((visibility("default")))
#endif
#endif
namespace facebook::react {
// JSExecutor functions sometimes take large strings, on the order of
// megabytes. Copying these can be expensive. Introducing a
// move-only, non-CopyConstructible type will let the compiler ensure
// that no copies occur. folly::MoveWrapper should be used when a
// large string needs to be curried into a std::function<>, which must
// by CopyConstructible.
class JSBigString {
public:
JSBigString() = default;
// Not copyable
JSBigString(const JSBigString &) = delete;
JSBigString &operator=(const JSBigString &) = delete;
virtual ~JSBigString() = default;
virtual bool isAscii() const = 0;
// This needs to be a \0 terminated string
virtual const char *c_str() const = 0;
// Length of the c_str without the NULL byte.
virtual size_t size() const = 0;
};
// Concrete JSBigString implementation which holds a std::string
// instance.
class JSBigStdString : public JSBigString {
public:
JSBigStdString(std::string str, bool isAscii = false) : m_isAscii(isAscii), m_str(std::move(str)) {}
bool isAscii() const override
{
return m_isAscii;
}
const char *c_str() const override
{
return m_str.c_str();
}
size_t size() const override
{
return m_str.size();
}
private:
bool m_isAscii;
std::string m_str;
};
// Concrete JSBigString implementation which holds a heap-allocated
// buffer, and provides an accessor for writing to it. This can be
// used to construct a JSBigString in place, such as by reading from a
// file.
class RN_EXPORT JSBigBufferString : public JSBigString {
public:
JSBigBufferString(size_t size) : m_data(new char[size + 1]), m_size(size)
{
// Guarantee nul-termination. The caller is responsible for
// filling in the rest of m_data.
m_data[m_size] = '\0';
}
~JSBigBufferString() override
{
delete[] m_data;
}
bool isAscii() const override
{
return true;
}
const char *c_str() const override
{
return m_data;
}
size_t size() const override
{
return m_size;
}
char *data()
{
return m_data;
}
private:
char *m_data;
size_t m_size;
};
// JSBigString interface implemented by a file-backed mmap region.
class RN_EXPORT JSBigFileString : public JSBigString {
public:
JSBigFileString(int fd, size_t size, off_t offset = 0);
~JSBigFileString() override;
bool isAscii() const override
{
return true;
}
const char *c_str() const override;
size_t size() const override;
int fd() const;
static std::unique_ptr<const JSBigFileString> fromPath(const std::string &sourceURL);
private:
int m_fd; // The file descriptor being mmapped
size_t m_size; // The size of the mmapped region
mutable off_t m_pageOff; // The offset in the mmapped region to the data.
off_t m_mapOff; // The offset in the file to the mmapped region.
mutable const char *m_data; // Pointer to the mmapped region.
};
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
#include "JSBundleType.h"
namespace facebook::react {
static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5;
// "Hermes" in ancient Greek encoded in UTF-16BE and truncated to 8 bytes.
static uint64_t constexpr HermesBCBundleMagicNumber = 0x1F1903C103BC1FC6;
ScriptTag parseTypeFromHeader(const BundleHeader& header) {
switch (header.magic32.value) {
case RAMBundleMagicNumber:
return ScriptTag::RAMBundle;
default:
return ScriptTag::String;
}
}
const char* stringForScriptTag(const ScriptTag& tag) {
switch (tag) {
case ScriptTag::String:
return "String";
case ScriptTag::RAMBundle:
return "RAM Bundle";
}
return "";
}
bool isHermesBytecodeBundle(const BundleHeader& header) {
return header.magic64 == HermesBCBundleMagicNumber;
}
} // namespace facebook::react

View File

@@ -0,0 +1,74 @@
/*
* 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.
*/
#pragma once
#include <folly/Portability.h>
#include <cstdint>
#include <cstring>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
/*
* Scripts given to the JS Executors to run could be in any of the following
* formats. They are tagged so the executor knows how to run them.
* Hermes bytecode bundles (as encoded by hermesc, not metro) are not treated
* in a special way, they will be identified as ScriptTag::String.
*/
enum struct ScriptTag {
String = 0,
RAMBundle,
};
/**
* RAM bundles and BC bundles begin with headers. For RAM bundles this is
* 4 bytes, for BC bundles this is 12 bytes. This structure holds the first 12
* bytes from a bundle in a way that gives access to that information.
*/
FOLLY_PACK_PUSH
struct FOLLY_PACK_ATTR Magic32 {
uint32_t value;
uint32_t reserved_;
};
struct FOLLY_PACK_ATTR BundleHeader {
BundleHeader()
{
std::memset(this, 0, sizeof(BundleHeader));
}
union {
Magic32 magic32;
uint64_t magic64;
};
uint32_t version;
};
FOLLY_PACK_POP
/**
* Takes the first 8 bytes of a bundle, and returns a tag describing the
* bundle's format.
*/
RN_EXPORT ScriptTag parseTypeFromHeader(const BundleHeader &header);
/**
* Convert an `ScriptTag` enum into a string, useful for emitting in errors
* and diagnostic messages.
*/
RN_EXPORT const char *stringForScriptTag(const ScriptTag &tag);
/**
* Check whether a given bundle is hermesc-generated bytecode
*/
RN_EXPORT bool isHermesBytecodeBundle(const BundleHeader &header);
} // namespace facebook::react

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
#include "JSExecutor.h"
#include "RAMBundleRegistry.h"
#include <jsinspector-modern/ReactCdp.h>
#include <array>
namespace facebook::react {
std::string JSExecutor::getSyntheticBundlePath(
uint32_t bundleId,
const std::string& bundlePath) {
#ifndef RCT_REMOVE_LEGACY_ARCH
if (bundleId == RAMBundleRegistry::MAIN_BUNDLE_ID) {
return bundlePath;
}
#endif // RCT_REMOVE_LEGACY_ARCH
std::array<char, 32> buffer{};
std::snprintf(buffer.data(), buffer.size(), "seg-%u.js", bundleId);
return buffer.data();
}
jsinspector_modern::RuntimeTargetDelegate&
JSExecutor::getRuntimeTargetDelegate() {
if (!runtimeTargetDelegate_) {
runtimeTargetDelegate_.emplace(getDescription());
}
return *runtimeTargetDelegate_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,145 @@
/*
* 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.
*/
#pragma once
#include <memory>
#include <string>
#include <cxxreact/NativeModule.h>
#include <folly/dynamic.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/ReactCdp.h>
#include <react/timing/primitives.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class JSBigString;
class JSExecutor;
class JSModulesUnbundle;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This interface describes the delegate interface required by
// Executor implementations to call from JS into native code.
class [[deprecated("This API will be removed along with the legacy architecture.")]] ExecutorDelegate {
public:
virtual ~ExecutorDelegate() = default;
virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;
virtual void callNativeModules(JSExecutor &executor, folly::dynamic &&calls, bool isEndOfBatch) = 0;
virtual MethodCallResult callSerializableNativeHook(
JSExecutor &executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic &&args) = 0;
};
class [[deprecated("This API will be removed along with the legacy architecture.")]] JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() = default;
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] JSExecutor {
public:
/**
* Prepares the JS runtime for React Native by installing global variables.
* Called once before any JS is evaluated.
*/
virtual void initializeRuntime() = 0;
/**
* Execute an application script bundle in the JS context.
*/
virtual void loadBundle(std::unique_ptr<const JSBigString> script, std::string sourceURL) = 0;
#ifndef RCT_REMOVE_LEGACY_ARCH
/**
* Add an application "RAM" bundle registry
*/
virtual void setBundleRegistry(std::unique_ptr<RAMBundleRegistry> bundleRegistry) = 0;
#endif // RCT_REMOVE_LEGACY_ARCH
/**
* Register a file path for an additional "RAM" bundle
*/
virtual void registerBundle(uint32_t bundleId, const std::string &bundlePath) = 0;
/**
* Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
* method ID and optional additional arguments in JS. The executor is
* responsible for using Bridge->callNativeModules to invoke any necessary
* native modules methods.
*/
virtual void
callFunction(const std::string &moduleId, const std::string &methodId, const folly::dynamic &arguments) = 0;
/**
* Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
* and optional additional arguments in JS and returns the next queue. The
* executor is responsible for using Bridge->callNativeModules to invoke any
* necessary native modules methods.
*/
virtual void invokeCallback(double callbackId, const folly::dynamic &arguments) = 0;
virtual void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) = 0;
virtual void *getJavaScriptContext()
{
return nullptr;
}
/**
* Returns whether or not the underlying executor supports debugging via the
* Chrome remote debugging protocol. If true, the executor should also
* override the \c createAgentDelegate method.
*/
virtual bool isInspectable()
{
return false;
}
/**
* The description is displayed in the dev menu, if there is one in
* this build. There is a default, but if this method returns a
* non-empty string, it will be used instead.
*/
virtual std::string getDescription() = 0;
virtual void handleMemoryPressure([[maybe_unused]] int pressureLevel) {}
virtual void destroy() {}
virtual ~JSExecutor() = default;
virtual void flush() {}
static std::string getSyntheticBundlePath(uint32_t bundleId, const std::string &bundlePath);
/**
* Get a reference to the \c RuntimeTargetDelegate owned (or implemented) by
* this executor. This reference must remain valid for the duration of the
* executor's lifetime.
*/
virtual jsinspector_modern::RuntimeTargetDelegate &getRuntimeTargetDelegate();
private:
/**
* Initialized by \c getRuntimeTargetDelegate if not overridden, and then
* never changes.
*/
std::optional<jsinspector_modern::FallbackRuntimeTargetDelegate> runtimeTargetDelegate_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,136 @@
/*
* 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.
*/
#include "JSIndexedRAMBundle.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <glog/logging.h>
#include <fstream>
#include <memory>
#include <sstream>
#include <folly/lang/Bits.h>
#include <glog/logging.h>
namespace facebook::react {
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)>
JSIndexedRAMBundle::buildFactory() {
return [](const std::string& bundlePath) {
return std::make_unique<JSIndexedRAMBundle>(bundlePath.c_str());
};
}
JSIndexedRAMBundle::JSIndexedRAMBundle(const char* sourcePath) {
m_bundle = std::make_unique<std::ifstream>(sourcePath, std::ifstream::binary);
if (!m_bundle) {
throw std::ios_base::failure(
std::string("Bundle ") + sourcePath +
"cannot be opened: " + std::to_string(m_bundle->rdstate()));
}
init();
}
JSIndexedRAMBundle::JSIndexedRAMBundle(
std::unique_ptr<const JSBigString> script) {
// tmpStream is needed because m_bundle is std::istream type
// which has no member 'write'
std::unique_ptr<std::stringstream> tmpStream =
std::make_unique<std::stringstream>();
tmpStream->write(script->c_str(), script->size());
m_bundle = std::move(tmpStream);
if (!m_bundle) {
throw std::ios_base::failure(
"Bundle from string cannot be opened: " +
std::to_string(m_bundle->rdstate()));
}
init();
}
void JSIndexedRAMBundle::init() {
// read in magic header, number of entries, and length of the startup section
uint32_t header[3];
static_assert(
sizeof(header) == 12,
"header size must exactly match the input file format");
readBundle(reinterpret_cast<char*>(header), sizeof(header));
const size_t numTableEntries = folly::Endian::little(header[1]);
const size_t startupCodeSize = folly::Endian::little(header[2]);
// allocate memory for meta data and lookup table.
m_table = ModuleTable(numTableEntries);
m_baseOffset = sizeof(header) + m_table.byteLength();
// read the lookup table from the file
readBundle(reinterpret_cast<char*>(m_table.data.get()), m_table.byteLength());
// read the startup code
m_startupCode = std::make_unique<JSBigBufferString>(startupCodeSize - 1);
readBundle(m_startupCode->data(), startupCodeSize - 1);
}
JSIndexedRAMBundle::Module JSIndexedRAMBundle::getModule(
uint32_t moduleId) const {
Module ret;
ret.name = std::to_string(moduleId) + ".js";
ret.code = getModuleCode(moduleId);
return ret;
}
std::unique_ptr<const JSBigString> JSIndexedRAMBundle::getStartupCode() {
CHECK(m_startupCode)
<< "startup code for a RAM Bundle can only be retrieved once";
return std::move(m_startupCode);
}
std::string JSIndexedRAMBundle::getModuleCode(const uint32_t id) const {
const auto moduleData = id < m_table.numEntries ? &m_table.data[id] : nullptr;
// entries without associated code have offset = 0 and length = 0
const uint32_t length =
moduleData != nullptr ? folly::Endian::little(moduleData->length) : 0;
if (length == 0) {
throw std::ios_base::failure(
"Error loading module" + std::to_string(id) + "from RAM Bundle");
}
std::string ret(length - 1, '\0');
readBundle(
&ret.front(),
length - 1,
m_baseOffset + folly::Endian::little(moduleData->offset));
return ret;
}
void JSIndexedRAMBundle::readBundle(char* buffer, const std::streamsize bytes)
const {
if (!m_bundle->read(buffer, bytes)) {
if ((m_bundle->rdstate() & std::ios::eofbit) != 0) {
throw std::ios_base::failure("Unexpected end of RAM Bundle file");
}
throw std::ios_base::failure(
"Error reading RAM Bundle: " + std::to_string(m_bundle->rdstate()));
}
}
void JSIndexedRAMBundle::readBundle(
char* buffer,
const std::streamsize bytes,
const std::ifstream::pos_type position) const {
if (!m_bundle->seekg(position)) {
throw std::ios_base::failure(
"Error reading RAM Bundle: " + std::to_string(m_bundle->rdstate()));
}
readBundle(buffer, bytes);
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,72 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <functional>
#include <istream>
#include <memory>
#include <string>
#include <cxxreact/JSBigString.h>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] JSIndexedRAMBundle
: public JSModulesUnbundle {
public:
static std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> buildFactory();
// Throws std::runtime_error on failure.
explicit JSIndexedRAMBundle(const char *sourcePath);
JSIndexedRAMBundle(std::unique_ptr<const JSBigString> script);
// Throws std::runtime_error on failure.
std::unique_ptr<const JSBigString> getStartupCode();
// Throws std::runtime_error on failure.
Module getModule(uint32_t moduleId) const override;
private:
struct ModuleData {
uint32_t offset;
uint32_t length;
};
static_assert(sizeof(ModuleData) == 8, "ModuleData must not have any padding and use sizes matching input files");
struct ModuleTable {
size_t numEntries;
std::unique_ptr<ModuleData[]> data;
ModuleTable() : numEntries(0) {};
ModuleTable(size_t entries)
: numEntries(entries), data(std::unique_ptr<ModuleData[]>(new ModuleData[numEntries])) {};
size_t byteLength() const
{
return numEntries * sizeof(ModuleData);
}
};
void init();
std::string getModuleCode(uint32_t id) const;
void readBundle(char *buffer, std::streamsize bytes) const;
void readBundle(char *buffer, std::streamsize bytes, std::istream::pos_type position) const;
mutable std::unique_ptr<std::istream> m_bundle;
ModuleTable m_table;
size_t m_baseOffset{};
std::unique_ptr<JSBigBufferString> m_startupCode;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cstdint>
#include <stdexcept>
#include <string>
namespace facebook::react {
class [[deprecated("This API will be removed along with the legacy architecture.")]] JSModulesUnbundle {
/**
* Represents the set of JavaScript modules that the application consists of.
* The source code of each module can be retrieved by module ID.
*
* The class is non-copyable because copying instances might involve copying
* several megabytes of memory.
*/
public:
class ModuleNotFound : public std::out_of_range {
public:
using std::out_of_range::out_of_range;
explicit ModuleNotFound(uint32_t moduleId)
: std::out_of_range::out_of_range("Module not found: " + std::to_string(moduleId))
{
}
};
struct Module {
std::string name;
std::string code;
};
JSModulesUnbundle() {}
virtual ~JSModulesUnbundle() = default;
virtual Module getModule(uint32_t moduleId) const = 0;
private:
JSModulesUnbundle(const JSModulesUnbundle &) = delete;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,119 @@
/*
* 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.
*/
#pragma once
#include <folly/dynamic.h>
namespace facebook {
namespace xplat {
namespace detail {
inline std::string toStringHelper()
{
return "";
}
template <typename T, typename... Rest>
inline std::string toStringHelper(const T &value, const Rest &...rest)
{
return std::to_string(value) + toStringHelper(rest...);
}
template <typename... Rest>
inline std::string toStringHelper(const char *value, const Rest &...rest)
{
return std::string(value) + toStringHelper(rest...);
}
template <typename R, typename M, typename... T>
R jsArg1(const folly::dynamic &arg, M asFoo, const T &...desc)
{
try {
return (arg.*asFoo)();
} catch (const folly::TypeError &ex) {
throw JsArgumentException("Error converting JavaScript arg " + toStringHelper(desc...) + " to C++: " + ex.what());
} catch (const std::range_error &ex) {
throw JsArgumentException(
"Could not convert argument " + toStringHelper(desc...) + " to required type: " + ex.what());
}
}
} // namespace detail
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const, const T &...desc)
{
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const &, const T &...desc)
{
return detail::jsArg1<R>(arg, asFoo, desc...);
}
template <typename T>
// NOLINTNEXTLINE (T62192316)
typename detail::is_dynamic<T>::type &jsArgAsDynamic(T &&args, size_t n)
{
try {
return args[n];
} catch (const std::out_of_range &ex) {
// Use 1-base counting for argument description.
throw JsArgumentException(
"JavaScript provided " + std::to_string(args.size()) + " arguments for C++ method which references at least " +
std::to_string(n + 1) + " arguments: " + ex.what());
}
}
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const)
{
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const &)
{
return jsArg(jsArgAsDynamic(args, n), asFoo, n);
}
namespace detail {
// This is a helper for jsArgAsArray and jsArgAsObject.
template <typename T>
typename detail::is_dynamic<T>::type &
jsArgAsType(T &&args, size_t n, const char *required, bool (folly::dynamic::*isFoo)() const)
{
T &ret = jsArgAsDynamic(args, n);
if ((ret.*isFoo)()) {
return ret;
}
// Use 1-base counting for argument description.
throw JsArgumentException(
"Argument " + std::to_string(n + 1) + " of type " + ret.typeName() + " is not required type " + required);
}
} // end namespace detail
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsArray(T &&args, size_t n)
{
return detail::jsArgAsType(args, n, "Array", &folly::dynamic::isArray);
}
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsObject(T &&args, size_t n)
{
return detail::jsArgAsType(args, n, "Object", &folly::dynamic::isObject);
}
} // namespace xplat
} // namespace facebook

View File

@@ -0,0 +1,113 @@
/*
* 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.
*/
#pragma once
#include <exception>
#include <string>
#include <folly/dynamic.h>
// When building a cross-platform module for React Native, arguments passed
// from JS are represented as a folly::dynamic. This class provides helpers to
// extract arguments from the folly::dynamic to concrete types usable by
// cross-platform code, and converting exceptions to a JsArgumentException so
// they can be caught and reported to RN consistently. The goal is to make the
// jsArgAs... methods at the end simple to use should be most common, but any
// non-detail method can be used when needed.
namespace facebook::xplat {
class JsArgumentException : public std::logic_error {
public:
JsArgumentException(const std::string &msg) : std::logic_error(msg) {}
};
// This extracts a single argument by calling the given method pointer on it.
// If an exception is thrown, the additional arguments are passed to
// std::to_string to be included in the exception string. This will be most
// commonly used when extracting values from non-scalar argument. The second
// overload accepts ref-qualified member functions.
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const, const T &...desc);
template <typename R, typename... T>
R jsArg(const folly::dynamic &arg, R (folly::dynamic::*asFoo)() const &, const T &...desc);
// This is like jsArg, but a operates on a dynamic representing an array of
// arguments. The argument n is used both to index the array and build the
// exception message, if any. It can be used directly, but will more often be
// used by the type-specific methods following.
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const);
template <typename R>
R jsArgN(const folly::dynamic &args, size_t n, R (folly::dynamic::*asFoo)() const &);
namespace detail {
// This is a type helper to implement functions which should work on both const
// and non-const folly::dynamic arguments, and return a type with the same
// constness. Basically, it causes the templates which use it to be defined
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
using type = typename std::enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type;
};
} // end namespace detail
// Easy to use conversion helpers are here:
// Extract the n'th arg from the given dynamic, as a dynamic. Throws a
// JsArgumentException if there is no n'th arg in the input.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsDynamic(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Array. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Array.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsArray(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a dynamic Object. Throws a
// JsArgumentException if there is no n'th arg in the input, or it is not an
// Object.
template <typename T>
typename detail::is_dynamic<T>::type &jsArgAsObject(T &&args, size_t n);
// Extract the n'th arg from the given dynamic, as a bool. Throws a
// JsArgumentException if this fails for some reason.
inline bool jsArgAsBool(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asBool);
}
// Extract the n'th arg from the given dynamic, as an integer. Throws a
// JsArgumentException if this fails for some reason.
inline int64_t jsArgAsInt(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asInt);
}
// Extract the n'th arg from the given dynamic, as a double. Throws a
// JsArgumentException if this fails for some reason.
inline double jsArgAsDouble(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asDouble);
}
// Extract the n'th arg from the given dynamic, as a string. Throws a
// JsArgumentException if this fails for some reason.
inline std::string jsArgAsString(const folly::dynamic &args, size_t n)
{
return jsArgN(args, n, &folly::dynamic::asString);
}
} // namespace facebook::xplat
#include <cxxreact/JsArgumentHelpers-inl.h>

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include <functional>
namespace facebook::react {
class MessageQueueThread {
public:
virtual ~MessageQueueThread() = default;
virtual void runOnQueue(std::function<void()> &&) = 0;
// runOnQueueSync and quitSynchronous are dangerous. They should only be
// used for initialization and cleanup.
virtual void runOnQueueSync(std::function<void()> &&) = 0;
// Once quitSynchronous() returns, no further work should run on the queue.
virtual void quitSynchronous() = 0;
};
} // namespace facebook::react

View File

@@ -0,0 +1,91 @@
/*
* 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.
*/
#include "MethodCall.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <folly/json.h>
#include <stdexcept>
namespace facebook::react {
#define REQUEST_MODULE_IDS 0
#define REQUEST_METHOD_IDS 1
#define REQUEST_PARAMS 2
#define REQUEST_CALLID 3
static const char* errorPrefix = "Malformed calls from JS: ";
std::vector<MethodCall> parseMethodCalls(folly::dynamic&& jsonData) {
if (jsonData.isNull()) {
return {};
}
if (!jsonData.isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + " input isn't array but " +
jsonData.typeName());
}
if (jsonData.size() < REQUEST_PARAMS + 1) {
throw std::invalid_argument(
std::string(errorPrefix) +
"size == " + std::to_string(jsonData.size()));
}
auto& moduleIds = jsonData[REQUEST_MODULE_IDS];
auto& methodIds = jsonData[REQUEST_METHOD_IDS];
auto& params = jsonData[REQUEST_PARAMS];
int callId = -1;
if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + "not all fields are arrays.\n\n" +
folly::toJson(jsonData));
}
if (moduleIds.size() != methodIds.size() ||
moduleIds.size() != params.size()) {
throw std::invalid_argument(
std::string(errorPrefix) + "field sizes are different.\n\n" +
folly::toJson(jsonData));
}
if (jsonData.size() > REQUEST_CALLID) {
if (!jsonData[REQUEST_CALLID].isNumber()) {
throw std::invalid_argument(
std::string(errorPrefix) + "invalid callId" +
jsonData[REQUEST_CALLID].typeName());
}
callId = (int)jsonData[REQUEST_CALLID].asInt();
}
std::vector<MethodCall> methodCalls;
for (size_t i = 0; i < moduleIds.size(); i++) {
if (!params[i].isArray()) {
throw std::invalid_argument(
std::string(errorPrefix) + "method arguments isn't array but " +
params[i].typeName());
}
methodCalls.emplace_back(
static_cast<int>(moduleIds[i].asInt()),
static_cast<int>(methodIds[i].asInt()),
std::move(params[i]),
callId);
// only increment callid if contains valid callid as callid is optional
callId += (callId != -1) ? 1 : 0;
}
return methodCalls;
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <map>
#include <string>
#include <vector>
#include <folly/dynamic.h>
namespace facebook::react {
struct [[deprecated("This API will be removed along with the legacy architecture.")]] MethodCall {
int moduleId;
int methodId;
folly::dynamic arguments;
int callId;
MethodCall(int mod, int meth, folly::dynamic &&args, int cid)
: moduleId(mod), methodId(meth), arguments(std::move(args)), callId(cid)
{
}
};
/// \throws std::invalid_argument
[[deprecated("This API will be removed along with the legacy architecture.")]]
std::vector<MethodCall> parseMethodCalls(folly::dynamic &&jsonData);
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,247 @@
/*
* 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.
*/
#include "ModuleRegistry.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <glog/logging.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include <utility>
#include "NativeModule.h"
#include "TraceSection.h"
namespace facebook::react {
namespace {
std::string normalizeName(std::string name) {
// TODO mhorowitz #10487027: This is super ugly. We should just
// change iOS to emit normalized names, drop the "RK..." from
// names hardcoded in Android, and then delete this and the
// similar hacks in js.
if (name.compare(0, 3, "RCT") == 0) {
return name.substr(3);
} else if (name.compare(0, 2, "RK") == 0) {
return name.substr(2);
}
return name;
}
} // namespace
ModuleRegistry::ModuleRegistry(
std::vector<std::unique_ptr<NativeModule>> modules,
ModuleNotFoundCallback callback)
: modules_{std::move(modules)},
moduleNotFoundCallback_{std::move(callback)} {}
void ModuleRegistry::updateModuleNamesFromIndex(size_t index) {
for (; index < modules_.size(); index++) {
std::string name = normalizeName(modules_[index]->getName());
modulesByName_[name] = index;
}
}
void ModuleRegistry::registerModules(
std::vector<std::unique_ptr<NativeModule>> modules) {
TraceSection s_("ModuleRegistry::registerModules");
// Noop if there are no NativeModules to add
if (modules.empty()) {
return;
}
if (modules_.empty() && unknownModules_.empty()) {
modules_ = std::move(modules);
} else {
size_t modulesSize = modules_.size();
size_t addModulesSize = modules.size();
bool addToNames = !modulesByName_.empty();
modules_.reserve(modulesSize + addModulesSize);
std::move(modules.begin(), modules.end(), std::back_inserter(modules_));
if (!unknownModules_.empty()) {
for (size_t index = modulesSize; index < modulesSize + addModulesSize;
index++) {
std::string name = normalizeName(modules_[index]->getName());
auto it = unknownModules_.find(name);
if (it != unknownModules_.end()) {
throw std::runtime_error(
"module " + name +
" was required without being registered and is now being registered.");
} else if (addToNames) {
modulesByName_[name] = index;
}
}
} else if (addToNames) {
updateModuleNamesFromIndex(modulesSize);
}
}
}
std::vector<std::string> ModuleRegistry::moduleNames() {
TraceSection s_("ModuleRegistry::moduleNames");
std::vector<std::string> names;
for (size_t i = 0; i < modules_.size(); i++) {
std::string name = normalizeName(modules_[i]->getName());
modulesByName_[name] = i;
names.push_back(std::move(name));
}
return names;
}
std::optional<ModuleConfig> ModuleRegistry::getConfig(const std::string& name) {
TraceSection s("ModuleRegistry::getConfig", "module", name);
// Initialize modulesByName_
if (modulesByName_.empty() && !modules_.empty()) {
moduleNames();
}
auto it = modulesByName_.find(name);
if (it == modulesByName_.end()) {
if (unknownModules_.find(name) != unknownModules_.end()) {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
if (!moduleNotFoundCallback_) {
unknownModules_.insert(name);
BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
return std::nullopt;
}
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
bool wasModuleLazilyLoaded = moduleNotFoundCallback_(name);
it = modulesByName_.find(name);
bool wasModuleRegisteredWithRegistry =
wasModuleLazilyLoaded && it != modulesByName_.end();
if (!wasModuleRegisteredWithRegistry) {
BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
unknownModules_.insert(name);
return std::nullopt;
}
} else {
BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
}
// If we've gotten this far, then we've signaled moduleJSRequireBeginningEnd
size_t index = it->second;
CHECK(index < modules_.size());
NativeModule* module = modules_[index].get();
// string name, object constants, array methodNames (methodId is index),
// [array promiseMethodIds], [array syncMethodIds]
folly::dynamic config = folly::dynamic::array(name);
{
TraceSection s_("ModuleRegistry::getConstants", "module", name);
/**
* In the case that there are constants, we'll initialize the NativeModule,
* and signal moduleJSRequireEndingStart. Otherwise, we'll simply signal the
* event. The Module will be initialized when we invoke one of its
* NativeModule methods.
*/
config.push_back(module->getConstants());
}
{
TraceSection s_("ModuleRegistry::getMethods", "module", name);
std::vector<MethodDescriptor> methods = module->getMethods();
folly::dynamic methodNames = folly::dynamic::array;
folly::dynamic promiseMethodIds = folly::dynamic::array;
folly::dynamic syncMethodIds = folly::dynamic::array;
for (auto& descriptor : methods) {
// TODO: #10487027 compare tags instead of doing string comparison?
methodNames.push_back(std::move(descriptor.name));
if (descriptor.type == "promise") {
promiseMethodIds.push_back(methodNames.size() - 1);
} else if (descriptor.type == "sync") {
syncMethodIds.push_back(methodNames.size() - 1);
}
}
if (!methodNames.empty()) {
config.push_back(std::move(methodNames));
if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
config.push_back(std::move(promiseMethodIds));
if (!syncMethodIds.empty()) {
config.push_back(std::move(syncMethodIds));
}
}
}
}
if (config.size() == 2 && config[1].empty()) {
// no constants or methods
return std::nullopt;
} else {
return ModuleConfig{.index = index, .config = std::move(config)};
}
}
std::string ModuleRegistry::getModuleName(unsigned int moduleId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->getName();
}
std::string ModuleRegistry::getModuleSyncMethodName(
unsigned int moduleId,
unsigned int methodId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->getSyncMethodName(methodId);
}
void ModuleRegistry::callNativeMethod(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params,
int callId) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
modules_[moduleId]->invoke(methodId, std::move(params), callId);
}
MethodCallResult ModuleRegistry::callSerializableNativeHook(
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& params) {
if (moduleId >= modules_.size()) {
throw std::runtime_error(
"moduleId " + std::to_string(moduleId) + " out of range [0.." +
std::to_string(modules_.size()) + ")");
}
return modules_[moduleId]->callSerializableNativeHook(
methodId, std::move(params));
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,83 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <memory>
#include <unordered_set>
#include <vector>
#include <cxxreact/JSExecutor.h>
#include <folly/dynamic.h>
#include <optional>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class NativeModule;
struct [[deprecated("This API will be removed along with the legacy architecture.")]] ModuleConfig {
size_t index;
folly::dynamic config;
};
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] ModuleRegistry {
public:
// not implemented:
// onBatchComplete: see
// https://our.intern.facebook.com/intern/tasks/?t=5279396 getModule: only
// used by views getAllModules: only used for cleanup; use RAII instead
// notifyCatalystInstanceInitialized: this is really only used by view-related
// code notifyCatalystInstanceDestroy: use RAII instead
using ModuleNotFoundCallback = std::function<bool(const std::string &name)>;
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules, ModuleNotFoundCallback callback = nullptr);
void registerModules(std::vector<std::unique_ptr<NativeModule>> modules);
std::vector<std::string> moduleNames();
std::optional<ModuleConfig> getConfig(const std::string &name);
void callNativeMethod(unsigned int moduleId, unsigned int methodId, folly::dynamic &&params, int callId);
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, folly::dynamic &&params);
std::string getModuleName(unsigned int moduleId);
std::string getModuleSyncMethodName(unsigned int moduleId, unsigned int methodId);
private:
// This is always populated
std::vector<std::unique_ptr<NativeModule>> modules_;
// This is used to extend the population of modulesByName_ if registerModules
// is called after moduleNames
void updateModuleNamesFromIndex(size_t index);
// This is only populated if moduleNames() is called. Values are indices into
// modules_.
std::unordered_map<std::string, size_t> modulesByName_;
// This is populated with modules that are requested via getConfig but are
// unknown. An error will be thrown if they are subsequently added to the
// registry.
std::unordered_set<std::string> unknownModules_;
// Function will be called if a module was requested but was not found.
// If the function returns true, ModuleRegistry will try to find the module
// again (assuming it's registered) If the function returns false,
// ModuleRegistry will not try to find the module and return nullptr instead.
ModuleNotFoundCallback moduleNotFoundCallback_;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,131 @@
/*
* 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.
*/
#pragma once
#include <memory>
namespace facebook::react {
/*
NOTE: we keep this internal copy of folly/MoveWrapper.h to unblock
the the workstream of dropping the dependency on folly in RN!
For a technical explanation on why we still need this we defer
to the doc in folly/Function.h:
"There are some limitations in std::function that folly::Function tries to
avoid. std::function is copy-constructible and requires that the callable that
it wraps is copy-constructible as well, which is a constraint that is often
inconvenient. In most cases when using a std::function you don't make use of
its copy-constructibility, so you might sometimes feel like you get back very
little in return for a noticeable restriction. This restriction becomes
apparent when trying to use a lambda capturing a unique_ptr (or any
non-copyable type) as a callback for a folly::Future.
std::unique_ptr<Foo> foo_ptr = new Foo;
some_future.then(
[foo_ptr = std::move(foo_ptr)] mutable
(int x)
{ foo_ptr->setX(x); }
);
This piece of code did not compile before folly::Future started using
folly::Function instead of std::function to store the callback. Because the
lambda captures something non-copyable (the unique_ptr), it is not copyable
itself. And std::function can only store copyable callables.
The implementation of folly::Future did not make use of the
copy-constructibility of std::function at any point. There was no benefit from
the fact that the std::function is copy-constructible, but the fact that it can
only wrap copy-constructible callables posed a restriction.
A workaround was available: folly::MoveWrapper, which wraps an object that may
be non-copyable and implements copy operations by moving the embedded object.
Using a folly::MoveWrapper, you can capture non-copyable objects in a lambda,
and the lambda itself is still copyable and may be wrapped in a std::function.
It is a pragmatic solution for the above problem, but you have to be a little
careful. The problem is that you cant use a MoveWrapper anywhere where copy
operations are assumed to behave like actual copy operations. Also, a
folly::MoveWrapper<std::unique_ptr<T>> essentially behaves like auto_ptr<T>. Ask
yourself whether youd want to use lots of auto_ptrs in your codebase. And the
original question still persists: we very often dont benefit from
copy-constructibility of std::function, so why do we have to live with this
restriction? I.e. why do we have to use MoveWrapper?"
*/
/** C++11 closures don't support move-in capture. Nor does std::bind.
facepalm.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3610.html
"[...] a work-around that should make people's stomach crawl:
write a wrapper that performs move-on-copy, much like the deprecated
auto_ptr"
Unlike auto_ptr, this doesn't require a heap allocation.
*/
template <class T>
class MoveWrapper {
public:
/** If value can be default-constructed, why not?
Then we don't have to move it in */
MoveWrapper() = default;
/// Move a value in.
explicit MoveWrapper(T &&t) : value(std::move(t)) {}
/// copy is move
MoveWrapper(const MoveWrapper &other) : value(std::move(other.value)) {}
/// move is also move
MoveWrapper(MoveWrapper &&other) noexcept : value(std::move(other.value)) {}
const T &operator*() const
{
return value;
}
T &operator*()
{
return value;
}
const T *operator->() const
{
return &value;
}
T *operator->()
{
return &value;
}
/// move the value out (sugar for std::move(*moveWrapper))
T &&move()
{
return std::move(value);
}
// If you want these you're probably doing it wrong, though they'd be
// easy enough to implement
MoveWrapper &operator=(const MoveWrapper &) = delete;
MoveWrapper &operator=(MoveWrapper &&) = delete;
private:
mutable T value;
};
/// Make a MoveWrapper from the argument. Because the name "makeMoveWrapper"
/// is already quite transparent in its intent, this will work for lvalues as
/// if you had wrapped them in std::move.
template <class T, class T0 = typename std::remove_reference<T>::type>
MoveWrapper<T0> makeMoveWrapper(T &&t)
{
return MoveWrapper<T0>(std::forward<T0>(t));
}
} // namespace facebook::react

View File

@@ -0,0 +1,43 @@
/*
* 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.
*/
#pragma once
#include <string>
#include <vector>
#include <folly/dynamic.h>
#include <optional>
namespace facebook::react {
#ifndef RCT_REMOVE_LEGACY_ARCH
struct [[deprecated("This API will be removed along with the legacy architecture.")]] MethodDescriptor {
std::string name;
// type is one of js MessageQueue.MethodTypes
std::string type;
MethodDescriptor(std::string n, std::string t) : name(std::move(n)), type(std::move(t)) {}
};
#endif // RCT_REMOVE_LEGACY_ARCH
using MethodCallResult = std::optional<folly::dynamic>;
#ifndef RCT_REMOVE_LEGACY_ARCH
class [[deprecated("This API will be removed along with the legacy architecture.")]] NativeModule {
public:
virtual ~NativeModule() = default;
virtual std::string getName() = 0;
virtual std::string getSyncMethodName(unsigned int methodId) = 0;
virtual std::vector<MethodDescriptor> getMethods() = 0;
virtual folly::dynamic getConstants() = 0;
virtual void invoke(unsigned int reactMethodId, folly::dynamic &&params, int callId) = 0;
virtual MethodCallResult callSerializableNativeHook(unsigned int reactMethodId, folly::dynamic &&args) = 0;
};
#endif // RCT_REMOVE_LEGACY_ARCH
} // namespace facebook::react

View File

@@ -0,0 +1,354 @@
/*
* 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.
*/
#include "NativeToJsBridge.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <ReactCommon/CallInvoker.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <jsi/jsi.h>
#include <reactperflogger/BridgeNativeModulePerfLogger.h>
#include "ErrorUtils.h"
#include "Instance.h"
#include "JSBigString.h"
#include "MessageQueueThread.h"
#include "MethodCall.h"
#include "ModuleRegistry.h"
#include "MoveWrapper.h"
#include "RAMBundleRegistry.h"
#include "TraceSection.h"
#include <memory>
#include <utility>
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceAsyncFlow;
#endif
namespace facebook::react {
// This class manages calls from JS to native code.
class [[deprecated(
"This API will be removed along with the legacy architecture.")]]
JsToNativeBridge : public react::ExecutorDelegate {
public:
JsToNativeBridge(
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<InstanceCallback> callback)
: m_registry(std::move(registry)), m_callback(std::move(callback)) {}
std::shared_ptr<ModuleRegistry> getModuleRegistry() override {
return m_registry;
}
bool isBatchActive() {
return m_batchHadNativeModuleOrTurboModuleCalls;
}
void callNativeModules(
[[maybe_unused]] JSExecutor& executor,
folly::dynamic&& calls,
bool isEndOfBatch) override {
CHECK(m_registry || calls.empty())
<< "native module calls cannot be completed with no native modules";
m_batchHadNativeModuleOrTurboModuleCalls =
m_batchHadNativeModuleOrTurboModuleCalls || !calls.empty();
std::vector<MethodCall> methodCalls = parseMethodCalls(std::move(calls));
BridgeNativeModulePerfLogger::asyncMethodCallBatchPreprocessEnd(
(int)methodCalls.size());
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : methodCalls) {
m_registry->callNativeMethod(
call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
// onBatchComplete will be called on the native (module) queue, but
// decrementPendingJSCalls will be called sync. Be aware that the bridge
// may still be processing native calls when the bridge idle signaler
// fires.
if (m_batchHadNativeModuleOrTurboModuleCalls) {
m_callback->onBatchComplete();
m_batchHadNativeModuleOrTurboModuleCalls = false;
}
m_callback->decrementPendingJSCalls();
}
}
MethodCallResult callSerializableNativeHook(
[[maybe_unused]] JSExecutor& executor,
unsigned int moduleId,
unsigned int methodId,
folly::dynamic&& args) override {
return m_registry->callSerializableNativeHook(
moduleId, methodId, std::move(args));
}
void recordTurboModuleAsyncMethodCall() noexcept {
m_batchHadNativeModuleOrTurboModuleCalls = true;
}
private:
// These methods are always invoked from an Executor. The NativeToJsBridge
// keeps a reference to the executor, and when destroy() is called, the
// executor is destroyed synchronously on its queue.
std::shared_ptr<ModuleRegistry> m_registry;
std::shared_ptr<InstanceCallback> m_callback;
std::atomic<bool> m_batchHadNativeModuleOrTurboModuleCalls{false};
};
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false)),
m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
m_executorMessageQueueThread(std::move(jsQueue)),
m_inspectable(m_executor->isInspectable()) {}
// This must be called on the same thread on which the constructor was called.
NativeToJsBridge::~NativeToJsBridge() {
CHECK(*m_destroyed)
<< "NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
}
void NativeToJsBridge::initializeRuntime() {
runOnExecutorQueue(
[](JSExecutor* executor) mutable { executor->initializeRuntime(); });
}
void NativeToJsBridge::loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
[this,
bundleRegistryWrap = makeMoveWrapper(std::move(bundleRegistry)),
startupScript = makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL =
std::move(startupScriptSourceURL)](JSExecutor* executor) mutable {
auto bundleRegistry = bundleRegistryWrap.move();
if (bundleRegistry) {
executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
executor->loadBundle(
std::move(*startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
});
}
void NativeToJsBridge::loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
if (bundleRegistry) {
m_executor->setBundleRegistry(std::move(bundleRegistry));
}
try {
m_executor->loadBundle(
std::move(startupScript), std::move(startupScriptSourceURL));
} catch (...) {
m_applicationScriptHasFailure = true;
throw;
}
}
void NativeToJsBridge::callFunction(
std::string&& module,
std::string&& method,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(TRACE_TAG_REACT, "JSCall", systraceCookie);
#endif
runOnExecutorQueue([this,
module = std::move(module),
method = std::move(method),
arguments = std::move(arguments),
systraceCookie](JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS function on a bad application bundle: "
<< module.c_str() << "." << method.c_str() << "()";
throw std::runtime_error(
"Attempting to call JS function on a bad application bundle: " +
module + "." + method + "()");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(TRACE_TAG_REACT, "JSCall", systraceCookie);
TraceSection s(
"NativeToJsBridge::callFunction", "module", module, "method", method);
#else
(void)(systraceCookie);
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(module, method, arguments);
});
}
void NativeToJsBridge::invokeCallback(
double callbackId,
folly::dynamic&& arguments) {
int systraceCookie = -1;
#ifdef WITH_FBSYSTRACE
systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(TRACE_TAG_REACT, "<callback>", systraceCookie);
#endif
runOnExecutorQueue(
[this, callbackId, arguments = std::move(arguments), systraceCookie](
JSExecutor* executor) {
if (m_applicationScriptHasFailure) {
LOG(ERROR)
<< "Attempting to call JS callback on a bad application bundle: "
<< callbackId;
throw std::runtime_error(
"Attempting to invoke JS callback on a bad application bundle.");
}
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(TRACE_TAG_REACT, "<callback>", systraceCookie);
TraceSection s("NativeToJsBridge::invokeCallback");
#else
(void)(systraceCookie);
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void NativeToJsBridge::registerBundle(
uint32_t bundleId,
const std::string& bundlePath) {
runOnExecutorQueue([bundleId, bundlePath](JSExecutor* executor) {
executor->registerBundle(bundleId, bundlePath);
});
}
void NativeToJsBridge::setGlobalVariable(
std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
runOnExecutorQueue([propName = std::move(propName),
jsonValue = makeMoveWrapper(std::move(jsonValue))](
JSExecutor* executor) mutable {
executor->setGlobalVariable(propName, jsonValue.move());
});
}
void* NativeToJsBridge::getJavaScriptContext() {
// TODO(cjhopman): this seems unsafe unless we require that it is only called
// on the main js queue.
return m_executor->getJavaScriptContext();
}
bool NativeToJsBridge::isInspectable() {
return m_inspectable;
}
bool NativeToJsBridge::isBatchActive() {
return m_delegate->isBatchActive();
}
void NativeToJsBridge::handleMemoryPressure(int pressureLevel) {
runOnExecutorQueue([=](JSExecutor* executor) {
executor->handleMemoryPressure(pressureLevel);
});
}
void NativeToJsBridge::destroy() {
// All calls made through runOnExecutorQueue have an early exit if
// m_destroyed is true. Setting this before the runOnQueueSync will cause
// pending work to be cancelled and we won't have to wait for it.
*m_destroyed = true;
m_executorMessageQueueThread->runOnQueueSync([this] {
m_executor->destroy();
m_executorMessageQueueThread->quitSynchronous();
m_executor = nullptr;
});
}
void NativeToJsBridge::runOnExecutorQueue(
std::function<void(JSExecutor*)>&& task) noexcept {
if (*m_destroyed) {
return;
}
std::shared_ptr<bool> isDestroyed = m_destroyed;
m_executorMessageQueueThread->runOnQueue(
[this, isDestroyed, task = std::move(task)] {
if (*isDestroyed) {
return;
}
// The executor is guaranteed to be valid for the duration of the task
// because:
// 1. the executor is only destroyed after it is unregistered
// 2. the executor is unregistered on this queue
// 3. we just confirmed that the executor hasn't been unregistered above
task(m_executor.get());
});
}
std::shared_ptr<NativeMethodCallInvoker>
NativeToJsBridge::getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) const {
class NativeMethodCallInvokerImpl : public NativeMethodCallInvoker {
private:
std::weak_ptr<JsToNativeBridge> m_jsToNativeBridge;
std::shared_ptr<NativeMethodCallInvoker> m_nativeInvoker;
public:
NativeMethodCallInvokerImpl(
std::weak_ptr<JsToNativeBridge> jsToNativeBridge,
std::shared_ptr<NativeMethodCallInvoker> nativeInvoker)
: m_jsToNativeBridge(std::move(jsToNativeBridge)),
m_nativeInvoker(std::move(nativeInvoker)) {}
void invokeAsync(
const std::string& methodName,
NativeMethodCallFunc&& func) noexcept override {
if (auto strongJsToNativeBridge = m_jsToNativeBridge.lock()) {
strongJsToNativeBridge->recordTurboModuleAsyncMethodCall();
}
m_nativeInvoker->invokeAsync(methodName, std::move(func));
}
void invokeSync(const std::string& methodName, NativeMethodCallFunc&& func)
override {
m_nativeInvoker->invokeSync(methodName, std::move(func));
}
};
return std::make_shared<NativeMethodCallInvokerImpl>(
m_delegate, std::move(nativeMethodCallInvoker));
}
jsinspector_modern::RuntimeTargetDelegate&
NativeToJsBridge::getInspectorTargetDelegate() {
return m_executor->getRuntimeTargetDelegate();
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,136 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <atomic>
#include <functional>
#include <map>
#include <vector>
#include <ReactCommon/CallInvoker.h>
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/JSExecutor.h>
#include <jsinspector-modern/ReactCdp.h>
namespace folly {
struct dynamic;
}
namespace facebook::react {
struct InstanceCallback;
class JsToNativeBridge;
class MessageQueueThread;
class ModuleRegistry;
class RAMBundleRegistry;
// This class manages calls from native code to JS. It also manages
// executors and their threads. All functions here can be called from
// any thread.
//
// Except for loadBundleSync(), all void methods will queue
// work to run on the jsQueue passed to the ctor, and return
// immediately.
class [[deprecated("This API will be removed along with the legacy architecture.")]] NativeToJsBridge {
public:
friend class JsToNativeBridge;
/**
* This must be called on the main JS thread.
*/
NativeToJsBridge(
JSExecutorFactory *jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::shared_ptr<InstanceCallback> callback);
virtual ~NativeToJsBridge();
/**
* Executes a function with the module ID and method ID and any additional
* arguments in JS.
*/
void callFunction(std::string &&module, std::string &&method, folly::dynamic &&arguments);
/**
* Invokes a callback with the cbID, and optional additional arguments in JS.
*/
void invokeCallback(double callbackId, folly::dynamic &&arguments);
/**
* Sets global variables in the JS Context.
*/
void initializeRuntime();
/**
* Starts the JS application. If bundleRegistry is non-null, then it is
* used to fetch JavaScript modules as individual scripts.
* Otherwise, the script is assumed to include all the modules.
*/
void loadBundle(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL);
void loadBundleSync(
std::unique_ptr<RAMBundleRegistry> bundleRegistry,
std::unique_ptr<const JSBigString> startupScript,
std::string sourceURL);
void registerBundle(uint32_t bundleId, const std::string &bundlePath);
void setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue);
void *getJavaScriptContext();
bool isInspectable();
bool isBatchActive();
void handleMemoryPressure(int pressureLevel);
/**
* Synchronously tears down the bridge and the main executor.
*/
void destroy();
void runOnExecutorQueue(std::function<void(JSExecutor *)> &&task) noexcept;
/**
* NativeMethodCallInvoker is used by TurboModules to schedule work on the
* NativeModule thread(s).
*/
std::shared_ptr<NativeMethodCallInvoker> getDecoratedNativeMethodCallInvoker(
std::shared_ptr<NativeMethodCallInvoker> nativeMethodCallInvoker) const;
jsinspector_modern::RuntimeTargetDelegate &getInspectorTargetDelegate();
private:
// This is used to avoid a race condition where a proxyCallback gets queued
// after ~NativeToJsBridge(), on the same thread. In that case, the callback
// will try to run the task on m_callback which will have been destroyed
// within ~NativeToJsBridge(), thus causing a SIGSEGV.
std::shared_ptr<bool> m_destroyed;
std::shared_ptr<JsToNativeBridge> m_delegate;
std::unique_ptr<JSExecutor> m_executor;
std::shared_ptr<MessageQueueThread> m_executorMessageQueueThread;
// Memoize this on the JS thread, so that it can be inspected from
// any thread later. This assumes inspectability doesn't change for
// a JSExecutor instance, which is true for all existing implementations.
bool m_inspectable;
// Keep track of whether the JS bundle containing the application logic causes
// exception when evaluated initially. If so, more calls to JS will very
// likely fail as well, so this flag can help prevent them.
bool m_applicationScriptHasFailure = false;
#ifdef WITH_FBSYSTRACE
std::atomic<uint_least32_t> m_systraceCookie{0};
#endif
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,84 @@
/*
* 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.
*/
#include "RAMBundleRegistry.h"
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <folly/String.h>
#include <memory>
namespace facebook::react {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
constexpr uint32_t RAMBundleRegistry::MAIN_BUNDLE_ID;
#pragma clang diagnostic pop
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::singleBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle) {
return std::make_unique<RAMBundleRegistry>(std::move(mainBundle));
}
std::unique_ptr<RAMBundleRegistry> RAMBundleRegistry::multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory) {
return std::make_unique<RAMBundleRegistry>(
std::move(mainBundle), std::move(factory));
}
RAMBundleRegistry::RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory)
: m_factory(std::move(factory)) {
m_bundles.emplace(MAIN_BUNDLE_ID, std::move(mainBundle));
}
void RAMBundleRegistry::registerBundle(
uint32_t bundleId,
std::string bundlePath) {
m_bundlePaths.emplace(bundleId, std::move(bundlePath));
}
JSModulesUnbundle::Module RAMBundleRegistry::getModule(
uint32_t bundleId,
uint32_t moduleId) {
if (m_bundles.find(bundleId) == m_bundles.end()) {
if (!m_factory) {
throw std::runtime_error(
"You need to register factory function in order to "
"support multiple RAM bundles.");
}
auto bundlePath = m_bundlePaths.find(bundleId);
if (bundlePath == m_bundlePaths.end()) {
throw std::runtime_error(
"In order to fetch RAM bundle from the registry, its file "
"path needs to be registered first.");
}
m_bundles.emplace(bundleId, m_factory(bundlePath->second));
}
auto module = getBundle(bundleId)->getModule(moduleId);
if (bundleId == MAIN_BUNDLE_ID) {
return module;
}
return {
.name = "seg-" + std::to_string(bundleId) + '_' + module.name,
.code = std::move(module.code),
};
}
JSModulesUnbundle* RAMBundleRegistry::getBundle(uint32_t bundleId) const {
return m_bundles.at(bundleId).get();
}
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,56 @@
/*
* 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.
*/
#pragma once
#ifndef RCT_REMOVE_LEGACY_ARCH
#include <cstdint>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include <cxxreact/JSModulesUnbundle.h>
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
namespace facebook::react {
class RN_EXPORT [[deprecated("This API will be removed along with the legacy architecture.")]] RAMBundleRegistry {
public:
constexpr static uint32_t MAIN_BUNDLE_ID = 0;
static std::unique_ptr<RAMBundleRegistry> singleBundleRegistry(std::unique_ptr<JSModulesUnbundle> mainBundle);
static std::unique_ptr<RAMBundleRegistry> multipleBundlesRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory);
explicit RAMBundleRegistry(
std::unique_ptr<JSModulesUnbundle> mainBundle,
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> factory = nullptr);
RAMBundleRegistry(RAMBundleRegistry &&) = default;
RAMBundleRegistry &operator=(RAMBundleRegistry &&) = default;
void registerBundle(uint32_t bundleId, std::string bundlePath);
JSModulesUnbundle::Module getModule(uint32_t bundleId, uint32_t moduleId);
virtual ~RAMBundleRegistry() = default;
private:
JSModulesUnbundle *getBundle(uint32_t bundleId) const;
std::function<std::unique_ptr<JSModulesUnbundle>(std::string)> m_factory;
std::unordered_map<uint32_t, std::string> m_bundlePaths;
std::unordered_map<uint32_t, std::unique_ptr<JSModulesUnbundle>> m_bundles;
};
} // namespace facebook::react
#endif // RCT_REMOVE_LEGACY_ARCH

View File

@@ -0,0 +1,57 @@
# coding: utf-8
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-cxxreact"
s.version = version
s.summary = "-" # TODO
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{cpp,h}", "*.h")
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-debug/React_debug.framework/Headers\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimeexecutor/React_runtimeexecutor.framework/Headers\"",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
}
s.header_dir = "cxxreact"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-callinvoker", version
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-perflogger", version
s.dependency "React-jsi", version
s.dependency "React-logger", version
s.dependency "React-debug", version
s.dependency "React-timing", version
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
s.resource_bundles = {'React-cxxreact_privacy' => 'PrivacyInfo.xcprivacy'}
if use_hermes()
s.dependency 'hermes-engine'
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,140 @@
/*
* 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.
*/
#include "ReactMarker.h"
#include <cxxreact/JSExecutor.h>
namespace facebook::react::ReactMarker {
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif
LogTaggedMarker logTaggedMarkerBridgelessImpl = nullptr;
LogTaggedMarker logTaggedMarkerImpl = nullptr;
std::shared_mutex logTaggedMarkerImplMutex;
#if __clang__
#pragma clang diagnostic pop
#endif
void logMarker(const ReactMarkerId markerId) {
logTaggedMarker(markerId, nullptr);
}
void logTaggedMarker(const ReactMarkerId markerId, const char* tag) {
LogTaggedMarker marker = nullptr;
{
std::shared_lock lock(logTaggedMarkerImplMutex);
marker = logTaggedMarkerImpl;
}
if (marker != nullptr) {
marker(markerId, tag);
}
}
void logMarkerBridgeless(const ReactMarkerId markerId) {
logTaggedMarkerBridgeless(markerId, nullptr);
}
void logTaggedMarkerBridgeless(const ReactMarkerId markerId, const char* tag) {
logTaggedMarkerBridgelessImpl(markerId, tag);
}
void logMarkerDone(const ReactMarkerId markerId, double markerTime) {
StartupLogger::getInstance().logStartupEvent(markerId, markerTime);
}
StartupLogger& StartupLogger::getInstance() {
static StartupLogger instance;
return instance;
}
void StartupLogger::logStartupEvent(
const ReactMarkerId markerId,
double markerTime) {
switch (markerId) {
case ReactMarkerId::APP_STARTUP_START:
if (!std::isnan(appStartupStartTime)) {
// We had a startup start time, which indicates a warm start (user
// closed the app and start again). In this case we need to invalidate
// all other startup timings.
reset();
}
appStartupStartTime = markerTime;
return;
case ReactMarkerId::APP_STARTUP_STOP:
if (std::isnan(appStartupEndTime)) {
appStartupEndTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_START:
if (std::isnan(initReactRuntimeStartTime)) {
initReactRuntimeStartTime = markerTime;
}
return;
case ReactMarkerId::INIT_REACT_RUNTIME_STOP:
if (std::isnan(initReactRuntimeEndTime)) {
initReactRuntimeEndTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_START:
if (std::isnan(runJSBundleStartTime)) {
runJSBundleStartTime = markerTime;
}
return;
case ReactMarkerId::RUN_JS_BUNDLE_STOP:
if (std::isnan(runJSBundleEndTime)) {
runJSBundleEndTime = markerTime;
}
return;
default:
return;
}
}
void StartupLogger::reset() {
appStartupStartTime = std::nan("");
appStartupEndTime = std::nan("");
initReactRuntimeStartTime = std::nan("");
initReactRuntimeEndTime = std::nan("");
runJSBundleStartTime = std::nan("");
runJSBundleEndTime = std::nan("");
}
double StartupLogger::getAppStartupStartTime() {
return appStartupStartTime;
}
double StartupLogger::getInitReactRuntimeStartTime() {
return initReactRuntimeStartTime;
}
double StartupLogger::getInitReactRuntimeEndTime() {
return initReactRuntimeEndTime;
}
double StartupLogger::getRunJSBundleStartTime() {
return runJSBundleStartTime;
}
double StartupLogger::getRunJSBundleEndTime() {
return runJSBundleEndTime;
}
double StartupLogger::getAppStartupEndTime() {
return appStartupEndTime;
}
} // namespace facebook::react::ReactMarker

View File

@@ -0,0 +1,100 @@
/*
* 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.
*/
#pragma once
#include <cmath>
#include <shared_mutex>
#ifdef __APPLE__
#include <functional>
#endif
namespace facebook::react::ReactMarker {
enum ReactMarkerId {
APP_STARTUP_START,
APP_STARTUP_STOP,
INIT_REACT_RUNTIME_START,
INIT_REACT_RUNTIME_STOP,
NATIVE_REQUIRE_START,
NATIVE_REQUIRE_STOP,
RUN_JS_BUNDLE_START,
RUN_JS_BUNDLE_STOP,
CREATE_REACT_CONTEXT_STOP,
JS_BUNDLE_STRING_CONVERT_START,
JS_BUNDLE_STRING_CONVERT_STOP,
NATIVE_MODULE_SETUP_START,
NATIVE_MODULE_SETUP_STOP,
REGISTER_JS_SEGMENT_START,
REGISTER_JS_SEGMENT_STOP,
REACT_INSTANCE_INIT_START,
REACT_INSTANCE_INIT_STOP
};
#ifdef __APPLE__
using LogTaggedMarker = std::function<void(const ReactMarkerId, const char *tag)>; // Bridge only
using LogTaggedMarkerBridgeless = std::function<void(const ReactMarkerId, const char *tag)>;
#else
typedef void (*LogTaggedMarker)(const ReactMarkerId, const char *tag); // Bridge only
typedef void (*LogTaggedMarkerBridgeless)(const ReactMarkerId, const char *tag);
#endif
#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif
extern RN_EXPORT std::shared_mutex logTaggedMarkerImplMutex;
/// - important: To ensure this gets read and written to in a thread safe
/// manner, make use of `logTaggedMarkerImplMutex`.
extern RN_EXPORT LogTaggedMarker logTaggedMarkerImpl;
extern RN_EXPORT LogTaggedMarker logTaggedMarkerBridgelessImpl;
extern RN_EXPORT void logMarker(ReactMarkerId markerId); // Bridge only
extern RN_EXPORT void logTaggedMarker(ReactMarkerId markerId,
const char *tag); // Bridge only
extern RN_EXPORT void logMarkerBridgeless(ReactMarkerId markerId);
extern RN_EXPORT void logTaggedMarkerBridgeless(ReactMarkerId markerId, const char *tag);
struct ReactMarkerEvent {
const ReactMarkerId markerId;
const char *tag;
double time;
};
class RN_EXPORT StartupLogger {
public:
static StartupLogger &getInstance();
void logStartupEvent(ReactMarkerId markerId, double markerTime);
void reset();
double getAppStartupStartTime();
double getInitReactRuntimeStartTime();
double getInitReactRuntimeEndTime();
double getRunJSBundleStartTime();
double getRunJSBundleEndTime();
double getAppStartupEndTime();
private:
StartupLogger() = default;
StartupLogger(const StartupLogger &) = delete;
StartupLogger &operator=(const StartupLogger &) = delete;
double appStartupStartTime = std::nan("");
double appStartupEndTime = std::nan("");
double initReactRuntimeStartTime = std::nan("");
double initReactRuntimeEndTime = std::nan("");
double runJSBundleStartTime = std::nan("");
double runJSBundleEndTime = std::nan("");
};
// When the marker got logged from the platform, it will notify here. This is
// used to collect react markers that are logged in the platform instead of in
// C++.
extern RN_EXPORT void logMarkerDone(ReactMarkerId markerId, double markerTime);
} // namespace facebook::react::ReactMarker

View File

@@ -0,0 +1,28 @@
/**
* 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.
*
* @generated by scripts/releases/set-version.js
*/
#pragma once
#include <cstdint>
#include <string_view>
#define REACT_NATIVE_VERSION_MAJOR 0
#define REACT_NATIVE_VERSION_MINOR 83
#define REACT_NATIVE_VERSION_PATCH 2
namespace facebook::react {
constexpr struct {
int32_t Major = 0;
int32_t Minor = 83;
int32_t Patch = 2;
std::string_view Prerelease = "";
} ReactNativeVersion;
} // namespace facebook::react

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include <exception>
#include <functional>
#include <string>
namespace facebook::react {
/**
* RecoverableError
*
* An exception that it is expected we should be able to recover from.
*/
struct RecoverableError : public std::exception {
explicit RecoverableError(const std::string &what_) : m_what{"facebook::react::Recoverable: " + what_} {}
virtual const char *what() const noexcept override
{
return m_what.c_str();
}
/**
* runRethrowingAsRecoverable
*
* Helper function that converts any exception of type `E`, thrown within the
* `act` routine into a recoverable error with the same message.
*/
template <typename E>
inline static void runRethrowingAsRecoverable(std::function<void()> act)
{
try {
act();
} catch (const E &err) {
throw RecoverableError(err.what());
}
}
private:
std::string m_what;
};
} // namespace facebook::react

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#pragma once
#include "TraceSection.h"
// NOTE: This is here for a backwards compatibility and should be removed once
// all of the external references to `facebook::react::SystraceSection` are
// gone.
namespace facebook::react {
#if defined(WITH_LOOM_TRACE)
#define SystraceSection TraceSection
#else
struct [[deprecated("Use TraceSection")]] SystraceSection : public TraceSection {
template <typename... ConvertsToStringPiece>
explicit SystraceSection(const char *name, ConvertsToStringPiece &&...args) : TraceSection(name, args...)
{
}
};
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,190 @@
/*
* 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.
*/
#pragma once
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
#endif
#ifdef WITH_PERFETTO
// clang-format off
// Windows.h macro fail, 'interface' clashes with a function in perfetto.h
#ifdef interface
#pragma push_macro("interface")
#undef interface
#define __RCT_INTERFACE_PUSHED
#endif
#include <perfetto.h>
#ifdef __RCT_INTERFACE_PUSHED
#undef __RCT_INTERFACE_PUSHED
#pragma pop_macro("interface")
#endif
// clang-format on
#include <reactperflogger/ReactPerfettoCategories.h>
#endif
#if defined(__APPLE__)
// This is required so that OS_LOG_TARGET_HAS_10_15_FEATURES will be set.
#include <os/trace_base.h>
#if OS_LOG_TARGET_HAS_10_15_FEATURES && !defined(WITH_LOOM_TRACE)
#include <os/log.h>
#include <os/signpost.h>
#include <sstream>
#endif
#endif
namespace facebook::react {
/**
* Allow providing an fbsystrace implementation that can short-circuit out
* quickly and can throttle too frequent events so we can get useful traces even
* if rendering etc. is spinning. For throttling we'll need file/line info so we
* use a macro.
*/
#if defined(WITH_LOOM_TRACE)
#define TraceSectionUnwrapped \
static constexpr const char systraceSectionFile[] = __FILE__; \
fbsystrace::FbSystraceSection<systraceSectionFile, __LINE__>
/**
* This is a convenience class to avoid lots of verbose profiling
* #ifdefs. If WITH_FBSYSTRACE is not defined, the optimizer will
* remove this completely. If it is defined, it will behave as
* FbSystraceSection, with the right tag provided. Use two separate classes to
* to ensure that the ODR rule isn't violated, that is, if WITH_FBSYSTRACE has
* different values in different files, there is no inconsistency in the sizes
* of defined symbols.
*/
#elif defined(WITH_PERFETTO)
struct TraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit TraceSection([[maybe_unused]] const char *name, [[maybe_unused]] ConvertsToStringPiece &&...args)
{
TRACE_EVENT_BEGIN("react-native", perfetto::DynamicString{name}, args...);
}
~TraceSection()
{
TRACE_EVENT_END("react-native");
}
};
using TraceSectionUnwrapped = TraceSection;
#elif defined(WITH_FBSYSTRACE)
struct ConcreteTraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit ConcreteTraceSection(const char *name, ConvertsToStringPiece &&...args)
: m_section(TRACE_TAG_REACT, name, args...)
{
}
private:
fbsystrace::FbSystraceSection m_section;
};
using TraceSectionUnwrapped = ConcreteTraceSection;
#else
struct DummyTraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit DummyTraceSection([[maybe_unused]] const char *name, [[maybe_unused]] ConvertsToStringPiece &&...args)
{
}
};
using TraceSectionUnwrapped = DummyTraceSection;
#endif
/**
* On recent Apple platforms we want to leverage the Instruments signposts APIs.
* To not break the other TraceSection implementations above we wrap them.
* In the case of WITH_LOOM_TRACE we don't use the signposts APIs because of the
* templated type for TraceSection.
*/
#if defined(__APPLE__) && OS_LOG_TARGET_HAS_10_15_FEATURES && !defined(WITH_LOOM_TRACE)
namespace systrace {
template <typename T, typename = void>
struct renderer {
static std::string render(const T &t)
{
std::ostringstream oss;
oss << t;
return oss.str();
}
};
template <typename T>
static auto render(const T &t) -> decltype(renderer<T>::render(std::declval<const T &>()))
{
return renderer<T>::render(t);
}
inline os_log_t instrumentsLogHandle = nullptr;
static inline os_log_t getOrCreateInstrumentsLogHandle()
{
static std::once_flag flag{};
std::call_once(flag, []() {
if (!instrumentsLogHandle) {
instrumentsLogHandle = os_log_create("dev.reactnative.instruments", OS_LOG_CATEGORY_DYNAMIC_TRACING);
}
});
return instrumentsLogHandle;
}
} // namespace systrace
struct TraceSection {
public:
template <typename... ConvertsToStringPiece>
explicit TraceSection(const char *name, ConvertsToStringPiece &&...args) : systraceSectionUnwrapped_(name, args...)
{
os_log_t instrumentsLogHandle = systrace::getOrCreateInstrumentsLogHandle();
// If the log isn't enabled, we don't want the performance overhead of the
// rest of the code below.
if (!os_signpost_enabled(instrumentsLogHandle)) {
return;
}
name_ = name;
const auto argsVector = std::vector<std::string>{systrace::render(args)...};
std::string argsString = "";
for (size_t i = 0; i < argsVector.size(); i += 2) {
argsString += argsVector[i] + "=" + argsVector[i + 1] + ";";
}
signpostID_ = os_signpost_id_make_with_pointer(instrumentsLogHandle, this);
os_signpost_interval_begin(instrumentsLogHandle, signpostID_, "Systrace", "%s begin: %s", name, argsString.c_str());
}
~TraceSection()
{
// We don't need to gate on os_signpost_enabled here because it's already
// checked in os_signpost_interval_end.
os_signpost_interval_end(systrace::instrumentsLogHandle, signpostID_, "Systrace", "%s end", name_.data());
}
private:
os_signpost_id_t signpostID_ = OS_SIGNPOST_ID_INVALID;
std::string_view name_;
TraceSectionUnwrapped systraceSectionUnwrapped_;
};
#else
#define TraceSection TraceSectionUnwrapped
#endif
} // namespace facebook::react

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
#include <gtest/gtest.h>
#include <exception>
#include <stdexcept>
#include <cxxreact/RecoverableError.h>
using namespace facebook::react;
TEST(RecoverableError, RunRethrowingAsRecoverableRecoverTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::runtime_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError& err) {
ASSERT_STREQ(err.what(), "facebook::react::Recoverable: catch me");
} catch (...) {
FAIL() << "Uncaught exception";
}
}
TEST(RecoverableError, RunRethrowingAsRecoverableFallthroughTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>(
[]() { throw std::logic_error("catch me"); });
FAIL() << "Unthrown exception";
} catch (const RecoverableError&) {
FAIL() << "Recovered exception that should have fallen through";
} catch (const std::exception& err) {
ASSERT_STREQ(err.what(), "catch me");
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.
*/
#include <cxxreact/JsArgumentHelpers.h>
#include <folly/dynamic.h>
#include <gtest/gtest.h>
#include <algorithm>
using namespace facebook::xplat;
using dynamic = folly::dynamic;
#define EXPECT_JSAE(statement, exstr) \
do { \
try { \
statement; \
FAIL() << "Expected JsArgumentException(" << (exstr) << ") not thrown"; \
} catch (const JsArgumentException& ex) { \
EXPECT_EQ(ex.what(), std::string(exstr)); \
} \
} while (0) // let any other exception escape, gtest will deal.
TEST(JsArgumentHelpersTest, args) {
const bool aBool = true;
const int64_t anInt = 17;
const double aDouble = 3.14;
const std::string aString = "word";
const dynamic anArray = dynamic::array("a", "b", "c");
const dynamic anObject = dynamic::object("k1", "v1")("k2", "v2");
const std::string aNumericString = std::to_string(anInt);
folly::dynamic args = dynamic::array(
aBool, anInt, aDouble, aString, anArray, anObject, aNumericString);
EXPECT_EQ(jsArgAsBool(args, 0), aBool);
EXPECT_EQ(jsArgAsInt(args, 1), anInt);
EXPECT_EQ(jsArgAsDouble(args, 2), aDouble);
EXPECT_EQ(jsArgAsString(args, 3), aString);
EXPECT_EQ(jsArgAsArray(args, 4), anArray);
EXPECT_EQ(jsArgAsObject(args, 5), anObject);
// const args
const folly::dynamic& cargs = args;
const folly::dynamic& a4 = jsArgAsArray(cargs, 4);
EXPECT_EQ(a4, anArray);
EXPECT_EQ(jsArgAsObject(cargs, 5), anObject);
// helpers returning dynamic should return same object without copying
EXPECT_EQ(&jsArgAsArray(args, 4), &(args[4]));
EXPECT_EQ(&jsArgAsArray(cargs, 4), &(args[4]));
// dynamics returned for mutable args should be mutable. The test is that
// this compiles.
jsArgAsArray(args, 4)[2] = "d";
jsArgAsArray(args, 4)[2] = "c";
// These fail to compile due to constness.
// jsArgAsArray(cargs, 4)[2] = "d";
// jsArgAsArray(cargs, 4)[2] = "c";
// ref-qualified member function tests
EXPECT_EQ(jsArgN(args, 3, &folly::dynamic::getString), aString);
EXPECT_EQ(jsArg(args[3], &folly::dynamic::getString), aString);
// conversions
EXPECT_EQ(jsArgAsDouble(args, 1), anInt * 1.0);
EXPECT_EQ(jsArgAsString(args, 1), aNumericString);
EXPECT_EQ(jsArgAsInt(args, 6), anInt);
// Test exception messages.
// out_of_range
EXPECT_JSAE(
jsArgAsBool(args, 7),
"JavaScript provided 7 arguments for C++ method which references at least "
"8 arguments: out of range in dynamic array");
// Conv range_error (invalid value conversion)
const std::string exhead = "Could not convert argument 3 to required type: ";
const std::string extail = ": Invalid leading character: \"word\"";
try {
jsArgAsInt(args, 3);
FAIL() << "Expected JsArgumentException(" << exhead << "..." << extail
<< ") not thrown";
} catch (const JsArgumentException& ex) {
const std::string exwhat = ex.what();
EXPECT_GT(exwhat.size(), exhead.size());
EXPECT_GT(exwhat.size(), extail.size());
EXPECT_TRUE(std::equal(exhead.cbegin(), exhead.cend(), exwhat.cbegin()))
<< "JsArgumentException('" << exwhat << "') does not begin with '"
<< exhead << "'";
EXPECT_TRUE(std::equal(extail.crbegin(), extail.crend(), exwhat.crbegin()))
<< "JsArgumentException('" << exwhat << "') does not end with '"
<< extail << "'";
}
// inconvertible types
EXPECT_JSAE(
jsArgAsArray(args, 2),
"Argument 3 of type double is not required type Array");
EXPECT_JSAE(
jsArgAsInt(args, 4),
"Error converting JavaScript arg 4 to C++: "
"TypeError: expected dynamic type 'int/double/bool/string', but had type 'array'");
// type predicate failure
EXPECT_JSAE(
jsArgAsObject(args, 4),
"Argument 5 of type array is not required type Object");
}

View File

@@ -0,0 +1,82 @@
/*
* 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.
*/
#include <fcntl.h>
#include <sys/mman.h>
#include <cxxreact/JSBigString.h>
#include <gtest/gtest.h>
using namespace facebook::react;
namespace {
int tempFileFromString(const std::string& contents) {
const char* tmpDir = getenv("TMPDIR");
if (tmpDir == nullptr) {
tmpDir = "/tmp";
}
std::string tmp{tmpDir};
tmp += "/temp.XXXXXX";
std::vector<char> tmpBuf{tmp.begin(), tmp.end()};
tmpBuf.push_back('\0');
const int fd = mkstemp(tmpBuf.data());
write(fd, contents.c_str(), contents.size() + 1);
return fd;
}
}; // namespace
TEST(JSBigFileString, MapWholeFileTest) {
std::string data{"Hello, world"};
const auto size = data.length() + 1;
// Initialise Big String
int fd = tempFileFromString("Hello, world");
JSBigFileString bigStr{fd, size};
// Test
ASSERT_STREQ(data.c_str(), bigStr.c_str());
}
TEST(JSBigFileString, MapPartTest) {
std::string data{"Hello, world"};
// Sub-string to actually map
std::string needle{"or"};
off_t offset = data.find(needle);
// Initialise Big String
int fd = tempFileFromString(data);
JSBigFileString bigStr{fd, needle.size(), offset};
// Test
EXPECT_EQ(needle.length(), bigStr.size());
for (unsigned int i = 0; i < needle.length(); ++i) {
EXPECT_EQ(needle[i], bigStr.c_str()[i]);
}
}
TEST(JSBigFileString, MapPartAtLargeOffsetTest) {
std::string data(8 * 4096, 'X');
data += "Hello World!";
// Sub-string to actually map
std::string needle{"or"};
off_t offset = data.find(needle);
// Initialise Big String
int fd = tempFileFromString(data);
JSBigFileString bigStr{fd, needle.size(), offset};
// Test
EXPECT_EQ(needle.length(), bigStr.size());
for (unsigned int i = 0; i < needle.length(); ++i) {
EXPECT_EQ(needle[i], bigStr.c_str()[i]);
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.
*/
#include <cxxreact/MethodCall.h>
#include <folly/json.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <gtest/gtest.h>
#pragma GCC diagnostic pop
using namespace facebook::react;
using dynamic = folly::dynamic;
TEST(parseMethodCalls, SingleReturnCallNoArgs) {
auto jsText = "[[7],[3],[[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(0, returnedCall.arguments.size());
EXPECT_EQ(7, returnedCall.moduleId);
EXPECT_EQ(3, returnedCall.methodId);
}
TEST(parseMethodCalls, InvalidReturnFormat) {
try {
auto input = dynamic::object("foo", 1);
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(dynamic::object("foo", 1));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(1, 4, dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input = dynamic::array(
dynamic::array(1), dynamic::array(4), dynamic::object("foo", 2));
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
try {
auto input =
dynamic::array(dynamic::array(1), dynamic::array(4), dynamic::array());
parseMethodCalls(std::move(input));
ADD_FAILURE();
} catch (const std::invalid_argument&) {
// ignored
}
}
TEST(parseMethodCalls, NumberReturn) {
auto jsText = "[[0],[0],[[\"foobar\"]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ("foobar", returnedCall.arguments[0].asString());
}
TEST(parseMethodCalls, StringReturn) {
auto jsText = "[[0],[0],[[42.16]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::DOUBLE, returnedCall.arguments[0].type());
EXPECT_EQ(42.16, returnedCall.arguments[0].asDouble());
}
TEST(parseMethodCalls, BooleanReturn) {
auto jsText = "[[0],[0],[[false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[0].type());
ASSERT_FALSE(returnedCall.arguments[0].asBool());
}
TEST(parseMethodCalls, NullReturn) {
auto jsText = "[[0],[0],[[null]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[0].type());
}
TEST(parseMethodCalls, MapReturn) {
auto jsText = R"([[0],[0],[[{"foo": "hello", "bar": 4.0, "baz": true}]]])";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::OBJECT, returnedCall.arguments[0].type());
auto& returnedMap = returnedCall.arguments[0];
auto foo = returnedMap.at("foo");
EXPECT_EQ(folly::dynamic("hello"), foo);
auto bar = returnedMap.at("bar");
EXPECT_EQ(folly::dynamic(4.0), bar);
auto baz = returnedMap.at("baz");
EXPECT_EQ(folly::dynamic(true), baz);
}
TEST(parseMethodCalls, ArrayReturn) {
auto jsText = "[[0],[0],[[[\"foo\", 42.0, false]]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(1, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::ARRAY, returnedCall.arguments[0].type());
auto& returnedArray = returnedCall.arguments[0];
EXPECT_EQ(3, returnedArray.size());
EXPECT_EQ(folly::dynamic("foo"), returnedArray[0]);
EXPECT_EQ(folly::dynamic(42.0), returnedArray[1]);
EXPECT_EQ(folly::dynamic(false), returnedArray[2]);
}
TEST(parseMethodCalls, ReturnMultipleParams) {
auto jsText = "[[0],[0],[[\"foo\", 14, null, false]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(1, returnedCalls.size());
auto returnedCall = returnedCalls[0];
EXPECT_EQ(4, returnedCall.arguments.size());
EXPECT_EQ(folly::dynamic::STRING, returnedCall.arguments[0].type());
EXPECT_EQ(folly::dynamic::INT64, returnedCall.arguments[1].type());
EXPECT_EQ(folly::dynamic::NULLT, returnedCall.arguments[2].type());
EXPECT_EQ(folly::dynamic::BOOL, returnedCall.arguments[3].type());
}
TEST(parseMethodCalls, ParseTwoCalls) {
auto jsText = "[[0,0],[1,1],[[],[]]]";
auto returnedCalls = parseMethodCalls(folly::parseJson(jsText));
EXPECT_EQ(2, returnedCalls.size());
}

View File

@@ -0,0 +1,17 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB devtoolsruntimesettings_SRCS *.cpp)
add_library(devtoolsruntimesettings OBJECT ${devtoolsruntimesettings_SRCS})
target_include_directories(devtoolsruntimesettings PUBLIC .)
target_link_libraries(devtoolsruntimesettings jsi react_codegen_rncore)
target_compile_reactnative_options(devtoolsruntimesettings PRIVATE)

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#include "DevToolsRuntimeSettings.h"
namespace facebook::react {
void DevToolsRuntimeSettings::setReloadAndProfileConfig(
NativePartialReloadAndProfileConfig config) {
if (config.shouldReloadAndProfile.has_value()) {
_config.shouldReloadAndProfile = config.shouldReloadAndProfile.value();
}
if (config.shouldReloadAndProfile.has_value()) {
_config.recordChangeDescriptions = config.recordChangeDescriptions.value();
}
};
NativeReloadAndProfileConfig
DevToolsRuntimeSettings::getReloadAndProfileConfig() const {
return _config;
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
/*
* 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.
*/
#pragma once
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
namespace facebook::react {
using NativePartialReloadAndProfileConfig =
NativeReactDevToolsRuntimeSettingsModulePartialReloadAndProfileConfig<std::optional<bool>, std::optional<bool>>;
template <>
struct Bridging<NativePartialReloadAndProfileConfig>
: NativeReactDevToolsRuntimeSettingsModulePartialReloadAndProfileConfigBridging<
NativePartialReloadAndProfileConfig> {};
using NativeReloadAndProfileConfig = NativeReactDevToolsRuntimeSettingsModuleReloadAndProfileConfig<bool, bool>;
template <>
struct Bridging<NativeReloadAndProfileConfig>
: NativeReactDevToolsRuntimeSettingsModuleReloadAndProfileConfigBridging<NativeReloadAndProfileConfig> {};
class DevToolsRuntimeSettings {
public:
// static to persist across Turbo Module reloads
static DevToolsRuntimeSettings &getInstance()
{
static DevToolsRuntimeSettings instance;
return instance;
}
private:
NativeReloadAndProfileConfig _config;
DevToolsRuntimeSettings() : _config() {}
public:
~DevToolsRuntimeSettings() = default;
DevToolsRuntimeSettings(const DevToolsRuntimeSettings &) = delete;
DevToolsRuntimeSettings(DevToolsRuntimeSettings &&) = delete;
void operator=(const DevToolsRuntimeSettings &) = delete;
void operator=(DevToolsRuntimeSettings &&) = delete;
void setReloadAndProfileConfig(NativePartialReloadAndProfileConfig config);
NativeReloadAndProfileConfig getReloadAndProfileConfig() const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,53 @@
# 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.
require "json"
# package.json
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-hermes"
s.version = version
s.summary = "Hermes engine for React Native"
s.homepage = "https://reactnative.dev/"
s.license = package['license']
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources(["executor/*.{cpp,h}",
"inspector-modern/chrome/*.{cpp,h}"],
["executor/*.h",
"inspector-modern/chrome/*.h"])
s.public_header_files = "executor/HermesExecutorFactory.h"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"${PODS_ROOT}/hermes-engine/destroot/include\" \"$(PODS_TARGET_SRCROOT)/..\"",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES",
}
s.header_dir = "reacthermes"
s.dependency "React-cxxreact", version
s.dependency "React-jsiexecutor", version
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-perflogger", version
s.dependency "hermes-engine"
s.dependency "React-jsi"
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "React-oscompat")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,45 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB_RECURSE hermes_executor_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
hermes_executor_common
OBJECT
${hermes_executor_SRC}
)
target_include_directories(hermes_executor_common PUBLIC .)
react_native_android_selector(reactnative reactnative "")
target_link_libraries(hermes_executor_common
hermes-engine::hermesvm
hermes_inspector_modern
jsi
jsireact
${reactnative}
)
target_compile_reactnative_options(hermes_executor_common PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(
hermes_executor_common
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
if (HERMES_V1_ENABLED)
target_compile_options(hermes_executor_common PRIVATE -DHERMES_V1_ENABLED=1)
endif()
else()
target_compile_options(
hermes_executor_common
PRIVATE
-DNDEBUG
)
endif()

View File

@@ -0,0 +1,271 @@
/*
* 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.
*/
#include "HermesExecutorFactory.h"
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/TraceSection.h>
#include <hermes/hermes.h>
#include <jsi/decorator.h>
#include <jsinspector-modern/InspectorFlags.h>
#include <hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h>
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
#include <hermes/inspector-modern/chrome/Registration.h>
#include <hermes/inspector/RuntimeAdapter.h>
#endif
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook::react {
namespace {
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
class HermesExecutorRuntimeAdapter
: public facebook::hermes::inspector_modern::RuntimeAdapter {
public:
HermesExecutorRuntimeAdapter(
std::shared_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> thread)
: runtime_(runtime), thread_(std::move(thread)) {}
virtual ~HermesExecutorRuntimeAdapter() = default;
HermesRuntime& getRuntime() override {
return *runtime_;
}
void tickleJs() override {
thread_->runOnQueue(
[weakRuntime = std::weak_ptr<HermesRuntime>(runtime_)]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsi::Function func =
runtime->global().getPropertyAsFunction(*runtime, "__tickleJs");
func.call(*runtime);
});
}
private:
std::shared_ptr<HermesRuntime> runtime_;
std::shared_ptr<MessageQueueThread> thread_;
};
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
struct ReentrancyCheck {
// This is effectively a very subtle and complex assert, so only
// include it in builds which would include asserts.
#ifndef NDEBUG
ReentrancyCheck() : tid(std::thread::id()), depth(0) {}
void before() {
std::thread::id this_id = std::this_thread::get_id();
std::thread::id expected = std::thread::id();
// A note on memory ordering: the main purpose of these checks is
// to observe a before/before race, without an intervening after.
// This will be detected by the compare_exchange_strong atomicity
// properties, regardless of memory order.
//
// For everything else, it is easiest to think of 'depth' as a
// proxy for any access made inside the VM. If access to depth
// are reordered incorrectly, the same could be true of any other
// operation made by the VM. In fact, using acquire/release
// memory ordering could create barriers which mask a programmer
// error. So, we use relaxed memory order, to avoid masking
// actual ordering errors. Although, in practice, ordering errors
// of this sort would be surprising, because the decorator would
// need to call after() without before().
if (tid.compare_exchange_strong(
expected, this_id, std::memory_order_relaxed)) {
// Returns true if tid and expected were the same. If they
// were, then the stored tid referred to no thread, and we
// atomically saved this thread's tid. Now increment depth.
assert(depth == 0 && "No thread id, but depth != 0");
++depth;
} else if (expected == this_id) {
// If the stored tid referred to a thread, expected was set to
// that value. If that value is this thread's tid, that's ok,
// just increment depth again.
assert(depth != 0 && "Thread id was set, but depth == 0");
++depth;
} else {
// The stored tid was some other thread. This indicates a bad
// programmer error, where VM methods were called on two
// different threads unsafely. Fail fast (and hard) so the
// crash can be analyzed.
__builtin_trap();
}
}
void after() {
assert(
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
"No thread id in after()");
if (--depth == 0) {
// If we decremented depth to zero, store no-thread into tid.
std::thread::id expected = std::this_thread::get_id();
bool didWrite = tid.compare_exchange_strong(
expected, std::thread::id(), std::memory_order_relaxed);
assert(didWrite && "Decremented to zero, but no tid write");
}
}
std::atomic<std::thread::id> tid;
// This is not atomic, as it is only written or read from the owning
// thread.
unsigned int depth;
#endif
};
// This adds ReentrancyCheck and debugger enable/teardown to the given
// Runtime.
class DecoratedRuntime : public jsi::WithRuntimeDecorator<ReentrancyCheck> {
public:
// The first argument may be another decorater which itself
// decorates the real HermesRuntime, depending on the build config.
// The second argument is the real HermesRuntime as well to
// manage the debugger registration.
DecoratedRuntime(
std::unique_ptr<Runtime> runtime,
HermesRuntime& hermesRuntime,
std::shared_ptr<MessageQueueThread> jsQueue,
bool enableDebugger,
const std::string& debuggerName)
: jsi::WithRuntimeDecorator<ReentrancyCheck>(*runtime, reentrancyCheck_),
runtime_(std::move(runtime)) {
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
enableDebugger_ = enableDebugger;
if (enableDebugger_) {
std::shared_ptr<HermesRuntime> rt(runtime_, &hermesRuntime);
auto adapter =
std::make_unique<HermesExecutorRuntimeAdapter>(rt, jsQueue);
debugToken_ = facebook::hermes::inspector_modern::chrome::enableDebugging(
std::move(adapter), debuggerName);
}
#else
(void)jsQueue;
#endif // HERMES_ENABLE_DEBUGGER
}
~DecoratedRuntime() {
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
if (enableDebugger_) {
facebook::hermes::inspector_modern::chrome::disableDebugging(debugToken_);
}
#endif // HERMES_ENABLE_DEBUGGER
}
private:
// runtime_ is a potentially decorated Runtime.
// hermesRuntime is a reference to a HermesRuntime managed by runtime_.
//
// HermesExecutorRuntimeAdapter requirements are kept, because the
// dtor will disable debugging on the HermesRuntime before the
// member managing it is destroyed.
std::shared_ptr<Runtime> runtime_;
ReentrancyCheck reentrancyCheck_;
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
bool enableDebugger_;
facebook::hermes::inspector_modern::chrome::DebugSessionToken debugToken_;
#endif // HERMES_ENABLE_DEBUGGER
};
} // namespace
void HermesExecutorFactory::setEnableDebugger(bool enableDebugger) {
enableDebugger_ = enableDebugger;
}
void HermesExecutorFactory::setDebuggerName(const std::string& debuggerName) {
debuggerName_ = debuggerName;
}
std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) {
std::unique_ptr<HermesRuntime> hermesRuntime;
{
TraceSection s("makeHermesRuntime");
hermesRuntime = hermes::makeHermesRuntime(runtimeConfig_);
}
HermesRuntime& hermesRuntimeRef = *hermesRuntime;
auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
bool enableDebugger = !inspectorFlags.getFuseboxEnabled() && enableDebugger_;
auto decoratedRuntime = std::make_shared<DecoratedRuntime>(
std::move(hermesRuntime),
hermesRuntimeRef,
jsQueue,
enableDebugger,
debuggerName_);
// So what do we have now?
// DecoratedRuntime -> HermesRuntime
//
// DecoratedRuntime is held by JSIExecutor. When it gets used, it
// will check that it's on the right thread, do any necessary trace
// logging, then call the real HermesRuntime. When it is destroyed,
// it will shut down the debugger before the HermesRuntime is. In
// the normal case where debugging is not compiled in,
// all that's left is the thread checking.
// Add js engine information to Error.prototype so in error reporting we
// can send this information.
auto errorPrototype =
decoratedRuntime->global()
.getPropertyAsObject(*decoratedRuntime, "Error")
.getPropertyAsObject(*decoratedRuntime, "prototype");
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
return std::make_unique<HermesExecutor>(
decoratedRuntime,
delegate,
jsQueue,
timeoutInvoker_,
runtimeInstaller_,
hermesRuntimeRef);
}
::hermes::vm::RuntimeConfig HermesExecutorFactory::defaultRuntimeConfig() {
return ::hermes::vm::RuntimeConfig::Builder()
.withEnableSampleProfiling(true)
.build();
}
HermesExecutor::HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker& timeoutInvoker,
RuntimeInstaller runtimeInstaller,
HermesRuntime& hermesRuntime)
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller),
runtime_(runtime),
hermesRuntime_(runtime_, &hermesRuntime) {}
jsinspector_modern::RuntimeTargetDelegate&
HermesExecutor::getRuntimeTargetDelegate() {
if (!targetDelegate_) {
targetDelegate_ =
std::make_unique<jsinspector_modern::HermesRuntimeTargetDelegate>(
hermesRuntime_);
}
return *targetDelegate_;
}
} // namespace facebook::react

View File

@@ -0,0 +1,66 @@
/*
* 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.
*/
#pragma once
#include <hermes/hermes.h>
#include <jsireact/JSIExecutor.h>
#include <utility>
namespace facebook::react {
class [[deprecated("This API will be removed along with the legacy architecture.")]] HermesExecutorFactory
: public JSExecutorFactory {
public:
explicit HermesExecutorFactory(
JSIExecutor::RuntimeInstaller runtimeInstaller,
const JSIScopedTimeoutInvoker &timeoutInvoker = JSIExecutor::defaultTimeoutInvoker,
::hermes::vm::RuntimeConfig runtimeConfig = defaultRuntimeConfig())
: runtimeInstaller_(runtimeInstaller), timeoutInvoker_(timeoutInvoker), runtimeConfig_(std::move(runtimeConfig))
{
assert(timeoutInvoker_ && "Should not have empty timeoutInvoker");
}
void setEnableDebugger(bool enableDebugger);
void setDebuggerName(const std::string &debuggerName);
std::unique_ptr<JSExecutor> createJSExecutor(
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private:
static ::hermes::vm::RuntimeConfig defaultRuntimeConfig();
JSIExecutor::RuntimeInstaller runtimeInstaller_;
JSIScopedTimeoutInvoker timeoutInvoker_;
::hermes::vm::RuntimeConfig runtimeConfig_;
bool enableDebugger_ = true;
std::string debuggerName_ = "Hermes React Native";
};
class [[deprecated("This API will be removed along with the legacy architecture.")]] HermesExecutor
: public JSIExecutor {
public:
HermesExecutor(
std::shared_ptr<jsi::Runtime> runtime,
std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue,
const JSIScopedTimeoutInvoker &timeoutInvoker,
RuntimeInstaller runtimeInstaller,
hermes::HermesRuntime &hermesRuntime);
jsinspector_modern::RuntimeTargetDelegate &getRuntimeTargetDelegate() override;
private:
JSIScopedTimeoutInvoker timeoutInvoker_;
std::shared_ptr<jsi::Runtime> runtime_;
std::shared_ptr<hermes::HermesRuntime> hermesRuntime_;
std::unique_ptr<jsinspector_modern::RuntimeTargetDelegate> targetDelegate_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,38 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../../..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-jsitracing"
s.version = version
s.summary = "Internal library for JSI debugging."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("JSITracing.{cpp,h}", "JSITracing.h")
s.header_dir = "."
s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"${PODS_TARGET_SRCROOT}/../..\"",
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"GCC_WARN_PEDANTIC" => "YES" }
resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_jsitracing")
s.dependency "React-jsi"
end

View File

@@ -0,0 +1,38 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB hermesinspectormodern_SRC CONFIGURE_DEPENDS chrome/*.cpp)
add_library(hermes_inspector_modern
OBJECT
${hermesinspectormodern_SRC})
target_compile_reactnative_options(hermes_inspector_modern PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(
hermes_inspector_modern
PRIVATE
-DHERMES_ENABLE_DEBUGGER=1
)
if (HERMES_V1_ENABLED)
target_compile_options(hermes_inspector_modern PRIVATE -DHERMES_V1_ENABLED=1)
endif()
endif()
target_include_directories(hermes_inspector_modern PUBLIC ${REACT_COMMON_DIR})
react_native_android_selector(reactnative reactnative "")
target_link_libraries(hermes_inspector_modern
hermes-engine::hermesvm
jsi
jsinspector
${reactnative})

View File

@@ -0,0 +1,143 @@
/*
* 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.
*/
#include "ConnectionDemux.h"
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/CDPHandler.h>
#include <jsinspector-modern/InspectorInterfaces.h>
#include <utility>
namespace facebook::hermes::inspector_modern::chrome {
using ::facebook::react::jsinspector_modern::ILocalConnection;
using ::facebook::react::jsinspector_modern::IRemoteConnection;
namespace {
class LocalConnection : public ILocalConnection {
public:
LocalConnection(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts);
~LocalConnection() override;
void sendMessage(std::string message) override;
void disconnect() override;
private:
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
LocalConnection::LocalConnection(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn,
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts)
: conn_(conn), inspectedContexts_(std::move(inspectedContexts)) {
inspectedContexts_->insert(conn->getTitle());
}
LocalConnection::~LocalConnection() = default;
void LocalConnection::sendMessage(std::string message) {
conn_->handle(std::move(message));
}
void LocalConnection::disconnect() {
inspectedContexts_->erase(conn_->getTitle());
conn_->unregisterCallbacks();
}
} // namespace
ConnectionDemux::ConnectionDemux(
facebook::react::jsinspector_modern::IInspector& inspector)
: globalInspector_(inspector),
inspectedContexts_(std::make_shared<std::unordered_set<std::string>>()) {}
ConnectionDemux::~ConnectionDemux() = default;
DebugSessionToken ConnectionDemux::enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title) {
std::scoped_lock lock(mutex_);
// TODO(#22976087): workaround for ComponentScript contexts never being
// destroyed.
//
// After a reload, the old ComponentScript VM instance stays alive. When we
// register the new CS VM instance, check for any previous CS VM (via strcmp
// of title) and remove them.
std::vector<int> pagesToDelete;
for (auto& conn : conns_) {
if (conn.second->getTitle() == title) {
pagesToDelete.push_back(conn.first);
}
}
for (auto pageId : pagesToDelete) {
removePage(pageId);
}
auto waitForDebugger =
(inspectedContexts_->find(title) != inspectedContexts_->end());
return addPage(
hermes::inspector_modern::chrome::CDPHandler::create(
std::move(adapter), title, waitForDebugger));
}
void ConnectionDemux::disableDebugging(DebugSessionToken session) {
std::scoped_lock lock(mutex_);
if (conns_.find(session) == conns_.end()) {
return;
}
removePage(session);
}
int ConnectionDemux::addPage(
std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn) {
auto connectFunc = [conn, this](std::unique_ptr<IRemoteConnection> remoteConn)
-> std::unique_ptr<ILocalConnection> {
// This cannot be unique_ptr as std::function is copyable but unique_ptr
// isn't. TODO: Change the CDPHandler API to accommodate this and not
// require a copyable callback?
std::shared_ptr<IRemoteConnection> sharedConn = std::move(remoteConn);
if (!conn->registerCallbacks(
[sharedConn](const std::string& message) {
sharedConn->onMessage(message);
},
[sharedConn]() { sharedConn->onDisconnect(); })) {
return nullptr;
}
return std::make_unique<LocalConnection>(conn, inspectedContexts_);
};
int pageId = globalInspector_.addPage(
conn->getTitle(), "Hermes", std::move(connectFunc));
conns_[pageId] = std::move(conn);
return pageId;
}
void ConnectionDemux::removePage(int pageId) {
globalInspector_.removePage(pageId);
auto conn = conns_.at(pageId);
std::string title = conn->getTitle();
inspectedContexts_->erase(title);
conn->unregisterCallbacks();
conns_.erase(pageId);
}
} // namespace facebook::hermes::inspector_modern::chrome
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)

View File

@@ -0,0 +1,55 @@
/*
* 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.
*/
#pragma once
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <hermes/hermes.h>
#include <hermes/inspector-modern/chrome/Registration.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <hermes/inspector/chrome/CDPHandler.h>
#include <jsinspector-modern/InspectorInterfaces.h>
namespace facebook::hermes::inspector_modern::chrome {
/*
* ConnectionDemux keeps track of all debuggable Hermes runtimes (called
* "pages" in the higher-level React Native API) in this process. See
* Registration.h for documentation of the public API.
*/
class ConnectionDemux {
public:
explicit ConnectionDemux(facebook::react::jsinspector_modern::IInspector &inspector);
~ConnectionDemux();
ConnectionDemux(const ConnectionDemux &) = delete;
ConnectionDemux &operator=(const ConnectionDemux &) = delete;
DebugSessionToken enableDebugging(std::unique_ptr<RuntimeAdapter> adapter, const std::string &title);
void disableDebugging(DebugSessionToken session);
private:
int addPage(std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler> conn);
void removePage(int pageId);
facebook::react::jsinspector_modern::IInspector &globalInspector_;
std::mutex mutex_;
std::unordered_map<int, std::shared_ptr<hermes::inspector_modern::chrome::CDPHandler>> conns_;
std::shared_ptr<std::unordered_set<std::string>> inspectedContexts_;
};
} // namespace facebook::hermes::inspector_modern::chrome
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)

View File

@@ -0,0 +1,129 @@
/*
* 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.
*/
#ifdef HERMES_ENABLE_DEBUGGER
#include "HermesRuntimeAgentDelegate.h"
#include <hermes/AsyncDebuggerAPI.h>
#include <hermes/cdp/CDPAgent.h>
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
#include <utility>
using namespace facebook::hermes;
namespace facebook::react::jsinspector_modern {
class HermesRuntimeAgentDelegate::Impl final : public RuntimeAgentDelegate {
using HermesState = hermes::cdp::State;
struct HermesStateWrapper : public ExportedState {
explicit HermesStateWrapper(HermesState state) : state_(std::move(state)) {}
static HermesState unwrapDestructively(ExportedState* wrapper) {
if (wrapper == nullptr) {
return {};
}
if (auto* typedWrapper = dynamic_cast<HermesStateWrapper*>(wrapper)) {
return std::move(typedWrapper->state_);
}
return {};
}
private:
HermesState state_;
};
public:
Impl(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
const RuntimeExecutor& runtimeExecutor)
: hermes_(
hermes::cdp::CDPAgent::create(
executionContextDescription.id,
runtimeTargetDelegate.getCDPDebugAPI(),
// RuntimeTask takes a HermesRuntime whereas our RuntimeExecutor
// takes a jsi::Runtime.
[runtimeExecutor,
&runtime](facebook::hermes::debugger::RuntimeTask fn) {
runtimeExecutor(
[&runtime, fn = std::move(fn)](auto&) { fn(runtime); });
},
std::move(frontendChannel),
HermesStateWrapper::unwrapDestructively(
previouslyExportedState.get()))) {
if (sessionState.isRuntimeDomainEnabled) {
hermes_->enableRuntimeDomain();
}
if (sessionState.isDebuggerDomainEnabled) {
hermes_->enableDebuggerDomain();
}
}
bool handleRequest(const cdp::PreparsedRequest& req) override {
if (req.method.starts_with("Log.") || req.method.starts_with("Network.")) {
// Since we know Hermes doesn't do anything useful with Log or Network
// messages, but our containing HostAgent will, bail out early.
// TODO: We need a way to negotiate this more dynamically with Hermes
// through the API.
return false;
}
// Forward everything else to Hermes's CDPAgent.
hermes_->handleCommand(req.toJson());
// Let the call know that this request is handled (i.e. it is Hermes's
// responsibility to respond with either success or an error).
return true;
}
std::unique_ptr<ExportedState> getExportedState() override {
return std::make_unique<HermesStateWrapper>(hermes_->getState());
}
private:
std::unique_ptr<hermes::cdp::CDPAgent> hermes_;
};
HermesRuntimeAgentDelegate::HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
HermesRuntime& runtime,
HermesRuntimeTargetDelegate& runtimeTargetDelegate,
RuntimeExecutor runtimeExecutor)
: impl_(
std::make_unique<Impl>(
std::move(frontendChannel),
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
runtime,
runtimeTargetDelegate,
std::move(runtimeExecutor))) {}
bool HermesRuntimeAgentDelegate::handleRequest(
const cdp::PreparsedRequest& req) {
return impl_->handleRequest(req);
}
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
HermesRuntimeAgentDelegate::getExportedState() {
return impl_->getExportedState();
}
} // namespace facebook::react::jsinspector_modern
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,80 @@
/*
* 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.
*/
#pragma once
#ifdef HERMES_ENABLE_DEBUGGER
#include "HermesRuntimeTargetDelegate.h"
#include <ReactCommon/RuntimeExecutor.h>
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
namespace facebook::react::jsinspector_modern {
/**
* A RuntimeAgentDelegate that handles requests from the Chrome DevTools
* Protocol for an instance of Hermes, using the modern CDPAgent API.
*/
class HermesRuntimeAgentDelegate : public RuntimeAgentDelegate {
public:
/**
* \param frontendChannel A channel used to send responses and events to the
* frontend.
* \param sessionState The state of the current CDP session. This will only
* be accessed on the main thread (during the constructor, in handleRequest,
* etc).
* \param previouslyExportedState The exported state from a previous instance
* of RuntimeAgentDelegate (NOT necessarily HermesRuntimeAgentDelegate).
* This may be nullptr, and if not nullptr it may be of any concrete type that
* implements RuntimeAgentDelegate::ExportedState.
* \param executionContextDescription A description of the execution context
* represented by this runtime. This is used for disambiguating the
* source/destination of CDP messages when there are multiple runtimes
* (concurrently or over the life of a Host).
* \param runtime The HermesRuntime that this agent is attached to. The caller
* is responsible for keeping this object alive for the duration of the
* \c HermesRuntimeAgentDelegate lifetime.
* \param runtimeTargetDelegate The \c HermesRuntimeTargetDelegate object
* object for the passed runtime.
* \param runtimeExecutor A callback for scheduling work on the JS thread.
* \c runtimeExecutor may drop scheduled work if the runtime is destroyed
* first.
*/
HermesRuntimeAgentDelegate(
FrontendChannel frontendChannel,
SessionState &sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState> previouslyExportedState,
const ExecutionContextDescription &executionContextDescription,
hermes::HermesRuntime &runtime,
HermesRuntimeTargetDelegate &runtimeTargetDelegate,
RuntimeExecutor runtimeExecutor);
/**
* Handle a CDP request. The response will be sent over the provided
* \c FrontendChannel synchronously or asynchronously.
* \param req The parsed request.
* \returns true if this agent has responded, or will respond asynchronously,
* to the request (with either a success or error message). False if the
* agent expects another agent to respond to the request instead.
*/
bool handleRequest(const cdp::PreparsedRequest &req) override;
std::unique_ptr<RuntimeAgentDelegate::ExportedState> getExportedState() override;
private:
class Impl;
const std::unique_ptr<Impl> impl_;
};
} // namespace facebook::react::jsinspector_modern
#else
#error "HERMES_ENABLE_DEBUGGER must be enabled to use HermesRuntimeAgentDelegate."
#endif // HERMES_ENABLE_DEBUGGER

View File

@@ -0,0 +1,176 @@
/*
* 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.
*/
#include "HermesRuntimeSamplingProfileSerializer.h"
#include <oscompat/OSCompat.h>
#include <variant>
namespace facebook::react::jsinspector_modern::tracing {
namespace {
namespace fhsp = facebook::hermes::sampling_profiler;
/// Fallback script ID for call frames, when Hermes didn't provide one or when
/// this frame is part of the VM, like native functions, used for parity with
/// Chromium + V8.
constexpr uint32_t FALLBACK_SCRIPT_ID = 0;
/// Garbage collector frame name, used for parity with Chromium + V8.
constexpr std::string_view GARBAGE_COLLECTOR_FRAME_NAME = "(garbage collector)";
/// Filters out Hermes Suspend frames related to Debugger.
/// Even though Debugger domain is expected to be disabled, Hermes might run
/// Debugger loop while recording sampling profile. We only allow GC frames.
inline bool shouldIgnoreHermesFrame(
const fhsp::ProfileSampleCallStackSuspendFrame& suspendFrame) {
return suspendFrame.getSuspendFrameKind() !=
fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC;
}
RuntimeSamplingProfile::SampleCallStackFrame convertNativeHermesFrame(
const fhsp::ProfileSampleCallStackNativeFunctionFrame& frame) {
return RuntimeSamplingProfile::SampleCallStackFrame{
.kind =
RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction,
.scriptId =
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
// for native function, no script ID to reference.
.functionName = frame.getFunctionName(),
};
}
RuntimeSamplingProfile::SampleCallStackFrame convertHostFunctionHermesFrame(
const fhsp::ProfileSampleCallStackHostFunctionFrame& frame) {
return RuntimeSamplingProfile::SampleCallStackFrame{
.kind = RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction,
.scriptId =
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
// for host function, no script ID to reference.
.functionName = frame.getFunctionName(),
};
}
RuntimeSamplingProfile::SampleCallStackFrame convertSuspendHermesFrame(
const fhsp::ProfileSampleCallStackSuspendFrame& frame) {
if (frame.getSuspendFrameKind() ==
fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC) {
return RuntimeSamplingProfile::SampleCallStackFrame{
.kind = RuntimeSamplingProfile::SampleCallStackFrame::Kind::
GarbageCollector,
.scriptId = FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no
// script ID to reference.
.functionName = GARBAGE_COLLECTOR_FRAME_NAME,
};
}
// We should have filtered out Debugger Suspend frames before in
// shouldFilterOutHermesFrame().
throw std::logic_error{"Unexpected Suspend frame found in Hermes call stack"};
}
RuntimeSamplingProfile::SampleCallStackFrame convertJSFunctionHermesFrame(
const fhsp::ProfileSampleCallStackJSFunctionFrame& frame) {
return RuntimeSamplingProfile::SampleCallStackFrame{
.kind = RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
.scriptId = frame.getScriptId(),
.functionName = frame.getFunctionName(),
.scriptURL = frame.hasScriptUrl()
? std::optional<std::string_view>{frame.getScriptUrl()}
: std::nullopt,
.lineNumber = frame.hasFunctionLineNumber()
? std::optional<uint32_t>{frame.getFunctionLineNumber() - 1}
// Hermes VM keeps line numbers as 1-based. Convert
// to 0-based.
: std::nullopt,
.columnNumber = frame.hasFunctionColumnNumber()
? std::optional<uint32_t>{frame.getFunctionColumnNumber() - 1}
// Hermes VM keeps column numbers as 1-based. Convert to
// 0-based.
: std::nullopt,
};
}
RuntimeSamplingProfile::Sample convertHermesSampleToTracingSample(
const fhsp::ProfileSample& hermesSample) {
uint64_t reconciledTimestamp = hermesSample.getTimestamp();
const auto callStackRange = hermesSample.getCallStackFramesRange();
std::vector<RuntimeSamplingProfile::SampleCallStackFrame>
reconciledSampleCallStack;
reconciledSampleCallStack.reserve(hermesSample.getCallStackFramesCount());
for (const auto& hermesFrame : callStackRange) {
if (std::holds_alternative<fhsp::ProfileSampleCallStackSuspendFrame>(
hermesFrame)) {
const auto& suspendFrame =
std::get<fhsp::ProfileSampleCallStackSuspendFrame>(hermesFrame);
if (shouldIgnoreHermesFrame(suspendFrame)) {
continue;
}
reconciledSampleCallStack.emplace_back(
convertSuspendHermesFrame(suspendFrame));
} else if (std::holds_alternative<
fhsp::ProfileSampleCallStackNativeFunctionFrame>(
hermesFrame)) {
const auto& nativeFunctionFrame =
std::get<fhsp::ProfileSampleCallStackNativeFunctionFrame>(
hermesFrame);
reconciledSampleCallStack.emplace_back(
convertNativeHermesFrame(nativeFunctionFrame));
} else if (std::holds_alternative<
fhsp::ProfileSampleCallStackHostFunctionFrame>(
hermesFrame)) {
const auto& hostFunctionFrame =
std::get<fhsp::ProfileSampleCallStackHostFunctionFrame>(hermesFrame);
reconciledSampleCallStack.emplace_back(
convertHostFunctionHermesFrame(hostFunctionFrame));
} else if (std::holds_alternative<
fhsp::ProfileSampleCallStackJSFunctionFrame>(hermesFrame)) {
const auto& jsFunctionFrame =
std::get<fhsp::ProfileSampleCallStackJSFunctionFrame>(hermesFrame);
reconciledSampleCallStack.emplace_back(
convertJSFunctionHermesFrame(jsFunctionFrame));
} else {
throw std::logic_error{"Unknown Hermes stack frame kind"};
}
}
return RuntimeSamplingProfile::Sample{
reconciledTimestamp,
hermesSample.getThreadId(),
std::move(reconciledSampleCallStack)};
}
} // namespace
/* static */ RuntimeSamplingProfile
HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile(
hermes::sampling_profiler::Profile hermesProfile) {
const auto samplesRange = hermesProfile.getSamplesRange();
std::vector<RuntimeSamplingProfile::Sample> reconciledSamples;
reconciledSamples.reserve(hermesProfile.getSamplesCount());
for (const auto& hermesSample : samplesRange) {
RuntimeSamplingProfile::Sample reconciledSample =
convertHermesSampleToTracingSample(hermesSample);
reconciledSamples.push_back(std::move(reconciledSample));
}
return RuntimeSamplingProfile{
"Hermes",
// Hermes' Profile should be the source of truth for this,
// but it is safe to reuse the process ID here, since everything runs in
// the same process.
oscompat::getCurrentProcessId(),
std::move(reconciledSamples),
std::make_unique<RawHermesRuntimeProfile>(std::move(hermesProfile))};
}
} // namespace facebook::react::jsinspector_modern::tracing

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
#pragma once
#include <hermes/hermes.h>
#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>
namespace facebook::react::jsinspector_modern::tracing {
class RawHermesRuntimeProfile : public RawRuntimeProfile {
public:
explicit RawHermesRuntimeProfile(hermes::sampling_profiler::Profile hermesProfile)
: hermesProfile_{std::move(hermesProfile)}
{
}
private:
hermes::sampling_profiler::Profile hermesProfile_;
};
class HermesRuntimeSamplingProfileSerializer {
public:
static tracing::RuntimeSamplingProfile serializeToTracingSamplingProfile(
hermes::sampling_profiler::Profile hermesProfile);
};
} // namespace facebook::react::jsinspector_modern::tracing

View File

@@ -0,0 +1,374 @@
/*
* 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.
*/
#include <jsi/jsi.h>
#include <jsinspector-modern/RuntimeTarget.h>
#include "HermesRuntimeSamplingProfileSerializer.h"
#include "HermesRuntimeTargetDelegate.h"
// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
// CDPHandler headers or types.
#ifdef HERMES_ENABLE_DEBUGGER
#include "HermesRuntimeAgentDelegate.h"
#include <hermes/cdp/CDPDebugAPI.h>
using namespace facebook::hermes::cdp;
#else
#include <jsinspector-modern/FallbackRuntimeTargetDelegate.h>
#endif // HERMES_ENABLE_DEBUGGER
#include <utility>
using namespace facebook::hermes;
namespace facebook::react::jsinspector_modern {
namespace {
const uint16_t HERMES_SAMPLING_FREQUENCY_HZ = 10000;
class HermesRuntimeSamplingProfileDelegate {
public:
explicit HermesRuntimeSamplingProfileDelegate(
std::shared_ptr<HermesRuntime> hermesRuntime)
: hermesRuntime_(std::move(hermesRuntime)) {}
void startSampling() {
auto* hermesAPI = jsi::castInterface<IHermesRootAPI>(makeHermesRootAPI());
hermesAPI->enableSamplingProfiler(HERMES_SAMPLING_FREQUENCY_HZ);
}
void stopSampling() {
auto* hermesAPI = jsi::castInterface<IHermesRootAPI>(makeHermesRootAPI());
hermesAPI->disableSamplingProfiler();
}
tracing::RuntimeSamplingProfile collectSamplingProfile() {
return tracing::HermesRuntimeSamplingProfileSerializer::
serializeToTracingSamplingProfile(
hermesRuntime_->dumpSampledTraceToProfile());
}
private:
std::shared_ptr<HermesRuntime> hermesRuntime_;
};
} // namespace
#ifdef HERMES_ENABLE_DEBUGGER
class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
using HermesStackTrace = debugger::StackTrace;
class HermesStackTraceWrapper : public StackTrace {
public:
explicit HermesStackTraceWrapper(HermesStackTrace&& hermesStackTrace)
: hermesStackTrace_{std::move(hermesStackTrace)} {}
HermesStackTrace& operator*() {
return hermesStackTrace_;
}
HermesStackTrace* operator->() {
return &hermesStackTrace_;
}
const HermesStackTrace& operator*() const {
return hermesStackTrace_;
}
const HermesStackTrace* operator->() const {
return &hermesStackTrace_;
}
private:
HermesStackTrace hermesStackTrace_;
};
public:
explicit Impl(
HermesRuntimeTargetDelegate& delegate,
std::shared_ptr<HermesRuntime> hermesRuntime)
: delegate_(delegate),
runtime_(hermesRuntime),
cdpDebugAPI_(CDPDebugAPI::create(*runtime_)),
samplingProfileDelegate_(
std::make_unique<HermesRuntimeSamplingProfileDelegate>(
std::move(hermesRuntime))) {}
CDPDebugAPI& getCDPDebugAPI() {
return *cdpDebugAPI_;
}
// RuntimeTargetDelegate methods
std::unique_ptr<RuntimeAgentDelegate> createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
RuntimeExecutor runtimeExecutor) override {
return std::unique_ptr<RuntimeAgentDelegate>(new HermesRuntimeAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
*runtime_,
delegate_,
std::move(runtimeExecutor)));
}
void addConsoleMessage(jsi::Runtime& /*unused*/, ConsoleMessage message)
override {
using HermesConsoleMessage = facebook::hermes::cdp::ConsoleMessage;
using HermesConsoleAPIType = facebook::hermes::cdp::ConsoleAPIType;
HermesConsoleAPIType type{};
switch (message.type) {
case ConsoleAPIType::kLog:
type = HermesConsoleAPIType::kLog;
break;
case ConsoleAPIType::kDebug:
type = HermesConsoleAPIType::kDebug;
break;
case ConsoleAPIType::kInfo:
type = HermesConsoleAPIType::kInfo;
break;
case ConsoleAPIType::kError:
type = HermesConsoleAPIType::kError;
break;
case ConsoleAPIType::kWarning:
type = HermesConsoleAPIType::kWarning;
break;
case ConsoleAPIType::kDir:
type = HermesConsoleAPIType::kDir;
break;
case ConsoleAPIType::kDirXML:
type = HermesConsoleAPIType::kDirXML;
break;
case ConsoleAPIType::kTable:
type = HermesConsoleAPIType::kTable;
break;
case ConsoleAPIType::kTrace:
type = HermesConsoleAPIType::kTrace;
break;
case ConsoleAPIType::kStartGroup:
type = HermesConsoleAPIType::kStartGroup;
break;
case ConsoleAPIType::kStartGroupCollapsed:
type = HermesConsoleAPIType::kStartGroupCollapsed;
break;
case ConsoleAPIType::kEndGroup:
type = HermesConsoleAPIType::kEndGroup;
break;
case ConsoleAPIType::kClear:
type = HermesConsoleAPIType::kClear;
break;
case ConsoleAPIType::kAssert:
type = HermesConsoleAPIType::kAssert;
break;
case ConsoleAPIType::kTimeEnd:
type = HermesConsoleAPIType::kTimeEnd;
break;
case ConsoleAPIType::kCount:
type = HermesConsoleAPIType::kCount;
break;
default:
throw std::logic_error{"Unknown console message type"};
}
HermesStackTrace hermesStackTrace{};
if (auto hermesStackTraceWrapper =
dynamic_cast<HermesStackTraceWrapper*>(message.stackTrace.get())) {
hermesStackTrace = std::move(**hermesStackTraceWrapper);
}
HermesConsoleMessage hermesConsoleMessage{
message.timestamp,
type,
std::move(message.args),
std::move(hermesStackTrace)};
cdpDebugAPI_->addConsoleMessage(std::move(hermesConsoleMessage));
}
bool supportsConsole() const override {
return true;
}
std::unique_ptr<StackTrace> captureStackTrace(
jsi::Runtime& /* runtime */,
size_t /* framesToSkip */) override {
// TODO(moti): Pass framesToSkip to Hermes. Ignoring framesToSkip happens
// to work for our current use case, because the HostFunction frame we want
// to skip is stripped by CDPDebugAPI::addConsoleMessage before being sent
// to the client. This is still conceptually wrong and could block us from
// properly representing the stack trace in other use cases, where native
// frames aren't stripped on serialisation.
return std::make_unique<HermesStackTraceWrapper>(
runtime_->getDebugger().captureStackTrace());
}
void enableSamplingProfiler() override {
samplingProfileDelegate_->startSampling();
}
void disableSamplingProfiler() override {
samplingProfileDelegate_->stopSampling();
}
tracing::RuntimeSamplingProfile collectSamplingProfile() override {
return samplingProfileDelegate_->collectSamplingProfile();
}
std::optional<folly::dynamic> serializeStackTrace(
const StackTrace& stackTrace) override {
if (auto* hermesStackTraceWrapper =
dynamic_cast<const HermesStackTraceWrapper*>(&stackTrace)) {
// The logic below is duplicated from
// facebook::hermes::cdp::message::makeCallFrames in
// hermes/cdp/MessageConverters.cpp (and rewritten to use Folly).
// TODO: Use a suitable Hermes API (D83560910 / D83560972 / D83562078) to
// serialize the stack trace to CDP-formatted JSON.
folly::dynamic cdpStackTrace = folly::dynamic::object();
auto& hermesStackTrace = **hermesStackTraceWrapper;
if (hermesStackTrace.callFrameCount() > 0) {
folly::dynamic callFrames = folly::dynamic::array();
callFrames.reserve(hermesStackTrace.callFrameCount());
for (int i = 0, n = hermesStackTrace.callFrameCount(); i != n; i++) {
auto callFrame = hermesStackTrace.callFrameForIndex(i);
if (callFrame.location.fileId ==
facebook::hermes::debugger::kInvalidLocation) {
continue;
}
folly::dynamic callFrameObj = folly::dynamic::object();
callFrameObj["functionName"] = callFrame.functionName;
callFrameObj["scriptId"] = std::to_string(callFrame.location.fileId);
callFrameObj["url"] = callFrame.location.fileName;
if (callFrame.location.line !=
facebook::hermes::debugger::kInvalidLocation) {
callFrameObj["lineNumber"] = callFrame.location.line - 1;
}
if (callFrame.location.column !=
facebook::hermes::debugger::kInvalidLocation) {
callFrameObj["columnNumber"] = callFrame.location.column - 1;
}
callFrames.push_back(std::move(callFrameObj));
}
cdpStackTrace["callFrames"] = std::move(callFrames);
}
return cdpStackTrace;
}
return std::nullopt;
}
private:
HermesRuntimeTargetDelegate& delegate_;
std::shared_ptr<HermesRuntime> runtime_;
const std::unique_ptr<CDPDebugAPI> cdpDebugAPI_;
std::unique_ptr<HermesRuntimeSamplingProfileDelegate>
samplingProfileDelegate_;
};
#else
/**
* A stub for HermesRuntimeTargetDelegate when Hermes is compiled without
* debugging support.
*/
class HermesRuntimeTargetDelegate::Impl final
: public FallbackRuntimeTargetDelegate {
public:
explicit Impl(
HermesRuntimeTargetDelegate&,
std::shared_ptr<HermesRuntime> hermesRuntime)
: FallbackRuntimeTargetDelegate{hermesRuntime->description()},
samplingProfileDelegate_(
std::make_unique<HermesRuntimeSamplingProfileDelegate>(
std::move(hermesRuntime))) {}
void enableSamplingProfiler() override {
samplingProfileDelegate_->startSampling();
}
void disableSamplingProfiler() override {
samplingProfileDelegate_->stopSampling();
}
tracing::RuntimeSamplingProfile collectSamplingProfile() override {
return samplingProfileDelegate_->collectSamplingProfile();
}
private:
std::unique_ptr<HermesRuntimeSamplingProfileDelegate>
samplingProfileDelegate_;
};
#endif // HERMES_ENABLE_DEBUGGER
HermesRuntimeTargetDelegate::HermesRuntimeTargetDelegate(
std::shared_ptr<HermesRuntime> hermesRuntime)
: impl_(std::make_unique<Impl>(*this, std::move(hermesRuntime))) {}
HermesRuntimeTargetDelegate::~HermesRuntimeTargetDelegate() = default;
std::unique_ptr<RuntimeAgentDelegate>
HermesRuntimeTargetDelegate::createAgentDelegate(
FrontendChannel frontendChannel,
SessionState& sessionState,
std::unique_ptr<RuntimeAgentDelegate::ExportedState>
previouslyExportedState,
const ExecutionContextDescription& executionContextDescription,
RuntimeExecutor runtimeExecutor) {
return impl_->createAgentDelegate(
frontendChannel,
sessionState,
std::move(previouslyExportedState),
executionContextDescription,
std::move(runtimeExecutor));
}
void HermesRuntimeTargetDelegate::addConsoleMessage(
jsi::Runtime& runtime,
ConsoleMessage message) {
impl_->addConsoleMessage(runtime, std::move(message));
}
bool HermesRuntimeTargetDelegate::supportsConsole() const {
return impl_->supportsConsole();
}
std::unique_ptr<StackTrace> HermesRuntimeTargetDelegate::captureStackTrace(
jsi::Runtime& runtime,
size_t framesToSkip) {
return impl_->captureStackTrace(runtime, framesToSkip);
}
void HermesRuntimeTargetDelegate::enableSamplingProfiler() {
impl_->enableSamplingProfiler();
}
void HermesRuntimeTargetDelegate::disableSamplingProfiler() {
impl_->disableSamplingProfiler();
}
tracing::RuntimeSamplingProfile
HermesRuntimeTargetDelegate::collectSamplingProfile() {
return impl_->collectSamplingProfile();
}
std::optional<folly::dynamic> HermesRuntimeTargetDelegate::serializeStackTrace(
const StackTrace& stackTrace) {
return impl_->serializeStackTrace(stackTrace);
}
#ifdef HERMES_ENABLE_DEBUGGER
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
return impl_->getCDPDebugAPI();
}
#endif
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,75 @@
/*
* 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.
*/
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <cxxreact/MessageQueueThread.h>
#include <hermes/hermes.h>
#include <jsinspector-modern/ReactCdp.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/cdp/CDPDebugAPI.h>
#endif
#include <memory>
namespace facebook::react::jsinspector_modern {
/**
* A RuntimeTargetDelegate that enables debugging a Hermes runtime over CDP.
*/
class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {
public:
/**
* Creates a HermesRuntimeTargetDelegate for the given runtime.
*/
explicit HermesRuntimeTargetDelegate(std::shared_ptr<hermes::HermesRuntime> hermesRuntime);
~HermesRuntimeTargetDelegate() override;
// RuntimeTargetDelegate methods
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate> createAgentDelegate(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState &sessionState,
std::unique_ptr<jsinspector_modern::RuntimeAgentDelegate::ExportedState> previouslyExportedState,
const jsinspector_modern::ExecutionContextDescription &executionContextDescription,
RuntimeExecutor runtimeExecutor) override;
void addConsoleMessage(jsi::Runtime &runtime, ConsoleMessage message) override;
bool supportsConsole() const override;
std::unique_ptr<StackTrace> captureStackTrace(jsi::Runtime &runtime, size_t framesToSkip) override;
void enableSamplingProfiler() override;
void disableSamplingProfiler() override;
tracing::RuntimeSamplingProfile collectSamplingProfile() override;
std::optional<folly::dynamic> serializeStackTrace(const StackTrace &stackTrace) override;
private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net
// effect is that callers can include HermesRuntimeTargetDelegate.h without
// setting HERMES_ENABLE_DEBUGGER one way or the other.
class Impl;
// Callers within this library may set HERMES_ENABLE_DEBUGGER to see this extra
// API.
#ifdef HERMES_ENABLE_DEBUGGER
friend class HermesRuntimeAgentDelegate;
hermes::cdp::CDPDebugAPI &getCDPDebugAPI();
#endif
std::unique_ptr<Impl> impl_;
};
} // namespace facebook::react::jsinspector_modern

View File

@@ -0,0 +1,37 @@
/*
* 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.
*/
#include "Registration.h"
#include "ConnectionDemux.h"
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
namespace facebook::hermes::inspector_modern::chrome {
namespace {
ConnectionDemux& demux() {
static ConnectionDemux instance{
facebook::react::jsinspector_modern::getInspectorInstance()};
return instance;
}
} // namespace
DebugSessionToken enableDebugging(
std::unique_ptr<RuntimeAdapter> adapter,
const std::string& title) {
return demux().enableDebugging(std::move(adapter), title);
}
void disableDebugging(DebugSessionToken session) {
demux().disableDebugging(session);
}
} // namespace facebook::hermes::inspector_modern::chrome
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
#pragma once
#if defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)
#include <memory>
#include <string>
#include <hermes/hermes.h>
#include <hermes/inspector/RuntimeAdapter.h>
namespace facebook::hermes::inspector_modern::chrome {
using DebugSessionToken = int;
/*
* enableDebugging adds this runtime to the list of debuggable JS targets
* (called "pages" in the higher-level React Native API) in this process. It
* should be called before any JS runs in the runtime. The returned token
* can be used to disable debugging for this runtime.
*/
extern DebugSessionToken enableDebugging(std::unique_ptr<RuntimeAdapter> adapter, const std::string &title);
/*
* disableDebugging removes this runtime from the list of debuggable JS targets
* in this process. The runtime to remove is identified by the token returned
* from enableDebugging.
*/
extern void disableDebugging(DebugSessionToken session);
} // namespace facebook::hermes::inspector_modern::chrome
#endif // defined(HERMES_ENABLE_DEBUGGER) && !defined(HERMES_V1_ENABLED)

View File

@@ -0,0 +1,148 @@
/*
* 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.
*/
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <hermes/inspector-modern/chrome/ConnectionDemux.h>
#include <jsinspector-modern/InspectorInterfaces.h>
namespace facebook {
namespace hermes {
namespace inspector_modern {
namespace chrome {
using ::facebook::react::jsinspector_modern::IInspector;
using ::facebook::react::jsinspector_modern::InspectorPageDescription;
using ::facebook::react::jsinspector_modern::IRemoteConnection;
namespace {
std::unordered_map<int, std::string> makePageMap(
const std::vector<InspectorPageDescription>& pages) {
std::unordered_map<int, std::string> pageMap;
for (auto& page : pages) {
pageMap[page.id] = page.title;
}
return pageMap;
}
void expectPages(
IInspector& inspector,
const std::unordered_map<int, std::string>& expected) {
auto pages = makePageMap(inspector.getPages());
EXPECT_EQ(pages, expected);
}
class TestRemoteConnection : public IRemoteConnection {
public:
class Data {
public:
void expectDisconnected() {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait_for(
lock, std::chrono::milliseconds(2500), [&] { return !connected_; });
EXPECT_FALSE(connected_);
}
void setDisconnected() {
std::scoped_lock lock(mutex_);
connected_ = false;
cv_.notify_one();
}
private:
std::mutex mutex_;
std::condition_variable cv_;
bool connected_{true};
};
TestRemoteConnection() : data_(std::make_shared<Data>()) {}
~TestRemoteConnection() {}
void onMessage(std::string message) override {}
void onDisconnect() override {
data_->setDisconnected();
}
std::shared_ptr<Data> getData() {
return data_;
}
private:
std::shared_ptr<Data> data_;
};
}; // namespace
TEST(ConnectionDemuxTests, TestEnableDisable) {
std::shared_ptr<HermesRuntime> runtime1(
facebook::hermes::makeHermesRuntime());
std::shared_ptr<HermesRuntime> runtime2(
facebook::hermes::makeHermesRuntime());
auto inspector =
facebook::react::jsinspector_modern::makeTestInspectorInstance();
ConnectionDemux demux{*inspector};
int id1 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime1), "page1");
int id2 = demux.enableDebugging(
std::make_unique<SharedRuntimeAdapter>(runtime2), "page2");
expectPages(*inspector, {{id1, "page1"}, {id2, "page2"}});
auto remoteConn1 = std::make_unique<TestRemoteConnection>();
auto remoteData1 = remoteConn1->getData();
auto localConn1 = inspector->connect(id1, std::move(remoteConn1));
EXPECT_NE(localConn1.get(), nullptr);
{
// If we connect to the same page id again without disconnecting, we should
// get null
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_EQ(localConn.get(), nullptr);
}
auto remoteConn2 = std::make_unique<TestRemoteConnection>();
auto remoteData2 = remoteConn2->getData();
auto localConn2 = inspector->connect(id2, std::move(remoteConn2));
EXPECT_NE(localConn2.get(), nullptr);
// Disable debugging on runtime2. This should remove its page from the list
// and call onDisconnect on its remoteConn
demux.disableDebugging(id2);
expectPages(*inspector, {{id1, "page1"}});
remoteData2->expectDisconnected();
// Disconnect conn1. Its page should still be in the page list and
// onDisconnect should be called.
localConn1->disconnect();
remoteData1->expectDisconnected();
{
// Should still be able to reconnect after disconnecting
auto remoteConn = std::make_unique<TestRemoteConnection>();
auto localConn = inspector->connect(id1, std::move(remoteConn));
EXPECT_NE(localConn.get(), nullptr);
}
}
} // namespace chrome
} // namespace inspector_modern
} // namespace hermes
} // namespace facebook

1607
node_modules/react-native/ReactCommon/jsc/JSCRuntime.cpp generated vendored Normal file

File diff suppressed because it is too large Load Diff

17
node_modules/react-native/ReactCommon/jsc/JSCRuntime.h generated vendored Normal file
View File

@@ -0,0 +1,17 @@
/*
* 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.
*/
#pragma once
#include <jsi/jsi.h>
#include <memory.h>
namespace facebook::jsc {
std::unique_ptr<jsi::Runtime> makeJSCRuntime();
} // namespace facebook::jsc

View File

@@ -0,0 +1,26 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB js_error_handler_SRC CONFIGURE_DEPENDS *.cpp)
add_library(
jserrorhandler
OBJECT
${js_error_handler_SRC}
)
target_include_directories(jserrorhandler PUBLIC .)
react_native_android_selector(marbufferjni marbufferjni "")
target_link_libraries(jserrorhandler
jsi
callinvoker
folly_runtime
${mapbufferjni}
react_featureflags
)
target_compile_reactnative_options(jserrorhandler PRIVATE)

View File

@@ -0,0 +1,431 @@
/*
* 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.
*/
#include "JsErrorHandler.h"
#include <cxxreact/ErrorUtils.h>
#include <glog/logging.h>
#include <react/bridging/Bridging.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <string>
#include "StackTraceParser.h"
using namespace facebook;
namespace {
std::string quote(const std::string& view) {
return "\"" + view + "\"";
}
int nextExceptionId() {
static int exceptionId = 0;
return exceptionId++;
}
bool isLooselyNull(const jsi::Value& value) {
return value.isNull() || value.isUndefined();
}
bool isEqualTo(
jsi::Runtime& runtime,
const jsi::Value& value,
const std::string& str) {
return jsi::Value::strictEquals(
runtime, value, jsi::String::createFromUtf8(runtime, str));
}
std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) {
return value.toString(runtime).utf8(runtime);
}
bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
return Boolean.call(runtime, value).getBool();
}
void objectAssign(
jsi::Runtime& runtime,
jsi::Object& target,
const jsi::Object& value) {
auto Object = runtime.global().getPropertyAsObject(runtime, "Object");
auto assign = Object.getPropertyAsFunction(runtime, "assign");
assign.callWithThis(runtime, Object, target, value);
}
jsi::Object wrapInErrorIfNecessary(
jsi::Runtime& runtime,
const jsi::Value& value) {
auto Error = runtime.global().getPropertyAsFunction(runtime, "Error");
auto isError =
value.isObject() && value.asObject(runtime).instanceOf(runtime, Error);
auto error = isError
? value.getObject(runtime)
: Error.callAsConstructor(runtime, value).getObject(runtime);
return error;
}
class SetFalseOnDestruct {
std::shared_ptr<bool> _value;
public:
SetFalseOnDestruct(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct& operator=(const SetFalseOnDestruct&) = delete;
SetFalseOnDestruct(SetFalseOnDestruct&&) = delete;
SetFalseOnDestruct& operator=(SetFalseOnDestruct&&) = delete;
explicit SetFalseOnDestruct(std::shared_ptr<bool> value)
: _value(std::move(value)) {}
~SetFalseOnDestruct() {
*_value = false;
}
};
void logErrorWhileReporting(
std::string message,
jsi::JSError& error,
jsi::JSError& originalError) {
LOG(ERROR) << "JsErrorHandler::" << message << std::endl
<< "Js error message: " << error.getMessage() << std::endl
<< "Original js error message: " << originalError.getMessage()
<< std::endl;
}
jsi::Value getBundleMetadata(jsi::Runtime& runtime, jsi::JSError& error) {
auto jsGetBundleMetadataValue =
runtime.global().getProperty(runtime, "__getBundleMetadata");
if (!jsGetBundleMetadataValue.isObject() ||
!jsGetBundleMetadataValue.asObject(runtime).isFunction(runtime)) {
return jsi::Value::null();
}
auto jsGetBundleMetadataValueFn =
jsGetBundleMetadataValue.asObject(runtime).asFunction(runtime);
try {
auto bundleMetadataValue = jsGetBundleMetadataValueFn.call(runtime);
if (bundleMetadataValue.isObject()) {
return bundleMetadataValue;
}
return bundleMetadataValue;
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"getBundleMetadata(): Error raised while calling __getBundleMetadata(). Returning null.",
ex,
error);
}
return jsi::Value::null();
}
} // namespace
namespace facebook::react {
template <>
struct Bridging<JsErrorHandler::ProcessedError::StackFrame> {
static jsi::Object toJs(
jsi::Runtime& runtime,
const JsErrorHandler::ProcessedError::StackFrame& frame) {
auto stackFrame = jsi::Object(runtime);
auto file = bridging::toJs(runtime, frame.file, nullptr);
auto lineNumber = bridging::toJs(runtime, frame.lineNumber, nullptr);
auto column = bridging::toJs(runtime, frame.column, nullptr);
stackFrame.setProperty(runtime, "file", file);
stackFrame.setProperty(runtime, "methodName", frame.methodName);
stackFrame.setProperty(runtime, "lineNumber", lineNumber);
stackFrame.setProperty(runtime, "column", column);
return stackFrame;
}
};
template <>
struct Bridging<JsErrorHandler::ProcessedError> {
static jsi::Object toJs(
jsi::Runtime& runtime,
const JsErrorHandler::ProcessedError& error) {
auto data = jsi::Object(runtime);
data.setProperty(runtime, "message", error.message);
data.setProperty(
runtime,
"originalMessage",
bridging::toJs(runtime, error.originalMessage, nullptr));
data.setProperty(
runtime, "name", bridging::toJs(runtime, error.name, nullptr));
data.setProperty(
runtime,
"componentStack",
bridging::toJs(runtime, error.componentStack, nullptr));
auto stack = jsi::Array(runtime, error.stack.size());
for (size_t i = 0; i < error.stack.size(); i++) {
auto& frame = error.stack[i];
stack.setValueAtIndex(runtime, i, bridging::toJs(runtime, frame));
}
data.setProperty(runtime, "stack", stack);
data.setProperty(runtime, "id", error.id);
data.setProperty(runtime, "isFatal", error.isFatal);
data.setProperty(runtime, "extraData", error.extraData);
return data;
}
};
std::ostream& operator<<(
std::ostream& os,
const JsErrorHandler::ProcessedError::StackFrame& frame) {
auto file = frame.file ? quote(*frame.file) : "nil";
auto methodName = quote(frame.methodName);
auto lineNumber =
frame.lineNumber ? std::to_string(*frame.lineNumber) : "nil";
auto column = frame.column ? std::to_string(*frame.column) : "nil";
os << "StackFrame { .file = " << file << ", .methodName = " << methodName
<< ", .lineNumber = " << lineNumber << ", .column = " << column << " }";
return os;
}
std::ostream& operator<<(
std::ostream& os,
const JsErrorHandler::ProcessedError& error) {
auto message = quote(error.message);
auto originalMessage =
error.originalMessage ? quote(*error.originalMessage) : "nil";
auto name = error.name ? quote(*error.name) : "nil";
auto componentStack =
error.componentStack ? quote(*error.componentStack) : "nil";
auto id = std::to_string(error.id);
auto isFatal = std::to_string(static_cast<int>(error.isFatal));
auto extraData = "jsi::Object{ <omitted> } ";
os << "ProcessedError {\n"
<< " .message = " << message << "\n"
<< " .originalMessage = " << originalMessage << "\n"
<< " .name = " << name << "\n"
<< " .componentStack = " << componentStack << "\n"
<< " .stack = [\n";
for (const auto& frame : error.stack) {
os << " " << frame << ", \n";
}
os << " ]\n"
<< " .id = " << id << "\n"
<< " .isFatal " << isFatal << "\n"
<< " .extraData = " << extraData << "\n"
<< "}";
return os;
}
JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError)
: _onJsError(std::move(onJsError)),
_inErrorHandler(std::make_shared<bool>(false)) {
};
JsErrorHandler::~JsErrorHandler() {}
void JsErrorHandler::handleError(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole) {
// TODO: Current error parsing works and is stable. Can investigate using
// REGEX_HERMES to get additional Hermes data, though it requires JS setup
if (!ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() &&
_isRuntimeReady) {
try {
handleJSError(runtime, error, isFatal);
return;
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleError(): Error raised while reporting using js pipeline. Using c++ pipeline instead.",
ex,
error);
// Re-try reporting using the c++ pipeline
_hasHandledFatalError = false;
}
}
handleErrorWithCppPipeline(runtime, error, isFatal, logToConsole);
}
void JsErrorHandler::handleErrorWithCppPipeline(
jsi::Runtime& runtime,
jsi::JSError& error,
bool isFatal,
bool logToConsole) {
*_inErrorHandler = true;
SetFalseOnDestruct temp{_inErrorHandler};
auto message = error.getMessage();
auto errorObj = wrapInErrorIfNecessary(runtime, error.value());
auto componentStackValue = errorObj.getProperty(runtime, "componentStack");
if (!isLooselyNull(componentStackValue)) {
message += "\n" + stringifyToCpp(runtime, componentStackValue);
}
auto nameValue = errorObj.getProperty(runtime, "name");
auto name = (isLooselyNull(nameValue) || isEqualTo(runtime, nameValue, ""))
? std::nullopt
: std::optional(stringifyToCpp(runtime, nameValue));
if (name && !message.starts_with(*name + ": ")) {
message = *name + ": " + message;
}
auto extraDataKey = jsi::PropNameID::forUtf8(runtime, "RN$ErrorExtraDataKey");
auto extraDataValue = errorObj.getProperty(runtime, extraDataKey);
auto extraData = jsi::Object(runtime);
if (extraDataValue.isObject()) {
objectAssign(runtime, extraData, extraDataValue.asObject(runtime));
}
auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine");
auto isDEV =
isTruthy(runtime, runtime.global().getProperty(runtime, "__DEV__"));
extraData.setProperty(runtime, "jsEngine", jsEngineValue);
extraData.setProperty(runtime, "rawStack", error.getStack());
extraData.setProperty(runtime, "__DEV__", isDEV);
extraData.setProperty(
runtime, "bundleMetadata", getBundleMetadata(runtime, error));
auto cause = errorObj.getProperty(runtime, "cause");
if (cause.isObject()) {
auto causeObj = cause.asObject(runtime);
// TODO: Consider just forwarding all properties. For now, just forward the
// stack properties to maintain symmetry with js pipeline
auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols");
extraData.setProperty(runtime, "stackSymbols", stackSymbols);
auto stackReturnAddresses =
causeObj.getProperty(runtime, "stackReturnAddresses");
extraData.setProperty(
runtime, "stackReturnAddresses", stackReturnAddresses);
auto stackElements = causeObj.getProperty(runtime, "stackElements");
extraData.setProperty(runtime, "stackElements", stackElements);
}
auto originalMessage = message == error.getMessage()
? std::nullopt
: std::optional(error.getMessage());
auto componentStack = !componentStackValue.isString()
? std::nullopt
: std::optional(componentStackValue.asString(runtime).utf8(runtime));
auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal");
auto stackFrames = StackTraceParser::parse(isHermes, error.getStack());
auto id = nextExceptionId();
ProcessedError processedError = {
.message =
_isRuntimeReady ? message : ("[runtime not ready]: " + message),
.originalMessage = originalMessage,
.name = name,
.componentStack = componentStack,
.stack = stackFrames,
.id = id,
.isFatal = isFatal,
.extraData = std::move(extraData),
};
auto data = bridging::toJs(runtime, processedError);
auto isComponentError =
isTruthy(runtime, errorObj.getProperty(runtime, "isComponentError"));
data.setProperty(runtime, "isComponentError", isComponentError);
if (logToConsole) {
try {
auto console = runtime.global().getPropertyAsObject(runtime, "console");
auto errorFn = console.getPropertyAsFunction(runtime, "error");
auto finalMessage =
jsi::String::createFromUtf8(runtime, processedError.message);
errorFn.callWithThis(runtime, console, finalMessage);
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleErrorWithCppPipeline(): Error while logToConsole: ",
ex,
error);
}
}
std::shared_ptr<bool> shouldPreventDefault = std::make_shared<bool>(false);
auto preventDefault = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "preventDefault"),
0,
[shouldPreventDefault](
jsi::Runtime& /*rt*/,
const jsi::Value& /*thisVal*/,
const jsi::Value* /*args*/,
size_t /*count*/) {
*shouldPreventDefault = true;
return jsi::Value::undefined();
});
data.setProperty(runtime, "preventDefault", preventDefault);
for (auto& errorListener : _errorListeners) {
try {
errorListener(runtime, jsi::Value(runtime, data));
} catch (jsi::JSError& ex) {
logErrorWhileReporting(
"handleErrorWithCppPipeline(): Error raised inside an error listener. Executing next listener.",
ex,
error);
}
}
if (*shouldPreventDefault) {
return;
}
auto errorType = errorObj.getProperty(runtime, "type");
auto isWarn = isEqualTo(runtime, errorType, "warn");
if (isFatal || !isWarn) {
if (isFatal) {
if (_hasHandledFatalError) {
return;
}
_hasHandledFatalError = true;
}
_onJsError(runtime, processedError);
}
}
void JsErrorHandler::registerErrorListener(
const std::function<void(jsi::Runtime&, jsi::Value)>& errorListener) {
_errorListeners.push_back(errorListener);
}
bool JsErrorHandler::hasHandledFatalError() {
return _hasHandledFatalError;
}
void JsErrorHandler::setRuntimeReady() {
_isRuntimeReady = true;
}
bool JsErrorHandler::isRuntimeReady() {
return _isRuntimeReady;
}
void JsErrorHandler::notifyOfFatalError() {
_hasHandledFatalError = true;
}
bool JsErrorHandler::inErrorHandler() {
return *_inErrorHandler;
}
} // namespace facebook::react

View File

@@ -0,0 +1,68 @@
/*
* 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.
*/
#pragma once
#include <jsi/jsi.h>
#include <iostream>
#include <optional>
namespace facebook::react {
class JsErrorHandler {
public:
struct ProcessedError {
struct StackFrame {
std::optional<std::string> file;
std::string methodName;
std::optional<int> lineNumber;
std::optional<int> column;
friend std::ostream &operator<<(std::ostream &os, const StackFrame &frame);
};
std::string message;
std::optional<std::string> originalMessage;
std::optional<std::string> name;
std::optional<std::string> componentStack;
std::vector<StackFrame> stack;
int id;
bool isFatal;
jsi::Object extraData;
friend std::ostream &operator<<(std::ostream &os, const ProcessedError &error);
};
using OnJsError = std::function<void(jsi::Runtime &runtime, const ProcessedError &error)>;
explicit JsErrorHandler(OnJsError onJsError);
~JsErrorHandler();
void handleError(jsi::Runtime &runtime, jsi::JSError &error, bool isFatal, bool logToConsole = true);
bool hasHandledFatalError();
void registerErrorListener(const std::function<void(jsi::Runtime &, jsi::Value)> &listener);
void setRuntimeReady();
bool isRuntimeReady();
void notifyOfFatalError();
bool inErrorHandler();
private:
/**
* This callback:
* 1. Shouldn't retain the ReactInstance. So that we don't get retain cycles.
* 2. Should be implemented by something that can outlive the react instance
* (both before init and after teardown). So that errors during init and
* teardown get reported properly.
**/
OnJsError _onJsError;
bool _hasHandledFatalError{};
bool _isRuntimeReady{};
std::shared_ptr<bool> _inErrorHandler;
std::vector<std::function<void(jsi::Runtime &, jsi::Value)>> _errorListeners;
void handleErrorWithCppPipeline(jsi::Runtime &runtime, jsi::JSError &error, bool isFatal, bool logToConsole);
};
} // namespace facebook::react

View File

@@ -0,0 +1,52 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
react_native_path = ".."
Pod::Spec.new do |s|
s.name = "React-jserrorhandler"
s.version = version
s.summary = "-"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.header_dir = "jserrorhandler"
s.source_files = podspec_sources(["JsErrorHandler.{cpp,h}", "StackTraceParser.{cpp,h}"], ["JsErrorHandler.h", "StackTraceParser.h"])
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard()
}
resolve_use_frameworks(s, header_mappings_dir: '../', module_name: "React_jserrorhandler")
s.dependency "React-jsi"
s.dependency "React-cxxreact"
s.dependency "ReactCommon/turbomodule/bridging"
add_dependency(s, "React-featureflags")
add_dependency(s, "React-debug")
if use_hermes()
s.dependency 'hermes-engine'
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,319 @@
/*
* 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.
*/
#include "StackTraceParser.h"
#include <glog/logging.h>
#include <charconv>
#include <optional>
#include <regex>
#include <sstream>
#include <string>
namespace facebook::react {
const std::string UNKNOWN_FUNCTION = "<unknown>";
// TODO(T198763073): Migrate away from std::regex in this file
// @lint-ignore-every CLANGTIDY facebook-hte-StdRegexIsAwful
/**
* Stack trace parsing for other jsvms:
* Port of https://github.com/errwischt/stacktrace-parser
*/
namespace {
std::optional<int> toInt(std::string_view input) {
int out;
const std::from_chars_result result =
std::from_chars(input.data(), input.data() + input.size(), out);
if (result.ec == std::errc::invalid_argument ||
result.ec == std::errc::result_out_of_range) {
return std::nullopt;
}
return out;
}
JsErrorHandler::ProcessedError::StackFrame parseStackFrame(
std::string_view file,
std::string_view methodName,
std::string_view lineStr,
std::string_view columnStr) {
JsErrorHandler::ProcessedError::StackFrame frame;
frame.file = file.empty() ? std::nullopt : std::optional(file);
frame.methodName = !methodName.empty() ? methodName : UNKNOWN_FUNCTION;
frame.lineNumber = !lineStr.empty() ? toInt(lineStr) : std::nullopt;
auto columnOpt = !columnStr.empty() ? toInt(columnStr) : std::nullopt;
frame.column = columnOpt ? std::optional(*columnOpt - 1) : std::nullopt;
return frame;
}
std::optional<JsErrorHandler::ProcessedError::StackFrame> parseChrome(
const std::string& line) {
static const std::regex chromeRe(
R"(^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$)",
std::regex::icase);
static const std::regex chromeEvalRe(R"(\((\S*)(?::(\d+))(?::(\d+))\))");
std::smatch match;
if (!std::regex_match(line, match, chromeRe)) {
return std::nullopt;
}
std::string methodName = match[1].str();
std::string file = match[2].str();
std::string lineStr = match[3].str();
std::string columnStr = match[4].str();
bool isNative = std::regex_search(file, std::regex("^native"));
bool isEval = std::regex_search(file, std::regex("^eval"));
std::string evalFile;
std::string evalLine;
std::string evalColumn;
if (isEval && std::regex_search(file, match, chromeEvalRe)) {
evalFile = match[1].str();
evalLine = match[2].str();
evalColumn = match[3].str();
file = evalFile;
lineStr = evalLine;
columnStr = evalColumn;
}
std::string actualFile = !isNative ? file : "";
return parseStackFrame(actualFile, methodName, lineStr, columnStr);
}
std::optional<JsErrorHandler::ProcessedError::StackFrame> parseWinjs(
const std::string& line) {
static const std::regex winjsRe(
R"(^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$)",
std::regex::icase);
std::smatch match;
if (!std::regex_match(line, match, winjsRe)) {
return std::nullopt;
}
std::string methodName = match[1].str();
std::string file = match[2].str();
std::string lineStr = match[3].str();
std::string columnStr = match[4].str();
return parseStackFrame(file, methodName, lineStr, columnStr);
}
std::optional<JsErrorHandler::ProcessedError::StackFrame> parseGecko(
const std::string& line) {
static const std::regex geckoRe(
R"(^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$)",
std::regex::icase);
static const std::regex geckoEvalRe(
R"((\S+) line (\d+)(?: > eval line \d+)* > eval)", std::regex::icase);
std::smatch match;
if (!std::regex_match(line, match, geckoRe)) {
return std::nullopt;
}
std::string methodName = match[1].str();
std::string tmpStr = match[2].str();
std::string file = match[3].str();
std::string lineStr = match[4].str();
std::string columnStr = match[5].str();
bool isEval = std::regex_search(file, std::regex(" > eval"));
std::string evalFile;
std::string evalLine;
if (isEval && std::regex_search(file, match, geckoEvalRe)) {
evalFile = match[1].str();
evalLine = match[2].str();
file = evalFile;
lineStr = evalLine;
columnStr = ""; // No column number in eval
}
return parseStackFrame(file, methodName, lineStr, columnStr);
}
std::optional<JsErrorHandler::ProcessedError::StackFrame> parseJSC(
const std::string& line) {
static const std::regex javaScriptCoreRe(
R"(^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)",
std::regex::icase);
std::smatch match;
if (!std::regex_match(line, match, javaScriptCoreRe)) {
return std::nullopt;
}
std::string methodName = match[1].str();
std::string tmpStr =
match[2].str(); // This captures any string within parentheses if present
std::string file = match[3].str();
std::string lineStr = match[4].str();
std::string columnStr = match[5].str();
return parseStackFrame(file, methodName, lineStr, columnStr);
}
std::optional<JsErrorHandler::ProcessedError::StackFrame> parseNode(
const std::string& line) {
static const std::regex nodeRe(
R"(^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)",
std::regex::icase);
std::smatch match;
if (!std::regex_match(line, match, nodeRe)) {
return std::nullopt;
}
std::string methodName = match[1].str();
std::string file = match[2].str();
std::string lineStr = match[3].str();
std::string columnStr = match[4].str();
return parseStackFrame(file, methodName, lineStr, columnStr);
}
std::vector<JsErrorHandler::ProcessedError::StackFrame> parseOthers(
const std::string& stackString) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> stack;
std::istringstream iss(stackString);
std::string line;
while (std::getline(iss, line)) {
std::optional<JsErrorHandler::ProcessedError::StackFrame> frame =
parseChrome(line);
if (!frame) {
frame = parseWinjs(line);
}
if (!frame) {
frame = parseGecko(line);
}
if (!frame) {
frame = parseNode(line);
}
if (!frame) {
frame = parseJSC(line);
}
if (frame) {
stack.push_back(*frame);
}
}
return stack;
}
} // namespace
/**
* Hermes stack trace parsing logic
*/
namespace {
struct HermesStackLocation {
std::string type;
std::string sourceUrl;
int line1Based{};
int column1Based{};
int virtualOffset0Based{};
};
struct HermesStackEntry {
std::string type;
std::string functionName;
HermesStackLocation location;
int count{};
};
bool isInternalBytecodeSourceUrl(const std::string& sourceUrl) {
return sourceUrl == "InternalBytecode.js";
}
std::vector<JsErrorHandler::ProcessedError::StackFrame> convertHermesStack(
const std::vector<HermesStackEntry>& stack) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> frames;
for (const auto& entry : stack) {
if (entry.type != "FRAME") {
continue;
}
if (entry.location.type == "NATIVE" ||
entry.location.type == "INTERNAL_BYTECODE") {
continue;
}
JsErrorHandler::ProcessedError::StackFrame frame;
frame.methodName = entry.functionName;
frame.file = entry.location.sourceUrl;
frame.lineNumber = entry.location.line1Based;
if (entry.location.type == "SOURCE") {
frame.column = entry.location.column1Based - 1;
} else {
frame.column = entry.location.virtualOffset0Based;
}
frames.push_back(frame);
}
return frames;
}
HermesStackEntry parseLine(const std::string& line) {
static const std::regex RE_FRAME(
R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)");
static const std::regex RE_SKIPPED(R"(^ {4}... skipping (\d+) frames$)");
HermesStackEntry entry;
std::smatch match;
if (std::regex_match(line, match, RE_FRAME)) {
entry.type = "FRAME";
entry.functionName = match[1].str();
std::string type = match[2].str();
std::string addressAt = match[3].str();
std::string sourceUrl = match[4].str();
if (type == "native") {
entry.location.type = "NATIVE";
} else {
int line1Based = std::stoi(match[5].str());
int columnOrOffset = std::stoi(match[6].str());
if (addressAt == "address at ") {
if (isInternalBytecodeSourceUrl(sourceUrl)) {
entry.location = {
"INTERNAL_BYTECODE", sourceUrl, line1Based, 0, columnOrOffset};
} else {
entry.location = {
"BYTECODE", sourceUrl, line1Based, 0, columnOrOffset};
}
} else {
entry.location = {"SOURCE", sourceUrl, line1Based, columnOrOffset, 0};
}
}
return entry;
}
if (std::regex_match(line, match, RE_SKIPPED)) {
entry.type = "SKIPPED";
entry.count = std::stoi(match[1].str());
}
return entry;
}
std::vector<JsErrorHandler::ProcessedError::StackFrame> parseHermes(
const std::string& stack) {
static const std::regex RE_COMPONENT_NO_STACK(R"(^ {4}at .*?$)");
std::istringstream stream(stack);
std::string line;
std::vector<HermesStackEntry> entries;
std::smatch match;
while (std::getline(stream, line)) {
if (line.empty()) {
continue;
}
HermesStackEntry entry = parseLine(line);
if (!entry.type.empty()) {
entries.push_back(entry);
continue;
}
if (std::regex_match(line, match, RE_COMPONENT_NO_STACK)) {
continue;
}
entries.clear();
}
return convertHermesStack(entries);
}
} // namespace
std::vector<JsErrorHandler::ProcessedError::StackFrame> StackTraceParser::parse(
const bool isHermes,
const std::string& stackString) {
std::vector<JsErrorHandler::ProcessedError::StackFrame> stackFrames =
isHermes ? parseHermes(stackString) : parseOthers(stackString);
return stackFrames;
}
} // namespace facebook::react

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#pragma once
#include <string>
#include <vector>
#include "JsErrorHandler.h"
namespace facebook::react {
class StackTraceParser {
public:
static std::vector<JsErrorHandler::ProcessedError::StackFrame> parse(bool isHermes, const std::string &stackString);
};
} // namespace facebook::react

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
# 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.
##################
### jsi ###
##################
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
file(GLOB jsi_SRC CONFIGURE_DEPENDS jsi/*.cpp)
add_library(jsi SHARED ${jsi_SRC})
target_include_directories(jsi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(jsi
folly_runtime
glog)
target_compile_reactnative_options(jsi PRIVATE)
target_compile_options(jsi PRIVATE -O3 -Wno-unused-lambda-capture)

View File

@@ -0,0 +1,50 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-jsi"
s.version = version
s.summary = "JavaScript Interface layer for React Native"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.header_dir = "jsi"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"
}
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
files_to_exclude = [
"jsi/jsilib-posix.cpp",
"jsi/jsilib-windows.cpp",
"**/test/*"
]
if use_hermes()
# JSI is a part of hermes-engine. Including them also in react-native will violate the One Definition Rulle.
files_to_exclude += [ "jsi/jsi.cpp" ]
s.dependency "hermes-engine"
end
s.exclude_files = files_to_exclude
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,28 @@
# 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.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(jsi
jsi.cpp)
target_include_directories(jsi PUBLIC ..)
set(jsi_compile_flags "")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR
"${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
list(APPEND jsi_compile_flags "-Wno-non-virtual-dtor")
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC")
# Turn on Error Handling in MSVC, otherwise objects are not destructed
# when they go out of scope due to exceptions.
list(APPEND jsi_compile_flags "/EHsc")
endif()
target_compile_options(jsi PRIVATE ${jsi_compile_flags})
install(DIRECTORY "${PROJECT_SOURCE_DIR}/API/jsi/" DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN "test" EXCLUDE)

View File

@@ -0,0 +1,212 @@
/*
* 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.
*/
#include "JSIDynamic.h"
#include <glog/logging.h>
#include <folly/dynamic.h>
#include <jsi/jsi.h>
using namespace facebook::jsi;
namespace facebook {
namespace jsi {
namespace {
struct FromDynamic {
FromDynamic(const folly::dynamic* dynArg, Object objArg)
: dyn(dynArg), obj(std::move(objArg)) {}
const folly::dynamic* dyn;
Object obj;
};
// This converts one element. If it's a collection, it gets pushed onto
// the stack for later processing.
Value valueFromDynamicShallow(
Runtime& runtime,
std::vector<FromDynamic>& stack,
const folly::dynamic& dyn) {
switch (dyn.type()) {
case folly::dynamic::NULLT:
return Value::null();
case folly::dynamic::ARRAY: {
Object arr = Array(runtime, dyn.size());
Value ret = Value(runtime, arr);
stack.emplace_back(&dyn, std::move(arr));
return ret;
}
case folly::dynamic::BOOL:
return Value(dyn.getBool());
case folly::dynamic::DOUBLE:
return dyn.getDouble();
case folly::dynamic::INT64:
return Value((double)dyn.getInt());
case folly::dynamic::OBJECT: {
auto obj = Object(runtime);
Value ret = Value(runtime, obj);
stack.emplace_back(&dyn, std::move(obj));
return ret;
}
case folly::dynamic::STRING:
return Value(String::createFromUtf8(runtime, dyn.getString()));
}
CHECK(false);
}
} // namespace
Value valueFromDynamic(Runtime& runtime, const folly::dynamic& dynInput) {
std::vector<FromDynamic> stack;
Value ret = valueFromDynamicShallow(runtime, stack, dynInput);
while (!stack.empty()) {
auto top = std::move(stack.back());
stack.pop_back();
switch (top.dyn->type()) {
case folly::dynamic::ARRAY: {
Array arr = std::move(top.obj).getArray(runtime);
for (size_t i = 0; i < top.dyn->size(); ++i) {
arr.setValueAtIndex(
runtime,
i,
valueFromDynamicShallow(runtime, stack, (*top.dyn)[i]));
}
break;
}
case folly::dynamic::OBJECT: {
Object obj = std::move(top.obj);
for (const auto& element : top.dyn->items()) {
if (element.first.isNumber() || element.first.isString()) {
obj.setProperty(
runtime,
PropNameID::forUtf8(runtime, element.first.asString()),
valueFromDynamicShallow(runtime, stack, element.second));
}
}
break;
}
default:
CHECK(false);
}
}
return ret;
}
namespace {
struct FromValue {
FromValue(folly::dynamic* dynArg, Object objArg)
: dyn(dynArg), obj(std::move(objArg)) {}
folly::dynamic* dyn;
Object obj;
};
// This converts one element. If it's a collection, it gets pushed
// onto the stack for later processing. The output is created by
// mutating the output argument, because we need its actual pointer to
// push onto the stack.
void dynamicFromValueShallow(
Runtime& runtime,
std::vector<FromValue>& stack,
const jsi::Value& value,
folly::dynamic& output) {
if (value.isUndefined() || value.isNull()) {
output = nullptr;
} else if (value.isBool()) {
output = value.getBool();
} else if (value.isNumber()) {
output = value.getNumber();
} else if (value.isString()) {
output = value.getString(runtime).utf8(runtime);
} else if (value.isObject()) {
Object obj = value.getObject(runtime);
if (obj.isArray(runtime)) {
output = folly::dynamic::array();
} else if (obj.isFunction(runtime)) {
throw JSError(runtime, "JS Functions are not convertible to dynamic");
} else {
output = folly::dynamic::object();
}
stack.emplace_back(&output, std::move(obj));
} else if (value.isBigInt()) {
throw JSError(runtime, "JS BigInts are not convertible to dynamic");
} else if (value.isSymbol()) {
throw JSError(runtime, "JS Symbols are not convertible to dynamic");
} else {
throw JSError(runtime, "Value is not convertible to dynamic");
}
}
} // namespace
folly::dynamic dynamicFromValue(
Runtime& runtime,
const Value& valueInput,
const std::function<bool(const std::string&)>& filterObjectKeys) {
std::vector<FromValue> stack;
folly::dynamic ret;
dynamicFromValueShallow(runtime, stack, valueInput, ret);
while (!stack.empty()) {
auto top = std::move(stack.back());
stack.pop_back();
if (top.obj.isArray(runtime)) {
// Inserting into a dyn can invalidate references into it, so we
// need to insert new elements up front, then push stuff onto
// the stack.
Array array = top.obj.getArray(runtime);
size_t arraySize = array.size(runtime);
for (size_t i = 0; i < arraySize; ++i) {
top.dyn->push_back(nullptr);
}
for (size_t i = 0; i < arraySize; ++i) {
dynamicFromValueShallow(
runtime, stack, array.getValueAtIndex(runtime, i), top.dyn->at(i));
}
} else {
Array names = top.obj.getPropertyNames(runtime);
std::vector<std::pair<std::string, jsi::Value>> props;
for (size_t i = 0; i < names.size(runtime); ++i) {
String name = names.getValueAtIndex(runtime, i).getString(runtime);
Value prop = top.obj.getProperty(runtime, name);
if (prop.isUndefined()) {
continue;
}
auto nameStr = name.utf8(runtime);
if (filterObjectKeys && filterObjectKeys(nameStr)) {
continue;
}
// The JSC conversion uses JSON.stringify, which substitutes
// null for a function, so we do the same here. Just dropping
// the pair might also work, but would require more testing.
if (prop.isObject() && prop.getObject(runtime).isFunction(runtime)) {
prop = Value::null();
}
props.emplace_back(std::move(nameStr), std::move(prop));
top.dyn->insert(props.back().first, nullptr);
}
for (const auto& prop : props) {
dynamicFromValueShallow(
runtime, stack, prop.second, (*top.dyn)[prop.first]);
}
}
}
return ret;
}
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
#pragma once
#include <folly/dynamic.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
facebook::jsi::Value valueFromDynamic(
facebook::jsi::Runtime& runtime,
const folly::dynamic& dyn);
folly::dynamic dynamicFromValue(
facebook::jsi::Runtime& runtime,
const facebook::jsi::Value& value,
const std::function<bool(const std::string&)>& filterObjectKeys = nullptr);
} // namespace jsi
} // namespace facebook

1064
node_modules/react-native/ReactCommon/jsi/jsi/decorator.h generated vendored Normal file

File diff suppressed because it is too large Load Diff

189
node_modules/react-native/ReactCommon/jsi/jsi/hermes.h generated vendored Normal file
View File

@@ -0,0 +1,189 @@
/*
* 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.
*/
#pragma once
#include <jsi/jsi.h>
struct SHUnit;
struct SHRuntime;
using SHUnitCreator = SHUnit* (*)();
namespace hermes::vm {
class GCExecTrace;
}
namespace facebook::hermes {
namespace sampling_profiler {
class Profile;
}
namespace debugger {
class Debugger;
}
/// Interface for Hermes-specific runtime methods.The actual implementations of
/// the pure virtual methods are provided by Hermes API.
class JSI_EXPORT IHermes : public jsi::ICast {
public:
static constexpr jsi::UUID uuid{
0xe85cfa22,
0xdfae,
0x11ef,
0xa6f7,
0x325096b39f47};
struct DebugFlags {
// Looking for the .lazy flag? It's no longer necessary.
// Source is evaluated lazily by default. See
// RuntimeConfig::CompilationMode.
};
/// Evaluate the given code in an unoptimized form, used for debugging.
/// This will be no-op if the implementation does not have debugger enabled.
virtual void debugJavaScript(
const std::string& src,
const std::string& sourceURL,
const DebugFlags& debugFlags) = 0;
/// Return a ICast pointer to an object that be cast into the interface
/// IHermesRootAPI. This root API object has static lifetime.
virtual ICast* getHermesRootAPI() = 0;
/// Dump sampled stack trace for a given runtime to a data structure that can
/// be used by third parties.
virtual sampling_profiler::Profile dumpSampledTraceToProfile() = 0;
/// Serialize the sampled stack to the format expected by DevTools'
/// Profiler.stop return type.
virtual void sampledTraceToStreamInDevToolsFormat(std::ostream& stream) = 0;
/// Resets the timezone offset cache used by Hermes for performance
/// optimization. Hermes maintains a cached timezone offset to accelerate date
/// and time calculations. However, this cache does not automatically detect
/// changes to the system timezone. When the system timezone changes, the
/// integration layer (e.g., React Native) must call this method to invalidate
/// the cache and ensure correct time calculations.
///
/// \note Call this method immediately after detecting any timezone change in
/// the integrator.
virtual void resetTimezoneCache() = 0;
/// Load a new segment into the Runtime.
/// The \param context must be a valid RequireContext retrieved from JS
/// using `require.context`.
virtual void loadSegment(
std::unique_ptr<const jsi::Buffer> buffer,
const jsi::Value& context) = 0;
/// Gets a guaranteed unique id for an Object (or, respectively, String
/// or PropNameId), which is assigned at allocation time and is
/// static throughout that object's (or string's, or PropNameID's)
/// lifetime.
virtual uint64_t getUniqueID(const jsi::Object& o) const = 0;
virtual uint64_t getUniqueID(const jsi::BigInt& s) const = 0;
virtual uint64_t getUniqueID(const jsi::String& s) const = 0;
virtual uint64_t getUniqueID(const jsi::PropNameID& pni) const = 0;
virtual uint64_t getUniqueID(const jsi::Symbol& sym) const = 0;
/// Same as the other \c getUniqueID, except it can return 0 for some values.
/// 0 means there is no ID associated with the value.
virtual uint64_t getUniqueID(const jsi::Value& val) const = 0;
/// From an ID retrieved from \p getUniqueID, go back to the object.
/// NOTE: This is much slower in general than the reverse operation, and takes
/// up more memory. Don't use this unless it's absolutely necessary.
/// \return a jsi::Object if a matching object is found, else returns null.
virtual jsi::Value getObjectForID(uint64_t id) = 0;
/// Get a structure representing the execution history (currently just of
/// GC, but will be generalized as necessary), to aid in debugging
/// non-deterministic execution.
virtual const ::hermes::vm::GCExecTrace& getGCExecTrace() const = 0;
/// Get IO tracking (aka HBC page access) info as a JSON string.
/// See hermes::vm::Runtime::getIOTrackingInfoJSON() for conditions
/// needed for there to be useful output.
virtual std::string getIOTrackingInfoJSON() = 0;
/// \return a reference to the Debugger for this Runtime.
virtual debugger::Debugger& getDebugger() = 0;
/// Register this runtime and thread for sampling profiler. Before using the
/// runtime on another thread, invoke this function again from the new thread
/// to make the sampling profiler target the new thread (and forget the old
/// thread).
virtual void registerForProfiling() = 0;
/// Unregister this runtime for sampling profiler.
virtual void unregisterForProfiling() = 0;
/// Define methods to interrupt JS execution and set time limits.
/// All JS compiled to bytecode via prepareJS, or evaluateJS, will support
/// interruption and time limit monitoring if the runtime is configured with
/// AsyncBreakCheckInEval. If JS prepared in other ways is executed, care must
/// be taken to ensure that it is compiled in a mode that supports it (i.e.,
/// the emitted code contains async break checks).
/// Asynchronously terminates the current execution. This can be called on
/// any thread.
virtual void asyncTriggerTimeout() = 0;
/// Register this runtime for execution time limit monitoring, with a time
/// limit of \p timeoutInMs milliseconds.
/// See compilation notes above.
virtual void watchTimeLimit(uint32_t timeoutInMs) = 0;
/// Unregister this runtime for execution time limit monitoring.
virtual void unwatchTimeLimit() = 0;
/// Same as \c evaluate JavaScript but with a source map, which will be
/// applied to exception traces and debug information.
///
/// This is an experimental Hermes-specific API. In the future it may be
/// renamed, moved or combined with another API, but the provided
/// functionality will continue to be available in some form.
virtual jsi::Value evaluateJavaScriptWithSourceMap(
const std::shared_ptr<const jsi::Buffer>& buffer,
const std::shared_ptr<const jsi::Buffer>& sourceMapBuf,
const std::string& sourceURL) = 0;
/// Associate the SHUnit returned by \p shUnitCreator with this runtime and
/// run its initialization code. The unit will be freed when the runtime is
/// destroyed.
virtual jsi::Value evaluateSHUnit(SHUnitCreator shUnitCreator) = 0;
/// Retrieve the underlying SHRuntime.
virtual SHRuntime* getSHRuntime() noexcept = 0;
/// Returns the underlying low level Hermes VM runtime instance.
/// This function is considered unsafe and unstable.
/// Direct use of a vm::Runtime should be avoided as the lower level APIs are
/// unsafe and they can change without notice.
virtual void* getVMRuntimeUnsafe() const = 0;
protected:
~IHermes() = default;
};
/// Interface for provide Hermes backend specific methods.
class IHermesSHUnit : public jsi::ICast {
public:
static constexpr jsi::UUID uuid{
0x52a2d522,
0xcbc6,
0x4236,
0x8d5d,
0x2636c320ed65,
};
/// Get the unit creating function pointer which can be passed to
/// evaluateSHUnit() for evaluation.
virtual SHUnitCreator getSHUnitCreator() const = 0;
protected:
~IHermesSHUnit() = default;
};
} // namespace facebook::hermes

View File

@@ -0,0 +1,132 @@
/*
* 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.
*/
#pragma once
#include <chrono>
#include <iosfwd>
#include <string>
#include <tuple>
#include <unordered_map>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
/// Methods for starting and collecting instrumentation, an \c Instrumentation
/// instance is associated with a particular \c Runtime instance, which it
/// controls the instrumentation of.
/// None of these functions should return newly created jsi values, nor should
/// it modify the values of any jsi values in the heap (although GCs are fine).
class JSI_EXPORT Instrumentation {
public:
/// Additional options controlling what to include when capturing a heap
/// snapshot.
struct HeapSnapshotOptions {
bool captureNumericValue{false};
};
virtual ~Instrumentation() = default;
/// Returns GC statistics as a JSON-encoded string, with an object containing
/// "type" and "version" fields outermost. "type" is a string, unique to a
/// particular implementation of \c jsi::Instrumentation, and "version" is a
/// number to indicate any revision to that implementation and its output
/// format.
///
/// \pre This call can only be made on the instrumentation instance of a
/// runtime initialised to collect GC statistics.
///
/// \post All cumulative measurements mentioned in the output are accumulated
/// across the entire lifetime of the Runtime.
///
/// \return the GC statistics collected so far, as a JSON-encoded string.
virtual std::string getRecordedGCStats() = 0;
/// Request statistics about the current state of the runtime's heap. This
/// function can be called at any time, and should produce information that is
/// correct at the instant it is called (i.e, not stale).
///
/// \return a map from a string key to a number associated with that
/// statistic.
virtual std::unordered_map<std::string, int64_t> getHeapInfo(
bool includeExpensive) = 0;
/// Perform a full garbage collection.
/// \param cause The cause of this collection, as it should be reported in
/// logs.
virtual void collectGarbage(std::string cause) = 0;
/// A HeapStatsUpdate is a tuple of the fragment index, the number of objects
/// in that fragment, and the number of bytes used by those objects.
/// A "fragment" is a view of all objects allocated within a time slice.
using HeapStatsUpdate = std::tuple<uint64_t, uint64_t, uint64_t>;
/// Start capturing JS stack-traces for all JS heap allocated objects. These
/// can be accessed via \c ::createSnapshotToFile().
/// \param fragmentCallback If present, invoke this callback every so often
/// with the most recently seen object ID, and a list of fragments that have
/// been updated. This callback will be invoked on the same thread that the
/// runtime is using.
virtual void startTrackingHeapObjectStackTraces(
std::function<void(
uint64_t lastSeenObjectID,
std::chrono::microseconds timestamp,
std::vector<HeapStatsUpdate> stats)> fragmentCallback) = 0;
/// Stop capture JS stack-traces for JS heap allocated objects.
virtual void stopTrackingHeapObjectStackTraces() = 0;
/// Start a heap sampling profiler that will sample heap allocations, and the
/// stack trace they were allocated at. Reports a summary of which functions
/// allocated the most.
/// \param samplingInterval The number of bytes allocated to wait between
/// samples. This will be used as the expected value of a poisson
/// distribution.
virtual void startHeapSampling(size_t samplingInterval) = 0;
/// Turns off the heap sampling profiler previously enabled via
/// \c startHeapSampling. Writes the output of the sampling heap profiler to
/// \p os. The output is a JSON formatted string.
virtual void stopHeapSampling(std::ostream& os) = 0;
/// Captures the heap to a file
///
/// \param path to save the heap capture.
/// \param options additional options for what to capture.
virtual void createSnapshotToFile(
const std::string& path,
const HeapSnapshotOptions& options = {false}) = 0;
/// Captures the heap to an output stream
///
/// \param os output stream to write to.
/// \param options additional options for what to capture.
virtual void createSnapshotToStream(
std::ostream& os,
const HeapSnapshotOptions& options = {false}) = 0;
/// If the runtime has been created to trace to a temp file, flush
/// any unwritten parts of the trace of bridge traffic to the file,
/// and return the name of the file. Otherwise, return the empty string.
/// Tracing is disabled after this call.
virtual std::string flushAndDisableBridgeTrafficTrace() = 0;
/// Write basic block profile trace to the given file name.
virtual void writeBasicBlockProfileTraceToFile(
const std::string& fileName) const = 0;
/// Write the opcode stats to the given stream.
virtual void dumpOpcodeStats(std::ostream& os) const = 0;
/// Dump external profiler symbols to the given file name.
virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0;
};
} // namespace jsi
} // namespace facebook

405
node_modules/react-native/ReactCommon/jsi/jsi/jsi-inl.h generated vendored Normal file
View File

@@ -0,0 +1,405 @@
/*
* 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.
*/
#pragma once
namespace facebook {
namespace jsi {
namespace detail {
inline Value toValue(Runtime&, std::nullptr_t) {
return Value::null();
}
inline Value toValue(Runtime&, bool b) {
return Value(b);
}
inline Value toValue(Runtime&, double d) {
return Value(d);
}
inline Value toValue(Runtime&, float f) {
return Value(static_cast<double>(f));
}
inline Value toValue(Runtime&, int i) {
return Value(i);
}
inline Value toValue(Runtime& runtime, const char* str) {
return String::createFromAscii(runtime, str);
}
inline Value toValue(Runtime& runtime, const std::string& str) {
return String::createFromUtf8(runtime, str);
}
template <typename T>
inline Value toValue(Runtime& runtime, const T& other) {
static_assert(
std::is_base_of<Pointer, T>::value,
"This type cannot be converted to Value");
return Value(runtime, other);
}
inline Value toValue(Runtime& runtime, const Value& value) {
return Value(runtime, value);
}
inline Value&& toValue(Runtime&, Value&& value) {
return std::move(value);
}
inline PropNameID toPropNameID(Runtime& runtime, const char* name) {
return PropNameID::forAscii(runtime, name);
}
inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) {
return PropNameID::forUtf8(runtime, name);
}
inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) {
return std::move(name);
}
/// Helper to throw while still compiling with exceptions turned off.
template <typename E, typename... Args>
[[noreturn]] inline void throwOrDie(Args&&... args) {
std::rethrow_exception(
std::make_exception_ptr(E{std::forward<Args>(args)...}));
}
} // namespace detail
template <typename T>
inline T Runtime::make(Runtime::PointerValue* pv) {
return T(pv);
}
inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) {
return pointer.ptr_;
}
inline const Runtime::PointerValue* Runtime::getPointerValue(
const jsi::Pointer& pointer) {
return pointer.ptr_;
}
inline const Runtime::PointerValue* Runtime::getPointerValue(
const jsi::Value& value) {
return value.data_.pointer.ptr_;
}
inline void Runtime::setRuntimeData(
const UUID& uuid,
const std::shared_ptr<void>& data) {
auto* dataPtr = new std::shared_ptr<void>(data);
setRuntimeDataImpl(uuid, dataPtr, [](const void* data) {
delete (const std::shared_ptr<void>*)data;
});
}
inline std::shared_ptr<void> Runtime::getRuntimeData(const UUID& uuid) {
auto* data = (const std::shared_ptr<void>*)getRuntimeDataImpl(uuid);
return data ? *data : nullptr;
}
Value Object::getPrototype(Runtime& runtime) const {
return runtime.getPrototypeOf(*this);
}
inline Value Object::getProperty(Runtime& runtime, const char* name) const {
return getProperty(runtime, String::createFromAscii(runtime, name));
}
inline Value Object::getProperty(Runtime& runtime, const String& name) const {
return runtime.getProperty(*this, name);
}
inline Value Object::getProperty(Runtime& runtime, const PropNameID& name)
const {
return runtime.getProperty(*this, name);
}
inline Value Object::getProperty(Runtime& runtime, const Value& name) const {
return runtime.getProperty(*this, name);
}
inline bool Object::hasProperty(Runtime& runtime, const char* name) const {
return hasProperty(runtime, String::createFromAscii(runtime, name));
}
inline bool Object::hasProperty(Runtime& runtime, const String& name) const {
return runtime.hasProperty(*this, name);
}
inline bool Object::hasProperty(Runtime& runtime, const PropNameID& name)
const {
return runtime.hasProperty(*this, name);
}
inline bool Object::hasProperty(Runtime& runtime, const Value& name) const {
return runtime.hasProperty(*this, name);
}
template <typename T>
void Object::setProperty(Runtime& runtime, const char* name, T&& value) const {
setProperty(
runtime, String::createFromAscii(runtime, name), std::forward<T>(value));
}
template <typename T>
void Object::setProperty(Runtime& runtime, const String& name, T&& value)
const {
setPropertyValue(
runtime, name, detail::toValue(runtime, std::forward<T>(value)));
}
template <typename T>
void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value)
const {
setPropertyValue(
runtime, name, detail::toValue(runtime, std::forward<T>(value)));
}
template <typename T>
void Object::setProperty(Runtime& runtime, const Value& name, T&& value) const {
setPropertyValue(
runtime, name, detail::toValue(runtime, std::forward<T>(value)));
}
inline void Object::deleteProperty(Runtime& runtime, const char* name) const {
deleteProperty(runtime, String::createFromAscii(runtime, name));
}
inline void Object::deleteProperty(Runtime& runtime, const String& name) const {
runtime.deleteProperty(*this, name);
}
inline void Object::deleteProperty(Runtime& runtime, const PropNameID& name)
const {
runtime.deleteProperty(*this, name);
}
inline void Object::deleteProperty(Runtime& runtime, const Value& name) const {
runtime.deleteProperty(*this, name);
}
inline Array Object::getArray(Runtime& runtime) const& {
assert(runtime.isArray(*this));
(void)runtime; // when assert is disabled we need to mark this as used
return Array(runtime.cloneObject(ptr_));
}
inline Array Object::getArray(Runtime& runtime) && {
assert(runtime.isArray(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return Array(value);
}
inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& {
assert(runtime.isArrayBuffer(*this));
(void)runtime; // when assert is disabled we need to mark this as used
return ArrayBuffer(runtime.cloneObject(ptr_));
}
inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && {
assert(runtime.isArrayBuffer(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return ArrayBuffer(value);
}
inline Function Object::getFunction(Runtime& runtime) const& {
assert(runtime.isFunction(*this));
return Function(runtime.cloneObject(ptr_));
}
inline Function Object::getFunction(Runtime& runtime) && {
assert(runtime.isFunction(*this));
(void)runtime; // when assert is disabled we need to mark this as used
Runtime::PointerValue* value = ptr_;
ptr_ = nullptr;
return Function(value);
}
template <typename T>
inline bool Object::isHostObject(Runtime& runtime) const {
return runtime.isHostObject(*this) &&
std::dynamic_pointer_cast<T>(runtime.getHostObject(*this));
}
template <>
inline bool Object::isHostObject<HostObject>(Runtime& runtime) const {
return runtime.isHostObject(*this);
}
template <typename T>
inline std::shared_ptr<T> Object::getHostObject(Runtime& runtime) const {
assert(isHostObject<T>(runtime));
return std::static_pointer_cast<T>(runtime.getHostObject(*this));
}
template <typename T>
inline std::shared_ptr<T> Object::asHostObject(Runtime& runtime) const {
if (!isHostObject<T>(runtime)) {
detail::throwOrDie<JSINativeException>(
"Object is not a HostObject of desired type");
}
return std::static_pointer_cast<T>(runtime.getHostObject(*this));
}
template <>
inline std::shared_ptr<HostObject> Object::getHostObject<HostObject>(
Runtime& runtime) const {
assert(runtime.isHostObject(*this));
return runtime.getHostObject(*this);
}
template <typename T>
inline bool Object::hasNativeState(Runtime& runtime) const {
return runtime.hasNativeState(*this) &&
std::dynamic_pointer_cast<T>(runtime.getNativeState(*this));
}
template <>
inline bool Object::hasNativeState<NativeState>(Runtime& runtime) const {
return runtime.hasNativeState(*this);
}
template <typename T>
inline std::shared_ptr<T> Object::getNativeState(Runtime& runtime) const {
assert(hasNativeState<T>(runtime));
return std::static_pointer_cast<T>(runtime.getNativeState(*this));
}
inline void Object::setNativeState(
Runtime& runtime,
std::shared_ptr<NativeState> state) const {
runtime.setNativeState(*this, state);
}
inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt)
const {
runtime.setExternalMemoryPressure(*this, amt);
}
inline Array Object::getPropertyNames(Runtime& runtime) const {
return runtime.getPropertyNames(*this);
}
inline Value WeakObject::lock(Runtime& runtime) const {
return runtime.lockWeakObject(*this);
}
template <typename T>
void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const {
setValueAtIndexImpl(
runtime, i, detail::toValue(runtime, std::forward<T>(value)));
}
inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const {
return runtime.getValueAtIndex(*this, i);
}
inline Function Function::createFromHostFunction(
Runtime& runtime,
const jsi::PropNameID& name,
unsigned int paramCount,
jsi::HostFunctionType func) {
return runtime.createFunctionFromHostFunction(
name, paramCount, std::move(func));
}
inline Value Function::call(Runtime& runtime, const Value* args, size_t count)
const {
return runtime.call(*this, Value::undefined(), args, count);
}
inline Value Function::call(Runtime& runtime, std::initializer_list<Value> args)
const {
return call(runtime, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::call(Runtime& runtime, Args&&... args) const {
// A more awesome version of this would be able to create raw values
// which can be used directly without wrapping and unwrapping, but
// this will do for now.
return call(runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
const Value* args,
size_t count) const {
return runtime.call(*this, Value(runtime, jsThis), args, count);
}
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
std::initializer_list<Value> args) const {
return callWithThis(runtime, jsThis, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::callWithThis(
Runtime& runtime,
const Object& jsThis,
Args&&... args) const {
// A more awesome version of this would be able to create raw values
// which can be used directly without wrapping and unwrapping, but
// this will do for now.
return callWithThis(
runtime, jsThis, {detail::toValue(runtime, std::forward<Args>(args))...});
}
template <typename... Args>
inline Array Array::createWithElements(Runtime& runtime, Args&&... args) {
return createWithElements(
runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
template <typename... Args>
inline std::vector<PropNameID> PropNameID::names(
Runtime& runtime,
Args&&... args) {
return names({detail::toPropNameID(runtime, std::forward<Args>(args))...});
}
template <size_t N>
inline std::vector<PropNameID> PropNameID::names(
PropNameID (&&propertyNames)[N]) {
std::vector<PropNameID> result;
result.reserve(N);
for (auto& name : propertyNames) {
result.push_back(std::move(name));
}
return result;
}
inline Value Function::callAsConstructor(
Runtime& runtime,
const Value* args,
size_t count) const {
return runtime.callAsConstructor(*this, args, count);
}
inline Value Function::callAsConstructor(
Runtime& runtime,
std::initializer_list<Value> args) const {
return callAsConstructor(runtime, args.begin(), args.size());
}
template <typename... Args>
inline Value Function::callAsConstructor(Runtime& runtime, Args&&... args)
const {
return callAsConstructor(
runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
}
String BigInt::toString(Runtime& runtime, int radix) const {
return runtime.bigintToString(*this, radix);
}
} // namespace jsi
} // namespace facebook

925
node_modules/react-native/ReactCommon/jsi/jsi/jsi.cpp generated vendored Normal file
View File

@@ -0,0 +1,925 @@
/*
* 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.
*/
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <map>
#include <mutex>
#include <stdexcept>
#include <jsi/instrumentation.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
namespace {
/// A global map used to store custom runtime data for VMs that do not provide
/// their own default implementation of setRuntimeData and getRuntimeData.
struct RuntimeDataGlobal {
/// Mutex protecting the Runtime data map
std::mutex mutex_{};
/// Maps a runtime pointer to a map of its custom data. At destruction of the
/// runtime, its entry will be removed from the global map.
std::unordered_map<
Runtime*,
std::unordered_map<
UUID,
std::pair<const void*, void (*)(const void* data)>,
UUID::Hash>>
dataMap_;
};
RuntimeDataGlobal& getRuntimeDataGlobal() {
static RuntimeDataGlobal runtimeData{};
return runtimeData;
}
/// A host object that, when destructed, will remove the runtime's custom data
/// entry from the global map of custom data.
class RemoveRuntimeDataHostObject : public jsi::HostObject {
public:
explicit RemoveRuntimeDataHostObject(Runtime* runtime) : runtime_(runtime) {}
RemoveRuntimeDataHostObject(const RemoveRuntimeDataHostObject&) = default;
RemoveRuntimeDataHostObject(RemoveRuntimeDataHostObject&&) = default;
RemoveRuntimeDataHostObject& operator=(const RemoveRuntimeDataHostObject&) =
default;
RemoveRuntimeDataHostObject& operator=(RemoveRuntimeDataHostObject&&) =
default;
~RemoveRuntimeDataHostObject() override {
auto& runtimeDataGlobal = getRuntimeDataGlobal();
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(runtime_);
// We install the RemoveRuntimeDataHostObject only when the first custom
// data for the runtime is added, and only this object is responsible for
// clearing runtime data. Thus, we should always be able to find the data
// entry.
assert(
runtimeMapIt != runtimeDataGlobal.dataMap_.end() &&
"Custom runtime data not found for this runtime");
for (auto [_, entry] : runtimeMapIt->second) {
auto* deleter = entry.second;
deleter(entry.first);
}
runtimeDataGlobal.dataMap_.erase(runtime_);
}
private:
Runtime* runtime_;
};
// This is used for generating short exception strings.
std::string kindToString(const Value& v, Runtime* rt = nullptr) {
if (v.isUndefined()) {
return "undefined";
} else if (v.isNull()) {
return "null";
} else if (v.isBool()) {
return v.getBool() ? "true" : "false";
} else if (v.isNumber()) {
return "a number";
} else if (v.isString()) {
return "a string";
} else if (v.isSymbol()) {
return "a symbol";
} else if (v.isBigInt()) {
return "a bigint";
} else {
assert(v.isObject() && "Expecting object.");
return rt != nullptr && v.getObject(*rt).isFunction(*rt) ? "a function"
: "an object";
}
}
// getPropertyAsFunction() will try to create a JSError. If the
// failure is in building a JSError, this will lead to infinite
// recursion. This function is used in place of getPropertyAsFunction
// when building JSError, to avoid that infinite recursion.
Value callGlobalFunction(Runtime& runtime, const char* name, const Value& arg) {
Value v = runtime.global().getProperty(runtime, name);
if (!v.isObject()) {
throw JSINativeException(
std::string("callGlobalFunction: JS global property '") + name +
"' is " + kindToString(v, &runtime) + ", expected a Function");
}
Object o = v.getObject(runtime);
if (!o.isFunction(runtime)) {
throw JSINativeException(
std::string("callGlobalFunction: JS global property '") + name +
"' is a non-callable Object, expected a Function");
}
Function f = std::move(o).getFunction(runtime);
return f.call(runtime, arg);
}
// Given a sequence of UTF8 encoded bytes, advance the input to past where a
// 32-bit unicode codepoint as been decoded and return the codepoint. If the
// UTF8 encoding is invalid, then return the value with the unicode replacement
// character (U+FFFD). This decoder also relies on zero termination at end of
// the input for bound checks.
// \param input char pointer pointing to the current character
// \return Unicode codepoint
uint32_t decodeUTF8(const char*& input) {
uint32_t ch = (unsigned char)input[0];
if (ch <= 0x7f) {
input += 1;
return ch;
}
uint32_t ret;
constexpr uint32_t replacementCharacter = 0xFFFD;
if ((ch & 0xE0) == 0xC0) {
uint32_t ch1 = (unsigned char)input[1];
if ((ch1 & 0xC0) != 0x80) {
input += 1;
return replacementCharacter;
}
ret = ((ch & 0x1F) << 6) | (ch1 & 0x3F);
input += 2;
if (ret <= 0x7F) {
return replacementCharacter;
}
} else if ((ch & 0xF0) == 0xE0) {
uint32_t ch1 = (unsigned char)input[1];
if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) {
input += 1;
return replacementCharacter;
}
uint32_t ch2 = (unsigned char)input[2];
if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) {
input += 2;
return replacementCharacter;
}
ret = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F);
input += 3;
if (ret <= 0x7FF) {
return replacementCharacter;
}
} else if ((ch & 0xF8) == 0xF0) {
uint32_t ch1 = (unsigned char)input[1];
if ((ch1 & 0x40) != 0 || (ch1 & 0x80) == 0) {
input += 1;
return replacementCharacter;
}
uint32_t ch2 = (unsigned char)input[2];
if ((ch2 & 0x40) != 0 || (ch2 & 0x80) == 0) {
input += 2;
return replacementCharacter;
}
uint32_t ch3 = (unsigned char)input[3];
if ((ch3 & 0x40) != 0 || (ch3 & 0x80) == 0) {
input += 3;
return replacementCharacter;
}
ret = ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) |
(ch3 & 0x3F);
input += 4;
if (ret <= 0xFFFF) {
return replacementCharacter;
}
if (ret > 0x10FFFF) {
return replacementCharacter;
}
} else {
input += 1;
return replacementCharacter;
}
return ret;
}
// Given a valid 32-bit unicode codepoint, encode it as UTF-16 into the output.
void encodeUTF16(std::u16string& out, uint32_t cp) {
if (cp < 0x10000) {
out.push_back((uint16_t)cp);
return;
}
cp -= 0x10000;
uint16_t highSurrogate = 0xD800 + ((cp >> 10) & 0x3FF);
out.push_back(highSurrogate);
uint16_t lowSurrogate = 0xDC00 + (cp & 0x3FF);
out.push_back(lowSurrogate);
}
// Convert the UTF8 encoded string into a UTF16 encoded string. If the
// input is not valid UTF8, the replacement character (U+FFFD) is used to
// represent the invalid sequence.
std::u16string convertUTF8ToUTF16(const std::string& utf8) {
std::u16string ret;
const char* curr = utf8.data();
const char* end = curr + utf8.length();
while (curr < end) {
auto cp = decodeUTF8(curr);
encodeUTF16(ret, cp);
}
return ret;
}
// Given a unsigned number, which is less than 16, return the hex character.
inline char hexDigit(unsigned x) {
return x < 10 ? '0' + x : 'A' + (x - 10);
}
// Given a sequence of UTF 16 code units, return true if all code units are
// ASCII characters
bool isAllASCII(const char16_t* utf16, size_t length) {
for (const char16_t* e = utf16 + length; utf16 != e; ++utf16) {
if (*utf16 > 0x7F)
return false;
}
return true;
}
// Given a sequences of UTF 16 code units, return a string that explicitly
// expresses the code units
std::string getUtf16CodeUnitString(const char16_t* utf16, size_t length) {
// Every character will need 4 hex digits + the character escape "\u".
// Plus 2 character for the opening and closing single quote.
std::string s = std::string(6 * length + 2, 0);
s.front() = '\'';
for (size_t i = 0; i != length; ++i) {
char16_t ch = utf16[i];
size_t start = (6 * i) + 1;
s[start] = '\\';
s[start + 1] = 'u';
s[start + 2] = hexDigit((ch >> 12) & 0x000f);
s[start + 3] = hexDigit((ch >> 8) & 0x000f);
s[start + 4] = hexDigit((ch >> 4) & 0x000f);
s[start + 5] = hexDigit(ch & 0x000f);
}
s.back() = '\'';
return s;
}
} // namespace
Buffer::~Buffer() = default;
MutableBuffer::~MutableBuffer() = default;
PreparedJavaScript::~PreparedJavaScript() = default;
Value HostObject::get(Runtime&, const PropNameID&) {
return Value();
}
void HostObject::set(Runtime& rt, const PropNameID& name, const Value&) {
std::string msg("TypeError: Cannot assign to property '");
msg += name.utf8(rt);
msg += "' on HostObject with default setter";
throw JSError(rt, msg);
}
HostObject::~HostObject() {}
NativeState::~NativeState() {}
Runtime::~Runtime() {}
ICast* Runtime::castInterface(const UUID& /*interfaceUUID*/) {
return nullptr;
}
Instrumentation& Runtime::instrumentation() {
class NoInstrumentation : public Instrumentation {
std::string getRecordedGCStats() override {
return "";
}
std::unordered_map<std::string, int64_t> getHeapInfo(bool) override {
return std::unordered_map<std::string, int64_t>{};
}
void collectGarbage(std::string) override {}
void startTrackingHeapObjectStackTraces(
std::function<void(
uint64_t,
std::chrono::microseconds,
std::vector<HeapStatsUpdate>)>) override {}
void stopTrackingHeapObjectStackTraces() override {}
void startHeapSampling(size_t) override {}
void stopHeapSampling(std::ostream&) override {}
void createSnapshotToFile(
const std::string& /*path*/,
const HeapSnapshotOptions& /*options*/) override {
throw JSINativeException(
"Default instrumentation cannot create a heap snapshot");
}
void createSnapshotToStream(
std::ostream& /*os*/,
const HeapSnapshotOptions& /*options*/) override {
throw JSINativeException(
"Default instrumentation cannot create a heap snapshot");
}
std::string flushAndDisableBridgeTrafficTrace() override {
std::abort();
}
void writeBasicBlockProfileTraceToFile(const std::string&) const override {
std::abort();
}
void dumpOpcodeStats(std::ostream&) const override {
std::abort();
}
void dumpProfilerSymbolsToFile(const std::string&) const override {
std::abort();
}
};
static NoInstrumentation sharedInstance;
return sharedInstance;
}
Value Runtime::createValueFromJsonUtf8(const uint8_t* json, size_t length) {
Function parseJson = global()
.getPropertyAsObject(*this, "JSON")
.getPropertyAsFunction(*this, "parse");
return parseJson.call(*this, String::createFromUtf8(*this, json, length));
}
String Runtime::createStringFromUtf16(const char16_t* utf16, size_t length) {
if (isAllASCII(utf16, length)) {
std::string buffer(utf16, utf16 + length);
return createStringFromAscii(buffer.data(), length);
}
auto s = getUtf16CodeUnitString(utf16, length);
return global()
.getPropertyAsFunction(*this, "eval")
.call(*this, s)
.getString(*this);
}
PropNameID Runtime::createPropNameIDFromUtf16(
const char16_t* utf16,
size_t length) {
auto jsString = createStringFromUtf16(utf16, length);
return createPropNameIDFromString(jsString);
}
std::u16string Runtime::utf16(const PropNameID& sym) {
auto utf8Str = utf8(sym);
return convertUTF8ToUTF16(utf8Str);
}
std::u16string Runtime::utf16(const String& str) {
auto utf8Str = utf8(str);
return convertUTF8ToUTF16(utf8Str);
}
void Runtime::getStringData(
const jsi::String& str,
void* ctx,
void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) {
auto utf16Str = utf16(str);
cb(ctx, false, utf16Str.data(), utf16Str.size());
}
void Runtime::getPropNameIdData(
const jsi::PropNameID& sym,
void* ctx,
void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) {
auto utf16Str = utf16(sym);
cb(ctx, false, utf16Str.data(), utf16Str.size());
}
void Runtime::setPrototypeOf(const Object& object, const Value& prototype) {
auto setPrototypeOfFn = global()
.getPropertyAsObject(*this, "Object")
.getPropertyAsFunction(*this, "setPrototypeOf");
setPrototypeOfFn.call(*this, object, prototype).asObject(*this);
}
Value Runtime::getPrototypeOf(const Object& object) {
auto setPrototypeOfFn = global()
.getPropertyAsObject(*this, "Object")
.getPropertyAsFunction(*this, "getPrototypeOf");
return setPrototypeOfFn.call(*this, object);
}
Object Runtime::createObjectWithPrototype(const Value& prototype) {
auto createFn = global()
.getPropertyAsObject(*this, "Object")
.getPropertyAsFunction(*this, "create");
return createFn.call(*this, prototype).asObject(*this);
}
void Runtime::deleteProperty(const Object& object, const PropNameID& name) {
auto nameStr = String::createFromUtf16(*this, name.utf16(*this));
auto deleteFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "deleteProperty");
auto res = deleteFn.call(*this, object, nameStr).getBool();
if (!res) {
throw JSError(*this, "Failed to delete property");
}
}
void Runtime::deleteProperty(const Object& object, const String& name) {
auto deleteFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "deleteProperty");
auto res = deleteFn.call(*this, object, name).getBool();
if (!res) {
throw JSError(*this, "Failed to delete property");
}
}
void Runtime::deleteProperty(const Object& object, const Value& name) {
auto deleteFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "deleteProperty");
auto res = deleteFn.call(*this, object, name).getBool();
if (!res) {
throw JSError(*this, "Failed to delete property");
}
}
void Runtime::setRuntimeDataImpl(
const UUID& uuid,
const void* data,
void (*deleter)(const void* data)) {
auto& runtimeDataGlobal = getRuntimeDataGlobal();
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
if (auto it = runtimeDataGlobal.dataMap_.find(this);
it != runtimeDataGlobal.dataMap_.end()) {
auto& map = it->second;
if (auto entryIt = map.find(uuid); entryIt != map.end()) {
// Free the old data
auto oldData = entryIt->second.first;
auto oldDataDeleter = entryIt->second.second;
oldDataDeleter(oldData);
}
map[uuid] = {data, deleter};
return;
}
// No custom data entry exist for this runtime in the global map, so create
// one.
runtimeDataGlobal.dataMap_[this][uuid] = {data, deleter};
// The first time data is added for this runtime is added to the map, install
// a host object on the global object of the runtime. This host object is used
// to release the runtime's entry from the global custom data map when the
// runtime is destroyed.
// Also, try to protect the host object by making it non-configurable,
// non-enumerable, and non-writable. These JSI operations are purposely
// performed after runtime-specific data map is added and the host object is
// created to prevent data leaks if any operations fail.
Object ho = Object::createFromHostObject(
*this, std::make_shared<RemoveRuntimeDataHostObject>(this));
global().setProperty(*this, "_jsiRuntimeDataCleanUp", ho);
auto definePropertyFn = global()
.getPropertyAsObject(*this, "Object")
.getPropertyAsFunction(*this, "defineProperty");
auto desc = Object(*this);
desc.setProperty(*this, "configurable", Value(false));
desc.setProperty(*this, "enumerable", Value(false));
desc.setProperty(*this, "writable", Value(false));
definePropertyFn.call(*this, global(), "_jsiRuntimeDataCleanUp", desc);
}
const void* Runtime::getRuntimeDataImpl(const UUID& uuid) {
auto& runtimeDataGlobal = getRuntimeDataGlobal();
std::lock_guard<std::mutex> lock(runtimeDataGlobal.mutex_);
if (auto runtimeMapIt = runtimeDataGlobal.dataMap_.find(this);
runtimeMapIt != runtimeDataGlobal.dataMap_.end()) {
if (auto customDataIt = runtimeMapIt->second.find(uuid);
customDataIt != runtimeMapIt->second.end()) {
return customDataIt->second.first;
}
}
return nullptr;
}
Value Runtime::getProperty(const Object& object, const Value& name) {
auto getFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "get");
return getFn.call(*this, object, name);
}
bool Runtime::hasProperty(const Object& object, const Value& name) {
auto hasFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "has");
return hasFn.call(*this, object, name).getBool();
}
void Runtime::setPropertyValue(
const Object& object,
const Value& name,
const Value& value) {
auto setFn = global()
.getPropertyAsObject(*this, "Reflect")
.getPropertyAsFunction(*this, "set");
auto setResult = setFn.call(*this, object, name, value).getBool();
if (!setResult) {
throw JSError(*this, "Failed to set the property");
}
}
Pointer& Pointer::operator=(Pointer&& other) noexcept {
if (ptr_) {
ptr_->invalidate();
}
ptr_ = other.ptr_;
other.ptr_ = nullptr;
return *this;
}
Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const {
Value v = getProperty(runtime, name);
if (!v.isObject()) {
throw JSError(
runtime,
std::string("getPropertyAsObject: property '") + name + "' is " +
kindToString(v, &runtime) + ", expected an Object");
}
return v.getObject(runtime);
}
Function Object::getPropertyAsFunction(Runtime& runtime, const char* name)
const {
Object obj = getPropertyAsObject(runtime, name);
if (!obj.isFunction(runtime)) {
throw JSError(
runtime,
std::string("getPropertyAsFunction: property '") + name + "' is " +
kindToString(std::move(obj), &runtime) + ", expected a Function");
};
return std::move(obj).getFunction(runtime);
}
Array Object::asArray(Runtime& runtime) const& {
if (!isArray(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected an array");
}
return getArray(runtime);
}
Array Object::asArray(Runtime& runtime) && {
if (!isArray(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected an array");
}
return std::move(*this).getArray(runtime);
}
Function Object::asFunction(Runtime& runtime) const& {
if (!isFunction(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected a function");
}
return getFunction(runtime);
}
Function Object::asFunction(Runtime& runtime) && {
if (!isFunction(runtime)) {
throw JSError(
runtime,
"Object is " + kindToString(Value(runtime, *this), &runtime) +
", expected a function");
}
return std::move(*this).getFunction(runtime);
}
Value::Value(Value&& other) noexcept : Value(other.kind_) {
if (kind_ == BooleanKind) {
data_.boolean = other.data_.boolean;
} else if (kind_ == NumberKind) {
data_.number = other.data_.number;
} else if (kind_ >= PointerKind) {
new (&data_.pointer) Pointer(std::move(other.data_.pointer));
}
// when the other's dtor runs, nothing will happen.
other.kind_ = UndefinedKind;
}
Value::Value(Runtime& runtime, const Value& other) : Value(other.kind_) {
// data_ is uninitialized, so use placement new to create non-POD
// types in it. Any other kind of initialization will call a dtor
// first, which is incorrect.
if (kind_ == BooleanKind) {
data_.boolean = other.data_.boolean;
} else if (kind_ == NumberKind) {
data_.number = other.data_.number;
} else if (kind_ == SymbolKind) {
new (&data_.pointer) Pointer(runtime.cloneSymbol(other.data_.pointer.ptr_));
} else if (kind_ == BigIntKind) {
new (&data_.pointer) Pointer(runtime.cloneBigInt(other.data_.pointer.ptr_));
} else if (kind_ == StringKind) {
new (&data_.pointer) Pointer(runtime.cloneString(other.data_.pointer.ptr_));
} else if (kind_ >= ObjectKind) {
new (&data_.pointer) Pointer(runtime.cloneObject(other.data_.pointer.ptr_));
}
}
Value::~Value() {
if (kind_ >= PointerKind) {
data_.pointer.~Pointer();
}
}
bool Value::strictEquals(Runtime& runtime, const Value& a, const Value& b) {
if (a.kind_ != b.kind_) {
return false;
}
switch (a.kind_) {
case UndefinedKind:
case NullKind:
return true;
case BooleanKind:
return a.data_.boolean == b.data_.boolean;
case NumberKind:
return a.data_.number == b.data_.number;
case SymbolKind:
return runtime.strictEquals(
static_cast<const Symbol&>(a.data_.pointer),
static_cast<const Symbol&>(b.data_.pointer));
case BigIntKind:
return runtime.strictEquals(
static_cast<const BigInt&>(a.data_.pointer),
static_cast<const BigInt&>(b.data_.pointer));
case StringKind:
return runtime.strictEquals(
static_cast<const String&>(a.data_.pointer),
static_cast<const String&>(b.data_.pointer));
case ObjectKind:
return runtime.strictEquals(
static_cast<const Object&>(a.data_.pointer),
static_cast<const Object&>(b.data_.pointer));
}
return false;
}
bool Value::asBool() const {
if (!isBool()) {
throw JSINativeException(
"Value is " + kindToString(*this) + ", expected a boolean");
}
return getBool();
}
double Value::asNumber() const {
if (!isNumber()) {
throw JSINativeException(
"Value is " + kindToString(*this) + ", expected a number");
}
return getNumber();
}
Object Value::asObject(Runtime& rt) const& {
if (!isObject()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected an Object");
}
return getObject(rt);
}
Object Value::asObject(Runtime& rt) && {
if (!isObject()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected an Object");
}
auto ptr = data_.pointer.ptr_;
data_.pointer.ptr_ = nullptr;
return static_cast<Object>(ptr);
}
Symbol Value::asSymbol(Runtime& rt) const& {
if (!isSymbol()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol");
}
return getSymbol(rt);
}
Symbol Value::asSymbol(Runtime& rt) && {
if (!isSymbol()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a Symbol");
}
return std::move(*this).getSymbol(rt);
}
BigInt Value::asBigInt(Runtime& rt) const& {
if (!isBigInt()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt");
}
return getBigInt(rt);
}
BigInt Value::asBigInt(Runtime& rt) && {
if (!isBigInt()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a BigInt");
}
return std::move(*this).getBigInt(rt);
}
String Value::asString(Runtime& rt) const& {
if (!isString()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a String");
}
return getString(rt);
}
String Value::asString(Runtime& rt) && {
if (!isString()) {
throw JSError(
rt, "Value is " + kindToString(*this, &rt) + ", expected a String");
}
return std::move(*this).getString(rt);
}
String Value::toString(Runtime& runtime) const {
Function toString = runtime.global().getPropertyAsFunction(runtime, "String");
return toString.call(runtime, *this).getString(runtime);
}
uint64_t BigInt::asUint64(Runtime& runtime) const {
if (!isUint64(runtime)) {
throw JSError(runtime, "Lossy truncation in BigInt64::asUint64");
}
return getUint64(runtime);
}
int64_t BigInt::asInt64(Runtime& runtime) const {
if (!isInt64(runtime)) {
throw JSError(runtime, "Lossy truncation in BigInt64::asInt64");
}
return getInt64(runtime);
}
Array Array::createWithElements(
Runtime& rt,
std::initializer_list<Value> elements) {
Array result(rt, elements.size());
size_t index = 0;
for (const auto& element : elements) {
result.setValueAtIndex(rt, index++, element);
}
return result;
}
std::vector<PropNameID> HostObject::getPropertyNames(Runtime&) {
return {};
}
Runtime::ScopeState* Runtime::pushScope() {
return nullptr;
}
void Runtime::popScope(ScopeState*) {}
JSError::JSError(Runtime& rt, Value&& value) {
setValue(rt, std::move(value));
}
JSError::JSError(Runtime& rt, std::string msg) : message_(std::move(msg)) {
try {
setValue(
rt,
callGlobalFunction(rt, "Error", String::createFromUtf8(rt, message_)));
} catch (const JSIException& ex) {
message_ = std::string(ex.what()) + " (while raising " + message_ + ")";
setValue(rt, String::createFromUtf8(rt, message_));
}
}
JSError::JSError(Runtime& rt, std::string msg, std::string stack)
: message_(std::move(msg)), stack_(std::move(stack)) {
try {
Object e(rt);
e.setProperty(rt, "message", String::createFromUtf8(rt, message_));
e.setProperty(rt, "stack", String::createFromUtf8(rt, stack_));
setValue(rt, std::move(e));
} catch (const JSIException& ex) {
setValue(rt, String::createFromUtf8(rt, ex.what()));
}
}
JSError::JSError(std::string what, Runtime& rt, Value&& value)
: JSIException(std::move(what)) {
setValue(rt, std::move(value));
}
JSError::JSError(Value&& value, std::string message, std::string stack)
: JSIException(message + "\n\n" + stack),
value_(std::make_shared<Value>(std::move(value))),
message_(std::move(message)),
stack_(std::move(stack)) {}
void JSError::setValue(Runtime& rt, Value&& value) {
value_ = std::make_shared<Value>(std::move(value));
if ((message_.empty() || stack_.empty()) && value_->isObject()) {
auto obj = value_->getObject(rt);
if (message_.empty()) {
try {
Value message = obj.getProperty(rt, "message");
if (!message.isUndefined() && !message.isString()) {
message = callGlobalFunction(rt, "String", message);
}
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else if (!message.isUndefined()) {
message_ = "String(e.message) is a " + kindToString(message, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
try {
Value stack = obj.getProperty(rt, "stack");
if (!stack.isUndefined() && !stack.isString()) {
stack = callGlobalFunction(rt, "String", stack);
}
if (stack.isString()) {
stack_ = stack.getString(rt).utf8(rt);
} else if (!stack.isUndefined()) {
stack_ = "String(e.stack) is a " + kindToString(stack, &rt);
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating stack string: ") +
ex.what() + "]";
}
}
}
if (message_.empty()) {
try {
if (value_->isString()) {
message_ = value_->getString(rt).utf8(rt);
} else {
Value message = callGlobalFunction(rt, "String", *value_);
if (message.isString()) {
message_ = message.getString(rt).utf8(rt);
} else {
message_ = "String(e) is a " + kindToString(message, &rt);
}
}
} catch (const JSIException& ex) {
message_ = std::string("[Exception while creating message string: ") +
ex.what() + "]";
}
}
if (stack_.empty()) {
stack_ = "no stack";
}
if (what_.empty()) {
what_ = message_ + "\n\n" + stack_;
}
}
JSIException::~JSIException() {}
JSINativeException::~JSINativeException() {}
JSError::~JSError() {}
} // namespace jsi
} // namespace facebook

1864
node_modules/react-native/ReactCommon/jsi/jsi/jsi.h generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
/*
* 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.
*/
#ifndef _WINDOWS
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include <cstdarg>
#include <stdexcept>
#include <jsi/jsilib.h>
namespace facebook {
namespace jsi {
namespace {
constexpr size_t kErrorBufferSize = 512;
__attribute__((format(printf, 1, 2))) void throwFormattedError(
const char* fmt,
...) {
char logBuffer[kErrorBufferSize];
va_list va_args;
va_start(va_args, fmt);
int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args);
va_end(va_args);
if (result < 0) {
throw JSINativeException(
std::string("Failed to format error message: ") + fmt);
}
throw JSINativeException(logBuffer);
}
class ScopedFile {
public:
ScopedFile(const std::string& path)
: path_(path), fd_(::open(path.c_str(), O_RDONLY)) {
if (fd_ == -1) {
throwFormattedError(
"Could not open %s: %s", path.c_str(), strerror(errno));
}
}
~ScopedFile() {
::close(fd_);
}
size_t size() {
struct stat fileInfo;
if (::fstat(fd_, &fileInfo) == -1) {
throwFormattedError(
"Could not stat %s: %s", path_.c_str(), strerror(errno));
}
return fileInfo.st_size;
}
uint8_t* mmap(size_t size) {
void* result = ::mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd_, 0);
if (result == MAP_FAILED) {
throwFormattedError(
"Could not mmap %s: %s", path_.c_str(), strerror(errno));
}
return reinterpret_cast<uint8_t*>(result);
}
const std::string& path_;
const int fd_;
};
} // namespace
FileBuffer::FileBuffer(const std::string& path) {
ScopedFile file(path);
size_ = file.size();
data_ = file.mmap(size_);
}
FileBuffer::~FileBuffer() {
if (::munmap(data_, size_)) {
// terminate the program with pending exception
try {
throwFormattedError(
"Could not unmap memory (%p, %zu bytes): %s",
data_,
size_,
strerror(errno));
} catch (...) {
std::terminate();
}
}
}
} // namespace jsi
} // namespace facebook
#endif // !defined(_WINDOWS)

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#ifdef _WINDOWS
#include <jsi/jsilib.h>
namespace facebook {
namespace jsi {
FileBuffer::FileBuffer(const std::string&) {
// TODO(T41045067) Implement this on Windows
throw new JSINativeException("FileBuffer is not implemented on Windows");
}
FileBuffer::~FileBuffer() {
assert(false && "FileBuffer is not implemented on Windows");
}
} // namespace jsi
} // namespace facebook
#endif //_WINDOWS

59
node_modules/react-native/ReactCommon/jsi/jsi/jsilib.h generated vendored Normal file
View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
#pragma once
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
class FileBuffer : public Buffer {
public:
FileBuffer(const std::string& path);
~FileBuffer() override;
size_t size() const override {
return size_;
}
const uint8_t* data() const override {
return data_;
}
private:
size_t size_;
uint8_t* data_;
};
// A trivial implementation of PreparedJavaScript that simply stores the source
// buffer and URL.
class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript,
public jsi::Buffer {
std::shared_ptr<const jsi::Buffer> buf_;
std::string sourceURL_;
public:
SourceJavaScriptPreparation(
std::shared_ptr<const jsi::Buffer> buf,
std::string sourceURL)
: buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {}
const std::string& sourceURL() const {
return sourceURL_;
}
size_t size() const override {
return buf_->size();
}
const uint8_t* data() const override {
return buf_->data();
}
};
} // namespace jsi
} // namespace facebook

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
/*
* 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.
*/
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
class Runtime;
using RuntimeFactory = std::function<std::shared_ptr<Runtime>()>;
std::vector<RuntimeFactory> runtimeGenerators();
class JSITestBase : public ::testing::TestWithParam<RuntimeFactory> {
public:
JSITestBase() : factory(GetParam()), runtime(factory()), rt(*runtime) {}
Value eval(const char* code) {
return rt.global().getPropertyAsFunction(rt, "eval").call(rt, code);
}
Function function(const std::string& code) {
return eval(("(" + code + ")").c_str()).getObject(rt).getFunction(rt);
}
bool checkValue(const Value& value, const std::string& jsValue) {
return function("function(value) { return value == " + jsValue + "; }")
.call(rt, std::move(value))
.getBool();
}
RuntimeFactory factory;
std::shared_ptr<Runtime> runtime;
Runtime& rt;
};
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,79 @@
/*
* 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.
*/
#pragma once
#include <mutex>
#include <jsi/decorator.h>
#include <jsi/jsi.h>
namespace facebook {
namespace jsi {
class ThreadSafeRuntime : public Runtime {
public:
virtual void lock() const = 0;
virtual void unlock() const = 0;
virtual Runtime& getUnsafeRuntime() = 0;
};
namespace detail {
template <typename R, typename L>
struct WithLock {
L lock;
WithLock(R& r) : lock(r) {}
void before() {
lock.lock();
}
void after() {
lock.unlock();
}
};
// The actual implementation of a given ThreadSafeRuntime. It's parameterized
// by:
//
// - R: The actual Runtime type that this wraps
// - L: A lock type that has three members:
// - L(R& r) // ctor
// - void lock()
// - void unlock()
template <typename R, typename L>
class ThreadSafeRuntimeImpl final
: public WithRuntimeDecorator<WithLock<R, L>, R, ThreadSafeRuntime> {
public:
template <typename... Args>
ThreadSafeRuntimeImpl(Args&&... args)
: WithRuntimeDecorator<WithLock<R, L>, R, ThreadSafeRuntime>(
unsafe_,
lock_),
unsafe_(std::forward<Args>(args)...),
lock_(unsafe_) {}
R& getUnsafeRuntime() override {
return WithRuntimeDecorator<WithLock<R, L>, R, ThreadSafeRuntime>::plain();
}
void lock() const override {
lock_.before();
}
void unlock() const override {
lock_.after();
}
private:
R unsafe_;
mutable WithLock<R, L> lock_;
};
} // namespace detail
} // namespace jsi
} // namespace facebook

View File

@@ -0,0 +1,26 @@
# 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.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
add_library(jsireact
OBJECT
jsireact/JSIExecutor.cpp
jsireact/JSINativeModules.cpp)
target_include_directories(jsireact PUBLIC .)
target_link_libraries(jsireact
react_cxxreact
reactperflogger
folly_runtime
glog
jsi)
target_compile_reactnative_options(jsireact PRIVATE)
target_compile_options(jsireact PRIVATE -O3)

Some files were not shown because too many files have changed in this diff Show More