Browse Source

ES6 enumeration order for plain objects/arrays

Also some small performance optimization, e.g. when getting enumerates keys
(Object.keys() et al) write the keys into a preallocated dense array which is
allocated directly to size.
pull/1054/head
Sami Vaarala 8 years ago
parent
commit
fd2434485e
  1. 4
      src-input/duk_api_public.h.in
  2. 2
      src-input/duk_bi_object.c
  3. 428
      src-input/duk_hobject_enum.c

4
src-input/duk_api_public.h.in

@ -177,10 +177,10 @@ struct duk_time_components {
/* Enumeration flags for duk_enum() */
#define DUK_ENUM_INCLUDE_NONENUMERABLE (1 << 0) /* enumerate non-numerable properties in addition to enumerable */
#define DUK_ENUM_INCLUDE_INTERNAL (1 << 1) /* enumerate internal properties (regardless of enumerability) */
#define DUK_ENUM_INCLUDE_INTERNAL (1 << 1) /* enumerate internal properties */
#define DUK_ENUM_OWN_PROPERTIES_ONLY (1 << 2) /* don't walk prototype chain, only check own properties */
#define DUK_ENUM_ARRAY_INDICES_ONLY (1 << 3) /* only enumerate array indices */
#define DUK_ENUM_SORT_ARRAY_INDICES (1 << 4) /* sort array indices, use with DUK_ENUM_ARRAY_INDICES_ONLY */
#define DUK_ENUM_SORT_ARRAY_INDICES (1 << 4) /* sort array indices (applied to full enumeration result, including inherited array indices) */
#define DUK_ENUM_NO_PROXY_BEHAVIOR (1 << 5) /* enumerate a proxy object itself without invoking proxy behavior */
/* Compilation flags for duk_compile() and duk_eval() */

2
src-input/duk_bi_object.c

@ -575,7 +575,7 @@ DUK_INTERNAL duk_ret_t duk_bi_object_constructor_is_extensible(duk_context *ctx)
#if defined(DUK_USE_OBJECT_BUILTIN) || defined(DUK_USE_REFLECT_BUILTIN)
/* Shared helper for Object.getOwnPropertyNames() and Object.keys().
* Magic: 0=getOwnPropertyNames, 1=Object.keys.
* Magic: 0=Object.getOwnPropertyNames() or Reflect.ownKeys(), 1=Object.keys().
*/
DUK_INTERNAL duk_ret_t duk_bi_object_constructor_keys_shared(duk_context *ctx) {
duk_hthread *thr = (duk_hthread *) ctx;

428
src-input/duk_hobject_enum.c

@ -1,5 +1,5 @@
/*
* Hobject enumeration support.
* Object enumeration support.
*
* Creates an internal enumeration state object to be used e.g. with for-in
* enumeration. The state object contains a snapshot of target object keys
@ -21,7 +21,9 @@
/* XXX: identify enumeration target with an object index (not top of stack) */
/* must match exactly the number of internal properties inserted to enumerator */
/* First enumerated key index in enumerator object, must match exactly the
* number of control properties inserted to the enumerator.
*/
#define DUK__ENUM_START_INDEX 2
DUK_LOCAL const duk_uint16_t duk__bufobj_virtual_props[] = {
@ -31,131 +33,210 @@ DUK_LOCAL const duk_uint16_t duk__bufobj_virtual_props[] = {
DUK_STRIDX_BYTES_PER_ELEMENT
};
#if 0
/* Current implementation suffices for ES6 for now because there's no symbol
* sorting, so commented out for now.
*/
/*
* Helper to sort array index keys. The keys are in the enumeration object
* entry part, starting from DUK__ENUM_START_INDEX, and the entry part is dense.
*
* We use insertion sort because it is simple (leading to compact code,)
* works nicely in-place, and minimizes operations if data is already sorted
* or nearly sorted (which is a very common case here). It also minimizes
* the use of element comparisons in general. This is nice because element
* comparisons here involve re-parsing the string keys into numbers each
* time, which is naturally very expensive.
* Helper to sort enumeration keys using a callback for pairwise duk_hstring
* comparisons. The keys are in the enumeration object entry part, starting
* from DUK__ENUM_START_INDEX, and the entry part is dense. Entry part values
* are all "true", e.g. "1" -> true, "3" -> true, "foo" -> true "2" -> true,
* so it suffices to just switch keys without switching values.
*
* Note that the entry part values are all "true", e.g.
*
* "1" -> true, "3" -> true, "2" -> true
* Insertion sort is used because (1) it's simple and compact, (2) works
* in-place, (3) minimizes operations if data is already nearly sorted,
* (4) doesn't reorder elements considered equal.
* http://en.wikipedia.org/wiki/Insertion_sort
*/
typedef duk_bool_t (*duk__sort_compare_fn)(duk_hstring *a, duk_hstring *b, duk_uarridx_t val_b);
DUK_LOCAL duk_bool_t duk__sort_compare_es6(duk_hstring *a, duk_hstring *b, duk_uarridx_t val_b) {
duk_uarridx_t val_a;
DUK_ASSERT(a != NULL);
DUK_ASSERT(b != NULL);
DUK_UNREF(b); /* Not actually needed now, val_b suffices. */
/* ES6 [[OwnPropertyKeys]] enumeration order for ordinary objects:
* (1) array indices in ascending order, (2) non-array-index keys in
* insertion order, symbols in insertion order:
* http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys.
*
* This rule is applied to "own properties" at each inheritance level;
* non-duplicate parent keys always follow child keys. For example,
* an inherited array index will enumerate -after- a symbol in the
* child.
*/
val_a = DUK_HSTRING_GET_ARRIDX_FAST(a);
if (val_b < val_a) {
/* Covers:
* - Both keys are array indices and a > b: don't insert here.
* - 'b' is array index, 'a' is not: don't insert here.
*/
return 0;
} else {
/* Covers:
* val_a < val_b where:
* - Both keys are array indices and a < b: insert here.
* - 'a' is array index, 'b' is not: insert here.
* val_a == val_b where:
* - Both keys are array indices and a == b: insert here
* (shouldn't actually happen, can't have non-duplicate
* identical array index keys).
* - Neither key is an array index: insert here, keeps key
* order regardless of the keys themselves.
*/
return 1;
}
}
DUK_LOCAL void duk__sort_enum_keys_es6(duk_hthread *thr, duk_hobject *h_obj, duk_int_fast32_t idx_start, duk_int_fast32_t idx_end) {
duk_hstring **keys;
duk_int_fast32_t idx;
DUK_ASSERT(h_obj != NULL);
DUK_ASSERT(idx_start >= DUK__ENUM_START_INDEX);
DUK_ASSERT(idx_end >= idx_start);
DUK_UNREF(thr);
if (idx_end <= idx_start + 1) {
return; /* Zero or one element(s). */
}
keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, h_obj);
for (idx = idx_start + 1; idx < idx_end; idx++) {
duk_hstring *h_curr;
duk_int_fast32_t idx_insert;
duk_uarridx_t val_curr;
h_curr = keys[idx];
DUK_ASSERT(h_curr != NULL);
/* Scan backwards for insertion place. This works very well
* when the elements are nearly in order which is the common
* (and optimized for) case.
*/
val_curr = DUK_HSTRING_GET_ARRIDX_FAST(h_curr); /* Remains same during scanning. */
for (idx_insert = idx - 1; idx_insert >= idx_start; idx_insert--) {
duk_hstring *h_insert;
h_insert = keys[idx_insert];
DUK_ASSERT(h_insert != NULL);
/* XXX: fixed callback rather than a callback argument; only
* one argument used and using a callback argument doesn't
* cause e.g. gcc to inline the callback.
*/
if (duk__sort_compare_es6(h_insert, h_curr, val_curr)) {
break;
}
}
/* If we're out of indices, idx_insert == idx_start - 1 and idx_insert++
* brings us back to idx_start.
*/
idx_insert++;
DUK_ASSERT(idx_insert >= 0 && idx_insert <= idx);
/* .-- p_insert .-- p_curr
* v v
* | ... | insert | ... | curr
*/
/* This could also done when the keys are in order, i.e.
* idx_insert == idx. The result would be an unnecessary
* memmove() but we use an explicit check because the keys
* are very often in order already.
*/
if (idx != idx_insert) {
DUK_MEMMOVE((void *) (keys + idx_insert + 1),
(const void *) (keys + idx_insert),
(size_t) ((idx - idx_insert) * sizeof(duk_hstring *)));
keys[idx_insert] = h_curr;
}
}
}
#endif /* disabled */
/*
* Helper to sort keys into ES6 [[OwnPropertyKeys]] enumeration order:
* array keys in ascending order first, followed by keys in insertion
* order, followed by symbols in insertion order (not handled here).
* Insertion sort based.
*
* so it suffices to only work in the key part without exchanging any keys,
* simplifying the sort.
* This algorithm nominally sorts array indices, but because the "no array
* index" marker is higher than any array index, non-array-index keys are
* sorted after array indices. Non-array-index keys are also considered
* equal for sorting which means that their order is kept as is, so the end
* result matches ES6 [[OwnPropertyKeys]].
*
* Insertion sort is used because (1) it's simple and compact, (2) works
* in-place, (3) minimizes operations if data is already nearly sorted,
* (4) doesn't reorder elements considered equal.
* http://en.wikipedia.org/wiki/Insertion_sort
*
* (Compiles to about 160 bytes now as a stand-alone function.)
*/
DUK_LOCAL void duk__sort_array_indices(duk_hthread *thr, duk_hobject *h_obj) {
DUK_LOCAL void duk__sort_enum_keys_es6(duk_hthread *thr, duk_hobject *h_obj, duk_int_fast32_t idx_start, duk_int_fast32_t idx_end) {
duk_hstring **keys;
duk_hstring **p_curr, **p_insert, **p_end;
duk_hstring *h_curr;
duk_uarridx_t val_highest, val_curr, val_insert;
DUK_ASSERT(h_obj != NULL);
DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(h_obj) >= 2); /* control props */
DUK_ASSERT(idx_start >= DUK__ENUM_START_INDEX);
DUK_ASSERT(idx_end >= idx_start);
DUK_UNREF(thr);
if (DUK_HOBJECT_GET_ENEXT(h_obj) <= 1 + DUK__ENUM_START_INDEX) {
return;
if (idx_end <= idx_start + 1) {
return; /* Zero or one element(s). */
}
keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, h_obj);
p_end = keys + DUK_HOBJECT_GET_ENEXT(h_obj);
keys += DUK__ENUM_START_INDEX;
DUK_DDD(DUK_DDDPRINT("keys=%p, p_end=%p (after skipping enum props)",
(void *) keys, (void *) p_end));
#if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2)
{
duk_uint_fast32_t i;
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h_obj); i++) {
DUK_DDD(DUK_DDDPRINT("initial: %ld %p -> %!O",
(long) i,
(void *) DUK_HOBJECT_E_GET_KEY_PTR(thr->heap, h_obj, i),
(duk_heaphdr *) DUK_HOBJECT_E_GET_KEY(thr->heap, h_obj, i)));
}
}
#endif
val_highest = DUK_HSTRING_GET_ARRIDX_SLOW(keys[0]);
for (p_curr = keys + 1; p_curr < p_end; p_curr++) {
p_curr = keys + idx_start;
val_highest = DUK_HSTRING_GET_ARRIDX_SLOW(*p_curr);
for (p_curr++, p_end = keys + idx_end; p_curr < p_end; p_curr++) {
DUK_ASSERT(*p_curr != NULL);
val_curr = DUK_HSTRING_GET_ARRIDX_SLOW(*p_curr);
if (val_curr >= val_highest) {
DUK_DDD(DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%ld, val_curr=%ld -> "
"already in correct order, next",
(void *) p_curr, (void *) p_end, (long) val_highest, (long) val_curr));
val_highest = val_curr;
continue;
}
DUK_DDD(DUK_DDDPRINT("p_curr=%p, p_end=%p, val_highest=%ld, val_curr=%ld -> "
"needs to be inserted",
(void *) p_curr, (void *) p_end, (long) val_highest, (long) val_curr));
/* Needs to be inserted; scan backwards, since we optimize
* for the case where elements are nearly in order.
*/
p_insert = p_curr - 1;
p_insert = p_curr;
for (;;) {
p_insert--; /* Start from p_curr - 1. */
val_insert = DUK_HSTRING_GET_ARRIDX_SLOW(*p_insert);
if (val_insert < val_curr) {
DUK_DDD(DUK_DDDPRINT("p_insert=%p, val_insert=%ld, val_curr=%ld -> insert after this",
(void *) p_insert, (long) val_insert, (long) val_curr));
p_insert++;
break;
}
if (p_insert == keys) {
DUK_DDD(DUK_DDDPRINT("p_insert=%p -> out of keys, insert to beginning", (void *) p_insert));
if (p_insert == keys + idx_start) {
break;
}
DUK_DDD(DUK_DDDPRINT("p_insert=%p, val_insert=%ld, val_curr=%ld -> search backwards",
(void *) p_insert, (long) val_insert, (long) val_curr));
p_insert--;
}
DUK_DDD(DUK_DDDPRINT("final p_insert=%p", (void *) p_insert));
/* .-- p_insert .-- p_curr
* v v
* | ... | insert | ... | curr
*/
h_curr = *p_curr;
DUK_DDD(DUK_DDDPRINT("memmove: dest=%p, src=%p, size=%ld, h_curr=%p",
(void *) (p_insert + 1), (void *) p_insert,
(long) (p_curr - p_insert), (void *) h_curr));
DUK_MEMMOVE((void *) (p_insert + 1),
(const void *) p_insert,
(size_t) ((p_curr - p_insert) * sizeof(duk_hstring *)));
*p_insert = h_curr;
/* keep val_highest */
}
#if defined(DUK_USE_DEBUG_LEVEL) && (DUK_USE_DEBUG_LEVEL >= 2)
{
duk_uint_fast32_t i;
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h_obj); i++) {
DUK_DDD(DUK_DDDPRINT("final: %ld %p -> %!O",
(long) i,
(void *) DUK_HOBJECT_E_GET_KEY_PTR(thr->heap, h_obj, i),
(duk_heaphdr *) DUK_HOBJECT_E_GET_KEY(thr->heap, h_obj, i)));
}
}
#endif
}
/*
@ -167,6 +248,16 @@ DUK_LOCAL void duk__sort_array_indices(duk_hthread *thr, duk_hobject *h_obj) {
* scan would be needed to eliminate duplicates found in the prototype chain.
*/
DUK_LOCAL void duk__add_enum_key(duk_context *ctx, duk_hstring *k) {
duk_push_hstring(ctx, k);
duk_push_true(ctx);
duk_put_prop(ctx, -3);
}
DUK_LOCAL void duk__add_enum_key_stridx(duk_context *ctx, duk_small_uint_t stridx) {
duk__add_enum_key(ctx, DUK_HTHREAD_GET_STRING((duk_hthread *) ctx, stridx));
}
DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint_t enum_flags) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *enum_target;
@ -178,19 +269,16 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
duk_hobject *h_trap_result;
#endif
duk_uint_fast32_t i, len; /* used for array, stack, and entry indices */
duk_uint_fast32_t sort_start_index;
DUK_ASSERT(ctx != NULL);
DUK_DDD(DUK_DDDPRINT("create enumerator, stack top: %ld", (long) duk_get_top(ctx)));
enum_target = duk_require_hobject(ctx, -1);
DUK_ASSERT(enum_target != NULL);
duk_push_object_internal(ctx);
res = duk_known_hobject(ctx, -1);
DUK_DDD(DUK_DDDPRINT("created internal object"));
/* [enum_target res] */
/* Target must be stored so that we can recheck whether or not
@ -285,13 +373,36 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
#endif /* DUK_USE_ES6_PROXY */
curr = enum_target;
sort_start_index = DUK__ENUM_START_INDEX;
DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(res) == DUK__ENUM_START_INDEX);
while (curr) {
duk_uint_fast32_t sort_end_index;
#if !defined(DUK_USE_PREFER_SIZE)
duk_bool_t need_sort = 0;
#endif
/* Enumeration proceeds by inheritance level. Virtual
* properties need to be handled specially, followed by
* array part, and finally entry part.
*
* If there are array index keys in the entry part or any
* other risk of the ES6 [[OwnPropertyKeys]] order being
* violated, need_sort is set and an explicit ES6 sort is
* done for the inheritance level.
*/
/* XXX: inheriting from proxy */
/*
* Virtual properties.
*
* String and buffer indices are virtual and always enumerable,
* 'length' is virtual and non-enumerable. Array and arguments
* object props have special behavior but are concrete.
*
* String and buffer objects don't have an array part so as long
* as virtual array index keys are enumerated first, we don't
* need to set need_sort.
*/
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
@ -333,13 +444,13 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
for (i = 0; i < len; i++) {
duk_hstring *k;
/* This is a bit fragile: the string is not
* reachable until it is pushed by the helper.
*/
k = duk_heap_string_intern_u32_checked(thr, i);
DUK_ASSERT(k);
duk_push_hstring(ctx, k);
duk_push_true(ctx);
/* [enum_target res key true] */
duk_put_prop(ctx, -3);
duk__add_enum_key(ctx, k);
/* [enum_target res] */
}
@ -365,26 +476,18 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
}
for (i = 0; i < n; i++) {
duk_push_hstring_stridx(ctx, duk__bufobj_virtual_props[i]);
duk_push_true(ctx);
duk_put_prop(ctx, -3);
duk__add_enum_key_stridx(ctx, duk__bufobj_virtual_props[i]);
}
}
} else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(curr)) {
if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
duk_push_true(ctx);
duk_put_prop(ctx, -3);
duk__add_enum_key_stridx(ctx, DUK_STRIDX_LENGTH);
}
}
/*
* Array part
*
* Note: ordering between array and entry part must match 'abandon array'
* behavior in duk_hobject_props.c: key order after an array is abandoned
* must be the same.
*/
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(curr); i++) {
@ -395,25 +498,18 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
if (DUK_TVAL_IS_UNUSED(tv)) {
continue;
}
k = duk_heap_string_intern_u32_checked(thr, i);
k = duk_heap_string_intern_u32_checked(thr, i); /* Fragile reachability. */
DUK_ASSERT(k);
duk_push_hstring(ctx, k);
duk_push_true(ctx);
/* [enum_target res key true] */
duk_put_prop(ctx, -3);
duk__add_enum_key(ctx, k);
/* [enum_target res] */
}
if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(curr)) {
/* Array .length comes after numeric indices. */
if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
duk_push_true(ctx);
duk_put_prop(ctx, -3);
duk__add_enum_key_stridx(ctx, DUK_STRIDX_LENGTH);
}
}
@ -428,31 +524,67 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
if (!k) {
continue;
}
if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i) &&
!(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) {
if (!(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) &&
!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i)) {
continue;
}
if (DUK_HSTRING_HAS_INTERNAL(k) &&
!(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) {
if (!(enum_flags & DUK_ENUM_INCLUDE_INTERNAL) &&
DUK_HSTRING_HAS_INTERNAL(k)) {
continue;
}
if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) &&
(DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) {
continue;
if (DUK_HSTRING_HAS_ARRIDX(k)) {
/* This in currently only possible if the
* object has no array part: the array part
* is exhaustive when it is present.
*/
#if !defined(DUK_USE_PREFER_SIZE)
need_sort = 1;
#endif
} else {
if (enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) {
continue;
}
}
DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, curr, i) ||
!DUK_TVAL_IS_UNUSED(&DUK_HOBJECT_E_GET_VALUE_PTR(thr->heap, curr, i)->v));
duk_push_hstring(ctx, k);
duk_push_true(ctx);
/* [enum_target res key true] */
duk_put_prop(ctx, -3);
duk__add_enum_key(ctx, k);
/* [enum_target res] */
}
/* Sort enumerated keys according to ES6 requirements for
* the "inheritance level" just processed. This is far from
* optimal, ES6 semantics could be achieved more efficiently
* by handling array index string keys (and symbol keys)
* specially above in effect doing the sort inline.
*
* Skip the sort if array index sorting is requested because
* we must consider all keys, also inherited, so an explicit
* sort is done for the whole result after we're done with the
* prototype chain.
*
* Also skip the sort if need_sort == 0, i.e. we know for
* certain that the enumerated order is already correct.
*/
sort_end_index = DUK_HOBJECT_GET_ENEXT(res);
if (!(enum_flags & DUK_ENUM_SORT_ARRAY_INDICES)) {
#if defined(DUK_USE_PREFER_SIZE)
duk__sort_enum_keys_es6(thr, res, sort_start_index, sort_end_index);
#else
if (need_sort) {
DUK_DDD(DUK_DDDPRINT("need to sort"));
duk__sort_enum_keys_es6(thr, res, sort_start_index, sort_end_index);
} else {
DUK_DDD(DUK_DDDPRINT("no need to sort"));
}
#endif
}
sort_start_index = sort_end_index;
if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) {
break;
}
@ -466,29 +598,18 @@ DUK_INTERNAL void duk_hobject_enumerator_create(duk_context *ctx, duk_small_uint
/* [res] */
if ((enum_flags & (DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) ==
(DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES)) {
/*
* Some E5/E5.1 algorithms require that array indices are iterated
* in a strictly ascending order. This is the case for e.g.
* Array.prototype.forEach() and JSON.stringify() PropertyList
* handling.
*
* To ensure this property for arrays with an array part (and
* arbitrary objects too, since e.g. forEach() can be applied
* to an array), the caller can request that we sort the keys
* here.
if (enum_flags & DUK_ENUM_SORT_ARRAY_INDICES) {
/* Some E5/E5.1 algorithms require that array indices are iterated
* in a strictly ascending order. This is the case for e.g.
* Array.prototype.forEach() and JSON.stringify() PropertyList
* handling. The caller can request an explicit sort in these
* cases.
*/
/* XXX: avoid this at least when enum_target is an Array, it has an
* array part, and no ancestor properties were included? Not worth
* it for JSON, but maybe worth it for forEach().
/* Sort to ES6 order which works for pure array incides but
* also for mixed keys.
*/
/* XXX: may need a 'length' filter for forEach()
*/
DUK_DDD(DUK_DDDPRINT("sort array indices by caller request"));
duk__sort_array_indices(thr, res);
duk__sort_enum_keys_es6(thr, res, DUK__ENUM_START_INDEX, DUK_HOBJECT_GET_ENEXT(res));
}
#if defined(DUK_USE_ES6_PROXY)
@ -607,8 +728,10 @@ DUK_INTERNAL duk_bool_t duk_hobject_enumerator_next(duk_context *ctx, duk_bool_t
DUK_INTERNAL duk_ret_t duk_hobject_get_enumerated_keys(duk_context *ctx, duk_small_uint_t enum_flags) {
duk_hthread *thr = (duk_hthread *) ctx;
duk_hobject *e;
duk_uint_fast32_t i;
duk_uint_fast32_t idx;
duk_harray *a;
duk_hstring **keys;
duk_tval *tv;
duk_uint_fast32_t count;
DUK_ASSERT(ctx != NULL);
DUK_ASSERT(duk_get_hobject(ctx, -1) != NULL);
@ -620,23 +743,34 @@ DUK_INTERNAL duk_ret_t duk_hobject_get_enumerated_keys(duk_context *ctx, duk_sma
*/
duk_hobject_enumerator_create(ctx, enum_flags);
duk_push_array(ctx);
e = duk_known_hobject(ctx, -1);
/* [enum_target enum res] */
e = duk_known_hobject(ctx, -2);
/* Create dense result array to exact size. */
DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(e) >= DUK__ENUM_START_INDEX);
count = (duk_uint32_t) (DUK_HOBJECT_GET_ENEXT(e) - DUK__ENUM_START_INDEX);
a = duk_push_harray_with_size(ctx, count);
DUK_ASSERT(a != NULL);
DUK_ASSERT(DUK_HOBJECT_GET_ASIZE((duk_hobject *) a) == count);
DUK_ASSERT(a->length == count);
tv = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a);
/* Fill result array, no side effects. */
idx = 0;
for (i = DUK__ENUM_START_INDEX; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(e); i++) {
keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, e);
keys += DUK__ENUM_START_INDEX;
while (count-- > 0) {
duk_hstring *k;
k = DUK_HOBJECT_E_GET_KEY(thr->heap, e, i);
DUK_ASSERT(k); /* enumerator must have no keys deleted */
k = *keys++;
DUK_ASSERT(k != NULL); /* enumerator must have no keys deleted */
/* [enum_target enum res] */
duk_push_hstring(ctx, k);
duk_put_prop_index(ctx, -2, idx);
idx++;
DUK_TVAL_SET_STRING(tv, k);
tv++;
DUK_HSTRING_INCREF(thr, k);
}
/* [enum_target enum res] */

Loading…
Cancel
Save