This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new 229f85f76 Bring in latest QuickJS fixes
229f85f76 is described below

commit 229f85f76fe87bfbff3128b2b57d34b2ac5d7b05
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
 

Reply via email to