707 lines
22 KiB
C
707 lines
22 KiB
C
/*
|
|
* 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 "third_party/duktape/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); /* Target is bare, plain put OK. */
|
|
|
|
/* 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); /* Target is bare, plain put OK. */
|
|
|
|
/*
|
|
* 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); /* Target is bare, plain put OK. */
|
|
|
|
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, (duk_uarridx_t) 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
|
|
duk_bool_t cond;
|
|
|
|
/* 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)
|
|
cond = DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr) || DUK_HOBJECT_IS_BUFOBJ(curr);
|
|
#else
|
|
cond = DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr);
|
|
#endif
|
|
cond = cond && !(enum_flags & DUK_ENUM_EXCLUDE_STRINGS);
|
|
if (cond) {
|
|
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, (duk_uint32_t) 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
|
|
*/
|
|
|
|
cond = !(enum_flags & DUK_ENUM_EXCLUDE_STRINGS);
|
|
if (cond) {
|
|
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, (duk_uint32_t) 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, (duk_int_fast32_t) sort_start_index, (duk_int_fast32_t) sort_end_index);
|
|
#else
|
|
if (need_sort) {
|
|
DUK_DDD(DUK_DDDPRINT("need to sort"));
|
|
duk__sort_enum_keys_es6(thr, res, (duk_int_fast32_t) sort_start_index, (duk_int_fast32_t) 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_int_fast32_t) DUK__ENUM_START_INDEX, (duk_int_fast32_t) 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_xget_owndataprop_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, (duk_uint32_t) count);
|
|
DUK_ASSERT(count == 0 || tv != NULL);
|
|
DUK_ASSERT(!duk_is_bare_object(thr, -1));
|
|
|
|
/* 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 */
|
|
}
|