This is an automated email from the ASF dual-hosted git repository. vatamane pushed a commit to branch bring-in-quickjs-fixes in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 606f8a6dbeac32b2967962d78202cc7d793b42b5 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Mon Apr 7 18:39:15 2025 -0400 Bring in latest QuickJS fixes * break statement with labels fixes [1] * fix buffer overflow in bjson string + bigint reader [2] * bigint fixes [3] [4] * weak map gc fix [5] * segfault on large number of properties [6] * undefined is a valid variable name in global scope [7] [1] https://github.com/bellard/quickjs/issues/275 [2] https://github.com/bellard/quickjs/issues/399 [3] https://github.com/bellard/quickjs/commit/083b7bab01 [4] https://github.com/bellard/quickjs/commit/fa706d5622 [5] https://github.com/bellard/quickjs/pull/393 [6] https://github.com/bellard/quickjs/issues/111 [7] https://github.com/bellard/quickjs/issues/370 --- .../patches/01-spidermonkey-185-mode.patch | 6 +- src/couch_quickjs/quickjs/Changelog | 8 + src/couch_quickjs/quickjs/Makefile | 1 + src/couch_quickjs/quickjs/quickjs-atom.h | 2 + src/couch_quickjs/quickjs/quickjs-libc.c | 18 +- src/couch_quickjs/quickjs/quickjs.c | 821 +++++++++++++++++---- src/couch_quickjs/quickjs/quickjs.h | 6 +- src/couch_quickjs/quickjs/test262.conf | 6 +- 8 files changed, 697 insertions(+), 171 deletions(-) diff --git a/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch b/src/couch_quickjs/patches/01-spidermonkey-185-mode.patch index ce4ebfaca..4951f7a44 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-03-31 07:37:37 -+++ quickjs/quickjs.c 2025-04-03 11:48:29 -@@ -29117,10 +29117,24 @@ +--- quickjs-master/quickjs.c 2025-04-07 13:01:30 ++++ quickjs/quickjs.c 2025-04-07 18:37:55 +@@ -29256,10 +29256,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/quickjs/Changelog b/src/couch_quickjs/quickjs/Changelog index dd099cde5..77805c084 100644 --- a/src/couch_quickjs/quickjs/Changelog +++ b/src/couch_quickjs/quickjs/Changelog @@ -1,3 +1,11 @@ +- removed the bignum extensions and qjscalc +- new BigInt implementation optimized for small numbers +- added WeakRef, FinalizationRegistry and symbols as weakrefs +- added builtin float64 printing and parsing functions for more correctness +- faster repeated string concatenation +- qjs: promise unhandled rejections are fatal errors by default +- misc bug fixes + 2024-01-13: - top-level-await support in modules diff --git a/src/couch_quickjs/quickjs/Makefile b/src/couch_quickjs/quickjs/Makefile index 3655b29de..a3abc9855 100644 --- a/src/couch_quickjs/quickjs/Makefile +++ b/src/couch_quickjs/quickjs/Makefile @@ -435,6 +435,7 @@ test: qjs ./qjs tests/test_bigint.js ./qjs tests/test_std.js ./qjs tests/test_worker.js + ./qjs tests/test_cyclic_import.js ifdef CONFIG_SHARED_LIBS ./qjs tests/test_bjson.js ./qjs examples/test_point.js diff --git a/src/couch_quickjs/quickjs/quickjs-atom.h b/src/couch_quickjs/quickjs/quickjs-atom.h index 628588ec6..c3034b63d 100644 --- a/src/couch_quickjs/quickjs/quickjs-atom.h +++ b/src/couch_quickjs/quickjs/quickjs-atom.h @@ -210,6 +210,8 @@ DEF(Float32Array, "Float32Array") DEF(Float64Array, "Float64Array") DEF(DataView, "DataView") DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") DEF(Map, "Map") DEF(Set, "Set") /* Map + 1 */ DEF(WeakMap, "WeakMap") /* Map + 2 */ diff --git a/src/couch_quickjs/quickjs/quickjs-libc.c b/src/couch_quickjs/quickjs/quickjs-libc.c index fd5d412aa..2c7113248 100644 --- a/src/couch_quickjs/quickjs/quickjs-libc.c +++ b/src/couch_quickjs/quickjs/quickjs-libc.c @@ -3825,9 +3825,18 @@ static JSValue js_print(JSContext *ctx, JSValueConst this_val, return JS_UNDEFINED; } +static JSValue js_console_log(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret; + ret = js_print(ctx, this_val, argc, argv); + fflush(stdout); + return ret; +} + void js_std_add_helpers(JSContext *ctx, int argc, char **argv) { - JSValue global_obj, console, args; + JSValue global_obj, console, args, performance; int i; /* XXX: should these global definitions be enumerable? */ @@ -3835,9 +3844,14 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) console = JS_NewObject(ctx); JS_SetPropertyStr(ctx, console, "log", - JS_NewCFunction(ctx, js_print, "log", 1)); + JS_NewCFunction(ctx, js_console_log, "log", 1)); JS_SetPropertyStr(ctx, global_obj, "console", console); + performance = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, performance, "now", + JS_NewCFunction(ctx, js_os_now, "now", 0)); + JS_SetPropertyStr(ctx, global_obj, "performance", performance); + /* same methods as the mozilla JS shell */ if (argc >= 0) { args = JS_NewArray(ctx); diff --git a/src/couch_quickjs/quickjs/quickjs.c b/src/couch_quickjs/quickjs/quickjs.c index 864096f05..1e8ddfa2e 100644 --- a/src/couch_quickjs/quickjs/quickjs.c +++ b/src/couch_quickjs/quickjs/quickjs.c @@ -171,7 +171,9 @@ enum { JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ - + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ }; @@ -193,7 +195,9 @@ typedef enum JSErrorEnum { JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ } JSErrorEnum; -#define JS_MAX_LOCAL_VARS 65535 +/* the variable and scope indexes must fit on 16 bits. The (-1) and + ARG_SCOPE_END values are reserved. */ +#define JS_MAX_LOCAL_VARS 65534 #define JS_STACK_SIZE_MAX 65534 #define JS_STRING_LEN_MAX ((1 << 30) - 1) @@ -249,6 +253,7 @@ struct JSRuntime { struct list_head tmp_obj_list; /* used during GC */ JSGCPhaseEnum gc_phase : 8; size_t malloc_gc_threshold; + struct list_head weakref_list; /* list of JSWeakRefHeader.link */ #ifdef DUMP_LEAKS struct list_head string_list; /* list of JSString.link */ #endif @@ -340,6 +345,17 @@ struct JSGCObjectHeader { struct list_head link; }; +typedef enum { + JS_WEAKREF_TYPE_MAP, + JS_WEAKREF_TYPE_WEAKREF, + JS_WEAKREF_TYPE_FINREC, +} JSWeakRefHeaderTypeEnum; + +typedef struct { + struct list_head link; + JSWeakRefHeaderTypeEnum weakref_type; +} JSWeakRefHeader; + typedef struct JSVarRef { union { JSGCObjectHeader header; /* must come first */ @@ -465,11 +481,6 @@ enum { JS_ATOM_TYPE_PRIVATE, }; -enum { - JS_ATOM_HASH_SYMBOL, - JS_ATOM_HASH_PRIVATE, -}; - typedef enum { JS_ATOM_KIND_STRING, JS_ATOM_KIND_SYMBOL, @@ -477,13 +488,14 @@ typedef enum { } JSAtomKindEnum; #define JS_ATOM_HASH_MASK ((1 << 30) - 1) +#define JS_ATOM_HASH_PRIVATE JS_ATOM_HASH_MASK struct JSString { JSRefCountHeader header; /* must come first, 32-bit */ uint32_t len : 31; uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ - /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, - for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + /* for JS_ATOM_TYPE_SYMBOL: hash = weakref_count, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = JS_ATOM_HASH_PRIVATE, atom_type = 3 XXX: could change encoding to have one more bit in hash */ uint32_t hash : 30; uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ @@ -905,12 +917,12 @@ struct JSObject { uint16_t class_id; /* see JS_CLASS_x */ }; }; - /* byte offsets: 16/24 */ + /* count the number of weak references to this object. The object + structure is freed only if header.ref_count = 0 and + weakref_count = 0 */ + uint32_t weakref_count; JSShape *shape; /* prototype and property names + flag */ JSProperty *prop; /* array of properties */ - /* byte offsets: 24/40 */ - struct JSMapRecord *first_weak_ref; /* XXX: use a bit and an external hash table? */ - /* byte offsets: 28/48 */ union { void *opaque; struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ @@ -967,7 +979,6 @@ struct JSObject { JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ } u; - /* byte sizes: 40/48/72 */ }; enum { @@ -1151,7 +1162,6 @@ static int JS_CreateProperty(JSContext *ctx, JSObject *p, int flags); static int js_string_memcmp(const JSString *p1, int pos1, const JSString *p2, int pos2, int len); -static void reset_weak_ref(JSRuntime *rt, JSObject *p); static JSValue js_array_buffer_constructor3(JSContext *ctx, JSValueConst new_target, uint64_t len, JSClassID class_id, @@ -1246,6 +1256,10 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); static JSValue js_object_groupBy(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int is_map); +static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh); +static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh); +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects); static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; @@ -1546,6 +1560,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) init_list_head(&rt->gc_obj_list); init_list_head(&rt->gc_zero_ref_count_list); rt->gc_phase = JS_GC_PHASE_NONE; + init_list_head(&rt->weakref_list); #ifdef DUMP_LEAKS init_list_head(&rt->string_list); @@ -1846,7 +1861,9 @@ void JS_FreeRuntime(JSRuntime *rt) } init_list_head(&rt->job_list); - JS_RunGC(rt); + /* don't remove the weak objects to avoid create new jobs with + FinalizationRegistry */ + JS_RunGCInternal(rt, FALSE); #ifdef DUMP_LEAKS /* leaking objects */ @@ -1888,6 +1905,7 @@ void JS_FreeRuntime(JSRuntime *rt) } #endif assert(list_empty(&rt->gc_obj_list)); + assert(list_empty(&rt->weakref_list)); /* free the classes */ for(i = 0; i < rt->class_count; i++) { @@ -1932,7 +1950,7 @@ void JS_FreeRuntime(JSRuntime *rt) printf(")"); break; case JS_ATOM_TYPE_SYMBOL: - if (p->hash == JS_ATOM_HASH_SYMBOL) { + if (p->hash != JS_ATOM_HASH_PRIVATE) { printf("Symbol("); JS_DumpString(rt, p); printf(")"); @@ -2063,6 +2081,7 @@ JSContext *JS_NewContext(JSRuntime *rt) JS_AddIntrinsicMapSet(ctx); JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); + JS_AddIntrinsicWeakRef(ctx); return ctx; } @@ -2540,14 +2559,10 @@ static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) case JS_ATOM_TYPE_GLOBAL_SYMBOL: return JS_ATOM_KIND_SYMBOL; case JS_ATOM_TYPE_SYMBOL: - switch(p->hash) { - case JS_ATOM_HASH_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_HASH_PRIVATE: + if (p->hash == JS_ATOM_HASH_PRIVATE) return JS_ATOM_KIND_PRIVATE; - default: - abort(); - } + else + return JS_ATOM_KIND_SYMBOL; default: abort(); } @@ -2617,7 +2632,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) } else { h1 = 0; /* avoid warning */ if (atom_type == JS_ATOM_TYPE_SYMBOL) { - h = JS_ATOM_HASH_SYMBOL; + h = 0; } else { h = JS_ATOM_HASH_PRIVATE; atom_type = JS_ATOM_TYPE_SYMBOL; @@ -2810,7 +2825,13 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) #ifdef DUMP_LEAKS list_del(&p->link); #endif - js_free_rt(rt, p); + if (p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE && p->hash != 0) { + /* live weak references are still present on this object: keep + it */ + } else { + js_free_rt(rt, p); + } rt->atom_count--; assert(rt->atom_count >= 0); } @@ -3155,7 +3176,7 @@ static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) return FALSE; p = rt->atom_array[v]; return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && - p->hash == JS_ATOM_HASH_SYMBOL) || + p->hash != JS_ATOM_HASH_PRIVATE) || p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && !(p->len == 0 && p->is_wide_char != 0)); } @@ -5043,7 +5064,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->is_uncatchable_error = 0; p->tmp_mark = 0; p->is_HTMLDDA = 0; - p->first_weak_ref = NULL; + p->weakref_count = 0; p->u.opaque = NULL; p->shape = sh; p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); @@ -5721,10 +5742,6 @@ static void free_object(JSRuntime *rt, JSObject *p) p->shape = NULL; p->prop = NULL; - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, p); - } - finalizer = rt->class_array[p->class_id].finalizer; if (finalizer) (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); @@ -5736,10 +5753,21 @@ static void free_object(JSRuntime *rt, JSObject *p) p->u.func.home_object = NULL; remove_gc_object(&p->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { - list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES) { + if (p->header.ref_count == 0 && p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + /* keep the object structure because there are may be + references to it */ + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } } else { - js_free_rt(rt, p); + /* keep the object structure in case there are weak references to it */ + if (p->weakref_count == 0) { + js_free_rt(rt, p); + } else { + p->header.mark = 0; /* reset the mark so that the weakref can be freed */ + } } } @@ -5824,6 +5852,7 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { list_del(&p->link); list_add(&p->link, &rt->gc_zero_ref_count_list); + p->mark = 1; /* indicate that the object is about to be freed */ if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount(rt); } @@ -5846,7 +5875,6 @@ void __JS_FreeValueRT(JSRuntime *rt, JSValue v) } break; default: - printf("__JS_FreeValue: unknown tag=%d\n", tag); abort(); } } @@ -5858,6 +5886,36 @@ void __JS_FreeValue(JSContext *ctx, JSValue v) /* garbage collection */ +static void gc_remove_weak_objects(JSRuntime *rt) +{ + struct list_head *el; + + /* add the freed objects to rt->gc_zero_ref_count_list so that + rt->weakref_list is not modified while we traverse it */ + rt->gc_phase = JS_GC_PHASE_DECREF; + + list_for_each(el, &rt->weakref_list) { + JSWeakRefHeader *wh = list_entry(el, JSWeakRefHeader, link); + switch(wh->weakref_type) { + case JS_WEAKREF_TYPE_MAP: + map_delete_weakrefs(rt, wh); + break; + case JS_WEAKREF_TYPE_WEAKREF: + weakref_delete_weakref(rt, wh); + break; + case JS_WEAKREF_TYPE_FINREC: + finrec_delete_weakref(rt, wh); + break; + default: + abort(); + } + } + + rt->gc_phase = JS_GC_PHASE_NONE; + /* free the freed objects here. */ + free_zero_refcount(rt); +} + static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, JSGCObjectTypeEnum type) { @@ -6108,14 +6166,27 @@ static void gc_free_cycles(JSRuntime *rt) assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE || p->gc_obj_type == JS_GC_OBJ_TYPE_ASYNC_FUNCTION); - js_free_rt(rt, p); + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT && + ((JSObject *)p)->weakref_count != 0) { + /* keep the object because there are weak references to it */ + p->mark = 0; + } else { + js_free_rt(rt, p); + } } init_list_head(&rt->gc_zero_ref_count_list); } -void JS_RunGC(JSRuntime *rt) +static void JS_RunGCInternal(JSRuntime *rt, BOOL remove_weak_objects) { + if (remove_weak_objects) { + /* free the weakly referenced object or symbol structures, delete + the associated Map/Set entries and queue the finalization + registry callbacks. */ + gc_remove_weak_objects(rt); + } + /* decrement the reference of the children of each object. mark = 1 after this pass. */ gc_decref(rt); @@ -6127,6 +6198,11 @@ void JS_RunGC(JSRuntime *rt) gc_free_cycles(rt); } +void JS_RunGC(JSRuntime *rt) +{ + JS_RunGCInternal(rt, TRUE); +} + /* Return false if not an object or if the object has already been freed (zombie objects are visible in finalizers when freeing cycles). */ @@ -7352,12 +7428,14 @@ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, JSValue val; JSContext *realm; JSAutoInitFunc *func; - + JSAutoInitIDEnum id; + if (js_shape_prepare_update(ctx, p, &prs)) return -1; realm = js_autoinit_get_realm(pr); - func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + id = js_autoinit_get_id(pr); + func = js_autoinit_func_table[id]; /* 'func' shall not modify the object properties 'pr' */ val = func(realm, p, prop, pr->u.init.opaque); js_autoinit_free(ctx->rt, pr); @@ -7365,7 +7443,15 @@ static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, pr->u.value = JS_UNDEFINED; if (JS_IsException(val)) return -1; - pr->u.value = val; + if (id == JS_AUTOINIT_ID_MODULE_NS && + JS_VALUE_GET_TAG(val) == JS_TAG_STRING) { + /* WARNING: a varref is returned as a string ! */ + prs->flags |= JS_PROP_VARREF; + pr->u.var_ref = JS_VALUE_GET_PTR(val); + pr->u.var_ref->header.ref_count++; + } else { + pr->u.value = val; + } return 0; } @@ -7860,7 +7946,21 @@ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, /* fill them */ - atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + atom_count = num_keys_count + str_keys_count; + if (atom_count < str_keys_count) + goto add_overflow; + atom_count += sym_keys_count; + if (atom_count < sym_keys_count) + goto add_overflow; + atom_count += exotic_keys_count; + if (atom_count < exotic_keys_count || atom_count > INT32_MAX) { + add_overflow: + JS_ThrowOutOfMemory(ctx); + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + /* XXX: need generic way to test for js_malloc(ctx, a * b) overflow */ + /* avoid allocating 0 bytes */ tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); if (!tab_atom) { @@ -10108,6 +10208,18 @@ void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id) return p; } +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; + return p->u.opaque; +} + static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) { int i; @@ -13297,6 +13409,7 @@ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, switch(op) { case OP_plus: JS_ThrowTypeError(ctx, "bigint argument with unary +"); + JS_FreeValue(ctx, op1); goto exception; case OP_inc: case OP_dec: @@ -13756,7 +13869,7 @@ static no_inline __exception int js_binary_logic_slow(JSContext *ctx, goto bigint_sar; } bigint_shl: - vd = (js_sdlimb_t)v1 << v2; + vd = (js_dlimb_t)v1 << v2; if (likely(vd >= JS_SHORT_BIG_INT_MIN && vd <= JS_SHORT_BIG_INT_MAX)) { v = vd; @@ -16612,7 +16725,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst obj; int scope_idx; call_argc = get_u16(pc); - scope_idx = get_u16(pc + 2) - 1; + scope_idx = get_u16(pc + 2) + ARG_SCOPE_END; pc += 4; call_argv = sp - call_argc; sf->cur_pc = pc; @@ -16643,7 +16756,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValue *tab; JSValueConst obj; - scope_idx = get_u16(pc) - 1; + scope_idx = get_u16(pc) + ARG_SCOPE_END; pc += 2; tab = build_arg_list(ctx, &len, sp[-1]); if (!tab) @@ -19681,7 +19794,8 @@ typedef struct BlockEnv { int drop_count; /* number of stack elements to drop */ int label_finally; /* -1 if none */ int scope_level; - int has_iterator; + uint8_t has_iterator : 1; + uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ } BlockEnv; typedef struct JSGlobalVar { @@ -23655,7 +23769,7 @@ static __exception int js_define_var(JSParseState *s, JSAtom name, int tok) && (fd->js_mode & JS_MODE_STRICT)) { return js_parse_error(s, "invalid variable name in strict mode"); } - if ((name == JS_ATOM_let || name == JS_ATOM_undefined) + if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) { return js_parse_error(s, "invalid lexical variable name"); } @@ -25650,6 +25764,7 @@ static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, be->label_finally = -1; be->scope_level = fd->scope_level; be->has_iterator = FALSE; + be->is_regular_stmt = FALSE; } static void pop_break_entry(JSFunctionDef *fd) @@ -25678,7 +25793,8 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) } if (!is_cont && top->label_break != -1 && - (name == JS_ATOM_NULL || top->label_name == name)) { + ((name == JS_ATOM_NULL && !top->is_regular_stmt) || + top->label_name == name)) { emit_goto(s, OP_goto, top->label_break); return 0; } @@ -26242,6 +26358,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s, label_break = new_label(s); push_break_entry(s->cur_func, &break_entry, label_name, label_break, -1, 0); + break_entry.is_regular_stmt = TRUE; if (!(s->cur_func->js_mode & JS_MODE_STRICT) && (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) { mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL; @@ -27510,7 +27627,7 @@ static void js_resolve_export_throw_error(JSContext *ctx, typedef enum { EXPORTED_NAME_AMBIGUOUS, EXPORTED_NAME_NORMAL, - EXPORTED_NAME_NS, + EXPORTED_NAME_DELAYED, } ExportedNameEntryEnum; typedef struct ExportedNameEntry { @@ -27519,7 +27636,6 @@ typedef struct ExportedNameEntry { union { JSExportEntry *me; /* using when the list is built */ JSVarRef *var_ref; /* EXPORTED_NAME_NORMAL */ - JSModuleDef *module; /* for EXPORTED_NAME_NS */ } u; } ExportedNameEntry; @@ -27629,7 +27745,29 @@ static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) { JSModuleDef *m = opaque; - return JS_GetModuleNamespace(ctx, m); + JSResolveResultEnum res; + JSExportEntry *res_me; + JSModuleDef *res_m; + JSVarRef *var_ref; + + res = js_resolve_export(ctx, &res_m, &res_me, m, atom); + if (res != JS_RESOLVE_RES_FOUND) { + /* fail safe: normally no error should happen here except for memory */ + js_resolve_export_throw_error(ctx, res, m, atom); + return JS_EXCEPTION; + } + if (res_me->local_name == JS_ATOM__star_) { + return JS_GetModuleNamespace(ctx, res_m->req_module_entries[res_me->u.req_module_idx].module); + } else { + if (res_me->u.local.var_ref) { + var_ref = res_me->u.local.var_ref; + } else { + JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj); + var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; + } + /* WARNING: a varref is returned as a string ! */ + return JS_MKPTR(JS_TAG_STRING, var_ref); + } } static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) @@ -27674,17 +27812,18 @@ static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) en->export_type = EXPORTED_NAME_AMBIGUOUS; } else { if (res_me->local_name == JS_ATOM__star_) { - en->export_type = EXPORTED_NAME_NS; - en->u.module = res_m->req_module_entries[res_me->u.req_module_idx].module; + en->export_type = EXPORTED_NAME_DELAYED; } else { - en->export_type = EXPORTED_NAME_NORMAL; if (res_me->u.local.var_ref) { en->u.var_ref = res_me->u.local.var_ref; } else { JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj); - p1 = JS_VALUE_GET_OBJ(res_m->func_obj); en->u.var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; } + if (en->u.var_ref == NULL) + en->export_type = EXPORTED_NAME_DELAYED; + else + en->export_type = EXPORTED_NAME_NORMAL; } } } @@ -27708,13 +27847,13 @@ static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) pr->u.var_ref = var_ref; } break; - case EXPORTED_NAME_NS: - /* the exported namespace must be created on demand */ + case EXPORTED_NAME_DELAYED: + /* the exported namespace or reference may depend on + circular references, so we resolve it lazily */ if (JS_DefineAutoInitProperty(ctx, obj, en->export_name, JS_AUTOINIT_ID_MODULE_NS, - en->u.module, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) - goto fail; + m, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) break; default: break; @@ -31296,14 +31435,14 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); dbuf_put_u16(&bc_out, call_argc); - dbuf_put_u16(&bc_out, s->scopes[scope].first + 1); + dbuf_put_u16(&bc_out, s->scopes[scope].first - ARG_SCOPE_END); } break; case OP_apply_eval: /* convert scope index to adjusted variable index */ scope = get_u16(bc_buf + pos + 1); mark_eval_captured_variables(ctx, s, scope); dbuf_putc(&bc_out, op); - dbuf_put_u16(&bc_out, s->scopes[scope].first + 1); + dbuf_put_u16(&bc_out, s->scopes[scope].first - ARG_SCOPE_END); break; case OP_scope_get_var_checkthis: case OP_scope_get_var_undef: @@ -35455,6 +35594,10 @@ static JSString *JS_ReadString(BCReaderState *s) return NULL; is_wide_char = len & 1; len >>= 1; + if (len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(s->ctx, "string too long"); + return NULL; + } p = js_alloc_string(s->ctx, len, is_wide_char); if (!p) { s->error_state = -1; @@ -35566,8 +35709,7 @@ static JSValue JS_ReadBigInt(BCReaderState *s) bc_read_trace(s, "}\n"); return __JS_NewShortBigInt(s->ctx, 0); } - p = js_bigint_new(s->ctx, - (len + (JS_LIMB_BITS / 8) - 1) / (JS_LIMB_BITS / 8)); + p = js_bigint_new(s->ctx, (len - 1) / (JS_LIMB_BITS / 8) + 1); if (!p) goto fail; for(i = 0; i < len / (JS_LIMB_BITS / 8); i++) { @@ -46494,9 +46636,7 @@ static const JSCFunctionListEntry js_symbol_funcs[] = { typedef struct JSMapRecord { int ref_count; /* used during enumeration to avoid freeing the record */ - BOOL empty; /* TRUE if the record is deleted */ - struct JSMapState *map; - struct JSMapRecord *next_weak_ref; + BOOL empty : 8; /* TRUE if the record is deleted */ struct list_head link; struct JSMapRecord *hash_next; JSValue key; @@ -46508,11 +46648,86 @@ typedef struct JSMapState { struct list_head records; /* list of JSMapRecord.link */ uint32_t record_count; JSMapRecord **hash_table; - uint32_t hash_size; /* must be a power of two */ + int hash_bits; + uint32_t hash_size; /* = 2 ^ hash_bits */ uint32_t record_count_threshold; /* count at which a hash table resize is needed */ + JSWeakRefHeader weakref_header; /* only used if is_weak = TRUE */ } JSMapState; +static BOOL js_weakref_is_target(JSValueConst val) +{ + switch (JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + return TRUE; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + if (p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash != JS_ATOM_HASH_PRIVATE) + return TRUE; + } + break; + default: + break; + } + return FALSE; +} + +/* JS_UNDEFINED is considered as a live weakref */ +/* XXX: add a specific JSWeakRef value type ? */ +static BOOL js_weakref_is_live(JSValueConst val) +{ + int *pref_count; + if (JS_IsUndefined(val)) + return TRUE; + pref_count = JS_VALUE_GET_PTR(val); + return (*pref_count != 0); +} + +/* 'val' can be JS_UNDEFINED */ +static void js_weakref_free(JSRuntime *rt, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + assert(p->weakref_count >= 1); + p->weakref_count--; + /* 'mark' is tested to avoid freeing the object structure when + it is about to be freed in a cycle or in + free_zero_refcount() */ + if (p->weakref_count == 0 && p->header.ref_count == 0 && + p->header.mark == 0) { + js_free_rt(rt, p); + } + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { + JSString *p = JS_VALUE_GET_STRING(val); + assert(p->hash >= 1); + p->hash--; + if (p->hash == 0 && p->header.ref_count == 0) { + /* can remove the dummy structure */ + js_free_rt(rt, p); + } + } +} + +/* val must be an object, a symbol or undefined (see + js_weakref_is_target). */ +static JSValue js_weakref_new(JSContext *ctx, JSValueConst val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(val); + p->weakref_count++; + } else if (JS_VALUE_GET_TAG(val) == JS_TAG_SYMBOL) { + JSString *p = JS_VALUE_GET_STRING(val); + /* XXX: could return an exception if too many references */ + assert(p->hash < JS_ATOM_HASH_MASK - 2); + p->hash++; + } else { + assert(JS_IsUndefined(val)); + } + return (JSValue)val; +} + #define MAGIC_SET (1 << 0) #define MAGIC_WEAK (1 << 1) @@ -46534,8 +46749,13 @@ static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target, goto fail; init_list_head(&s->records); s->is_weak = is_weak; + if (is_weak) { + s->weakref_header.weakref_type = JS_WEAKREF_TYPE_MAP; + list_add_tail(&s->weakref_header.link, &ctx->rt->weakref_list); + } JS_SetOpaque(obj, s); - s->hash_size = 1; + s->hash_bits = 1; + s->hash_size = 1U << s->hash_bits; s->hash_table = js_mallocz(ctx, sizeof(s->hash_table[0]) * s->hash_size); if (!s->hash_table) goto fail; @@ -46634,29 +46854,53 @@ static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key) return key; } +/* hash multipliers, same as the Linux kernel (see Knuth vol 3, + section 6.4, exercise 9) */ +#define HASH_MUL32 0x61C88647 +#define HASH_MUL64 UINT64_C(0x61C8864680B583EB) + +static uint32_t map_hash32(uint32_t a, int hash_bits) +{ + return (a * HASH_MUL32) >> (32 - hash_bits); +} + +static uint32_t map_hash64(uint64_t a, int hash_bits) +{ + return (a * HASH_MUL64) >> (64 - hash_bits); +} + +static uint32_t map_hash_pointer(uintptr_t a, int hash_bits) +{ +#ifdef JS_PTR64 + return map_hash64(a, hash_bits); +#else + return map_hash32(a, hash_bits); +#endif +} + /* XXX: better hash ? */ -static uint32_t map_hash_key(JSValueConst key) +/* precondition: 1 <= hash_bits <= 32 */ +static uint32_t map_hash_key(JSValueConst key, int hash_bits) { uint32_t tag = JS_VALUE_GET_NORM_TAG(key); uint32_t h; double d; - JSFloat64Union u; JSBigInt *p; JSBigIntBuf buf; switch(tag) { case JS_TAG_BOOL: - h = JS_VALUE_GET_INT(key); + h = map_hash32(JS_VALUE_GET_INT(key) ^ JS_TAG_BOOL, hash_bits); break; case JS_TAG_STRING: - h = hash_string(JS_VALUE_GET_STRING(key), 0); + h = map_hash32(hash_string(JS_VALUE_GET_STRING(key), 0) ^ JS_TAG_STRING, hash_bits); break; case JS_TAG_STRING_ROPE: - h = hash_string_rope(key, 0); + h = map_hash32(hash_string_rope(key, 0) ^ JS_TAG_STRING, hash_bits); break; case JS_TAG_OBJECT: case JS_TAG_SYMBOL: - h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163; + h = map_hash_pointer((uintptr_t)JS_VALUE_GET_PTR(key) ^ tag, hash_bits); break; case JS_TAG_INT: d = JS_VALUE_GET_INT(key); @@ -46667,9 +46911,8 @@ static uint32_t map_hash_key(JSValueConst key) if (isnan(d)) d = JS_FLOAT64_NAN; hash_float64: - u.d = d; - h = (u.u32[0] ^ u.u32[1]) * 3163; - return h ^ JS_TAG_FLOAT64; + h = map_hash64(float64_as_uint64(d) ^ JS_TAG_FLOAT64, hash_bits); + break; case JS_TAG_SHORT_BIG_INT: p = js_bigint_set_short(&buf, key); goto hash_bigint; @@ -46682,14 +46925,15 @@ static uint32_t map_hash_key(JSValueConst key) for(i = p->len - 1; i >= 0; i--) { h = h * 263 + p->tab[i]; } - h *= 3163; + /* the final step is necessary otherwise h mod n only + depends of p->tab[i] mod n */ + h = map_hash32(h ^ JS_TAG_BIG_INT, hash_bits); } break; default: h = 0; break; } - h ^= tag; return h; } @@ -46698,10 +46942,14 @@ static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, { JSMapRecord *mr; uint32_t h; - h = map_hash_key(key) & (s->hash_size - 1); + h = map_hash_key(key, s->hash_bits); for(mr = s->hash_table[h]; mr != NULL; mr = mr->hash_next) { - if (js_same_value_zero(ctx, mr->key, key)) - return mr; + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + /* cannot match */ + } else { + if (js_same_value_zero(ctx, mr->key, key)) + return mr; + } } return NULL; } @@ -46709,17 +46957,15 @@ static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, static void map_hash_resize(JSContext *ctx, JSMapState *s) { uint32_t new_hash_size, h; - size_t slack; + int new_hash_bits; struct list_head *el; JSMapRecord *mr, **new_hash_table; /* XXX: no reporting of memory allocation failure */ - if (s->hash_size == 1) - new_hash_size = 4; - else - new_hash_size = s->hash_size * 2; - new_hash_table = js_realloc2(ctx, s->hash_table, - sizeof(new_hash_table[0]) * new_hash_size, &slack); + new_hash_bits = min_int(s->hash_bits + 1, 31); + new_hash_size = 1U << new_hash_bits; + new_hash_table = js_realloc(ctx, s->hash_table, + sizeof(new_hash_table[0]) * new_hash_size); if (!new_hash_table) return; @@ -46727,13 +46973,15 @@ static void map_hash_resize(JSContext *ctx, JSMapState *s) list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); - if (!mr->empty) { - h = map_hash_key(mr->key) & (new_hash_size - 1); + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + } else { + h = map_hash_key(mr->key, new_hash_bits); mr->hash_next = new_hash_table[h]; new_hash_table[h] = mr; } } s->hash_table = new_hash_table; + s->hash_bits = new_hash_bits; s->hash_size = new_hash_size; s->record_count_threshold = new_hash_size * 2; } @@ -46748,18 +46996,13 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s, if (!mr) return NULL; mr->ref_count = 1; - mr->map = s; mr->empty = FALSE; if (s->is_weak) { - JSObject *p = JS_VALUE_GET_OBJ(key); - /* Add the weak reference */ - mr->next_weak_ref = p->first_weak_ref; - p->first_weak_ref = mr; + mr->key = js_weakref_new(ctx, key); } else { - JS_DupValue(ctx, key); + mr->key = JS_DupValue(ctx, key); } - mr->key = (JSValue)key; - h = map_hash_key(key) & (s->hash_size - 1); + h = map_hash_key(key, s->hash_bits); mr->hash_next = s->hash_table[h]; s->hash_table[h] = mr; list_add_tail(&mr->link, &s->records); @@ -46770,27 +47013,6 @@ static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s, return mr; } -/* Remove the weak reference from the object weak - reference list. we don't use a doubly linked list to - save space, assuming a given object has few weak - references to it */ -static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr) -{ - JSMapRecord **pmr, *mr1; - JSObject *p; - - p = JS_VALUE_GET_OBJ(mr->key); - pmr = &p->first_weak_ref; - for(;;) { - mr1 = *pmr; - assert(mr1 != NULL); - if (mr1 == mr) - break; - pmr = &mr1->next_weak_ref; - } - *pmr = mr1->next_weak_ref; -} - /* warning: the record must be removed from the hash table before */ static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) { @@ -46798,7 +47020,7 @@ static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) return; if (s->is_weak) { - delete_weak_ref(rt, mr); + js_weakref_free(rt, mr->key); } else { JS_FreeValueRT(rt, mr->key); } @@ -46825,45 +47047,36 @@ static void map_decref_record(JSRuntime *rt, JSMapRecord *mr) } } -static void reset_weak_ref(JSRuntime *rt, JSObject *p) +static void map_delete_weakrefs(JSRuntime *rt, JSWeakRefHeader *wh) { - JSMapRecord *mr, *mr_next, **pmr, *mr1; - JSMapState *s; + JSMapState *s = container_of(wh, JSMapState, weakref_header); + struct list_head *el, *el1; + JSMapRecord *mr1, **pmr; uint32_t h; - JSValue key; - - /* first pass to remove the records from the WeakMap/WeakSet - lists */ - key = JS_MKPTR(JS_TAG_OBJECT, p); - for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) { - s = mr->map; - assert(s->is_weak); - assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ - - /* remove the record from hash table */ - h = map_hash_key(key) & (s->hash_size - 1); - pmr = &s->hash_table[h]; - for(;;) { - mr1 = *pmr; - assert(mr1 != NULL); - if (mr1 == mr) - break; - pmr = &mr1->hash_next; - } - *pmr = mr->hash_next; - - list_del(&mr->link); - } - /* second pass to free the values to avoid modifying the weak - reference list while traversing it. */ - for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) { - mr_next = mr->next_weak_ref; - JS_FreeValueRT(rt, mr->value); - js_free_rt(rt, mr); - } + list_for_each_safe(el, el1, &s->records) { + JSMapRecord *mr = list_entry(el, JSMapRecord, link); + if (!js_weakref_is_live(mr->key)) { - p->first_weak_ref = NULL; /* fail safe */ + /* even if key is not live it can be hashed as a pointer */ + h = map_hash_key(mr->key, s->hash_bits); + pmr = &s->hash_table[h]; + for(;;) { + mr1 = *pmr; + /* the entry may already be removed from the hash + table if the map was resized */ + if (mr1 == NULL) + goto done; + if (mr1 == mr) + break; + pmr = &mr1->hash_next; + } + /* remove from the hash table */ + *pmr = mr1->hash_next; + done: + map_delete_record(rt, s, mr); + } + } } static JSValue js_map_set(JSContext *ctx, JSValueConst this_val, @@ -46876,8 +47089,8 @@ static JSValue js_map_set(JSContext *ctx, JSValueConst this_val, if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); - if (s->is_weak && !JS_IsObject(key)) - return JS_ThrowTypeErrorNotAnObject(ctx); + if (s->is_weak && !js_weakref_is_target(key)) + return JS_ThrowTypeError(ctx, "invalid value used as %s key", (magic & MAGIC_SET) ? "WeakSet" : "WeakMap"); if (magic & MAGIC_SET) value = JS_UNDEFINED; else @@ -46937,14 +47150,18 @@ static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); - h = map_hash_key(key) & (s->hash_size - 1); + h = map_hash_key(key, s->hash_bits); pmr = &s->hash_table[h]; for(;;) { mr = *pmr; if (mr == NULL) return JS_FALSE; - if (js_same_value_zero(ctx, mr->key, key)) - break; + if (mr->empty || (s->is_weak && !js_weakref_is_live(mr->key))) { + /* not valid */ + } else { + if (js_same_value_zero(ctx, mr->key, key)) + break; + } pmr = &mr->hash_next; } @@ -47164,7 +47381,7 @@ static void js_map_finalizer(JSRuntime *rt, JSValue val) mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { if (s->is_weak) - delete_weak_ref(rt, mr); + js_weakref_free(rt, mr->key); else JS_FreeValueRT(rt, mr->key); JS_FreeValueRT(rt, mr->value); @@ -47172,6 +47389,9 @@ static void js_map_finalizer(JSRuntime *rt, JSValue val) js_free_rt(rt, mr); } js_free_rt(rt, s->hash_table); + if (s->is_weak) { + list_del(&s->weakref_header.link); + } js_free_rt(rt, s); } } @@ -53730,3 +53950,280 @@ void JS_AddIntrinsicTypedArrays(JSContext *ctx) JS_AddIntrinsicAtomics(ctx); #endif } + +/* WeakRef */ + +typedef struct JSWeakRefData { + JSWeakRefHeader weakref_header; + JSValue target; +} JSWeakRefData; + +static void js_weakref_finalizer(JSRuntime *rt, JSValue val) +{ + JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF); + if (!wrd) + return; + js_weakref_free(rt, wrd->target); + list_del(&wrd->weakref_header.link); + js_free_rt(rt, wrd); +} + +static void weakref_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) +{ + JSWeakRefData *wrd = container_of(wh, JSWeakRefData, weakref_header); + + if (!js_weakref_is_live(wrd->target)) { + js_weakref_free(rt, wrd->target); + wrd->target = JS_UNDEFINED; + } +} + +static JSValue js_weakref_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSValueConst arg; + JSValue obj; + + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + arg = argv[0]; + if (!js_weakref_is_target(arg)) + return JS_ThrowTypeError(ctx, "invalid target"); + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSWeakRefData *wrd = js_mallocz(ctx, sizeof(*wrd)); + if (!wrd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + wrd->target = js_weakref_new(ctx, arg); + wrd->weakref_header.weakref_type = JS_WEAKREF_TYPE_WEAKREF; + list_add_tail(&wrd->weakref_header.link, &ctx->rt->weakref_list); + JS_SetOpaque(obj, wrd); + return obj; +} + +static JSValue js_weakref_deref(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF); + if (!wrd) + return JS_EXCEPTION; + if (js_weakref_is_live(wrd->target)) + return JS_DupValue(ctx, wrd->target); + else + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry js_weakref_proto_funcs[] = { + JS_CFUNC_DEF("deref", 0, js_weakref_deref ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_weakref_class_def[] = { + { JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */ +}; + +typedef struct JSFinRecEntry { + struct list_head link; + JSValue target; + JSValue held_val; + JSValue token; +} JSFinRecEntry; + +typedef struct JSFinalizationRegistryData { + JSWeakRefHeader weakref_header; + struct list_head entries; /* list of JSFinRecEntry.link */ + JSContext *ctx; + JSValue cb; +} JSFinalizationRegistryData; + +static void js_finrec_finalizer(JSRuntime *rt, JSValue val) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + if (frd) { + struct list_head *el, *el1; + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + js_weakref_free(rt, fre->target); + js_weakref_free(rt, fre->token); + JS_FreeValueRT(rt, fre->held_val); + js_free_rt(rt, fre); + } + JS_FreeValueRT(rt, frd->cb); + list_del(&frd->weakref_header.link); + js_free_rt(rt, frd); + } +} + +static void js_finrec_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + struct list_head *el; + if (frd) { + list_for_each(el, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + JS_MarkValue(rt, fre->held_val, mark_func); + } + JS_MarkValue(rt, frd->cb, mark_func); + } +} + +static JSValue js_finrec_job(JSContext *ctx, int argc, JSValueConst *argv) +{ + return JS_Call(ctx, argv[0], JS_UNDEFINED, 1, &argv[1]); +} + +static void finrec_delete_weakref(JSRuntime *rt, JSWeakRefHeader *wh) +{ + JSFinalizationRegistryData *frd = container_of(wh, JSFinalizationRegistryData, weakref_header); + struct list_head *el, *el1; + + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + + if (!js_weakref_is_live(fre->token)) { + js_weakref_free(rt, fre->token); + fre->token = JS_UNDEFINED; + } + + if (!js_weakref_is_live(fre->target)) { + JSValueConst args[2]; + args[0] = frd->cb; + args[1] = fre->held_val; + JS_EnqueueJob(frd->ctx, js_finrec_job, 2, args); + + js_weakref_free(rt, fre->target); + js_weakref_free(rt, fre->token); + JS_FreeValueRT(rt, fre->held_val); + list_del(&fre->link); + js_free_rt(rt, fre); + } + } +} + +static JSValue js_finrec_constructor(JSContext *ctx, JSValueConst new_target, + int argc, JSValueConst *argv) +{ + JSValueConst cb; + JSValue obj; + JSFinalizationRegistryData *frd; + + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + cb = argv[0]; + if (!JS_IsFunction(ctx, cb)) + return JS_ThrowTypeError(ctx, "argument must be a function"); + + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_FINALIZATION_REGISTRY); + if (JS_IsException(obj)) + return JS_EXCEPTION; + frd = js_mallocz(ctx, sizeof(*frd)); + if (!frd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + frd->weakref_header.weakref_type = JS_WEAKREF_TYPE_FINREC; + list_add_tail(&frd->weakref_header.link, &ctx->rt->weakref_list); + init_list_head(&frd->entries); + frd->ctx = ctx; /* XXX: JS_DupContext() ? */ + frd->cb = JS_DupValue(ctx, cb); + JS_SetOpaque(obj, frd); + return obj; +} + +static JSValue js_finrec_register(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst target, held_val, token; + JSFinalizationRegistryData *frd; + JSFinRecEntry *fre; + + frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + if (!frd) + return JS_EXCEPTION; + target = argv[0]; + held_val = argv[1]; + token = argc > 2 ? argv[2] : JS_UNDEFINED; + + if (!js_weakref_is_target(target)) + return JS_ThrowTypeError(ctx, "invalid target"); + if (js_same_value(ctx, target, held_val)) + return JS_ThrowTypeError(ctx, "held value cannot be the target"); + if (!JS_IsUndefined(token) && !js_weakref_is_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + fre = js_malloc(ctx, sizeof(*fre)); + if (!fre) + return JS_EXCEPTION; + fre->target = js_weakref_new(ctx, target); + fre->held_val = JS_DupValue(ctx, held_val); + fre->token = js_weakref_new(ctx, token); + list_add_tail(&fre->link, &frd->entries); + return JS_UNDEFINED; +} + +static JSValue js_finrec_unregister(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + JSValueConst token; + BOOL removed; + struct list_head *el, *el1; + + if (!frd) + return JS_EXCEPTION; + token = argv[0]; + if (!js_weakref_is_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + + removed = FALSE; + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + if (js_weakref_is_live(fre->token) && js_same_value(ctx, fre->token, token)) { + js_weakref_free(ctx->rt, fre->target); + js_weakref_free(ctx->rt, fre->token); + JS_FreeValue(ctx, fre->held_val); + list_del(&fre->link); + js_free(ctx, fre); + removed = TRUE; + } + } + return JS_NewBool(ctx, removed); +} + +static const JSCFunctionListEntry js_finrec_proto_funcs[] = { + JS_CFUNC_DEF("register", 2, js_finrec_register ), + JS_CFUNC_DEF("unregister", 1, js_finrec_unregister ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "FinalizationRegistry", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_finrec_class_def[] = { + { JS_ATOM_FinalizationRegistry, js_finrec_finalizer, js_finrec_mark }, /* JS_CLASS_FINALIZATION_REGISTRY */ +}; + +void JS_AddIntrinsicWeakRef(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + + /* WeakRef */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) { + init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF, + countof(js_weakref_class_def)); + } + ctx->class_proto[JS_CLASS_WEAK_REF] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_WEAK_REF], + js_weakref_proto_funcs, + countof(js_weakref_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "WeakRef", js_weakref_constructor, 1, ctx->class_proto[JS_CLASS_WEAK_REF]); + + /* FinalizationRegistry */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_FINALIZATION_REGISTRY)) { + init_class_range(rt, js_finrec_class_def, JS_CLASS_FINALIZATION_REGISTRY, + countof(js_finrec_class_def)); + } + ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY], + js_finrec_proto_funcs, + countof(js_finrec_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "FinalizationRegistry", js_finrec_constructor, 1, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY]); +} diff --git a/src/couch_quickjs/quickjs/quickjs.h b/src/couch_quickjs/quickjs/quickjs.h index 13cb5f8d3..d3bd07f1f 100644 --- a/src/couch_quickjs/quickjs/quickjs.h +++ b/src/couch_quickjs/quickjs/quickjs.h @@ -322,7 +322,9 @@ static inline JSValue __JS_NewShortBigInt(JSContext *ctx, int64_t d) #define JS_PROP_NO_ADD (1 << 16) /* internal use */ #define JS_PROP_NO_EXOTIC (1 << 17) /* internal use */ -#define JS_DEFAULT_STACK_SIZE (256 * 1024) +#ifndef JS_DEFAULT_STACK_SIZE +#define JS_DEFAULT_STACK_SIZE (1024 * 1024) +#endif /* JS_Eval() flags */ #define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ @@ -405,6 +407,7 @@ void JS_AddIntrinsicProxy(JSContext *ctx); void JS_AddIntrinsicMapSet(JSContext *ctx); void JS_AddIntrinsicTypedArrays(JSContext *ctx); void JS_AddIntrinsicPromise(JSContext *ctx); +void JS_AddIntrinsicWeakRef(JSContext *ctx); JSValue js_string_codePointRange(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -829,6 +832,7 @@ int JS_DefinePropertyGetSet(JSContext *ctx, JSValueConst this_obj, void JS_SetOpaque(JSValue obj, void *opaque); void *JS_GetOpaque(JSValueConst obj, JSClassID class_id); void *JS_GetOpaque2(JSContext *ctx, JSValueConst obj, JSClassID class_id); +void *JS_GetAnyOpaque(JSValueConst obj, JSClassID *class_id); /* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */ JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, diff --git a/src/couch_quickjs/quickjs/test262.conf b/src/couch_quickjs/quickjs/test262.conf index 522457242..f15d1504a 100644 --- a/src/couch_quickjs/quickjs/test262.conf +++ b/src/couch_quickjs/quickjs/test262.conf @@ -107,7 +107,7 @@ Error.isError=skip explicit-resource-management=skip exponentiation export-star-as-namespace-from-module -FinalizationRegistry=skip +FinalizationRegistry Float16Array=skip Float32Array Float64Array @@ -217,7 +217,7 @@ Symbol.split Symbol.toPrimitive Symbol.toStringTag Symbol.unscopables -symbols-as-weakmap-keys=skip +symbols-as-weakmap-keys tail-call-optimization=skip template Temporal=skip @@ -231,7 +231,7 @@ Uint8Array uint8array-base64=skip Uint8ClampedArray WeakMap -WeakRef=skip +WeakRef WeakSet well-formed-json-stringify
