mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: This PR fixes RTTI (run-time type information) for ShadowNodeWrapper and ShadowNodeListWrapper classes, i.e., calls to dynamic_cast and dynamic_pointer_cast that are called via JSI's getHostObject calls. The fix is simply to add a so-called "key function" in a form of virtual destructor. Key functions needs to be a virtual non-pure and non-inlined functions that points the compiler as to which library contains the vtable/type information for a given class (see https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-vtable and https://developer.android.com/ndk/guides/common-problems#rttiexceptions_not_working_across_library_boundaries) Without the "key function", calls to dynamic_cast for ShadowNodeWrapper instances won't work across library boundaries because the class will have separate definitions in each separate library, therefore objects created in one of those libraries won't be recognized as the same type by the other library. This has been a problem in reanimated and gesture-handler libraries where we call `object.getHostObject<ShadowNodeWrapper>(rt)` (this is a method from JSI) in order to access ShadowNode instance from a handle we have in JS. I think, this issue is going to be relevant to more libraries that cope with view instances. In this scenario, we have a separate library, say "libreanimated.so" that calls to `getHostObject` which is an inline function that calls `dynamic_cast` for the `ShadowNodeWrapper` class. On the other hand, the instances of `ShadowNodeWrapper` are created by the code from `libreact_render_uimanager.so`. Because of that `dynamic_cast` fails even though it is called on instance of `ShadowNodeWrapper` because the class has separate vtable/type info: one in `libreanimated.so` and one in `libreact_render_uimanager.so` (by "fails" I mean that it actually returns `nullptr`). This problem has been documented here: https://developer.android.com/ndk/guides/common-problems#rttiexceptions_not_working_across_library_boundaries where the solution is for the class to have a so-called "key function". The key function makes it so that compiler sees that one of the implementation for a given class is missing and therefore can safely assume that a vtable/type info for a given class is embedded into some library we link to. This change adds a virtual destructor that is declared in the header file but defined in file that gets compiled as a part of `libreact_render_uimanager`. As a result, the compiler only creates one vtable/type info and calls to dynamic_cast works as expected in all libraries for `ShadowNodeWrapper` and `ShadowNodeListWrapper` classes. This issue would only surface on Android, because on iOS all libraries by default are bundled together via Pods, whereas on Android each library is loaded separately using dynamic loading. ## Changelog [Fabric][Android specific] - Fix dynamic_cast (RTTI) for ShadowNodeWrapper and similar classes when accessed by third-party libraries. Pull Request resolved: https://github.com/facebook/react-native/pull/33500 Test Plan: 1. In order to test this you need to add a library that'd include `<react/renderer/uimanager/primitives.h>` (i.e. use this branch of reanimated library: https://github.com/software-mansion/react-native-reanimated/tree/fabric) 2. After compiling the app inspect libreact_render_uimanager.so and libreanimated.so artifacts with `nm` tool 3. Notice that symbols like `vtable for facebook::react::ShadowNodeWrapper` and `typeinfo for facebook::react::ShadowNodeWrapper` are only present in the former and not in the latter library (before this change you'd see them both) Reviewed By: ShikaSD Differential Revision: D35143600 Pulled By: javache fbshipit-source-id: 5fb25a02365b99a515edc81e5485a77017c56eb8
163 lines
4.7 KiB
C++
163 lines
4.7 KiB
C++
/*
|
|
* 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/JSIDynamic.h>
|
|
#include <jsi/jsi.h>
|
|
#include <react/debug/react_native_assert.h>
|
|
#include <react/renderer/core/EventHandler.h>
|
|
#include <react/renderer/core/ShadowNode.h>
|
|
|
|
namespace facebook::react {
|
|
|
|
using BackgroundExecutor =
|
|
std::function<void(std::function<void()> &&callback)>;
|
|
|
|
struct EventHandlerWrapper : public EventHandler {
|
|
EventHandlerWrapper(jsi::Function eventHandler)
|
|
: callback(std::move(eventHandler)) {}
|
|
|
|
jsi::Function callback;
|
|
};
|
|
|
|
struct ShadowNodeWrapper : public jsi::HostObject {
|
|
ShadowNodeWrapper(SharedShadowNode shadowNode)
|
|
: shadowNode(std::move(shadowNode)) {}
|
|
|
|
// The below method needs to be implemented out-of-line in order for the class
|
|
// to have at least one "key function" (see
|
|
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-vtable)
|
|
~ShadowNodeWrapper() override;
|
|
|
|
ShadowNode::Shared shadowNode;
|
|
};
|
|
|
|
struct ShadowNodeListWrapper : public jsi::HostObject {
|
|
ShadowNodeListWrapper(SharedShadowNodeUnsharedList shadowNodeList)
|
|
: shadowNodeList(shadowNodeList) {}
|
|
|
|
// The below method needs to be implemented out-of-line in order for the class
|
|
// to have at least one "key function" (see
|
|
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-vtable)
|
|
~ShadowNodeListWrapper() override;
|
|
|
|
SharedShadowNodeUnsharedList shadowNodeList;
|
|
};
|
|
|
|
inline static ShadowNode::Shared shadowNodeFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
if (value.isNull()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return value.getObject(runtime)
|
|
.getHostObject<ShadowNodeWrapper>(runtime)
|
|
->shadowNode;
|
|
}
|
|
|
|
inline static jsi::Value valueFromShadowNode(
|
|
jsi::Runtime &runtime,
|
|
const ShadowNode::Shared &shadowNode) {
|
|
return jsi::Object::createFromHostObject(
|
|
runtime, std::make_shared<ShadowNodeWrapper>(shadowNode));
|
|
}
|
|
|
|
inline static SharedShadowNodeUnsharedList shadowNodeListFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
return value.getObject(runtime)
|
|
.getHostObject<ShadowNodeListWrapper>(runtime)
|
|
->shadowNodeList;
|
|
}
|
|
|
|
inline static ShadowNode::UnsharedListOfShared shadowNodeListFromWeakList(
|
|
ShadowNode::UnsharedListOfWeak const &weakShadowNodeList) {
|
|
auto result = std::make_shared<ShadowNode::ListOfShared>();
|
|
for (auto const &weakShadowNode : *weakShadowNodeList) {
|
|
auto sharedShadowNode = weakShadowNode.lock();
|
|
if (!sharedShadowNode) {
|
|
return nullptr;
|
|
}
|
|
result->push_back(sharedShadowNode);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline static ShadowNode::UnsharedListOfWeak weakShadowNodeListFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
auto shadowNodeList = value.getObject(runtime)
|
|
.getHostObject<ShadowNodeListWrapper>(runtime)
|
|
->shadowNodeList;
|
|
|
|
auto weakShadowNodeList = std::make_shared<ShadowNode::ListOfWeak>();
|
|
for (auto const &shadowNode : *shadowNodeList) {
|
|
weakShadowNodeList->push_back(shadowNode);
|
|
}
|
|
|
|
return weakShadowNodeList;
|
|
}
|
|
|
|
inline static jsi::Value valueFromShadowNodeList(
|
|
jsi::Runtime &runtime,
|
|
const SharedShadowNodeUnsharedList &shadowNodeList) {
|
|
return jsi::Object::createFromHostObject(
|
|
runtime, std::make_unique<ShadowNodeListWrapper>(shadowNodeList));
|
|
}
|
|
|
|
inline static Tag tagFromValue(jsi::Value const &value) {
|
|
return (Tag)value.getNumber();
|
|
}
|
|
|
|
inline static SharedEventTarget eventTargetFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &eventTargetValue,
|
|
jsi::Value const &tagValue) {
|
|
react_native_assert(!eventTargetValue.isNull());
|
|
if (eventTargetValue.isNull()) {
|
|
return nullptr;
|
|
}
|
|
return std::make_shared<EventTarget>(
|
|
runtime, eventTargetValue, tagFromValue(tagValue));
|
|
}
|
|
|
|
inline static SurfaceId surfaceIdFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
return (SurfaceId)value.getNumber();
|
|
}
|
|
|
|
inline static int displayModeToInt(DisplayMode const value) {
|
|
// the result of this method should be in sync with
|
|
// Libraries/ReactNative/DisplayMode.js
|
|
switch (value) {
|
|
case DisplayMode::Visible:
|
|
return 1;
|
|
case DisplayMode::Suspended:
|
|
return 2;
|
|
case DisplayMode::Hidden:
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
inline static std::string stringFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
return value.getString(runtime).utf8(runtime);
|
|
}
|
|
|
|
inline static folly::dynamic commandArgsFromValue(
|
|
jsi::Runtime &runtime,
|
|
jsi::Value const &value) {
|
|
return jsi::dynamicFromValue(runtime, value);
|
|
}
|
|
|
|
} // namespace facebook::react
|