LibJS: Impose limits on a valid duration

This is a normative change in the Temporal proposal. See:
https://github.com/tc39/proposal-temporal/commit/1104cad
https://github.com/tc39/proposal-temporal/commit/45462c4

Although our Temporal implementation is wildly out of date, this AO and
the changes to it are relied on in Intl.DurationFormat.
This commit is contained in:
Timothy Flynn 2024-08-13 12:36:06 -04:00 committed by Andreas Kling
parent 78328ab83c
commit 300f8d3dbb
Notes: github-actions[bot] 2024-08-14 09:49:11 +00:00
2 changed files with 61 additions and 0 deletions

View file

@ -6,6 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Math.h>
#include <AK/StringBuilder.h>
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/AbstractOperations.h>
@ -256,6 +257,44 @@ bool is_valid_duration(double years, double months, double weeks, double days, d
return false;
}
// 3. If abs(years) ≥ 2**32, return false.
if (AK::fabs(years) > NumericLimits<u32>::max())
return false;
// 4. If abs(months) ≥ 2**32, return false.
if (AK::fabs(months) > NumericLimits<u32>::max())
return false;
// 5. If abs(weeks) ≥ 2**32, return false.
if (AK::fabs(weeks) > NumericLimits<u32>::max())
return false;
// 6. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds + (𝔽(milliseconds)) × 10**-3 + (𝔽(microseconds)) × 10**-6 + (𝔽(nanoseconds)) × 10**-9.
// 7. NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10**-3, 10**-6, and 10**-9 respectively may be imprecise when milliseconds, microseconds, or nanoseconds is an unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo() with sufficient bits in the quotient. String manipulation will also give an exact result, since the multiplication is by a power of 10.
static Crypto::SignedBigInteger days_to_nanoseconds { 8.64e13 };
static Crypto::SignedBigInteger hours_to_nanoseconds { 3.6e12 };
static Crypto::SignedBigInteger minutes_to_nanoseconds { 6e10 };
static Crypto::SignedBigInteger seconds_to_nanoseconds { 1e9 };
static Crypto::SignedBigInteger milliseconds_to_nanoseconds { 1e6 };
static Crypto::SignedBigInteger microseconds_to_nanoseconds { 1e3 };
auto normalized_nanoseconds = Crypto::SignedBigInteger { days }.multiplied_by(days_to_nanoseconds);
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { hours }.multiplied_by(hours_to_nanoseconds));
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { minutes }.multiplied_by(minutes_to_nanoseconds));
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { seconds }.multiplied_by(seconds_to_nanoseconds));
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { milliseconds }.multiplied_by(milliseconds_to_nanoseconds));
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { microseconds }.multiplied_by(microseconds_to_nanoseconds));
normalized_nanoseconds = normalized_nanoseconds.plus(Crypto::SignedBigInteger { nanoseconds });
// 8. If abs(normalizedSeconds) ≥ 2**53, return false.
static auto maximum_time = Crypto::SignedBigInteger { MAX_ARRAY_LIKE_INDEX }.plus(Crypto::SignedBigInteger { 1 }).multiplied_by(seconds_to_nanoseconds);
if (normalized_nanoseconds.is_negative())
normalized_nanoseconds.negate();
if (normalized_nanoseconds >= maximum_time)
return false;
// 3. Return true.
return true;
}

View file

@ -108,4 +108,26 @@ describe("errors", () => {
);
}
});
test("invalid duration string: exceed duration limits", () => {
const values = [
"P4294967296Y", // abs(years) >= 2**32
"P4294967296M", // abs(months) >= 2**32
"P4294967296W", // abs(weeks) >= 2**32
"P104249991375D", // days >= 2*53 seconds
"PT2501999792984H", // hours >= 2*53 seconds
"PT150119987579017M", // minutes >= 2*53 seconds
"PT9007199254740992S", // seconds >= 2*53 seconds
];
for (const value of values) {
expect(() => {
Temporal.Duration.from(value);
}).toThrowWithMessage(RangeError, `Invalid duration`);
expect(() => {
Temporal.Duration.from("-" + value);
}).toThrowWithMessage(RangeError, `Invalid duration`);
}
});
});