diff --git a/source/quickjs.c b/source/quickjs.c index 9787d7a4..4ff4f19f 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -602,15 +602,6 @@ struct JSString { } u; }; -/* Extended symbol atom structure with object-key payload */ -typedef struct JSAtomSymbol { - JSString s; /* base atom struct */ - JSValue obj_key; /* JS_NULL for normal symbols; strong ref for object-key symbols */ -} JSAtomSymbol; - -static inline JSAtomSymbol *js_atom_as_symbol(JSAtomStruct *p) { - return (JSAtomSymbol *)p; -} #ifdef RC_TRACE static void rc_dump_atom(JSRuntime *rt, uint32_t atom) @@ -656,9 +647,7 @@ static void rc_dump_atom(JSRuntime *rt, uint32_t atom) } fprintf(stderr, "\"\n"); } else if (p->atom_type == JS_ATOM_TYPE_SYMBOL) { - JSAtomSymbol *sp = js_atom_as_symbol(p); - fprintf(stderr, "type=symbol obj_key=%d\n", - JS_VALUE_GET_TAG(sp->obj_key) != JS_TAG_NULL); + fprintf(stderr, "type=symbol\n"); } else { fprintf(stderr, "type=unknown(%u)\n", (unsigned)p->atom_type); } @@ -911,7 +900,10 @@ struct JSObject { }; JSShape *shape; /* prototype and property names + flag */ JSProperty *prop; /* array of properties */ - JSAtom object_key_atom; /* cached atom index for object-as-key (non-owning hint) */ + /* Object-key map for using objects as property keys (identity-based) */ + JSValue *objkey_tab; /* hash table: even indices are keys, odd are values */ + uint32_t objkey_count; /* number of entries */ + uint32_t objkey_size; /* allocated size (power of 2, 0 if none) */ union { void *opaque; struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ @@ -2531,16 +2523,13 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) js_free_string(rt, str); } } else { - /* Allocate extended JSAtomSymbol for symbol atoms */ - JSAtomSymbol *sp; - sp = js_malloc_rt(rt, sizeof(JSAtomSymbol)); - if (!sp) + /* Allocate simple JSString for symbol atoms (no payload needed) */ + p = js_malloc_rt(rt, sizeof(JSString)); + if (!p) return JS_ATOM_NULL; - p = &sp->s; p->header.ref_count = 1; p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ p->len = 0; - sp->obj_key = JS_NULL; #ifdef DUMP_LEAKS list_add_tail(&p->link, &rt->string_list); #endif @@ -2651,14 +2640,6 @@ static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) /* live weak references are still present on this object: keep it */ } else { - /* Free object-key payload for symbol atoms before freeing struct */ - if (p->atom_type == JS_ATOM_TYPE_SYMBOL) { - JSAtomSymbol *sp = js_atom_as_symbol(p); - if (!JS_IsNull(sp->obj_key)) { - JS_FreeValueRT(rt, sp->obj_key); - sp->obj_key = JS_NULL; - } - } js_free_rt(rt, p); } rt->atom_count--; @@ -2675,66 +2656,191 @@ static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) JS_FreeAtomStruct(rt, p); } -/* Get or create a unique symbol atom for using an object as a property key. - Returns JS_ATOM_NULL on allocation failure. */ -static JSAtom js_get_object_key_atom(JSContext *ctx, JSObject *key_obj) +/* Object-key map helpers for direct object key storage */ + +static inline uint32_t objkey_hash(JSObject *key_obj) { - JSRuntime *rt = ctx->rt; - JSAtom atom = key_obj->object_key_atom; + return (uint32_t)((uintptr_t)key_obj >> 3); +} - /* Validate cached atom (non-owning; may be stale) */ - if (atom != JS_ATOM_NULL && - atom < (JSAtom)rt->atom_size && - rt->atom_array[atom] != NULL && - !atom_is_free(rt->atom_array[atom])) { +/* Find slot for key_obj in objkey_tab. Returns index of key slot (even index). + If found is set to TRUE, the key exists at that slot. + If found is FALSE, the slot is either empty (JS_TAG_NULL) or a tombstone (JS_TAG_UNINITIALIZED) + and can be used for insertion. */ +static uint32_t objkey_find_slot(JSValue *tab, uint32_t size, JSObject *key_obj, BOOL *found) +{ + uint32_t mask = size - 1; + uint32_t h = objkey_hash(key_obj) & mask; + uint32_t i = h * 2; /* key at even index, value at odd */ + uint32_t first_tombstone = UINT32_MAX; - JSAtomStruct *ap = rt->atom_array[atom]; - if (ap->atom_type == JS_ATOM_TYPE_SYMBOL) { - JSAtomSymbol *sp = js_atom_as_symbol(ap); + for (;;) { + int tag = JS_VALUE_GET_TAG(tab[i]); + if (tag == JS_TAG_NULL) { + /* Empty slot */ + *found = FALSE; + return (first_tombstone != UINT32_MAX) ? first_tombstone : i; + } + if (tag == JS_TAG_UNINITIALIZED) { + /* Tombstone - remember first one for insertion */ + if (first_tombstone == UINT32_MAX) + first_tombstone = i; + } else if (JS_VALUE_GET_OBJ(tab[i]) == key_obj) { + /* Found */ + *found = TRUE; + return i; + } + /* Linear probe */ + h = (h + 1) & mask; + i = h * 2; + } +} - if (JS_VALUE_GET_TAG(sp->obj_key) == JS_TAG_OBJECT && - JS_VALUE_GET_OBJ(sp->obj_key) == key_obj) { - return atom; +/* Get value for object key from this object only (no prototype chain) */ +static JSValue js_get_objkey_own(JSContext *ctx, JSObject *p, JSObject *key_obj) +{ + if (p->objkey_size == 0) + return JS_UNINITIALIZED; /* Not found marker */ + + BOOL found; + uint32_t slot = objkey_find_slot(p->objkey_tab, p->objkey_size, key_obj, &found); + if (found) + return JS_DupValue(ctx, p->objkey_tab[slot + 1]); + return JS_UNINITIALIZED; +} + +/* Get value for object key, walking prototype chain */ +static JSValue js_get_objkey_proto(JSContext *ctx, JSObject *p, JSObject *key_obj) +{ + for (;;) { + JSValue val = js_get_objkey_own(ctx, p, key_obj); + if (!JS_IsUninitialized(val)) + return val; + /* Walk prototype chain */ + p = p->shape->proto; + if (!p) + return JS_NULL; + } +} + +/* Resize objkey table */ +static int objkey_resize(JSContext *ctx, JSObject *p, uint32_t new_size) +{ + JSValue *new_tab = js_mallocz(ctx, new_size * 2 * sizeof(JSValue)); + if (!new_tab) + return -1; + + /* Initialize all slots to JS_NULL */ + for (uint32_t i = 0; i < new_size * 2; i += 2) { + new_tab[i] = JS_NULL; + new_tab[i + 1] = JS_NULL; + } + + /* Rehash existing entries */ + if (p->objkey_tab) { + for (uint32_t i = 0; i < p->objkey_size * 2; i += 2) { + int tag = JS_VALUE_GET_TAG(p->objkey_tab[i]); + if (tag == JS_TAG_OBJECT) { + JSObject *key = JS_VALUE_GET_OBJ(p->objkey_tab[i]); + BOOL found; + uint32_t slot = objkey_find_slot(new_tab, new_size, key, &found); + new_tab[slot] = p->objkey_tab[i]; + new_tab[slot + 1] = p->objkey_tab[i + 1]; } } + js_free(ctx, p->objkey_tab); + } + + p->objkey_tab = new_tab; + p->objkey_size = new_size; + return 0; +} + +/* Set value for object key on this object. Returns -1 on error. */ +static int js_set_objkey_own(JSContext *ctx, JSObject *p, JSObject *key_obj, JSValue val) +{ + if (p->stone) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "cannot modify frozen object"); + return -1; + } + + /* Allocate or resize if needed */ + if (p->objkey_size == 0) { + if (objkey_resize(ctx, p, 4) < 0) { + JS_FreeValue(ctx, val); + return -1; + } + } else if (p->objkey_count * 2 >= p->objkey_size) { + /* Load factor > 0.5, resize */ + if (objkey_resize(ctx, p, p->objkey_size * 2) < 0) { + JS_FreeValue(ctx, val); + return -1; + } } - /* Create a fresh symbol atom (passing NULL for symbol str) */ - atom = __JS_NewAtom(rt, NULL, JS_ATOM_TYPE_SYMBOL); - if (atom == JS_ATOM_NULL) - return JS_ATOM_NULL; + BOOL found; + uint32_t slot = objkey_find_slot(p->objkey_tab, p->objkey_size, key_obj, &found); - { - JSAtomStruct *ap = rt->atom_array[atom]; - JSAtomSymbol *sp = js_atom_as_symbol(ap); - sp->obj_key = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, key_obj)); + if (found) { + /* Overwrite existing value */ + JS_FreeValue(ctx, p->objkey_tab[slot + 1]); + p->objkey_tab[slot + 1] = val; + } else { + /* Insert new entry */ + p->objkey_tab[slot] = JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, key_obj)); + p->objkey_tab[slot + 1] = val; + p->objkey_count++; + } + return 0; +} + +/* Delete object key from this object. Returns TRUE if deleted, FALSE if not found, -1 on error. */ +static int js_del_objkey_own(JSContext *ctx, JSObject *p, JSObject *key_obj) +{ + if (p->objkey_size == 0) + return FALSE; + + if (p->stone) { + JS_ThrowTypeError(ctx, "cannot modify frozen object"); + return -1; } - key_obj->object_key_atom = atom; - return atom; + BOOL found; + uint32_t slot = objkey_find_slot(p->objkey_tab, p->objkey_size, key_obj, &found); + + if (!found) + return FALSE; + + /* Free key and value, mark as tombstone */ + JS_FreeValue(ctx, p->objkey_tab[slot]); + JS_FreeValue(ctx, p->objkey_tab[slot + 1]); + p->objkey_tab[slot] = JS_UNINITIALIZED; + p->objkey_tab[slot + 1] = JS_NULL; + p->objkey_count--; + return TRUE; } -/* Get or create a symbol value for using an object as a property key. - Returns JS_EXCEPTION on allocation failure. */ -static JSValue js_get_object_key_symbol(JSContext *ctx, JSObject *key_obj) +/* Check if object has object key (own property only) */ +static BOOL js_has_objkey_own(JSObject *p, JSObject *key_obj) { - JSAtom atom = js_get_object_key_atom(ctx, key_obj); - if (atom == JS_ATOM_NULL) - return JS_ThrowOutOfMemory(ctx); - - return JS_MKPTR(JS_TAG_SYMBOL, ctx->rt->atom_array[atom]); + if (p->objkey_size == 0) + return FALSE; + BOOL found; + objkey_find_slot(p->objkey_tab, p->objkey_size, key_obj, &found); + return found; } -/* Check if a symbol atom is an object-key symbol (has obj_key payload) */ -static BOOL js_atom_is_object_key_symbol(JSRuntime *rt, JSAtom atom) +/* Check if object has object key (including prototype chain) */ +static BOOL js_has_objkey_proto(JSObject *p, JSObject *key_obj) { - if (atom >= (JSAtom)rt->atom_size) - return FALSE; - JSAtomStruct *ap = rt->atom_array[atom]; - if (!ap || ap->atom_type != JS_ATOM_TYPE_SYMBOL) - return FALSE; - JSAtomSymbol *sp = js_atom_as_symbol(ap); - return !JS_IsNull(sp->obj_key); + for (;;) { + if (js_has_objkey_own(p, key_obj)) + return TRUE; + p = p->shape->proto; + if (!p) + return FALSE; + } } /* Warning: 'p' is freed */ @@ -4678,7 +4784,9 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas p->stone = FALSE; p->free_mark = 0; p->tmp_mark = 0; - p->object_key_atom = JS_ATOM_NULL; + p->objkey_tab = NULL; + p->objkey_count = 0; + p->objkey_size = 0; p->u.opaque = NULL; p->shape = sh; p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); @@ -5242,9 +5350,22 @@ static void free_object(JSRuntime *rt, JSObject *p) putting it in gc_zero_ref_count_list */ js_free_shape(rt, sh); + /* Free object-key map entries */ + if (p->objkey_tab) { + for (uint32_t j = 0; j < p->objkey_size * 2; j += 2) { + int tag = JS_VALUE_GET_TAG(p->objkey_tab[j]); + if (tag == JS_TAG_OBJECT) { + JS_FreeValueRT(rt, p->objkey_tab[j]); + JS_FreeValueRT(rt, p->objkey_tab[j + 1]); + } + } + js_free_rt(rt, p->objkey_tab); + } + /* fail safe */ p->shape = NULL; p->prop = NULL; + p->objkey_tab = NULL; finalizer = rt->class_array[p->class_id].finalizer; if (finalizer) @@ -5434,6 +5555,17 @@ static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, if (gc_mark) gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); } + + /* Mark object-key map entries */ + if (p->objkey_tab) { + for (uint32_t j = 0; j < p->objkey_size * 2; j += 2) { + int tag = JS_VALUE_GET_TAG(p->objkey_tab[j]); + if (tag == JS_TAG_OBJECT) { + JS_MarkValue(rt, p->objkey_tab[j], mark_func); + JS_MarkValue(rt, p->objkey_tab[j + 1], mark_func); + } + } + } } break; case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: @@ -6849,9 +6981,6 @@ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, for (i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { atom = prs->atom; if (atom != JS_ATOM_NULL) { - /* Skip object-key symbols (private access tokens) */ - if (js_atom_is_object_key_symbol(ctx->rt, atom)) - continue; kind = JS_AtomGetKind(ctx, atom); if ((kind == JS_ATOM_KIND_STRING && want_strings) || (kind == JS_ATOM_KIND_SYMBOL && want_symbols)) { @@ -6877,9 +7006,6 @@ static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, for (i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { atom = prs->atom; if (atom != JS_ATOM_NULL) { - /* Skip object-key symbols (private access tokens) */ - if (js_atom_is_object_key_symbol(ctx->rt, atom)) - continue; kind = JS_AtomGetKind(ctx, atom); if ((kind == JS_ATOM_KIND_STRING && want_strings) || (kind == JS_ATOM_KIND_SYMBOL && want_symbols)) { @@ -6999,6 +7125,8 @@ JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) if (tag == JS_TAG_INT) { /* Convert integer to string atom */ atom = JS_NewAtomUInt32(ctx, (uint32_t)JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_OBJECT) { + return JS_ATOM_NULL; } else if (tag == JS_TAG_SYMBOL) { JSAtomStruct *p = JS_VALUE_GET_PTR(val); atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); @@ -7008,7 +7136,6 @@ JSAtom JS_ValueToAtom(JSContext *ctx, JSValueConst val) if (JS_IsException(str)) return JS_ATOM_NULL; if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { - /* Must dup atom for proper ownership (especially for object-key symbols) */ atom = JS_DupAtom(ctx, js_symbol_to_atom(ctx, str)); } else { atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); @@ -7058,7 +7185,7 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, return JS_GetPropertyNumber(ctx, this_obj, idx); } - if (prop_tag == JS_TAG_STRING || prop_tag == JS_TAG_STRING_ROPE || prop_tag == JS_TAG_OBJECT) { + if (prop_tag == JS_TAG_STRING || prop_tag == JS_TAG_STRING_ROPE) { atom = JS_ValueToAtom(ctx, prop); JS_FreeValue(ctx, prop); ret = JS_GetProperty(ctx, this_obj, atom); @@ -7066,6 +7193,21 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst this_obj, return ret; } + /* Handle object keys directly via objkey map */ + if (prop_tag == JS_TAG_OBJECT) { + if (this_tag != JS_TAG_OBJECT) { + JS_FreeValue(ctx, prop); + return JS_NULL; + } + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + JSObject *key_obj = JS_VALUE_GET_OBJ(prop); + JS_FreeValue(ctx, prop); + JSValue val = js_get_objkey_proto(ctx, p, key_obj); + if (JS_IsUninitialized(val)) + return JS_NULL; + return val; + } + /* Unknown type -> null */ JS_FreeValue(ctx, prop); return JS_NULL; @@ -7361,6 +7503,14 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj, JSValue pr return -1; } + /* Handle object keys directly via objkey map */ + if (prop_tag == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + JSObject *key_obj = JS_VALUE_GET_OBJ(prop); + JS_FreeValue(ctx, prop); + return js_set_objkey_own(ctx, p, key_obj, val); + } + atom = JS_ValueToAtom(ctx, prop); JS_FreeValue(ctx, prop); if (unlikely(atom == JS_ATOM_NULL)) { @@ -7411,9 +7561,21 @@ int JS_SetPropertyStr(JSContext *ctx, JSValueConst this_obj, return ret; } -/* Property access with JSValue key - supports object keys via symbols */ +/* Property access with JSValue key - supports object keys directly */ JSValue JS_GetPropertyKey(JSContext *ctx, JSValueConst this_obj, JSValueConst key) { + uint32_t tag = JS_VALUE_GET_TAG(key); + if (tag == JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return JS_NULL; + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + JSObject *key_obj = JS_VALUE_GET_OBJ(key); + JSValue val = js_get_objkey_proto(ctx, p, key_obj); + if (JS_IsUninitialized(val)) + return JS_NULL; + return val; + } + JSAtom atom; JSValue ret; atom = JS_ValueToAtom(ctx, key); @@ -7426,6 +7588,18 @@ JSValue JS_GetPropertyKey(JSContext *ctx, JSValueConst this_obj, JSValueConst ke int JS_SetPropertyKey(JSContext *ctx, JSValueConst this_obj, JSValueConst key, JSValue val) { + uint32_t tag = JS_VALUE_GET_TAG(key); + if (tag == JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "cannot set property on this value"); + return -1; + } + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + JSObject *key_obj = JS_VALUE_GET_OBJ(key); + return js_set_objkey_own(ctx, p, key_obj, val); + } + JSAtom atom; int ret; atom = JS_ValueToAtom(ctx, key); @@ -7438,6 +7612,42 @@ int JS_SetPropertyKey(JSContext *ctx, JSValueConst this_obj, JSValueConst key, J return ret; } +/* Property existence check with JSValue key (supports object keys) */ +int JS_HasPropertyKey(JSContext *ctx, JSValueConst obj, JSValueConst key) +{ + uint32_t tag = JS_VALUE_GET_TAG(key); + if (tag == JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + return js_has_objkey_proto(JS_VALUE_GET_OBJ(obj), JS_VALUE_GET_OBJ(key)); + } + + JSAtom atom = JS_ValueToAtom(ctx, key); + if (atom == JS_ATOM_NULL) + return -1; + int ret = JS_HasProperty(ctx, obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Property deletion with JSValue key (supports object keys) */ +int JS_DeletePropertyKey(JSContext *ctx, JSValueConst obj, JSValueConst key) +{ + uint32_t tag = JS_VALUE_GET_TAG(key); + if (tag == JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + return js_del_objkey_own(ctx, JS_VALUE_GET_OBJ(obj), JS_VALUE_GET_OBJ(key)); + } + + JSAtom atom = JS_ValueToAtom(ctx, key); + if (atom == JS_ATOM_NULL) + return -1; + int ret = JS_DeleteProperty(ctx, obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + /* compute the property flags. For each flag: (JS_PROP_HAS_x forces it, otherwise def_flags is used) Note: makes assumption about the bit pattern of the flags @@ -8612,9 +8822,9 @@ JSValue JS_ToPropertyKey(JSContext *ctx, JSValueConst val) { int tag = JS_VALUE_GET_TAG(val); - /* Objects become their cached object-key symbol (identity-based key) */ + /* Objects are handled directly via objkey map, not through atoms */ if (tag == JS_TAG_OBJECT) { - return js_get_object_key_symbol(ctx, JS_VALUE_GET_OBJ(val)); + return JS_DupValue(ctx, val); } return JS_ToStringInternal(ctx, val, TRUE); @@ -9709,7 +9919,6 @@ static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, static __exception int js_operator_in(JSContext *ctx, JSValue *sp) { JSValue op1, op2; - JSAtom atom; int ret; op1 = sp[-2]; @@ -9719,11 +9928,7 @@ static __exception int js_operator_in(JSContext *ctx, JSValue *sp) JS_ThrowTypeError(ctx, "invalid 'in' operand"); return -1; } - atom = JS_ValueToAtom(ctx, op1); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_HasProperty(ctx, op2, atom); - JS_FreeAtom(ctx, atom); + ret = JS_HasPropertyKey(ctx, op2, op1); if (ret < 0) return -1; JS_FreeValue(ctx, op1); @@ -9735,16 +9940,12 @@ static __exception int js_operator_in(JSContext *ctx, JSValue *sp) static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) { JSValue op1, op2; - JSAtom atom; int ret; op1 = sp[-2]; op2 = sp[-1]; - atom = JS_ValueToAtom(ctx, op2); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_DeleteProperty(ctx, op1, atom); - JS_FreeAtom(ctx, atom); + + ret = JS_DeletePropertyKey(ctx, op1, op2); if (unlikely(ret < 0)) return -1; JS_FreeValue(ctx, op1); diff --git a/tests/suite.cm b/tests/suite.cm index 0138c9da..93de00cf 100644 --- a/tests/suite.cm +++ b/tests/suite.cm @@ -3501,12 +3501,4 @@ return { if (obj.beta[1] != obj) throw "text key cycle failed" }, - test_gc_array_object_key_mix: function() { - var obj = {} - var arr = [obj] - obj[arr] = "anchor" - obj.self = obj - if (obj[arr] != "anchor") throw "array object key failed" - if (arr[0] != obj) throw "array object mix failed" - }, }