Summary: X-link: https://github.com/facebook/react-native/pull/37243 X-link: https://github.com/facebook/litho/pull/944 Pull Request resolved: https://github.com/facebook/yoga/pull/1279 Java bindings for Yoga rely solely on garbage collection for memory management. Each Java `YogaNode` has references to its children and parent Java Nodes. This means, for a node to be garbage collected, it cannot be reachable from any user accessible node. Each node then has single ownership of a `YGNodeRef`. When the `YogaNode` is garbage collected, a finalizer is run to call `YGNodeFree` and free the underlying native Yoga Node. This may cause a use-after-free if finalizers are run from multiple threads. This is because `YGNodeFree` does more than just freeing, but instead also interacts with its parent and children nodes to detach itself, and remove any dangling pointers. If multiple threads run finalizers at once, one may traverse and try to mutate a node which another is freeing. Because we know the entire connected tree is dead, there is no need to remove dangling pointers, so I want to expose a way to just free a Yoga Node, without it mutating the tree as a side effect. This adds a currently private `YGNodeDeallocate` that frees without traversal. Ideally from naming this is what `YGNodeFree` would do, but we think changing the behavior of that might be too disruptive to OSS. At the same time there may be other memory safety related API changes we would like to eventually make, so this isn't made public beyond the JNI bindings to prevent needing to transition more APIs. Changelog: [Internal] Reviewed By: rshest Differential Revision: D45556206 fbshipit-source-id: 62a1394c6f6bdc2b437b388098ea362a0fbcd0f7
161 lines
3.9 KiB
C++
161 lines
3.9 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 <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
#include <yoga/Yoga.h>
|
|
|
|
#include "CompactValue.h"
|
|
|
|
using YGVector = std::vector<YGNodeRef>;
|
|
|
|
YG_EXTERN_C_BEGIN
|
|
|
|
void YGNodeCalculateLayoutWithContext(
|
|
YGNodeRef node,
|
|
float availableWidth,
|
|
float availableHeight,
|
|
YGDirection ownerDirection,
|
|
void* layoutContext);
|
|
|
|
// Deallocates a Yoga Node. Unlike YGNodeFree, does not remove the node from
|
|
// its parent or children.
|
|
void YGNodeDeallocate(YGNodeRef node);
|
|
|
|
YG_EXTERN_C_END
|
|
|
|
namespace facebook {
|
|
namespace yoga {
|
|
|
|
inline bool isUndefined(float value) {
|
|
return std::isnan(value);
|
|
}
|
|
|
|
inline bool isUndefined(double value) {
|
|
return std::isnan(value);
|
|
}
|
|
|
|
void throwLogicalErrorWithMessage(const char* message);
|
|
|
|
} // namespace yoga
|
|
} // namespace facebook
|
|
|
|
extern const std::array<YGEdge, 4> trailing;
|
|
extern const std::array<YGEdge, 4> leading;
|
|
extern const YGValue YGValueUndefined;
|
|
extern const YGValue YGValueAuto;
|
|
extern const YGValue YGValueZero;
|
|
|
|
struct YGCachedMeasurement {
|
|
float availableWidth;
|
|
float availableHeight;
|
|
YGMeasureMode widthMeasureMode;
|
|
YGMeasureMode heightMeasureMode;
|
|
|
|
float computedWidth;
|
|
float computedHeight;
|
|
|
|
YGCachedMeasurement()
|
|
: availableWidth(-1),
|
|
availableHeight(-1),
|
|
widthMeasureMode(YGMeasureModeUndefined),
|
|
heightMeasureMode(YGMeasureModeUndefined),
|
|
computedWidth(-1),
|
|
computedHeight(-1) {}
|
|
|
|
bool operator==(YGCachedMeasurement measurement) const {
|
|
using namespace facebook;
|
|
|
|
bool isEqual = widthMeasureMode == measurement.widthMeasureMode &&
|
|
heightMeasureMode == measurement.heightMeasureMode;
|
|
|
|
if (!yoga::isUndefined(availableWidth) ||
|
|
!yoga::isUndefined(measurement.availableWidth)) {
|
|
isEqual = isEqual && availableWidth == measurement.availableWidth;
|
|
}
|
|
if (!yoga::isUndefined(availableHeight) ||
|
|
!yoga::isUndefined(measurement.availableHeight)) {
|
|
isEqual = isEqual && availableHeight == measurement.availableHeight;
|
|
}
|
|
if (!yoga::isUndefined(computedWidth) ||
|
|
!yoga::isUndefined(measurement.computedWidth)) {
|
|
isEqual = isEqual && computedWidth == measurement.computedWidth;
|
|
}
|
|
if (!yoga::isUndefined(computedHeight) ||
|
|
!yoga::isUndefined(measurement.computedHeight)) {
|
|
isEqual = isEqual && computedHeight == measurement.computedHeight;
|
|
}
|
|
|
|
return isEqual;
|
|
}
|
|
};
|
|
|
|
// This value was chosen based on empirical data:
|
|
// 98% of analyzed layouts require less than 8 entries.
|
|
#define YG_MAX_CACHED_RESULT_COUNT 8
|
|
|
|
namespace facebook {
|
|
namespace yoga {
|
|
namespace detail {
|
|
|
|
template <size_t Size>
|
|
class Values {
|
|
private:
|
|
std::array<CompactValue, Size> values_;
|
|
|
|
public:
|
|
Values() = default;
|
|
Values(const Values& other) = default;
|
|
|
|
explicit Values(const YGValue& defaultValue) noexcept {
|
|
values_.fill(defaultValue);
|
|
}
|
|
|
|
const CompactValue& operator[](size_t i) const noexcept { return values_[i]; }
|
|
CompactValue& operator[](size_t i) noexcept { return values_[i]; }
|
|
|
|
template <size_t I>
|
|
YGValue get() const noexcept {
|
|
return std::get<I>(values_);
|
|
}
|
|
|
|
template <size_t I>
|
|
void set(YGValue& value) noexcept {
|
|
std::get<I>(values_) = value;
|
|
}
|
|
|
|
template <size_t I>
|
|
void set(YGValue&& value) noexcept {
|
|
set<I>(value);
|
|
}
|
|
|
|
bool operator==(const Values& other) const noexcept {
|
|
for (size_t i = 0; i < Size; ++i) {
|
|
if (values_[i] != other.values_[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Values& operator=(const Values& other) = default;
|
|
};
|
|
} // namespace detail
|
|
} // namespace yoga
|
|
} // namespace facebook
|
|
|
|
static const float kDefaultFlexGrow = 0.0f;
|
|
static const float kDefaultFlexShrink = 0.0f;
|
|
static const float kWebDefaultFlexShrink = 1.0f;
|
|
|
|
extern bool YGFloatsEqual(const float a, const float b);
|