From b65866c360676360c1f5dc084d2279e74ee47db2 Mon Sep 17 00:00:00 2001 From: harsha-cpp Date: Tue, 9 Jun 2026 16:51:17 +0530 Subject: [PATCH] guard shrink-factor division against floating-point near-zero --- tests/YGFlexShrinkBorderBugTest.cpp | 48 +++++++++++++++++++++++++++++ yoga/algorithm/CalculateLayout.cpp | 9 +++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/YGFlexShrinkBorderBugTest.cpp diff --git a/tests/YGFlexShrinkBorderBugTest.cpp b/tests/YGFlexShrinkBorderBugTest.cpp new file mode 100644 index 0000000000..26c5ce1990 --- /dev/null +++ b/tests/YGFlexShrinkBorderBugTest.cpp @@ -0,0 +1,48 @@ +/* + * 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. + */ + +// Regression test for https://github.com/facebook/yoga/issues/1665 +// flexBasis:0 + flexShrink:1 + borderWidth + minWidth produces an astronomically +// large width (~1.65e11) instead of being clamped to minWidth. + +#include +#include + +TEST(YGFlexShrinkBorderBug, flex_basis_0_border_minwidth_row) { + YGConfigRef config = YGConfigNew(); + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetWidth(root, 393.0f); + YGNodeStyleSetHeight(root, 100.0f); + + // Four row children: flexBasis:0, flexGrow:1, flexShrink:1, minWidth:160 + // First child has borderWidth:0.594443, rest 0.594442. + // Bug: first child (and all others) get width ~1.65e11 instead of 160. + float borders[] = {0.594443f, 0.594442f, 0.594442f, 0.594442f}; + for (int i = 0; i < 4; i++) { + YGNodeRef child = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexBasis(child, 0.0f); + YGNodeStyleSetFlexGrow(child, 1.0f); + YGNodeStyleSetFlexShrink(child, 1.0f); + YGNodeStyleSetMinWidth(child, 160.0f); + YGNodeStyleSetBorder(child, YGEdgeAll, borders[i]); + YGNodeInsertChild(root, child, i); + } + + YGNodeCalculateLayout(root, 393.0f, 100.0f, YGDirectionLTR); + + for (int i = 0; i < 4; i++) { + YGNodeRef child = YGNodeGetChild(root, i); + EXPECT_GE(YGNodeLayoutGetWidth(child), 160.0f) + << "child[" << i << "] width below minWidth"; + EXPECT_LE(YGNodeLayoutGetWidth(child), 200.0f) + << "child[" << i << "] width is astronomically large (bug)"; + } + + YGNodeFreeRecursive(root); + YGConfigFree(config); +} diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index f48f261e31..92ef62e657 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -925,8 +925,15 @@ static float distributeFreeSpaceSecondPass( if (flexShrinkScaledFactor != 0) { float childSize = YGUndefined; + // Use a relative epsilon guard instead of exact equality: after the + // first pass removes all constrained items, totalFlexShrinkScaledFactors + // may be near-zero rather than exactly 0 due to floating-point + // cancellation. Dividing by a near-zero value produces a gigantic + // childSize that overwhelms the min/max clamp. + const float shrinkFactorMagnitude = + std::abs(flexLine.layout.totalFlexShrinkScaledFactors); if (yoga::isDefined(flexLine.layout.totalFlexShrinkScaledFactors) && - flexLine.layout.totalFlexShrinkScaledFactors == 0) { + shrinkFactorMagnitude < 1e-6f) { childSize = childFlexBasis + flexShrinkScaledFactor; } else { childSize = childFlexBasis +