make for in faster and spec compliant (github issue #137)

This commit is contained in:
Fabrice Bellard 2024-01-06 14:43:29 +01:00
parent 24aa7ba4f0
commit df3781d082
2 changed files with 170 additions and 89 deletions

213
quickjs.c
View file

@ -637,9 +637,11 @@ typedef enum JSIteratorKindEnum {
typedef struct JSForInIterator { typedef struct JSForInIterator {
JSValue obj; JSValue obj;
BOOL is_array;
uint32_t array_length;
uint32_t idx; uint32_t idx;
uint32_t atom_count;
uint8_t in_prototype_chain;
uint8_t is_array;
JSPropertyEnum *tab_atom; /* is_array = FALSE */
} JSForInIterator; } JSForInIterator;
typedef struct JSRegExp { typedef struct JSRegExp {
@ -5399,7 +5401,15 @@ static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val)
{ {
JSObject *p = JS_VALUE_GET_OBJ(val); JSObject *p = JS_VALUE_GET_OBJ(val);
JSForInIterator *it = p->u.for_in_iterator; JSForInIterator *it = p->u.for_in_iterator;
int i;
JS_FreeValueRT(rt, it->obj); JS_FreeValueRT(rt, it->obj);
if (!it->is_array) {
for(i = 0; i < it->atom_count; i++) {
JS_FreeAtomRT(rt, it->tab_atom[i].atom);
}
js_free_rt(rt, it->tab_atom);
}
js_free_rt(rt, it); js_free_rt(rt, it);
} }
@ -14842,10 +14852,10 @@ static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValueConst *
static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj)
{ {
JSObject *p; JSObject *p, *p1;
JSPropertyEnum *tab_atom; JSPropertyEnum *tab_atom;
int i; int i;
JSValue enum_obj, obj1; JSValue enum_obj;
JSForInIterator *it; JSForInIterator *it;
uint32_t tag, tab_atom_count; uint32_t tag, tab_atom_count;
@ -14868,14 +14878,65 @@ static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj)
it->is_array = FALSE; it->is_array = FALSE;
it->obj = obj; it->obj = obj;
it->idx = 0; it->idx = 0;
p = JS_VALUE_GET_OBJ(enum_obj); it->tab_atom = NULL;
p->u.for_in_iterator = it; it->atom_count = 0;
it->in_prototype_chain = FALSE;
p1 = JS_VALUE_GET_OBJ(enum_obj);
p1->u.for_in_iterator = it;
if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED)
return enum_obj; return enum_obj;
/* fast path: assume no enumerable properties in the prototype chain */ p = JS_VALUE_GET_OBJ(obj);
obj1 = JS_DupValue(ctx, obj); if (p->fast_array) {
JSShape *sh;
JSShapeProperty *prs;
/* check that there are no enumerable normal fields */
sh = p->shape;
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
if (prs->flags & JS_PROP_ENUMERABLE)
goto normal_case;
}
/* for fast arrays, we only store the number of elements */
it->is_array = TRUE;
it->atom_count = p->u.array.count;
} else {
normal_case:
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) {
JS_FreeValue(ctx, enum_obj);
return JS_EXCEPTION;
}
it->tab_atom = tab_atom;
it->atom_count = tab_atom_count;
}
return enum_obj;
}
/* obj -> enum_obj */
static __exception int js_for_in_start(JSContext *ctx, JSValue *sp)
{
sp[-1] = build_for_in_iterator(ctx, sp[-1]);
if (JS_IsException(sp[-1]))
return -1;
return 0;
}
/* return -1 if exception, 0 if slow case, 1 if the enumeration is finished */
static __exception int js_for_in_prepare_prototype_chain_enum(JSContext *ctx,
JSValueConst enum_obj)
{
JSObject *p;
JSForInIterator *it;
JSPropertyEnum *tab_atom;
uint32_t tab_atom_count, i;
JSValue obj1;
p = JS_VALUE_GET_OBJ(enum_obj);
it = p->u.for_in_iterator;
/* check if there are enumerable properties in the prototype chain (fast path) */
obj1 = JS_DupValue(ctx, it->obj);
for(;;) { for(;;) {
obj1 = JS_GetPrototypeFree(ctx, obj1); obj1 = JS_GetPrototypeFree(ctx, obj1);
if (JS_IsNull(obj1)) if (JS_IsNull(obj1))
@ -14899,75 +14960,29 @@ static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj)
goto fail; goto fail;
} }
} }
JS_FreeValue(ctx, obj1);
p = JS_VALUE_GET_OBJ(obj); return 1;
if (p->fast_array) {
JSShape *sh;
JSShapeProperty *prs;
/* check that there are no enumerable normal fields */
sh = p->shape;
for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) {
if (prs->flags & JS_PROP_ENUMERABLE)
goto normal_case;
}
/* for fast arrays, we only store the number of elements */
it->is_array = TRUE;
it->array_length = p->u.array.count;
} else {
normal_case:
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p,
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY))
goto fail;
for(i = 0; i < tab_atom_count; i++) {
JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, enum_obj, 0);
}
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
}
return enum_obj;
slow_path: slow_path:
/* non enumerable properties hide the enumerables ones in the /* add the visited properties, even if they are not enumerable */
prototype chain */ if (it->is_array) {
obj1 = JS_DupValue(ctx, obj);
for(;;) {
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count,
JS_VALUE_GET_OBJ(obj1), JS_VALUE_GET_OBJ(it->obj),
JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) {
JS_FreeValue(ctx, obj1);
goto fail; goto fail;
} }
for(i = 0; i < tab_atom_count; i++) { it->is_array = FALSE;
JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, it->tab_atom = tab_atom;
(tab_atom[i].is_enumerable ? it->atom_count = tab_atom_count;
JS_PROP_ENUMERABLE : 0));
} }
js_free_prop_enum(ctx, tab_atom, tab_atom_count);
obj1 = JS_GetPrototypeFree(ctx, obj1);
if (JS_IsNull(obj1))
break;
if (JS_IsException(obj1))
goto fail;
/* must check for timeout to avoid infinite loop */
if (js_poll_interrupts(ctx)) {
JS_FreeValue(ctx, obj1);
goto fail;
}
}
return enum_obj;
fail: for(i = 0; i < it->atom_count; i++) {
JS_FreeValue(ctx, enum_obj); if (JS_DefinePropertyValue(ctx, enum_obj, it->tab_atom[i].atom, JS_NULL, JS_PROP_ENUMERABLE) < 0)
return JS_EXCEPTION; goto fail;
} }
/* obj -> enum_obj */
static __exception int js_for_in_start(JSContext *ctx, JSValue *sp)
{
sp[-1] = build_for_in_iterator(ctx, sp[-1]);
if (JS_IsException(sp[-1]))
return -1;
return 0; return 0;
fail:
return -1;
} }
/* enum_obj -> enum_obj value done */ /* enum_obj -> enum_obj value done */
@ -14977,6 +14992,8 @@ static __exception int js_for_in_next(JSContext *ctx, JSValue *sp)
JSObject *p; JSObject *p;
JSAtom prop; JSAtom prop;
JSForInIterator *it; JSForInIterator *it;
JSPropertyEnum *tab_atom;
uint32_t tab_atom_count;
int ret; int ret;
enum_obj = sp[-1]; enum_obj = sp[-1];
@ -14989,29 +15006,69 @@ static __exception int js_for_in_next(JSContext *ctx, JSValue *sp)
it = p->u.for_in_iterator; it = p->u.for_in_iterator;
for(;;) { for(;;) {
if (it->is_array) { if (it->idx >= it->atom_count) {
if (it->idx >= it->array_length) if (JS_IsNull(it->obj) || JS_IsUndefined(it->obj))
goto done; /* not an object */
/* no more property in the current object: look in the prototype */
if (!it->in_prototype_chain) {
ret = js_for_in_prepare_prototype_chain_enum(ctx, enum_obj);
if (ret < 0)
return -1;
if (ret)
goto done; goto done;
it->in_prototype_chain = TRUE;
}
it->obj = JS_GetPrototypeFree(ctx, it->obj);
if (JS_IsException(it->obj))
return -1;
if (JS_IsNull(it->obj))
goto done; /* no more prototype */
/* must check for timeout to avoid infinite loop */
if (js_poll_interrupts(ctx))
return -1;
if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count,
JS_VALUE_GET_OBJ(it->obj),
JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) {
return -1;
}
js_free_prop_enum(ctx, it->tab_atom, it->atom_count);
it->tab_atom = tab_atom;
it->atom_count = tab_atom_count;
it->idx = 0;
} else {
if (it->is_array) {
prop = __JS_AtomFromUInt32(it->idx); prop = __JS_AtomFromUInt32(it->idx);
it->idx++; it->idx++;
} else { } else {
JSShape *sh = p->shape; BOOL is_enumerable;
JSShapeProperty *prs; prop = it->tab_atom[it->idx].atom;
if (it->idx >= sh->prop_count) is_enumerable = it->tab_atom[it->idx].is_enumerable;
goto done;
prs = get_shape_prop(sh) + it->idx;
prop = prs->atom;
it->idx++; it->idx++;
if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) if (it->in_prototype_chain) {
/* slow case: we are in the prototype chain */
ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(enum_obj), prop);
if (ret < 0)
return ret;
if (ret)
continue; /* already visited */
/* add to the visited property list */
if (JS_DefinePropertyValue(ctx, enum_obj, prop, JS_NULL,
JS_PROP_ENUMERABLE) < 0)
return -1;
}
if (!is_enumerable)
continue; continue;
} }
/* check if the property was deleted */ /* check if the property was deleted */
ret = JS_HasProperty(ctx, it->obj, prop); ret = JS_GetOwnPropertyInternal(ctx, NULL, JS_VALUE_GET_OBJ(it->obj), prop);
if (ret < 0) if (ret < 0)
return ret; return ret;
if (ret) if (ret)
break; break;
} }
}
/* return the property */ /* return the property */
sp[0] = JS_AtomToValue(ctx, prop); sp[0] = JS_AtomToValue(ctx, prop);
sp[1] = JS_FALSE; sp[1] = JS_FALSE;

View file

@ -167,6 +167,29 @@ function test_for_in2()
assert(tab.toString() == "x,y"); assert(tab.toString() == "x,y");
} }
function test_for_in_proxy() {
let removed_key = "";
let target = {}
let proxy = new Proxy(target, {
ownKeys: function() {
return ["a", "b", "c"];
},
getOwnPropertyDescriptor: function(target, key) {
if (removed_key != "" && key == removed_key)
return undefined;
else
return { enumerable: true, configurable: true, value: this[key] };
}
});
let str = "";
for(let o in proxy) {
str += " " + o;
if (o == "a")
removed_key = "b";
}
assert(str == " a c");
}
function test_for_break() function test_for_break()
{ {
var i, c; var i, c;
@ -357,6 +380,7 @@ test_switch1();
test_switch2(); test_switch2();
test_for_in(); test_for_in();
test_for_in2(); test_for_in2();
test_for_in_proxy();
test_try_catch1(); test_try_catch1();
test_try_catch2(); test_try_catch2();