From e25861ca4cb9819f704dea4f22a7ca4fbf71b382 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Sat, 7 Nov 2026 14:04:01 +0100 Subject: [PATCH 01/16] fixed invalid use of js_get_length32() (#597) --- quickjs.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/quickjs.c b/quickjs.c index 0600dee..49e03a1 100644 --- a/quickjs.c +++ b/quickjs.c @@ -42330,6 +42330,7 @@ static JSValue js_function_constructor(JSContext *ctx, JSValueConst new_target, return JS_EXCEPTION; } +/* never use except where 'obj' is an Array or a string */ static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, JSValueConst obj) { @@ -45353,7 +45354,7 @@ exception: typedef struct JSArrayIteratorData { JSValue obj; JSIteratorKindEnum kind; - uint32_t idx; + int64_t idx; } JSArrayIteratorData; static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val) @@ -45418,7 +45419,7 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, BOOL *pdone, int magic) { JSArrayIteratorData *it; - uint32_t len, idx; + int64_t len, idx; JSValue val, obj; JSObject *p; @@ -45436,7 +45437,7 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, } len = p->u.array.count; } else { - if (js_get_length32(ctx, &len, it->obj)) { + if (js_get_length64(ctx, &len, it->obj)) { fail1: *pdone = FALSE; return JS_EXCEPTION; @@ -45453,9 +45454,9 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, it->idx = idx + 1; *pdone = FALSE; if (it->kind == JS_ITERATOR_KIND_KEY) { - return JS_NewUint32(ctx, idx); + return JS_NewInt64(ctx, idx); } else { - val = JS_GetPropertyUint32(ctx, it->obj, idx); + val = JS_GetPropertyInt64(ctx, it->obj, idx); if (JS_IsException(val)) return JS_EXCEPTION; if (it->kind == JS_ITERATOR_KIND_VALUE) { @@ -45463,7 +45464,7 @@ static JSValue js_array_iterator_next(JSContext *ctx, JSValueConst this_val, } else { JSValueConst args[2]; JSValue num; - num = JS_NewUint32(ctx, idx); + num = JS_NewInt64(ctx, idx); args[0] = num; args[1] = val; obj = js_create_array(ctx, 2, args); @@ -48535,8 +48536,10 @@ static JSValue js_string_match(JSContext *ctx, JSValueConst this_val, return result; } -/* if captures != NULL, captures_val and matched are ignored. Otherwise, - captures_len is ignored */ +/* if captures != NULL, captures_val and matched are + ignored. Otherwise, captures_len is ignored. 'matched' is + JS_UNDEFINED or a string. 'captures_val' is JS_UNDEFINED or an + array. */ static int js_string_GetSubstitution(JSContext *ctx, StringBuffer *b, JSValueConst matched, @@ -48566,9 +48569,11 @@ static int js_string_GetSubstitution(JSContext *ctx, } else { captures_len = 0; if (!JS_IsUndefined(captures_val)) { + /* captures_val is always an Array if not undefined */ if (js_get_length32(ctx, &captures_len, captures_val)) goto exception; } + /* 'matched' is a string */ if (js_get_length32(ctx, &matched_len, matched)) goto exception; } @@ -51656,9 +51661,8 @@ static JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValueConst this_val, JSString *p, *sp; StringBuffer b_s, *b = &b_s; ValueBuffer v_b, *results = &v_b; - int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode; - uint32_t nCaptures; - int64_t position; + int nextSourcePosition, j, functionalReplace, is_global, fullUnicode; + int64_t nCaptures, n, position; if (!JS_IsObject(rx)) return JS_ThrowTypeErrorNotAnObject(ctx); @@ -51738,7 +51742,7 @@ static JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValueConst this_val, for(j = 0; j < results->len; j++) { JSValueConst result; result = results->arr[j]; - if (js_get_length32(ctx, &nCaptures, result) < 0) + if (js_get_length64(ctx, &nCaptures, result) < 0) goto exception; JS_FreeValue(ctx, matched); matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); @@ -54053,6 +54057,7 @@ static int js_proxy_get_own_property_names(JSContext *ctx, { JSProxyData *s; JSValue method, prop_array, val; + int64_t len64; uint32_t len, i, len2; JSPropertyEnum *tab, *tab2; JSAtom atom; @@ -54080,8 +54085,13 @@ static int js_proxy_get_own_property_names(JSContext *ctx, len = 0; tab2 = NULL; len2 = 0; - if (js_get_length32(ctx, &len, prop_array)) + if (js_get_length64(ctx, &len64, prop_array)) goto fail; + if (len64 > INT32_MAX) { + JS_ThrowOutOfMemory(ctx); + goto fail; + } + len = len64; if (len > 0) { tab = js_mallocz(ctx, sizeof(tab[0]) * len); if (!tab) -- 2.47.3 From 3134fbd403d205e61350b9c384fd0c8a222f34a0 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Sat, 7 Nov 2026 14:07:01 +0100 Subject: [PATCH 02/16] added tests for Array iterator length property access --- tests/test_builtin.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_builtin.js b/tests/test_builtin.js index d0e1691..7fcc4bd 100644 --- a/tests/test_builtin.js +++ b/tests/test_builtin.js @@ -244,6 +244,7 @@ function test_array() assert(a.toString(), ",,,,,,,,,,2"); test_array_extension(); + test_array_iterator(); } function test_array_extension() @@ -269,6 +270,17 @@ function test_array_extension() assert(o.length, 3); } +function test_array_iterator() +{ + var n = 0; + for (var x of Array.prototype.values.call({ length: -1, 0: "a" })) { if (++n > 3) break; } + assert(n, 0); + + n = 0; + for (var k of Array.prototype.keys.call({ length: 2 ** 32 + 1 })) { if (++n > 5) break; } + assert(n, 6); +} + function test_string() { var a; -- 2.47.3 From 66fdddf173f7074d11a26f4c04d1d43a5eecc711 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Sat, 7 Nov 2026 14:18:02 +0100 Subject: [PATCH 03/16] Apply ToIndex to the maxByteLength option of the ArrayBuffer constructor (devanshc777) --- quickjs.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/quickjs.c b/quickjs.c index 49e03a1..a15818e 100644 --- a/quickjs.c +++ b/quickjs.c @@ -60765,10 +60765,10 @@ static JSValue js_array_buffer_constructor0(JSContext *ctx, JSValueConst new_tar { uint64_t len, max_len, *pmax_len = NULL; JSValue obj, val; - int64_t i; - - if (JS_ToIndex(ctx, &len, argv[0])) - return JS_EXCEPTION; + int ret; + + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; if (argc < 2) goto next; if (!JS_IsObject(argv[1])) @@ -60782,12 +60782,12 @@ static JSValue js_array_buffer_constructor0(JSContext *ctx, JSValueConst new_tar return JS_EXCEPTION; if (JS_IsUndefined(val)) goto next; - if (JS_ToInt64Free(ctx, &i, val)) + ret = JS_ToIndex(ctx, &max_len, val); + JS_FreeValue(ctx, val); + if (ret) return JS_EXCEPTION; - // don't have to check i < 0 because len >= 0 - if (len > i || i > MAX_SAFE_INTEGER) + if (len > max_len) return JS_ThrowRangeError(ctx, "invalid array buffer max length"); - max_len = i; pmax_len = &max_len; next: return js_array_buffer_constructor2(ctx, new_target, len, pmax_len, -- 2.47.3 From 092277ac1eb2997916a8ad0c4fbf961c91180498 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Sat, 7 Nov 2026 14:25:12 +0100 Subject: [PATCH 04/16] avoid dereferencing the iterator twice in js_append_enumerate() --- quickjs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickjs.c b/quickjs.c index a15818e..737cfeb 100644 --- a/quickjs.c +++ b/quickjs.c @@ -17317,9 +17317,9 @@ static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) ft.generic_magic = js_create_array_iterator; is_array_iterator = JS_IsCFunction(ctx, iterator, ft.generic, JS_ITERATOR_KIND_VALUE); - JS_FreeValue(ctx, iterator); - enumobj = JS_GetIterator(ctx, sp[-1], FALSE); + enumobj = JS_GetIterator2(ctx, sp[-1], iterator); + JS_FreeValue(ctx, iterator); if (JS_IsException(enumobj)) return -1; method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); -- 2.47.3 From e36f8644e33529ddededc4398501ad3cd7c91b24 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 14:41:44 +0100 Subject: [PATCH 05/16] fixed malloc error handling in JS_DefineProperty() (#604) --- quickjs.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/quickjs.c b/quickjs.c index 737cfeb..788c8f8 100644 --- a/quickjs.c +++ b/quickjs.c @@ -10644,21 +10644,25 @@ int JS_DefineProperty(JSContext *ctx, JSValueConst this_obj, if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { JSPropertyGetSet *getset; - if (js_shape_prepare_update(ctx, p, &prs)) + getset = js_malloc(ctx, sizeof(JSPropertyGetSet)); + if (!getset) return -1; + if (js_shape_prepare_update(ctx, p, &prs)) { + js_free(ctx, getset); + return -1; + } /* convert to getset */ if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { if (unlikely(p->class_id == JS_CLASS_GLOBAL_OBJECT)) { - if (remove_global_object_property(ctx, p, prs, pr)) + if (remove_global_object_property(ctx, p, prs, pr)) { + js_free(ctx, getset); return -1; + } } free_var_ref(ctx->rt, pr->u.var_ref); } else { JS_FreeValue(ctx, pr->u.value); } - getset = js_malloc(ctx, sizeof(JSPropertyGetSet)); - if (!getset) - return -1; prs->flags = (prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | JS_PROP_GETSET; -- 2.47.3 From 3cddd4c4698126819ecf09e45d429ec9a4a59125 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 15:08:42 +0100 Subject: [PATCH 06/16] fixed waitAsync() polling in quickjs-libc (#603) --- quickjs-libc.c | 5 +++-- tests/test_worker.js | 16 +++++++++++++--- tests/test_worker_module.js | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 3ac128e..b9e855b 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -2619,7 +2619,7 @@ static int js_os_poll(JSContext *ctx) { JSRuntime *rt = JS_GetRuntime(ctx); JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int min_delay, nfds; + int min_delay, nfds, atomics_async_waker_fd_index; int64_t cur_time, delay; JSOSRWHandler *rh; struct list_head *el; @@ -2698,6 +2698,7 @@ static int js_os_poll(JSContext *ctx) } } + atomics_async_waker_fd_index = nfds; if (js_poll_add_poll_fd(ts, &nfds, ts->atomics_async_waker.read_fd, POLLIN)) return -1; @@ -2728,7 +2729,7 @@ static int js_os_poll(JSContext *ctx) } } } - if (ts->poll_fds[ts->atomics_async_waker.read_fd].revents != 0) { + if (ts->poll_fds[atomics_async_waker_fd_index].revents != 0) { js_waker_clear(&ts->atomics_async_waker); } } diff --git a/tests/test_worker.js b/tests/test_worker.js index 4b52bf8..64d4813 100644 --- a/tests/test_worker.js +++ b/tests/test_worker.js @@ -44,10 +44,20 @@ function test_worker() break; case "sab_done": { - let buf = ev.buf; /* check that the SharedArrayBuffer was modified */ - assert(buf[2], 10); - worker.postMessage({ type: "abort" }); + assert(ev.buf[2], 10); + /* start the waitAsync() test */ + let sab = new SharedArrayBuffer(4); + let buf = new Int32Array(sab); + worker.postMessage({ type: "wait_async", buf: buf }); + } + break; + case "notify": + { + let buf = ev.buf; + Atomics.store(buf, 0, 1); + /* notify the waitAsync() */ + Atomics.notify(buf, 0, 1); } break; case "done": diff --git a/tests/test_worker_module.js b/tests/test_worker_module.js index ddf8e40..df9358c 100644 --- a/tests/test_worker_module.js +++ b/tests/test_worker_module.js @@ -17,6 +17,10 @@ function handle_msg(e) { ev.buf[2] = 10; parent.postMessage({ type: "sab_done", buf: ev.buf }); break; + case "wait_async": + Atomics.waitAsync(ev.buf, 0, 0, Infinity).value.then( () => { parent.postMessage({ type: "done" }); } ); + parent.postMessage({ type: "notify", buf: ev.buf }); + break; } } -- 2.47.3 From 813636e7af1b622c69223f2a63969ee2074deb9c Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 15:19:15 +0100 Subject: [PATCH 07/16] fixed duplicate Invocation of the ArrayBuffer Release Callback (Divy Srivastava, #606) --- quickjs.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index 788c8f8..9c67217 100644 --- a/quickjs.c +++ b/quickjs.c @@ -60994,8 +60994,13 @@ void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj) if (!abuf || abuf->detached) return; - if (abuf->free_func) + if (abuf->free_func) { abuf->free_func(ctx->rt, abuf->opaque, abuf->data); + /* The backing data has been released. Do not release it again when + the detached ArrayBuffer object is later finalized. */ + abuf->free_func = NULL; + abuf->opaque = NULL; + } abuf->data = NULL; abuf->byte_length = 0; abuf->detached = TRUE; @@ -61160,6 +61165,8 @@ static JSValue js_array_buffer_transfer(JSContext *ctx, abuf->data = NULL; abuf->byte_length = 0; abuf->detached = TRUE; + abuf->free_func = NULL; + abuf->opaque = NULL; js_array_buffer_update_typed_arrays(abuf); } if (magic == JS_ARRAY_BUFFER_TRANSFER_TO_IMMUTABLE && !JS_IsException(res)) { -- 2.47.3 From 56d38b8e90eb74f36cd75e3e63506bde9827df67 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 15:28:18 +0100 Subject: [PATCH 08/16] fixed JS_GetTypedArrayBuffer() length reporting with resizable array buffers (#608) --- quickjs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs.c b/quickjs.c index 9c67217..8f36a55 100644 --- a/quickjs.c +++ b/quickjs.c @@ -61535,7 +61535,7 @@ JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj, if (pbyte_offset) *pbyte_offset = ta->offset; if (pbyte_length) - *pbyte_length = ta->length; + *pbyte_length = (size_t)p->u.array.count << typed_array_size_log2(p->class_id); if (pbytes_per_element) { *pbytes_per_element = 1 << typed_array_size_log2(p->class_id); } -- 2.47.3 From 014389e048e40b3d8866c12241daf71a54ed5b14 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 15:42:04 +0100 Subject: [PATCH 09/16] quickjs-libc: JSWaker workaround when the pipe buffer is full (#605) --- quickjs-libc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index b9e855b..2cea4e9 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -2369,6 +2369,8 @@ static int js_waker_init(JSWaker *w) if (pipe(fds) < 0) return -1; + fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK); + fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK); w->read_fd = fds[0]; w->write_fd = fds[1]; return 0; @@ -2382,7 +2384,10 @@ static void js_waker_signal(JSWaker *w) ret = write(w->write_fd, "", 1); if (ret == 1) break; - if (ret < 0 && (errno != EAGAIN || errno != EINTR)) + /* if errno = EAGAIN the pipe is full and we consider it is + likely that the poll() will be waked anyway, so we don't + need to block */ + if (ret < 0 && errno != EINTR) break; } } -- 2.47.3 From f2e4461d4df507a09961dc9be10c44ff55005a01 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 17:36:13 +0100 Subject: [PATCH 10/16] added missing bound checks to dates in temporal objects and simplified constructor invocation (#610) --- temporal/duration.c | 26 ++++------ temporal/instant.c | 43 ++++------------- temporal/plaindate.c | 81 ++++++++------------------------ temporal/plaindatetime.c | 73 ++++++++++------------------ temporal/plainmonthday.c | 48 ++++++++----------- temporal/plaintime.c | 26 ++++------ temporal/plainyearmonth.c | 94 +++++++++---------------------------- temporal/temporal.c | 11 +---- temporal/temporal_private.h | 1 - temporal/zoneddatetime.c | 56 +++++----------------- 10 files changed, 123 insertions(+), 336 deletions(-) diff --git a/temporal/duration.c b/temporal/duration.c index 4322709..1e0e96c 100644 --- a/temporal/duration.c +++ b/temporal/duration.c @@ -54,7 +54,7 @@ static TIUnit ti_larger_of_two_units(TIUnit a, TIUnit b) return a < b ? a : b; } -JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) +static JSValue js_create_duration2(JSContext *ctx, JSValueConst new_target, const JSDurationData *d) { JSDurationData *s; JSValue obj; @@ -64,7 +64,7 @@ JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) *s = *d; /* Round fields to float64-representable integers to match spec behavior */ duration_round_fields_to_float64(s); - obj = JS_NewObjectClass(ctx, (int)js_duration_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_duration_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -73,6 +73,11 @@ JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) return obj; } +JSValue js_create_duration(JSContext *ctx, const JSDurationData *d) +{ + return js_create_duration2(ctx, JS_UNDEFINED, d); +} + static int to_integer_if_integral(JSContext *ctx, JSValueConst val, int64_t *pres) { double d; @@ -625,10 +630,6 @@ static JSValue js_duration_constructor(JSContext *ctx, JSValueConst new_target, { JSDurationData d = {0}; js_int128_t tmp_microseconds = 0, tmp_nanoseconds = 0; - JSValue obj; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.Duration constructor cannot be called as a function"); - } if (argc > 0 && to_integer_if_integral(ctx, argv[0], &d.years) < 0) return JS_EXCEPTION; if (argc > 1 && to_integer_if_integral(ctx, argv[1], &d.months) < 0) @@ -655,18 +656,7 @@ static JSValue js_duration_constructor(JSContext *ctx, JSValueConst new_target, return JS_ThrowRangeError(ctx, "duration out of range"); if (duration_is_out_of_range(&d)) return JS_ThrowRangeError(ctx, "duration out of range"); - { - JSDurationData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - *s = d; - obj = js_create_temporal_object(ctx, new_target, js_duration_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_duration2(ctx, new_target, &d); } /* Temporal.Duration.from */ diff --git a/temporal/instant.c b/temporal/instant.c index c163710..c97ae02 100644 --- a/temporal/instant.c +++ b/temporal/instant.c @@ -25,15 +25,17 @@ static const JSClassDef js_instant_class = { .finalizer = js_instant_finalizer, }; -JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) +static JSValue js_create_instant2(JSContext *ctx, JSValueConst new_target, const JSTimeSpec *ts) { JSInstantData *s; JSValue obj; + if (!is_valid_epoch_timespec(ts)) + return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->ts = *ts; - obj = JS_NewObjectClass(ctx, (int)js_instant_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_instant_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -42,6 +44,11 @@ JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) return obj; } +JSValue js_create_instant(JSContext *ctx, const JSTimeSpec *ts) +{ + return js_create_instant2(ctx, JS_UNDEFINED, ts); +} + /* --- Date math helpers --- */ static int ti_is_leap_year(int64_t y) @@ -342,32 +349,13 @@ static JSValue ti_to_temporal_instant(JSContext *ctx, JSValueConst item) static JSValue js_instant_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - (void)argc; - JSValue obj = JS_UNDEFINED; JSTimeSpec ts; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.Instant constructor cannot be called as a function"); - } if (JS_IsUndefined(argv[0])) { return JS_ThrowTypeError(ctx, "Temporal.Instant requires an epochNanoseconds argument"); } if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; - if (!is_valid_epoch_timespec(&ts)) { - return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); - } - { - JSInstantData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->ts = ts; - obj = js_create_temporal_object(ctx, new_target, js_instant_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_instant2(ctx, new_target, &ts); } /* Temporal.Instant.fromEpochMilliseconds */ @@ -408,8 +396,6 @@ static JSValue js_instant_fromEpochMilliseconds(JSContext *ctx, JSValueConst thi if (js_number_to_int64(ctx, val, &ms) < 0) return JS_EXCEPTION; ms_to_timespec(ms, &ts); - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epochMilliseconds out of range"); return js_create_instant(ctx, &ts); } @@ -422,8 +408,6 @@ static JSValue js_instant_fromEpochNanoseconds(JSContext *ctx, JSValueConst this JSTimeSpec ts; if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epochNanoseconds out of range"); return js_create_instant(ctx, &ts); } @@ -1051,8 +1035,6 @@ static JSValue js_instant_round(JSContext *ctx, JSValueConst this_val, /* Convert back to JSTimeSpec */ ti_ns_to_timespec(rounded_ns, &result); - if (!is_valid_epoch_timespec(&result)) - return JS_ThrowRangeError(ctx, "rounded value out of range"); return js_create_instant(ctx, &result); } @@ -1377,11 +1359,6 @@ static JSValue js_instant_add_subtract(JSContext *ctx, JSValueConst this_val, total_ns = -total_ns; total_ns += ti_timespec_to_ns(&s->ts); - - if (total_ns < (js_int128_t)(-INSTANT_MAX_SECS) * 1000000000 || - total_ns > (js_int128_t)INSTANT_MAX_SECS * 1000000000) - return JS_ThrowRangeError(ctx, "instant arithmetic overflow"); - ti_ns_to_timespec(total_ns, &result); return js_create_instant(ctx, &result); } diff --git a/temporal/plaindate.c b/temporal/plaindate.c index ef6341e..8ce1479 100644 --- a/temporal/plaindate.c +++ b/temporal/plaindate.c @@ -23,15 +23,10 @@ static const JSClassDef js_plaindate_class = { .finalizer = js_plaindate_finalizer, }; -JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, int day, CalendarType calendar) +static JSValue js_create_plaindate_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int day, CalendarType calendar) { - if (calendar == CAL_ISO8601 || calendar == CAL_GREGORY) { - /* ISO date range: -271821-04-19 to +275760-09-13 */ - if (year < -271821 || year > 275760 || - (year == -271821 && (month < 4 || (month == 4 && day < 19))) || - (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "PlainDate out of range"); - } + if (!ti_is_date_within_iso_limits(year, month, day)) + return JS_ThrowRangeError(ctx, "PlainDate out of range"); { JSPlainDateData *s; JSValue obj; @@ -42,7 +37,7 @@ JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, i s->month = month; s->day = day; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plaindate_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaindate_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -52,6 +47,11 @@ JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, i } } +JSValue js_create_plaindate_with_calendar(JSContext *ctx, int year, int month, int day, CalendarType calendar) +{ + return js_create_plaindate_with_calendar2(ctx, JS_UNDEFINED, year, month, day, calendar); +} + JSValue js_create_plaindate(JSContext *ctx, int year, int month, int day) { return js_create_plaindate_with_calendar(ctx, year, month, day, CAL_ISO8601); @@ -60,20 +60,12 @@ JSValue js_create_plaindate(JSContext *ctx, int year, int month, int day) JSValue ti_create_plaindate_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { - int year, month, day; - JSValue obj; + int year, month, day, iso_y, iso_m, iso_d; if (ti_resolve_date_from_fields(ctx, fields, overflow_constrain, &year, &month, &day, 1) < 0) return JS_EXCEPTION; - - { - int iso_y = year, iso_m = month, iso_d = day; - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { - ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); - } - obj = js_create_plaindate_with_calendar(ctx, iso_y, iso_m, iso_d, fields->calendar); - } - return obj; + ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); + return js_create_plaindate_with_calendar(ctx, iso_y, iso_m, iso_d, fields->calendar); } int pd_is_leap_year(int year) @@ -84,6 +76,8 @@ int pd_is_leap_year(int year) int pd_days_in_month(int year, int month) { static const int days[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + if (month < 1 || month > 12) + return 30; /* fail safe */ if (month == 2 && pd_is_leap_year(year)) return 29; return days[month - 1]; @@ -139,9 +133,7 @@ static JSValue js_plaindate_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { int year, month, day; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainDate constructor cannot be called as a function"); - } + CalendarType calendar = CAL_ISO8601; if (pd_to_integer_with_truncation(ctx, argv[0], &year) < 0) return JS_EXCEPTION; if (pd_to_integer_with_truncation(ctx, argv[1], &month) < 0) @@ -150,33 +142,12 @@ static JSValue js_plaindate_constructor(JSContext *ctx, JSValueConst new_target, return JS_EXCEPTION; if (!pd_is_valid_date(year, month, day)) return JS_ThrowRangeError(ctx, "invalid PlainDate"); - if (year < -271821 || year > 275760 || - (year == -271821 && (month < 4 || (month == 4 && day < 19))) || - (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "PlainDate out of range"); - { - JSPlainDateData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->year = year; - s->month = month; - s->day = day; - s->calendar = CAL_ISO8601; - if (argc > 3) { - int cal_res = ti_validate_constructor_calendar(ctx, argv[3], &s->calendar); - if (cal_res < 0) { - js_free(ctx, s); - return JS_EXCEPTION; - } - } - obj = js_create_temporal_object(ctx, new_target, js_plaindate_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); + calendar = CAL_ISO8601; + if (argc > 3) { + if (ti_validate_constructor_calendar(ctx, argv[3], &calendar) < 0) return JS_EXCEPTION; - } - return obj; } + return js_create_plaindate_with_calendar2(ctx, new_target, year, month, day, calendar); } /* Getters */ @@ -758,17 +729,7 @@ static JSValue js_plaindate_add_subtract(JSContext *ctx, JSValueConst this_val, ti_calendar_date_to_iso(s->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; } - if (year < -271821 || year > 275760) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - /* Check against ISO date bounds */ - if (year == -271821 && (month < 4 || (month == 4 && day < 19))) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - if (year == 275760 && (month > 9 || (month == 9 && day > 13))) - return JS_ThrowRangeError(ctx, "invalid PlainDate"); - { - JSValue obj = js_create_plaindate_with_calendar(ctx, year, month, day, s->calendar); - return obj; - } + return js_create_plaindate_with_calendar(ctx, year, month, day, s->calendar); } static JSValue js_plaindate_difference(JSContext *ctx, JSValueConst this_val, @@ -1085,8 +1046,6 @@ static JSValue js_plaindate_toZonedDateTime(JSContext *ctx, JSValueConst this_va } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, timeZone, s->calendar); } diff --git a/temporal/plaindatetime.c b/temporal/plaindatetime.c index 38b3646..51f2b71 100644 --- a/temporal/plaindatetime.c +++ b/temporal/plaindatetime.c @@ -40,17 +40,17 @@ JSValue js_create_plaindatetime(JSContext *ctx, int year, int month, int day, millisecond, microsecond, nanosecond, CAL_ISO8601); } -JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int month, int day, - int hour, int minute, int second, - int millisecond, int microsecond, int nanosecond, - CalendarType calendar) +static JSValue js_create_plaindatetime_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int day, + int hour, int minute, int second, + int millisecond, int microsecond, int nanosecond, + CalendarType calendar) { JSPlainDateTimeData *s; JSValue obj; - if ((calendar == CAL_ISO8601 || calendar == CAL_GREGORY) && - !pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) + if (!pdt_is_within_limits(year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond)) { return JS_ThrowRangeError(ctx, "PlainDateTime outside valid ISO range"); + } s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; @@ -64,7 +64,7 @@ JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int mont s->microsecond = microsecond; s->nanosecond = nanosecond; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plaindatetime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaindatetime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -73,6 +73,15 @@ JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int mont return obj; } +JSValue js_create_plaindatetime_with_calendar(JSContext *ctx, int year, int month, int day, + int hour, int minute, int second, + int millisecond, int microsecond, int nanosecond, + CalendarType calendar) +{ + return js_create_plaindatetime_with_calendar2(ctx, JS_UNDEFINED, year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond, calendar); +} + static JSValue ti_create_plaindatetime_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { @@ -110,7 +119,7 @@ static JSValue ti_create_plaindatetime_from_fields(JSContext *ctx, TIPreparedFie if (ns > 999) ns = 999; else if (ns < 0) ns = 0; } - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { + { int iso_y, iso_m, iso_d; ti_calendar_date_to_iso(fields->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; @@ -219,9 +228,7 @@ static JSValue js_plaindatetime_constructor(JSContext *ctx, JSValueConst new_tar { int year, month, day, hour = 0, minute = 0, second = 0; int millisecond = 0, microsecond = 0, nanosecond = 0; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainDateTime constructor cannot be called as a function"); - } + CalendarType calendar = CAL_ISO8601; if (JS_IsUndefined(argv[0])) { JS_ThrowRangeError(ctx, "value must be an integer"); return JS_EXCEPTION; @@ -256,40 +263,13 @@ static JSValue js_plaindatetime_constructor(JSContext *ctx, JSValueConst new_tar return JS_ThrowRangeError(ctx, "invalid PlainDateTime date"); if (!pdt_is_valid_time(hour, minute, second, millisecond, microsecond, nanosecond)) return JS_ThrowRangeError(ctx, "invalid PlainDateTime time"); - { - CalendarType calendar = CAL_ISO8601; - if (argc > 9) { - int cal_res = ti_validate_constructor_calendar(ctx, argv[9], &calendar); - if (cal_res < 0) - return JS_EXCEPTION; - } - if ((calendar == CAL_ISO8601 || calendar == CAL_GREGORY) && - !pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) - return JS_ThrowRangeError(ctx, "PlainDateTime outside valid range"); - { - JSPlainDateTimeData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->year = year; - s->month = month; - s->day = day; - s->hour = hour; - s->minute = minute; - s->second = second; - s->millisecond = millisecond; - s->microsecond = microsecond; - s->nanosecond = nanosecond; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plaindatetime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); + if (argc > 9) { + int cal_res = ti_validate_constructor_calendar(ctx, argv[9], &calendar); + if (cal_res < 0) return JS_EXCEPTION; - } - return obj; - } } + return js_create_plaindatetime_with_calendar2(ctx, new_target, year, month, day, hour, minute, second, + millisecond, microsecond, nanosecond, calendar); } /* Getters */ @@ -963,9 +943,6 @@ static JSValue js_plaindatetime_with(JSContext *ctx, JSValueConst this_val, ti_calendar_date_to_iso(s->calendar, year, month, day, &iso_y, &iso_m, &iso_d); year = iso_y; month = iso_m; day = iso_d; } - if (!pdt_is_within_limits(year, month, day, hour, minute, second, - millisecond, microsecond, nanosecond)) - return JS_ThrowRangeError(ctx, "PlainDateTime outside valid range"); return js_create_plaindatetime_with_calendar(ctx, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, s->calendar); } @@ -1579,8 +1556,6 @@ static JSValue js_plaindatetime_toZonedDateTime(JSContext *ctx, JSValueConst thi } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, timeZone, s->calendar); } } diff --git a/temporal/plainmonthday.c b/temporal/plainmonthday.c index 9fbb4b9..cc5b51e 100644 --- a/temporal/plainmonthday.c +++ b/temporal/plainmonthday.c @@ -24,12 +24,11 @@ static const JSClassDef js_plainmonthday_class = { .finalizer = js_plainmonthday_finalizer, }; -JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int iso_month, int iso_day, CalendarType calendar) +static JSValue js_create_plainmonthday_with_calendar2(JSContext *ctx, JSValueConst new_target, int iso_year, int iso_month, int iso_day, CalendarType calendar) { JSPlainMonthDayData *s; JSValue obj; - /* Validate ISO year range (ISO date limits: -271821 to 275760) */ - if (iso_year < -271821 || iso_year > 275760) + if (!ti_is_date_within_iso_limits(iso_year, iso_month, iso_day)) return JS_ThrowRangeError(ctx, "PlainMonthDay out of range"); s = js_mallocz(ctx, sizeof(*s)); if (!s) @@ -38,7 +37,7 @@ JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int s->iso_month = iso_month; s->iso_day = iso_day; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plainmonthday_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plainmonthday_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -47,6 +46,11 @@ JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int return obj; } +JSValue js_create_plainmonthday_with_calendar(JSContext *ctx, int iso_year, int iso_month, int iso_day, CalendarType calendar) +{ + return js_create_plainmonthday_with_calendar2(ctx, JS_UNDEFINED, iso_year, iso_month, iso_day, calendar); +} + static int pmd_to_integer_with_truncation(JSContext *ctx, JSValueConst val, int *pres) { double d; @@ -73,9 +77,6 @@ static JSValue js_plainmonthday_constructor(JSContext *ctx, JSValueConst new_tar int argc, JSValueConst *argv) { int month, day, year = 1972; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainMonthDay constructor cannot be called as a function"); - } if (pmd_to_integer_with_truncation(ctx, argv[0], &month) < 0) return JS_EXCEPTION; if (pmd_to_integer_with_truncation(ctx, argv[1], &day) < 0) @@ -95,27 +96,7 @@ static JSValue js_plainmonthday_constructor(JSContext *ctx, JSValueConst new_tar } if (month < 1 || month > 12 || day < 1 || day > pd_days_in_month(year, month)) return JS_ThrowRangeError(ctx, "invalid PlainMonthDay"); - /* ISODateWithinLimits check for CreateTemporalMonthDay */ - if (year < -271821 || (year == -271821 && (month < 4 || (month == 4 && day < 19)))) - return JS_ThrowRangeError(ctx, "date outside supported range"); - if (year > 275760 || (year == 275760 && (month > 9 || (month == 9 && day > 13)))) - return JS_ThrowRangeError(ctx, "date outside supported range"); - { - JSPlainMonthDayData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->iso_year = year; - s->iso_month = month; - s->iso_day = day; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plainmonthday_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plainmonthday_with_calendar2(ctx, new_target, year, month, day, calendar); } } @@ -409,6 +390,17 @@ static JSValue js_plainmonthday_with(JSContext *ctx, JSValueConst this_val, return JS_ThrowRangeError(ctx, "month and monthCode must agree"); { int ref_year = has_year ? year_for_overflow : orig_cal_y; + int max_month = ti_calendar_months_in_year(s->calendar, ref_year); + + if (month < 1 || month > max_month) { + if (!overflow_constrain) + return JS_ThrowRangeError(ctx, "invalid PlainMonthDay"); + if (month > max_month) + month = max_month; + else if (month < 1) + month = 1; + } + int dim = ti_calendar_days_in_month(s->calendar, ref_year, month); if (day > dim || day < 1) { if (!overflow_constrain) diff --git a/temporal/plaintime.c b/temporal/plaintime.c index 33d4613..109bcbd 100644 --- a/temporal/plaintime.c +++ b/temporal/plaintime.c @@ -23,7 +23,7 @@ static const JSClassDef js_plaintime_class = { .finalizer = js_plaintime_finalizer, }; -JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) +static JSValue js_create_plaintime2(JSContext *ctx, JSValueConst new_target, const JSPlainTimeData *t) { JSPlainTimeData *s; JSValue obj; @@ -31,7 +31,7 @@ JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) if (!s) return JS_EXCEPTION; *s = *t; - obj = JS_NewObjectClass(ctx, (int)js_plaintime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plaintime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -40,6 +40,11 @@ JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) return obj; } +JSValue js_create_plaintime(JSContext *ctx, const JSPlainTimeData *t) +{ + return js_create_plaintime2(ctx, JS_UNDEFINED, t); +} + /* Forward declaration */ JSValue js_plaintime_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -89,10 +94,6 @@ static JSValue js_plaintime_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { JSPlainTimeData t = {0}; - JSValue obj; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainTime constructor cannot be called as a function"); - } if (argc > 0 && pt_to_integer_with_truncation(ctx, argv[0], &t.hour) < 0) return JS_EXCEPTION; if (argc > 1 && pt_to_integer_with_truncation(ctx, argv[1], &t.minute) < 0) @@ -107,18 +108,7 @@ static JSValue js_plaintime_constructor(JSContext *ctx, JSValueConst new_target, return JS_EXCEPTION; if (!pt_is_valid_time(t.hour, t.minute, t.second, t.millisecond, t.microsecond, t.nanosecond)) return JS_ThrowRangeError(ctx, "invalid PlainTime"); - { - JSPlainTimeData *s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - *s = t; - obj = js_create_temporal_object(ctx, new_target, js_plaintime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plaintime2(ctx, new_target, &t); } /* Getters */ diff --git a/temporal/plainyearmonth.c b/temporal/plainyearmonth.c index 18285df..520244b 100644 --- a/temporal/plainyearmonth.c +++ b/temporal/plainyearmonth.c @@ -23,15 +23,20 @@ static const JSClassDef js_plainyearmonth_class = { .finalizer = js_plainyearmonth_finalizer, }; -static JSValue js_create_plainyearmonth(JSContext *ctx, int year, int month, int referenceISODay) +static int pym_is_valid_year_month(int year, int month) { - return js_create_plainyearmonth_with_calendar(ctx, year, month, referenceISODay, CAL_ISO8601); + return month >= 1 && month <= 12 && + ((year > -271821 || (year == -271821 && month >= 4)) && + (year < 275760 || (year == 275760 && month <= 9))); } -JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int month, int referenceISODay, CalendarType calendar) +static JSValue js_create_plainyearmonth_with_calendar2(JSContext *ctx, JSValueConst new_target, int year, int month, int referenceISODay, CalendarType calendar) { JSPlainYearMonthData *s; JSValue obj; + /* XXX: test the day too ? */ + if (!pym_is_valid_year_month(year, month)) + return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; @@ -39,7 +44,7 @@ JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int mon s->iso_month = month; s->iso_day = referenceISODay; s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_plainyearmonth_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_plainyearmonth_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -48,12 +53,15 @@ JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int mon return obj; } +JSValue js_create_plainyearmonth_with_calendar(JSContext *ctx, int year, int month, int referenceISODay, CalendarType calendar) +{ + return js_create_plainyearmonth_with_calendar2(ctx, JS_UNDEFINED, year, month, referenceISODay, calendar); +} + static JSValue ti_create_plainyearmonth_from_fields(JSContext *ctx, TIPreparedFields *fields, int overflow_constrain) { int year, month, dummy_day = 1; - JSValue obj; - JSPlainYearMonthData *s; if (ti_resolve_date_from_fields(ctx, fields, overflow_constrain, &year, &month, &dummy_day, 0) < 0) return JS_EXCEPTION; @@ -72,46 +80,14 @@ static JSValue ti_create_plainyearmonth_from_fields(JSContext *ctx, TIPreparedFi } } - if (fields->calendar == CAL_ISO8601 || fields->calendar == CAL_GREGORY) { - int is_valid = month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); - if (!is_valid) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - } - { - int iso_d = 1; + int iso_y, iso_m, iso_d = 1; /* Convert calendar date to ISO for all non-ISO/non-Gregory calendars */ - if (fields->calendar != CAL_ISO8601 && fields->calendar != CAL_GREGORY) { - int iso_y, iso_m; - ti_calendar_date_to_iso(fields->calendar, year, month, 1, &iso_y, &iso_m, &iso_d); - year = iso_y; - month = iso_m; - } - { - int is_valid = month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); - if (!is_valid) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - } - obj = js_create_plainyearmonth(ctx, year, month, iso_d); - } - if (!JS_IsException(obj)) { - s = JS_GetOpaque(obj, js_plainyearmonth_class_id); - if (s) { - s->calendar = fields->calendar; - } + ti_calendar_date_to_iso(fields->calendar, year, month, 1, &iso_y, &iso_m, &iso_d); + year = iso_y; + month = iso_m; + return js_create_plainyearmonth_with_calendar(ctx, year, month, iso_d, fields->calendar); } - return obj; -} - -static int pym_is_valid_year_month(int year, int month) -{ - return month >= 1 && month <= 12 && - ((year > -271821 || (year == -271821 && month >= 4)) && - (year < 275760 || (year == 275760 && month <= 9))); } static int pym_to_integer_with_truncation(JSContext *ctx, JSValueConst val, int *pres) @@ -136,19 +112,12 @@ static JSValue js_plainyearmonth_constructor(JSContext *ctx, JSValueConst new_ta int argc, JSValueConst *argv) { int year, month, day = 1; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.PlainYearMonth constructor cannot be called as a function"); - } if (pym_to_integer_with_truncation(ctx, argv[0], &year) < 0) return JS_EXCEPTION; if (pym_to_integer_with_truncation(ctx, argv[1], &month) < 0) return JS_EXCEPTION; if (!pym_is_valid_year_month(year, month)) return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (year < -271821 || (year == -271821 && month < 4)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (year > 275760 || (year == 275760 && month > 9)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); { CalendarType calendar = CAL_ISO8601; if (argc > 2) { @@ -164,22 +133,7 @@ static JSValue js_plainyearmonth_constructor(JSContext *ctx, JSValueConst new_ta return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); if (argc <= 3 || JS_IsUndefined(argv[3])) day = 1; - { - JSPlainYearMonthData *s = js_mallocz(ctx, sizeof(*s)); - JSValue obj; - if (!s) - return JS_EXCEPTION; - s->iso_year = year; - s->iso_month = month; - s->iso_day = day; - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_plainyearmonth_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - return JS_EXCEPTION; - } - return obj; - } + return js_create_plainyearmonth_with_calendar2(ctx, new_target, year, month, day, calendar); } } @@ -672,9 +626,7 @@ static JSValue js_plainyearmonth_add_subtract(JSContext *ctx, JSValueConst this_ /* Convert ISO to calendar space for arithmetic */ pym_get_calendar_date(s, &cal_year, &cal_month, &cal_day); /* Check that base date is within ISO limits */ - if (s->iso_year < -271821 || (s->iso_year == -271821 && (s->iso_month < 4 || (s->iso_month == 4 && s->iso_day < 19)))) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (s->iso_year > 275760 || (s->iso_year == 275760 && (s->iso_month > 9 || (s->iso_month == 9 && s->iso_day > 13)))) + if (!ti_is_date_within_iso_limits(s->iso_year, s->iso_month, s->iso_day)) return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); { int orig_mc = 0, orig_has_l = 0; @@ -705,10 +657,6 @@ static JSValue js_plainyearmonth_add_subtract(JSContext *ctx, JSValueConst this_ { int iso_y, iso_m, iso_d; ti_calendar_date_to_iso(s->calendar, cal_year, cal_month, cal_day, &iso_y, &iso_m, &iso_d); - if (iso_y < -271821 || (iso_y == -271821 && iso_m < 4)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); - if (iso_y > 275760 || (iso_y == 275760 && iso_m > 9)) - return JS_ThrowRangeError(ctx, "invalid PlainYearMonth"); return js_create_plainyearmonth_with_calendar(ctx, iso_y, iso_m, iso_d, s->calendar); } } diff --git a/temporal/temporal.c b/temporal/temporal.c index f1ced5d..e6bd521 100644 --- a/temporal/temporal.c +++ b/temporal/temporal.c @@ -3880,16 +3880,7 @@ int ti_validate_timezone_identifier(JSContext *ctx, JSValueConst v, const char * *out_str = str; return 1; } -JSValue js_create_temporal_object(JSContext *ctx, JSValueConst new_target, - JSClassID class_id, void *opaque) -{ - - JSValue obj = JS_CreateFromConstructor(ctx, new_target, class_id); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JS_SetOpaque(obj, opaque); - return obj; -} + int is_valid_epoch_timespec(const JSTimeSpec *ts) { if (ts->secs > INSTANT_MAX_SECS || ts->secs < -INSTANT_MAX_SECS) diff --git a/temporal/temporal_private.h b/temporal/temporal_private.h index c8ba297..53801cc 100644 --- a/temporal/temporal_private.h +++ b/temporal/temporal_private.h @@ -209,7 +209,6 @@ void ti_append_calendar_annotation(char *buf, int buf_size, CalendarType cal, in int ti_canonicalize_timezone_identifier(const char *str, int64_t offset_ns, char *out, size_t out_size); int ti_validate_constructor_calendar(JSContext *ctx, JSValueConst v, CalendarType *out); int ti_validate_timezone_identifier(JSContext *ctx, JSValueConst v, const char **out_str); -JSValue js_create_temporal_object(JSContext *ctx, JSValueConst new_target, JSClassID class_id, void *opaque); int is_valid_epoch_timespec(const JSTimeSpec *ts); static inline js_int128_t ti_sec_nsec_to_ns(int64_t secs, uint32_t nsecs) diff --git a/temporal/zoneddatetime.c b/temporal/zoneddatetime.c index e5c0e02..2c3d791 100644 --- a/temporal/zoneddatetime.c +++ b/temporal/zoneddatetime.c @@ -36,18 +36,20 @@ static const JSClassDef js_zoneddatetime_class = { .finalizer = js_zoneddatetime_finalizer, }; -JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, +static JSValue js_create_zoneddatetime2(JSContext *ctx, JSValueConst new_target, const JSTimeSpec *ts, const char *timeZone, CalendarType calendar) { JSZonedDateTimeData *s; JSValue obj; + if (!is_valid_epoch_timespec(ts)) + return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); s = js_mallocz(ctx, sizeof(*s)); if (!s) return JS_EXCEPTION; s->ts = *ts; pstrcpy(s->timeZone, sizeof(s->timeZone), timeZone); s->calendar = calendar; - obj = JS_NewObjectClass(ctx, (int)js_zoneddatetime_class_id); + obj = JS_CreateFromConstructor(ctx, new_target, js_zoneddatetime_class_id); if (JS_IsException(obj)) { js_free(ctx, s); return JS_EXCEPTION; @@ -56,6 +58,12 @@ JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, return obj; } +JSValue js_create_zoneddatetime(JSContext *ctx, const JSTimeSpec *ts, + const char *timeZone, CalendarType calendar) +{ + return js_create_zoneddatetime2(ctx, JS_UNDEFINED, ts, timeZone, calendar); +} + /* Apply timezone offset to UTC epoch seconds to get local wall-clock time */ /* Create a ZonedDateTime from prepared fields */ JSValue ti_create_zoneddatetime_from_fields(JSContext *ctx, TIPreparedFields *fields, @@ -234,9 +242,6 @@ JSValue ti_create_zoneddatetime_from_fields(JSContext *ctx, TIPreparedFields *fi } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - return js_create_zoneddatetime(ctx, &ts, timeZone, fields->calendar); } static void zdt_apply_timezone_offset(JSContext *ctx, JSZonedDateTimeData *s, @@ -271,15 +276,11 @@ static void zdt_apply_timezone_offset(JSContext *ctx, JSZonedDateTimeData *s, static JSValue js_zoneddatetime_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { - JSZonedDateTimeData *s; JSValue obj; JSTimeSpec ts; const char *timeZone = "UTC"; CalendarType calendar = CAL_ISO8601; int tz_allocated = 0; - if (JS_IsUndefined(new_target)) { - return JS_ThrowTypeError(ctx, "Temporal.ZonedDateTime constructor cannot be called as a function"); - } if (ti_bigint_to_timespec(ctx, argv[0], &ts) < 0) return JS_EXCEPTION; if (!is_valid_epoch_timespec(&ts)) @@ -298,20 +299,7 @@ static JSValue js_zoneddatetime_constructor(JSContext *ctx, JSValueConst new_tar return JS_EXCEPTION; } } - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - if (tz_allocated) JS_FreeCString(ctx, timeZone); - return JS_EXCEPTION; - } - s->ts = ts; - pstrcpy(s->timeZone, sizeof(s->timeZone), timeZone); - s->calendar = calendar; - obj = js_create_temporal_object(ctx, new_target, js_zoneddatetime_class_id, s); - if (JS_IsException(obj)) { - js_free(ctx, s); - if (tz_allocated) JS_FreeCString(ctx, timeZone); - return JS_EXCEPTION; - } + obj = js_create_zoneddatetime2(ctx, new_target, &ts, timeZone, calendar); if (tz_allocated) JS_FreeCString(ctx, timeZone); return obj; } @@ -902,13 +890,6 @@ static JSValue js_zoneddatetime_startOfDay(JSContext *ctx, JSValueConst this_val } else { ts.secs -= offset_sec; } - /* Validate epoch nanoseconds are within Instant limits */ - { - js_int128_t epoch_ns = ti_timespec_to_ns(&ts); - js_int128_t limit = (js_int128_t)8640000000000LL * 1000000000LL; - if (epoch_ns < -limit || epoch_ns > limit) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - } return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -1657,8 +1638,6 @@ static JSValue js_zoneddatetime_round(JSContext *ctx, JSValueConst this_val, ti_ns_to_timespec(rounded_ns, &result); - if (!is_valid_epoch_timespec(&result)) - return JS_ThrowRangeError(ctx, "outside representable range"); return js_create_zoneddatetime(ctx, &result, s->timeZone, s->calendar); } @@ -2040,8 +2019,6 @@ static JSValue js_zoneddatetime_with(JSContext *ctx, JSValueConst this_val, } } } - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -2134,8 +2111,6 @@ static JSValue js_zoneddatetime_withPlainTime(JSContext *ctx, JSValueConst this_ } } JS_FreeValue(ctx, pt_val); - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "invalid epoch nanoseconds"); return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -2755,12 +2730,6 @@ static JSValue js_zoneddatetime_add_subtract(JSContext *ctx, JSValueConst this_v ti_ns_to_timespec(total_ns, &ts); } } - { - js_int128_t epoch_ns = ti_timespec_to_ns(&ts); - js_int128_t limit = (js_int128_t)8640000000000LL * 1000000000LL; - if (epoch_ns < -limit || epoch_ns > limit) - return JS_ThrowRangeError(ctx, "epoch nanoseconds outside valid limits"); - } return js_create_zoneddatetime(ctx, &ts, s->timeZone, s->calendar); } @@ -3398,9 +3367,6 @@ JSValue js_zoneddatetime_from(JSContext *ctx, JSValueConst this_val, ts.nsecs = (uint32_t)new_nsec; } } - /* Validate epoch nanoseconds are within valid range */ - if (!is_valid_epoch_timespec(&ts)) - return JS_ThrowRangeError(ctx, "invalid ZonedDateTime string"); { CalendarType cal = r.has_calendar ? r.calendar : CAL_ISO8601; if (cal == CAL_UNKNOWN) -- 2.47.3 From e5ac135f8db58f8d6b7b5862b8263ce6ba9c5ab6 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Mon, 9 Nov 2026 18:16:48 +0100 Subject: [PATCH 11/16] test_worker.js: explicitly terminate the worker --- tests/test_worker_module.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_worker_module.js b/tests/test_worker_module.js index df9358c..ba893f4 100644 --- a/tests/test_worker_module.js +++ b/tests/test_worker_module.js @@ -8,17 +8,13 @@ function handle_msg(e) { var ev = e.data; // print("child_recv", JSON.stringify(ev)); switch(ev.type) { - case "abort": - parent.postMessage({ type: "done" }); - parent.onmessage = null; /* terminate the worker */ - break; case "sab": /* modify the SharedArrayBuffer */ ev.buf[2] = 10; parent.postMessage({ type: "sab_done", buf: ev.buf }); break; case "wait_async": - Atomics.waitAsync(ev.buf, 0, 0, Infinity).value.then( () => { parent.postMessage({ type: "done" }); } ); + Atomics.waitAsync(ev.buf, 0, 0, Infinity).value.then( () => { parent.postMessage({ type: "done" }); parent.onmessage = null; /* terminate the worker */ }); parent.postMessage({ type: "notify", buf: ev.buf }); break; } -- 2.47.3 From b19b2ec23e1ef8e58c110b225045157e36fa14d7 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 10 Nov 2026 10:07:24 +0100 Subject: [PATCH 12/16] fixed object spread with strings (#611) --- quickjs.c | 27 +++++++++++++-------------- tests/test_language.js | 2 ++ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/quickjs.c b/quickjs.c index 8f36a55..03af563 100644 --- a/quickjs.c +++ b/quickjs.c @@ -17379,12 +17379,12 @@ exception: static __exception int JS_CopyDataProperties(JSContext *ctx, JSValueConst target, - JSValueConst source, + JSValueConst source0, JSValueConst excluded, BOOL setprop) { JSPropertyEnum *tab_atom; - JSValue val; + JSValue val, source; uint32_t i, tab_atom_count; JSObject *p; JSObject *pexcl = NULL; @@ -17392,8 +17392,11 @@ static __exception int JS_CopyDataProperties(JSContext *ctx, JSPropertyDescriptor desc; BOOL is_enumerable; - if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + if (JS_IsNull(source0) || JS_IsUndefined(source0)) return 0; + source = JS_ToObject(ctx, source0); + if (JS_IsException(source)) + return -1; if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) pexcl = JS_VALUE_GET_OBJ(excluded); @@ -17410,8 +17413,10 @@ static __exception int JS_CopyDataProperties(JSContext *ctx, } } if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - gpn_flags)) + gpn_flags)) { + JS_FreeValue(ctx, source); return -1; + } for (i = 0; i < tab_atom_count; i++) { if (pexcl) { @@ -17446,9 +17451,11 @@ static __exception int JS_CopyDataProperties(JSContext *ctx, goto exception; } JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + JS_FreeValue(ctx, source); return 0; exception: JS_FreePropertyEnum(ctx, tab_atom, tab_atom_count); + JS_FreeValue(ctx, source); return -1; } @@ -41851,27 +41858,19 @@ static JSValue js_object_assign(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { // Object.assign(obj, source1) - JSValue obj, s; + JSValue obj; int i; - s = JS_UNDEFINED; obj = JS_ToObject(ctx, argv[0]); if (JS_IsException(obj)) goto exception; for (i = 1; i < argc; i++) { - if (!JS_IsNull(argv[i]) && !JS_IsUndefined(argv[i])) { - s = JS_ToObject(ctx, argv[i]); - if (JS_IsException(s)) + if (JS_CopyDataProperties(ctx, obj, argv[i], JS_UNDEFINED, TRUE)) goto exception; - if (JS_CopyDataProperties(ctx, obj, s, JS_UNDEFINED, TRUE)) - goto exception; - JS_FreeValue(ctx, s); - } } return obj; exception: JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, s); return JS_EXCEPTION; } diff --git a/tests/test_language.js b/tests/test_language.js index aa96c22..ba479ba 100644 --- a/tests/test_language.js +++ b/tests/test_language.js @@ -472,6 +472,8 @@ function test_spread() x = [ ...[ , ] ]; assert(Object.getOwnPropertyNames(x).toString(), "0,length"); + + assert(JSON.stringify({ ..."ab" }), '{"0":"a","1":"b"}'); } function test_function_length() -- 2.47.3 From 8120a9e1e822f222efd09fafc42b353f2f6a9567 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 10 Nov 2026 10:54:22 +0100 Subject: [PATCH 13/16] temporal: stricter timezone validation --- temporal/temporal.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/temporal/temporal.c b/temporal/temporal.c index e6bd521..ccabbbe 100644 --- a/temporal/temporal.c +++ b/temporal/temporal.c @@ -196,7 +196,7 @@ int ti_parse_temporal_timezone_string(const char *str, int64_t *offset_ns) /* ISO date-time string with ±HHMM or ±HH:MM offset at end */ if (len >= 5) { const char *offset_start = NULL; - int offset_len = 0; + size_t offset_len = 0; int i; for (i = (int)(len - 6); i >= 0; i--) { if ((str[i] == '+' || str[i] == '-') && str[i + 3] == ':') { @@ -235,11 +235,7 @@ int ti_parse_temporal_timezone_string(const char *str, int64_t *offset_ns) int hours = h1 * 10 + h2; int mins = m1 * 10 + m2; if (hours <= 23 && mins <= 59) { - if ((int)len > (int)(offset_start - str) + offset_len && offset_start[offset_len] == ':') - return -1; - if ((int)len > (int)(offset_start - str) + offset_len && offset_start[offset_len] == '.') - return -1; - if (len >= 7 && !memcmp(str, "-000000", 7)) + if (len > (offset_start - str) + offset_len) return -1; *offset_ns = sign * ((int64_t)hours * 3600 + mins * 60) * 1000000000LL; return 0; @@ -3877,6 +3873,15 @@ int ti_validate_timezone_identifier(JSContext *ctx, JSValueConst v, const char * return JS_ThrowRangeError(ctx, "invalid time zone"), -1; } } + /* An offset time zone identifier or an available named time zone + identifier (GetAvailableNamedTimeZoneIdentifier is not empty) */ + if (str[0] == '+' || str[0] == '-') { + int64_t offset_ns; + if (ti_parse_temporal_timezone_string(str, &offset_ns) < 0) { + JS_FreeCString(ctx, str); + return JS_ThrowRangeError(ctx, "invalid time zone"), -1; + } + } *out_str = str; return 1; } -- 2.47.3 From 681f105dfd3164c19edb1df3d22587a7a6ba73d9 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 10 Nov 2026 11:01:43 +0100 Subject: [PATCH 14/16] intl: removed buffer overflow (#610 point 3) --- intl/intl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intl/intl.c b/intl/intl.c index a2c9ede..43be6f2 100644 --- a/intl/intl.c +++ b/intl/intl.c @@ -982,7 +982,7 @@ char *canonicalize_locale(JSContext *ctx, const char *locale) strntolower(subtags[i], len); /* variant alias mappings */ if (!strcmp(subtags[i], "heploc")) - pstrcpy(subtags[i], sizeof(buf), "alalc97"); + subtags[i] = (char *)"alalc97"; } { /* Special case: hepburn + heploc -> alalc97 (remove hepburn) */ -- 2.47.3 From 3cbe3f2fea7c14c1e57c434346b491418159b9a9 Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 10 Nov 2026 11:16:24 +0100 Subject: [PATCH 15/16] moved OOB in Intl.NumberFormat (#610 point 4) --- intl/numberformat.c | 8 ++++++-- tests/test_intl.js | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/intl/numberformat.c b/intl/numberformat.c index 60ef2f1..8efe89f 100644 --- a/intl/numberformat.c +++ b/intl/numberformat.c @@ -2425,9 +2425,13 @@ static JSValue intl_mv_format_scientific(JSContext *ctx, IntlMV *mv, JSNumberFor /* Integer part of mantissa */ { char *int_buf = js_malloc(ctx, (size_t)digits_before + 1); + int n; if (!int_buf) goto sci_fail; - memcpy(int_buf, mv->digits, (size_t)digits_before); - int_buf[digits_before] = '\0'; + n = min_int(digits_before, mv->num_digits); + memcpy(int_buf, mv->digits, n); + while (n < digits_before) + int_buf[n++] = '0'; + int_buf[n] = '\0'; dbuf_putstr(&db, int_buf); if (!JS_IsUndefined(parts)) nf_add_part(ctx, parts, "integer", int_buf); diff --git a/tests/test_intl.js b/tests/test_intl.js index 4b3b81b..b881fe9 100644 --- a/tests/test_intl.js +++ b/tests/test_intl.js @@ -224,7 +224,8 @@ function test_numberFormat() { assert(new Intl.NumberFormat('en', { style: 'percent' }).format(0.5), "50%", "en percent"); assert(new Intl.NumberFormat('en', { style: 'currency', currency: 'EUR' }).format(1234.56), "€1,234.56", "en EUR currency"); - + assert(new Intl.NumberFormat("en", { notation: "engineering" }).format(1e23), "100E21"); + // de assert(new Intl.NumberFormat('de').format(1234567.89), "1.234.567,89", "de number"); assert(new Intl.NumberFormat('de').format(1000000), "1.000.000", "de grouping"); -- 2.47.3 From e585640ffa2cf88ce7c2d587583c6fe15928ec2f Mon Sep 17 00:00:00 2001 From: Fabrice Bellard Date: Tue, 10 Nov 2026 11:56:48 +0100 Subject: [PATCH 16/16] intl: fixed DateTimeFormat OOM leaks - simplified and fixed listformat (#610 point 11) --- intl/datetimeformat.c | 7 +- intl/listformat.c | 176 ++++++++++++++---------------------------- 2 files changed, 62 insertions(+), 121 deletions(-) diff --git a/intl/datetimeformat.c b/intl/datetimeformat.c index 4c3a1a3..ff999c9 100644 --- a/intl/datetimeformat.c +++ b/intl/datetimeformat.c @@ -2736,7 +2736,7 @@ static JSValue js_datetimeformat_constructor(JSContext *ctx, dbuf_put(&db, (uint8_t *)"", (size_t)1); if (dbuf_error(&db)) { dbuf_free(&db); - return JS_EXCEPTION; + goto fail; } js_free(ctx, s->locale); s->locale = js_strdup(ctx, (char *)db.buf); @@ -3809,9 +3809,12 @@ static JSValue js_datetimeformat_resolvedOptions(JSContext *ctx, JSValueConst th } obj = JS_NewObject(ctx); - if (JS_IsException(obj)) + if (JS_IsException(obj)) { + JS_FreeValue(ctx, unwrapped); return JS_EXCEPTION; + } + /* XXX: handle JS_DefineProperty errors */ JS_DefinePropertyValueStr(ctx, obj, "locale", JS_NewString(ctx, s->locale), JS_PROP_C_W_E); JS_DefinePropertyValueStr(ctx, obj, "calendar", diff --git a/intl/listformat.c b/intl/listformat.c index 69e5577..596f3ad 100644 --- a/intl/listformat.c +++ b/intl/listformat.c @@ -129,20 +129,40 @@ static JSValue js_listformat_constructor(JSContext *ctx, return JS_EXCEPTION; } +typedef struct { + const char **items; + int count; +} StringList; + +static void free_string_list(JSContext *ctx, StringList *list) +{ + int i; + for(i = 0; i < list->count; i++) { + if (list->items[i]) + JS_FreeCString(ctx, list->items[i]); + } + js_free(ctx, list->items); +} + /* StringListFromIterable abstract operation. - Returns a JSValue array of strings, or JS_EXCEPTION on error. + Return 0 if OK, -1 if ERROR. Uses JS_GetIterator / JS_IteratorNext / JS_IteratorClose. */ -static JSValue string_list_from_iterable(JSContext *ctx, JSValueConst iterable) +static int string_list_from_iterable(JSContext *ctx, StringList *list, JSValueConst iterable) { JSValue iter = JS_UNDEFINED; JSValue next_method = JS_UNDEFINED; - JSValue r = JS_UNDEFINED; + int ret; JSValue v; BOOL done; - int64_t k; - + uint32_t list_size; + const char *str; + + list->items = NULL; + list->count = 0; + list_size = 0; + if (JS_IsUndefined(iterable)) - return JS_NewArray(ctx); + return 0; iter = JS_GetIterator(ctx, iterable, FALSE); if (JS_IsException(iter)) @@ -150,11 +170,8 @@ static JSValue string_list_from_iterable(JSContext *ctx, JSValueConst iterable) next_method = JS_GetPropertyStr(ctx, iter, "next"); if (JS_IsException(next_method)) goto exception; - r = JS_NewArray(ctx); - if (JS_IsException(r)) - goto exception; - for (k = 0;; k++) { + for (;;) { v = JS_IteratorNext(ctx, iter, next_method, &done); if (JS_IsException(v)) goto exception; @@ -167,29 +184,45 @@ static JSValue string_list_from_iterable(JSContext *ctx, JSValueConst iterable) JS_ThrowTypeError(ctx, "iterable yielded non-string value"); goto exception_close; } - if (JS_DefinePropertyValueUint32(ctx, r, (uint32_t)k, v, - JS_PROP_C_W_E | JS_PROP_THROW) < 0) + if ((list->count + 1) > list_size) { + const char **new_items; + if (list_size == 0) + list_size = 4; + else + list_size = list_size + list_size / 2; + if (list_size > INT32_MAX) { + JS_FreeValue(ctx, v); + JS_ThrowOutOfMemory(ctx); + goto exception_close; + } + new_items = js_realloc(ctx, list->items, sizeof(new_items[0]) * list_size); + if (!new_items) { + JS_FreeValue(ctx, v); + goto exception_close; + } + list->items = new_items; + } + str = JS_ToCString(ctx, v); + JS_FreeValue(ctx, v); + if (!str) goto exception_close; + list->items[list->count++] = str; } + ret = 0; done: JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); - return r; + return ret; exception_close: JS_IteratorClose(ctx, iter, TRUE); exception: - JS_FreeValue(ctx, r); - r = JS_EXCEPTION; + free_string_list(ctx, list); + ret = -1; goto done; } /* Helpers for list formatting */ -typedef struct { - const char **items; - int count; -} StringList; - static int is_locale_spanish(const char *locale) { return strstart(locale, "es", NULL); @@ -407,56 +440,16 @@ static JSValue js_listformat_format(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSListFormatData *s = JS_GetOpaque2(ctx, this_val, js_listformat_class_id); - JSValue arr = JS_UNDEFINED; JSValue ret = JS_UNDEFINED; StringList list; - int64_t len = 0, i; - const char **items = NULL; DynBuf db; if (!s) return JS_EXCEPTION; - arr = string_list_from_iterable(ctx, argv[0]); - if (JS_IsException(arr)) + if (string_list_from_iterable(ctx, &list, argv[0])) return JS_EXCEPTION; - { - JSValue len_val = JS_GetPropertyStr(ctx, arr, "length"); - if (JS_IsException(len_val)) - goto fail; - if (JS_ToInt64(ctx, &len, len_val) < 0) { - JS_FreeValue(ctx, len_val); - goto fail; - } - JS_FreeValue(ctx, len_val); - } - - { - size_t alloc_count = (len > 0) ? (size_t)len : 1; - items = js_malloc(ctx, sizeof(*items) * alloc_count); - } - if (!items) - goto fail; - - for (i = 0; i < len; i++) { - JSValue val; - const char *str; - val = JS_GetPropertyUint32(ctx, arr, (uint32_t)i); - if (JS_IsException(val)) { - JS_FreeValue(ctx, val); - goto fail; - } - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto fail; - items[i] = str; - } - - list.items = items; - list.count = (int)len; - js_intl_dbuf_init(ctx, &db); js_listformat_format_to_dbuf(ctx, s, &list, &db); dbuf_putc(&db, '\0'); @@ -470,14 +463,7 @@ static JSValue js_listformat_format(JSContext *ctx, JSValueConst this_val, dbuf_free(&db); fail: - if (items) { - for (i = 0; i < len; i++) { - if (items[i]) - JS_FreeCString(ctx, items[i]); - } - js_free(ctx, items); - } - JS_FreeValue(ctx, arr); + free_string_list(ctx, &list); return ret; } @@ -485,66 +471,18 @@ static JSValue js_listformat_formatToParts(JSContext *ctx, JSValueConst this_val int argc, JSValueConst *argv) { JSListFormatData *s = JS_GetOpaque2(ctx, this_val, js_listformat_class_id); - JSValue arr = JS_UNDEFINED; JSValue ret = JS_UNDEFINED; StringList list; - int64_t len = 0, i; - const char **items = NULL; if (!s) return JS_EXCEPTION; - arr = string_list_from_iterable(ctx, argv[0]); - if (JS_IsException(arr)) + if (string_list_from_iterable(ctx, &list, argv[0])) return JS_EXCEPTION; - { - JSValue len_val = JS_GetPropertyStr(ctx, arr, "length"); - if (JS_IsException(len_val)) - goto fail; - if (JS_ToInt64(ctx, &len, len_val) < 0) { - JS_FreeValue(ctx, len_val); - goto fail; - } - JS_FreeValue(ctx, len_val); - } - - { - size_t alloc_count = (len > 0) ? (size_t)len : 1; - items = js_malloc(ctx, sizeof(*items) * alloc_count); - } - if (!items) - goto fail; - - for (i = 0; i < len; i++) { - JSValue val; - const char *str; - val = JS_GetPropertyUint32(ctx, arr, (uint32_t)i); - if (JS_IsException(val)) { - JS_FreeValue(ctx, val); - goto fail; - } - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto fail; - items[i] = str; - } - - list.items = items; - list.count = (int)len; - ret = js_listformat_format_to_parts(ctx, s, &list); - fail: - if (items) { - for (i = 0; i < len; i++) { - if (items[i]) - JS_FreeCString(ctx, items[i]); - } - js_free(ctx, items); - } - JS_FreeValue(ctx, arr); + free_string_list(ctx, &list); return ret; } -- 2.47.3