Skip to content

Commit ef4d8ab

Browse files
authored
Force evaluation order in set_date_fields (#268)
1 parent 33f7249 commit ef4d8ab

File tree

2 files changed

+113
-40
lines changed

2 files changed

+113
-40
lines changed

quickjs.c

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46793,7 +46793,7 @@ static __exception int get_date_fields(JSContext *ctx, JSValue obj,
4679346793
return FALSE; /* NaN */
4679446794
d = 0; /* initialize all fields to 0 */
4679546795
} else {
46796-
d = dval;
46796+
d = dval; /* assuming -8.64e15 <= dval <= -8.64e15 */
4679746797
if (is_local) {
4679846798
tz = -getTimezoneOffset(d);
4679946799
d += tz * 60000;
@@ -46839,33 +46839,63 @@ static double time_clip(double t) {
4683946839
return NAN;
4684046840
}
4684146841

46842-
/* The spec mandates the use of 'double' and it fixes the order
46842+
/* The spec mandates the use of 'double' and it specifies the order
4684346843
of the operations */
4684446844
static double set_date_fields(double fields[], int is_local) {
46845-
int64_t y;
46846-
double days, d, h, m1;
46847-
int i, m, md;
46848-
46849-
m1 = fields[1];
46850-
m = fmod(m1, 12);
46851-
if (m < 0)
46852-
m += 12;
46853-
y = (int64_t)(fields[0] + floor(m1 / 12));
46854-
days = days_from_year(y);
46855-
46856-
for(i = 0; i < m; i++) {
46857-
md = month_days[i];
46845+
double y, m, dt, ym, mn, day, h, s, milli, time, tv;
46846+
int yi, mi, i;
46847+
int64_t days;
46848+
volatile double temp; /* enforce evaluation order */
46849+
46850+
/* emulate 21.4.1.15 MakeDay ( year, month, date ) */
46851+
y = fields[0];
46852+
m = fields[1];
46853+
dt = fields[2];
46854+
ym = y + floor(m / 12);
46855+
mn = fmod(m, 12);
46856+
if (mn < 0)
46857+
mn += 12;
46858+
if (ym < -271821 || ym > 275760)
46859+
return NAN;
46860+
46861+
yi = ym;
46862+
mi = mn;
46863+
days = days_from_year(yi);
46864+
for(i = 0; i < mi; i++) {
46865+
days += month_days[i];
4685846866
if (i == 1)
46859-
md += days_in_year(y) - 365;
46860-
days += md;
46867+
days += days_in_year(yi) - 365;
46868+
}
46869+
day = days + dt - 1;
46870+
46871+
/* emulate 21.4.1.14 MakeTime ( hour, min, sec, ms ) */
46872+
h = fields[3];
46873+
m = fields[4];
46874+
s = fields[5];
46875+
milli = fields[6];
46876+
/* Use a volatile intermediary variable to ensure order of evaluation
46877+
* as specified in ECMA. This fixes a test262 error on
46878+
* test262/test/built-ins/Date/UTC/fp-evaluation-order.js.
46879+
* Without the volatile qualifier, the compile can generate code
46880+
* that performs the computation in a different order or with instructions
46881+
* that produce a different result such as FMA (float multiply and add).
46882+
*/
46883+
time = h * 3600000;
46884+
time += (temp = m * 60000);
46885+
time += (temp = s * 1000);
46886+
time += milli;
46887+
46888+
/* emulate 21.4.1.16 MakeDate ( day, time ) */
46889+
tv = (temp = day * 86400000) + time; /* prevent generation of FMA */
46890+
if (!isfinite(tv))
46891+
return NAN;
46892+
46893+
/* adjust for local time and clip */
46894+
if (is_local) {
46895+
int64_t ti = tv < INT64_MIN ? INT64_MIN : tv >= 0x1p63 ? INT64_MAX : (int64_t)tv;
46896+
tv += getTimezoneOffset(ti) * 60000;
4686146897
}
46862-
days += fields[2] - 1;
46863-
h = fields[3] * 3600000 + fields[4] * 60000 +
46864-
fields[5] * 1000 + fields[6];
46865-
d = days * 86400000 + h;
46866-
if (is_local)
46867-
d += getTimezoneOffset(d) * 60000;
46868-
return time_clip(d);
46898+
return time_clip(tv);
4686946899
}
4687046900

4687146901
static JSValue get_date_field(JSContext *ctx, JSValue this_val,
@@ -47475,6 +47505,7 @@ static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValue this_val,
4747547505
if (isnan(v))
4747647506
return JS_NAN;
4747747507
else
47508+
/* assuming -8.64e15 <= v <= -8.64e15 */
4747847509
return JS_NewInt64(ctx, getTimezoneOffset((int64_t)trunc(v)));
4747947510
}
4748047511

tests/test_builtin.js

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,22 @@ function assert(actual, expected, message) {
8484
if (arguments.length == 1)
8585
expected = true;
8686

87-
if (actual === expected)
88-
return;
89-
90-
if (actual !== null && expected !== null
91-
&& typeof actual == 'object' && typeof expected == 'object'
92-
&& actual.toString() === expected.toString())
93-
return;
94-
87+
if (typeof actual === typeof expected) {
88+
if (actual === expected) {
89+
if (actual !== 0 || (1 / actual) === (1 / expected))
90+
return;
91+
}
92+
if (typeof actual === 'number') {
93+
if (isNaN(actual) && isNaN(expected))
94+
return true;
95+
}
96+
if (typeof actual === 'object') {
97+
if (actual !== null && expected !== null
98+
&& actual.constructor === expected.constructor
99+
&& actual.toString() === expected.toString())
100+
return;
101+
}
102+
}
95103
throw Error("assertion failed: got |" + actual + "|" +
96104
", expected |" + expected + "|" +
97105
(message ? " (" + message + ")" : ""));
@@ -594,20 +602,54 @@ function test_date()
594602
assert(d.toISOString(), "2017-09-22T18:10:11.091Z");
595603
a = Date.parse(d.toISOString());
596604
assert((new Date(a)).toISOString(), d.toISOString());
605+
s = new Date("2020-01-01T01:01:01.123Z").toISOString();
606+
assert(s, "2020-01-01T01:01:01.123Z");
607+
// implementation defined behavior
597608
s = new Date("2020-01-01T01:01:01.1Z").toISOString();
598-
assert(s == "2020-01-01T01:01:01.100Z");
609+
assert(s, "2020-01-01T01:01:01.100Z");
599610
s = new Date("2020-01-01T01:01:01.12Z").toISOString();
600-
assert(s == "2020-01-01T01:01:01.120Z");
601-
s = new Date("2020-01-01T01:01:01.123Z").toISOString();
602-
assert(s == "2020-01-01T01:01:01.123Z");
611+
assert(s, "2020-01-01T01:01:01.120Z");
603612
s = new Date("2020-01-01T01:01:01.1234Z").toISOString();
604-
assert(s == "2020-01-01T01:01:01.123Z");
613+
assert(s, "2020-01-01T01:01:01.123Z");
605614
s = new Date("2020-01-01T01:01:01.12345Z").toISOString();
606-
assert(s == "2020-01-01T01:01:01.123Z");
615+
assert(s, "2020-01-01T01:01:01.123Z");
607616
s = new Date("2020-01-01T01:01:01.1235Z").toISOString();
608-
assert(s == "2020-01-01T01:01:01.124Z");
617+
assert(s == "2020-01-01T01:01:01.124Z" || // QuickJS
618+
s == "2020-01-01T01:01:01.123Z"); // nodeJS
609619
s = new Date("2020-01-01T01:01:01.9999Z").toISOString();
610-
assert(s == "2020-01-01T01:01:02.000Z");
620+
assert(s == "2020-01-01T01:01:02.000Z" || // QuickJS
621+
s == "2020-01-01T01:01:01.999Z"); // nodeJS
622+
623+
assert(Date.UTC(2017), 1483228800000);
624+
assert(Date.UTC(2017, 9), 1506816000000);
625+
assert(Date.UTC(2017, 9, 22), 1508630400000);
626+
assert(Date.UTC(2017, 9, 22, 18), 1508695200000);
627+
assert(Date.UTC(2017, 9, 22, 18, 10), 1508695800000);
628+
assert(Date.UTC(2017, 9, 22, 18, 10, 11), 1508695811000);
629+
assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91), 1508695811091);
630+
631+
assert(Date.UTC(NaN), NaN);
632+
assert(Date.UTC(2017, NaN), NaN);
633+
assert(Date.UTC(2017, 9, NaN), NaN);
634+
assert(Date.UTC(2017, 9, 22, NaN), NaN);
635+
assert(Date.UTC(2017, 9, 22, 18, NaN), NaN);
636+
assert(Date.UTC(2017, 9, 22, 18, 10, NaN), NaN);
637+
assert(Date.UTC(2017, 9, 22, 18, 10, 11, NaN), NaN);
638+
assert(Date.UTC(2017, 9, 22, 18, 10, 11, 91, NaN), 1508695811091);
639+
640+
// TODO: Fix rounding errors on Windows/Cygwin.
641+
if (!['win32', 'cygwin'].includes(os.platform)) {
642+
// from test262/test/built-ins/Date/UTC/fp-evaluation-order.js
643+
assert(Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740), 29312,
644+
'order of operations / precision in MakeTime');
645+
assert(Date.UTC(1970, 0, 213503982336, 0, 0, 0, -18446744073709552000), 34447360,
646+
'precision in MakeDate');
647+
}
648+
//assert(Date.UTC(2017 - 1e9, 9 + 12e9), 1506816000000); // node fails this
649+
assert(Date.UTC(2017, 9, 22 - 1e10, 18 + 24e10), 1508695200000);
650+
assert(Date.UTC(2017, 9, 22, 18 - 1e10, 10 + 60e10), 1508695800000);
651+
assert(Date.UTC(2017, 9, 22, 18, 10 - 1e10, 11 + 60e10), 1508695811000);
652+
assert(Date.UTC(2017, 9, 22, 18, 10, 11 - 1e12, 91 + 1000e12), 1508695811091);
611653
}
612654

613655
function test_regexp()

0 commit comments

Comments
 (0)