Files
yoga/yoga/node/Node.h
Nick Gerleman fb53cb7443 Reland: Sanitize measure function results
Summary:
D57285584 was reverted because we have service code with a faulty measure function, and adding logging to Yoga when invalid measurements were received was enough to spike error rate to elevated levels and block release.

This is a reland of the below change, with a couple modifications:
1. We log warnings instead of errors, which from what I heard, shouldn't block release, but should still make signal
2. We only zero the dimension which was NaN, to preserve exact behavior

## Original

We've started seeing assertion failures in Yoga where a `NaN` value makes its way to an `availableHeight` constraint when measuring Litho tree.

Because it's only happening on Litho, I have some suspicion this might be originating from a Litho-specific measure function. This adds sanitization in Yoga to measure function results, where we will log an error, and set size to zero, if either dimension ends up being negative of `NaN`.

This doesn't really help track down where the error was happening, but Yoga doesn't have great context to show this to begin with. If we see this is issue, next steps would be Litho internal intrumentation to find culprit.

Changelog: [Internal]

Reviewed By: sbuggay

Differential Revision: D57473295

fbshipit-source-id: 979f1b9a51f5550a8d3ca534276ec191a3cb7b9e
2024-05-17 19:12:13 -07:00

300 lines
7.4 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 <cstdint>
#include <cstdio>
#include <vector>
#include <yoga/Yoga.h>
#include <yoga/config/Config.h>
#include <yoga/enums/Dimension.h>
#include <yoga/enums/Direction.h>
#include <yoga/enums/Edge.h>
#include <yoga/enums/Errata.h>
#include <yoga/enums/MeasureMode.h>
#include <yoga/enums/NodeType.h>
#include <yoga/enums/PhysicalEdge.h>
#include <yoga/node/LayoutResults.h>
#include <yoga/style/Style.h>
// Tag struct used to form the opaque YGNodeRef for the public C API
struct YGNode {};
namespace facebook::yoga {
class YG_EXPORT Node : public ::YGNode {
public:
Node();
explicit Node(const Config* config);
Node(Node&& node) noexcept;
// Does not expose true value semantics, as children are not cloned eagerly.
// Should we remove this?
Node(const Node& node) = default;
// assignment means potential leaks of existing children, or alternatively
// freeing unowned memory, double free, or freeing stack memory.
Node& operator=(const Node&) = delete;
// Getters
void* getContext() const {
return context_;
}
bool alwaysFormsContainingBlock() const {
return alwaysFormsContainingBlock_;
}
bool getHasNewLayout() const {
return hasNewLayout_;
}
NodeType getNodeType() const {
return nodeType_;
}
bool hasMeasureFunc() const noexcept {
return measureFunc_ != nullptr;
}
YGSize measure(
float availableWidth,
MeasureMode widthMode,
float availableHeight,
MeasureMode heightMode);
bool hasBaselineFunc() const noexcept {
return baselineFunc_ != nullptr;
}
float baseline(float width, float height) const;
float dimensionWithMargin(FlexDirection axis, float widthSize);
bool isLayoutDimensionDefined(FlexDirection axis);
/**
* Whether the node has a "definite length" along the given axis.
* https://www.w3.org/TR/css-sizing-3/#definite
*/
inline bool hasDefiniteLength(Dimension dimension, float ownerSize) {
auto usedValue = getResolvedDimension(dimension).resolve(ownerSize);
return usedValue.isDefined() && usedValue.unwrap() >= 0.0f;
}
bool hasErrata(Errata errata) const {
return config_->hasErrata(errata);
}
YGDirtiedFunc getDirtiedFunc() const {
return dirtiedFunc_;
}
// For Performance reasons passing as reference.
Style& style() {
return style_;
}
const Style& style() const {
return style_;
}
// For Performance reasons passing as reference.
LayoutResults& getLayout() {
return layout_;
}
const LayoutResults& getLayout() const {
return layout_;
}
size_t getLineIndex() const {
return lineIndex_;
}
bool isReferenceBaseline() const {
return isReferenceBaseline_;
}
// returns the Node that owns this Node. An owner is used to identify
// the YogaTree that a Node belongs to. This method will return the parent
// of the Node when a Node only belongs to one YogaTree or nullptr when
// the Node is shared between two or more YogaTrees.
Node* getOwner() const {
return owner_;
}
const std::vector<Node*>& getChildren() const {
return children_;
}
Node* getChild(size_t index) const {
return children_.at(index);
}
size_t getChildCount() const {
return children_.size();
}
const Config* getConfig() const {
return config_;
}
bool isDirty() const {
return isDirty_;
}
std::array<Style::Length, 2> getResolvedDimensions() const {
return resolvedDimensions_;
}
Style::Length getResolvedDimension(Dimension dimension) const {
return resolvedDimensions_[static_cast<size_t>(dimension)];
}
// Setters
void setContext(void* context) {
context_ = context;
}
void setAlwaysFormsContainingBlock(bool alwaysFormsContainingBlock) {
alwaysFormsContainingBlock_ = alwaysFormsContainingBlock;
}
void setHasNewLayout(bool hasNewLayout) {
hasNewLayout_ = hasNewLayout;
}
void setNodeType(NodeType nodeType) {
nodeType_ = nodeType;
}
void setMeasureFunc(YGMeasureFunc measureFunc);
void setBaselineFunc(YGBaselineFunc baseLineFunc) {
baselineFunc_ = baseLineFunc;
}
void setDirtiedFunc(YGDirtiedFunc dirtiedFunc) {
dirtiedFunc_ = dirtiedFunc;
}
void setStyle(const Style& style) {
style_ = style;
}
void setLayout(const LayoutResults& layout) {
layout_ = layout;
}
void setLineIndex(size_t lineIndex) {
lineIndex_ = lineIndex;
}
void setIsReferenceBaseline(bool isReferenceBaseline) {
isReferenceBaseline_ = isReferenceBaseline;
}
void setOwner(Node* owner) {
owner_ = owner;
}
void setChildren(const std::vector<Node*>& children) {
children_ = children;
}
// TODO: rvalue override for setChildren
void setConfig(Config* config);
void setDirty(bool isDirty);
void setLayoutLastOwnerDirection(Direction direction);
void setLayoutComputedFlexBasis(FloatOptional computedFlexBasis);
void setLayoutComputedFlexBasisGeneration(
uint32_t computedFlexBasisGeneration);
void setLayoutMeasuredDimension(float measuredDimension, Dimension dimension);
void setLayoutHadOverflow(bool hadOverflow);
void setLayoutDimension(float LengthValue, Dimension dimension);
void setLayoutDirection(Direction direction);
void setLayoutMargin(float margin, PhysicalEdge edge);
void setLayoutBorder(float border, PhysicalEdge edge);
void setLayoutPadding(float padding, PhysicalEdge edge);
void setLayoutPosition(float position, PhysicalEdge edge);
void setPosition(
Direction direction,
float mainSize,
float crossSize,
float ownerWidth);
// Other methods
Style::Length resolveFlexBasisPtr() const;
void resolveDimension();
Direction resolveDirection(Direction ownerDirection);
void clearChildren();
/// Replaces the occurrences of oldChild with newChild
void replaceChild(Node* oldChild, Node* newChild);
void replaceChild(Node* child, size_t index);
void insertChild(Node* child, size_t index);
/// Removes the first occurrence of child
bool removeChild(Node* child);
void removeChild(size_t index);
void cloneChildrenIfNeeded();
void markDirtyAndPropagate();
float resolveFlexGrow() const;
float resolveFlexShrink() const;
bool isNodeFlexible();
void reset();
private:
// Used to allow resetting the node
Node& operator=(Node&&) noexcept = default;
float relativePosition(
FlexDirection axis,
Direction direction,
float axisSize) const;
void useWebDefaults() {
style_.setFlexDirection(FlexDirection::Row);
style_.setAlignContent(Align::Stretch);
}
bool hasNewLayout_ : 1 = true;
bool isReferenceBaseline_ : 1 = false;
bool isDirty_ : 1 = true;
bool alwaysFormsContainingBlock_ : 1 = false;
NodeType nodeType_ : bitCount<NodeType>() = NodeType::Default;
void* context_ = nullptr;
YGMeasureFunc measureFunc_ = nullptr;
YGBaselineFunc baselineFunc_ = nullptr;
YGDirtiedFunc dirtiedFunc_ = nullptr;
Style style_;
LayoutResults layout_;
size_t lineIndex_ = 0;
Node* owner_ = nullptr;
std::vector<Node*> children_;
const Config* config_;
std::array<Style::Length, 2> resolvedDimensions_{
{value::undefined(), value::undefined()}};
};
inline Node* resolveRef(const YGNodeRef ref) {
return static_cast<Node*>(ref);
}
inline const Node* resolveRef(const YGNodeConstRef ref) {
return static_cast<const Node*>(ref);
}
} // namespace facebook::yoga