AK: Avoid overflow in Duration::to_time_units() with fraction near 1

With numerators or denominators approaching NumericLimits<u32>::max(),
we could overflow in the sum of the remainder and the rounding
contribution. Instead, divide them separately and sum them afterward.
This commit is contained in:
Zaggy1024
2026-04-15 18:03:02 -05:00
committed by Gregory Bertilson
parent affbe61da0
commit 3844edaaed
Notes: github-actions[bot] 2026-04-16 20:09:36 +00:00
2 changed files with 12 additions and 4 deletions

View File

@@ -231,11 +231,17 @@ i64 Duration::to_time_units(u32 numerator, u32 denominator) const
auto seconds_product = saturating_mul(m_seconds, static_cast<i64>(denominator));
auto time_units = seconds_product / numerator;
auto remainder = seconds_product % numerator;
auto seconds_remainder_dividend = seconds_product % numerator;
auto remainder_in_nanoseconds = remainder * 1'000'000'000;
auto rounding_half = static_cast<i64>(numerator) * 500'000'000;
time_units = saturating_add(time_units, ((static_cast<i64>(m_nanoseconds) * denominator + remainder_in_nanoseconds + rounding_half) / numerator) / 1'000'000'000);
auto seconds_remainder_nanosecond_dividend = seconds_remainder_dividend * 1'000'000'000;
auto rounding_half_nanosecond_dividend = static_cast<i64>(numerator) * 500'000'000;
auto sub_seconds_nanosecond_dividend = (static_cast<i64>(m_nanoseconds) * denominator) + seconds_remainder_nanosecond_dividend;
auto sub_seconds_nanosecond_units = sub_seconds_nanosecond_dividend / numerator;
auto sub_seconds_units_remainder_nanosecond_dividend = sub_seconds_nanosecond_dividend % numerator;
auto rounding_adjustment_nanosecond_units = (rounding_half_nanosecond_dividend + sub_seconds_units_remainder_nanosecond_dividend) / numerator;
time_units = saturating_add(time_units, (sub_seconds_nanosecond_units + rounding_adjustment_nanosecond_units) / 1'000'000'000);
return time_units;
}

View File

@@ -986,6 +986,8 @@ TEST_CASE(time_units)
EXPECT_EQ(Duration::from_seconds(2'147'483'649).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max());
EXPECT_EQ(Duration::from_seconds(2'147'483'648).to_time_units(1, NumericLimits<u32>::max()), NumericLimits<i64>::max() - (NumericLimits<u32>::max() / 2));
EXPECT_EQ((Duration::from_seconds(1) + Duration::from_nanoseconds(999'999'999)).to_time_units(NumericLimits<u32>::max(), NumericLimits<u32>::max() - 1), 2);
EXPECT_DEATH("From time units with zero numerator", (void)Duration::from_time_units(1, 0, 1));
EXPECT_DEATH("From time units with zero denominator", (void)Duration::from_time_units(1, 1, 0));
}