Skip to content

Commit

Permalink
Remove usages of RTTI in places used by react-native-windows (#31694)
Browse files Browse the repository at this point in the history
Summary:
Adding runtime type information adds greatly to the binary size, so react-native-windows builds without it.  But some parts of the fabric code currently uses dynamic_cast, which means to use fabric we have to build with RTTI turned on.  This PR removes the usages of dynamic_cast that are hit in release builds, which should allow react-native-windows to turn off RTTI in release builds.

Required for:  microsoft/react-native-windows#7981

One thing to note, the comment in ShadowNodeTraits indicates that core was reserving the first 16 bits.  I'm adding two more.  Is that ok?  Should core be reserving more for future use?

## Changelog

<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->

[Internal] [Fixed] - Remove uses of dynamic_cast in release builds

Pull Request resolved: #31694

Test Plan:
Verified that I can build react-native-windows with Fabric in release, without RTTI.
Boot / clicked around in RNW RNTester

Reviewed By: sammy-SC

Differential Revision: D29040383

Pulled By: JoshuaGross

fbshipit-source-id: e49286e59c4ba54faf0b4de5e244dfa1f7c3f193
  • Loading branch information
acoates-ms authored and facebook-github-bot committed Jun 11, 2021
1 parent 06c33e9 commit 050922a
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 14 deletions.
Expand Up @@ -33,7 +33,7 @@ void BaseTextShadowNode::buildAttributedString(
for (auto const &childNode : parentNode.getChildren()) {
// RawShadowNode
auto rawTextShadowNode =
std::dynamic_pointer_cast<RawTextShadowNode const>(childNode);
traitCast<RawTextShadowNode const*>(childNode.get());
if (rawTextShadowNode) {
auto fragment = AttributedString::Fragment{};
fragment.string = rawTextShadowNode->getConcreteProps().text;
Expand All @@ -49,8 +49,7 @@ void BaseTextShadowNode::buildAttributedString(
}

// TextShadowNode
auto textShadowNode =
std::dynamic_pointer_cast<TextShadowNode const>(childNode);
auto textShadowNode = traitCast<TextShadowNode const*>(childNode.get());
if (textShadowNode) {
auto localTextAttributes = baseTextAttributes;
localTextAttributes.apply(
Expand Down
45 changes: 43 additions & 2 deletions ReactCommon/react/renderer/components/text/RawTextShadowNode.h
Expand Up @@ -21,8 +21,49 @@ extern const char RawTextComponentName[];
* is represented as `<RawText text="Hello!"/>`.
* <RawText> component must not have any children.
*/
using RawTextShadowNode =
ConcreteShadowNode<RawTextComponentName, ShadowNode, RawTextProps>;
class RawTextShadowNode : public ConcreteShadowNode<
RawTextComponentName,
ShadowNode,
RawTextProps> {
public:
using ConcreteShadowNode::ConcreteShadowNode;
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::RawText);
return traits;
}
};

template <>
inline RawTextShadowNode const& traitCast<RawTextShadowNode const&>(
ShadowNode const& shadowNode) {
bool castable =
shadowNode.getTraits().check(ShadowNodeTraits::Trait::RawText);
react_native_assert(
castable ==
(dynamic_cast<RawTextShadowNode const*>(&shadowNode) != nullptr));
react_native_assert(castable);
(void)castable;
return static_cast<RawTextShadowNode const&>(shadowNode);
}

template <>
inline RawTextShadowNode const* traitCast<RawTextShadowNode const*>(
ShadowNode const* shadowNode) {
if (!shadowNode) {
return nullptr;
}
bool castable =
shadowNode->getTraits().check(ShadowNodeTraits::Trait::RawText);
react_native_assert(
castable ==
(dynamic_cast<RawTextShadowNode const*>(shadowNode) != nullptr));
if (!castable) {
return nullptr;
}
return static_cast<RawTextShadowNode const*>(shadowNode);
}


} // namespace react
} // namespace facebook
32 changes: 32 additions & 0 deletions ReactCommon/react/renderer/components/text/TextShadowNode.h
Expand Up @@ -34,6 +34,7 @@ class TextShadowNode : public ConcreteShadowNode<
#ifdef ANDROID
traits.set(ShadowNodeTraits::Trait::FormsView);
#endif
traits.set(ShadowNodeTraits::Trait::Text);

return traits;
}
Expand All @@ -57,5 +58,36 @@ class TextShadowNode : public ConcreteShadowNode<
#endif
};


template <>
inline TextShadowNode const& traitCast<TextShadowNode const&>(
ShadowNode const& shadowNode) {
bool castable =
shadowNode.getTraits().check(ShadowNodeTraits::Trait::Text);
react_native_assert(
castable ==
(dynamic_cast<TextShadowNode const*>(&shadowNode) != nullptr));
react_native_assert(castable);
(void)castable;
return static_cast<TextShadowNode const&>(shadowNode);
}

template <>
inline TextShadowNode const* traitCast<TextShadowNode const*>(
ShadowNode const* shadowNode) {
if (!shadowNode) {
return nullptr;
}
bool castable =
shadowNode->getTraits().check(ShadowNodeTraits::Trait::Text);
react_native_assert(
castable ==
(dynamic_cast<TextShadowNode const*>(shadowNode) != nullptr));
if (!castable) {
return nullptr;
}
return static_cast<TextShadowNode const*>(shadowNode);
}

} // namespace react
} // namespace facebook
Expand Up @@ -24,16 +24,19 @@ static inline void interpolateViewProps(
const SharedProps &oldPropsShared,
const SharedProps &newPropsShared,
SharedProps &interpolatedPropsShared) {

// Verify the static_casts below are safe
react_native_assert(
dynamic_cast<ViewProps const *>(oldPropsShared.get()) != nullptr &&
dynamic_cast<ViewProps const *>(newPropsShared.get()) != nullptr &&
dynamic_cast<ViewProps const *>(interpolatedPropsShared.get()) != nullptr);

ViewProps const *oldViewProps =
dynamic_cast<ViewProps const *>(oldPropsShared.get());
static_cast<ViewProps const *>(oldPropsShared.get());
ViewProps const *newViewProps =
dynamic_cast<ViewProps const *>(newPropsShared.get());
static_cast<ViewProps const *>(newPropsShared.get());
ViewProps *interpolatedProps = const_cast<ViewProps *>(
dynamic_cast<ViewProps const *>(interpolatedPropsShared.get()));

react_native_assert(
oldViewProps != nullptr && newViewProps != nullptr &&
interpolatedProps != nullptr);
static_cast<ViewProps const *>(interpolatedPropsShared.get()));

interpolatedProps->opacity = oldViewProps->opacity +
(newViewProps->opacity - oldViewProps->opacity) * animationProgress;
Expand Down
2 changes: 1 addition & 1 deletion ReactCommon/react/renderer/core/LayoutableShadowNode.cpp
Expand Up @@ -216,7 +216,7 @@ ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint(
ShadowNode::Shared node,
Point point) {
auto layoutableShadowNode =
dynamic_cast<const LayoutableShadowNode *>(node.get());
traitCast<const LayoutableShadowNode *>(node.get());

if (!layoutableShadowNode) {
return nullptr;
Expand Down
8 changes: 7 additions & 1 deletion ReactCommon/react/renderer/core/ShadowNodeTraits.h
Expand Up @@ -21,7 +21,7 @@ class ShadowNodeTraits {
public:
/*
* Underlying type for the traits.
* The first 16 bits are reserved for Core.
* The first 18 bits are reserved for Core.
*/
enum Trait : int32_t {
None = 0,
Expand Down Expand Up @@ -82,6 +82,12 @@ class ShadowNodeTraits {
// Indicates that `children` list is shared between nodes and need
// to be cloned before the first mutation.
ChildrenAreShared = 1 << 15,

// Inherits 'RawTextShadowNode'
RawText = 1 << 16,

// Inherits 'TextShadowNode'
Text = 1 << 17,
};

/*
Expand Down
34 changes: 34 additions & 0 deletions ReactCommon/react/renderer/core/tests/traitCastTest.cpp
Expand Up @@ -47,6 +47,9 @@ TEST(traitCastTest, testOne) {

auto rootShadowNode = builder.build(element);

std::shared_ptr<ShadowNode> shadowNodeForRawTextShadowNode{rawTextShadowNode};
std::shared_ptr<ShadowNode> shadowNodeForTextShadowNode{textShadowNode};

// Casting `nullptr` returns `nullptrs`.
EXPECT_FALSE(traitCast<LayoutableShadowNode const *>(nullptr));
EXPECT_FALSE(traitCast<YogaLayoutableShadowNode const *>(nullptr));
Expand Down Expand Up @@ -102,4 +105,35 @@ TEST(traitCastTest, testOne) {
traitCast<LayoutableShadowNode const &>(*rawTextShadowNode), "");
EXPECT_DEATH_IF_SUPPORTED(
traitCast<YogaLayoutableShadowNode const &>(*rawTextShadowNode), "");

// trait cast to `RawTextShadowNode` works on `RawTextShadowNode`
// and not on TextShadowNode or ViewShadowNode
EXPECT_TRUE(
traitCast<RawTextShadowNode const *>(shadowNodeForRawTextShadowNode.get()));
EXPECT_NO_FATAL_FAILURE(
traitCast<RawTextShadowNode const &>(*shadowNodeForRawTextShadowNode));
EXPECT_FALSE(
traitCast<RawTextShadowNode const *>(shadowNodeForTextShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
traitCast<RawTextShadowNode const &>(*shadowNodeForTextShadowNode), "");
EXPECT_FALSE(
traitCast<RawTextShadowNode const *>(viewShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
traitCast<RawTextShadowNode const &>(*viewShadowNode), "");

// trait cast to `TextShadowNode` works on `TextShadowNode`
// and not on RawTextShadowNode or ViewShadowNode
EXPECT_TRUE(
traitCast<TextShadowNode const *>(shadowNodeForTextShadowNode.get()));
EXPECT_NO_FATAL_FAILURE(
traitCast<TextShadowNode const &>(*shadowNodeForTextShadowNode));
EXPECT_FALSE(
traitCast<TextShadowNode const *>(shadowNodeForRawTextShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
traitCast<TextShadowNode const &>(*shadowNodeForRawTextShadowNode), "");
EXPECT_FALSE(
traitCast<TextShadowNode const *>(viewShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
traitCast<TextShadowNode const &>(*viewShadowNode), "");

}

0 comments on commit 050922a

Please sign in to comment.