Files
react-native/ReactCommon/react/renderer/core/tests/traitCastTest.cpp
T
Nick Gerleman c2a089fddf Better ShadowNode memory safety (#36258)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/36258

This fixes a few instances where YogaLayoutableShadowNode (or general shadownode casting) could offer better memory safety.

1. The reference form of traitCast() now terminates on invalid cast, instead of debug assert, since it is better to crash in production than to corrupt memory (which will crash somewhere later, in a much more confusing way).
2. We use traitCast() in more places where we previously would static_cast. This means needing to formally add a mutable version.
3. We bounds-check yoga children access in a single place by using `std::vector` `at()` instead of `[]`.
4. Removed `Trait::UnreservedTrait1` API, since multiple libraries using it can collide and we lose the memory safety benefits of `traitCast`.

This change is in response to a bug where `YogaLayoutableShadowNode` may perform an invalid `static_cast` of `RawTextShadowNode` if a text or number is rendered directly inside of a `<View>` (instead of a `<Text>` element).

This does not yet fix the underlying logic of YogaLayoutableShadowNode to act gracefully when a RawTextShadowNode makes its way into children. We just terminate, instead of corrupting memory.

Changelog:
[General][Breaking] -  Better Fabric ShadowNode Memory Safety (Removes `Trait::UnreservedTrait` API)

Reviewed By: javache

Differential Revision: D43271779

fbshipit-source-id: 727c1230f72664bf4d261871c66ca61ddf0d5ffa
2023-02-27 10:07:51 -08:00

147 lines
6.2 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.
*/
#include <gtest/gtest.h>
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/text/ParagraphComponentDescriptor.h>
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
#include <react/renderer/components/text/TextComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
#include <react/renderer/core/TraitCast.h>
#include <react/renderer/element/Element.h>
#include <react/renderer/element/testUtils.h>
using namespace facebook::react;
TEST(traitCastTest, testOne) {
auto builder = simpleComponentBuilder();
auto viewShadowNode = std::shared_ptr<ViewShadowNode>{};
auto scrollViewShadowNode = std::shared_ptr<ScrollViewShadowNode>{};
auto paragraphShadowNode = std::shared_ptr<ParagraphShadowNode>{};
auto textShadowNode = std::shared_ptr<TextShadowNode>{};
auto rawTextShadowNode = std::shared_ptr<RawTextShadowNode>{};
// clang-format off
auto element =
Element<ScrollViewShadowNode>()
.reference(scrollViewShadowNode)
.children({
Element<ParagraphShadowNode>()
.reference(paragraphShadowNode)
.children({
Element<TextShadowNode>()
.reference(textShadowNode),
Element<RawTextShadowNode>()
.reference(rawTextShadowNode)
}),
Element<ViewShadowNode>()
.reference(viewShadowNode),
});
// clang-format on
auto rootShadowNode = builder.build(element);
std::shared_ptr<ShadowNode> shadowNodeForRawTextShadowNode{rawTextShadowNode};
std::shared_ptr<ShadowNode> shadowNodeForTextShadowNode{textShadowNode};
// Casting `nullptr` returns `nullptrs`.
ShadowNode *nullShadowNode = nullptr;
EXPECT_FALSE(traitCast<LayoutableShadowNode const *>(nullShadowNode));
EXPECT_FALSE(traitCast<YogaLayoutableShadowNode const *>(nullShadowNode));
EXPECT_FALSE(traitCast<LayoutableShadowNode const *>(nullShadowNode));
EXPECT_FALSE(traitCast<LayoutableShadowNode *>(nullShadowNode));
EXPECT_FALSE(traitCast<LayoutableShadowNode>(
std::shared_ptr<ShadowNode>(nullShadowNode)));
// `ViewShadowNode` is `LayoutableShadowNode` and `YogaLayoutableShadowNode`.
EXPECT_TRUE(traitCast<LayoutableShadowNode const *>(viewShadowNode.get()));
EXPECT_TRUE(
traitCast<YogaLayoutableShadowNode const *>(viewShadowNode.get()));
EXPECT_NO_FATAL_FAILURE(
traitCast<LayoutableShadowNode const &>(*viewShadowNode));
EXPECT_NO_FATAL_FAILURE(
traitCast<YogaLayoutableShadowNode const &>(*viewShadowNode));
EXPECT_NO_FATAL_FAILURE(
traitCast<YogaLayoutableShadowNode &>(*viewShadowNode));
EXPECT_TRUE(traitCast<LayoutableShadowNode *>(viewShadowNode.get()));
EXPECT_TRUE(traitCast<LayoutableShadowNode>(viewShadowNode));
// `ScrollViewShadowNode` is `LayoutableShadowNode` and
// `YogaLayoutableShadowNode`.
EXPECT_TRUE(
traitCast<LayoutableShadowNode const *>(scrollViewShadowNode.get()));
EXPECT_TRUE(
traitCast<YogaLayoutableShadowNode const *>(scrollViewShadowNode.get()));
EXPECT_NO_FATAL_FAILURE(
traitCast<LayoutableShadowNode const &>(*scrollViewShadowNode));
EXPECT_NO_FATAL_FAILURE(
traitCast<YogaLayoutableShadowNode const &>(*scrollViewShadowNode));
// `ParagraphShadowNode` is `LayoutableShadowNode` and
// `YogaLayoutableShadowNode`.
EXPECT_TRUE(
traitCast<LayoutableShadowNode const *>(paragraphShadowNode.get()));
EXPECT_TRUE(
traitCast<YogaLayoutableShadowNode const *>(paragraphShadowNode.get()));
EXPECT_NO_FATAL_FAILURE(
traitCast<LayoutableShadowNode const &>(*paragraphShadowNode));
EXPECT_NO_FATAL_FAILURE(
traitCast<YogaLayoutableShadowNode const &>(*paragraphShadowNode));
// `TextShadowNode` is *not* `LayoutableShadowNode` nor
// `YogaLayoutableShadowNode`.
EXPECT_FALSE(traitCast<LayoutableShadowNode const *>(textShadowNode.get()));
EXPECT_FALSE(
traitCast<YogaLayoutableShadowNode const *>(textShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
traitCast<LayoutableShadowNode const &>(*textShadowNode), "");
EXPECT_DEATH_IF_SUPPORTED(
traitCast<YogaLayoutableShadowNode const &>(*textShadowNode), "");
// `RawTextShadowNode` is *not* `LayoutableShadowNode` nor
// `YogaLayoutableShadowNode`.
EXPECT_FALSE(
traitCast<LayoutableShadowNode const *>(rawTextShadowNode.get()));
EXPECT_FALSE(
traitCast<YogaLayoutableShadowNode const *>(rawTextShadowNode.get()));
EXPECT_DEATH_IF_SUPPORTED(
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), "");
}