You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

699 lines
22 KiB

/*
* 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
* and internal control state for enumeration. Enumerator flags allow caller
* to e.g. request internal/non-enumerable properties, and to enumerate only
* "own" properties.
*
* Also creates the result value for e.g. Object.keys() based on the same
* internal structure.
*
* This snapshot-based enumeration approach is used to simplify enumeration:
* non-snapshot-based approaches are difficult to reconcile with mutating
* the enumeration target, running multiple long-lived enumerators at the
* same time, garbage collection details, etc. The downside is that the
* enumerator object is memory inefficient especially for iterating arrays.
*/
#include "duk_internal.h"
/* XXX: identify enumeration target with an object index (not top of stack) */
/* 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
/* Current implementation suffices for ES2015 for now because there's no symbol
* sorting, so commented out for now.
*/
/*
* 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.
*
* ES2015 [[OwnPropertyKeys]] enumeration order for ordinary objects:
* (1) array indices in ascending order,
* (2) non-array-index keys in insertion order, and
* (3) 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.
*
* 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
*/
/* Sort key, must hold array indices, "not array index" marker, and one more
* higher value for symbols.
*/
#if !defined(DUK_USE_SYMBOL_BUILTIN)
typedef duk_uint32_t duk__sort_key_t;
#elif defined(DUK_USE_64BIT_OPS)
typedef duk_uint64_t duk__sort_key_t;
#else
typedef duk_double_t duk__sort_key_t;
#endif
/* Get sort key for a duk_hstring. */
DUK_LOCAL duk__sort_key_t duk__hstring_sort_key(duk_hstring *x) {
duk__sort_key_t val;
/* For array indices [0,0xfffffffe] use the array index as is.
* For strings, use 0xffffffff, the marker 'arridx' already in
* duk_hstring. For symbols, any value above 0xffffffff works,
* as long as it is the same for all symbols; currently just add
* the masked flag field into the arridx temporary.
*/
DUK_ASSERT(x != NULL);
DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(x) || DUK_HSTRING_GET_ARRIDX_FAST(x) == DUK_HSTRING_NO_ARRAY_INDEX);
val = (duk__sort_key_t) DUK_HSTRING_GET_ARRIDX_FAST(x);
#if defined(DUK_USE_SYMBOL_BUILTIN)
val = val + (duk__sort_key_t) (DUK_HEAPHDR_GET_FLAGS_RAW((duk_heaphdr *) x) & DUK_HSTRING_FLAG_SYMBOL);
#endif
return (duk__sort_key_t) val;
}
/* Insert element 'b' after element 'a'? */
DUK_LOCAL duk_bool_t duk__sort_compare_es6(duk_hstring *a, duk_hstring *b, duk__sort_key_t val_b) {
duk__sort_key_t val_a;
DUK_ASSERT(a != NULL);
DUK_ASSERT(b != NULL);
DUK_UNREF(b); /* Not actually needed now, val_b suffices. */
val_a = duk__hstring_sort_key(a);
if (val_a > val_b) {
return 0;
} else {
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__sort_key_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_sort_key(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);
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;
}
}
}
/*
* Create an internal enumerator object E, which has its keys ordered
* to match desired enumeration ordering. Also initialize internal control
* properties for enumeration.
*
* Note: if an array was used to hold enumeration keys instead, an array
* scan would be needed to eliminate duplicates found in the prototype chain.
*/
DUK_LOCAL void duk__add_enum_key(duk_hthread *thr, duk_hstring *k) {
/* 'k' may be unreachable on entry so must push without any
* potential for GC.
*/
duk_push_hstring(thr, k);
duk_push_true(thr);
duk_put_prop(thr, -3);
}
DUK_LOCAL void duk__add_enum_key_stridx(duk_hthread *thr, duk_small_uint_t stridx) {
duk__add_enum_key(thr, DUK_HTHREAD_GET_STRING(thr, stridx));
}
DUK_INTERNAL void duk_hobject_enumerator_create(duk_hthread *thr, duk_small_uint_t enum_flags) {
duk_hobject *enum_target;
duk_hobject *curr;
duk_hobject *res;
#if defined(DUK_USE_ES6_PROXY)
duk_hobject *h_proxy_target;
duk_hobject *h_proxy_handler;
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(thr != NULL);
enum_target = duk_require_hobject(thr, -1);
DUK_ASSERT(enum_target != NULL);
duk_push_bare_object(thr);
res = duk_known_hobject(thr, -1);
/* [enum_target res] */
/* Target must be stored so that we can recheck whether or not
* keys still exist when we enumerate. This is not done if the
* enumeration result comes from a proxy trap as there is no
* real object to check against.
*/
duk_push_hobject(thr, enum_target);
duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_TARGET);
/* Initialize index so that we skip internal control keys. */
duk_push_int(thr, DUK__ENUM_START_INDEX);
duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_NEXT);
/*
* Proxy object handling
*/
#if defined(DUK_USE_ES6_PROXY)
if (DUK_LIKELY((enum_flags & DUK_ENUM_NO_PROXY_BEHAVIOR) != 0)) {
goto skip_proxy;
}
if (DUK_LIKELY(!duk_hobject_proxy_check(enum_target,
&h_proxy_target,
&h_proxy_handler))) {
goto skip_proxy;
}
/* XXX: share code with Object.keys() Proxy handling */
/* In ES2015 for-in invoked the "enumerate" trap; in ES2016 "enumerate"
* has been obsoleted and "ownKeys" is used instead.
*/
DUK_DDD(DUK_DDDPRINT("proxy enumeration"));
duk_push_hobject(thr, h_proxy_handler);
if (!duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_OWN_KEYS)) {
/* No need to replace the 'enum_target' value in stack, only the
* enum_target reference. This also ensures that the original
* enum target is reachable, which keeps the proxy and the proxy
* target reachable. We do need to replace the internal _Target.
*/
DUK_DDD(DUK_DDDPRINT("no ownKeys trap, enumerate proxy target instead"));
DUK_DDD(DUK_DDDPRINT("h_proxy_target=%!O", (duk_heaphdr *) h_proxy_target));
enum_target = h_proxy_target;
duk_push_hobject(thr, enum_target); /* -> [ ... enum_target res handler undefined target ] */
duk_put_prop_stridx_short(thr, -4, DUK_STRIDX_INT_TARGET);
duk_pop_2(thr); /* -> [ ... enum_target res ] */
goto skip_proxy;
}
/* [ ... enum_target res handler trap ] */
duk_insert(thr, -2);
duk_push_hobject(thr, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */
duk_call_method(thr, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */
h_trap_result = duk_require_hobject(thr, -1);
DUK_UNREF(h_trap_result);
duk_proxy_ownkeys_postprocess(thr, h_proxy_target, enum_flags);
/* -> [ ... enum_target res trap_result keys_array ] */
/* Copy cleaned up trap result keys into the enumerator object. */
/* XXX: result is a dense array; could make use of that. */
DUK_ASSERT(duk_is_array(thr, -1));
len = (duk_uint_fast32_t) duk_get_length(thr, -1);
for (i = 0; i < len; i++) {
(void) duk_get_prop_index(thr, -1, i);
DUK_ASSERT(duk_is_string(thr, -1)); /* postprocess cleaned up */
/* [ ... enum_target res trap_result keys_array val ] */
duk_push_true(thr);
/* [ ... enum_target res trap_result keys_array val true ] */
duk_put_prop(thr, -5);
}
/* [ ... enum_target res trap_result keys_array ] */
duk_pop_2(thr);
duk_remove_m2(thr);
/* [ ... res ] */
/* The internal _Target property is kept pointing to the original
* enumeration target (the proxy object), so that the enumerator
* 'next' operation can read property values if so requested. The
* fact that the _Target is a proxy disables key existence check
* during enumeration.
*/
DUK_DDD(DUK_DDDPRINT("proxy enumeration, final res: %!O", (duk_heaphdr *) res));
goto compact_and_return;
skip_proxy:
#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 ES2015 [[OwnPropertyKeys]] order being
* violated, need_sort is set and an explicit ES2015 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)
if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_IS_BUFOBJ(curr)) {
#else
if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) {
#endif
duk_bool_t have_length = 1;
/* String and buffer enumeration behavior is identical now,
* so use shared handler.
*/
if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr)) {
duk_hstring *h_val;
h_val = duk_hobject_get_internal_value_string(thr->heap, curr);
DUK_ASSERT(h_val != NULL); /* string objects must not created without internal value */
len = (duk_uint_fast32_t) DUK_HSTRING_GET_CHARLEN(h_val);
}
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
else {
duk_hbufobj *h_bufobj;
DUK_ASSERT(DUK_HOBJECT_IS_BUFOBJ(curr));
h_bufobj = (duk_hbufobj *) curr;
if (h_bufobj == NULL || !h_bufobj->is_typedarray) {
/* Zero length seems like a good behavior for neutered buffers.
* ArrayBuffer (non-view) and DataView don't have index properties
* or .length property.
*/
len = 0;
have_length = 0;
} else {
/* There's intentionally no check for
* current underlying buffer length.
*/
len = (duk_uint_fast32_t) (h_bufobj->length >> h_bufobj->shift);
}
}
#endif /* DUK_USE_BUFFEROBJECT_SUPPORT */
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_strtable_intern_u32_checked(thr, i);
DUK_ASSERT(k);
duk__add_enum_key(thr, k);
/* [enum_target res] */
}
/* 'length' and other virtual properties are not
* enumerable, but are included if non-enumerable
* properties are requested.
*/
if (have_length && (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) {
duk__add_enum_key_stridx(thr, DUK_STRIDX_LENGTH);
}
}
/*
* Array part
*/
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(curr); i++) {
duk_hstring *k;
duk_tval *tv;
tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, curr, i);
if (DUK_TVAL_IS_UNUSED(tv)) {
continue;
}
k = duk_heap_strtable_intern_u32_checked(thr, i); /* Fragile reachability. */
DUK_ASSERT(k);
duk__add_enum_key(thr, 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__add_enum_key_stridx(thr, DUK_STRIDX_LENGTH);
}
}
/*
* Entries part
*/
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(curr); i++) {
duk_hstring *k;
k = DUK_HOBJECT_E_GET_KEY(thr->heap, curr, i);
if (!k) {
continue;
}
if (!(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) &&
!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i)) {
continue;
}
if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) {
if (!(enum_flags & DUK_ENUM_INCLUDE_HIDDEN) &&
DUK_HSTRING_HAS_HIDDEN(k)) {
continue;
}
if (!(enum_flags & DUK_ENUM_INCLUDE_SYMBOLS)) {
continue;
}
#if !defined(DUK_USE_PREFER_SIZE)
need_sort = 1;
#endif
} else {
DUK_ASSERT(!DUK_HSTRING_HAS_HIDDEN(k)); /* would also have symbol flag */
if (enum_flags & DUK_ENUM_EXCLUDE_STRINGS) {
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__add_enum_key(thr, k);
/* [enum_target res] */
}
/* Sort enumerated keys according to ES2015 requirements for
* the "inheritance level" just processed. This is far from
* optimal, ES2015 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;
}
curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
}
/* [enum_target res] */
duk_remove_m2(thr);
/* [res] */
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.
*/
/* Sort to ES2015 order which works for pure array incides but
* also for mixed keys.
*/
duk__sort_enum_keys_es6(thr, res, DUK__ENUM_START_INDEX, DUK_HOBJECT_GET_ENEXT(res));
}
#if defined(DUK_USE_ES6_PROXY)
compact_and_return:
#endif
/* compact; no need to seal because object is internal */
duk_hobject_compact_props(thr, res);
DUK_DDD(DUK_DDDPRINT("created enumerator object: %!iT", (duk_tval *) duk_get_tval(thr, -1)));
}
/*
* Returns non-zero if a key and/or value was enumerated, and:
*
* [enum] -> [key] (get_value == 0)
* [enum] -> [key value] (get_value == 1)
*
* Returns zero without pushing anything on the stack otherwise.
*/
DUK_INTERNAL duk_bool_t duk_hobject_enumerator_next(duk_hthread *thr, duk_bool_t get_value) {
duk_hobject *e;
duk_hobject *enum_target;
duk_hstring *res = NULL;
duk_uint_fast32_t idx;
duk_bool_t check_existence;
DUK_ASSERT(thr != NULL);
/* [... enum] */
e = duk_require_hobject(thr, -1);
/* XXX use get tval ptr, more efficient */
duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_NEXT);
idx = (duk_uint_fast32_t) duk_require_uint(thr, -1);
duk_pop(thr);
DUK_DDD(DUK_DDDPRINT("enumeration: index is: %ld", (long) idx));
/* Enumeration keys are checked against the enumeration target (to see
* that they still exist). In the proxy enumeration case _Target will
* be the proxy, and checking key existence against the proxy is not
* required (or sensible, as the keys may be fully virtual).
*/
duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_TARGET);
enum_target = duk_require_hobject(thr, -1);
DUK_ASSERT(enum_target != NULL);
#if defined(DUK_USE_ES6_PROXY)
check_existence = (!DUK_HOBJECT_IS_PROXY(enum_target));
#else
check_existence = 1;
#endif
duk_pop(thr); /* still reachable */
DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT",
(duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(thr, -1)));
/* no array part */
for (;;) {
duk_hstring *k;
if (idx >= DUK_HOBJECT_GET_ENEXT(e)) {
DUK_DDD(DUK_DDDPRINT("enumeration: ran out of elements"));
break;
}
/* we know these because enum objects are internally created */
k = DUK_HOBJECT_E_GET_KEY(thr->heap, e, idx);
DUK_ASSERT(k != NULL);
DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, e, idx));
DUK_ASSERT(!DUK_TVAL_IS_UNUSED(&DUK_HOBJECT_E_GET_VALUE(thr->heap, e, idx).v));
idx++;
/* recheck that the property still exists */
if (check_existence && !duk_hobject_hasprop_raw(thr, enum_target, k)) {
DUK_DDD(DUK_DDDPRINT("property deleted during enumeration, skip"));
continue;
}
DUK_DDD(DUK_DDDPRINT("enumeration: found element, key: %!O", (duk_heaphdr *) k));
res = k;
break;
}
DUK_DDD(DUK_DDDPRINT("enumeration: updating next index to %ld", (long) idx));
duk_push_u32(thr, (duk_uint32_t) idx);
duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_INT_NEXT);
/* [... enum] */
if (res) {
duk_push_hstring(thr, res);
if (get_value) {
duk_push_hobject(thr, enum_target);
duk_dup_m2(thr); /* -> [... enum key enum_target key] */
duk_get_prop(thr, -2); /* -> [... enum key enum_target val] */
duk_remove_m2(thr); /* -> [... enum key val] */
duk_remove(thr, -3); /* -> [... key val] */
} else {
duk_remove_m2(thr); /* -> [... key] */
}
return 1;
} else {
duk_pop(thr); /* -> [...] */
return 0;
}
}
/*
* Get enumerated keys in an Ecmascript array. Matches Object.keys() behavior
* described in E5 Section 15.2.3.14.
*/
DUK_INTERNAL duk_ret_t duk_hobject_get_enumerated_keys(duk_hthread *thr, duk_small_uint_t enum_flags) {
duk_hobject *e;
duk_hstring **keys;
duk_tval *tv;
duk_uint_fast32_t count;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(duk_get_hobject(thr, -1) != NULL);
/* Create a temporary enumerator to get the (non-duplicated) key list;
* the enumerator state is initialized without being needed, but that
* has little impact.
*/
duk_hobject_enumerator_create(thr, enum_flags);
e = duk_known_hobject(thr, -1);
/* [enum_target enum res] */
/* 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);
/* XXX: uninit would be OK */
tv = duk_push_harray_with_size_outptr(thr, count);
DUK_ASSERT(count == 0 || tv != NULL);
/* Fill result array, no side effects. */
keys = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, e);
keys += DUK__ENUM_START_INDEX;
while (count-- > 0) {
duk_hstring *k;
k = *keys++;
DUK_ASSERT(k != NULL); /* enumerator must have no keys deleted */
DUK_TVAL_SET_STRING(tv, k);
tv++;
DUK_HSTRING_INCREF(thr, k);
}
/* [enum_target enum res] */
duk_remove_m2(thr);
/* [enum_target res] */
return 1; /* return 1 to allow callers to tail call */
}