mirror of https://github.com/svaarala/duktape.git
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.
649 lines
20 KiB
649 lines
20 KiB
/*
|
|
* Hobject 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) */
|
|
|
|
/* must match exactly the number of internal properties inserted to enumerator */
|
|
#define DUK__ENUM_START_INDEX 2
|
|
|
|
DUK_LOCAL const duk_uint16_t duk__bufobj_virtual_props[] = {
|
|
DUK_STRIDX_LENGTH,
|
|
DUK_STRIDX_BYTE_LENGTH,
|
|
DUK_STRIDX_BYTE_OFFSET,
|
|
DUK_STRIDX_BYTES_PER_ELEMENT
|
|
};
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* Note that the entry part values are all "true", e.g.
|
|
*
|
|
* "1" -> true, "3" -> true, "2" -> true
|
|
*
|
|
* so it suffices to only work in the key part without exchanging any keys,
|
|
* simplifying the sort.
|
|
*
|
|
* 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_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_UNREF(thr);
|
|
|
|
if (DUK_HOBJECT_GET_ENEXT(h_obj) <= 1 + DUK__ENUM_START_INDEX) {
|
|
return;
|
|
}
|
|
|
|
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++) {
|
|
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;
|
|
for (;;) {
|
|
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));
|
|
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
|
|
}
|
|
|
|
/*
|
|
* 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_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;
|
|
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_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_require_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
|
|
* 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(ctx, enum_target);
|
|
duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET);
|
|
|
|
/* Initialize index so that we skip internal control keys. */
|
|
duk_push_int(ctx, DUK__ENUM_START_INDEX);
|
|
duk_put_prop_stridx(ctx, -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(thr,
|
|
enum_target,
|
|
&h_proxy_target,
|
|
&h_proxy_handler))) {
|
|
goto skip_proxy;
|
|
}
|
|
|
|
DUK_DDD(DUK_DDDPRINT("proxy enumeration"));
|
|
duk_push_hobject(ctx, h_proxy_handler);
|
|
if (!duk_get_prop_stridx(ctx, -1, DUK_STRIDX_ENUMERATE)) {
|
|
/* 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 enumerate 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(ctx, enum_target); /* -> [ ... enum_target res handler undefined target ] */
|
|
duk_put_prop_stridx(ctx, -4, DUK_STRIDX_INT_TARGET);
|
|
|
|
duk_pop_2(ctx); /* -> [ ... enum_target res ] */
|
|
goto skip_proxy;
|
|
}
|
|
|
|
/* [ ... enum_target res handler trap ] */
|
|
duk_insert(ctx, -2);
|
|
duk_push_hobject(ctx, h_proxy_target); /* -> [ ... enum_target res trap handler target ] */
|
|
duk_call_method(ctx, 1 /*nargs*/); /* -> [ ... enum_target res trap_result ] */
|
|
h_trap_result = duk_require_hobject(ctx, -1);
|
|
DUK_UNREF(h_trap_result);
|
|
|
|
/* Copy trap result keys into the enumerator object. */
|
|
len = (duk_uint_fast32_t) duk_get_length(ctx, -1);
|
|
for (i = 0; i < len; i++) {
|
|
/* XXX: not sure what the correct semantic details are here,
|
|
* e.g. handling of missing values (gaps), handling of non-array
|
|
* trap results, etc.
|
|
*
|
|
* For keys, we simply skip non-string keys which seems to be
|
|
* consistent with how e.g. Object.keys() will process proxy trap
|
|
* results (ES6, Section 19.1.2.14).
|
|
*/
|
|
if (duk_get_prop_index(ctx, -1, i) && duk_is_string(ctx, -1)) {
|
|
/* [ ... enum_target res trap_result val ] */
|
|
duk_push_true(ctx);
|
|
/* [ ... enum_target res trap_result val true ] */
|
|
duk_put_prop(ctx, -4);
|
|
} else {
|
|
duk_pop(ctx);
|
|
}
|
|
}
|
|
/* [ ... enum_target res trap_result ] */
|
|
duk_pop(ctx);
|
|
duk_remove(ctx, -2);
|
|
|
|
/* [ ... 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;
|
|
while (curr) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#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
|
|
/* 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 ||
|
|
(DUK_HOBJECT_GET_CLASS_NUMBER(curr) == DUK_HOBJECT_CLASS_ARRAYBUFFER &&
|
|
((enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) == 0))) {
|
|
/* Zero length seems like a good behavior for neutered buffers.
|
|
* ArrayBuffer index properties are non-enumerable, so use len = 0
|
|
* to ensure they're not enumerated (unless requested explicitly).
|
|
*/
|
|
len = 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;
|
|
|
|
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);
|
|
|
|
/* [enum_target res] */
|
|
}
|
|
|
|
/* 'length' and other virtual properties are not
|
|
* enumerable, but are included if non-enumerable
|
|
* properties are requested.
|
|
*/
|
|
|
|
if (enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE) {
|
|
duk_uint_fast32_t n;
|
|
|
|
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
|
|
if (DUK_HOBJECT_IS_BUFOBJ(curr)) {
|
|
n = sizeof(duk__bufobj_virtual_props) / sizeof(duk_uint16_t);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(curr));
|
|
DUK_ASSERT(duk__bufobj_virtual_props[0] == DUK_STRIDX_LENGTH);
|
|
n = 1; /* only 'length' */
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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++) {
|
|
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_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);
|
|
|
|
/* [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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(thr->heap, curr, i) &&
|
|
!(enum_flags & DUK_ENUM_INCLUDE_NONENUMERABLE)) {
|
|
continue;
|
|
}
|
|
if (DUK_HSTRING_HAS_INTERNAL(k) &&
|
|
!(enum_flags & DUK_ENUM_INCLUDE_INTERNAL)) {
|
|
continue;
|
|
}
|
|
if ((enum_flags & DUK_ENUM_ARRAY_INDICES_ONLY) &&
|
|
(DUK_HSTRING_GET_ARRIDX_SLOW(k) == DUK_HSTRING_NO_ARRAY_INDEX)) {
|
|
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);
|
|
|
|
/* [enum_target res] */
|
|
}
|
|
|
|
if (enum_flags & DUK_ENUM_OWN_PROPERTIES_ONLY) {
|
|
break;
|
|
}
|
|
|
|
curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
|
|
}
|
|
|
|
/* [enum_target res] */
|
|
|
|
duk_remove(ctx, -2);
|
|
|
|
/* [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.
|
|
*/
|
|
|
|
/* 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().
|
|
*/
|
|
|
|
/* XXX: may need a 'length' filter for forEach()
|
|
*/
|
|
DUK_DDD(DUK_DDDPRINT("sort array indices by caller request"));
|
|
duk__sort_array_indices(thr, 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(ctx, -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_context *ctx, duk_bool_t get_value) {
|
|
duk_hthread *thr = (duk_hthread *) ctx;
|
|
duk_hobject *e;
|
|
duk_hobject *enum_target;
|
|
duk_hstring *res = NULL;
|
|
duk_uint_fast32_t idx;
|
|
duk_bool_t check_existence;
|
|
|
|
DUK_ASSERT(ctx != NULL);
|
|
|
|
/* [... enum] */
|
|
|
|
e = duk_require_hobject(ctx, -1);
|
|
|
|
/* XXX use get tval ptr, more efficient */
|
|
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_NEXT);
|
|
idx = (duk_uint_fast32_t) duk_require_uint(ctx, -1);
|
|
duk_pop(ctx);
|
|
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(ctx, -1, DUK_STRIDX_INT_TARGET);
|
|
enum_target = duk_require_hobject(ctx, -1);
|
|
DUK_ASSERT(enum_target != NULL);
|
|
#if defined(DUK_USE_ES6_PROXY)
|
|
check_existence = (!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(enum_target));
|
|
#else
|
|
check_existence = 1;
|
|
#endif
|
|
duk_pop(ctx); /* still reachable */
|
|
|
|
DUK_DDD(DUK_DDDPRINT("getting next enum value, enum_target=%!iO, enumerator=%!iT",
|
|
(duk_heaphdr *) enum_target, (duk_tval *) duk_get_tval(ctx, -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(ctx, (duk_uint32_t) idx);
|
|
duk_put_prop_stridx(ctx, -2, DUK_STRIDX_INT_NEXT);
|
|
|
|
/* [... enum] */
|
|
|
|
if (res) {
|
|
duk_push_hstring(ctx, res);
|
|
if (get_value) {
|
|
duk_push_hobject(ctx, enum_target);
|
|
duk_dup_m2(ctx); /* -> [... enum key enum_target key] */
|
|
duk_get_prop(ctx, -2); /* -> [... enum key enum_target val] */
|
|
duk_remove(ctx, -2); /* -> [... enum key val] */
|
|
duk_remove(ctx, -3); /* -> [... key val] */
|
|
} else {
|
|
duk_remove(ctx, -2); /* -> [... key] */
|
|
}
|
|
return 1;
|
|
} else {
|
|
duk_pop(ctx); /* -> [...] */
|
|
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_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_ASSERT(ctx != NULL);
|
|
DUK_ASSERT(duk_get_hobject(ctx, -1) != NULL);
|
|
DUK_UNREF(thr);
|
|
|
|
/* 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(ctx, enum_flags);
|
|
duk_push_array(ctx);
|
|
|
|
/* [enum_target enum res] */
|
|
|
|
e = duk_require_hobject(ctx, -2);
|
|
DUK_ASSERT(e != NULL);
|
|
|
|
idx = 0;
|
|
for (i = DUK__ENUM_START_INDEX; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(e); i++) {
|
|
duk_hstring *k;
|
|
|
|
k = DUK_HOBJECT_E_GET_KEY(thr->heap, e, i);
|
|
DUK_ASSERT(k); /* enumerator must have no keys deleted */
|
|
|
|
/* [enum_target enum res] */
|
|
duk_push_hstring(ctx, k);
|
|
duk_put_prop_index(ctx, -2, idx);
|
|
idx++;
|
|
}
|
|
|
|
/* [enum_target enum res] */
|
|
duk_remove(ctx, -2);
|
|
|
|
/* [enum_target res] */
|
|
|
|
return 1; /* return 1 to allow callers to tail call */
|
|
}
|
|
|