From f96080bf82d98ce04d1aa6e6d0c424a5c88decd7 Mon Sep 17 00:00:00 2001 From: Nick Vatamaniuc Date: Mon, 23 Mar 2026 17:23:40 -0400 Subject: [PATCH] Update QuickJS: bytecode size checks, memory fixes, test262 * Test262 updated: 83341 -> 83394 tests pass * Added JSON.parse source text access https://github.com/bellard/quickjs/commit/d7ae12ae71dfd6ab2997527d295014a8996fa0f9 * Added basic protection against too large function in serialized bytecode https://github.com/bellard/quickjs/commit/a31dcef98c7d1d8927871ac169a1c24ad8875046 Fixes: * Use-after-free via re-entrant GC in FinalizationRegistry weak ref cleanup https://github.com/bellard/quickjs/commit/5022f2b76ab0e0c1f535224c796bef7bb1ab8e7d * Async generator lifecycle bug https://github.com/bellard/quickjs/commit/e7b9f217b46d567aed203918ba0c36f234d2bd3f * `RegExp.escape` https://github.com/bellard/quickjs/commit/4d16546cdf60cdec670ea6f033253140f861ba55 * Buffer overflow in Atomics with resizable typed arrays https://github.com/bellard/quickjs/commit/46bd985b337b1a66597e30c38dd45c8e1f49feee * Memory leak in Iterator.prototype.map https://github.com/bellard/quickjs/commit/f1b63fc1ad0b62262c069b2e1615737b2bb63631 * Stack underflow with generator in iterable https://github.com/bellard/quickjs/commit/69090b969fa9d01deeb05011a2bf8537ddf61266 * TypedArray constructor semantics which removes a buffer overflow https://github.com/bellard/quickjs/commit/68caa5f226401a7e148fb8f7358d4688c71372aa * Buffer overflow when the fast array is resized https://github.com/bellard/quickjs/commit/0989d4cb4a1e26f12ba1d65e3d5963342211b1dd * GC traversal of uninitialized fast-array https://github.com/bellard/quickjs/commit/4c722cea4e709682003eb0da67b393ea38c56e5e --- .../patches/01-spidermonkey-185-mode.patch | 6 +- .../patches/02-test262-errors.patch | 4 +- src/couch_quickjs/quickjs/Makefile | 2 +- src/couch_quickjs/quickjs/quickjs-atom.h | 1 + src/couch_quickjs/quickjs/quickjs.c | 771 +++++++++++++----- src/couch_quickjs/quickjs/test262.conf | 6 +- src/couch_quickjs/quickjs/test262_errors.txt | 6 - 7 files changed, 579 insertions(+), 217 deletions(-) diff --git a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch index db740bf2bf6..65f4738568a 100644 --- a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch +++ b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch @@ -1,6 +1,6 @@ ---- quickjs-master/quickjs.c 2025-12-22 09:12:46 -+++ quickjs/quickjs.c 2026-01-04 23:55:00 -@@ -31443,10 +31443,24 @@ +--- quickjs-master/quickjs.c 2026-03-23 13:45:52 ++++ quickjs/quickjs.c 2026-03-23 17:19:56 +@@ -31462,10 +31462,24 @@ if (s->token.val == TOK_FUNCTION || (token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, TRUE) == TOK_FUNCTION)) { diff --git a/src/couch_quickjs/patches/02-test262-errors.patch b/src/couch_quickjs/patches/02-test262-errors.patch index 81646c9003f..db454f201df 100644 --- a/src/couch_quickjs/patches/02-test262-errors.patch +++ b/src/couch_quickjs/patches/02-test262-errors.patch @@ -1,5 +1,5 @@ ---- quickjs-master/test262_errors.txt 2025-12-22 09:12:46 -+++ quickjs/test262_errors.txt 2026-01-04 23:55:00 +--- quickjs-master/test262_errors.txt 2026-03-23 13:45:52 ++++ quickjs/test262_errors.txt 2026-03-23 17:19:56 @@ -23,6 +23,8 @@ test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from-and-import-star-as-and-export.js:74: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-import-star-as-and-export.js' is ambiguous test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js:75: SyntaxError: export 'foo' in module 'test262/test/language/module-code/ambiguous-export-bindings/namespace-unambiguous-if-export-star-as-from.js' is ambiguous diff --git a/src/couch_quickjs/quickjs/Makefile b/src/couch_quickjs/quickjs/Makefile index dcbbf7e5e23..2182368266b 100644 --- a/src/couch_quickjs/quickjs/Makefile +++ b/src/couch_quickjs/quickjs/Makefile @@ -55,7 +55,7 @@ PREFIX?=/usr/local #CONFIG_UBSAN=y # TEST262 bootstrap config: commit id and shallow "since" parameter -TEST262_COMMIT?=d0994d64b07cb6c164dd9f345c94ed797a53d69f +TEST262_COMMIT?=5c8206929d81b2d3d727ca6aac56c18358c8d790 TEST262_SINCE?=2025-09-01 OBJDIR=.obj diff --git a/src/couch_quickjs/quickjs/quickjs-atom.h b/src/couch_quickjs/quickjs/quickjs-atom.h index dac2df67879..dc64f2fb028 100644 --- a/src/couch_quickjs/quickjs/quickjs-atom.h +++ b/src/couch_quickjs/quickjs/quickjs-atom.h @@ -147,6 +147,7 @@ DEF(flags, "flags") DEF(global, "global") DEF(unicode, "unicode") DEF(raw, "raw") +DEF(rawJSON, "rawJSON") DEF(new_target, "new.target") DEF(this_active_func, "this.active_func") DEF(home_object, "") diff --git a/src/couch_quickjs/quickjs/quickjs.c b/src/couch_quickjs/quickjs/quickjs.c index 6cad52d76c5..972f9787bdd 100644 --- a/src/couch_quickjs/quickjs/quickjs.c +++ b/src/couch_quickjs/quickjs/quickjs.c @@ -168,6 +168,7 @@ enum { JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ JS_CLASS_GENERATOR, /* u.generator_data */ JS_CLASS_GLOBAL_OBJECT, /* u.global_object */ + JS_CLASS_RAWJSON, JS_CLASS_PROXY, /* u.proxy_data */ JS_CLASS_PROMISE, /* u.promise_data */ JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ @@ -1597,6 +1598,7 @@ static JSClassShortDef const js_std_class_def[] = { { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ { JS_ATOM_Object, js_global_object_finalizer, js_global_object_mark }, /* JS_CLASS_GLOBAL_OBJECT */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_RAWJSON */ }; static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, @@ -1841,15 +1843,17 @@ int JS_GetStripInfo(JSRuntime *rt) return rt->strip_flags; } -/* return 0 if OK, < 0 if exception */ -int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, - int argc, JSValueConst *argv) +static int JS_EnqueueJob2(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv, BOOL no_exception) { JSRuntime *rt = ctx->rt; JSJobEntry *e; int i; - e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (no_exception) + e = js_malloc_rt(ctx->rt, sizeof(*e) + argc * sizeof(JSValue)); + else + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); if (!e) return -1; e->realm = JS_DupContext(ctx); @@ -1862,6 +1866,13 @@ int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, return 0; } +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValueConst *argv) +{ + return JS_EnqueueJob2(ctx, job_func, argc, argv, FALSE); +} + BOOL JS_IsJobPending(JSRuntime *rt) { return !list_empty(&rt->job_list); @@ -9153,14 +9164,15 @@ static inline int add_fast_array_element(JSContext *ctx, JSObject *p, return TRUE; } -/* Allocate a new fast array. Its 'length' property is set to zero. It - maximum size is 2^31-1 elements. For convenience, 'len' is a 64 bit - integer. WARNING: the content of the array is not initialized. */ +/* Allocate a new fast array initialized to JS_UNDEFINED. Its maximum + size is 2^31-1 elements. For convenience, 'len' is a 64 bit + integer. */ static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) { JSValue arr; JSObject *p; - + int i; + if (len > INT32_MAX) return JS_ThrowRangeError(ctx, "invalid array length"); arr = JS_NewArray(ctx); @@ -9173,6 +9185,10 @@ static JSValue js_allocate_fast_array(JSContext *ctx, int64_t len) return JS_EXCEPTION; } p->u.array.count = len; + for(i = 0; i < len; i++) + p->u.array.u.values[i] = JS_UNDEFINED; + /* update the 'length' field */ + set_value(ctx, &p->prop[0].u.value, JS_NewInt32(ctx, len)); } return arr; } @@ -21100,9 +21116,8 @@ static JSValue js_async_generator_resolve_function(JSContext *ctx, } else { js_async_generator_resolve(ctx, s, arg, TRUE); } - } else { + } else if (s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING) { /* restart function execution after await() */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); s->func_state->throw_flag = is_reject; if (is_reject) { JS_Throw(ctx, JS_DupValue(ctx, arg)); @@ -21558,7 +21573,7 @@ typedef struct JSParseState { JSFunctionDef *cur_func; BOOL is_module; /* parsing a module */ BOOL allow_html_comments; - BOOL ext_json; /* true if accepting JSON superset */ + BOOL ext_json; /* JSON parsing: true if accepting JSON superset */ GetLineColCache get_line_col_cache; } JSParseState; @@ -28191,8 +28206,7 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, JS_FreeAtom(ctx, var_name); if (token_is_pseudo_keyword(s, JS_ATOM_of)) { - break_entry.has_iterator = is_for_of = TRUE; - break_entry.drop_count += 2; + is_for_of = TRUE; if (has_initializer) goto initializer_error; } else if (s->token.val == TOK_IN) { @@ -28221,6 +28235,11 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name, the TDZ values are in the closures */ close_scopes(s, s->cur_func->scope_level, block_scope_level); if (is_for_of) { + /* set has_iterator after the iterable expression is parsed so + that a yield in the expression does not try to close a + not-yet-created iterator */ + break_entry.has_iterator = TRUE; + break_entry.drop_count += 2; if (is_async) emit_op(s, OP_for_await_of_start); else @@ -28774,7 +28793,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, if (js_parse_expect(s, '}')) goto fail; if (default_label_pos >= 0) { - /* Ugly patch for the the `default` label, shameful and risky */ + /* Ugly patch for the `default` label, shameful and risky */ put_u32(s->cur_func->byte_code.buf + default_label_pos, label_case); s->cur_func->label_slots[label_case].pos = default_label_pos + 4; @@ -38160,9 +38179,10 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) uint16_t v16; uint8_t v8; int idx, i, local_count; - int function_size, cpool_offset, byte_code_offset; + int cpool_offset, byte_code_offset; int closure_var_offset, vardefs_offset; - + uint64_t function_size; + memset(&bc, 0, sizeof(bc)); bc.header.ref_count = 1; //bc.gc_header.mark = 0; @@ -38212,16 +38232,19 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) function_size = offsetof(JSFunctionBytecode, debug); } cpool_offset = function_size; - function_size += bc.cpool_count * sizeof(*bc.cpool); + function_size += (uint64_t)bc.cpool_count * sizeof(*bc.cpool); vardefs_offset = function_size; - function_size += local_count * sizeof(*bc.vardefs); + function_size += (uint64_t)local_count * sizeof(*bc.vardefs); closure_var_offset = function_size; - function_size += bc.closure_var_count * sizeof(*bc.closure_var); + function_size += (uint64_t)bc.closure_var_count * sizeof(*bc.closure_var); byte_code_offset = function_size; if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; } + if (function_size > INT32_MAX) + return JS_ThrowOutOfMemory(ctx); + b = js_mallocz(ctx, function_size); if (!b) return JS_EXCEPTION; @@ -41409,21 +41432,14 @@ static JSValue js_array_with(JSContext *ctx, JSValueConst this_val, } else { for (; i < idx; i++, pval++) if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) - goto fill_and_fail; + goto exception; *pval = JS_DupValue(ctx, argv[1]); for (i++, pval++; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - fill_and_fail: - for (; i < len; i++, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; - ret = arr; arr = JS_UNDEFINED; @@ -42307,17 +42323,10 @@ static JSValue js_array_toReversed(JSContext *ctx, JSValueConst this_val, } else { // Query order is observable; test262 expects descending order. for (; i >= 0; i--, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - // Exception; initialize remaining elements. - for (; i >= 0; i--, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; } ret = arr; @@ -42506,17 +42515,11 @@ static JSValue js_array_toSpliced(JSContext *ctx, JSValueConst this_val, assert(pval == last); - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, newlen)) < 0) - goto exception; - done: ret = arr; arr = JS_UNDEFINED; exception: - while (pval != last) - *pval++ = JS_UNDEFINED; - JS_FreeValue(ctx, arr); JS_FreeValue(ctx, obj); return ret; @@ -42861,16 +42864,10 @@ static JSValue js_array_toSorted(JSContext *ctx, JSValueConst this_val, *pval = JS_DupValue(ctx, arrp[i]); } else { for (; i < len; i++, pval++) { - if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { - for (; i < len; i++, pval++) - *pval = JS_UNDEFINED; + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) goto exception; - } } } - - if (JS_SetProperty(ctx, arr, JS_ATOM_length, JS_NewInt64(ctx, len)) < 0) - goto exception; } ret = js_array_sort(ctx, arr, argc, argv); @@ -43991,6 +43988,7 @@ static JSValue js_iterator_helper_next(JSContext *ctx, JSValueConst this_val, args[1] = index_val; ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); JS_FreeValue(ctx, index_val); + JS_FreeValue(ctx, item); if (JS_IsException(ret)) goto fail; goto done; @@ -47348,9 +47346,10 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, JSValue str; StringBuffer b_s, *b = &b_s; JSString *p; - uint32_t c, i; + uint32_t c; char s[16]; - + int i, i0; + if (!JS_IsString(argv[0])) return JS_ThrowTypeError(ctx, "not a string"); str = JS_ToString(ctx, argv[0]); /* must call it to linearlize ropes */ @@ -47358,8 +47357,9 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; p = JS_VALUE_GET_STRING(str); string_buffer_init2(ctx, b, 0, p->is_wide_char); - for (i = 0; i < p->len; i++) { - c = string_get(p, i); + for (i = 0; i < p->len; ) { + i0 = i; + c = string_getc(p, &i); if (c < 33) { if (c >= 9 && c <= 13) { string_buffer_putc8(b, '\\'); @@ -47371,7 +47371,7 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { - if (i == 0) + if (i0 == 0) goto hex2; } else if (strchr(",-=<>#&!%:;@~'`\"", c)) { goto hex2; @@ -47387,7 +47387,7 @@ static JSValue js_regexp_escape(JSContext *ctx, JSValueConst this_val, snprintf(s, sizeof(s), "\\u%04x", c); string_buffer_puts8(b, s); } else { - string_buffer_putc16(b, c); + string_buffer_putc(b, c); } } JS_FreeValue(ctx, str); @@ -48645,23 +48645,189 @@ static int json_parse_expect(JSParseState *s, int tok) return json_next_token(s); } -static JSValue json_parse_value(JSParseState *s) + +typedef struct { + int count; + uint32_t hash_size; + struct JSONParseRecordEntry *entries; + uint32_t *hash_table; +} JSONParseRecordObject; + +typedef struct JSONParseRecord { + JSValue value; + union { + JSONParseRecordObject obj; + struct { + int count; + struct JSONParseRecord *elements; + } array; + struct { + uint32_t source_pos; + uint32_t source_len; + } primitive; + } u; +} JSONParseRecord; + +typedef struct JSONParseRecordEntry { + JSAtom atom; + uint32_t hash_next; + JSONParseRecord parse_record; +} JSONParseRecordEntry; + +static void json_parse_record_init_obj(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.obj.count = 0; + pr->u.obj.entries = NULL; + pr->u.obj.hash_table = NULL; + pr->u.obj.hash_size = 0; +} + +static void json_parse_record_init_array(JSContext *ctx, JSONParseRecord *pr, JSValueConst val) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.array.count = 0; + pr->u.array.elements = NULL; +} + +static void json_parse_record_init_primitive(JSContext *ctx, JSONParseRecord *pr, JSValueConst val, + uint32_t source_pos, uint32_t source_len) +{ + pr->value = JS_DupValue(ctx, val); + pr->u.primitive.source_pos = source_pos; + pr->u.primitive.source_len = source_len; +} + +static int json_parse_record_resize_hash(JSContext *ctx, JSONParseRecordObject *po, uint32_t new_hash_size) +{ + uint32_t i, h, *new_hash_table; + JSONParseRecordEntry *e; + + new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + js_free(ctx, po->hash_table); + po->hash_table = new_hash_table; + po->hash_size = new_hash_size; + + for(i = 0; i < po->hash_size; i++) { + po->hash_table[i] = -1; + } + for(i = 0; i < po->count; i++) { + e = &po->entries[i]; + h = e->atom & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = i; + } + return 0; +} + +static JSONParseRecord *json_parse_record_add(JSContext *ctx, JSONParseRecord *pr, JSAtom key, int *psize) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + JSONParseRecord *pr1; + uint32_t h; + + if (js_resize_array(ctx, (void **)&po->entries, sizeof(po->entries[0]), + psize, po->count + 1)) { + return NULL; + } + /* don't use a hash table when the number of entries is small */ + if (po->count >= 8 && (po->count + 1) > po->hash_size) { + int hash_bits = 32 - clz32(po->count); + if (json_parse_record_resize_hash(ctx, po, 1 << hash_bits)) + return NULL; + } + + e = &po->entries[po->count++]; + e->atom = JS_DupAtom(ctx, key); + pr1 = &e->parse_record; + pr1->value = JS_UNDEFINED; + if (po->hash_size != 0) { + h = key & (po->hash_size - 1); + e->hash_next = po->hash_table[h]; + po->hash_table[h] = po->count - 1; + } + return pr1; +} + +static JSONParseRecord *json_parse_record_find(JSONParseRecord *pr, JSAtom key) +{ + JSONParseRecordObject *po = &pr->u.obj; + JSONParseRecordEntry *e; + uint32_t h, i; + + if (po->hash_size == 0) { + for(i = 0; i < po->count; i++) { + if (po->entries[i].atom == key) + return &po->entries[i].parse_record; + } + } else { + h = key & (po->hash_size - 1); + i = po->hash_table[h]; + while (i != -1) { + e = &po->entries[i]; + if (e->atom == key) + return &e->parse_record; + i = e->hash_next; + } + } + return NULL; +} + +static void json_free_parse_record(JSContext *ctx, JSONParseRecord *pr) +{ + int i; + if (!pr) + return; + if (JS_IsObject(pr->value)) { + if (JS_IsArray(ctx, pr->value)) { + for(i = 0; i < pr->u.array.count; i++) { + json_free_parse_record(ctx, &pr->u.array.elements[i]); + } + js_free(ctx, pr->u.array.elements); + } else { + for(i = 0; i < pr->u.obj.count; i++) { + JS_FreeAtom(ctx, pr->u.obj.entries[i].atom); + json_free_parse_record(ctx, &pr->u.obj.entries[i].parse_record); + } + js_free(ctx, pr->u.obj.entries); + js_free(ctx, pr->u.obj.hash_table); + } + } + JS_FreeValue(ctx, pr->value); + pr->value = JS_UNDEFINED; /* fail safe */ +} + +/* 'pr' can be NULL */ +static JSValue json_parse_value(JSParseState *s, JSONParseRecord *pr) { JSContext *ctx = s->ctx; JSValue val = JS_NULL; int ret; + if (pr) { + pr->value = JS_UNDEFINED; + } + switch(s->token.val) { case '{': { JSValue prop_val; JSAtom prop_name; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewObject(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_obj(ctx, pr, val); + pr_size = 0; + } if (s->token.val != '}') { for(;;) { if (s->token.val == TOK_STRING) { @@ -48678,7 +48844,14 @@ static JSValue json_parse_value(JSParseState *s) goto fail1; if (json_parse_expect(s, ':')) goto fail1; - prop_val = json_parse_value(s); + if (pr) { + pr1 = json_parse_record_add(ctx, pr, prop_name, &pr_size); + if (!pr1) + goto fail1; + } else { + pr1 = NULL; + } + prop_val = json_parse_value(s, pr1); if (JS_IsException(prop_val)) { fail1: JS_FreeAtom(ctx, prop_name); @@ -48706,16 +48879,31 @@ static JSValue json_parse_value(JSParseState *s) { JSValue el; uint32_t idx; - + JSONParseRecord *pr1; + int pr_size; + if (json_next_token(s)) goto fail; val = JS_NewArray(ctx); if (JS_IsException(val)) goto fail; + if (pr) { + json_parse_record_init_array(ctx, pr, val); + pr_size = 0; + } if (s->token.val != ']') { idx = 0; for(;;) { - el = json_parse_value(s); + if (pr) { + if (js_resize_array(ctx, (void **)&pr->u.array.elements, sizeof(pr->u.array.elements[0]), + &pr_size, pr->u.array.count + 1)) + goto fail; + pr1 = &pr->u.array.elements[pr->u.array.count++]; + pr1->value = JS_UNDEFINED; + } else { + pr1 = NULL; + } + el = json_parse_value(s, pr1); if (JS_IsException(el)) goto fail; ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E); @@ -48736,11 +48924,21 @@ static JSValue json_parse_value(JSParseState *s) break; case TOK_STRING: val = JS_DupValue(ctx, s->token.u.str.str); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; case TOK_NUMBER: val = s->token.u.num.val; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } if (json_next_token(s)) goto fail; break; @@ -48748,8 +48946,18 @@ static JSValue json_parse_value(JSParseState *s) if (s->token.u.ident.atom == JS_ATOM_false || s->token.u.ident.atom == JS_ATOM_true) { val = JS_NewBool(ctx, s->token.u.ident.atom == JS_ATOM_true); + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_null) { val = JS_NULL; + if (pr) { + json_parse_record_init_primitive(ctx, pr, val, + s->token.ptr - s->buf_start, + s->buf_ptr - s->token.ptr); + } } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { /* Note: json5 identifier handling is ambiguous e.g. is '{ NaN: 1 }' a valid JSON5 production ? */ @@ -48774,12 +48982,13 @@ static JSValue json_parse_value(JSParseState *s) } return val; fail: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, val); return JS_EXCEPTION; } -JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, - const char *filename, int flags) +JSValue JS_ParseJSON3(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags, JSONParseRecord *pr) { JSParseState s1, *s = &s1; JSValue val = JS_UNDEFINED; @@ -48788,12 +48997,14 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); if (json_next_token(s)) goto fail; - val = json_parse_value(s); + val = json_parse_value(s, pr); if (JS_IsException(val)) goto fail; if (s->token.val != TOK_EOF) { - if (js_parse_error(s, "unexpected data at the end")) + if (js_parse_error(s, "unexpected data at the end")) { + json_free_parse_record(ctx, pr); goto fail; + } } return val; fail: @@ -48802,17 +49013,25 @@ JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, return JS_EXCEPTION; } +JSValue JS_ParseJSON2(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename, int flags) +{ + return JS_ParseJSON3(ctx, buf, buf_len, filename, flags, NULL); +} + JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { - return JS_ParseJSON2(ctx, buf, buf_len, filename, 0); + return JS_ParseJSON3(ctx, buf, buf_len, filename, 0, NULL); } +/* if pr != NULL, then pr->value = holder by construction */ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, - JSAtom name, JSValueConst reviver) + JSAtom name, JSValueConst reviver, + const char *text_str, JSONParseRecord *pr) { - JSValue val, new_el, name_val, res; - JSValueConst args[2]; + JSValue val, new_el, name_val, res, context; + JSValueConst args[3]; int ret, is_array; uint32_t i, len = 0; JSAtom prop; @@ -48825,6 +49044,29 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, val = JS_GetProperty(ctx, holder, name); if (JS_IsException(val)) return val; + + if (pr) { + if (JS_IsArray(ctx, pr->value)) { + if (__JS_AtomIsTaggedInt(name)) { + uint32_t idx = __JS_AtomToUInt32(name); + if (idx < pr->u.array.count) { + pr = &pr->u.array.elements[idx]; + } else { + pr = NULL; + } + } + } else { + pr = json_parse_record_find(pr, name); + } + if (pr && !js_same_value(ctx, pr->value, val)) { + pr = NULL; + } + } + + context = JS_NewObject(ctx); + if (JS_IsException(context)) + goto fail; + if (JS_IsObject(val)) { is_array = JS_IsArray(ctx, val); if (is_array < 0) @@ -48845,7 +49087,7 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, } else { prop = JS_DupAtom(ctx, atoms[i].atom); } - new_el = internalize_json_property(ctx, val, prop, reviver); + new_el = internalize_json_property(ctx, val, prop, reviver, text_str, pr); if (JS_IsException(new_el)) { JS_FreeAtom(ctx, prop); goto fail; @@ -48859,6 +49101,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, if (ret < 0) goto fail; } + } else { + if (pr) { + new_el = JS_NewStringLen(ctx, text_str + pr->u.primitive.source_pos, + pr->u.primitive.source_len); + if (JS_IsException(new_el)) + goto fail; + if (JS_DefinePropertyValue(ctx, context, JS_ATOM_source, new_el, JS_PROP_C_W_E) < 0) + goto fail; + } } JS_FreePropertyEnum(ctx, atoms, len); atoms = NULL; @@ -48867,12 +49118,15 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, goto fail; args[0] = name_val; args[1] = val; - res = JS_Call(ctx, reviver, holder, 2, args); + args[2] = context; + res = JS_Call(ctx, reviver, holder, 3, args); JS_FreeValue(ctx, name_val); JS_FreeValue(ctx, val); + JS_FreeValue(ctx, context); return res; fail: JS_FreePropertyEnum(ctx, atoms, len); + JS_FreeValue(ctx, context); JS_FreeValue(ctx, val); return JS_EXCEPTION; } @@ -48880,37 +49134,113 @@ static JSValue internalize_json_property(JSContext *ctx, JSValueConst holder, static JSValue js_json_parse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - JSValue obj, root; - JSValueConst reviver; + JSValue obj; const char *str; size_t len; - + str = JS_ToCStringLen(ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; - obj = JS_ParseJSON(ctx, str, len, ""); - JS_FreeCString(ctx, str); - if (JS_IsException(obj)) - return obj; if (argc > 1 && JS_IsFunction(ctx, argv[1])) { + JSONParseRecord pr_s, *pr = &pr_s, *pr1; + JSValue root; + JSValueConst reviver; + int size; + reviver = argv[1]; root = JS_NewObject(ctx); - if (JS_IsException(root)) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (JS_IsException(root)) + goto fail; + json_parse_record_init_obj(ctx, pr, root); + size = 0; + pr1 = json_parse_record_add(ctx, pr, JS_ATOM_empty_string, &size); + if (!pr1) + goto fail1; + + obj = JS_ParseJSON3(ctx, str, len, "", 0, pr1); + if (JS_IsException(obj)) + goto fail1; + if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, obj); + fail1: + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); - return JS_EXCEPTION; + goto fail; } + obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, - reviver); + reviver, str, pr); + json_free_parse_record(ctx, pr); JS_FreeValue(ctx, root); + } else { + obj = JS_ParseJSON3(ctx, str, len, "", 0, NULL); + } + JS_FreeCString(ctx, str); + return obj; + fail: + JS_FreeCString(ctx, str); + return JS_EXCEPTION; +} + +static JSValue js_json_isRawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + return JS_NewBool(ctx, p->class_id == JS_CLASS_RAWJSON); + } else { + return JS_FALSE; + } +} + +static BOOL is_valid_raw_json_char(int c) +{ + return ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '"'); +} + +static JSValue js_json_rawJSON(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue str, res, obj; + JSString *p; + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + if (p->len == 0 || + !is_valid_raw_json_char(string_get(p, 0)) || + !is_valid_raw_json_char(string_get(p, p->len - 1))) { + goto syntax_error; + } + res = js_json_parse(ctx, JS_UNDEFINED, 1, (JSValueConst *)&str); + if (JS_IsException(res)) { + syntax_error: + JS_ThrowSyntaxError(ctx, "invalid rawJSON string"); + goto fail; + } + JS_FreeValue(ctx, res); + + obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_RAWJSON); + if (JS_IsException(obj)) + goto fail; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_rawJSON, str, JS_PROP_ENUMERABLE) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } + JS_PreventExtensions(ctx, obj); return obj; + fail: + JS_FreeValue(ctx, str); + return JS_EXCEPTION; } + typedef struct JSONStringifyContext { JSValueConst replacer_func; JSValue stack; @@ -49080,11 +49410,18 @@ static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, if (JS_IsException(val)) goto exception; goto concat_primitive; - } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) - { + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) { /* This will thow the same error as for the primitive object */ set_value(ctx, &val, JS_DupValue(ctx, p->u.object_data)); goto concat_primitive; + } else if (cl == JS_CLASS_RAWJSON) { + JSValue val1; + val1 = JS_GetProperty(ctx, val, JS_ATOM_rawJSON); + if (JS_IsException(val1)) + goto exception; + JS_FreeValue(ctx, val); + val = val1; + goto concat_value; } v = js_array_includes(ctx, jsc->stack, 1, (JSValueConst *)&val); if (JS_IsException(v)) @@ -49373,7 +49710,9 @@ static JSValue js_json_stringify(JSContext *ctx, JSValueConst this_val, } static const JSCFunctionListEntry js_json_funcs[] = { + JS_CFUNC_DEF("isRawJSON", 1, js_json_isRawJSON ), JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("rawJSON", 1, js_json_rawJSON ), JS_CFUNC_DEF("stringify", 3, js_json_stringify ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ), }; @@ -56635,6 +56974,8 @@ static JSValue js_typed_array_with(JSContext *ctx, JSValueConst this_val, if (typed_array_is_oob(p) || idx < 0 || idx >= p->u.array.count) return JS_ThrowRangeError(ctx, "invalid array index"); + /* warning: 'this_val' may have been resized, so 'len' may be + larger than its length */ arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, p->class_id, len); if (JS_IsException(arr)) { @@ -57711,7 +58052,7 @@ static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { struct TA_sort_context { JSContext *ctx; int exception; /* 1 = exception, 2 = detached typed array */ - JSValueConst arr; + uint8_t *array; JSValueConst cmp; JSValue (*getfun)(JSContext *ctx, const void *a); int elt_size; @@ -57724,7 +58065,6 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { JSValueConst argv[2]; JSValue res; int cmp; - JSObject *p; cmp = 0; if (!psc->exception) { @@ -57732,15 +58072,9 @@ static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { error */ a_idx = *(uint32_t *)a; b_idx = *(uint32_t *)b; - p = JS_VALUE_GET_PTR(psc->arr); - if (a_idx >= p->u.array.count || b_idx >= p->u.array.count) { - /* OOB case */ - psc->exception = 2; - return 0; - } - argv[0] = psc->getfun(ctx, p->u.array.u.uint8_ptr + + argv[0] = psc->getfun(ctx, psc->array + a_idx * (size_t)psc->elt_size); - argv[1] = psc->getfun(ctx, p->u.array.u.uint8_ptr + + argv[1] = psc->getfun(ctx, psc->array + b_idx * (size_t)(psc->elt_size)); res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); if (JS_IsException(res)) { @@ -57781,7 +58115,6 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, tsc.ctx = ctx; tsc.exception = 0; - tsc.arr = this_val; tsc.cmp = argv[0]; if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) @@ -57844,65 +58177,69 @@ static JSValue js_typed_array_sort(JSContext *ctx, JSValueConst this_val, elt_size = 1 << typed_array_size_log2(p->class_id); if (!JS_IsUndefined(tsc.cmp)) { uint32_t *array_idx; - void *array_tmp; + void *array; size_t i, j; - /* XXX: a stable sort would use less memory */ + /* the array must be copied because the comparison + function may modify it */ + array = js_malloc(ctx, len * elt_size); + if (!array) + return JS_EXCEPTION; + memcpy(array, p->u.array.u.ptr, len * elt_size); + + /* array_idx is needed to have a stable sort */ array_idx = js_malloc(ctx, len * sizeof(array_idx[0])); - if (!array_idx) + if (!array_idx) { + js_free(ctx, array); return JS_EXCEPTION; + } for(i = 0; i < len; i++) array_idx[i] = i; tsc.elt_size = elt_size; + tsc.array = array; rqsort(array_idx, len, sizeof(array_idx[0]), js_TA_cmp_generic, &tsc); if (tsc.exception) { - if (tsc.exception == 1) - goto fail; + if (tsc.exception == 1) { + js_free(ctx, array_idx); + js_free(ctx, array); + return JS_EXCEPTION; + } /* detached typed array during the sort: no error */ } else { void *array_ptr = p->u.array.u.ptr; len = min_int(len, p->u.array.count); - if (len != 0) { - array_tmp = js_malloc(ctx, len * elt_size); - if (!array_tmp) { - fail: - js_free(ctx, array_idx); - return JS_EXCEPTION; + switch(elt_size) { + case 1: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint8_t *)array_ptr)[i] = ((uint8_t *)array)[j]; } - memcpy(array_tmp, array_ptr, len * elt_size); - switch(elt_size) { - case 1: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint8_t *)array_ptr)[i] = ((uint8_t *)array_tmp)[j]; - } - break; - case 2: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint16_t *)array_ptr)[i] = ((uint16_t *)array_tmp)[j]; - } - break; - case 4: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint32_t *)array_ptr)[i] = ((uint32_t *)array_tmp)[j]; - } - break; - case 8: - for(i = 0; i < len; i++) { - j = array_idx[i]; - ((uint64_t *)array_ptr)[i] = ((uint64_t *)array_tmp)[j]; - } - break; - default: - abort(); + break; + case 2: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint16_t *)array_ptr)[i] = ((uint16_t *)array)[j]; + } + break; + case 4: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint32_t *)array_ptr)[i] = ((uint32_t *)array)[j]; + } + break; + case 8: + for(i = 0; i < len; i++) { + j = array_idx[i]; + ((uint64_t *)array_ptr)[i] = ((uint64_t *)array)[j]; } - js_free(ctx, array_tmp); + break; + default: + abort(); } } js_free(ctx, array_idx); + js_free(ctx, array); } else { rqsort(p->u.array.u.ptr, len, elt_size, cmpfun, &tsc); if (tsc.exception) @@ -58135,9 +58472,6 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, JS_ThrowTypeErrorArrayBufferOOB(ctx); goto fail; } - ta = p->u.typed_array; - src_buffer = ta->buffer; - src_abuf = src_buffer->u.array_buffer; size_log2 = typed_array_size_log2(classid); buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, (uint64_t)len << size_log2, @@ -58153,8 +58487,12 @@ static JSValue js_typed_array_constructor_ta(JSContext *ctx, abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); if (typed_array_init(ctx, obj, buffer, 0, len, /*track_rab*/FALSE)) goto fail; - if (p->class_id == classid) { - /* same type: copy the content */ + ta = p->u.typed_array; + src_buffer = ta->buffer; + src_abuf = src_buffer->u.array_buffer; + if (p->class_id == classid && + (int64_t)ta->offset + (int64_t)abuf->byte_length <= src_abuf->byte_length) { + /* same type and no overflow: copy the content */ memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length); } else { for(i = 0; i < len; i++) { @@ -58187,38 +58525,54 @@ static JSValue js_typed_array_constructor(JSContext *ctx, if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) { if (JS_ToIndex(ctx, &len, argv[0])) return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) + return JS_EXCEPTION; buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, len << size_log2, NULL); if (JS_IsException(buffer)) - return JS_EXCEPTION; + goto fail; offset = 0; } else { JSObject *p = JS_VALUE_GET_OBJ(argv[0]); if (p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) { - abuf = p->u.array_buffer; - if (JS_ToIndex(ctx, &offset, argv[1])) + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) return JS_EXCEPTION; - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - if ((offset & ((1 << size_log2) - 1)) != 0 || - offset > abuf->byte_length) - return JS_ThrowRangeError(ctx, "invalid offset"); + if (JS_ToIndex(ctx, &offset, argv[1])) + goto fail; + if ((offset & ((1 << size_log2) - 1)) != 0) + goto invalid_offset; + abuf = p->u.array_buffer; if (JS_IsUndefined(argv[2])) { + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + if (offset > abuf->byte_length) { + invalid_offset: + JS_ThrowRangeError(ctx, "invalid offset"); + goto fail; + } track_rab = array_buffer_is_resizable(abuf); - if (!track_rab) + if (!track_rab) { if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) goto invalid_length; + } len = (abuf->byte_length - offset) >> size_log2; } else { if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } if ((offset + (len << size_log2)) > abuf->byte_length) { invalid_length: - return JS_ThrowRangeError(ctx, "invalid length"); + JS_ThrowRangeError(ctx, "invalid length"); + goto fail; } } buffer = JS_DupValue(ctx, argv[0]); @@ -58232,17 +58586,12 @@ static JSValue js_typed_array_constructor(JSContext *ctx, } } } - - obj = js_create_from_ctor(ctx, new_target, classid); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, buffer); - return JS_EXCEPTION; - } - if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } + if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) + goto fail; return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; } static void js_typed_array_finalizer(JSRuntime *rt, JSValue val) @@ -58671,9 +59020,8 @@ typedef enum AtomicsOpEnum { ATOMICS_OP_LOAD, } AtomicsOpEnum; -static int js_atomics_get_ptr(JSContext *ctx, - void **pptr, - JSArrayBuffer **pabuf, +static int js_atomics_get_ptr(JSContext *ctx, void **pptr, + JSObject **pobj, uint64_t *pidx, int *psize_log2, JSClassID *pclass_id, JSValueConst obj, JSValueConst idx_val, int is_waitable) @@ -58740,13 +59088,16 @@ static int js_atomics_get_ptr(JSContext *ctx, size_log2 = typed_array_size_log2(p->class_id); ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); - if (pabuf) - *pabuf = abuf; + + *pptr = ptr; + if (pobj) + *pobj = p; + if (pidx) + *pidx = idx; if (psize_log2) *psize_log2 = size_log2; if (pclass_id) *pclass_id = p->class_id; - *pptr = ptr; return 0; } @@ -58755,13 +59106,13 @@ static JSValue js_atomics_op(JSContext *ctx, int argc, JSValueConst *argv, int op) { int size_log2; - uint64_t v, a, rep_val; + uint64_t v, a, rep_val, idx; void *ptr; JSValue ret; JSClassID class_id; - JSArrayBuffer *abuf; - - if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, &class_id, + JSObject *p; + + if (js_atomics_get_ptr(ctx, &ptr, &p, &idx, &size_log2, &class_id, argv[0], argv[1], 0)) return JS_EXCEPTION; rep_val = 0; @@ -58789,8 +59140,10 @@ static JSValue js_atomics_op(JSContext *ctx, rep_val = v32; } } - if (abuf->detached) + if (typed_array_is_oob(p)) return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (idx >= p->u.array.count) + return JS_ThrowRangeError(ctx, "out-of-bound access"); } switch(op | (size_log2 << 3)) { @@ -58901,48 +59254,53 @@ static JSValue js_atomics_store(JSContext *ctx, int size_log2; void *ptr; JSValue ret; - JSArrayBuffer *abuf; - - if (js_atomics_get_ptr(ctx, &ptr, &abuf, &size_log2, NULL, + JSObject *p; + uint64_t idx; + int64_t v; + + if (js_atomics_get_ptr(ctx, &ptr, &p, &idx, &size_log2, NULL, argv[0], argv[1], 0)) return JS_EXCEPTION; if (size_log2 == 3) { - int64_t v64; ret = JS_ToBigIntFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; - if (JS_ToBigInt64(ctx, &v64, ret)) { + if (JS_ToBigInt64(ctx, &v, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - atomic_store((_Atomic(uint64_t) *)ptr, v64); } else { - uint32_t v; + uint32_t v32; /* XXX: spec, would be simpler to return the written value */ ret = JS_ToIntegerFree(ctx, JS_DupValue(ctx, argv[2])); if (JS_IsException(ret)) return ret; - if (JS_ToUint32(ctx, &v, ret)) { + if (JS_ToUint32(ctx, &v32, ret)) { JS_FreeValue(ctx, ret); return JS_EXCEPTION; } - if (abuf->detached) - return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); - switch(size_log2) { - case 0: - atomic_store((_Atomic(uint8_t) *)ptr, v); - break; - case 1: - atomic_store((_Atomic(uint16_t) *)ptr, v); - break; - case 2: - atomic_store((_Atomic(uint32_t) *)ptr, v); - break; - default: - abort(); - } + v = v32; + } + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (idx >= p->u.array.count) + return JS_ThrowRangeError(ctx, "out-of-bound access"); + + switch(size_log2) { + case 0: + atomic_store((_Atomic(uint8_t) *)ptr, v); + break; + case 1: + atomic_store((_Atomic(uint16_t) *)ptr, v); + break; + case 2: + atomic_store((_Atomic(uint32_t) *)ptr, v); + break; + case 3: + atomic_store((_Atomic(uint64_t) *)ptr, v); + break; + default: + abort(); } return ret; } @@ -59025,9 +59383,10 @@ static JSValue js_atomics_wait(JSContext *ctx, int ret, size_log2, res; double d; - if (js_atomics_get_ptr(ctx, &ptr, NULL, &size_log2, NULL, + if (js_atomics_get_ptr(ctx, &ptr, NULL, NULL, &size_log2, NULL, argv[0], argv[1], 2)) return JS_EXCEPTION; + /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ if (size_log2 == 3) { if (JS_ToBigInt64(ctx, &v, argv[2])) return JS_EXCEPTION; @@ -59103,8 +59462,9 @@ static JSValue js_atomics_notify(JSContext *ctx, void *ptr; JSAtomicsWaiter *waiter; JSArrayBuffer *abuf; - - if (js_atomics_get_ptr(ctx, &ptr, &abuf, NULL, NULL, argv[0], argv[1], 1)) + JSObject *p; + + if (js_atomics_get_ptr(ctx, &ptr, &p, NULL, NULL, NULL, argv[0], argv[1], 1)) return JS_EXCEPTION; if (JS_IsUndefined(argv[2])) { @@ -59115,7 +59475,9 @@ static JSValue js_atomics_notify(JSContext *ctx, } n = 0; + abuf = p->u.typed_array->buffer->u.array_buffer; if (abuf->shared && count > 0) { + /* 'argv[0]' is a SharedArrayBuffer so it cannot be detached nor reduced */ pthread_mutex_lock(&js_atomics_mutex); init_list_head(&waiter_list); list_for_each_safe(el, el1, &js_atomics_waiter_list) { @@ -59404,7 +59766,8 @@ static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) JSValueConst args[2]; args[0] = frd->cb; args[1] = fre->held_val; - JS_EnqueueJob(frd->realm, js_finrec_job, 2, args); + /* no exception is raised to avoid recursing into the GC */ + JS_EnqueueJob2(frd->realm, js_finrec_job, 2, args, TRUE); js_weakref_free(rt, fre->target); js_weakref_free(rt, fre->token); diff --git a/src/couch_quickjs/quickjs/test262.conf b/src/couch_quickjs/quickjs/test262.conf index aa76e639510..ab8b0a5bd30 100644 --- a/src/couch_quickjs/quickjs/test262.conf +++ b/src/couch_quickjs/quickjs/test262.conf @@ -71,6 +71,7 @@ async-iteration Atomics Atomics.pause Atomics.waitAsync=skip +await-dictionary=skip BigInt caller canonical-tz=skip @@ -119,7 +120,9 @@ hashbang host-gc-required immutable-arraybuffer=skip import-attributes +import-bytes=skip import-defer=skip +import-text=skip import.meta joint-iteration=skip Int16Array @@ -135,6 +138,7 @@ Intl.DateTimeFormat-fractionalSecondDigits=skip Intl.DisplayNames-v2=skip Intl.DisplayNames=skip Intl.DurationFormat=skip +Intl.Era-monthcode=skip Intl.ListFormat=skip Intl.Locale-info=skip Intl.Locale=skip @@ -146,7 +150,7 @@ IsHTMLDDA iterator-helpers iterator-sequencing json-modules -json-parse-with-source=skip +json-parse-with-source json-superset legacy-regexp=skip let diff --git a/src/couch_quickjs/quickjs/test262_errors.txt b/src/couch_quickjs/quickjs/test262_errors.txt index 796c296e2af..4d6d0bd3e8e 100644 --- a/src/couch_quickjs/quickjs/test262_errors.txt +++ b/src/couch_quickjs/quickjs/test262_errors.txt @@ -33,14 +33,8 @@ test262/test/staging/sm/Function/function-name-for.js:13: Test262Error: Expected test262/test/staging/sm/Function/implicit-this-in-parameter-expression.js:12: Test262Error: Expected SameValue(«[object Object]», «undefined») to be true test262/test/staging/sm/Function/invalid-parameter-list.js:13: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/Function/invalid-parameter-list.js:13: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all -test262/test/staging/sm/String/string-upper-lower-mapping.js:16: Test262Error: Expected SameValue(«"꟏"», «"꟎"») to be true -test262/test/staging/sm/String/string-upper-lower-mapping.js:16: strict mode: Test262Error: Expected SameValue(«"꟏"», «"꟎"») to be true -test262/test/staging/sm/TypedArray/constructor-buffer-sequence.js:29: Test262Error: Expected a ExpectedError but got a Error -test262/test/staging/sm/TypedArray/constructor-buffer-sequence.js:29: strict mode: Test262Error: Expected a ExpectedError but got a Error test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: Test262Error: Expected SameValue(«2», «6») to be true test262/test/staging/sm/TypedArray/prototype-constructor-identity.js:17: strict mode: Test262Error: Expected SameValue(«2», «6») to be true -test262/test/staging/sm/TypedArray/sort_modifications.js:9: Test262Error: Int8Array at index 0 for size 4 Expected SameValue(«0», «1») to be true -test262/test/staging/sm/TypedArray/sort_modifications.js:9: strict mode: Test262Error: Int8Array at index 0 for size 4 Expected SameValue(«0», «1») to be true test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/async-contains-unicode-escape.js:11: strict mode: Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all test262/test/staging/sm/async-functions/await-in-arrow-parameters.js:10: Test262Error: AsyncFunction:(a = (b = await/r/g) => {}) => {} Expected a SyntaxError to be thrown but no exception was thrown at all