Skip to content

Commit 80a09a0

Browse files
committed
Normative: Prevent indefinite loops in NormalizedTimeDurationToDays
It's possible to make at least the second loop continue indefinitely with a contrived calendar and time zone. TODO: Still to be determined if this precludes any non-contrived use cases. If so, we will keep the loops, but still put an upper limit on the number of iterations.
1 parent 45b5e1f commit 80a09a0

File tree

2 files changed

+42
-34
lines changed

2 files changed

+42
-34
lines changed

polyfill/lib/ecmascript.mjs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,20 +3260,34 @@ export function NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec,
32603260
// back inside the period where it belongs. Note that this case only can
32613261
// happen for positive durations because the only direction that
32623262
// `disambiguation: 'compatible'` can change clock time is forwards.
3263-
if (sign === 1) {
3264-
while (days > 0 && relativeResult.epochNs.greater(endNs)) {
3265-
days--;
3266-
relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZoneRec, calendar, days);
3267-
// may do disambiguation
3263+
if (sign === 1 && days > 0 && relativeResult.epochNs.greater(endNs)) {
3264+
days--;
3265+
relativeResult = AddDaysToZonedDateTime(start, dtStart, timeZoneRec, calendar, days);
3266+
// may do disambiguation
3267+
if (days > 0 && relativeResult.epochNs.greater(endNs)) {
3268+
throw new RangeError('inconsistent result from custom time zone getInstantFor()');
32683269
}
32693270
}
32703271
norm = TimeDuration.fromEpochNsDiff(endNs, relativeResult.epochNs);
32713272

3272-
let isOverflow = false;
3273-
let dayLengthNs;
3274-
do {
3275-
// calculate length of the next day (day that contains the time remainder)
3276-
const oneDayFarther = AddDaysToZonedDateTime(
3273+
// calculate length of the next day (day that contains the time remainder)
3274+
let oneDayFarther = AddDaysToZonedDateTime(
3275+
relativeResult.instant,
3276+
relativeResult.dateTime,
3277+
timeZoneRec,
3278+
calendar,
3279+
sign
3280+
);
3281+
let dayLengthNs = TimeDuration.fromEpochNsDiff(oneDayFarther.epochNs, relativeResult.epochNs);
3282+
const oneDayLess = norm.subtract(dayLengthNs);
3283+
let isOverflow = oneDayLess.sign() * sign >= 0;
3284+
if (isOverflow) {
3285+
norm = oneDayLess;
3286+
relativeResult = oneDayFarther;
3287+
days += sign;
3288+
3289+
// ensure there was no more overflow
3290+
oneDayFarther = AddDaysToZonedDateTime(
32773291
relativeResult.instant,
32783292
relativeResult.dateTime,
32793293
timeZoneRec,
@@ -3282,14 +3296,9 @@ export function NormalizedTimeDurationToDays(norm, zonedRelativeTo, timeZoneRec,
32823296
);
32833297

32843298
dayLengthNs = TimeDuration.fromEpochNsDiff(oneDayFarther.epochNs, relativeResult.epochNs);
3285-
const oneDayLess = norm.subtract(dayLengthNs);
3286-
isOverflow = oneDayLess.sign() * sign >= 0;
3287-
if (isOverflow) {
3288-
norm = oneDayLess;
3289-
relativeResult = oneDayFarther;
3290-
days += sign;
3291-
}
3292-
} while (isOverflow);
3299+
isOverflow = norm.subtract(dayLengthNs).sign() * sign >= 0;
3300+
if (isOverflow) throw new RangeError('inconsistent result from custom time zone getPossibleInstantsFor()');
3301+
}
32933302
if (days !== 0 && MathSign(days) != sign) {
32943303
throw new RangeError('Time zone or calendar converted nanoseconds into a number of days with the opposite sign');
32953304
}

spec/zoneddatetime.html

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,23 +1444,22 @@ <h1>
14441444
1. Else if _days_ &lt; 0 and _timeSign_ &lt; 0, then
14451445
1. Set _days_ to _days_ + 1.
14461446
1. Let _relativeResult_ be ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_).
1447-
1. If _sign_ is 1, then
1448-
1. Repeat, while _days_ &gt; 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) &gt; _endNs_,
1449-
1. Set _days_ to _days_ - 1.
1450-
1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_).
1447+
1. If _sign_ = 1, and _days_ &gt; 0, and ℝ(_relativeResult_.[[EpochNanoseconds]]) &gt; _endNs_, then
1448+
1. Set _days_ to _days_ - 1.
1449+
1. Set _relativeResult_ to ? AddDaysToZonedDateTime(_startInstant_, _startDateTime_, _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _days_).
1450+
1. If _days_ &gt; 0 and ℝ(_relativeResult_.[[EpochNanoseconds]]) &gt; _endNs_, throw a *RangeError* exception.
14511451
1. Set _norm_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_endNs_, _relativeResult_.[[EpochNanoseconds]]).
1452-
1. Let _done_ be *false*.
1453-
1. Let _dayLengthNs_ be ~unset~.
1454-
1. Repeat, while _done_ is *false*,
1455-
1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
1456-
1. Set _dayLengthNs_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther_.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]).
1457-
1. Let _oneDayLess_ be ! SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_).
1458-
1. If NormalizedTimeDurationSign(_oneDayLess_) &times; _sign_ &ge; 0, then
1459-
1. Set _norm_ to _oneDayLess_.
1460-
1. Set _relativeResult_ to _oneDayFarther_.
1461-
1. Set _days_ to _days_ + _sign_.
1462-
1. Else,
1463-
1. Set _done_ to *true*.
1452+
1. Let _oneDayFarther_ be ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
1453+
1. Let _dayLengthNs_ be NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]).
1454+
1. Let _oneDayLess_ be ! SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_).
1455+
1. If NormalizedTimeDurationSign(_oneDayLess_) &times; _sign_ &ge; 0, then
1456+
1. Set _norm_ to _oneDayLess_.
1457+
1. Set _relativeResult_ to _oneDayFarther_.
1458+
1. Set _days_ to _days_ + _sign_.
1459+
1. Set _oneDayFarther_ to ? AddDaysToZonedDateTime(_relativeResult_.[[Instant]], _relativeResult_.[[DateTime]], _timeZoneRec_, _zonedRelativeTo_.[[Calendar]], _sign_).
1460+
1. Set _dayLengthNs_ to NormalizedTimeDurationFromEpochNanosecondsDifference(_oneDayFarther.[[EpochNanoseconds]], _relativeResult_.[[EpochNanoseconds]]).
1461+
1. If NormalizedTimeDurationSign(? SubtractNormalizedTimeDuration(_norm_, _dayLengthNs_)) &times; _sign_ &ge; 0, then
1462+
1. Throw a *RangeError* exception.
14641463
1. If _days_ &lt; 0 and _sign_ = 1, throw a *RangeError* exception.
14651464
1. If _days_ &gt; 0 and _sign_ = -1, throw a *RangeError* exception.
14661465
1. If NormalizedTimeDurationSign(_norm_) = -1, then

0 commit comments

Comments
 (0)