/* * QuickJS Javascript Engine * * Copyright (c) 2017-2021 Fabrice Bellard * Copyright (c) 2017-2021 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "libc/assert.h" #include "third_party/quickjs/internal.h" asm(".ident\t\"\\n\\n\ QuickJS (MIT License)\\n\ Copyright (c) 2017-2021 Fabrice Bellard\\n\ Copyright (c) 2017-2021 Charlie Gordon\""); asm(".include \"libc/disclaimer.inc\""); /* clang-format off */ static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv, int magic) { JSMapState *s; JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED; JSValueConst arr; BOOL is_set, is_weak; is_set = magic & MAGIC_SET; is_weak = ((magic & MAGIC_WEAK) != 0); obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic); if (JS_IsException(obj)) return JS_EXCEPTION; s = js_mallocz(ctx, sizeof(*s)); if (!s) goto fail; init_list_head(&s->records); s->is_weak = is_weak; JS_SetOpaque(obj, s); s->hash_size = 1; s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size); if (!s->hash_table) goto fail; init_list_head(&s->hash_table[0]); s->record_count_threshold = 4; arr = JS_UNDEFINED; if (argc > 0) arr = argv[0]; if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) { JSValue item, ret; BOOL done; adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set); if (JS_IsException(adder)) goto fail; if (!JS_IsFunction(ctx, adder)) { JS_ThrowTypeError(ctx, "set/add is not a function"); goto fail; } iter = JS_GetIterator(ctx, arr, FALSE); if (JS_IsException(iter)) goto fail; next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); if (JS_IsException(next_method)) goto fail; for(;;) { item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); if (JS_IsException(item)) goto fail; if (done) { JS_FreeValue(ctx, item); break; } if (is_set) { ret = JS_Call(ctx, adder, obj, 1, (JSValueConst *)&item); if (JS_IsException(ret)) { JS_FreeValue(ctx, item); goto fail; } } else { JSValue key, value; JSValueConst args[2]; key = JS_UNDEFINED; value = JS_UNDEFINED; if (!JS_IsObject(item)) { JS_ThrowTypeErrorNotAnObject(ctx); goto fail1; } key = JS_GetPropertyUint32(ctx, item, 0); if (JS_IsException(key)) goto fail1; value = JS_GetPropertyUint32(ctx, item, 1); if (JS_IsException(value)) goto fail1; args[0] = key; args[1] = value; ret = JS_Call(ctx, adder, obj, 2, args); if (JS_IsException(ret)) { fail1: JS_FreeValue(ctx, item); JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); goto fail; } JS_FreeValue(ctx, key); JS_FreeValue(ctx, value); } JS_FreeValue(ctx, ret); JS_FreeValue(ctx, item); } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, adder); } return obj; fail: if (JS_IsObject(iter)) { /* close the iterator object, preserving pending exception */ JS_IteratorClose(ctx, iter, TRUE); } JS_FreeValue(ctx, next_method); JS_FreeValue(ctx, iter); JS_FreeValue(ctx, adder); JS_FreeValue(ctx, obj); return JS_EXCEPTION; } /* XXX: could normalize strings to speed up comparison */ static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key) { uint32_t tag = JS_VALUE_GET_TAG(key); /* convert -0.0 to +0.0 */ if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) { key = JS_NewInt32(ctx, 0); } return key; } /* XXX: better hash ? */ static uint32_t map_hash_key(JSContext *ctx, JSValueConst key) { uint32_t tag = JS_VALUE_GET_NORM_TAG(key); uint32_t h; double d; JSFloat64Union u; switch(tag) { case JS_TAG_BOOL: h = JS_VALUE_GET_INT(key); break; case JS_TAG_STRING: h = hash_string(JS_VALUE_GET_STRING(key), 0); break; case JS_TAG_OBJECT: case JS_TAG_SYMBOL: h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163; break; case JS_TAG_INT: d = JS_VALUE_GET_INT(key) * 3163; goto hash_float64; case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64(key); /* normalize the NaN */ if (isnan(d)) d = JS_FLOAT64_NAN; hash_float64: u.d = d; h = (u.u32[0] ^ u.u32[1]) * 3163; break; default: h = 0; /* XXX: bignum support */ break; } h ^= tag; return h; } static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, JSValueConst key) { struct list_head *el; JSMapRecord *mr; uint32_t h; h = map_hash_key(ctx, key) & (s->hash_size - 1); list_for_each(el, &s->hash_table[h]) { mr = list_entry(el, JSMapRecord, hash_link); if (js_same_value_zero(ctx, mr->key, key)) return mr; } return NULL; } static void map_hash_resize(JSContext *ctx, JSMapState *s) { uint32_t new_hash_size, i, h; size_t slack; struct list_head *new_hash_table, *el; JSMapRecord *mr; /* 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); if (!new_hash_table) return; new_hash_size += slack / sizeof(*new_hash_table); for(i = 0; i < new_hash_size; i++) init_list_head(&new_hash_table[i]); list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { h = map_hash_key(ctx, mr->key) & (new_hash_size - 1); list_add_tail(&mr->hash_link, &new_hash_table[h]); } } s->hash_table = new_hash_table; s->hash_size = new_hash_size; s->record_count_threshold = new_hash_size * 2; } static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s, JSValueConst key) { uint32_t h; JSMapRecord *mr; mr = js_malloc(ctx, sizeof(*mr)); 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; } else { JS_DupValue(ctx, key); } mr->key = (JSValue)key; h = map_hash_key(ctx, key) & (s->hash_size - 1); list_add_tail(&mr->hash_link, &s->hash_table[h]); list_add_tail(&mr->link, &s->records); s->record_count++; if (s->record_count >= s->record_count_threshold) { map_hash_resize(ctx, 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; } static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) { if (mr->empty) return; list_del(&mr->hash_link); if (s->is_weak) { delete_weak_ref(rt, mr); } else { JS_FreeValueRT(rt, mr->key); } JS_FreeValueRT(rt, mr->value); if (--mr->ref_count == 0) { list_del(&mr->link); js_free_rt(rt, mr); } else { /* keep a zombie record for iterators */ mr->empty = TRUE; mr->key = JS_UNDEFINED; mr->value = JS_UNDEFINED; } s->record_count--; } static void map_decref_record(JSRuntime *rt, JSMapRecord *mr) { if (--mr->ref_count == 0) { /* the record can be safely removed */ assert(mr->empty); list_del(&mr->link); js_free_rt(rt, mr); } } void reset_weak_ref(JSRuntime *rt, JSObject *p) { JSMapRecord *mr, *mr_next; JSMapState *s; /* first pass to remove the records from the WeakMap/WeakSet lists */ 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 */ list_del(&mr->hash_link); 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); } p->first_weak_ref = NULL; /* fail safe */ } static JSValue js_map_set(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key, value; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); if (s->is_weak && !JS_IsObject(key)) return JS_ThrowTypeErrorNotAnObject(ctx); if (magic & MAGIC_SET) value = JS_UNDEFINED; else value = argv[1]; mr = map_find_record(ctx, s, key); if (mr) { JS_FreeValue(ctx, mr->value); } else { mr = map_add_record(ctx, s, key); if (!mr) return JS_EXCEPTION; } mr->value = JS_DupValue(ctx, value); return JS_DupValue(ctx, this_val); } static JSValue js_map_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); if (!mr) return JS_UNDEFINED; else return JS_DupValue(ctx, mr->value); } static JSValue js_map_has(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); return JS_NewBool(ctx, (mr != NULL)); } static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSMapRecord *mr; JSValueConst key; if (!s) return JS_EXCEPTION; key = map_normalize_key(ctx, argv[0]); mr = map_find_record(ctx, s, key); if (!mr) return JS_FALSE; map_delete_record(ctx->rt, s, mr); return JS_TRUE; } static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); struct list_head *el, *el1; JSMapRecord *mr; if (!s) return JS_EXCEPTION; list_for_each_safe(el, el1, &s->records) { mr = list_entry(el, JSMapRecord, link); map_delete_record(ctx->rt, s, mr); } return JS_UNDEFINED; } static JSValue js_map_get_size(JSContext *ctx, JSValueConst this_val, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); if (!s) return JS_EXCEPTION; return JS_NewUint32(ctx, s->record_count); } static JSValue js_map_forEach(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); JSValueConst func, this_arg; JSValue ret, args[3]; struct list_head *el; JSMapRecord *mr; if (!s) return JS_EXCEPTION; func = argv[0]; if (argc > 1) this_arg = argv[1]; else this_arg = JS_UNDEFINED; if (check_function(ctx, func)) return JS_EXCEPTION; /* Note: the list can be modified while traversing it, but the current element is locked */ el = s->records.next; while (el != &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { mr->ref_count++; /* must duplicate in case the record is deleted */ args[1] = JS_DupValue(ctx, mr->key); if (magic) args[0] = args[1]; else args[0] = JS_DupValue(ctx, mr->value); args[2] = (JSValue)this_val; ret = JS_Call(ctx, func, this_arg, 3, (JSValueConst *)args); JS_FreeValue(ctx, args[0]); if (!magic) JS_FreeValue(ctx, args[1]); el = el->next; map_decref_record(ctx->rt, mr); if (JS_IsException(ret)) return ret; JS_FreeValue(ctx, ret); } else { el = el->next; } } return JS_UNDEFINED; } void js_map_finalizer(JSRuntime *rt, JSValue val) { JSObject *p; JSMapState *s; struct list_head *el, *el1; JSMapRecord *mr; p = JS_VALUE_GET_OBJ(val); s = p->u.map_state; if (s) { /* if the object is deleted we are sure that no iterator is using it */ list_for_each_safe(el, el1, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!mr->empty) { if (s->is_weak) delete_weak_ref(rt, mr); else JS_FreeValueRT(rt, mr->key); JS_FreeValueRT(rt, mr->value); } js_free_rt(rt, mr); } js_free_rt(rt, s->hash_table); js_free_rt(rt, s); } } void js_map_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSMapState *s; struct list_head *el; JSMapRecord *mr; s = p->u.map_state; if (s) { list_for_each(el, &s->records) { mr = list_entry(el, JSMapRecord, link); if (!s->is_weak) JS_MarkValue(rt, mr->key, mark_func); JS_MarkValue(rt, mr->value, mark_func); } } } void js_map_iterator_finalizer(JSRuntime *rt, JSValue val) { JSObject *p; JSMapIteratorData *it; p = JS_VALUE_GET_OBJ(val); it = p->u.map_iterator_data; if (it) { /* During the GC sweep phase the Map finalizer may be called before the Map iterator finalizer */ if (JS_IsLiveObject(rt, it->obj) && it->cur_record) { map_decref_record(rt, it->cur_record); } JS_FreeValueRT(rt, it->obj); js_free_rt(rt, it); } } void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) { JSObject *p = JS_VALUE_GET_OBJ(val); JSMapIteratorData *it; it = p->u.map_iterator_data; if (it) { /* the record is already marked by the object */ JS_MarkValue(rt, it->obj, mark_func); } } static JSValue js_create_map_iterator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { JSIteratorKindEnum kind; JSMapState *s; JSMapIteratorData *it; JSValue enum_obj; kind = magic >> 2; magic &= 3; s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); if (!s) return JS_EXCEPTION; enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic); if (JS_IsException(enum_obj)) goto fail; it = js_malloc(ctx, sizeof(*it)); if (!it) { JS_FreeValue(ctx, enum_obj); goto fail; } it->obj = JS_DupValue(ctx, this_val); it->kind = kind; it->cur_record = NULL; JS_SetOpaque(enum_obj, it); return enum_obj; fail: return JS_EXCEPTION; } static JSValue js_map_iterator_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, BOOL *pdone, int magic) { JSMapIteratorData *it; JSMapState *s; JSMapRecord *mr; struct list_head *el; it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic); if (!it) { *pdone = FALSE; return JS_EXCEPTION; } if (JS_IsUndefined(it->obj)) goto done; s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic); assert(s != NULL); if (!it->cur_record) { el = s->records.next; } else { mr = it->cur_record; el = mr->link.next; map_decref_record(ctx->rt, mr); /* the record can be freed here */ } for(;;) { if (el == &s->records) { /* no more record */ it->cur_record = NULL; JS_FreeValue(ctx, it->obj); it->obj = JS_UNDEFINED; done: /* end of enumeration */ *pdone = TRUE; return JS_UNDEFINED; } mr = list_entry(el, JSMapRecord, link); if (!mr->empty) break; /* get the next record */ el = mr->link.next; } /* lock the record so that it won't be freed */ mr->ref_count++; it->cur_record = mr; *pdone = FALSE; if (it->kind == JS_ITERATOR_KIND_KEY) { return JS_DupValue(ctx, mr->key); } else { JSValueConst args[2]; args[0] = mr->key; if (magic) args[1] = mr->key; else args[1] = mr->value; if (it->kind == JS_ITERATOR_KIND_VALUE) { return JS_DupValue(ctx, args[1]); } else { return js_create_array(ctx, 2, args); } } } static const JSCFunctionListEntry js_map_funcs[] = { JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), }; static const JSCFunctionListEntry js_map_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ), JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ), JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0), JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ), JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ), JS_ALIAS_DEF("[Symbol.iterator]", "entries" ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_set_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ), JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ), JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ), JS_ALIAS_DEF("keys", "values" ), JS_ALIAS_DEF("[Symbol.iterator]", "values" ), JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = { JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_weak_map_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry js_weak_set_proto_funcs[] = { JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ), JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ), JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ), }; static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = { js_map_proto_funcs, js_set_proto_funcs, js_weak_map_proto_funcs, js_weak_set_proto_funcs, js_map_iterator_proto_funcs, js_set_iterator_proto_funcs, }; static const uint8_t js_map_proto_funcs_count[6] = { countof(js_map_proto_funcs), countof(js_set_proto_funcs), countof(js_weak_map_proto_funcs), countof(js_weak_set_proto_funcs), countof(js_map_iterator_proto_funcs), countof(js_set_iterator_proto_funcs), }; void JS_AddIntrinsicMapSet(JSContext *ctx) { int i; JSValue obj1; char buf[ATOM_GET_STR_BUF_SIZE]; for(i = 0; i < 4; i++) { const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), JS_ATOM_Map + i); ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i], js_map_proto_funcs_ptr[i], js_map_proto_funcs_count[i]); obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0, JS_CFUNC_constructor_magic, i); if (i < 2) { JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs, countof(js_map_funcs)); } JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[JS_CLASS_MAP + i]); } for(i = 0; i < 2; i++) { ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] = JS_NewObjectProto(ctx, ctx->iterator_proto); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i], js_map_proto_funcs_ptr[i + 4], js_map_proto_funcs_count[i + 4]); } }