-
Notifications
You must be signed in to change notification settings - Fork 24.5k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix dynamic_cast (RTTI) by adding key function to ShadowNodeWrapper and related classes #33500
Conversation
…nd related classes
|
@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
Base commit: 96c611b |
@@ -30,13 +30,21 @@ struct ShadowNodeWrapper : public jsi::HostObject { | |||
ShadowNodeWrapper(SharedShadowNode shadowNode) | |||
: shadowNode(std::move(shadowNode)) {} | |||
|
|||
// The below method needs to be 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) | |||
virtual ~ShadowNodeWrapper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apparently, it needs to not be inlined and exists in one of the binaries to be treated a s a key function
@@ -30,13 +30,21 @@ struct ShadowNodeWrapper : public jsi::HostObject { | |||
ShadowNodeWrapper(SharedShadowNode shadowNode) | |||
: shadowNode(std::move(shadowNode)) {} | |||
|
|||
// The below method needs to be 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) | |||
virtual ~ShadowNodeWrapper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use override, which implies virtual.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that's also one of the reason the internal build is failing. Thanks for pointing this out 👍
ShadowNode::Shared shadowNode; | ||
}; | ||
|
||
struct ShadowNodeListWrapper : public jsi::HostObject { | ||
ShadowNodeListWrapper(SharedShadowNodeUnsharedList shadowNodeList) | ||
: shadowNodeList(shadowNodeList) {} | ||
|
||
// The below method needs to be 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) | ||
virtual ~ShadowNodeListWrapper(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
Base commit: 96c611b |
Thanks for prompt review @javache – AFAIK dynamic_cast should work ok w/o rtti compiler flag enabled so long there is a only one library that has a vtable for the class.I'm not entirely sure I understand the traits approach nevertheless I think an alternative solution here would be to replace dynamic_cast from the jsi implementation with reinterpret_cast as it like with |
@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
The traits solution only applies to subclasses of ShadowView, not in scenarios where you have a HostObject which could be anything. |
I'm handing this over to @javache for driving it to landing 👍 |
This pull request was successfully merged by @kmagiera in 58a2eb7. When will my fix make it into a release? | Upcoming Releases |
…nd related classes (#33500) 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: facebook/react-native#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
…nd related classes (#33500) 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: #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
…nd related classes (facebook#33500) 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: facebook#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
…gain (#45290) Summary: This PR restores the virtual destructor for `ShadowNodeWrapper` which was added in #33500 and unfortunately removed in #40864. The virtual destructor here serves as a key function. Without a key function, `obj.hasNativeState<ShadowNodeWrapper>(rt)` **does not** work correctly between shared library boundaries on Android and always returns false. We need this pretty badly in third-party libraries like react-native-reanimated or react-native-gesture-handler. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [ANDROID] [FIXED] - Fix dynamic_cast (RTTI) for ShadowNodeWrapper when accessed by third-party libraries again Pull Request resolved: #45290 Test Plan: This patch fixes an issue in Reanimated's fabric-example app. Reviewed By: fabriziocucci Differential Revision: D59375554 Pulled By: javache fbshipit-source-id: 09f3eda89a67c26d6dacca3428e08d1b7138d350
…gain (#45290) Summary: This PR restores the virtual destructor for `ShadowNodeWrapper` which was added in #33500 and unfortunately removed in #40864. The virtual destructor here serves as a key function. Without a key function, `obj.hasNativeState<ShadowNodeWrapper>(rt)` **does not** work correctly between shared library boundaries on Android and always returns false. We need this pretty badly in third-party libraries like react-native-reanimated or react-native-gesture-handler. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [ANDROID] [FIXED] - Fix dynamic_cast (RTTI) for ShadowNodeWrapper when accessed by third-party libraries again Pull Request resolved: #45290 Test Plan: This patch fixes an issue in Reanimated's fabric-example app. Reviewed By: fabriziocucci Differential Revision: D59375554 Pulled By: javache fbshipit-source-id: 09f3eda89a67c26d6dacca3428e08d1b7138d350
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 togetHostObject
which is an inline function that callsdynamic_cast
for theShadowNodeWrapper
class. On the other hand, the instances ofShadowNodeWrapper
are created by the code fromlibreact_render_uimanager.so
. Because of thatdynamic_cast
fails even though it is called on instance ofShadowNodeWrapper
because the class has separate vtable/type info: one inlibreanimated.so
and one inlibreact_render_uimanager.so
(by "fails" I mean that it actually returnsnullptr
).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 forShadowNodeWrapper
andShadowNodeListWrapper
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.
Test Plan
<react/renderer/uimanager/primitives.h>
(i.e. use this branch of reanimated library: https://github.com/software-mansion/react-native-reanimated/tree/fabric)nm
toolvtable for facebook::react::ShadowNodeWrapper
andtypeinfo for facebook::react::ShadowNodeWrapper
are only present in the former and not in the latter library (before this change you'd see them both)