mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
Bug 1874684 - Part 30: Make BalanceTimeDuration a fallible operation. r=mgaudet
BalanceTimeDuration is fallible, cf. <https://github.com/tc39/proposal-temporal/issues/2785>. Depends on D204393 Differential Revision: https://phabricator.services.mozilla.com/D204394
This commit is contained in:
parent
409917e576
commit
4f70a7669b
@ -1291,33 +1291,49 @@ static NormalizedTimeAndDays NormalizedTimeDurationToDays(
|
||||
static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
|
||||
int64_t minutes, int64_t seconds,
|
||||
int64_t milliseconds,
|
||||
double microseconds,
|
||||
double nanoseconds) {
|
||||
int64_t microseconds,
|
||||
int64_t nanoseconds) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(IsValidDuration(
|
||||
{0, 0, 0, double(days), double(hours), double(minutes), double(seconds),
|
||||
double(milliseconds), microseconds, nanoseconds}));
|
||||
double(milliseconds), double(microseconds), double(nanoseconds)}));
|
||||
|
||||
// |days|, |hours|, |minutes|, and |seconds| are safe integers, so we don't
|
||||
// need to convert to `double` and back for the `ℝ(𝔽(x))` conversion.
|
||||
// All values are safe integers, so we don't need to convert to `double` and
|
||||
// back for the `ℝ(𝔽(x))` conversion.
|
||||
MOZ_ASSERT(IsSafeInteger(days));
|
||||
MOZ_ASSERT(IsSafeInteger(hours));
|
||||
MOZ_ASSERT(IsSafeInteger(minutes));
|
||||
MOZ_ASSERT(IsSafeInteger(seconds));
|
||||
|
||||
// |milliseconds| is explicitly casted to double by consumers, so we can also
|
||||
// omit the `ℝ(𝔽(x))` conversion.
|
||||
MOZ_ASSERT(IsSafeInteger(milliseconds));
|
||||
MOZ_ASSERT(IsSafeInteger(microseconds));
|
||||
MOZ_ASSERT(IsSafeInteger(nanoseconds));
|
||||
|
||||
// Step 2.
|
||||
// NB: Adds +0.0 to correctly handle negative zero.
|
||||
return {
|
||||
days,
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
milliseconds,
|
||||
microseconds + (+0.0),
|
||||
nanoseconds + (+0.0),
|
||||
double(microseconds),
|
||||
double(nanoseconds),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
|
||||
* microseconds, nanoseconds )
|
||||
*/
|
||||
static TimeDuration CreateTimeDurationRecord(int64_t milliseconds,
|
||||
const Int128& microseconds,
|
||||
const Int128& nanoseconds) {
|
||||
// Step 1.
|
||||
MOZ_ASSERT(IsValidDuration({0, 0, 0, 0, 0, 0, 0, double(milliseconds),
|
||||
double(microseconds), double(nanoseconds)}));
|
||||
|
||||
// Step 2.
|
||||
return {
|
||||
0, 0, 0, 0, milliseconds, double(microseconds), double(nanoseconds),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1327,6 +1343,8 @@ static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
|
||||
TimeDuration js::temporal::BalanceTimeDuration(
|
||||
const NormalizedTimeDuration& duration, TemporalUnit largestUnit) {
|
||||
MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
|
||||
MOZ_ASSERT(largestUnit <= TemporalUnit::Second,
|
||||
"fallible fractional seconds units");
|
||||
|
||||
auto [seconds, nanoseconds] = duration;
|
||||
|
||||
@ -1473,118 +1491,180 @@ TimeDuration js::temporal::BalanceTimeDuration(
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 8.
|
||||
case TemporalUnit::Millisecond: {
|
||||
static_assert((NormalizedTimeDuration::max().seconds + 1) *
|
||||
ToMilliseconds(TemporalUnit::Second) <=
|
||||
INT64_MAX,
|
||||
"total number duration milliseconds fits into int64");
|
||||
|
||||
int64_t millis = seconds * ToMilliseconds(TemporalUnit::Second);
|
||||
|
||||
// Set to zero per step 1.
|
||||
seconds = 0;
|
||||
|
||||
// Step 8.a.
|
||||
microseconds = nanoseconds / 1000;
|
||||
|
||||
// Step 8.b.
|
||||
nanoseconds = nanoseconds % 1000;
|
||||
|
||||
// Step 8.c.
|
||||
milliseconds = microseconds / 1000;
|
||||
|
||||
// Step 8.d.
|
||||
microseconds = microseconds % 1000;
|
||||
|
||||
MOZ_ASSERT(std::abs(milliseconds) <= 999);
|
||||
milliseconds += millis;
|
||||
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit =
|
||||
(int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
|
||||
constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
|
||||
|
||||
static_assert(
|
||||
int64_t(double(max)) < limit && int64_t(double(max + 1)) >= limit,
|
||||
"max is the maximum allowed milliseconds value");
|
||||
|
||||
auto totalMillis =
|
||||
(seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
|
||||
if (totalMillis > max) {
|
||||
// FIXME: spec bug - handle too large duration values
|
||||
// https://github.com/tc39/proposal-temporal/issues/2785
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
case TemporalUnit::Microsecond: {
|
||||
// Step 9.a.
|
||||
int64_t microseconds = nanoseconds / 1000;
|
||||
|
||||
// Step 9.b.
|
||||
nanoseconds = nanoseconds % 1000;
|
||||
|
||||
MOZ_ASSERT(std::abs(microseconds) <= 999'999);
|
||||
double micros =
|
||||
std::fma(double(seconds), ToMicroseconds(TemporalUnit::Second),
|
||||
double(microseconds));
|
||||
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit = Int128{int64_t(1) << 53} *
|
||||
Int128{ToMicroseconds(TemporalUnit::Second)};
|
||||
constexpr auto max =
|
||||
(Int128{0x1e8} << 64) + Int128{0x47ff'ffff'fff7'ffff};
|
||||
static_assert(max < limit);
|
||||
|
||||
auto totalMicros =
|
||||
(Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
|
||||
Int128{microseconds};
|
||||
if (totalMicros > max) {
|
||||
// FIXME: spec bug - handle too large duration values
|
||||
// https://github.com/tc39/proposal-temporal/issues/2785
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
return CreateTimeDurationRecord(0, 0, 0, 0, 0, micros,
|
||||
double(nanoseconds));
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
case TemporalUnit::Nanosecond: {
|
||||
MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
|
||||
double nanos =
|
||||
std::fma(double(seconds), ToNanoseconds(TemporalUnit::Second),
|
||||
double(nanoseconds));
|
||||
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit = Int128{int64_t(1) << 53} *
|
||||
Int128{ToNanoseconds(TemporalUnit::Second)};
|
||||
constexpr auto max =
|
||||
(Int128{0x77359} << 64) + Int128{0x3fff'ffff'dfff'ffff};
|
||||
static_assert(max < limit);
|
||||
|
||||
auto totalNanos =
|
||||
(Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
|
||||
Int128{nanoseconds};
|
||||
if (totalNanos > max) {
|
||||
// FIXME: spec bug - handle too large duration values
|
||||
// https://github.com/tc39/proposal-temporal/issues/2785
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
return CreateTimeDurationRecord(0, 0, 0, 0, 0, 0, nanos);
|
||||
}
|
||||
|
||||
case TemporalUnit::Millisecond:
|
||||
case TemporalUnit::Microsecond:
|
||||
case TemporalUnit::Nanosecond:
|
||||
case TemporalUnit::Auto:
|
||||
MOZ_CRASH("Unexpected temporal unit");
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
|
||||
double(microseconds), double(nanoseconds));
|
||||
microseconds, nanoseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* BalanceTimeDuration ( norm, largestUnit )
|
||||
*/
|
||||
bool js::temporal::BalanceTimeDuration(JSContext* cx,
|
||||
const NormalizedTimeDuration& duration,
|
||||
TemporalUnit largestUnit,
|
||||
TimeDuration* result) {
|
||||
MOZ_ASSERT(IsValidNormalizedTimeDuration(duration));
|
||||
|
||||
auto [seconds, nanoseconds] = duration;
|
||||
|
||||
// Negative nanoseconds are represented as the difference to 1'000'000'000.
|
||||
// Convert these back to their absolute value and adjust the seconds part
|
||||
// accordingly.
|
||||
//
|
||||
// For example the nanoseconds duration |-1n| is represented as the
|
||||
// duration {seconds: -1, nanoseconds: 999'999'999}.
|
||||
if (seconds < 0 && nanoseconds > 0) {
|
||||
seconds += 1;
|
||||
nanoseconds -= ToNanoseconds(TemporalUnit::Second);
|
||||
}
|
||||
|
||||
// Steps 1-3. (Not applicable in our implementation.)
|
||||
//
|
||||
// We don't need to convert to positive numbers, because integer division
|
||||
// truncates and the %-operator has modulo semantics.
|
||||
|
||||
// Steps 4-10.
|
||||
switch (largestUnit) {
|
||||
// Steps 4-7.
|
||||
case TemporalUnit::Year:
|
||||
case TemporalUnit::Month:
|
||||
case TemporalUnit::Week:
|
||||
case TemporalUnit::Day:
|
||||
case TemporalUnit::Hour:
|
||||
case TemporalUnit::Minute:
|
||||
case TemporalUnit::Second:
|
||||
*result = BalanceTimeDuration(duration, largestUnit);
|
||||
return true;
|
||||
|
||||
// Step 8.
|
||||
case TemporalUnit::Millisecond: {
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit =
|
||||
(int64_t(1) << 53) * ToMilliseconds(TemporalUnit::Second);
|
||||
|
||||
// The largest possible milliseconds value whose double representation
|
||||
// doesn't exceed the normalized seconds limit.
|
||||
constexpr auto max = int64_t(0x7cff'ffff'ffff'fdff);
|
||||
|
||||
// Assert |max| is the maximum allowed milliseconds value.
|
||||
static_assert(double(max) < double(limit));
|
||||
static_assert(double(max + 1) >= double(limit));
|
||||
|
||||
static_assert((NormalizedTimeDuration::max().seconds + 1) *
|
||||
ToMilliseconds(TemporalUnit::Second) <=
|
||||
INT64_MAX,
|
||||
"total number duration milliseconds fits into int64");
|
||||
|
||||
// Step 8.a.
|
||||
int64_t microseconds = nanoseconds / 1000;
|
||||
|
||||
// Step 8.b.
|
||||
nanoseconds = nanoseconds % 1000;
|
||||
|
||||
// Step 8.c.
|
||||
int64_t milliseconds = microseconds / 1000;
|
||||
MOZ_ASSERT(std::abs(milliseconds) <= 999);
|
||||
|
||||
// Step 8.d.
|
||||
microseconds = microseconds % 1000;
|
||||
|
||||
auto millis =
|
||||
(seconds * ToMilliseconds(TemporalUnit::Second)) + milliseconds;
|
||||
if (std::abs(millis) > max) {
|
||||
JS_ReportErrorNumberASCII(
|
||||
cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
*result = CreateTimeDurationRecord(millis, Int128{microseconds},
|
||||
Int128{nanoseconds});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
case TemporalUnit::Microsecond: {
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit = Uint128{int64_t(1) << 53} *
|
||||
Uint128{ToMicroseconds(TemporalUnit::Second)};
|
||||
|
||||
// The largest possible microseconds value whose double representation
|
||||
// doesn't exceed the normalized seconds limit.
|
||||
constexpr auto max =
|
||||
(Uint128{0x1e8} << 64) + Uint128{0x47ff'ffff'fff7'ffff};
|
||||
static_assert(max < limit);
|
||||
|
||||
// Assert |max| is the maximum allowed microseconds value.
|
||||
MOZ_ASSERT(double(max) < double(limit));
|
||||
MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
|
||||
|
||||
// Step 9.a.
|
||||
int64_t microseconds = nanoseconds / 1000;
|
||||
MOZ_ASSERT(std::abs(microseconds) <= 999'999);
|
||||
|
||||
// Step 9.b.
|
||||
nanoseconds = nanoseconds % 1000;
|
||||
|
||||
auto micros =
|
||||
(Int128{seconds} * Int128{ToMicroseconds(TemporalUnit::Second)}) +
|
||||
Int128{microseconds};
|
||||
if (micros.abs() > max) {
|
||||
JS_ReportErrorNumberASCII(
|
||||
cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
*result = CreateTimeDurationRecord(0, micros, Int128{nanoseconds});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
case TemporalUnit::Nanosecond: {
|
||||
// The number of normalized seconds must not exceed `2**53 - 1`.
|
||||
constexpr auto limit = Uint128{int64_t(1) << 53} *
|
||||
Uint128{ToNanoseconds(TemporalUnit::Second)};
|
||||
|
||||
// The largest possible nanoseconds value whose double representation
|
||||
// doesn't exceed the normalized seconds limit.
|
||||
constexpr auto max =
|
||||
(Uint128{0x77359} << 64) + Uint128{0x3fff'ffff'dfff'ffff};
|
||||
static_assert(max < limit);
|
||||
|
||||
// Assert |max| is the maximum allowed nanoseconds value.
|
||||
MOZ_ASSERT(double(max) < double(limit));
|
||||
MOZ_ASSERT(double(max + Uint128{1}) >= double(limit));
|
||||
|
||||
MOZ_ASSERT(std::abs(nanoseconds) <= 999'999'999);
|
||||
|
||||
auto nanos =
|
||||
(Int128{seconds} * Int128{ToNanoseconds(TemporalUnit::Second)}) +
|
||||
Int128{nanoseconds};
|
||||
if (nanos.abs() > max) {
|
||||
JS_ReportErrorNumberASCII(
|
||||
cx, GetErrorMessage, nullptr,
|
||||
JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
*result = CreateTimeDurationRecord(0, Int128{}, nanos);
|
||||
return true;
|
||||
}
|
||||
|
||||
case TemporalUnit::Auto:
|
||||
break;
|
||||
}
|
||||
MOZ_CRASH("Unexpected temporal unit");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1678,7 +1758,10 @@ static bool BalanceTimeDurationRelative(
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
auto balanceResult = BalanceTimeDuration(normalized, largestUnit);
|
||||
TimeDuration balanceResult;
|
||||
if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanceResult)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 11.
|
||||
*result = {
|
||||
@ -2098,7 +2181,10 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
|
||||
}
|
||||
|
||||
// Step 6.f.
|
||||
auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
|
||||
TimeDuration balanced;
|
||||
if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 6.g.
|
||||
*result = balanced.toDuration();
|
||||
@ -2182,7 +2268,10 @@ static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
|
||||
}
|
||||
|
||||
// Step 7.n.
|
||||
auto balanced = temporal::BalanceTimeDuration(normalized, largestUnit);
|
||||
TimeDuration balanced;
|
||||
if (!temporal::BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Steps 7.o.
|
||||
*result = {
|
||||
@ -2255,7 +2344,10 @@ static bool AddDuration(
|
||||
endNs, zonedRelativeTo.instant());
|
||||
|
||||
// Step 17.b.
|
||||
auto balanced = BalanceTimeDuration(normalized, largestUnit);
|
||||
TimeDuration balanced;
|
||||
if (!BalanceTimeDuration(cx, normalized, largestUnit, &balanced)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 17.c.
|
||||
*result = balanced.toDuration();
|
||||
@ -5194,7 +5286,10 @@ static bool Duration_round(JSContext* cx, const CallArgs& args) {
|
||||
}
|
||||
|
||||
// Step 41.b.
|
||||
balanceResult = temporal::BalanceTimeDuration(withDays, largestUnit);
|
||||
if (!temporal::BalanceTimeDuration(cx, withDays, largestUnit,
|
||||
&balanceResult)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 41.
|
||||
|
@ -274,6 +274,12 @@ bool ToTemporalDurationRecord(JSContext* cx,
|
||||
TimeDuration BalanceTimeDuration(const NormalizedTimeDuration& duration,
|
||||
TemporalUnit largestUnit);
|
||||
|
||||
/**
|
||||
* BalanceTimeDuration ( norm, largestUnit )
|
||||
*/
|
||||
bool BalanceTimeDuration(JSContext* cx, const NormalizedTimeDuration& duration,
|
||||
TemporalUnit largestUnit, TimeDuration* result);
|
||||
|
||||
/**
|
||||
* BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
|
||||
* smallestUnit, plainRelativeTo, calendarRec )
|
||||
|
@ -695,7 +695,10 @@ static bool DifferenceTemporalInstant(JSContext* cx,
|
||||
settings.smallestUnit, settings.roundingMode);
|
||||
|
||||
// Step 6.
|
||||
auto balanced = BalanceTimeDuration(difference, settings.largestUnit);
|
||||
TimeDuration balanced;
|
||||
if (!BalanceTimeDuration(cx, difference, settings.largestUnit, &balanced)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
auto duration = balanced.toDuration();
|
||||
|
@ -1056,7 +1056,10 @@ static bool DifferenceTemporalPlainDateTime(JSContext* cx,
|
||||
}
|
||||
|
||||
// Step 12.e.
|
||||
balancedTime = BalanceTimeDuration(withDays, settings.largestUnit);
|
||||
if (!BalanceTimeDuration(cx, withDays, settings.largestUnit,
|
||||
&balancedTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 12.f.
|
||||
auto toBalance = DateDuration{
|
||||
@ -1079,7 +1082,10 @@ static bool DifferenceTemporalPlainDateTime(JSContext* cx,
|
||||
}
|
||||
|
||||
// Step 13.b.
|
||||
balancedTime = BalanceTimeDuration(withDays, settings.largestUnit);
|
||||
if (!BalanceTimeDuration(cx, withDays, settings.largestUnit,
|
||||
&balancedTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 13.c.
|
||||
balancedDate = {
|
||||
|
@ -992,7 +992,10 @@ static bool DifferenceTemporalPlainTime(JSContext* cx,
|
||||
}
|
||||
|
||||
// Step 7.
|
||||
auto balancedDuration = BalanceTimeDuration(diff, settings.largestUnit);
|
||||
TimeDuration balancedDuration;
|
||||
if (!BalanceTimeDuration(cx, diff, settings.largestUnit, &balancedDuration)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 8.
|
||||
auto duration = balancedDuration.toDuration();
|
||||
|
@ -1420,7 +1420,11 @@ static bool DifferenceTemporalZonedDateTime(JSContext* cx,
|
||||
settings.smallestUnit, settings.roundingMode);
|
||||
|
||||
// Step 6.b.
|
||||
auto balancedTime = BalanceTimeDuration(difference, settings.largestUnit);
|
||||
TimeDuration balancedTime;
|
||||
if (!BalanceTimeDuration(cx, difference, settings.largestUnit,
|
||||
&balancedTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6.c.
|
||||
auto duration = balancedTime.toDuration();
|
||||
|
Loading…
Reference in New Issue
Block a user