http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/b8103f03/thirdparty/civetweb-1.9.1/src/third_party/duktape-1.5.2/src-separate/duk_hobject_props.c ---------------------------------------------------------------------- diff --git a/thirdparty/civetweb-1.9.1/src/third_party/duktape-1.5.2/src-separate/duk_hobject_props.c b/thirdparty/civetweb-1.9.1/src/third_party/duktape-1.5.2/src-separate/duk_hobject_props.c deleted file mode 100644 index f7510df..0000000 --- a/thirdparty/civetweb-1.9.1/src/third_party/duktape-1.5.2/src-separate/duk_hobject_props.c +++ /dev/null @@ -1,5999 +0,0 @@ -/* - * Hobject property set/get functionality. - * - * This is very central functionality for size, performance, and compliance. - * It is also rather intricate; see hobject-algorithms.rst for discussion on - * the algorithms and memory-management.rst for discussion on refcounts and - * side effect issues. - * - * Notes: - * - * - It might be tempting to assert "refcount nonzero" for objects - * being operated on, but that's not always correct: objects with - * a zero refcount may be operated on by the refcount implementation - * (finalization) for instance. Hence, no refcount assertions are made. - * - * - Many operations (memory allocation, identifier operations, etc) - * may cause arbitrary side effects (e.g. through GC and finalization). - * These side effects may invalidate duk_tval pointers which point to - * areas subject to reallocation (like value stack). Heap objects - * themselves have stable pointers. Holding heap object pointers or - * duk_tval copies is not problematic with respect to side effects; - * care must be taken when holding and using argument duk_tval pointers. - * - * - If a finalizer is executed, it may operate on the the same object - * we're currently dealing with. For instance, the finalizer might - * delete a certain property which has already been looked up and - * confirmed to exist. Ideally finalizers would be disabled if GC - * happens during property access. At the moment property table realloc - * disables finalizers, and all DECREFs may cause arbitrary changes so - * handle DECREF carefully. - * - * - The order of operations for a DECREF matters. When DECREF is executed, - * the entire object graph must be consistent; note that a refzero may - * lead to a mark-and-sweep through a refcount finalizer. - */ - -/* - * XXX: array indices are mostly typed as duk_uint32_t here; duk_uarridx_t - * might be more appropriate. - */ - -/* - * XXX: duk_uint_fast32_t should probably be used in many places here. - */ - -#include "duk_internal.h" - -/* - * Local defines - */ - -#define DUK__NO_ARRAY_INDEX DUK_HSTRING_NO_ARRAY_INDEX - -/* hash probe sequence */ -#define DUK__HASH_INITIAL(hash,h_size) DUK_HOBJECT_HASH_INITIAL((hash),(h_size)) -#define DUK__HASH_PROBE_STEP(hash) DUK_HOBJECT_HASH_PROBE_STEP((hash)) - -/* marker values for hash part */ -#define DUK__HASH_UNUSED DUK_HOBJECT_HASHIDX_UNUSED -#define DUK__HASH_DELETED DUK_HOBJECT_HASHIDX_DELETED - -/* valstack space that suffices for all local calls, including recursion - * of other than Duktape calls (getters etc) - */ -#define DUK__VALSTACK_SPACE 10 - -/* valstack space allocated especially for proxy lookup which does a - * recursive property lookup - */ -#define DUK__VALSTACK_PROXY_LOOKUP 20 - -/* - * Local prototypes - */ - -DUK_LOCAL_DECL duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc); -DUK_LOCAL_DECL void duk__check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, duk_bool_t throw_flag); -DUK_LOCAL_DECL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc); - -DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length_smaller(duk_hthread *thr, duk_hobject *obj, duk_uint32_t old_len, duk_uint32_t new_len, duk_bool_t force_flag, duk_uint32_t *out_result_len); -DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj); - -DUK_LOCAL_DECL duk_bool_t duk__get_propdesc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_small_uint_t flags); -DUK_LOCAL_DECL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_uint32_t arr_idx, duk_propdesc *out_desc, duk_small_uint_t flags); -DUK_LOCAL duk_uint32_t duk__get_old_array_length(duk_hthread *thr, duk_hobject *obj, duk_propdesc *temp_desc); - -/* - * Misc helpers - */ - -/* Convert a duk_tval number (caller checks) to a 32-bit index. Returns - * DUK__NO_ARRAY_INDEX if the number is not whole or not a valid array - * index. - */ -/* XXX: for fastints, could use a variant which assumes a double duk_tval - * (and doesn't need to check for fastint again). - */ -DUK_LOCAL duk_uint32_t duk__tval_number_to_arr_idx(duk_tval *tv) { - duk_double_t dbl; - duk_uint32_t idx; - - DUK_ASSERT(tv != NULL); - DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); - - /* -0 is accepted here as index 0 because ToString(-0) == "0" which is - * in canonical form and thus an array index. - */ - dbl = DUK_TVAL_GET_NUMBER(tv); - idx = (duk_uint32_t) dbl; - if ((duk_double_t) idx == dbl) { - /* Is whole and within 32 bit range. If the value happens to be 0xFFFFFFFF, - * it's not a valid array index but will then match DUK__NO_ARRAY_INDEX. - */ - return idx; - } - return DUK__NO_ARRAY_INDEX; -} - -#if defined(DUK_USE_FASTINT) -/* Convert a duk_tval fastint (caller checks) to a 32-bit index. */ -DUK_LOCAL duk_uint32_t duk__tval_fastint_to_arr_idx(duk_tval *tv) { - duk_int64_t t; - - DUK_ASSERT(tv != NULL); - DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); - - t = DUK_TVAL_GET_FASTINT(tv); - if ((t & ~0xffffffffULL) != 0) { - /* Catches >0x100000000 and negative values. */ - return DUK__NO_ARRAY_INDEX; - } - - /* If the value happens to be 0xFFFFFFFF, it's not a valid array index - * but will then match DUK__NO_ARRAY_INDEX. - */ - return (duk_uint32_t) t; -} -#endif /* DUK_USE_FASTINT */ - -/* Push an arbitrary duk_tval to the stack, coerce it to string, and return - * both a duk_hstring pointer and an array index (or DUK__NO_ARRAY_INDEX). - */ -DUK_LOCAL duk_uint32_t duk__push_tval_to_hstring_arr_idx(duk_context *ctx, duk_tval *tv, duk_hstring **out_h) { - duk_uint32_t arr_idx; - duk_hstring *h; - - DUK_ASSERT(ctx != NULL); - DUK_ASSERT(tv != NULL); - DUK_ASSERT(out_h != NULL); - - duk_push_tval(ctx, tv); - duk_to_string(ctx, -1); - h = duk_get_hstring(ctx, -1); - DUK_ASSERT(h != NULL); - *out_h = h; - - arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(h); - return arr_idx; -} - -/* String is an own (virtual) property of a lightfunc. */ -DUK_LOCAL duk_bool_t duk__key_is_lightfunc_ownprop(duk_hthread *thr, duk_hstring *key) { - DUK_UNREF(thr); - return (key == DUK_HTHREAD_STRING_LENGTH(thr) || - key == DUK_HTHREAD_STRING_NAME(thr)); -} - -/* - * Helpers for managing property storage size - */ - -/* Get default hash part size for a certain entry part size. */ -#if defined(DUK_USE_HOBJECT_HASH_PART) -DUK_LOCAL duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) { - DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES); - - if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) { - duk_uint32_t res; - - /* result: hash_prime(floor(1.2 * e_size)) */ - res = duk_util_get_hash_prime(e_size + e_size / DUK_HOBJECT_H_SIZE_DIVISOR); - - /* if fails, e_size will be zero = not an issue, except performance-wise */ - DUK_ASSERT(res == 0 || res > e_size); - return res; - } else { - return 0; - } -} -#endif /* USE_PROP_HASH_PART */ - -/* Get minimum entry part growth for a certain size. */ -DUK_LOCAL duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) { - duk_uint32_t res; - - DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES); - - res = (e_size + DUK_HOBJECT_E_MIN_GROW_ADD) / DUK_HOBJECT_E_MIN_GROW_DIVISOR; - DUK_ASSERT(res >= 1); /* important for callers */ - return res; -} - -/* Get minimum array part growth for a certain size. */ -DUK_LOCAL duk_uint32_t duk__get_min_grow_a(duk_uint32_t a_size) { - duk_uint32_t res; - - DUK_ASSERT((duk_size_t) a_size <= DUK_HOBJECT_MAX_PROPERTIES); - - res = (a_size + DUK_HOBJECT_A_MIN_GROW_ADD) / DUK_HOBJECT_A_MIN_GROW_DIVISOR; - DUK_ASSERT(res >= 1); /* important for callers */ - return res; -} - -/* Count actually used entry part entries (non-NULL keys). */ -DUK_LOCAL duk_uint32_t duk__count_used_e_keys(duk_hthread *thr, duk_hobject *obj) { - duk_uint_fast32_t i; - duk_uint_fast32_t n = 0; - duk_hstring **e; - - DUK_ASSERT(obj != NULL); - DUK_UNREF(thr); - - e = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, obj); - for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) { - if (*e++) { - n++; - } - } - return (duk_uint32_t) n; -} - -/* Count actually used array part entries and array minimum size. - * NOTE: 'out_min_size' can be computed much faster by starting from the - * end and breaking out early when finding first used entry, but this is - * not needed now. - */ -DUK_LOCAL void duk__compute_a_stats(duk_hthread *thr, duk_hobject *obj, duk_uint32_t *out_used, duk_uint32_t *out_min_size) { - duk_uint_fast32_t i; - duk_uint_fast32_t used = 0; - duk_uint_fast32_t highest_idx = (duk_uint_fast32_t) -1; /* see below */ - duk_tval *a; - - DUK_ASSERT(obj != NULL); - DUK_ASSERT(out_used != NULL); - DUK_ASSERT(out_min_size != NULL); - DUK_UNREF(thr); - - a = DUK_HOBJECT_A_GET_BASE(thr->heap, obj); - for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) { - duk_tval *tv = a++; - if (!DUK_TVAL_IS_UNUSED(tv)) { - used++; - highest_idx = i; - } - } - - /* Initial value for highest_idx is -1 coerced to unsigned. This - * is a bit odd, but (highest_idx + 1) will then wrap to 0 below - * for out_min_size as intended. - */ - - *out_used = used; - *out_min_size = highest_idx + 1; /* 0 if no used entries */ -} - -/* Check array density and indicate whether or not the array part should be abandoned. */ -DUK_LOCAL duk_bool_t duk__abandon_array_density_check(duk_uint32_t a_used, duk_uint32_t a_size) { - /* - * Array abandon check; abandon if: - * - * new_used / new_size < limit - * new_used < limit * new_size || limit is 3 bits fixed point - * new_used < limit' / 8 * new_size || *8 - * 8*new_used < limit' * new_size || :8 - * new_used < limit' * (new_size / 8) - * - * Here, new_used = a_used, new_size = a_size. - * - * Note: some callers use approximate values for a_used and/or a_size - * (e.g. dropping a '+1' term). This doesn't affect the usefulness - * of the check, but may confuse debugging. - */ - - return (a_used < DUK_HOBJECT_A_ABANDON_LIMIT * (a_size >> 3)); -} - -/* Fast check for extending array: check whether or not a slow density check is required. */ -DUK_LOCAL duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx, duk_uint32_t old_size) { - /* - * In a fast check we assume old_size equals old_used (i.e., existing - * array is fully dense). - * - * Slow check if: - * - * (new_size - old_size) / old_size > limit - * new_size - old_size > limit * old_size - * new_size > (1 + limit) * old_size || limit' is 3 bits fixed point - * new_size > (1 + (limit' / 8)) * old_size || * 8 - * 8 * new_size > (8 + limit') * old_size || : 8 - * new_size > (8 + limit') * (old_size / 8) - * new_size > limit'' * (old_size / 8) || limit'' = 9 -> max 25% increase - * arr_idx + 1 > limit'' * (old_size / 8) - * - * This check doesn't work well for small values, so old_size is rounded - * up for the check (and the '+ 1' of arr_idx can be ignored in practice): - * - * arr_idx > limit'' * ((old_size + 7) / 8) - */ - - return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3)); -} - -/* - * Proxy helpers - */ - -#if defined(DUK_USE_ES6_PROXY) -DUK_INTERNAL duk_bool_t duk_hobject_proxy_check(duk_hthread *thr, duk_hobject *obj, duk_hobject **out_target, duk_hobject **out_handler) { - duk_tval *tv_target; - duk_tval *tv_handler; - duk_hobject *h_target; - duk_hobject *h_handler; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(out_target != NULL); - DUK_ASSERT(out_handler != NULL); - - /* Caller doesn't need to check exotic proxy behavior (but does so for - * some fast paths). - */ - if (DUK_LIKELY(!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) { - return 0; - } - - tv_handler = duk_hobject_find_existing_entry_tval_ptr(thr->heap, obj, DUK_HTHREAD_STRING_INT_HANDLER(thr)); - if (!tv_handler) { - DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REVOKED); - return 0; - } - DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_handler)); - h_handler = DUK_TVAL_GET_OBJECT(tv_handler); - DUK_ASSERT(h_handler != NULL); - *out_handler = h_handler; - tv_handler = NULL; /* avoid issues with relocation */ - - tv_target = duk_hobject_find_existing_entry_tval_ptr(thr->heap, obj, DUK_HTHREAD_STRING_INT_TARGET(thr)); - if (!tv_target) { - DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REVOKED); - return 0; - } - DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_target)); - h_target = DUK_TVAL_GET_OBJECT(tv_target); - DUK_ASSERT(h_target != NULL); - *out_target = h_target; - tv_target = NULL; /* avoid issues with relocation */ - - return 1; -} -#endif /* DUK_USE_ES6_PROXY */ - -/* Get Proxy target object. If the argument is not a Proxy, return it as is. - * If a Proxy is revoked, an error is thrown. - */ -#if defined(DUK_USE_ES6_PROXY) -DUK_INTERNAL duk_hobject *duk_hobject_resolve_proxy_target(duk_hthread *thr, duk_hobject *obj) { - duk_hobject *h_target; - duk_hobject *h_handler; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - - /* Resolve Proxy targets until Proxy chain ends. No explicit check for - * a Proxy loop: user code cannot create such a loop without tweaking - * internal properties directly. - */ - - while (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) { - if (duk_hobject_proxy_check(thr, obj, &h_target, &h_handler)) { - DUK_ASSERT(h_target != NULL); - obj = h_target; - } else { - break; - } - } - - DUK_ASSERT(obj != NULL); - return obj; -} -#endif /* DUK_USE_ES6_PROXY */ - -#if defined(DUK_USE_ES6_PROXY) -DUK_LOCAL duk_bool_t duk__proxy_check_prop(duk_hthread *thr, duk_hobject *obj, duk_small_uint_t stridx_trap, duk_tval *tv_key, duk_hobject **out_target) { - duk_context *ctx = (duk_context *) thr; - duk_hobject *h_handler; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(tv_key != NULL); - DUK_ASSERT(out_target != NULL); - - if (!duk_hobject_proxy_check(thr, obj, out_target, &h_handler)) { - return 0; - } - DUK_ASSERT(*out_target != NULL); - DUK_ASSERT(h_handler != NULL); - - /* XXX: At the moment Duktape accesses internal keys like _Finalizer using a - * normal property set/get which would allow a proxy handler to interfere with - * such behavior and to get access to internal key strings. This is not a problem - * as such because internal key strings can be created in other ways too (e.g. - * through buffers). The best fix is to change Duktape internal lookups to - * skip proxy behavior. Until that, internal property accesses bypass the - * proxy and are applied to the target (as if the handler did not exist). - * This has some side effects, see test-bi-proxy-internal-keys.js. - */ - - if (DUK_TVAL_IS_STRING(tv_key)) { - duk_hstring *h_key = (duk_hstring *) DUK_TVAL_GET_STRING(tv_key); - DUK_ASSERT(h_key != NULL); - if (DUK_HSTRING_HAS_INTERNAL(h_key)) { - DUK_DDD(DUK_DDDPRINT("internal key, skip proxy handler and apply to target")); - return 0; - } - } - - /* The handler is looked up with a normal property lookup; it may be an - * accessor or the handler object itself may be a proxy object. If the - * handler is a proxy, we need to extend the valstack as we make a - * recursive proxy check without a function call in between (in fact - * there is no limit to the potential recursion here). - * - * (For sanity, proxy creation rejects another proxy object as either - * the handler or the target at the moment so recursive proxy cases - * are not realized now.) - */ - - /* XXX: C recursion limit if proxies are allowed as handler/target values */ - - duk_require_stack(ctx, DUK__VALSTACK_PROXY_LOOKUP); - duk_push_hobject(ctx, h_handler); - if (duk_get_prop_stridx(ctx, -1, stridx_trap)) { - /* -> [ ... handler trap ] */ - duk_insert(ctx, -2); /* -> [ ... trap handler ] */ - - /* stack prepped for func call: [ ... trap handler ] */ - return 1; - } else { - duk_pop_2(ctx); - return 0; - } -} -#endif /* DUK_USE_ES6_PROXY */ - -/* - * Reallocate property allocation, moving properties to the new allocation. - * - * Includes key compaction, rehashing, and can also optionally abandoning - * the array part, 'migrating' array entries into the beginning of the - * new entry part. Arguments are not validated here, so e.g. new_h_size - * MUST be a valid prime. - * - * There is no support for in-place reallocation or just compacting keys - * without resizing the property allocation. This is intentional to keep - * code size minimal. - * - * The implementation is relatively straightforward, except for the array - * abandonment process. Array abandonment requires that new string keys - * are interned, which may trigger GC. All keys interned so far must be - * reachable for GC at all times; valstack is used for that now. - * - * Also, a GC triggered during this reallocation process must not interfere - * with the object being resized. This is currently controlled by using - * heap->mark_and_sweep_base_flags to indicate that no finalizers will be - * executed (as they can affect ANY object) and no objects are compacted - * (it would suffice to protect this particular object only, though). - * - * Note: a non-checked variant would be nice but is a bit tricky to - * implement for the array abandonment process. It's easy for - * everything else. - * - * Note: because we need to potentially resize the valstack (as part - * of abandoning the array part), any tval pointers to the valstack - * will become invalid after this call. - */ - -DUK_LOCAL -void duk__realloc_props(duk_hthread *thr, - duk_hobject *obj, - duk_uint32_t new_e_size, - duk_uint32_t new_a_size, - duk_uint32_t new_h_size, - duk_bool_t abandon_array) { - duk_context *ctx = (duk_context *) thr; -#ifdef DUK_USE_MARK_AND_SWEEP - duk_small_uint_t prev_mark_and_sweep_base_flags; -#endif - duk_uint32_t new_alloc_size; - duk_uint32_t new_e_size_adjusted; - duk_uint8_t *new_p; - duk_hstring **new_e_k; - duk_propvalue *new_e_pv; - duk_uint8_t *new_e_f; - duk_tval *new_a; - duk_uint32_t *new_h; - duk_uint32_t new_e_next; - duk_uint_fast32_t i; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(ctx != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(!abandon_array || new_a_size == 0); /* if abandon_array, new_a_size must be 0 */ - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL || (DUK_HOBJECT_GET_ESIZE(obj) == 0 && DUK_HOBJECT_GET_ASIZE(obj) == 0)); - DUK_ASSERT(new_h_size == 0 || new_h_size >= new_e_size); /* required to guarantee success of rehashing, - * intentionally use unadjusted new_e_size - */ - DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - /* - * Pre resize assertions. - */ - -#ifdef DUK_USE_ASSERTIONS - /* XXX: pre-checks (such as no duplicate keys) */ -#endif - - /* - * For property layout 1, tweak e_size to ensure that the whole entry - * part (key + val + flags) is a suitable multiple for alignment - * (platform specific). - * - * Property layout 2 does not require this tweaking and is preferred - * on low RAM platforms requiring alignment. - */ - -#if defined(DUK_USE_HOBJECT_LAYOUT_2) || defined(DUK_USE_HOBJECT_LAYOUT_3) - DUK_DDD(DUK_DDDPRINT("using layout 2 or 3, no need to pad e_size: %ld", (long) new_e_size)); - new_e_size_adjusted = new_e_size; -#elif defined(DUK_USE_HOBJECT_LAYOUT_1) && (DUK_HOBJECT_ALIGN_TARGET == 1) - DUK_DDD(DUK_DDDPRINT("using layout 1, but no need to pad e_size: %ld", (long) new_e_size)); - new_e_size_adjusted = new_e_size; -#elif defined(DUK_USE_HOBJECT_LAYOUT_1) && ((DUK_HOBJECT_ALIGN_TARGET == 4) || (DUK_HOBJECT_ALIGN_TARGET == 8)) - new_e_size_adjusted = (new_e_size + DUK_HOBJECT_ALIGN_TARGET - 1) & (~(DUK_HOBJECT_ALIGN_TARGET - 1)); - DUK_DDD(DUK_DDDPRINT("using layout 1, and alignment target is %ld, adjusted e_size: %ld -> %ld", - (long) DUK_HOBJECT_ALIGN_TARGET, (long) new_e_size, (long) new_e_size_adjusted)); - DUK_ASSERT(new_e_size_adjusted >= new_e_size); -#else -#error invalid hobject layout defines -#endif - - /* - * Debug logging after adjustment. - */ - - DUK_DDD(DUK_DDDPRINT("attempt to resize hobject %p props (%ld -> %ld bytes), from {p=%p,e_size=%ld,e_next=%ld,a_size=%ld,h_size=%ld} to " - "{e_size=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld", - (void *) obj, - (long) DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), - DUK_HOBJECT_GET_ASIZE(obj), - DUK_HOBJECT_GET_HSIZE(obj)), - (long) DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size), - (void *) DUK_HOBJECT_GET_PROPS(thr->heap, obj), - (long) DUK_HOBJECT_GET_ESIZE(obj), - (long) DUK_HOBJECT_GET_ENEXT(obj), - (long) DUK_HOBJECT_GET_ASIZE(obj), - (long) DUK_HOBJECT_GET_HSIZE(obj), - (long) new_e_size_adjusted, - (long) new_a_size, - (long) new_h_size, - (long) abandon_array, - (long) new_e_size)); - - /* - * Property count check. This is the only point where we ensure that - * we don't get more (allocated) property space that we can handle. - * There aren't hard limits as such, but some algorithms fail (e.g. - * finding next higher prime, selecting hash part size) if we get too - * close to the 4G property limit. - * - * Since this works based on allocation size (not actually used size), - * the limit is a bit approximate but good enough in practice. - */ - - if (new_e_size_adjusted + new_a_size > DUK_HOBJECT_MAX_PROPERTIES) { - DUK_ERROR_ALLOC_DEFMSG(thr); - } - - /* - * Compute new alloc size and alloc new area. - * - * The new area is allocated as a dynamic buffer and placed into the - * valstack for reachability. The actual buffer is then detached at - * the end. - * - * Note: heap_mark_and_sweep_base_flags are altered here to ensure - * no-one touches this object while we're resizing and rehashing it. - * The flags must be reset on every exit path after it. Finalizers - * and compaction is prevented currently for all objects while it - * would be enough to restrict it only for the current object. - */ - -#ifdef DUK_USE_MARK_AND_SWEEP - prev_mark_and_sweep_base_flags = thr->heap->mark_and_sweep_base_flags; - thr->heap->mark_and_sweep_base_flags |= - DUK_MS_FLAG_NO_FINALIZERS | /* avoid attempts to add/remove object keys */ - DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* avoid attempt to compact the current object */ -#endif - - new_alloc_size = DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted, new_a_size, new_h_size); - DUK_DDD(DUK_DDDPRINT("new hobject allocation size is %ld", (long) new_alloc_size)); - if (new_alloc_size == 0) { - /* for zero size, don't push anything on valstack */ - DUK_ASSERT(new_e_size_adjusted == 0); - DUK_ASSERT(new_a_size == 0); - DUK_ASSERT(new_h_size == 0); - new_p = NULL; - } else { - /* This may trigger mark-and-sweep with arbitrary side effects, - * including an attempted resize of the object we're resizing, - * executing a finalizer which may add or remove properties of - * the object we're resizing etc. - */ - - /* Note: buffer is dynamic so that we can 'steal' the actual - * allocation later. - */ - - new_p = (duk_uint8_t *) duk_push_dynamic_buffer(ctx, new_alloc_size); /* errors out if out of memory */ - DUK_ASSERT(new_p != NULL); /* since new_alloc_size > 0 */ - } - - /* Set up pointers to the new property area: this is hidden behind a macro - * because it is memory layout specific. - */ - DUK_HOBJECT_P_SET_REALLOC_PTRS(new_p, new_e_k, new_e_pv, new_e_f, new_a, new_h, - new_e_size_adjusted, new_a_size, new_h_size); - DUK_UNREF(new_h); /* happens when hash part dropped */ - new_e_next = 0; - - /* if new_p == NULL, all of these pointers are NULL */ - DUK_ASSERT((new_p != NULL) || - (new_e_k == NULL && new_e_pv == NULL && new_e_f == NULL && - new_a == NULL && new_h == NULL)); - - DUK_DDD(DUK_DDDPRINT("new alloc size %ld, new_e_k=%p, new_e_pv=%p, new_e_f=%p, new_a=%p, new_h=%p", - (long) new_alloc_size, (void *) new_e_k, (void *) new_e_pv, (void *) new_e_f, - (void *) new_a, (void *) new_h)); - - /* - * Migrate array to start of entries if requested. - * - * Note: from an enumeration perspective the order of entry keys matters. - * Array keys should appear wherever they appeared before the array abandon - * operation. - */ - - if (abandon_array) { - /* - * Note: assuming new_a_size == 0, and that entry part contains - * no conflicting keys, refcounts do not need to be adjusted for - * the values, as they remain exactly the same. - * - * The keys, however, need to be interned, incref'd, and be - * reachable for GC. Any intern attempt may trigger a GC and - * claim any non-reachable strings, so every key must be reachable - * at all times. - * - * A longjmp must not occur here, as the new_p allocation would - * be freed without these keys being decref'd, hence the messy - * decref handling if intern fails. - */ - DUK_ASSERT(new_a_size == 0); - - for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) { - duk_tval *tv1; - duk_tval *tv2; - duk_hstring *key; - - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); - - tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i); - if (DUK_TVAL_IS_UNUSED(tv1)) { - continue; - } - - DUK_ASSERT(new_p != NULL && new_e_k != NULL && - new_e_pv != NULL && new_e_f != NULL); - - /* - * Intern key via the valstack to ensure reachability behaves - * properly. We must avoid longjmp's here so use non-checked - * primitives. - * - * Note: duk_check_stack() potentially reallocs the valstack, - * invalidating any duk_tval pointers to valstack. Callers - * must be careful. - */ - - /* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */ - if (!duk_check_stack(ctx, 1)) { - goto abandon_error; - } - DUK_ASSERT_VALSTACK_SPACE(thr, 1); - key = duk_heap_string_intern_u32(thr->heap, i); - if (!key) { - goto abandon_error; - } - duk_push_hstring(ctx, key); /* keep key reachable for GC etc; guaranteed not to fail */ - - /* key is now reachable in the valstack */ - - DUK_HSTRING_INCREF(thr, key); /* second incref for the entry reference */ - new_e_k[new_e_next] = key; - tv2 = &new_e_pv[new_e_next].v; /* array entries are all plain values */ - DUK_TVAL_SET_TVAL(tv2, tv1); - new_e_f[new_e_next] = DUK_PROPDESC_FLAG_WRITABLE | - DUK_PROPDESC_FLAG_ENUMERABLE | - DUK_PROPDESC_FLAG_CONFIGURABLE; - new_e_next++; - - /* Note: new_e_next matches pushed temp key count, and nothing can - * fail above between the push and this point. - */ - } - - DUK_DDD(DUK_DDDPRINT("abandon array: pop %ld key temps from valstack", (long) new_e_next)); - duk_pop_n(ctx, new_e_next); - } - - /* - * Copy keys and values in the entry part (compacting them at the same time). - */ - - for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) { - duk_hstring *key; - - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); - - key = DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i); - if (!key) { - continue; - } - - DUK_ASSERT(new_p != NULL && new_e_k != NULL && - new_e_pv != NULL && new_e_f != NULL); - - new_e_k[new_e_next] = key; - new_e_pv[new_e_next] = DUK_HOBJECT_E_GET_VALUE(thr->heap, obj, i); - new_e_f[new_e_next] = DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, i); - new_e_next++; - } - /* the entries [new_e_next, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */ - - /* - * Copy array elements to new array part. - */ - - if (new_a_size > DUK_HOBJECT_GET_ASIZE(obj)) { - /* copy existing entries as is */ - DUK_ASSERT(new_p != NULL && new_a != NULL); - if (DUK_HOBJECT_GET_ASIZE(obj) > 0) { - /* Avoid zero copy with an invalid pointer. If obj->p is NULL, - * the 'new_a' pointer will be invalid which is not allowed even - * when copy size is zero. - */ - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); - DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) > 0); - DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * DUK_HOBJECT_GET_ASIZE(obj)); - } - - /* fill new entries with -unused- (required, gc reachable) */ - for (i = DUK_HOBJECT_GET_ASIZE(obj); i < new_a_size; i++) { - duk_tval *tv = &new_a[i]; - DUK_TVAL_SET_UNUSED(tv); - } - } else { -#ifdef DUK_USE_ASSERTIONS - /* caller must have decref'd values above new_a_size (if that is necessary) */ - if (!abandon_array) { - for (i = new_a_size; i < DUK_HOBJECT_GET_ASIZE(obj); i++) { - duk_tval *tv; - tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i); - - /* current assertion is quite strong: decref's and set to unused */ - DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv)); - } - } -#endif - if (new_a_size > 0) { - /* Avoid zero copy with an invalid pointer. If obj->p is NULL, - * the 'new_a' pointer will be invalid which is not allowed even - * when copy size is zero. - */ - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); - DUK_ASSERT(new_a_size > 0); - DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * new_a_size); - } - } - - /* - * Rebuild the hash part always from scratch (guaranteed to finish). - * - * Any resize of hash part requires rehashing. In addition, by rehashing - * get rid of any elements marked deleted (DUK__HASH_DELETED) which is critical - * to ensuring the hash part never fills up. - */ - -#if defined(DUK_USE_HOBJECT_HASH_PART) - if (DUK_UNLIKELY(new_h_size > 0)) { - DUK_ASSERT(new_h != NULL); - - /* fill new_h with u32 0xff = UNUSED */ - DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL); - DUK_ASSERT(new_h_size > 0); - DUK_MEMSET(new_h, 0xff, sizeof(duk_uint32_t) * new_h_size); - - DUK_ASSERT(new_e_next <= new_h_size); /* equality not actually possible */ - for (i = 0; i < new_e_next; i++) { - duk_hstring *key = new_e_k[i]; - duk_uint32_t j, step; - - DUK_ASSERT(key != NULL); - j = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size); - step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key)); - - for (;;) { - DUK_ASSERT(new_h[j] != DUK__HASH_DELETED); /* should never happen */ - if (new_h[j] == DUK__HASH_UNUSED) { - DUK_DDD(DUK_DDDPRINT("rebuild hit %ld -> %ld", (long) j, (long) i)); - new_h[j] = i; - break; - } - DUK_DDD(DUK_DDDPRINT("rebuild miss %ld, step %ld", (long) j, (long) step)); - j = (j + step) % new_h_size; - - /* guaranteed to finish */ - DUK_ASSERT(j != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size)); - } - } - } else { - DUK_DDD(DUK_DDDPRINT("no hash part, no rehash")); - } -#endif /* DUK_USE_HOBJECT_HASH_PART */ - - /* - * Nice debug log. - */ - - DUK_DD(DUK_DDPRINT("resized hobject %p props (%ld -> %ld bytes), from {p=%p,e_size=%ld,e_next=%ld,a_size=%ld,h_size=%ld} to " - "{p=%p,e_size=%ld,e_next=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld", - (void *) obj, - (long) DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj), - DUK_HOBJECT_GET_ASIZE(obj), - DUK_HOBJECT_GET_HSIZE(obj)), - (long) new_alloc_size, - (void *) DUK_HOBJECT_GET_PROPS(thr->heap, obj), - (long) DUK_HOBJECT_GET_ESIZE(obj), - (long) DUK_HOBJECT_GET_ENEXT(obj), - (long) DUK_HOBJECT_GET_ASIZE(obj), - (long) DUK_HOBJECT_GET_HSIZE(obj), - (void *) new_p, - (long) new_e_size_adjusted, - (long) new_e_next, - (long) new_a_size, - (long) new_h_size, - (long) abandon_array, - (long) new_e_size)); - - /* - * All done, switch properties ('p') allocation to new one. - */ - - DUK_FREE(thr->heap, DUK_HOBJECT_GET_PROPS(thr->heap, obj)); /* NULL obj->p is OK */ - DUK_HOBJECT_SET_PROPS(thr->heap, obj, new_p); - DUK_HOBJECT_SET_ESIZE(obj, new_e_size_adjusted); - DUK_HOBJECT_SET_ENEXT(obj, new_e_next); - DUK_HOBJECT_SET_ASIZE(obj, new_a_size); - DUK_HOBJECT_SET_HSIZE(obj, new_h_size); - - if (new_p) { - /* - * Detach actual buffer from dynamic buffer in valstack, and - * pop it from the stack. - * - * XXX: the buffer object is certainly not reachable at this point, - * so it would be nice to free it forcibly even with only - * mark-and-sweep enabled. Not a big issue though. - */ - (void) duk_steal_buffer(ctx, -1, NULL); - duk_pop(ctx); - } else { - DUK_ASSERT(new_alloc_size == 0); - /* no need to pop, nothing was pushed */ - } - - /* clear array part flag only after switching */ - if (abandon_array) { - DUK_HOBJECT_CLEAR_ARRAY_PART(obj); - } - - DUK_DDD(DUK_DDDPRINT("resize result: %!O", (duk_heaphdr *) obj)); - -#ifdef DUK_USE_MARK_AND_SWEEP - thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags; -#endif - - /* - * Post resize assertions. - */ - -#ifdef DUK_USE_ASSERTIONS - /* XXX: post-checks (such as no duplicate keys) */ -#endif - return; - - /* - * Abandon array failed, need to decref keys already inserted - * into the beginning of new_e_k before unwinding valstack. - */ - - abandon_error: - DUK_D(DUK_DPRINT("hobject resize failed during abandon array, decref keys")); - i = new_e_next; - while (i > 0) { - i--; - DUK_ASSERT(new_e_k != NULL); - DUK_ASSERT(new_e_k[i] != NULL); - DUK_HSTRING_DECREF(thr, new_e_k[i]); /* side effects */ - } - -#ifdef DUK_USE_MARK_AND_SWEEP - thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags; -#endif - - DUK_ERROR_ALLOC_DEFMSG(thr); -} - -/* - * Helpers to resize properties allocation on specific needs. - */ - -/* Grow entry part allocation for one additional entry. */ -DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) { - duk_uint32_t old_e_used; /* actually used, non-NULL entries */ - duk_uint32_t new_e_size; - duk_uint32_t new_a_size; - duk_uint32_t new_h_size; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - - /* Duktape 0.11.0 and prior tried to optimize the resize by not - * counting the number of actually used keys prior to the resize. - * This worked mostly well but also caused weird leak-like behavior - * as in: test-bug-object-prop-alloc-unbounded.js. So, now we count - * the keys explicitly to compute the new entry part size. - */ - - old_e_used = duk__count_used_e_keys(thr, obj); - new_e_size = old_e_used + duk__get_min_grow_e(old_e_used); -#if defined(DUK_USE_HOBJECT_HASH_PART) - new_h_size = duk__get_default_h_size(new_e_size); -#else - new_h_size = 0; -#endif - new_a_size = DUK_HOBJECT_GET_ASIZE(obj); - DUK_ASSERT(new_e_size >= old_e_used + 1); /* duk__get_min_grow_e() is always >= 1 */ - - duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); -} - -/* Grow array part for a new highest array index. */ -DUK_LOCAL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx) { - duk_uint32_t new_e_size; - duk_uint32_t new_a_size; - duk_uint32_t new_h_size; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(highest_arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)); - - /* minimum new length is highest_arr_idx + 1 */ - - new_e_size = DUK_HOBJECT_GET_ESIZE(obj); - new_h_size = DUK_HOBJECT_GET_HSIZE(obj); - new_a_size = highest_arr_idx + duk__get_min_grow_a(highest_arr_idx); - DUK_ASSERT(new_a_size >= highest_arr_idx + 1); /* duk__get_min_grow_a() is always >= 1 */ - - duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); -} - -/* Abandon array part, moving array entries into entries part. - * This requires a props resize, which is a heavy operation. - * We also compact the entries part while we're at it, although - * this is not strictly required. - */ -DUK_LOCAL void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) { - duk_uint32_t new_e_size; - duk_uint32_t new_a_size; - duk_uint32_t new_h_size; - duk_uint32_t e_used; /* actually used, non-NULL keys */ - duk_uint32_t a_used; - duk_uint32_t a_size; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - - e_used = duk__count_used_e_keys(thr, obj); - duk__compute_a_stats(thr, obj, &a_used, &a_size); - - /* - * Must guarantee all actually used array entries will fit into - * new entry part. Add one growth step to ensure we don't run out - * of space right away. - */ - - new_e_size = e_used + a_used; - new_e_size = new_e_size + duk__get_min_grow_e(new_e_size); - new_a_size = 0; -#if defined(DUK_USE_HOBJECT_HASH_PART) - new_h_size = duk__get_default_h_size(new_e_size); -#else - new_h_size = 0; -#endif - - DUK_DD(DUK_DDPRINT("abandon array part for hobject %p, " - "array stats before: e_used=%ld, a_used=%ld, a_size=%ld; " - "resize to e_size=%ld, a_size=%ld, h_size=%ld", - (void *) obj, (long) e_used, (long) a_used, (long) a_size, - (long) new_e_size, (long) new_a_size, (long) new_h_size)); - - duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 1); -} - -/* - * Compact an object. Minimizes allocation size for objects which are - * not likely to be extended. This is useful for internal and non- - * extensible objects, but can also be called for non-extensible objects. - * May abandon the array part if it is computed to be too sparse. - * - * This call is relatively expensive, as it needs to scan both the - * entries and the array part. - * - * The call may fail due to allocation error. - */ - -DUK_INTERNAL void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj) { - duk_uint32_t e_size; /* currently used -> new size */ - duk_uint32_t a_size; /* currently required */ - duk_uint32_t a_used; /* actually used */ - duk_uint32_t h_size; - duk_bool_t abandon_array; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - -#if defined(DUK_USE_ROM_OBJECTS) - if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) { - DUK_DD(DUK_DDPRINT("ignore attempt to compact a rom object")); - return; - } -#endif - - e_size = duk__count_used_e_keys(thr, obj); - duk__compute_a_stats(thr, obj, &a_used, &a_size); - - DUK_DD(DUK_DDPRINT("compacting hobject, used e keys %ld, used a keys %ld, min a size %ld, " - "resized array density would be: %ld/%ld = %lf", - (long) e_size, (long) a_used, (long) a_size, - (long) a_used, (long) a_size, - (double) a_used / (double) a_size)); - - if (duk__abandon_array_density_check(a_used, a_size)) { - DUK_DD(DUK_DDPRINT("decided to abandon array during compaction, a_used=%ld, a_size=%ld", - (long) a_used, (long) a_size)); - abandon_array = 1; - e_size += a_used; - a_size = 0; - } else { - DUK_DD(DUK_DDPRINT("decided to keep array during compaction")); - abandon_array = 0; - } - -#if defined(DUK_USE_HOBJECT_HASH_PART) - if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) { - h_size = duk__get_default_h_size(e_size); - } else { - h_size = 0; - } -#else - h_size = 0; -#endif - - DUK_DD(DUK_DDPRINT("compacting hobject -> new e_size %ld, new a_size=%ld, new h_size=%ld, abandon_array=%ld", - (long) e_size, (long) a_size, (long) h_size, (long) abandon_array)); - - duk__realloc_props(thr, obj, e_size, a_size, h_size, abandon_array); -} - -/* - * Find an existing key from entry part either by linear scan or by - * using the hash index (if it exists). - * - * Sets entry index (and possibly the hash index) to output variables, - * which allows the caller to update the entry and hash entries in-place. - * If entry is not found, both values are set to -1. If entry is found - * but there is no hash part, h_idx is set to -1. - */ - -DUK_INTERNAL void duk_hobject_find_existing_entry(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_int_t *e_idx, duk_int_t *h_idx) { - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(e_idx != NULL); - DUK_ASSERT(h_idx != NULL); - DUK_UNREF(heap); - - if (DUK_LIKELY(DUK_HOBJECT_GET_HSIZE(obj) == 0)) - { - /* Linear scan: more likely because most objects are small. - * This is an important fast path. - * - * XXX: this might be worth inlining for property lookups. - */ - duk_uint_fast32_t i; - duk_uint_fast32_t n; - duk_hstring **h_keys_base; - DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup")); - - h_keys_base = DUK_HOBJECT_E_GET_KEY_BASE(heap, obj); - n = DUK_HOBJECT_GET_ENEXT(obj); - for (i = 0; i < n; i++) { - if (h_keys_base[i] == key) { - *e_idx = i; - *h_idx = -1; - return; - } - } - } -#if defined(DUK_USE_HOBJECT_HASH_PART) - else - { - /* hash lookup */ - duk_uint32_t n; - duk_uint32_t i, step; - duk_uint32_t *h_base; - - DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup")); - - h_base = DUK_HOBJECT_H_GET_BASE(heap, obj); - n = DUK_HOBJECT_GET_HSIZE(obj); - i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n); - step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key)); - - for (;;) { - duk_uint32_t t; - - DUK_ASSERT_DISABLE(i >= 0); /* unsigned */ - DUK_ASSERT(i < DUK_HOBJECT_GET_HSIZE(obj)); - t = h_base[i]; - DUK_ASSERT(t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED || - (t < DUK_HOBJECT_GET_ESIZE(obj))); /* t >= 0 always true, unsigned */ - - if (t == DUK__HASH_UNUSED) { - break; - } else if (t == DUK__HASH_DELETED) { - DUK_DDD(DUK_DDDPRINT("lookup miss (deleted) i=%ld, t=%ld", - (long) i, (long) t)); - } else { - DUK_ASSERT(t < DUK_HOBJECT_GET_ESIZE(obj)); - if (DUK_HOBJECT_E_GET_KEY(heap, obj, t) == key) { - DUK_DDD(DUK_DDDPRINT("lookup hit i=%ld, t=%ld -> key %p", - (long) i, (long) t, (void *) key)); - *e_idx = t; - *h_idx = i; - return; - } - DUK_DDD(DUK_DDDPRINT("lookup miss i=%ld, t=%ld", - (long) i, (long) t)); - } - i = (i + step) % n; - - /* guaranteed to finish, as hash is never full */ - DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n)); - } - } -#endif /* DUK_USE_HOBJECT_HASH_PART */ - - /* not found */ - *e_idx = -1; - *h_idx = -1; -} - -/* For internal use: get non-accessor entry value */ -DUK_INTERNAL duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_hstring *key) { - duk_int_t e_idx; - duk_int_t h_idx; - - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_UNREF(heap); - - duk_hobject_find_existing_entry(heap, obj, key, &e_idx, &h_idx); - if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx)) { - return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx); - } else { - return NULL; - } -} - -/* For internal use: get non-accessor entry value and attributes */ -DUK_INTERNAL duk_tval *duk_hobject_find_existing_entry_tval_ptr_and_attrs(duk_heap *heap, duk_hobject *obj, duk_hstring *key, duk_int_t *out_attrs) { - duk_int_t e_idx; - duk_int_t h_idx; - - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(out_attrs != NULL); - DUK_UNREF(heap); - - duk_hobject_find_existing_entry(heap, obj, key, &e_idx, &h_idx); - if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx)) { - *out_attrs = DUK_HOBJECT_E_GET_FLAGS(heap, obj, e_idx); - return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx); - } else { - *out_attrs = 0; - return NULL; - } -} - -/* For internal use: get array part value */ -DUK_INTERNAL duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_uarridx_t i) { - duk_tval *tv; - - DUK_ASSERT(obj != NULL); - DUK_UNREF(heap); - - if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) { - return NULL; - } - if (i >= DUK_HOBJECT_GET_ASIZE(obj)) { - return NULL; - } - tv = DUK_HOBJECT_A_GET_VALUE_PTR(heap, obj, i); - return tv; -} - -/* - * Allocate and initialize a new entry, resizing the properties allocation - * if necessary. Returns entry index (e_idx) or throws an error if alloc fails. - * - * Sets the key of the entry (increasing the key's refcount), and updates - * the hash part if it exists. Caller must set value and flags, and update - * the entry value refcount. A decref for the previous value is not necessary. - */ - -DUK_LOCAL duk_bool_t duk__alloc_entry_checked(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) { - duk_uint32_t idx; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj) <= DUK_HOBJECT_GET_ESIZE(obj)); - -#ifdef DUK_USE_ASSERTIONS - /* key must not already exist in entry part */ - { - duk_uint_fast32_t i; - for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) { - DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i) != key); - } - } -#endif - - if (DUK_HOBJECT_GET_ENEXT(obj) >= DUK_HOBJECT_GET_ESIZE(obj)) { - /* only need to guarantee 1 more slot, but allocation growth is in chunks */ - DUK_DDD(DUK_DDDPRINT("entry part full, allocate space for one more entry")); - duk__grow_props_for_new_entry_item(thr, obj); - } - DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj) < DUK_HOBJECT_GET_ESIZE(obj)); - idx = DUK_HOBJECT_POSTINC_ENEXT(obj); - - /* previous value is assumed to be garbage, so don't touch it */ - DUK_HOBJECT_E_SET_KEY(thr->heap, obj, idx, key); - DUK_HSTRING_INCREF(thr, key); - -#if defined(DUK_USE_HOBJECT_HASH_PART) - if (DUK_UNLIKELY(DUK_HOBJECT_GET_HSIZE(obj) > 0)) { - duk_uint32_t n; - duk_uint32_t i, step; - duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(thr->heap, obj); - - n = DUK_HOBJECT_GET_HSIZE(obj); - i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n); - step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key)); - - for (;;) { - duk_uint32_t t = h_base[i]; - if (t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED) { - DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() inserted key into hash part, %ld -> %ld", - (long) i, (long) idx)); - DUK_ASSERT_DISABLE(i >= 0); /* unsigned */ - DUK_ASSERT(i < DUK_HOBJECT_GET_HSIZE(obj)); - DUK_ASSERT_DISABLE(idx >= 0); - DUK_ASSERT(idx < DUK_HOBJECT_GET_ESIZE(obj)); - h_base[i] = idx; - break; - } - DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() miss %ld", (long) i)); - i = (i + step) % n; - - /* guaranteed to find an empty slot */ - DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), DUK_HOBJECT_GET_HSIZE(obj))); - } - } -#endif /* DUK_USE_HOBJECT_HASH_PART */ - - /* Note: we could return the hash index here too, but it's not - * needed right now. - */ - - DUK_ASSERT_DISABLE(idx >= 0); - DUK_ASSERT(idx < DUK_HOBJECT_GET_ESIZE(obj)); - DUK_ASSERT(idx < DUK_HOBJECT_GET_ENEXT(obj)); - return idx; -} - -/* - * Object internal value - * - * Returned value is guaranteed to be reachable / incref'd, caller does not need - * to incref OR decref. No proxies or accessors are invoked, no prototype walk. - */ - -DUK_INTERNAL duk_bool_t duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv_out) { - duk_int_t e_idx; - duk_int_t h_idx; - - DUK_ASSERT(heap != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(tv_out != NULL); - - /* always in entry part, no need to look up parents etc */ - duk_hobject_find_existing_entry(heap, obj, DUK_HEAP_STRING_INT_VALUE(heap), &e_idx, &h_idx); - if (e_idx >= 0) { - DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx)); - DUK_TVAL_SET_TVAL(tv_out, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx)); - return 1; - } - DUK_TVAL_SET_UNDEFINED(tv_out); - return 0; -} - -DUK_INTERNAL duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj) { - duk_tval tv; - - DUK_ASSERT(heap != NULL); - DUK_ASSERT(obj != NULL); - - /* This is not strictly necessary, but avoids compiler warnings; e.g. - * gcc won't reliably detect that no uninitialized data is read below. - */ - DUK_MEMZERO((void *) &tv, sizeof(duk_tval)); - - if (duk_hobject_get_internal_value(heap, obj, &tv)) { - duk_hstring *h; - DUK_ASSERT(DUK_TVAL_IS_STRING(&tv)); - h = DUK_TVAL_GET_STRING(&tv); - return h; - } - - return NULL; -} - -/* - * Arguments handling helpers (argument map mainly). - * - * An arguments object has exotic behavior for some numeric indices. - * Accesses may translate to identifier operations which may have - * arbitrary side effects (potentially invalidating any duk_tval - * pointers). - */ - -/* Lookup 'key' from arguments internal 'map', perform a variable lookup - * if mapped, and leave the result on top of stack (and return non-zero). - * Used in E5 Section 10.6 algorithms [[Get]] and [[GetOwnProperty]]. - */ -DUK_LOCAL -duk_bool_t duk__lookup_arguments_map(duk_hthread *thr, - duk_hobject *obj, - duk_hstring *key, - duk_propdesc *temp_desc, - duk_hobject **out_map, - duk_hobject **out_varenv) { - duk_context *ctx = (duk_context *) thr; - duk_hobject *map; - duk_hobject *varenv; - duk_bool_t rc; - - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - DUK_DDD(DUK_DDDPRINT("arguments map lookup: thr=%p, obj=%p, key=%p, temp_desc=%p " - "(obj -> %!O, key -> %!O)", - (void *) thr, (void *) obj, (void *) key, (void *) temp_desc, - (duk_heaphdr *) obj, (duk_heaphdr *) key)); - - if (!duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) { - DUK_DDD(DUK_DDDPRINT("-> no 'map'")); - return 0; - } - - map = duk_require_hobject(ctx, -1); - DUK_ASSERT(map != NULL); - duk_pop(ctx); /* map is reachable through obj */ - - if (!duk_hobject_get_own_propdesc(thr, map, key, temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) { - DUK_DDD(DUK_DDDPRINT("-> 'map' exists, but key not in map")); - return 0; - } - - /* [... varname] */ - DUK_DDD(DUK_DDDPRINT("-> 'map' exists, and contains key, key is mapped to argument/variable binding %!T", - (duk_tval *) duk_get_tval(ctx, -1))); - DUK_ASSERT(duk_is_string(ctx, -1)); /* guaranteed when building arguments */ - - /* get varenv for varname (callee's declarative lexical environment) */ - rc = duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_VARENV(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE); - DUK_UNREF(rc); - DUK_ASSERT(rc != 0); /* arguments MUST have an initialized lexical environment reference */ - varenv = duk_require_hobject(ctx, -1); - DUK_ASSERT(varenv != NULL); - duk_pop(ctx); /* varenv remains reachable through 'obj' */ - - DUK_DDD(DUK_DDDPRINT("arguments varenv is: %!dO", (duk_heaphdr *) varenv)); - - /* success: leave varname in stack */ - *out_map = map; - *out_varenv = varenv; - return 1; /* [... varname] */ -} - -/* Lookup 'key' from arguments internal 'map', and leave replacement value - * on stack top if mapped (and return non-zero). - * Used in E5 Section 10.6 algorithm for [[GetOwnProperty]] (used by [[Get]]). - */ -DUK_LOCAL duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) { - duk_context *ctx = (duk_context *) thr; - duk_hobject *map; - duk_hobject *varenv; - duk_hstring *varname; - - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) { - DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic get behavior")); - return 0; - } - - /* [... varname] */ - - varname = duk_require_hstring(ctx, -1); - DUK_ASSERT(varname != NULL); - duk_pop(ctx); /* varname is still reachable */ - - DUK_DDD(DUK_DDDPRINT("arguments object automatic getvar for a bound variable; " - "key=%!O, varname=%!O", - (duk_heaphdr *) key, - (duk_heaphdr *) varname)); - - (void) duk_js_getvar_envrec(thr, varenv, varname, 1 /*throw*/); - - /* [... value this_binding] */ - - duk_pop(ctx); - - /* leave result on stack top */ - return 1; -} - -/* Lookup 'key' from arguments internal 'map', perform a variable write if mapped. - * Used in E5 Section 10.6 algorithm for [[DefineOwnProperty]] (used by [[Put]]). - * Assumes stack top contains 'put' value (which is NOT popped). - */ -DUK_LOCAL void duk__check_arguments_map_for_put(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc, duk_bool_t throw_flag) { - duk_context *ctx = (duk_context *) thr; - duk_hobject *map; - duk_hobject *varenv; - duk_hstring *varname; - - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) { - DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic put behavior")); - return; - } - - /* [... put_value varname] */ - - varname = duk_require_hstring(ctx, -1); - DUK_ASSERT(varname != NULL); - duk_pop(ctx); /* varname is still reachable */ - - DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; " - "key=%!O, varname=%!O, value=%!T", - (duk_heaphdr *) key, - (duk_heaphdr *) varname, - (duk_tval *) duk_require_tval(ctx, -1))); - - /* [... put_value] */ - - /* - * Note: although arguments object variable mappings are only established - * for non-strict functions (and a call to a non-strict function created - * the arguments object in question), an inner strict function may be doing - * the actual property write. Hence the throw_flag applied here comes from - * the property write call. - */ - - duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, -1), throw_flag); - - /* [... put_value] */ -} - -/* Lookup 'key' from arguments internal 'map', delete mapping if found. - * Used in E5 Section 10.6 algorithm for [[Delete]]. Note that the - * variable/argument itself (where the map points) is not deleted. - */ -DUK_LOCAL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) { - duk_context *ctx = (duk_context *) thr; - duk_hobject *map; - - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - if (!duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) { - DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic delete behavior")); - return; - } - - map = duk_require_hobject(ctx, -1); - DUK_ASSERT(map != NULL); - duk_pop(ctx); /* map is reachable through obj */ - - DUK_DDD(DUK_DDDPRINT("-> have 'map', delete key %!O from map (if exists)); ignore result", - (duk_heaphdr *) key)); - - /* Note: no recursion issue, we can trust 'map' to behave */ - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(map)); - DUK_DDD(DUK_DDDPRINT("map before deletion: %!O", (duk_heaphdr *) map)); - (void) duk_hobject_delprop_raw(thr, map, key, 0); /* ignore result */ - DUK_DDD(DUK_DDDPRINT("map after deletion: %!O", (duk_heaphdr *) map)); -} - -/* - * Ecmascript compliant [[GetOwnProperty]](P), for internal use only. - * - * If property is found: - * - Fills descriptor fields to 'out_desc' - * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the - * property onto the stack ('undefined' for accessor properties). - * - Returns non-zero - * - * If property is not found: - * - 'out_desc' is left in untouched state (possibly garbage) - * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE - * set) - * - Returns zero - * - * Notes: - * - * - Getting a property descriptor may cause an allocation (and hence - * GC) to take place, hence reachability and refcount of all related - * values matter. Reallocation of value stack, properties, etc may - * invalidate many duk_tval pointers (concretely, those which reside - * in memory areas subject to reallocation). However, heap object - * pointers are never affected (heap objects have stable pointers). - * - * - The value of a plain property is always reachable and has a non-zero - * reference count. - * - * - The value of a virtual property is not necessarily reachable from - * elsewhere and may have a refcount of zero. Hence we push it onto - * the valstack for the caller, which ensures it remains reachable - * while it is needed. - * - * - There are no virtual accessor properties. Hence, all getters and - * setters are always related to concretely stored properties, which - * ensures that the get/set functions in the resulting descriptor are - * reachable and have non-zero refcounts. Should there be virtual - * accessor properties later, this would need to change. - */ - -DUK_LOCAL duk_bool_t duk__get_own_propdesc_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_uint32_t arr_idx, duk_propdesc *out_desc, duk_small_uint_t flags) { - duk_context *ctx = (duk_context *) thr; - duk_tval *tv; - - DUK_DDD(DUK_DDDPRINT("duk_hobject_get_own_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, " - "arr_idx=%ld (obj -> %!O, key -> %!O)", - (void *) thr, (void *) obj, (void *) key, (void *) out_desc, - (long) flags, (long) arr_idx, - (duk_heaphdr *) obj, (duk_heaphdr *) key)); - - DUK_ASSERT(ctx != NULL); - DUK_ASSERT(thr != NULL); - DUK_ASSERT(thr->heap != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(out_desc != NULL); - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - /* XXX: optimize this filling behavior later */ - out_desc->flags = 0; - out_desc->get = NULL; - out_desc->set = NULL; - out_desc->e_idx = -1; - out_desc->h_idx = -1; - out_desc->a_idx = -1; - - /* - * Array part - */ - - if (DUK_HOBJECT_HAS_ARRAY_PART(obj) && arr_idx != DUK__NO_ARRAY_INDEX) { - if (arr_idx < DUK_HOBJECT_GET_ASIZE(obj)) { - tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx); - if (!DUK_TVAL_IS_UNUSED(tv)) { - DUK_DDD(DUK_DDDPRINT("-> found in array part")); - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_tval(ctx, tv); - } - /* implicit attributes */ - out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE | - DUK_PROPDESC_FLAG_CONFIGURABLE | - DUK_PROPDESC_FLAG_ENUMERABLE; - out_desc->a_idx = arr_idx; - goto prop_found; - } - } - /* assume array part is comprehensive (contains all array indexed elements - * or none of them); hence no need to check the entries part here. - */ - DUK_DDD(DUK_DDDPRINT("-> not found as a concrete property (has array part, " - "should be there if present)")); - goto prop_not_found_concrete; - } - - /* - * Entries part - */ - - duk_hobject_find_existing_entry(thr->heap, obj, key, &out_desc->e_idx, &out_desc->h_idx); - if (out_desc->e_idx >= 0) { - duk_int_t e_idx = out_desc->e_idx; - out_desc->flags = DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, e_idx); - if (out_desc->flags & DUK_PROPDESC_FLAG_ACCESSOR) { - DUK_DDD(DUK_DDDPRINT("-> found accessor property in entry part")); - out_desc->get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, e_idx); - out_desc->set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, e_idx); - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - /* a dummy undefined value is pushed to make valstack - * behavior uniform for caller - */ - duk_push_undefined(ctx); - } - } else { - DUK_DDD(DUK_DDDPRINT("-> found plain property in entry part")); - tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, e_idx); - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_tval(ctx, tv); - } - } - goto prop_found; - } - - /* - * Not found as a concrete property, check whether a String object - * virtual property matches. - */ - - prop_not_found_concrete: - - if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj)) { - DUK_DDD(DUK_DDDPRINT("string object exotic property get for key: %!O, arr_idx: %ld", - (duk_heaphdr *) key, (long) arr_idx)); - - if (arr_idx != DUK__NO_ARRAY_INDEX) { - duk_hstring *h_val; - - DUK_DDD(DUK_DDDPRINT("array index exists")); - - h_val = duk_hobject_get_internal_value_string(thr->heap, obj); - DUK_ASSERT(h_val); - if (arr_idx < DUK_HSTRING_GET_CHARLEN(h_val)) { - DUK_DDD(DUK_DDDPRINT("-> found, array index inside string")); - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_hstring(ctx, h_val); - duk_substring(ctx, -1, arr_idx, arr_idx + 1); /* [str] -> [substr] */ - } - out_desc->flags = DUK_PROPDESC_FLAG_ENUMERABLE | /* E5 Section 15.5.5.2 */ - DUK_PROPDESC_FLAG_VIRTUAL; - - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)); - return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */ - } else { - /* index is above internal string length -> property is fully normal */ - DUK_DDD(DUK_DDDPRINT("array index outside string -> normal property")); - } - } else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) { - duk_hstring *h_val; - - DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior")); - - h_val = duk_hobject_get_internal_value_string(thr->heap, obj); - DUK_ASSERT(h_val != NULL); - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h_val)); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; /* E5 Section 15.5.5.1 */ - - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)); - return 1; /* cannot be arguments exotic */ - } - } else if (DUK_HOBJECT_IS_BUFFEROBJECT(obj)) { - duk_hbufferobject *h_bufobj; - duk_uint_t byte_off; - duk_small_uint_t elem_size; - - h_bufobj = (duk_hbufferobject *) obj; - DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj); - DUK_DDD(DUK_DDDPRINT("bufferobject property get for key: %!O, arr_idx: %ld", - (duk_heaphdr *) key, (long) arr_idx)); - - if (arr_idx != DUK__NO_ARRAY_INDEX) { - DUK_DDD(DUK_DDDPRINT("array index exists")); - - /* Careful with wrapping: arr_idx upshift may easily wrap, whereas - * length downshift won't. - */ - if (arr_idx < (h_bufobj->length >> h_bufobj->shift)) { - byte_off = arr_idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */ - elem_size = 1 << h_bufobj->shift; - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_uint8_t *data; - - if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) { - data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off; - duk_hbufferobject_push_validated_read(ctx, h_bufobj, data, elem_size); - } else { - DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)")); - duk_push_uint(ctx, 0); - } - } - out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE | - DUK_PROPDESC_FLAG_ENUMERABLE | - DUK_PROPDESC_FLAG_VIRTUAL; - - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)); - return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */ - } else { - /* index is above internal buffer length -> property is fully normal */ - DUK_DDD(DUK_DDDPRINT("array index outside buffer -> normal property")); - } - } else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) { - DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior")); - - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - /* Length in elements: take into account shift, but - * intentionally don't check the underlying buffer here. - */ - duk_push_uint(ctx, h_bufobj->length >> h_bufobj->shift); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; - - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)); - return 1; /* cannot be arguments exotic */ - } else if (key == DUK_HTHREAD_STRING_BYTE_LENGTH(thr)) { - /* If neutered must return 0; length is zeroed during - * neutering. - */ - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_uint(ctx, h_bufobj->length); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; - return 1; /* cannot be arguments exotic */ - } else if (key == DUK_HTHREAD_STRING_BYTE_OFFSET(thr)) { - /* If neutered must return 0; offset is zeroed during - * neutering. - */ - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_uint(ctx, h_bufobj->offset); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; - return 1; /* cannot be arguments exotic */ - } else if (key == DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr)) { - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_push_uint(ctx, 1 << h_bufobj->shift); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; - return 1; /* cannot be arguments exotic */ - } - } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj)) { - DUK_DDD(DUK_DDDPRINT("duktape/c object exotic property get for key: %!O, arr_idx: %ld", - (duk_heaphdr *) key, (long) arr_idx)); - - if (key == DUK_HTHREAD_STRING_LENGTH(thr)) { - DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior")); - - if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) { - duk_int16_t func_nargs = ((duk_hnativefunction *) obj)->nargs; - duk_push_int(ctx, func_nargs == DUK_HNATIVEFUNCTION_NARGS_VARARGS ? 0 : func_nargs); - } - out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; /* not enumerable */ - - DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)); - return 1; /* cannot be arguments exotic */ - } - } - - /* Array properties have exotic behavior but they are concrete, - * so no special handling here. - * - * Arguments exotic behavior (E5 Section 10.6, [[GetOwnProperty]] - * is only relevant as a post-check implemented below; hence no - * check here. - */ - - /* - * Not found as concrete or virtual - */ - - DUK_DDD(DUK_DDDPRINT("-> not found (virtual, entry part, or array part)")); - return 0; - - /* - * Found - * - * Arguments object has exotic post-processing, see E5 Section 10.6, - * description of [[GetOwnProperty]] variant for arguments. - */ - - prop_found: - DUK_DDD(DUK_DDDPRINT("-> property found, checking for arguments exotic post-behavior")); - - /* Notes: - * - only numbered indices are relevant, so arr_idx fast reject is good - * (this is valid unless there are more than 4**32-1 arguments). - * - since variable lookup has no side effects, this can be skipped if - * DUK_GETDESC_FLAG_PUSH_VALUE is not set. - */ - - if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) && - arr_idx != DUK__NO_ARRAY_INDEX && - (flags & DUK_GETDESC_FLAG_PUSH_VALUE)) { - duk_propdesc temp_desc; - - /* Magically bound variable cannot be an accessor. However, - * there may be an accessor property (or a plain property) in - * place with magic behavior removed. This happens e.g. when - * a magic property is redefined with defineProperty(). - * Cannot assert for "not accessor" here. - */ - - /* replaces top of stack with new value if necessary */ - DUK_ASSERT((flags & DUK_GETDESC_FLAG_PUSH_VALUE) != 0); - - if (duk__check_arguments_map_for_get(thr, obj, key, &temp_desc)) { - DUK_DDD(DUK_DDDPRINT("-> arguments exotic behavior overrides result: %!T -> %!T", - (duk_tval *) duk_get_tval(ctx, -2), - (duk_tval *) duk_get_tval(ctx, -1))); - /* [... old_result result] -> [... result] */ - duk_remove(ctx, -2); - } - } - - return 1; -} - -DUK_INTERNAL duk_bool_t duk_hobject_get_own_propdesc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_small_uint_t flags) { - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(out_desc != NULL); - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - return duk__get_own_propdesc_raw(thr, obj, key, DUK_HSTRING_GET_ARRIDX_SLOW(key), out_desc, flags); -} - -/* - * Ecmascript compliant [[GetProperty]](P), for internal use only. - * - * If property is found: - * - Fills descriptor fields to 'out_desc' - * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the - * property onto the stack ('undefined' for accessor properties). - * - Returns non-zero - * - * If property is not found: - * - 'out_desc' is left in untouched state (possibly garbage) - * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE - * set) - * - Returns zero - * - * May cause arbitrary side effects and invalidate (most) duk_tval - * pointers. - */ - -DUK_LOCAL duk_bool_t duk__get_propdesc(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *out_desc, duk_small_uint_t flags) { - duk_hobject *curr; - duk_uint32_t arr_idx; - duk_uint_t sanity; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(thr->heap != NULL); - DUK_ASSERT(obj != NULL); - DUK_ASSERT(key != NULL); - DUK_ASSERT(out_desc != NULL); - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key); - - DUK_DDD(DUK_DDDPRINT("duk__get_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, " - "arr_idx=%ld (obj -> %!O, key -> %!O)", - (void *) thr, (void *) obj, (void *) key, (void *) out_desc, - (long) flags, (long) arr_idx, - (duk_heaphdr *) obj, (duk_heaphdr *) key)); - - curr = obj; - DUK_ASSERT(curr != NULL); - sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; - do { - if (duk__get_own_propdesc_raw(thr, curr, key, arr_idx, out_desc, flags)) { - /* stack contains value (if requested), 'out_desc' is set */ - return 1; - } - - /* not found in 'curr', next in prototype chain; impose max depth */ - if (sanity-- == 0) { - if (flags & DUK_GETDESC_FLAG_IGNORE_PROTOLOOP) { - /* treat like property not found */ - break; - } else { - DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT); - } - } - curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr); - } while (curr); - - /* out_desc is left untouched (possibly garbage), caller must use return - * value to determine whether out_desc can be looked up - */ - - return 0; -} - -/* - * Shallow fast path checks for accessing array elements with numeric - * indices. The goal is to try to avoid coercing an array index to an - * (interned) string for the most common lookups, in particular, for - * standard Array objects. - * - * Interning is avoided but only for a very narrow set of cases: - * - Object has array part, index is within array allocation, and - * value is not unused (= key exists) - * - Object has no interfering exotic behavior (e.g. arguments or - * string object exotic behaviors interfere, array exotic - * behavior does not). - * - * Current shortcoming: if key does not exist (even if it is within - * the array allocation range) a slow path lookup with interning is - * always required. This can probably be fixed so that there is a - * quick fast path for non-existent elements as well, at least for - * standard Array objects. - */ - -DUK_LOCAL duk_tval *duk__getprop_shallow_fastpath_array_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key) { - duk_tval *tv; - duk_uint32_t idx; - - DUK_UNREF(thr); - - if (!(DUK_HOBJECT_HAS_ARRAY_PART(obj) && - !DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) && - !DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj) && - !DUK_HOBJECT_IS_BUFFEROBJECT(obj) && - !DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) { - /* Must have array part and no conflicting exotic behaviors. - * Doesn't need to have array special behavior, e.g. Arguments - * object has array part. - */ - return NULL; - } - - /* Arrays never have other exotic behaviors. */ - - DUK_DDD(DUK_DDDPRINT("fast path attempt (no exotic string/arguments/buffer " - "behavior, object has array part)")); - -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - idx = duk__tval_fastint_to_arr_idx(tv_key); - } else -#endif - if (DUK_TVAL_IS_DOUBLE(tv_key)) { - idx = duk__tval_number_to_arr_idx(tv_key); - } else { - DUK_DDD(DUK_DDDPRINT("key is not a number")); - return NULL; - } - - /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which - * is 0xffffffffUL. We don't need to check for that explicitly - * because 0xffffffffUL will never be inside object 'a_size'. - */ - - if (idx >= DUK_HOBJECT_GET_ASIZE(obj)) { - DUK_DDD(DUK_DDDPRINT("key is not an array index or outside array part")); - return NULL; - } - DUK_ASSERT(idx != 0xffffffffUL); - DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX); - - /* XXX: for array instances we could take a shortcut here and assume - * Array.prototype doesn't contain an array index property. - */ - - DUK_DDD(DUK_DDDPRINT("key is a valid array index and inside array part")); - tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, idx); - if (!DUK_TVAL_IS_UNUSED(tv)) { - DUK_DDD(DUK_DDDPRINT("-> fast path successful")); - return tv; - } - - DUK_DDD(DUK_DDDPRINT("fast path attempt failed, fall back to slow path")); - return NULL; -} - -DUK_LOCAL duk_bool_t duk__putprop_shallow_fastpath_array_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key, duk_tval *tv_val, duk_propdesc *temp_desc) { - duk_tval *tv; - duk_uint32_t idx; - duk_uint32_t old_len, new_len; - - if (!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj) && - DUK_HOBJECT_HAS_ARRAY_PART(obj) && - DUK_HOBJECT_HAS_EXTENSIBLE(obj))) { - return 0; - } - DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); /* caller ensures */ - -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - idx = duk__tval_fastint_to_arr_idx(tv_key); - } else -#endif - if (DUK_TVAL_IS_DOUBLE(tv_key)) { - idx = duk__tval_number_to_arr_idx(tv_key); - } else { - DUK_DDD(DUK_DDDPRINT("key is not a number")); - return 0; - } - - /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which - * is 0xffffffffUL. We don't need to check for that explicitly - * because 0xffffffffUL will never be inside object 'a_size'. - */ - - if (idx >= DUK_HOBJECT_GET_ASIZE(obj)) { /* for resizing of array part, use slow path */ - return 0; - } - DUK_ASSERT(idx != 0xffffffffUL); - DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX); - - old_len = duk__get_old_array_length(thr, obj, temp_desc); - - if (idx >= old_len) { - DUK_DDD(DUK_DDDPRINT("write new array entry requires length update " - "(arr_idx=%ld, old_len=%ld)", - (long) idx, (long) old_len)); - if (!(temp_desc->flags & DUK_PROPDESC_FLAG_WRITABLE)) { - DUK_ERROR_TYPE(thr, DUK_STR_NOT_WRITABLE); - return 0; /* not reachable */ - } - new_len = idx + 1; - - /* No resize has occurred so temp_desc->e_idx is still OK */ - tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, temp_desc->e_idx); - DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); - DUK_TVAL_SET_FASTINT_U32(tv, new_len); /* no need for decref/incref because value is a number */ - } else { - ; - } - - tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, idx); - DUK_TVAL_SET_TVAL_UPDREF(thr, tv, tv_val); /* side effects */ - - DUK_DDD(DUK_DDDPRINT("array fast path success for index %ld", (long) idx)); - return 1; -} - -/* - * Fast path for bufferobject getprop/putprop - */ - -DUK_LOCAL duk_bool_t duk__getprop_fastpath_bufobj_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key) { - duk_context *ctx; - duk_uint32_t idx; - duk_hbufferobject *h_bufobj; - duk_uint_t byte_off; - duk_small_uint_t elem_size; - duk_uint8_t *data; - - ctx = (duk_context *) thr; - - if (!DUK_HOBJECT_IS_BUFFEROBJECT(obj)) { - return 0; - } - h_bufobj = (duk_hbufferobject *) obj; - -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - idx = duk__tval_fastint_to_arr_idx(tv_key); - } else -#endif - if (DUK_TVAL_IS_DOUBLE(tv_key)) { - idx = duk__tval_number_to_arr_idx(tv_key); - } else { - return 0; - } - - /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which - * is 0xffffffffUL. We don't need to check for that explicitly - * because 0xffffffffUL will never be inside bufferobject length. - */ - - /* Careful with wrapping (left shifting idx would be unsafe). */ - if (idx >= (h_bufobj->length >> h_bufobj->shift)) { - return 0; - } - DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX); - - byte_off = idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */ - elem_size = 1 << h_bufobj->shift; - - if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) { - data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off; - duk_hbufferobject_push_validated_read(ctx, h_bufobj, data, elem_size); - } else { - DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)")); - duk_push_uint(ctx, 0); - } - - return 1; -} - -DUK_LOCAL duk_bool_t duk__putprop_fastpath_bufobj_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key, duk_tval *tv_val) { - duk_context *ctx; - duk_uint32_t idx; - duk_hbufferobject *h_bufobj; - duk_uint_t byte_off; - duk_small_uint_t elem_size; - duk_uint8_t *data; - - ctx = (duk_context *) thr; - - if (!(DUK_HOBJECT_IS_BUFFEROBJECT(obj) && - DUK_TVAL_IS_NUMBER(tv_val))) { - return 0; - } - DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); /* caller ensures; rom objects are never bufferobjects now */ - - h_bufobj = (duk_hbufferobject *) obj; -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - idx = duk__tval_fastint_to_arr_idx(tv_key); - } else -#endif - if (DUK_TVAL_IS_DOUBLE(tv_key)) { - idx = duk__tval_number_to_arr_idx(tv_key); - } else { - return 0; - } - - /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which - * is 0xffffffffUL. We don't need to check for that explicitly - * because 0xffffffffUL will never be inside bufferobject length. - */ - - /* Careful with wrapping (left shifting idx would be unsafe). */ - if (idx >= (h_bufobj->length >> h_bufobj->shift)) { - return 0; - } - DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX); - - byte_off = idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */ - elem_size = 1 << h_bufobj->shift; - - /* Value is required to be a number in the fast path so there - * are no side effects in write coercion. - */ - duk_push_tval(ctx, tv_val); - DUK_ASSERT(duk_is_number(ctx, -1)); - - if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) { - data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off; - duk_hbufferobject_validated_write(ctx, h_bufobj, data, elem_size); - } else { - DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (write skipped)")); - } - - duk_pop(ctx); - return 1; -} - -/* - * GETPROP: Ecmascript property read. - */ - -DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) { - duk_context *ctx = (duk_context *) thr; - duk_tval tv_obj_copy; - duk_tval tv_key_copy; - duk_hobject *curr = NULL; - duk_hstring *key = NULL; - duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX; - duk_propdesc desc; - duk_uint_t sanity; - - DUK_DDD(DUK_DDDPRINT("getprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)", - (void *) thr, (void *) tv_obj, (void *) tv_key, - (duk_tval *) tv_obj, (duk_tval *) tv_key)); - - DUK_ASSERT(ctx != NULL); - DUK_ASSERT(thr != NULL); - DUK_ASSERT(thr->heap != NULL); - DUK_ASSERT(tv_obj != NULL); - DUK_ASSERT(tv_key != NULL); - - DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE); - - /* - * Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of - * them being invalidated by a valstack resize. - * - * XXX: this is now an overkill for many fast paths. Rework this - * to be faster (although switching to a valstack discipline might - * be a better solution overall). - */ - - DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj); - DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key); - tv_obj = &tv_obj_copy; - tv_key = &tv_key_copy; - - /* - * Coercion and fast path processing - */ - - switch (DUK_TVAL_GET_TAG(tv_obj)) { - case DUK_TAG_UNDEFINED: - case DUK_TAG_NULL: { - /* Note: unconditional throw */ - DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject")); -#if defined(DUK_USE_PARANOID_ERRORS) - DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE); -#else - DUK_ERROR_FMT2(thr, DUK_ERR_TYPE_ERROR, "cannot read property %s of %s", - duk_push_string_tval_readable(ctx, tv_key), duk_push_string_tval_readable(ctx, tv_obj)); -#endif - return 0; - } - - case DUK_TAG_BOOLEAN: { - DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype")); - curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE]; - break; - } - - case DUK_TAG_STRING: { - duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj); - duk_int_t pop_count; - -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - arr_idx = duk__tval_fastint_to_arr_idx(tv_key); - DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path fastint; arr_idx %ld", (long) arr_idx)); - pop_count = 0; - } else -#endif - if (DUK_TVAL_IS_NUMBER(tv_key)) { - arr_idx = duk__tval_number_to_arr_idx(tv_key); - DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path number; arr_idx %ld", (long) arr_idx)); - pop_count = 0; - } else { - arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key); - DUK_ASSERT(key != NULL); - DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after " - "coercion key is %!T, arr_idx %ld", - (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx)); - pop_count = 1; - } - - if (arr_idx != DUK__NO_ARRAY_INDEX && - arr_idx < DUK_HSTRING_GET_CHARLEN(h)) { - duk_pop_n(ctx, pop_count); - duk_push_hstring(ctx, h); - duk_substring(ctx, -1, arr_idx, arr_idx + 1); /* [str] -> [substr] */ - - DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is an index inside string length " - "after coercion -> return char)", - (duk_tval *) duk_get_tval(ctx, -1))); - return 1; - } - - if (pop_count == 0) { - /* This is a pretty awkward control flow, but we need to recheck the - * key coercion here. - */ - arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key); - DUK_ASSERT(key != NULL); - DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after " - "coercion key is %!T, arr_idx %ld", - (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx)); - } - - if (key == DUK_HTHREAD_STRING_LENGTH(thr)) { - duk_pop(ctx); /* [key] -> [] */ - duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h)); /* [] -> [res] */ - - DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is 'length' after coercion -> " - "return string length)", - (duk_tval *) duk_get_tval(ctx, -1))); - return 1; - } - DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype")); - curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE]; - goto lookup; /* avoid double coercion */ - } - - case DUK_TAG_OBJECT: { - duk_tval *tmp; - - curr = DUK_TVAL_GET_OBJECT(tv_obj); - DUK_ASSERT(curr != NULL); - - tmp = duk__getprop_shallow_fastpath_array_tval(thr, curr, tv_key); - if (tmp) { - duk_push_tval(ctx, tmp); - - DUK_DDD(DUK_DDDPRINT("-> %!T (base is object, key is a number, array part " - "fast path)", - (duk_tval *) duk_get_tval(ctx, -1))); - return 1; - } - - if (duk__getprop_fastpath_bufobj_tval(thr, curr, tv_key) != 0) { - /* Read value pushed on stack. */ - DUK_DDD(DUK_DDDPRINT("-> %!T (base is bufobj, key is a number, bufferobject " - "fast path)", - (duk_tval *) duk_get_tval(ctx, -1))); - return 1; - } - -#if defined(DUK_USE_ES6_PROXY) - if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(curr))) { - duk_hobject *h_target; - - if (duk__proxy_check_prop(thr, curr, DUK_STRIDX_GET, tv_key, &h_target)) { - /* -> [ ... trap handler ] */ - DUK_DDD(DUK_DDDPRINT("-> proxy object 'get' for key %!T", (duk_tval *) tv_key)); - duk_push_hobject(ctx, h_target); /* target */ - duk_push_tval(ctx, tv_key); /* P */ - duk_push_tval(ctx, tv_obj); /* Receiver: Proxy object */ - duk_call_method(ctx, 3 /*nargs*/); - - /* Target object must be checked for a conflicting - * non-configurable property. - */ - arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key); - DUK_ASSERT(key != NULL); - - if (duk__get_own_propdesc_raw(thr, h_target, key, arr_idx, &desc, DUK_GETDESC_FLAG_PUSH_VALUE)) { - duk_tval *tv_hook = duk_require_tval(ctx, -3); /* value from hook */ - duk_tval *tv_targ = duk_require_tval(ctx, -1); /* value from target */ - duk_bool_t datadesc_reject; - duk_bool_t accdesc_reject; - - DUK_DDD(DUK_DDDPRINT("proxy 'get': target has matching property %!O, check for " - "conflicting property; tv_hook=%!T, tv_targ=%!T, desc.flags=0x%08lx, " - "desc.get=%p, desc.set=%p", - (duk_heaphdr *) key, (duk_tval *) tv_hook, (duk_tval *) tv_targ, - (unsigned long) desc.flags, - (void *) desc.get, (void *) desc.set)); - - datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) && - !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && - !(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) && - !duk_js_samevalue(tv_hook, tv_targ); - accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) && - !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && - (desc.get == NULL) && - !DUK_TVAL_IS_UNDEFINED(tv_hook); - if (datadesc_reject || accdesc_reject) { - DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED); - } - - duk_pop_2(ctx); - } else { - duk_pop(ctx); - } - return 1; /* return value */ - } - - curr = h_target; /* resume lookup from target */ - DUK_TVAL_SET_OBJECT(tv_obj, curr); - } -#endif /* DUK_USE_ES6_PROXY */ - - if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(curr)) { - arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key); - DUK_ASSERT(key != NULL); - - if (duk__check_arguments_map_for_get(thr, curr, key, &desc)) { - DUK_DDD(DUK_DDDPRINT("-> %!T (base is object with arguments exotic behavior, " - "key matches magically bound property -> skip standard " - "Get with replacement value)", - (duk_tval *) duk_get_tval(ctx, -1))); - - /* no need for 'caller' post-check, because 'key' must be an array index */ - - duk_remove(ctx, -2); /* [key result] -> [result] */ - return 1; - } - - goto lookup; /* avoid double coercion */ - } - break; - } - - /* Buffer has virtual properties similar to string, but indexed values - * are numbers, not 1-byte buffers/strings which would perform badly. - */ - case DUK_TAG_BUFFER: { - duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj); - duk_int_t pop_count; - - /* - * Because buffer values are often looped over, a number fast path - * is important. - */ - -#if defined(DUK_USE_FASTINT) - if (DUK_TVAL_IS_FASTINT(tv_key)) { - arr_idx = duk__tval_fastint_to_arr_idx(tv_
<TRUNCATED>
