2 * Hobject property set/get functionality.
4 * This is very central functionality for size, performance, and compliance.
5 * It is also rather intricate; see hobject-algorithms.rst for discussion on
6 * the algorithms and memory-management.rst for discussion on refcounts and
11 * - It might be tempting to assert "refcount nonzero" for objects
12 * being operated on, but that's not always correct: objects with
13 * a zero refcount may be operated on by the refcount implementation
14 * (finalization) for instance. Hence, no refcount assertions are made.
16 * - Many operations (memory allocation, identifier operations, etc)
17 * may cause arbitrary side effects (e.g. through GC and finalization).
18 * These side effects may invalidate duk_tval pointers which point to
19 * areas subject to reallocation (like value stack). Heap objects
20 * themselves have stable pointers. Holding heap object pointers or
21 * duk_tval copies is not problematic with respect to side effects;
22 * care must be taken when holding and using argument duk_tval pointers.
24 * - If a finalizer is executed, it may operate on the the same object
25 * we're currently dealing with. For instance, the finalizer might
26 * delete a certain property which has already been looked up and
27 * confirmed to exist. Ideally finalizers would be disabled if GC
28 * happens during property access. At the moment property table realloc
29 * disables finalizers, and all DECREFs may cause arbitrary changes so
30 * handle DECREF carefully.
32 * - The order of operations for a DECREF matters. When DECREF is executed,
33 * the entire object graph must be consistent; note that a refzero may
34 * lead to a mark-and-sweep through a refcount finalizer.
38 * XXX: array indices are mostly typed as duk_uint32_t here; duk_uarridx_t
39 * might be more appropriate.
43 * XXX: duk_uint_fast32_t should probably be used in many places here.
46 #include "duk_internal.h"
52 #define DUK__NO_ARRAY_INDEX DUK_HSTRING_NO_ARRAY_INDEX
54 /* hash probe sequence */
55 #define DUK__HASH_INITIAL(hash,h_size) DUK_HOBJECT_HASH_INITIAL((hash),(h_size))
56 #define DUK__HASH_PROBE_STEP(hash) DUK_HOBJECT_HASH_PROBE_STEP((hash))
58 /* marker values for hash part */
59 #define DUK__HASH_UNUSED DUK_HOBJECT_HASHIDX_UNUSED
60 #define DUK__HASH_DELETED DUK_HOBJECT_HASHIDX_DELETED
62 /* valstack space that suffices for all local calls, including recursion
63 * of other than Duktape calls (getters etc)
65 #define DUK__VALSTACK_SPACE 10
67 /* valstack space allocated especially for proxy lookup which does a
68 * recursive property lookup
70 #define DUK__VALSTACK_PROXY_LOOKUP 20
76 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
);
77 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
);
78 DUK_LOCAL_DECL
void duk__check_arguments_map_for_delete(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_propdesc
*temp_desc
);
80 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
);
81 DUK_LOCAL_DECL duk_bool_t
duk__handle_put_array_length(duk_hthread
*thr
, duk_hobject
*obj
);
83 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
);
84 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
);
85 DUK_LOCAL duk_uint32_t
duk__get_old_array_length(duk_hthread
*thr
, duk_hobject
*obj
, duk_propdesc
*temp_desc
);
91 /* Convert a duk_tval number (caller checks) to a 32-bit index. Returns
92 * DUK__NO_ARRAY_INDEX if the number is not whole or not a valid array
95 /* XXX: for fastints, could use a variant which assumes a double duk_tval
96 * (and doesn't need to check for fastint again).
98 DUK_LOCAL duk_uint32_t
duk__tval_number_to_arr_idx(duk_tval
*tv
) {
102 DUK_ASSERT(tv
!= NULL
);
103 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
105 /* -0 is accepted here as index 0 because ToString(-0) == "0" which is
106 * in canonical form and thus an array index.
108 dbl
= DUK_TVAL_GET_NUMBER(tv
);
109 idx
= (duk_uint32_t
) dbl
;
110 if ((duk_double_t
) idx
== dbl
) {
111 /* Is whole and within 32 bit range. If the value happens to be 0xFFFFFFFF,
112 * it's not a valid array index but will then match DUK__NO_ARRAY_INDEX.
116 return DUK__NO_ARRAY_INDEX
;
119 #if defined(DUK_USE_FASTINT)
120 /* Convert a duk_tval fastint (caller checks) to a 32-bit index. */
121 DUK_LOCAL duk_uint32_t
duk__tval_fastint_to_arr_idx(duk_tval
*tv
) {
124 DUK_ASSERT(tv
!= NULL
);
125 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv
));
127 t
= DUK_TVAL_GET_FASTINT(tv
);
128 if ((t
& ~0xffffffffULL
) != 0) {
129 /* Catches >0x100000000 and negative values. */
130 return DUK__NO_ARRAY_INDEX
;
133 /* If the value happens to be 0xFFFFFFFF, it's not a valid array index
134 * but will then match DUK__NO_ARRAY_INDEX.
136 return (duk_uint32_t
) t
;
138 #endif /* DUK_USE_FASTINT */
140 /* Push an arbitrary duk_tval to the stack, coerce it to string, and return
141 * both a duk_hstring pointer and an array index (or DUK__NO_ARRAY_INDEX).
143 DUK_LOCAL duk_uint32_t
duk__push_tval_to_hstring_arr_idx(duk_context
*ctx
, duk_tval
*tv
, duk_hstring
**out_h
) {
144 duk_uint32_t arr_idx
;
147 DUK_ASSERT(ctx
!= NULL
);
148 DUK_ASSERT(tv
!= NULL
);
149 DUK_ASSERT(out_h
!= NULL
);
151 duk_push_tval(ctx
, tv
);
152 duk_to_string(ctx
, -1);
153 h
= duk_get_hstring(ctx
, -1);
154 DUK_ASSERT(h
!= NULL
);
157 arr_idx
= DUK_HSTRING_GET_ARRIDX_FAST(h
);
161 /* String is an own (virtual) property of a lightfunc. */
162 DUK_LOCAL duk_bool_t
duk__key_is_lightfunc_ownprop(duk_hthread
*thr
, duk_hstring
*key
) {
164 return (key
== DUK_HTHREAD_STRING_LENGTH(thr
) ||
165 key
== DUK_HTHREAD_STRING_NAME(thr
));
169 * Helpers for managing property storage size
172 /* Get default hash part size for a certain entry part size. */
173 #if defined(DUK_USE_HOBJECT_HASH_PART)
174 DUK_LOCAL duk_uint32_t
duk__get_default_h_size(duk_uint32_t e_size
) {
175 DUK_ASSERT(e_size
<= DUK_HOBJECT_MAX_PROPERTIES
);
177 if (e_size
>= DUK_HOBJECT_E_USE_HASH_LIMIT
) {
180 /* result: hash_prime(floor(1.2 * e_size)) */
181 res
= duk_util_get_hash_prime(e_size
+ e_size
/ DUK_HOBJECT_H_SIZE_DIVISOR
);
183 /* if fails, e_size will be zero = not an issue, except performance-wise */
184 DUK_ASSERT(res
== 0 || res
> e_size
);
190 #endif /* USE_PROP_HASH_PART */
192 /* Get minimum entry part growth for a certain size. */
193 DUK_LOCAL duk_uint32_t
duk__get_min_grow_e(duk_uint32_t e_size
) {
196 DUK_ASSERT(e_size
<= DUK_HOBJECT_MAX_PROPERTIES
);
198 res
= (e_size
+ DUK_HOBJECT_E_MIN_GROW_ADD
) / DUK_HOBJECT_E_MIN_GROW_DIVISOR
;
199 DUK_ASSERT(res
>= 1); /* important for callers */
203 /* Get minimum array part growth for a certain size. */
204 DUK_LOCAL duk_uint32_t
duk__get_min_grow_a(duk_uint32_t a_size
) {
207 DUK_ASSERT((duk_size_t
) a_size
<= DUK_HOBJECT_MAX_PROPERTIES
);
209 res
= (a_size
+ DUK_HOBJECT_A_MIN_GROW_ADD
) / DUK_HOBJECT_A_MIN_GROW_DIVISOR
;
210 DUK_ASSERT(res
>= 1); /* important for callers */
214 /* Count actually used entry part entries (non-NULL keys). */
215 DUK_LOCAL duk_uint32_t
duk__count_used_e_keys(duk_hthread
*thr
, duk_hobject
*obj
) {
217 duk_uint_fast32_t n
= 0;
220 DUK_ASSERT(obj
!= NULL
);
223 e
= DUK_HOBJECT_E_GET_KEY_BASE(thr
->heap
, obj
);
224 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
229 return (duk_uint32_t
) n
;
232 /* Count actually used array part entries and array minimum size.
233 * NOTE: 'out_min_size' can be computed much faster by starting from the
234 * end and breaking out early when finding first used entry, but this is
237 DUK_LOCAL
void duk__compute_a_stats(duk_hthread
*thr
, duk_hobject
*obj
, duk_uint32_t
*out_used
, duk_uint32_t
*out_min_size
) {
239 duk_uint_fast32_t used
= 0;
240 duk_uint_fast32_t highest_idx
= (duk_uint_fast32_t
) -1; /* see below */
243 DUK_ASSERT(obj
!= NULL
);
244 DUK_ASSERT(out_used
!= NULL
);
245 DUK_ASSERT(out_min_size
!= NULL
);
248 a
= DUK_HOBJECT_A_GET_BASE(thr
->heap
, obj
);
249 for (i
= 0; i
< DUK_HOBJECT_GET_ASIZE(obj
); i
++) {
251 if (!DUK_TVAL_IS_UNUSED(tv
)) {
257 /* Initial value for highest_idx is -1 coerced to unsigned. This
258 * is a bit odd, but (highest_idx + 1) will then wrap to 0 below
259 * for out_min_size as intended.
263 *out_min_size
= highest_idx
+ 1; /* 0 if no used entries */
266 /* Check array density and indicate whether or not the array part should be abandoned. */
267 DUK_LOCAL duk_bool_t
duk__abandon_array_density_check(duk_uint32_t a_used
, duk_uint32_t a_size
) {
269 * Array abandon check; abandon if:
271 * new_used / new_size < limit
272 * new_used < limit * new_size || limit is 3 bits fixed point
273 * new_used < limit' / 8 * new_size || *8
274 * 8*new_used < limit' * new_size || :8
275 * new_used < limit' * (new_size / 8)
277 * Here, new_used = a_used, new_size = a_size.
279 * Note: some callers use approximate values for a_used and/or a_size
280 * (e.g. dropping a '+1' term). This doesn't affect the usefulness
281 * of the check, but may confuse debugging.
284 return (a_used
< DUK_HOBJECT_A_ABANDON_LIMIT
* (a_size
>> 3));
287 /* Fast check for extending array: check whether or not a slow density check is required. */
288 DUK_LOCAL duk_bool_t
duk__abandon_array_slow_check_required(duk_uint32_t arr_idx
, duk_uint32_t old_size
) {
290 * In a fast check we assume old_size equals old_used (i.e., existing
291 * array is fully dense).
295 * (new_size - old_size) / old_size > limit
296 * new_size - old_size > limit * old_size
297 * new_size > (1 + limit) * old_size || limit' is 3 bits fixed point
298 * new_size > (1 + (limit' / 8)) * old_size || * 8
299 * 8 * new_size > (8 + limit') * old_size || : 8
300 * new_size > (8 + limit') * (old_size / 8)
301 * new_size > limit'' * (old_size / 8) || limit'' = 9 -> max 25% increase
302 * arr_idx + 1 > limit'' * (old_size / 8)
304 * This check doesn't work well for small values, so old_size is rounded
305 * up for the check (and the '+ 1' of arr_idx can be ignored in practice):
307 * arr_idx > limit'' * ((old_size + 7) / 8)
310 return (arr_idx
> DUK_HOBJECT_A_FAST_RESIZE_LIMIT
* ((old_size
+ 7) >> 3));
317 #if defined(DUK_USE_ES6_PROXY)
318 DUK_INTERNAL duk_bool_t
duk_hobject_proxy_check(duk_hthread
*thr
, duk_hobject
*obj
, duk_hobject
**out_target
, duk_hobject
**out_handler
) {
320 duk_tval
*tv_handler
;
321 duk_hobject
*h_target
;
322 duk_hobject
*h_handler
;
324 DUK_ASSERT(thr
!= NULL
);
325 DUK_ASSERT(obj
!= NULL
);
326 DUK_ASSERT(out_target
!= NULL
);
327 DUK_ASSERT(out_handler
!= NULL
);
329 /* Caller doesn't need to check exotic proxy behavior (but does so for
332 if (DUK_LIKELY(!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj
))) {
336 tv_handler
= duk_hobject_find_existing_entry_tval_ptr(thr
->heap
, obj
, DUK_HTHREAD_STRING_INT_HANDLER(thr
));
338 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REVOKED
);
341 DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_handler
));
342 h_handler
= DUK_TVAL_GET_OBJECT(tv_handler
);
343 DUK_ASSERT(h_handler
!= NULL
);
344 *out_handler
= h_handler
;
345 tv_handler
= NULL
; /* avoid issues with relocation */
347 tv_target
= duk_hobject_find_existing_entry_tval_ptr(thr
->heap
, obj
, DUK_HTHREAD_STRING_INT_TARGET(thr
));
349 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REVOKED
);
352 DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_target
));
353 h_target
= DUK_TVAL_GET_OBJECT(tv_target
);
354 DUK_ASSERT(h_target
!= NULL
);
355 *out_target
= h_target
;
356 tv_target
= NULL
; /* avoid issues with relocation */
360 #endif /* DUK_USE_ES6_PROXY */
362 /* Get Proxy target object. If the argument is not a Proxy, return it as is.
363 * If a Proxy is revoked, an error is thrown.
365 #if defined(DUK_USE_ES6_PROXY)
366 DUK_INTERNAL duk_hobject
*duk_hobject_resolve_proxy_target(duk_hthread
*thr
, duk_hobject
*obj
) {
367 duk_hobject
*h_target
;
368 duk_hobject
*h_handler
;
370 DUK_ASSERT(thr
!= NULL
);
371 DUK_ASSERT(obj
!= NULL
);
373 /* Resolve Proxy targets until Proxy chain ends. No explicit check for
374 * a Proxy loop: user code cannot create such a loop without tweaking
375 * internal properties directly.
378 while (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj
))) {
379 if (duk_hobject_proxy_check(thr
, obj
, &h_target
, &h_handler
)) {
380 DUK_ASSERT(h_target
!= NULL
);
387 DUK_ASSERT(obj
!= NULL
);
390 #endif /* DUK_USE_ES6_PROXY */
392 #if defined(DUK_USE_ES6_PROXY)
393 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
) {
394 duk_context
*ctx
= (duk_context
*) thr
;
395 duk_hobject
*h_handler
;
397 DUK_ASSERT(thr
!= NULL
);
398 DUK_ASSERT(obj
!= NULL
);
399 DUK_ASSERT(tv_key
!= NULL
);
400 DUK_ASSERT(out_target
!= NULL
);
402 if (!duk_hobject_proxy_check(thr
, obj
, out_target
, &h_handler
)) {
405 DUK_ASSERT(*out_target
!= NULL
);
406 DUK_ASSERT(h_handler
!= NULL
);
408 /* XXX: At the moment Duktape accesses internal keys like _Finalizer using a
409 * normal property set/get which would allow a proxy handler to interfere with
410 * such behavior and to get access to internal key strings. This is not a problem
411 * as such because internal key strings can be created in other ways too (e.g.
412 * through buffers). The best fix is to change Duktape internal lookups to
413 * skip proxy behavior. Until that, internal property accesses bypass the
414 * proxy and are applied to the target (as if the handler did not exist).
415 * This has some side effects, see test-bi-proxy-internal-keys.js.
418 if (DUK_TVAL_IS_STRING(tv_key
)) {
419 duk_hstring
*h_key
= (duk_hstring
*) DUK_TVAL_GET_STRING(tv_key
);
420 DUK_ASSERT(h_key
!= NULL
);
421 if (DUK_HSTRING_HAS_INTERNAL(h_key
)) {
422 DUK_DDD(DUK_DDDPRINT("internal key, skip proxy handler and apply to target"));
427 /* The handler is looked up with a normal property lookup; it may be an
428 * accessor or the handler object itself may be a proxy object. If the
429 * handler is a proxy, we need to extend the valstack as we make a
430 * recursive proxy check without a function call in between (in fact
431 * there is no limit to the potential recursion here).
433 * (For sanity, proxy creation rejects another proxy object as either
434 * the handler or the target at the moment so recursive proxy cases
435 * are not realized now.)
438 /* XXX: C recursion limit if proxies are allowed as handler/target values */
440 duk_require_stack(ctx
, DUK__VALSTACK_PROXY_LOOKUP
);
441 duk_push_hobject(ctx
, h_handler
);
442 if (duk_get_prop_stridx(ctx
, -1, stridx_trap
)) {
443 /* -> [ ... handler trap ] */
444 duk_insert(ctx
, -2); /* -> [ ... trap handler ] */
446 /* stack prepped for func call: [ ... trap handler ] */
453 #endif /* DUK_USE_ES6_PROXY */
456 * Reallocate property allocation, moving properties to the new allocation.
458 * Includes key compaction, rehashing, and can also optionally abandoning
459 * the array part, 'migrating' array entries into the beginning of the
460 * new entry part. Arguments are not validated here, so e.g. new_h_size
461 * MUST be a valid prime.
463 * There is no support for in-place reallocation or just compacting keys
464 * without resizing the property allocation. This is intentional to keep
467 * The implementation is relatively straightforward, except for the array
468 * abandonment process. Array abandonment requires that new string keys
469 * are interned, which may trigger GC. All keys interned so far must be
470 * reachable for GC at all times; valstack is used for that now.
472 * Also, a GC triggered during this reallocation process must not interfere
473 * with the object being resized. This is currently controlled by using
474 * heap->mark_and_sweep_base_flags to indicate that no finalizers will be
475 * executed (as they can affect ANY object) and no objects are compacted
476 * (it would suffice to protect this particular object only, though).
478 * Note: a non-checked variant would be nice but is a bit tricky to
479 * implement for the array abandonment process. It's easy for
482 * Note: because we need to potentially resize the valstack (as part
483 * of abandoning the array part), any tval pointers to the valstack
484 * will become invalid after this call.
488 void duk__realloc_props(duk_hthread
*thr
,
490 duk_uint32_t new_e_size
,
491 duk_uint32_t new_a_size
,
492 duk_uint32_t new_h_size
,
493 duk_bool_t abandon_array
) {
494 duk_context
*ctx
= (duk_context
*) thr
;
495 #ifdef DUK_USE_MARK_AND_SWEEP
496 duk_small_uint_t prev_mark_and_sweep_base_flags
;
498 duk_uint32_t new_alloc_size
;
499 duk_uint32_t new_e_size_adjusted
;
501 duk_hstring
**new_e_k
;
502 duk_propvalue
*new_e_pv
;
503 duk_uint8_t
*new_e_f
;
506 duk_uint32_t new_e_next
;
509 DUK_ASSERT(thr
!= NULL
);
510 DUK_ASSERT(ctx
!= NULL
);
511 DUK_ASSERT(obj
!= NULL
);
512 DUK_ASSERT(!abandon_array
|| new_a_size
== 0); /* if abandon_array, new_a_size must be 0 */
513 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
|| (DUK_HOBJECT_GET_ESIZE(obj
) == 0 && DUK_HOBJECT_GET_ASIZE(obj
) == 0));
514 DUK_ASSERT(new_h_size
== 0 || new_h_size
>= new_e_size
); /* required to guarantee success of rehashing,
515 * intentionally use unadjusted new_e_size
517 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
));
518 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
521 * Pre resize assertions.
524 #ifdef DUK_USE_ASSERTIONS
525 /* XXX: pre-checks (such as no duplicate keys) */
529 * For property layout 1, tweak e_size to ensure that the whole entry
530 * part (key + val + flags) is a suitable multiple for alignment
531 * (platform specific).
533 * Property layout 2 does not require this tweaking and is preferred
534 * on low RAM platforms requiring alignment.
537 #if defined(DUK_USE_HOBJECT_LAYOUT_2) || defined(DUK_USE_HOBJECT_LAYOUT_3)
538 DUK_DDD(DUK_DDDPRINT("using layout 2 or 3, no need to pad e_size: %ld", (long) new_e_size
));
539 new_e_size_adjusted
= new_e_size
;
540 #elif defined(DUK_USE_HOBJECT_LAYOUT_1) && (DUK_HOBJECT_ALIGN_TARGET == 1)
541 DUK_DDD(DUK_DDDPRINT("using layout 1, but no need to pad e_size: %ld", (long) new_e_size
));
542 new_e_size_adjusted
= new_e_size
;
543 #elif defined(DUK_USE_HOBJECT_LAYOUT_1) && ((DUK_HOBJECT_ALIGN_TARGET == 4) || (DUK_HOBJECT_ALIGN_TARGET == 8))
544 new_e_size_adjusted
= (new_e_size
+ DUK_HOBJECT_ALIGN_TARGET
- 1) & (~(DUK_HOBJECT_ALIGN_TARGET
- 1));
545 DUK_DDD(DUK_DDDPRINT("using layout 1, and alignment target is %ld, adjusted e_size: %ld -> %ld",
546 (long) DUK_HOBJECT_ALIGN_TARGET
, (long) new_e_size
, (long) new_e_size_adjusted
));
547 DUK_ASSERT(new_e_size_adjusted
>= new_e_size
);
549 #error invalid hobject layout defines
553 * Debug logging after adjustment.
556 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 "
557 "{e_size=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld",
559 (long) DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj
),
560 DUK_HOBJECT_GET_ASIZE(obj
),
561 DUK_HOBJECT_GET_HSIZE(obj
)),
562 (long) DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted
, new_a_size
, new_h_size
),
563 (void *) DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
),
564 (long) DUK_HOBJECT_GET_ESIZE(obj
),
565 (long) DUK_HOBJECT_GET_ENEXT(obj
),
566 (long) DUK_HOBJECT_GET_ASIZE(obj
),
567 (long) DUK_HOBJECT_GET_HSIZE(obj
),
568 (long) new_e_size_adjusted
,
571 (long) abandon_array
,
575 * Property count check. This is the only point where we ensure that
576 * we don't get more (allocated) property space that we can handle.
577 * There aren't hard limits as such, but some algorithms fail (e.g.
578 * finding next higher prime, selecting hash part size) if we get too
579 * close to the 4G property limit.
581 * Since this works based on allocation size (not actually used size),
582 * the limit is a bit approximate but good enough in practice.
585 if (new_e_size_adjusted
+ new_a_size
> DUK_HOBJECT_MAX_PROPERTIES
) {
586 DUK_ERROR_ALLOC_DEFMSG(thr
);
590 * Compute new alloc size and alloc new area.
592 * The new area is allocated as a dynamic buffer and placed into the
593 * valstack for reachability. The actual buffer is then detached at
596 * Note: heap_mark_and_sweep_base_flags are altered here to ensure
597 * no-one touches this object while we're resizing and rehashing it.
598 * The flags must be reset on every exit path after it. Finalizers
599 * and compaction is prevented currently for all objects while it
600 * would be enough to restrict it only for the current object.
603 #ifdef DUK_USE_MARK_AND_SWEEP
604 prev_mark_and_sweep_base_flags
= thr
->heap
->mark_and_sweep_base_flags
;
605 thr
->heap
->mark_and_sweep_base_flags
|=
606 DUK_MS_FLAG_NO_FINALIZERS
| /* avoid attempts to add/remove object keys */
607 DUK_MS_FLAG_NO_OBJECT_COMPACTION
; /* avoid attempt to compact the current object */
610 new_alloc_size
= DUK_HOBJECT_P_COMPUTE_SIZE(new_e_size_adjusted
, new_a_size
, new_h_size
);
611 DUK_DDD(DUK_DDDPRINT("new hobject allocation size is %ld", (long) new_alloc_size
));
612 if (new_alloc_size
== 0) {
613 /* for zero size, don't push anything on valstack */
614 DUK_ASSERT(new_e_size_adjusted
== 0);
615 DUK_ASSERT(new_a_size
== 0);
616 DUK_ASSERT(new_h_size
== 0);
619 /* This may trigger mark-and-sweep with arbitrary side effects,
620 * including an attempted resize of the object we're resizing,
621 * executing a finalizer which may add or remove properties of
622 * the object we're resizing etc.
625 /* Note: buffer is dynamic so that we can 'steal' the actual
629 new_p
= (duk_uint8_t
*) duk_push_dynamic_buffer(ctx
, new_alloc_size
); /* errors out if out of memory */
630 DUK_ASSERT(new_p
!= NULL
); /* since new_alloc_size > 0 */
633 /* Set up pointers to the new property area: this is hidden behind a macro
634 * because it is memory layout specific.
636 DUK_HOBJECT_P_SET_REALLOC_PTRS(new_p
, new_e_k
, new_e_pv
, new_e_f
, new_a
, new_h
,
637 new_e_size_adjusted
, new_a_size
, new_h_size
);
638 DUK_UNREF(new_h
); /* happens when hash part dropped */
641 /* if new_p == NULL, all of these pointers are NULL */
642 DUK_ASSERT((new_p
!= NULL
) ||
643 (new_e_k
== NULL
&& new_e_pv
== NULL
&& new_e_f
== NULL
&&
644 new_a
== NULL
&& new_h
== NULL
));
646 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",
647 (long) new_alloc_size
, (void *) new_e_k
, (void *) new_e_pv
, (void *) new_e_f
,
648 (void *) new_a
, (void *) new_h
));
651 * Migrate array to start of entries if requested.
653 * Note: from an enumeration perspective the order of entry keys matters.
654 * Array keys should appear wherever they appeared before the array abandon
660 * Note: assuming new_a_size == 0, and that entry part contains
661 * no conflicting keys, refcounts do not need to be adjusted for
662 * the values, as they remain exactly the same.
664 * The keys, however, need to be interned, incref'd, and be
665 * reachable for GC. Any intern attempt may trigger a GC and
666 * claim any non-reachable strings, so every key must be reachable
669 * A longjmp must not occur here, as the new_p allocation would
670 * be freed without these keys being decref'd, hence the messy
671 * decref handling if intern fails.
673 DUK_ASSERT(new_a_size
== 0);
675 for (i
= 0; i
< DUK_HOBJECT_GET_ASIZE(obj
); i
++) {
680 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
);
682 tv1
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, i
);
683 if (DUK_TVAL_IS_UNUSED(tv1
)) {
687 DUK_ASSERT(new_p
!= NULL
&& new_e_k
!= NULL
&&
688 new_e_pv
!= NULL
&& new_e_f
!= NULL
);
691 * Intern key via the valstack to ensure reachability behaves
692 * properly. We must avoid longjmp's here so use non-checked
695 * Note: duk_check_stack() potentially reallocs the valstack,
696 * invalidating any duk_tval pointers to valstack. Callers
700 /* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */
701 if (!duk_check_stack(ctx
, 1)) {
704 DUK_ASSERT_VALSTACK_SPACE(thr
, 1);
705 key
= duk_heap_string_intern_u32(thr
->heap
, i
);
709 duk_push_hstring(ctx
, key
); /* keep key reachable for GC etc; guaranteed not to fail */
711 /* key is now reachable in the valstack */
713 DUK_HSTRING_INCREF(thr
, key
); /* second incref for the entry reference */
714 new_e_k
[new_e_next
] = key
;
715 tv2
= &new_e_pv
[new_e_next
].v
; /* array entries are all plain values */
716 DUK_TVAL_SET_TVAL(tv2
, tv1
);
717 new_e_f
[new_e_next
] = DUK_PROPDESC_FLAG_WRITABLE
|
718 DUK_PROPDESC_FLAG_ENUMERABLE
|
719 DUK_PROPDESC_FLAG_CONFIGURABLE
;
722 /* Note: new_e_next matches pushed temp key count, and nothing can
723 * fail above between the push and this point.
727 DUK_DDD(DUK_DDDPRINT("abandon array: pop %ld key temps from valstack", (long) new_e_next
));
728 duk_pop_n(ctx
, new_e_next
);
732 * Copy keys and values in the entry part (compacting them at the same time).
735 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
738 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
);
740 key
= DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
);
745 DUK_ASSERT(new_p
!= NULL
&& new_e_k
!= NULL
&&
746 new_e_pv
!= NULL
&& new_e_f
!= NULL
);
748 new_e_k
[new_e_next
] = key
;
749 new_e_pv
[new_e_next
] = DUK_HOBJECT_E_GET_VALUE(thr
->heap
, obj
, i
);
750 new_e_f
[new_e_next
] = DUK_HOBJECT_E_GET_FLAGS(thr
->heap
, obj
, i
);
753 /* the entries [new_e_next, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */
756 * Copy array elements to new array part.
759 if (new_a_size
> DUK_HOBJECT_GET_ASIZE(obj
)) {
760 /* copy existing entries as is */
761 DUK_ASSERT(new_p
!= NULL
&& new_a
!= NULL
);
762 if (DUK_HOBJECT_GET_ASIZE(obj
) > 0) {
763 /* Avoid zero copy with an invalid pointer. If obj->p is NULL,
764 * the 'new_a' pointer will be invalid which is not allowed even
765 * when copy size is zero.
767 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
);
768 DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj
) > 0);
769 DUK_MEMCPY((void *) new_a
, (void *) DUK_HOBJECT_A_GET_BASE(thr
->heap
, obj
), sizeof(duk_tval
) * DUK_HOBJECT_GET_ASIZE(obj
));
772 /* fill new entries with -unused- (required, gc reachable) */
773 for (i
= DUK_HOBJECT_GET_ASIZE(obj
); i
< new_a_size
; i
++) {
774 duk_tval
*tv
= &new_a
[i
];
775 DUK_TVAL_SET_UNUSED(tv
);
778 #ifdef DUK_USE_ASSERTIONS
779 /* caller must have decref'd values above new_a_size (if that is necessary) */
780 if (!abandon_array
) {
781 for (i
= new_a_size
; i
< DUK_HOBJECT_GET_ASIZE(obj
); i
++) {
783 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, i
);
785 /* current assertion is quite strong: decref's and set to unused */
786 DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv
));
790 if (new_a_size
> 0) {
791 /* Avoid zero copy with an invalid pointer. If obj->p is NULL,
792 * the 'new_a' pointer will be invalid which is not allowed even
793 * when copy size is zero.
795 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
);
796 DUK_ASSERT(new_a_size
> 0);
797 DUK_MEMCPY((void *) new_a
, (void *) DUK_HOBJECT_A_GET_BASE(thr
->heap
, obj
), sizeof(duk_tval
) * new_a_size
);
802 * Rebuild the hash part always from scratch (guaranteed to finish).
804 * Any resize of hash part requires rehashing. In addition, by rehashing
805 * get rid of any elements marked deleted (DUK__HASH_DELETED) which is critical
806 * to ensuring the hash part never fills up.
809 #if defined(DUK_USE_HOBJECT_HASH_PART)
810 if (DUK_UNLIKELY(new_h_size
> 0)) {
811 DUK_ASSERT(new_h
!= NULL
);
813 /* fill new_h with u32 0xff = UNUSED */
814 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
) != NULL
);
815 DUK_ASSERT(new_h_size
> 0);
816 DUK_MEMSET(new_h
, 0xff, sizeof(duk_uint32_t
) * new_h_size
);
818 DUK_ASSERT(new_e_next
<= new_h_size
); /* equality not actually possible */
819 for (i
= 0; i
< new_e_next
; i
++) {
820 duk_hstring
*key
= new_e_k
[i
];
821 duk_uint32_t j
, step
;
823 DUK_ASSERT(key
!= NULL
);
824 j
= DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), new_h_size
);
825 step
= DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key
));
828 DUK_ASSERT(new_h
[j
] != DUK__HASH_DELETED
); /* should never happen */
829 if (new_h
[j
] == DUK__HASH_UNUSED
) {
830 DUK_DDD(DUK_DDDPRINT("rebuild hit %ld -> %ld", (long) j
, (long) i
));
834 DUK_DDD(DUK_DDDPRINT("rebuild miss %ld, step %ld", (long) j
, (long) step
));
835 j
= (j
+ step
) % new_h_size
;
837 /* guaranteed to finish */
838 DUK_ASSERT(j
!= (duk_uint32_t
) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), new_h_size
));
842 DUK_DDD(DUK_DDDPRINT("no hash part, no rehash"));
844 #endif /* DUK_USE_HOBJECT_HASH_PART */
850 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 "
851 "{p=%p,e_size=%ld,e_next=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld",
853 (long) DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj
),
854 DUK_HOBJECT_GET_ASIZE(obj
),
855 DUK_HOBJECT_GET_HSIZE(obj
)),
856 (long) new_alloc_size
,
857 (void *) DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
),
858 (long) DUK_HOBJECT_GET_ESIZE(obj
),
859 (long) DUK_HOBJECT_GET_ENEXT(obj
),
860 (long) DUK_HOBJECT_GET_ASIZE(obj
),
861 (long) DUK_HOBJECT_GET_HSIZE(obj
),
863 (long) new_e_size_adjusted
,
867 (long) abandon_array
,
871 * All done, switch properties ('p') allocation to new one.
874 DUK_FREE(thr
->heap
, DUK_HOBJECT_GET_PROPS(thr
->heap
, obj
)); /* NULL obj->p is OK */
875 DUK_HOBJECT_SET_PROPS(thr
->heap
, obj
, new_p
);
876 DUK_HOBJECT_SET_ESIZE(obj
, new_e_size_adjusted
);
877 DUK_HOBJECT_SET_ENEXT(obj
, new_e_next
);
878 DUK_HOBJECT_SET_ASIZE(obj
, new_a_size
);
879 DUK_HOBJECT_SET_HSIZE(obj
, new_h_size
);
883 * Detach actual buffer from dynamic buffer in valstack, and
884 * pop it from the stack.
886 * XXX: the buffer object is certainly not reachable at this point,
887 * so it would be nice to free it forcibly even with only
888 * mark-and-sweep enabled. Not a big issue though.
890 (void) duk_steal_buffer(ctx
, -1, NULL
);
893 DUK_ASSERT(new_alloc_size
== 0);
894 /* no need to pop, nothing was pushed */
897 /* clear array part flag only after switching */
899 DUK_HOBJECT_CLEAR_ARRAY_PART(obj
);
902 DUK_DDD(DUK_DDDPRINT("resize result: %!O", (duk_heaphdr
*) obj
));
904 #ifdef DUK_USE_MARK_AND_SWEEP
905 thr
->heap
->mark_and_sweep_base_flags
= prev_mark_and_sweep_base_flags
;
909 * Post resize assertions.
912 #ifdef DUK_USE_ASSERTIONS
913 /* XXX: post-checks (such as no duplicate keys) */
918 * Abandon array failed, need to decref keys already inserted
919 * into the beginning of new_e_k before unwinding valstack.
923 DUK_D(DUK_DPRINT("hobject resize failed during abandon array, decref keys"));
927 DUK_ASSERT(new_e_k
!= NULL
);
928 DUK_ASSERT(new_e_k
[i
] != NULL
);
929 DUK_HSTRING_DECREF(thr
, new_e_k
[i
]); /* side effects */
932 #ifdef DUK_USE_MARK_AND_SWEEP
933 thr
->heap
->mark_and_sweep_base_flags
= prev_mark_and_sweep_base_flags
;
936 DUK_ERROR_ALLOC_DEFMSG(thr
);
940 * Helpers to resize properties allocation on specific needs.
943 /* Grow entry part allocation for one additional entry. */
944 DUK_LOCAL
void duk__grow_props_for_new_entry_item(duk_hthread
*thr
, duk_hobject
*obj
) {
945 duk_uint32_t old_e_used
; /* actually used, non-NULL entries */
946 duk_uint32_t new_e_size
;
947 duk_uint32_t new_a_size
;
948 duk_uint32_t new_h_size
;
950 DUK_ASSERT(thr
!= NULL
);
951 DUK_ASSERT(obj
!= NULL
);
953 /* Duktape 0.11.0 and prior tried to optimize the resize by not
954 * counting the number of actually used keys prior to the resize.
955 * This worked mostly well but also caused weird leak-like behavior
956 * as in: test-bug-object-prop-alloc-unbounded.js. So, now we count
957 * the keys explicitly to compute the new entry part size.
960 old_e_used
= duk__count_used_e_keys(thr
, obj
);
961 new_e_size
= old_e_used
+ duk__get_min_grow_e(old_e_used
);
962 #if defined(DUK_USE_HOBJECT_HASH_PART)
963 new_h_size
= duk__get_default_h_size(new_e_size
);
967 new_a_size
= DUK_HOBJECT_GET_ASIZE(obj
);
968 DUK_ASSERT(new_e_size
>= old_e_used
+ 1); /* duk__get_min_grow_e() is always >= 1 */
970 duk__realloc_props(thr
, obj
, new_e_size
, new_a_size
, new_h_size
, 0);
973 /* Grow array part for a new highest array index. */
974 DUK_LOCAL
void duk__grow_props_for_array_item(duk_hthread
*thr
, duk_hobject
*obj
, duk_uint32_t highest_arr_idx
) {
975 duk_uint32_t new_e_size
;
976 duk_uint32_t new_a_size
;
977 duk_uint32_t new_h_size
;
979 DUK_ASSERT(thr
!= NULL
);
980 DUK_ASSERT(obj
!= NULL
);
981 DUK_ASSERT(highest_arr_idx
>= DUK_HOBJECT_GET_ASIZE(obj
));
983 /* minimum new length is highest_arr_idx + 1 */
985 new_e_size
= DUK_HOBJECT_GET_ESIZE(obj
);
986 new_h_size
= DUK_HOBJECT_GET_HSIZE(obj
);
987 new_a_size
= highest_arr_idx
+ duk__get_min_grow_a(highest_arr_idx
);
988 DUK_ASSERT(new_a_size
>= highest_arr_idx
+ 1); /* duk__get_min_grow_a() is always >= 1 */
990 duk__realloc_props(thr
, obj
, new_e_size
, new_a_size
, new_h_size
, 0);
993 /* Abandon array part, moving array entries into entries part.
994 * This requires a props resize, which is a heavy operation.
995 * We also compact the entries part while we're at it, although
996 * this is not strictly required.
998 DUK_LOCAL
void duk__abandon_array_checked(duk_hthread
*thr
, duk_hobject
*obj
) {
999 duk_uint32_t new_e_size
;
1000 duk_uint32_t new_a_size
;
1001 duk_uint32_t new_h_size
;
1002 duk_uint32_t e_used
; /* actually used, non-NULL keys */
1003 duk_uint32_t a_used
;
1004 duk_uint32_t a_size
;
1006 DUK_ASSERT(thr
!= NULL
);
1007 DUK_ASSERT(obj
!= NULL
);
1009 e_used
= duk__count_used_e_keys(thr
, obj
);
1010 duk__compute_a_stats(thr
, obj
, &a_used
, &a_size
);
1013 * Must guarantee all actually used array entries will fit into
1014 * new entry part. Add one growth step to ensure we don't run out
1015 * of space right away.
1018 new_e_size
= e_used
+ a_used
;
1019 new_e_size
= new_e_size
+ duk__get_min_grow_e(new_e_size
);
1021 #if defined(DUK_USE_HOBJECT_HASH_PART)
1022 new_h_size
= duk__get_default_h_size(new_e_size
);
1027 DUK_DD(DUK_DDPRINT("abandon array part for hobject %p, "
1028 "array stats before: e_used=%ld, a_used=%ld, a_size=%ld; "
1029 "resize to e_size=%ld, a_size=%ld, h_size=%ld",
1030 (void *) obj
, (long) e_used
, (long) a_used
, (long) a_size
,
1031 (long) new_e_size
, (long) new_a_size
, (long) new_h_size
));
1033 duk__realloc_props(thr
, obj
, new_e_size
, new_a_size
, new_h_size
, 1);
1037 * Compact an object. Minimizes allocation size for objects which are
1038 * not likely to be extended. This is useful for internal and non-
1039 * extensible objects, but can also be called for non-extensible objects.
1040 * May abandon the array part if it is computed to be too sparse.
1042 * This call is relatively expensive, as it needs to scan both the
1043 * entries and the array part.
1045 * The call may fail due to allocation error.
1048 DUK_INTERNAL
void duk_hobject_compact_props(duk_hthread
*thr
, duk_hobject
*obj
) {
1049 duk_uint32_t e_size
; /* currently used -> new size */
1050 duk_uint32_t a_size
; /* currently required */
1051 duk_uint32_t a_used
; /* actually used */
1052 duk_uint32_t h_size
;
1053 duk_bool_t abandon_array
;
1055 DUK_ASSERT(thr
!= NULL
);
1056 DUK_ASSERT(obj
!= NULL
);
1058 #if defined(DUK_USE_ROM_OBJECTS)
1059 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)) {
1060 DUK_DD(DUK_DDPRINT("ignore attempt to compact a rom object"));
1065 e_size
= duk__count_used_e_keys(thr
, obj
);
1066 duk__compute_a_stats(thr
, obj
, &a_used
, &a_size
);
1068 DUK_DD(DUK_DDPRINT("compacting hobject, used e keys %ld, used a keys %ld, min a size %ld, "
1069 "resized array density would be: %ld/%ld = %lf",
1070 (long) e_size
, (long) a_used
, (long) a_size
,
1071 (long) a_used
, (long) a_size
,
1072 (double) a_used
/ (double) a_size
));
1074 if (duk__abandon_array_density_check(a_used
, a_size
)) {
1075 DUK_DD(DUK_DDPRINT("decided to abandon array during compaction, a_used=%ld, a_size=%ld",
1076 (long) a_used
, (long) a_size
));
1081 DUK_DD(DUK_DDPRINT("decided to keep array during compaction"));
1085 #if defined(DUK_USE_HOBJECT_HASH_PART)
1086 if (e_size
>= DUK_HOBJECT_E_USE_HASH_LIMIT
) {
1087 h_size
= duk__get_default_h_size(e_size
);
1095 DUK_DD(DUK_DDPRINT("compacting hobject -> new e_size %ld, new a_size=%ld, new h_size=%ld, abandon_array=%ld",
1096 (long) e_size
, (long) a_size
, (long) h_size
, (long) abandon_array
));
1098 duk__realloc_props(thr
, obj
, e_size
, a_size
, h_size
, abandon_array
);
1102 * Find an existing key from entry part either by linear scan or by
1103 * using the hash index (if it exists).
1105 * Sets entry index (and possibly the hash index) to output variables,
1106 * which allows the caller to update the entry and hash entries in-place.
1107 * If entry is not found, both values are set to -1. If entry is found
1108 * but there is no hash part, h_idx is set to -1.
1111 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
) {
1112 DUK_ASSERT(obj
!= NULL
);
1113 DUK_ASSERT(key
!= NULL
);
1114 DUK_ASSERT(e_idx
!= NULL
);
1115 DUK_ASSERT(h_idx
!= NULL
);
1118 if (DUK_LIKELY(DUK_HOBJECT_GET_HSIZE(obj
) == 0))
1120 /* Linear scan: more likely because most objects are small.
1121 * This is an important fast path.
1123 * XXX: this might be worth inlining for property lookups.
1125 duk_uint_fast32_t i
;
1126 duk_uint_fast32_t n
;
1127 duk_hstring
**h_keys_base
;
1128 DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup"));
1130 h_keys_base
= DUK_HOBJECT_E_GET_KEY_BASE(heap
, obj
);
1131 n
= DUK_HOBJECT_GET_ENEXT(obj
);
1132 for (i
= 0; i
< n
; i
++) {
1133 if (h_keys_base
[i
] == key
) {
1140 #if defined(DUK_USE_HOBJECT_HASH_PART)
1145 duk_uint32_t i
, step
;
1146 duk_uint32_t
*h_base
;
1148 DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup"));
1150 h_base
= DUK_HOBJECT_H_GET_BASE(heap
, obj
);
1151 n
= DUK_HOBJECT_GET_HSIZE(obj
);
1152 i
= DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), n
);
1153 step
= DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key
));
1158 DUK_ASSERT_DISABLE(i
>= 0); /* unsigned */
1159 DUK_ASSERT(i
< DUK_HOBJECT_GET_HSIZE(obj
));
1161 DUK_ASSERT(t
== DUK__HASH_UNUSED
|| t
== DUK__HASH_DELETED
||
1162 (t
< DUK_HOBJECT_GET_ESIZE(obj
))); /* t >= 0 always true, unsigned */
1164 if (t
== DUK__HASH_UNUSED
) {
1166 } else if (t
== DUK__HASH_DELETED
) {
1167 DUK_DDD(DUK_DDDPRINT("lookup miss (deleted) i=%ld, t=%ld",
1168 (long) i
, (long) t
));
1170 DUK_ASSERT(t
< DUK_HOBJECT_GET_ESIZE(obj
));
1171 if (DUK_HOBJECT_E_GET_KEY(heap
, obj
, t
) == key
) {
1172 DUK_DDD(DUK_DDDPRINT("lookup hit i=%ld, t=%ld -> key %p",
1173 (long) i
, (long) t
, (void *) key
));
1178 DUK_DDD(DUK_DDDPRINT("lookup miss i=%ld, t=%ld",
1179 (long) i
, (long) t
));
1183 /* guaranteed to finish, as hash is never full */
1184 DUK_ASSERT(i
!= (duk_uint32_t
) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), n
));
1187 #endif /* DUK_USE_HOBJECT_HASH_PART */
1194 /* For internal use: get non-accessor entry value */
1195 DUK_INTERNAL duk_tval
*duk_hobject_find_existing_entry_tval_ptr(duk_heap
*heap
, duk_hobject
*obj
, duk_hstring
*key
) {
1199 DUK_ASSERT(obj
!= NULL
);
1200 DUK_ASSERT(key
!= NULL
);
1203 duk_hobject_find_existing_entry(heap
, obj
, key
, &e_idx
, &h_idx
);
1204 if (e_idx
>= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap
, obj
, e_idx
)) {
1205 return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap
, obj
, e_idx
);
1211 /* For internal use: get non-accessor entry value and attributes */
1212 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
) {
1216 DUK_ASSERT(obj
!= NULL
);
1217 DUK_ASSERT(key
!= NULL
);
1218 DUK_ASSERT(out_attrs
!= NULL
);
1221 duk_hobject_find_existing_entry(heap
, obj
, key
, &e_idx
, &h_idx
);
1222 if (e_idx
>= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap
, obj
, e_idx
)) {
1223 *out_attrs
= DUK_HOBJECT_E_GET_FLAGS(heap
, obj
, e_idx
);
1224 return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap
, obj
, e_idx
);
1231 /* For internal use: get array part value */
1232 DUK_INTERNAL duk_tval
*duk_hobject_find_existing_array_entry_tval_ptr(duk_heap
*heap
, duk_hobject
*obj
, duk_uarridx_t i
) {
1235 DUK_ASSERT(obj
!= NULL
);
1238 if (!DUK_HOBJECT_HAS_ARRAY_PART(obj
)) {
1241 if (i
>= DUK_HOBJECT_GET_ASIZE(obj
)) {
1244 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(heap
, obj
, i
);
1249 * Allocate and initialize a new entry, resizing the properties allocation
1250 * if necessary. Returns entry index (e_idx) or throws an error if alloc fails.
1252 * Sets the key of the entry (increasing the key's refcount), and updates
1253 * the hash part if it exists. Caller must set value and flags, and update
1254 * the entry value refcount. A decref for the previous value is not necessary.
1257 DUK_LOCAL duk_bool_t
duk__alloc_entry_checked(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
) {
1260 DUK_ASSERT(thr
!= NULL
);
1261 DUK_ASSERT(obj
!= NULL
);
1262 DUK_ASSERT(key
!= NULL
);
1263 DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj
) <= DUK_HOBJECT_GET_ESIZE(obj
));
1265 #ifdef DUK_USE_ASSERTIONS
1266 /* key must not already exist in entry part */
1268 duk_uint_fast32_t i
;
1269 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
1270 DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
) != key
);
1275 if (DUK_HOBJECT_GET_ENEXT(obj
) >= DUK_HOBJECT_GET_ESIZE(obj
)) {
1276 /* only need to guarantee 1 more slot, but allocation growth is in chunks */
1277 DUK_DDD(DUK_DDDPRINT("entry part full, allocate space for one more entry"));
1278 duk__grow_props_for_new_entry_item(thr
, obj
);
1280 DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj
) < DUK_HOBJECT_GET_ESIZE(obj
));
1281 idx
= DUK_HOBJECT_POSTINC_ENEXT(obj
);
1283 /* previous value is assumed to be garbage, so don't touch it */
1284 DUK_HOBJECT_E_SET_KEY(thr
->heap
, obj
, idx
, key
);
1285 DUK_HSTRING_INCREF(thr
, key
);
1287 #if defined(DUK_USE_HOBJECT_HASH_PART)
1288 if (DUK_UNLIKELY(DUK_HOBJECT_GET_HSIZE(obj
) > 0)) {
1290 duk_uint32_t i
, step
;
1291 duk_uint32_t
*h_base
= DUK_HOBJECT_H_GET_BASE(thr
->heap
, obj
);
1293 n
= DUK_HOBJECT_GET_HSIZE(obj
);
1294 i
= DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), n
);
1295 step
= DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key
));
1298 duk_uint32_t t
= h_base
[i
];
1299 if (t
== DUK__HASH_UNUSED
|| t
== DUK__HASH_DELETED
) {
1300 DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() inserted key into hash part, %ld -> %ld",
1301 (long) i
, (long) idx
));
1302 DUK_ASSERT_DISABLE(i
>= 0); /* unsigned */
1303 DUK_ASSERT(i
< DUK_HOBJECT_GET_HSIZE(obj
));
1304 DUK_ASSERT_DISABLE(idx
>= 0);
1305 DUK_ASSERT(idx
< DUK_HOBJECT_GET_ESIZE(obj
));
1309 DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() miss %ld", (long) i
));
1312 /* guaranteed to find an empty slot */
1313 DUK_ASSERT(i
!= (duk_uint32_t
) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key
), DUK_HOBJECT_GET_HSIZE(obj
)));
1316 #endif /* DUK_USE_HOBJECT_HASH_PART */
1318 /* Note: we could return the hash index here too, but it's not
1322 DUK_ASSERT_DISABLE(idx
>= 0);
1323 DUK_ASSERT(idx
< DUK_HOBJECT_GET_ESIZE(obj
));
1324 DUK_ASSERT(idx
< DUK_HOBJECT_GET_ENEXT(obj
));
1329 * Object internal value
1331 * Returned value is guaranteed to be reachable / incref'd, caller does not need
1332 * to incref OR decref. No proxies or accessors are invoked, no prototype walk.
1335 DUK_INTERNAL duk_bool_t
duk_hobject_get_internal_value(duk_heap
*heap
, duk_hobject
*obj
, duk_tval
*tv_out
) {
1339 DUK_ASSERT(heap
!= NULL
);
1340 DUK_ASSERT(obj
!= NULL
);
1341 DUK_ASSERT(tv_out
!= NULL
);
1343 /* always in entry part, no need to look up parents etc */
1344 duk_hobject_find_existing_entry(heap
, obj
, DUK_HEAP_STRING_INT_VALUE(heap
), &e_idx
, &h_idx
);
1346 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap
, obj
, e_idx
));
1347 DUK_TVAL_SET_TVAL(tv_out
, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap
, obj
, e_idx
));
1350 DUK_TVAL_SET_UNDEFINED(tv_out
);
1354 DUK_INTERNAL duk_hstring
*duk_hobject_get_internal_value_string(duk_heap
*heap
, duk_hobject
*obj
) {
1357 DUK_ASSERT(heap
!= NULL
);
1358 DUK_ASSERT(obj
!= NULL
);
1360 /* This is not strictly necessary, but avoids compiler warnings; e.g.
1361 * gcc won't reliably detect that no uninitialized data is read below.
1363 DUK_MEMZERO((void *) &tv
, sizeof(duk_tval
));
1365 if (duk_hobject_get_internal_value(heap
, obj
, &tv
)) {
1367 DUK_ASSERT(DUK_TVAL_IS_STRING(&tv
));
1368 h
= DUK_TVAL_GET_STRING(&tv
);
1376 * Arguments handling helpers (argument map mainly).
1378 * An arguments object has exotic behavior for some numeric indices.
1379 * Accesses may translate to identifier operations which may have
1380 * arbitrary side effects (potentially invalidating any duk_tval
1384 /* Lookup 'key' from arguments internal 'map', perform a variable lookup
1385 * if mapped, and leave the result on top of stack (and return non-zero).
1386 * Used in E5 Section 10.6 algorithms [[Get]] and [[GetOwnProperty]].
1389 duk_bool_t
duk__lookup_arguments_map(duk_hthread
*thr
,
1392 duk_propdesc
*temp_desc
,
1393 duk_hobject
**out_map
,
1394 duk_hobject
**out_varenv
) {
1395 duk_context
*ctx
= (duk_context
*) thr
;
1397 duk_hobject
*varenv
;
1400 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1402 DUK_DDD(DUK_DDDPRINT("arguments map lookup: thr=%p, obj=%p, key=%p, temp_desc=%p "
1403 "(obj -> %!O, key -> %!O)",
1404 (void *) thr
, (void *) obj
, (void *) key
, (void *) temp_desc
,
1405 (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
));
1407 if (!duk_hobject_get_own_propdesc(thr
, obj
, DUK_HTHREAD_STRING_INT_MAP(thr
), temp_desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
1408 DUK_DDD(DUK_DDDPRINT("-> no 'map'"));
1412 map
= duk_require_hobject(ctx
, -1);
1413 DUK_ASSERT(map
!= NULL
);
1414 duk_pop(ctx
); /* map is reachable through obj */
1416 if (!duk_hobject_get_own_propdesc(thr
, map
, key
, temp_desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
1417 DUK_DDD(DUK_DDDPRINT("-> 'map' exists, but key not in map"));
1422 DUK_DDD(DUK_DDDPRINT("-> 'map' exists, and contains key, key is mapped to argument/variable binding %!T",
1423 (duk_tval
*) duk_get_tval(ctx
, -1)));
1424 DUK_ASSERT(duk_is_string(ctx
, -1)); /* guaranteed when building arguments */
1426 /* get varenv for varname (callee's declarative lexical environment) */
1427 rc
= duk_hobject_get_own_propdesc(thr
, obj
, DUK_HTHREAD_STRING_INT_VARENV(thr
), temp_desc
, DUK_GETDESC_FLAG_PUSH_VALUE
);
1429 DUK_ASSERT(rc
!= 0); /* arguments MUST have an initialized lexical environment reference */
1430 varenv
= duk_require_hobject(ctx
, -1);
1431 DUK_ASSERT(varenv
!= NULL
);
1432 duk_pop(ctx
); /* varenv remains reachable through 'obj' */
1434 DUK_DDD(DUK_DDDPRINT("arguments varenv is: %!dO", (duk_heaphdr
*) varenv
));
1436 /* success: leave varname in stack */
1438 *out_varenv
= varenv
;
1439 return 1; /* [... varname] */
1442 /* Lookup 'key' from arguments internal 'map', and leave replacement value
1443 * on stack top if mapped (and return non-zero).
1444 * Used in E5 Section 10.6 algorithm for [[GetOwnProperty]] (used by [[Get]]).
1446 DUK_LOCAL duk_bool_t
duk__check_arguments_map_for_get(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_propdesc
*temp_desc
) {
1447 duk_context
*ctx
= (duk_context
*) thr
;
1449 duk_hobject
*varenv
;
1450 duk_hstring
*varname
;
1452 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1454 if (!duk__lookup_arguments_map(thr
, obj
, key
, temp_desc
, &map
, &varenv
)) {
1455 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic get behavior"));
1461 varname
= duk_require_hstring(ctx
, -1);
1462 DUK_ASSERT(varname
!= NULL
);
1463 duk_pop(ctx
); /* varname is still reachable */
1465 DUK_DDD(DUK_DDDPRINT("arguments object automatic getvar for a bound variable; "
1466 "key=%!O, varname=%!O",
1467 (duk_heaphdr
*) key
,
1468 (duk_heaphdr
*) varname
));
1470 (void) duk_js_getvar_envrec(thr
, varenv
, varname
, 1 /*throw*/);
1472 /* [... value this_binding] */
1476 /* leave result on stack top */
1480 /* Lookup 'key' from arguments internal 'map', perform a variable write if mapped.
1481 * Used in E5 Section 10.6 algorithm for [[DefineOwnProperty]] (used by [[Put]]).
1482 * Assumes stack top contains 'put' value (which is NOT popped).
1484 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
) {
1485 duk_context
*ctx
= (duk_context
*) thr
;
1487 duk_hobject
*varenv
;
1488 duk_hstring
*varname
;
1490 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1492 if (!duk__lookup_arguments_map(thr
, obj
, key
, temp_desc
, &map
, &varenv
)) {
1493 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic put behavior"));
1497 /* [... put_value varname] */
1499 varname
= duk_require_hstring(ctx
, -1);
1500 DUK_ASSERT(varname
!= NULL
);
1501 duk_pop(ctx
); /* varname is still reachable */
1503 DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
1504 "key=%!O, varname=%!O, value=%!T",
1505 (duk_heaphdr
*) key
,
1506 (duk_heaphdr
*) varname
,
1507 (duk_tval
*) duk_require_tval(ctx
, -1)));
1509 /* [... put_value] */
1512 * Note: although arguments object variable mappings are only established
1513 * for non-strict functions (and a call to a non-strict function created
1514 * the arguments object in question), an inner strict function may be doing
1515 * the actual property write. Hence the throw_flag applied here comes from
1516 * the property write call.
1519 duk_js_putvar_envrec(thr
, varenv
, varname
, duk_require_tval(ctx
, -1), throw_flag
);
1521 /* [... put_value] */
1524 /* Lookup 'key' from arguments internal 'map', delete mapping if found.
1525 * Used in E5 Section 10.6 algorithm for [[Delete]]. Note that the
1526 * variable/argument itself (where the map points) is not deleted.
1528 DUK_LOCAL
void duk__check_arguments_map_for_delete(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_propdesc
*temp_desc
) {
1529 duk_context
*ctx
= (duk_context
*) thr
;
1532 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1534 if (!duk_hobject_get_own_propdesc(thr
, obj
, DUK_HTHREAD_STRING_INT_MAP(thr
), temp_desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
1535 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic delete behavior"));
1539 map
= duk_require_hobject(ctx
, -1);
1540 DUK_ASSERT(map
!= NULL
);
1541 duk_pop(ctx
); /* map is reachable through obj */
1543 DUK_DDD(DUK_DDDPRINT("-> have 'map', delete key %!O from map (if exists)); ignore result",
1544 (duk_heaphdr
*) key
));
1546 /* Note: no recursion issue, we can trust 'map' to behave */
1547 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(map
));
1548 DUK_DDD(DUK_DDDPRINT("map before deletion: %!O", (duk_heaphdr
*) map
));
1549 (void) duk_hobject_delprop_raw(thr
, map
, key
, 0); /* ignore result */
1550 DUK_DDD(DUK_DDDPRINT("map after deletion: %!O", (duk_heaphdr
*) map
));
1554 * Ecmascript compliant [[GetOwnProperty]](P), for internal use only.
1556 * If property is found:
1557 * - Fills descriptor fields to 'out_desc'
1558 * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the
1559 * property onto the stack ('undefined' for accessor properties).
1560 * - Returns non-zero
1562 * If property is not found:
1563 * - 'out_desc' is left in untouched state (possibly garbage)
1564 * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE
1570 * - Getting a property descriptor may cause an allocation (and hence
1571 * GC) to take place, hence reachability and refcount of all related
1572 * values matter. Reallocation of value stack, properties, etc may
1573 * invalidate many duk_tval pointers (concretely, those which reside
1574 * in memory areas subject to reallocation). However, heap object
1575 * pointers are never affected (heap objects have stable pointers).
1577 * - The value of a plain property is always reachable and has a non-zero
1580 * - The value of a virtual property is not necessarily reachable from
1581 * elsewhere and may have a refcount of zero. Hence we push it onto
1582 * the valstack for the caller, which ensures it remains reachable
1583 * while it is needed.
1585 * - There are no virtual accessor properties. Hence, all getters and
1586 * setters are always related to concretely stored properties, which
1587 * ensures that the get/set functions in the resulting descriptor are
1588 * reachable and have non-zero refcounts. Should there be virtual
1589 * accessor properties later, this would need to change.
1592 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
) {
1593 duk_context
*ctx
= (duk_context
*) thr
;
1596 DUK_DDD(DUK_DDDPRINT("duk_hobject_get_own_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, "
1597 "arr_idx=%ld (obj -> %!O, key -> %!O)",
1598 (void *) thr
, (void *) obj
, (void *) key
, (void *) out_desc
,
1599 (long) flags
, (long) arr_idx
,
1600 (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
));
1602 DUK_ASSERT(ctx
!= NULL
);
1603 DUK_ASSERT(thr
!= NULL
);
1604 DUK_ASSERT(thr
->heap
!= NULL
);
1605 DUK_ASSERT(obj
!= NULL
);
1606 DUK_ASSERT(key
!= NULL
);
1607 DUK_ASSERT(out_desc
!= NULL
);
1608 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1610 /* XXX: optimize this filling behavior later */
1611 out_desc
->flags
= 0;
1612 out_desc
->get
= NULL
;
1613 out_desc
->set
= NULL
;
1614 out_desc
->e_idx
= -1;
1615 out_desc
->h_idx
= -1;
1616 out_desc
->a_idx
= -1;
1622 if (DUK_HOBJECT_HAS_ARRAY_PART(obj
) && arr_idx
!= DUK__NO_ARRAY_INDEX
) {
1623 if (arr_idx
< DUK_HOBJECT_GET_ASIZE(obj
)) {
1624 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, arr_idx
);
1625 if (!DUK_TVAL_IS_UNUSED(tv
)) {
1626 DUK_DDD(DUK_DDDPRINT("-> found in array part"));
1627 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1628 duk_push_tval(ctx
, tv
);
1630 /* implicit attributes */
1631 out_desc
->flags
= DUK_PROPDESC_FLAG_WRITABLE
|
1632 DUK_PROPDESC_FLAG_CONFIGURABLE
|
1633 DUK_PROPDESC_FLAG_ENUMERABLE
;
1634 out_desc
->a_idx
= arr_idx
;
1638 /* assume array part is comprehensive (contains all array indexed elements
1639 * or none of them); hence no need to check the entries part here.
1641 DUK_DDD(DUK_DDDPRINT("-> not found as a concrete property (has array part, "
1642 "should be there if present)"));
1643 goto prop_not_found_concrete
;
1650 duk_hobject_find_existing_entry(thr
->heap
, obj
, key
, &out_desc
->e_idx
, &out_desc
->h_idx
);
1651 if (out_desc
->e_idx
>= 0) {
1652 duk_int_t e_idx
= out_desc
->e_idx
;
1653 out_desc
->flags
= DUK_HOBJECT_E_GET_FLAGS(thr
->heap
, obj
, e_idx
);
1654 if (out_desc
->flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
1655 DUK_DDD(DUK_DDDPRINT("-> found accessor property in entry part"));
1656 out_desc
->get
= DUK_HOBJECT_E_GET_VALUE_GETTER(thr
->heap
, obj
, e_idx
);
1657 out_desc
->set
= DUK_HOBJECT_E_GET_VALUE_SETTER(thr
->heap
, obj
, e_idx
);
1658 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1659 /* a dummy undefined value is pushed to make valstack
1660 * behavior uniform for caller
1662 duk_push_undefined(ctx
);
1665 DUK_DDD(DUK_DDDPRINT("-> found plain property in entry part"));
1666 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, e_idx
);
1667 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1668 duk_push_tval(ctx
, tv
);
1675 * Not found as a concrete property, check whether a String object
1676 * virtual property matches.
1679 prop_not_found_concrete
:
1681 if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj
)) {
1682 DUK_DDD(DUK_DDDPRINT("string object exotic property get for key: %!O, arr_idx: %ld",
1683 (duk_heaphdr
*) key
, (long) arr_idx
));
1685 if (arr_idx
!= DUK__NO_ARRAY_INDEX
) {
1688 DUK_DDD(DUK_DDDPRINT("array index exists"));
1690 h_val
= duk_hobject_get_internal_value_string(thr
->heap
, obj
);
1692 if (arr_idx
< DUK_HSTRING_GET_CHARLEN(h_val
)) {
1693 DUK_DDD(DUK_DDDPRINT("-> found, array index inside string"));
1694 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1695 duk_push_hstring(ctx
, h_val
);
1696 duk_substring(ctx
, -1, arr_idx
, arr_idx
+ 1); /* [str] -> [substr] */
1698 out_desc
->flags
= DUK_PROPDESC_FLAG_ENUMERABLE
| /* E5 Section 15.5.5.2 */
1699 DUK_PROPDESC_FLAG_VIRTUAL
;
1701 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
));
1702 return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
1704 /* index is above internal string length -> property is fully normal */
1705 DUK_DDD(DUK_DDDPRINT("array index outside string -> normal property"));
1707 } else if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
1710 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1712 h_val
= duk_hobject_get_internal_value_string(thr
->heap
, obj
);
1713 DUK_ASSERT(h_val
!= NULL
);
1714 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1715 duk_push_uint(ctx
, (duk_uint_t
) DUK_HSTRING_GET_CHARLEN(h_val
));
1717 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
; /* E5 Section 15.5.5.1 */
1719 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
));
1720 return 1; /* cannot be arguments exotic */
1722 } else if (DUK_HOBJECT_IS_BUFFEROBJECT(obj
)) {
1723 duk_hbufferobject
*h_bufobj
;
1724 duk_uint_t byte_off
;
1725 duk_small_uint_t elem_size
;
1727 h_bufobj
= (duk_hbufferobject
*) obj
;
1728 DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj
);
1729 DUK_DDD(DUK_DDDPRINT("bufferobject property get for key: %!O, arr_idx: %ld",
1730 (duk_heaphdr
*) key
, (long) arr_idx
));
1732 if (arr_idx
!= DUK__NO_ARRAY_INDEX
) {
1733 DUK_DDD(DUK_DDDPRINT("array index exists"));
1735 /* Careful with wrapping: arr_idx upshift may easily wrap, whereas
1736 * length downshift won't.
1738 if (arr_idx
< (h_bufobj
->length
>> h_bufobj
->shift
)) {
1739 byte_off
= arr_idx
<< h_bufobj
->shift
; /* no wrap assuming h_bufobj->length is valid */
1740 elem_size
= 1 << h_bufobj
->shift
;
1741 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1744 if (h_bufobj
->buf
!= NULL
&& DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj
, byte_off
+ elem_size
)) {
1745 data
= (duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h_bufobj
->buf
) + h_bufobj
->offset
+ byte_off
;
1746 duk_hbufferobject_push_validated_read(ctx
, h_bufobj
, data
, elem_size
);
1748 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)"));
1749 duk_push_uint(ctx
, 0);
1752 out_desc
->flags
= DUK_PROPDESC_FLAG_WRITABLE
|
1753 DUK_PROPDESC_FLAG_ENUMERABLE
|
1754 DUK_PROPDESC_FLAG_VIRTUAL
;
1756 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
));
1757 return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
1759 /* index is above internal buffer length -> property is fully normal */
1760 DUK_DDD(DUK_DDDPRINT("array index outside buffer -> normal property"));
1762 } else if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
1763 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1765 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1766 /* Length in elements: take into account shift, but
1767 * intentionally don't check the underlying buffer here.
1769 duk_push_uint(ctx
, h_bufobj
->length
>> h_bufobj
->shift
);
1771 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
;
1773 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
));
1774 return 1; /* cannot be arguments exotic */
1775 } else if (key
== DUK_HTHREAD_STRING_BYTE_LENGTH(thr
)) {
1776 /* If neutered must return 0; length is zeroed during
1779 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1780 duk_push_uint(ctx
, h_bufobj
->length
);
1782 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
;
1783 return 1; /* cannot be arguments exotic */
1784 } else if (key
== DUK_HTHREAD_STRING_BYTE_OFFSET(thr
)) {
1785 /* If neutered must return 0; offset is zeroed during
1788 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1789 duk_push_uint(ctx
, h_bufobj
->offset
);
1791 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
;
1792 return 1; /* cannot be arguments exotic */
1793 } else if (key
== DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr
)) {
1794 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1795 duk_push_uint(ctx
, 1 << h_bufobj
->shift
);
1797 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
;
1798 return 1; /* cannot be arguments exotic */
1800 } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj
)) {
1801 DUK_DDD(DUK_DDDPRINT("duktape/c object exotic property get for key: %!O, arr_idx: %ld",
1802 (duk_heaphdr
*) key
, (long) arr_idx
));
1804 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
1805 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1807 if (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) {
1808 duk_int16_t func_nargs
= ((duk_hnativefunction
*) obj
)->nargs
;
1809 duk_push_int(ctx
, func_nargs
== DUK_HNATIVEFUNCTION_NARGS_VARARGS
? 0 : func_nargs
);
1811 out_desc
->flags
= DUK_PROPDESC_FLAG_VIRTUAL
; /* not enumerable */
1813 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
));
1814 return 1; /* cannot be arguments exotic */
1818 /* Array properties have exotic behavior but they are concrete,
1819 * so no special handling here.
1821 * Arguments exotic behavior (E5 Section 10.6, [[GetOwnProperty]]
1822 * is only relevant as a post-check implemented below; hence no
1827 * Not found as concrete or virtual
1830 DUK_DDD(DUK_DDDPRINT("-> not found (virtual, entry part, or array part)"));
1836 * Arguments object has exotic post-processing, see E5 Section 10.6,
1837 * description of [[GetOwnProperty]] variant for arguments.
1841 DUK_DDD(DUK_DDDPRINT("-> property found, checking for arguments exotic post-behavior"));
1844 * - only numbered indices are relevant, so arr_idx fast reject is good
1845 * (this is valid unless there are more than 4**32-1 arguments).
1846 * - since variable lookup has no side effects, this can be skipped if
1847 * DUK_GETDESC_FLAG_PUSH_VALUE is not set.
1850 if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
) &&
1851 arr_idx
!= DUK__NO_ARRAY_INDEX
&&
1852 (flags
& DUK_GETDESC_FLAG_PUSH_VALUE
)) {
1853 duk_propdesc temp_desc
;
1855 /* Magically bound variable cannot be an accessor. However,
1856 * there may be an accessor property (or a plain property) in
1857 * place with magic behavior removed. This happens e.g. when
1858 * a magic property is redefined with defineProperty().
1859 * Cannot assert for "not accessor" here.
1862 /* replaces top of stack with new value if necessary */
1863 DUK_ASSERT((flags
& DUK_GETDESC_FLAG_PUSH_VALUE
) != 0);
1865 if (duk__check_arguments_map_for_get(thr
, obj
, key
, &temp_desc
)) {
1866 DUK_DDD(DUK_DDDPRINT("-> arguments exotic behavior overrides result: %!T -> %!T",
1867 (duk_tval
*) duk_get_tval(ctx
, -2),
1868 (duk_tval
*) duk_get_tval(ctx
, -1)));
1869 /* [... old_result result] -> [... result] */
1870 duk_remove(ctx
, -2);
1877 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
) {
1878 DUK_ASSERT(thr
!= NULL
);
1879 DUK_ASSERT(obj
!= NULL
);
1880 DUK_ASSERT(key
!= NULL
);
1881 DUK_ASSERT(out_desc
!= NULL
);
1882 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1884 return duk__get_own_propdesc_raw(thr
, obj
, key
, DUK_HSTRING_GET_ARRIDX_SLOW(key
), out_desc
, flags
);
1888 * Ecmascript compliant [[GetProperty]](P), for internal use only.
1890 * If property is found:
1891 * - Fills descriptor fields to 'out_desc'
1892 * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the
1893 * property onto the stack ('undefined' for accessor properties).
1894 * - Returns non-zero
1896 * If property is not found:
1897 * - 'out_desc' is left in untouched state (possibly garbage)
1898 * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE
1902 * May cause arbitrary side effects and invalidate (most) duk_tval
1906 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
) {
1908 duk_uint32_t arr_idx
;
1911 DUK_ASSERT(thr
!= NULL
);
1912 DUK_ASSERT(thr
->heap
!= NULL
);
1913 DUK_ASSERT(obj
!= NULL
);
1914 DUK_ASSERT(key
!= NULL
);
1915 DUK_ASSERT(out_desc
!= NULL
);
1916 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
1918 arr_idx
= DUK_HSTRING_GET_ARRIDX_FAST(key
);
1920 DUK_DDD(DUK_DDDPRINT("duk__get_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, "
1921 "arr_idx=%ld (obj -> %!O, key -> %!O)",
1922 (void *) thr
, (void *) obj
, (void *) key
, (void *) out_desc
,
1923 (long) flags
, (long) arr_idx
,
1924 (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
));
1927 DUK_ASSERT(curr
!= NULL
);
1928 sanity
= DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY
;
1930 if (duk__get_own_propdesc_raw(thr
, curr
, key
, arr_idx
, out_desc
, flags
)) {
1931 /* stack contains value (if requested), 'out_desc' is set */
1935 /* not found in 'curr', next in prototype chain; impose max depth */
1936 if (sanity
-- == 0) {
1937 if (flags
& DUK_GETDESC_FLAG_IGNORE_PROTOLOOP
) {
1938 /* treat like property not found */
1941 DUK_ERROR_RANGE(thr
, DUK_STR_PROTOTYPE_CHAIN_LIMIT
);
1944 curr
= DUK_HOBJECT_GET_PROTOTYPE(thr
->heap
, curr
);
1947 /* out_desc is left untouched (possibly garbage), caller must use return
1948 * value to determine whether out_desc can be looked up
1955 * Shallow fast path checks for accessing array elements with numeric
1956 * indices. The goal is to try to avoid coercing an array index to an
1957 * (interned) string for the most common lookups, in particular, for
1958 * standard Array objects.
1960 * Interning is avoided but only for a very narrow set of cases:
1961 * - Object has array part, index is within array allocation, and
1962 * value is not unused (= key exists)
1963 * - Object has no interfering exotic behavior (e.g. arguments or
1964 * string object exotic behaviors interfere, array exotic
1965 * behavior does not).
1967 * Current shortcoming: if key does not exist (even if it is within
1968 * the array allocation range) a slow path lookup with interning is
1969 * always required. This can probably be fixed so that there is a
1970 * quick fast path for non-existent elements as well, at least for
1971 * standard Array objects.
1974 DUK_LOCAL duk_tval
*duk__getprop_shallow_fastpath_array_tval(duk_hthread
*thr
, duk_hobject
*obj
, duk_tval
*tv_key
) {
1980 if (!(DUK_HOBJECT_HAS_ARRAY_PART(obj
) &&
1981 !DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
) &&
1982 !DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj
) &&
1983 !DUK_HOBJECT_IS_BUFFEROBJECT(obj
) &&
1984 !DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj
))) {
1985 /* Must have array part and no conflicting exotic behaviors.
1986 * Doesn't need to have array special behavior, e.g. Arguments
1987 * object has array part.
1992 /* Arrays never have other exotic behaviors. */
1994 DUK_DDD(DUK_DDDPRINT("fast path attempt (no exotic string/arguments/buffer "
1995 "behavior, object has array part)"));
1997 #if defined(DUK_USE_FASTINT)
1998 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
1999 idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2002 if (DUK_TVAL_IS_DOUBLE(tv_key
)) {
2003 idx
= duk__tval_number_to_arr_idx(tv_key
);
2005 DUK_DDD(DUK_DDDPRINT("key is not a number"));
2009 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2010 * is 0xffffffffUL. We don't need to check for that explicitly
2011 * because 0xffffffffUL will never be inside object 'a_size'.
2014 if (idx
>= DUK_HOBJECT_GET_ASIZE(obj
)) {
2015 DUK_DDD(DUK_DDDPRINT("key is not an array index or outside array part"));
2018 DUK_ASSERT(idx
!= 0xffffffffUL
);
2019 DUK_ASSERT(idx
!= DUK__NO_ARRAY_INDEX
);
2021 /* XXX: for array instances we could take a shortcut here and assume
2022 * Array.prototype doesn't contain an array index property.
2025 DUK_DDD(DUK_DDDPRINT("key is a valid array index and inside array part"));
2026 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, idx
);
2027 if (!DUK_TVAL_IS_UNUSED(tv
)) {
2028 DUK_DDD(DUK_DDDPRINT("-> fast path successful"));
2032 DUK_DDD(DUK_DDDPRINT("fast path attempt failed, fall back to slow path"));
2036 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
) {
2039 duk_uint32_t old_len
, new_len
;
2041 if (!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj
) &&
2042 DUK_HOBJECT_HAS_ARRAY_PART(obj
) &&
2043 DUK_HOBJECT_HAS_EXTENSIBLE(obj
))) {
2046 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)); /* caller ensures */
2048 #if defined(DUK_USE_FASTINT)
2049 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
2050 idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2053 if (DUK_TVAL_IS_DOUBLE(tv_key
)) {
2054 idx
= duk__tval_number_to_arr_idx(tv_key
);
2056 DUK_DDD(DUK_DDDPRINT("key is not a number"));
2060 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2061 * is 0xffffffffUL. We don't need to check for that explicitly
2062 * because 0xffffffffUL will never be inside object 'a_size'.
2065 if (idx
>= DUK_HOBJECT_GET_ASIZE(obj
)) { /* for resizing of array part, use slow path */
2068 DUK_ASSERT(idx
!= 0xffffffffUL
);
2069 DUK_ASSERT(idx
!= DUK__NO_ARRAY_INDEX
);
2071 old_len
= duk__get_old_array_length(thr
, obj
, temp_desc
);
2073 if (idx
>= old_len
) {
2074 DUK_DDD(DUK_DDDPRINT("write new array entry requires length update "
2075 "(arr_idx=%ld, old_len=%ld)",
2076 (long) idx
, (long) old_len
));
2077 if (!(temp_desc
->flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
2078 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_WRITABLE
);
2079 return 0; /* not reachable */
2083 /* No resize has occurred so temp_desc->e_idx is still OK */
2084 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, temp_desc
->e_idx
);
2085 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
2086 DUK_TVAL_SET_FASTINT_U32(tv
, new_len
); /* no need for decref/incref because value is a number */
2091 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, idx
);
2092 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv
, tv_val
); /* side effects */
2094 DUK_DDD(DUK_DDDPRINT("array fast path success for index %ld", (long) idx
));
2099 * Fast path for bufferobject getprop/putprop
2102 DUK_LOCAL duk_bool_t
duk__getprop_fastpath_bufobj_tval(duk_hthread
*thr
, duk_hobject
*obj
, duk_tval
*tv_key
) {
2105 duk_hbufferobject
*h_bufobj
;
2106 duk_uint_t byte_off
;
2107 duk_small_uint_t elem_size
;
2110 ctx
= (duk_context
*) thr
;
2112 if (!DUK_HOBJECT_IS_BUFFEROBJECT(obj
)) {
2115 h_bufobj
= (duk_hbufferobject
*) obj
;
2117 #if defined(DUK_USE_FASTINT)
2118 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
2119 idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2122 if (DUK_TVAL_IS_DOUBLE(tv_key
)) {
2123 idx
= duk__tval_number_to_arr_idx(tv_key
);
2128 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2129 * is 0xffffffffUL. We don't need to check for that explicitly
2130 * because 0xffffffffUL will never be inside bufferobject length.
2133 /* Careful with wrapping (left shifting idx would be unsafe). */
2134 if (idx
>= (h_bufobj
->length
>> h_bufobj
->shift
)) {
2137 DUK_ASSERT(idx
!= DUK__NO_ARRAY_INDEX
);
2139 byte_off
= idx
<< h_bufobj
->shift
; /* no wrap assuming h_bufobj->length is valid */
2140 elem_size
= 1 << h_bufobj
->shift
;
2142 if (h_bufobj
->buf
!= NULL
&& DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj
, byte_off
+ elem_size
)) {
2143 data
= (duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h_bufobj
->buf
) + h_bufobj
->offset
+ byte_off
;
2144 duk_hbufferobject_push_validated_read(ctx
, h_bufobj
, data
, elem_size
);
2146 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)"));
2147 duk_push_uint(ctx
, 0);
2153 DUK_LOCAL duk_bool_t
duk__putprop_fastpath_bufobj_tval(duk_hthread
*thr
, duk_hobject
*obj
, duk_tval
*tv_key
, duk_tval
*tv_val
) {
2156 duk_hbufferobject
*h_bufobj
;
2157 duk_uint_t byte_off
;
2158 duk_small_uint_t elem_size
;
2161 ctx
= (duk_context
*) thr
;
2163 if (!(DUK_HOBJECT_IS_BUFFEROBJECT(obj
) &&
2164 DUK_TVAL_IS_NUMBER(tv_val
))) {
2167 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)); /* caller ensures; rom objects are never bufferobjects now */
2169 h_bufobj
= (duk_hbufferobject
*) obj
;
2170 #if defined(DUK_USE_FASTINT)
2171 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
2172 idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2175 if (DUK_TVAL_IS_DOUBLE(tv_key
)) {
2176 idx
= duk__tval_number_to_arr_idx(tv_key
);
2181 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2182 * is 0xffffffffUL. We don't need to check for that explicitly
2183 * because 0xffffffffUL will never be inside bufferobject length.
2186 /* Careful with wrapping (left shifting idx would be unsafe). */
2187 if (idx
>= (h_bufobj
->length
>> h_bufobj
->shift
)) {
2190 DUK_ASSERT(idx
!= DUK__NO_ARRAY_INDEX
);
2192 byte_off
= idx
<< h_bufobj
->shift
; /* no wrap assuming h_bufobj->length is valid */
2193 elem_size
= 1 << h_bufobj
->shift
;
2195 /* Value is required to be a number in the fast path so there
2196 * are no side effects in write coercion.
2198 duk_push_tval(ctx
, tv_val
);
2199 DUK_ASSERT(duk_is_number(ctx
, -1));
2201 if (h_bufobj
->buf
!= NULL
&& DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj
, byte_off
+ elem_size
)) {
2202 data
= (duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h_bufobj
->buf
) + h_bufobj
->offset
+ byte_off
;
2203 duk_hbufferobject_validated_write(ctx
, h_bufobj
, data
, elem_size
);
2205 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (write skipped)"));
2213 * GETPROP: Ecmascript property read.
2216 DUK_INTERNAL duk_bool_t
duk_hobject_getprop(duk_hthread
*thr
, duk_tval
*tv_obj
, duk_tval
*tv_key
) {
2217 duk_context
*ctx
= (duk_context
*) thr
;
2218 duk_tval tv_obj_copy
;
2219 duk_tval tv_key_copy
;
2220 duk_hobject
*curr
= NULL
;
2221 duk_hstring
*key
= NULL
;
2222 duk_uint32_t arr_idx
= DUK__NO_ARRAY_INDEX
;
2226 DUK_DDD(DUK_DDDPRINT("getprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
2227 (void *) thr
, (void *) tv_obj
, (void *) tv_key
,
2228 (duk_tval
*) tv_obj
, (duk_tval
*) tv_key
));
2230 DUK_ASSERT(ctx
!= NULL
);
2231 DUK_ASSERT(thr
!= NULL
);
2232 DUK_ASSERT(thr
->heap
!= NULL
);
2233 DUK_ASSERT(tv_obj
!= NULL
);
2234 DUK_ASSERT(tv_key
!= NULL
);
2236 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
2239 * Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
2240 * them being invalidated by a valstack resize.
2242 * XXX: this is now an overkill for many fast paths. Rework this
2243 * to be faster (although switching to a valstack discipline might
2244 * be a better solution overall).
2247 DUK_TVAL_SET_TVAL(&tv_obj_copy
, tv_obj
);
2248 DUK_TVAL_SET_TVAL(&tv_key_copy
, tv_key
);
2249 tv_obj
= &tv_obj_copy
;
2250 tv_key
= &tv_key_copy
;
2253 * Coercion and fast path processing
2256 switch (DUK_TVAL_GET_TAG(tv_obj
)) {
2257 case DUK_TAG_UNDEFINED
:
2258 case DUK_TAG_NULL
: {
2259 /* Note: unconditional throw */
2260 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
2261 #if defined(DUK_USE_PARANOID_ERRORS)
2262 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_BASE
);
2264 DUK_ERROR_FMT2(thr
, DUK_ERR_TYPE_ERROR
, "cannot read property %s of %s",
2265 duk_push_string_tval_readable(ctx
, tv_key
), duk_push_string_tval_readable(ctx
, tv_obj
));
2270 case DUK_TAG_BOOLEAN
: {
2271 DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
2272 curr
= thr
->builtins
[DUK_BIDX_BOOLEAN_PROTOTYPE
];
2276 case DUK_TAG_STRING
: {
2277 duk_hstring
*h
= DUK_TVAL_GET_STRING(tv_obj
);
2278 duk_int_t pop_count
;
2280 #if defined(DUK_USE_FASTINT)
2281 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
2282 arr_idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2283 DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path fastint; arr_idx %ld", (long) arr_idx
));
2287 if (DUK_TVAL_IS_NUMBER(tv_key
)) {
2288 arr_idx
= duk__tval_number_to_arr_idx(tv_key
);
2289 DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path number; arr_idx %ld", (long) arr_idx
));
2292 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2293 DUK_ASSERT(key
!= NULL
);
2294 DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
2295 "coercion key is %!T, arr_idx %ld",
2296 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
2300 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
2301 arr_idx
< DUK_HSTRING_GET_CHARLEN(h
)) {
2302 duk_pop_n(ctx
, pop_count
);
2303 duk_push_hstring(ctx
, h
);
2304 duk_substring(ctx
, -1, arr_idx
, arr_idx
+ 1); /* [str] -> [substr] */
2306 DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is an index inside string length "
2307 "after coercion -> return char)",
2308 (duk_tval
*) duk_get_tval(ctx
, -1)));
2312 if (pop_count
== 0) {
2313 /* This is a pretty awkward control flow, but we need to recheck the
2314 * key coercion here.
2316 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2317 DUK_ASSERT(key
!= NULL
);
2318 DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
2319 "coercion key is %!T, arr_idx %ld",
2320 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
2323 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
2324 duk_pop(ctx
); /* [key] -> [] */
2325 duk_push_uint(ctx
, (duk_uint_t
) DUK_HSTRING_GET_CHARLEN(h
)); /* [] -> [res] */
2327 DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is 'length' after coercion -> "
2328 "return string length)",
2329 (duk_tval
*) duk_get_tval(ctx
, -1)));
2332 DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
2333 curr
= thr
->builtins
[DUK_BIDX_STRING_PROTOTYPE
];
2334 goto lookup
; /* avoid double coercion */
2337 case DUK_TAG_OBJECT
: {
2340 curr
= DUK_TVAL_GET_OBJECT(tv_obj
);
2341 DUK_ASSERT(curr
!= NULL
);
2343 tmp
= duk__getprop_shallow_fastpath_array_tval(thr
, curr
, tv_key
);
2345 duk_push_tval(ctx
, tmp
);
2347 DUK_DDD(DUK_DDDPRINT("-> %!T (base is object, key is a number, array part "
2349 (duk_tval
*) duk_get_tval(ctx
, -1)));
2353 if (duk__getprop_fastpath_bufobj_tval(thr
, curr
, tv_key
) != 0) {
2354 /* Read value pushed on stack. */
2355 DUK_DDD(DUK_DDDPRINT("-> %!T (base is bufobj, key is a number, bufferobject "
2357 (duk_tval
*) duk_get_tval(ctx
, -1)));
2361 #if defined(DUK_USE_ES6_PROXY)
2362 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(curr
))) {
2363 duk_hobject
*h_target
;
2365 if (duk__proxy_check_prop(thr
, curr
, DUK_STRIDX_GET
, tv_key
, &h_target
)) {
2366 /* -> [ ... trap handler ] */
2367 DUK_DDD(DUK_DDDPRINT("-> proxy object 'get' for key %!T", (duk_tval
*) tv_key
));
2368 duk_push_hobject(ctx
, h_target
); /* target */
2369 duk_push_tval(ctx
, tv_key
); /* P */
2370 duk_push_tval(ctx
, tv_obj
); /* Receiver: Proxy object */
2371 duk_call_method(ctx
, 3 /*nargs*/);
2373 /* Target object must be checked for a conflicting
2374 * non-configurable property.
2376 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2377 DUK_ASSERT(key
!= NULL
);
2379 if (duk__get_own_propdesc_raw(thr
, h_target
, key
, arr_idx
, &desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
2380 duk_tval
*tv_hook
= duk_require_tval(ctx
, -3); /* value from hook */
2381 duk_tval
*tv_targ
= duk_require_tval(ctx
, -1); /* value from target */
2382 duk_bool_t datadesc_reject
;
2383 duk_bool_t accdesc_reject
;
2385 DUK_DDD(DUK_DDDPRINT("proxy 'get': target has matching property %!O, check for "
2386 "conflicting property; tv_hook=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
2387 "desc.get=%p, desc.set=%p",
2388 (duk_heaphdr
*) key
, (duk_tval
*) tv_hook
, (duk_tval
*) tv_targ
,
2389 (unsigned long) desc
.flags
,
2390 (void *) desc
.get
, (void *) desc
.set
));
2392 datadesc_reject
= !(desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) &&
2393 !(desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) &&
2394 !(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) &&
2395 !duk_js_samevalue(tv_hook
, tv_targ
);
2396 accdesc_reject
= (desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) &&
2397 !(desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) &&
2398 (desc
.get
== NULL
) &&
2399 !DUK_TVAL_IS_UNDEFINED(tv_hook
);
2400 if (datadesc_reject
|| accdesc_reject
) {
2401 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
2408 return 1; /* return value */
2411 curr
= h_target
; /* resume lookup from target */
2412 DUK_TVAL_SET_OBJECT(tv_obj
, curr
);
2414 #endif /* DUK_USE_ES6_PROXY */
2416 if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(curr
)) {
2417 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2418 DUK_ASSERT(key
!= NULL
);
2420 if (duk__check_arguments_map_for_get(thr
, curr
, key
, &desc
)) {
2421 DUK_DDD(DUK_DDDPRINT("-> %!T (base is object with arguments exotic behavior, "
2422 "key matches magically bound property -> skip standard "
2423 "Get with replacement value)",
2424 (duk_tval
*) duk_get_tval(ctx
, -1)));
2426 /* no need for 'caller' post-check, because 'key' must be an array index */
2428 duk_remove(ctx
, -2); /* [key result] -> [result] */
2432 goto lookup
; /* avoid double coercion */
2437 /* Buffer has virtual properties similar to string, but indexed values
2438 * are numbers, not 1-byte buffers/strings which would perform badly.
2440 case DUK_TAG_BUFFER
: {
2441 duk_hbuffer
*h
= DUK_TVAL_GET_BUFFER(tv_obj
);
2442 duk_int_t pop_count
;
2445 * Because buffer values are often looped over, a number fast path
2449 #if defined(DUK_USE_FASTINT)
2450 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
2451 arr_idx
= duk__tval_fastint_to_arr_idx(tv_key
);
2452 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path fastint; arr_idx %ld", (long) arr_idx
));
2457 if (DUK_TVAL_IS_NUMBER(tv_key
)) {
2458 arr_idx
= duk__tval_number_to_arr_idx(tv_key
);
2459 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx
));
2462 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2463 DUK_ASSERT(key
!= NULL
);
2464 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
2465 "coercion key is %!T, arr_idx %ld",
2466 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
2470 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
2471 arr_idx
< DUK_HBUFFER_GET_SIZE(h
)) {
2472 duk_pop_n(ctx
, pop_count
);
2473 duk_push_uint(ctx
, ((duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h
))[arr_idx
]);
2475 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is an index inside buffer length "
2476 "after coercion -> return byte as number)",
2477 (duk_tval
*) duk_get_tval(ctx
, -1)));
2481 if (pop_count
== 0) {
2482 /* This is a pretty awkward control flow, but we need to recheck the
2483 * key coercion here.
2485 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2486 DUK_ASSERT(key
!= NULL
);
2487 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
2488 "coercion key is %!T, arr_idx %ld",
2489 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
2492 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
) ||
2493 key
== DUK_HTHREAD_STRING_BYTE_LENGTH(thr
)) {
2494 duk_pop(ctx
); /* [key] -> [] */
2495 duk_push_uint(ctx
, (duk_uint_t
) DUK_HBUFFER_GET_SIZE(h
)); /* [] -> [res] */
2497 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'length' or 'byteLength' "
2498 "after coercion -> return buffer length)",
2499 (duk_tval
*) duk_get_tval(ctx
, -1)));
2501 } else if (key
== DUK_HTHREAD_STRING_BYTE_OFFSET(thr
)) {
2502 duk_pop(ctx
); /* [key] -> [] */
2503 duk_push_uint(ctx
, 0); /* [] -> [res] */
2505 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'byteOffset' after coercion -> "
2506 "return 0 for consistency with Buffer objects)",
2507 (duk_tval
*) duk_get_tval(ctx
, -1)));
2509 } else if (key
== DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr
)) {
2510 duk_pop(ctx
); /* [key] -> [] */
2511 duk_push_uint(ctx
, 1); /* [] -> [res] */
2513 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'BYTES_PER_ELEMENT' after coercion -> "
2514 "return 1 for consistency with Buffer objects)",
2515 (duk_tval
*) duk_get_tval(ctx
, -1)));
2519 DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
2520 curr
= thr
->builtins
[DUK_BIDX_BUFFER_PROTOTYPE
];
2521 goto lookup
; /* avoid double coercion */
2524 case DUK_TAG_POINTER
: {
2525 DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
2526 curr
= thr
->builtins
[DUK_BIDX_POINTER_PROTOTYPE
];
2530 case DUK_TAG_LIGHTFUNC
: {
2531 duk_int_t lf_flags
= DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_obj
);
2533 /* Must coerce key: if key is an object, it may coerce to e.g. 'length'. */
2534 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2536 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
2537 duk_int_t lf_len
= DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags
);
2539 duk_push_int(ctx
, lf_len
);
2541 } else if (key
== DUK_HTHREAD_STRING_NAME(thr
)) {
2543 duk_push_lightfunc_name(ctx
, tv_obj
);
2547 DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
2548 curr
= thr
->builtins
[DUK_BIDX_FUNCTION_PROTOTYPE
];
2549 goto lookup
; /* avoid double coercion */
2552 #if defined(DUK_USE_FASTINT)
2553 case DUK_TAG_FASTINT
:
2557 DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
2558 DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_obj
));
2559 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj
));
2560 curr
= thr
->builtins
[DUK_BIDX_NUMBER_PROTOTYPE
];
2565 /* key coercion (unless already coerced above) */
2566 DUK_ASSERT(key
== NULL
);
2567 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2568 DUK_ASSERT(key
!= NULL
);
2575 /* [key] (coerced) */
2576 DUK_ASSERT(curr
!= NULL
);
2577 DUK_ASSERT(key
!= NULL
);
2579 sanity
= DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY
;
2581 if (!duk__get_own_propdesc_raw(thr
, curr
, key
, arr_idx
, &desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
2585 if (desc
.get
!= NULL
) {
2586 /* accessor with defined getter */
2587 DUK_ASSERT((desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) != 0);
2589 duk_pop(ctx
); /* [key undefined] -> [key] */
2590 duk_push_hobject(ctx
, desc
.get
);
2591 duk_push_tval(ctx
, tv_obj
); /* note: original, uncoerced base */
2592 #ifdef DUK_USE_NONSTD_GETTER_KEY_ARGUMENT
2594 duk_call_method(ctx
, 1); /* [key getter this key] -> [key retval] */
2596 duk_call_method(ctx
, 0); /* [key getter this] -> [key retval] */
2599 /* [key value] or [key undefined] */
2601 /* data property or accessor without getter */
2602 DUK_ASSERT(((desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) == 0) ||
2603 (desc
.get
== NULL
));
2605 /* if accessor without getter, return value is undefined */
2606 DUK_ASSERT(((desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) == 0) ||
2607 duk_is_undefined(ctx
, -1));
2609 /* Note: for an accessor without getter, falling through to
2610 * check for "caller" exotic behavior is unnecessary as
2611 * "undefined" will never activate the behavior. But it does
2612 * no harm, so we'll do it anyway.
2616 goto found
; /* [key result] */
2619 /* XXX: option to pretend property doesn't exist if sanity limit is
2620 * hit might be useful.
2622 if (sanity
-- == 0) {
2623 DUK_ERROR_RANGE(thr
, DUK_STR_PROTOTYPE_CHAIN_LIMIT
);
2625 curr
= DUK_HOBJECT_GET_PROTOTYPE(thr
->heap
, curr
);
2632 duk_to_undefined(ctx
, -1); /* [key] -> [undefined] (default value) */
2634 DUK_DDD(DUK_DDDPRINT("-> %!T (not found)", (duk_tval
*) duk_get_tval(ctx
, -1)));
2638 * Found; post-processing (Function and arguments objects)
2644 #if !defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
2645 /* Special behavior for 'caller' property of (non-bound) function objects
2646 * and non-strict Arguments objects: if 'caller' -value- (!) is a strict
2647 * mode function, throw a TypeError (E5 Sections 15.3.5.4, 10.6).
2648 * Quite interestingly, a non-strict function with no formal arguments
2649 * will get an arguments object -without- special 'caller' behavior!
2651 * The E5.1 spec is a bit ambiguous if this special behavior applies when
2652 * a bound function is the base value (not the 'caller' value): Section
2653 * 15.3.4.5 (describing bind()) states that [[Get]] for bound functions
2654 * matches that of Section 15.3.5.4 ([[Get]] for Function instances).
2655 * However, Section 13.3.5.4 has "NOTE: Function objects created using
2656 * Function.prototype.bind use the default [[Get]] internal method."
2657 * The current implementation assumes this means that bound functions
2658 * should not have the special [[Get]] behavior.
2660 * The E5.1 spec is also a bit unclear if the TypeError throwing is
2661 * applied if the 'caller' value is a strict bound function. The
2662 * current implementation will throw even for both strict non-bound
2663 * and strict bound functions.
2665 * See test-dev-strict-func-as-caller-prop-value.js for quite extensive
2668 * This exotic behavior is disabled when the non-standard 'caller' property
2669 * is enabled, as it conflicts with the free use of 'caller'.
2671 if (key
== DUK_HTHREAD_STRING_CALLER(thr
) &&
2672 DUK_TVAL_IS_OBJECT(tv_obj
)) {
2673 duk_hobject
*orig
= DUK_TVAL_GET_OBJECT(tv_obj
);
2674 DUK_ASSERT(orig
!= NULL
);
2676 if (DUK_HOBJECT_IS_NONBOUND_FUNCTION(orig
) ||
2677 DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig
)) {
2680 /* XXX: The TypeError is currently not applied to bound
2681 * functions because the 'strict' flag is not copied by
2682 * bind(). This may or may not be correct, the specification
2683 * only refers to the value being a "strict mode Function
2684 * object" which is ambiguous.
2686 DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(orig
));
2688 h
= duk_get_hobject(ctx
, -1); /* NULL if not an object */
2690 DUK_HOBJECT_IS_FUNCTION(h
) &&
2691 DUK_HOBJECT_HAS_STRICT(h
)) {
2692 /* XXX: sufficient to check 'strict', assert for 'is function' */
2693 DUK_ERROR_TYPE(thr
, DUK_STR_STRICT_CALLER_READ
);
2697 #endif /* !DUK_USE_NONSTD_FUNC_CALLER_PROPERTY */
2699 duk_remove(ctx
, -2); /* [key result] -> [result] */
2701 DUK_DDD(DUK_DDDPRINT("-> %!T (found)", (duk_tval
*) duk_get_tval(ctx
, -1)));
2706 * HASPROP: Ecmascript property existence check ("in" operator).
2708 * Interestingly, the 'in' operator does not do any coercion of
2709 * the target object.
2712 DUK_INTERNAL duk_bool_t
duk_hobject_hasprop(duk_hthread
*thr
, duk_tval
*tv_obj
, duk_tval
*tv_key
) {
2713 duk_context
*ctx
= (duk_context
*) thr
;
2714 duk_tval tv_key_copy
;
2717 duk_uint32_t arr_idx
;
2721 DUK_DDD(DUK_DDDPRINT("hasprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
2722 (void *) thr
, (void *) tv_obj
, (void *) tv_key
,
2723 (duk_tval
*) tv_obj
, (duk_tval
*) tv_key
));
2725 DUK_ASSERT(thr
!= NULL
);
2726 DUK_ASSERT(thr
->heap
!= NULL
);
2727 DUK_ASSERT(tv_obj
!= NULL
);
2728 DUK_ASSERT(tv_key
!= NULL
);
2729 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
2731 DUK_TVAL_SET_TVAL(&tv_key_copy
, tv_key
);
2732 tv_key
= &tv_key_copy
;
2735 * The 'in' operator requires an object as its right hand side,
2736 * throwing a TypeError unconditionally if this is not the case.
2738 * However, lightfuncs need to behave like fully fledged objects
2739 * here to be maximally transparent, so we need to handle them
2743 /* XXX: Refactor key coercion so that it's only called once. It can't
2744 * be trivially lifted here because the object must be type checked
2748 if (DUK_TVAL_IS_OBJECT(tv_obj
)) {
2749 obj
= DUK_TVAL_GET_OBJECT(tv_obj
);
2750 DUK_ASSERT(obj
!= NULL
);
2752 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2753 } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj
)) {
2754 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
2755 if (duk__key_is_lightfunc_ownprop(thr
, key
)) {
2758 goto pop_and_return
;
2761 /* If not found, resume existence check from Function.prototype.
2762 * We can just substitute the value in this case; nothing will
2763 * need the original base value (as would be the case with e.g.
2766 obj
= thr
->builtins
[DUK_BIDX_FUNCTION_PROTOTYPE
];
2768 /* Note: unconditional throw */
2769 DUK_DDD(DUK_DDDPRINT("base object is not an object -> reject"));
2770 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_BASE
);
2773 /* XXX: fast path for arrays? */
2775 DUK_ASSERT(key
!= NULL
);
2776 DUK_ASSERT(obj
!= NULL
);
2779 #if defined(DUK_USE_ES6_PROXY)
2780 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj
))) {
2781 duk_hobject
*h_target
;
2782 duk_bool_t tmp_bool
;
2784 /* XXX: the key in 'key in obj' is string coerced before we're called
2785 * (which is the required behavior in E5/E5.1/E6) so the key is a string
2789 if (duk__proxy_check_prop(thr
, obj
, DUK_STRIDX_HAS
, tv_key
, &h_target
)) {
2790 /* [ ... key trap handler ] */
2791 DUK_DDD(DUK_DDDPRINT("-> proxy object 'has' for key %!T", (duk_tval
*) tv_key
));
2792 duk_push_hobject(ctx
, h_target
); /* target */
2793 duk_push_tval(ctx
, tv_key
); /* P */
2794 duk_call_method(ctx
, 2 /*nargs*/);
2795 tmp_bool
= duk_to_boolean(ctx
, -1);
2797 /* Target object must be checked for a conflicting
2798 * non-configurable property.
2801 if (duk__get_own_propdesc_raw(thr
, h_target
, key
, arr_idx
, &desc
, 0 /*flags*/)) { /* don't push value */
2802 DUK_DDD(DUK_DDDPRINT("proxy 'has': target has matching property %!O, check for "
2803 "conflicting property; desc.flags=0x%08lx, "
2804 "desc.get=%p, desc.set=%p",
2805 (duk_heaphdr
*) key
, (unsigned long) desc
.flags
,
2806 (void *) desc
.get
, (void *) desc
.set
));
2807 /* XXX: Extensibility check for target uses IsExtensible(). If we
2808 * implemented the isExtensible trap and didn't reject proxies as
2809 * proxy targets, it should be respected here.
2811 if (!((desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && /* property is configurable and */
2812 DUK_HOBJECT_HAS_EXTENSIBLE(h_target
))) { /* ... target is extensible */
2813 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
2818 duk_pop_2(ctx
); /* [ key trap_result ] -> [] */
2822 obj
= h_target
; /* resume check from proxy target */
2824 #endif /* DUK_USE_ES6_PROXY */
2826 /* XXX: inline into a prototype walking loop? */
2828 rc
= duk__get_propdesc(thr
, obj
, key
, &desc
, 0 /*flags*/); /* don't push value */
2832 duk_pop(ctx
); /* [ key ] -> [] */
2837 * HASPROP variant used internally.
2839 * This primitive must never throw an error, callers rely on this.
2840 * In particular, don't throw an error for prototype loops; instead,
2841 * pretend like the property doesn't exist if a prototype sanity limit
2844 * Does not implement proxy behavior: if applied to a proxy object,
2845 * returns key existence on the proxy object itself.
2848 DUK_INTERNAL duk_bool_t
duk_hobject_hasprop_raw(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
) {
2851 DUK_ASSERT(thr
!= NULL
);
2852 DUK_ASSERT(thr
->heap
!= NULL
);
2853 DUK_ASSERT(obj
!= NULL
);
2854 DUK_ASSERT(key
!= NULL
);
2856 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
2858 return duk__get_propdesc(thr
, obj
, key
, &dummy
, DUK_GETDESC_FLAG_IGNORE_PROTOLOOP
); /* don't push value */
2862 * Helper: handle Array object 'length' write which automatically
2863 * deletes properties, see E5 Section 15.4.5.1, step 3. This is
2864 * quite tricky to get right.
2866 * Used by duk_hobject_putprop().
2869 DUK_LOCAL duk_uint32_t
duk__get_old_array_length(duk_hthread
*thr
, duk_hobject
*obj
, duk_propdesc
*temp_desc
) {
2874 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
2876 /* This function is only called for objects with array exotic behavior.
2877 * The [[DefineOwnProperty]] algorithm for arrays requires that
2878 * 'length' can never have a value outside the unsigned 32-bit range,
2879 * attempt to write such a value is a RangeError. Here we can thus
2880 * assert for this. When Duktape internals go around the official
2881 * property write interface (doesn't happen often) this assumption is
2882 * easy to accidentally break, so such code must be written carefully.
2883 * See test-bi-array-push-maxlen.js.
2886 rc
= duk__get_own_propdesc_raw(thr
, obj
, DUK_HTHREAD_STRING_LENGTH(thr
), DUK__NO_ARRAY_INDEX
, temp_desc
, 0 /*flags*/); /* don't push value */
2888 DUK_ASSERT(rc
!= 0); /* arrays MUST have a 'length' property */
2889 DUK_ASSERT(temp_desc
->e_idx
>= 0);
2891 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, temp_desc
->e_idx
);
2892 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
)); /* array 'length' is always a number, as we coerce it */
2893 DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv
) >= 0.0);
2894 DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv
) <= (double) 0xffffffffUL
);
2895 DUK_ASSERT((duk_double_t
) (duk_uint32_t
) DUK_TVAL_GET_NUMBER(tv
) == DUK_TVAL_GET_NUMBER(tv
));
2896 #if defined(DUK_USE_FASTINT)
2897 /* Downgrade checks are not made everywhere, so 'length' is not always
2898 * a fastint (it is a number though). This can be removed once length
2899 * is always guaranteed to be a fastint.
2901 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv
) || DUK_TVAL_IS_DOUBLE(tv
));
2902 if (DUK_TVAL_IS_FASTINT(tv
)) {
2903 res
= (duk_uint32_t
) DUK_TVAL_GET_FASTINT_U32(tv
);
2905 res
= (duk_uint32_t
) DUK_TVAL_GET_DOUBLE(tv
);
2908 res
= (duk_uint32_t
) DUK_TVAL_GET_NUMBER(tv
);
2909 #endif /* DUK_USE_FASTINT */
2914 DUK_LOCAL duk_uint32_t
duk__to_new_array_length_checked(duk_hthread
*thr
) {
2915 duk_context
*ctx
= (duk_context
*) thr
;
2919 /* Input value should be on stack top and will be coerced and
2920 * popped. Refuse to update an Array's 'length' to a value
2921 * outside the 32-bit range. Negative zero is accepted as zero.
2926 d
= duk_to_number(ctx
, -1);
2927 res
= (duk_uint32_t
) d
;
2928 if ((duk_double_t
) res
!= d
) {
2929 DUK_ERROR_RANGE(thr
, DUK_STR_INVALID_ARRAY_LENGTH
);
2935 /* Delete elements required by a smaller length, taking into account
2936 * potentially non-configurable elements. Returns non-zero if all
2937 * elements could be deleted, and zero if all or some elements could
2938 * not be deleted. Also writes final "target length" to 'out_result_len'.
2939 * This is the length value that should go into the 'length' property
2940 * (must be set by the caller). Never throws an error.
2943 duk_bool_t
duk__handle_put_array_length_smaller(duk_hthread
*thr
,
2945 duk_uint32_t old_len
,
2946 duk_uint32_t new_len
,
2947 duk_bool_t force_flag
,
2948 duk_uint32_t
*out_result_len
) {
2949 duk_uint32_t target_len
;
2950 duk_uint_fast32_t i
;
2951 duk_uint32_t arr_idx
;
2956 DUK_DDD(DUK_DDDPRINT("new array length smaller than old (%ld -> %ld), "
2957 "probably need to remove elements",
2958 (long) old_len
, (long) new_len
));
2961 * New length is smaller than old length, need to delete properties above
2964 * If array part exists, this is straightforward: array entries cannot
2965 * be non-configurable so this is guaranteed to work.
2967 * If array part does not exist, array-indexed values are scattered
2968 * in the entry part, and some may not be configurable (preventing length
2969 * from becoming lower than their index + 1). To handle the algorithm
2970 * in E5 Section 15.4.5.1, step l correctly, we scan the entire property
2974 DUK_ASSERT(thr
!= NULL
);
2975 DUK_ASSERT(obj
!= NULL
);
2976 DUK_ASSERT(new_len
< old_len
);
2977 DUK_ASSERT(out_result_len
!= NULL
);
2978 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
2980 if (DUK_HOBJECT_HAS_ARRAY_PART(obj
)) {
2982 * All defined array-indexed properties are in the array part
2983 * (we assume the array part is comprehensive), and all array
2984 * entries are writable, configurable, and enumerable. Thus,
2985 * nothing can prevent array entries from being deleted.
2988 DUK_DDD(DUK_DDDPRINT("have array part, easy case"));
2990 if (old_len
< DUK_HOBJECT_GET_ASIZE(obj
)) {
2991 /* XXX: assertion that entries >= old_len are already unused */
2994 i
= DUK_HOBJECT_GET_ASIZE(obj
);
2996 DUK_ASSERT(i
<= DUK_HOBJECT_GET_ASIZE(obj
));
2998 while (i
> new_len
) {
3000 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, i
);
3001 DUK_TVAL_SET_UNUSED_UPDREF(thr
, tv
); /* side effects */
3004 *out_result_len
= new_len
;
3008 * Entries part is a bit more complex
3011 /* Stage 1: find highest preventing non-configurable entry (if any).
3012 * When forcing, ignore non-configurability.
3015 DUK_DDD(DUK_DDDPRINT("no array part, slow case"));
3017 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 1: find target_len "
3018 "(highest preventing non-configurable entry (if any))"));
3020 target_len
= new_len
;
3022 DUK_DDD(DUK_DDDPRINT("array length write, no array part; force flag -> skip stage 1"));
3025 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
3026 key
= DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
);
3028 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i
));
3031 if (!DUK_HSTRING_HAS_ARRIDX(key
)) {
3032 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i
));
3036 DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key
)); /* XXX: macro checks for array index flag, which is unnecessary here */
3037 arr_idx
= DUK_HSTRING_GET_ARRIDX_SLOW(key
);
3038 DUK_ASSERT(arr_idx
!= DUK__NO_ARRAY_INDEX
);
3039 DUK_ASSERT(arr_idx
< old_len
); /* consistency requires this */
3041 if (arr_idx
< new_len
) {
3042 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below new_len",
3043 (long) i
, (long) arr_idx
));
3046 if (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(thr
->heap
, obj
, i
)) {
3047 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is a relevant array index %ld, but configurable",
3048 (long) i
, (long) arr_idx
));
3052 /* relevant array index is non-configurable, blocks write */
3053 if (arr_idx
>= target_len
) {
3054 DUK_DDD(DUK_DDDPRINT("entry at index %ld has arr_idx %ld, is not configurable, "
3055 "update target_len %ld -> %ld",
3056 (long) i
, (long) arr_idx
, (long) target_len
,
3057 (long) (arr_idx
+ 1)));
3058 target_len
= arr_idx
+ 1;
3063 /* stage 2: delete configurable entries above target length */
3065 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld, target_len=%ld",
3066 (long) old_len
, (long) new_len
, (long) target_len
));
3068 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 2: remove "
3069 "entries >= target_len"));
3071 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
3072 key
= DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
);
3074 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i
));
3077 if (!DUK_HSTRING_HAS_ARRIDX(key
)) {
3078 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i
));
3082 DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key
)); /* XXX: macro checks for array index flag, which is unnecessary here */
3083 arr_idx
= DUK_HSTRING_GET_ARRIDX_SLOW(key
);
3084 DUK_ASSERT(arr_idx
!= DUK__NO_ARRAY_INDEX
);
3085 DUK_ASSERT(arr_idx
< old_len
); /* consistency requires this */
3087 if (arr_idx
< target_len
) {
3088 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below target_len",
3089 (long) i
, (long) arr_idx
));
3092 DUK_ASSERT(force_flag
|| DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(thr
->heap
, obj
, i
)); /* stage 1 guarantees */
3094 DUK_DDD(DUK_DDDPRINT("delete entry index %ld: key is array index %ld",
3095 (long) i
, (long) arr_idx
));
3098 * Slow delete, but we don't care as we're already in a very slow path.
3099 * The delete always succeeds: key has no exotic behavior, property
3100 * is configurable, and no resize occurs.
3102 rc
= duk_hobject_delprop_raw(thr
, obj
, key
, force_flag
? DUK_DELPROP_FLAG_FORCE
: 0);
3104 DUK_ASSERT(rc
!= 0);
3107 /* stage 3: update length (done by caller), decide return code */
3109 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 3: update length (done by caller)"));
3111 *out_result_len
= target_len
;
3113 if (target_len
== new_len
) {
3114 DUK_DDD(DUK_DDDPRINT("target_len matches new_len, return success"));
3117 DUK_DDD(DUK_DDDPRINT("target_len does not match new_len (some entry prevented "
3118 "full length adjustment), return error"));
3125 /* XXX: is valstack top best place for argument? */
3126 DUK_LOCAL duk_bool_t
duk__handle_put_array_length(duk_hthread
*thr
, duk_hobject
*obj
) {
3127 duk_context
*ctx
= (duk_context
*) thr
;
3129 duk_uint32_t old_len
;
3130 duk_uint32_t new_len
;
3131 duk_uint32_t result_len
;
3135 DUK_DDD(DUK_DDDPRINT("handling a put operation to array 'length' exotic property, "
3137 (duk_tval
*) duk_get_tval(ctx
, -1)));
3139 DUK_ASSERT(thr
!= NULL
);
3140 DUK_ASSERT(ctx
!= NULL
);
3141 DUK_ASSERT(obj
!= NULL
);
3143 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
3145 DUK_ASSERT(duk_is_valid_index(ctx
, -1));
3148 * Get old and new length
3151 old_len
= duk__get_old_array_length(thr
, obj
, &desc
);
3152 duk_dup(ctx
, -1); /* [in_val in_val] */
3153 new_len
= duk__to_new_array_length_checked(thr
); /* -> [in_val] */
3154 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) old_len
, (long) new_len
));
3160 if (!(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
3161 DUK_DDD(DUK_DDDPRINT("length is not writable, fail"));
3166 * New length not lower than old length => no changes needed
3167 * (not even array allocation).
3170 if (new_len
>= old_len
) {
3171 DUK_DDD(DUK_DDDPRINT("new length is higher than old length, just update length, no deletions"));
3173 DUK_ASSERT(desc
.e_idx
>= 0);
3174 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, desc
.e_idx
));
3175 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, desc
.e_idx
);
3176 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
3177 /* no decref needed for a number */
3178 DUK_TVAL_SET_FASTINT_U32(tv
, new_len
);
3179 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
3183 DUK_DDD(DUK_DDDPRINT("new length is lower than old length, probably must delete entries"));
3186 * New length lower than old length => delete elements, then
3189 * Note: even though a bunch of elements have been deleted, the 'desc' is
3190 * still valid as properties haven't been resized (and entries compacted).
3193 rc
= duk__handle_put_array_length_smaller(thr
, obj
, old_len
, new_len
, 0 /*force_flag*/, &result_len
);
3194 DUK_ASSERT(result_len
>= new_len
&& result_len
<= old_len
);
3196 DUK_ASSERT(desc
.e_idx
>= 0);
3197 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, desc
.e_idx
));
3198 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, desc
.e_idx
);
3199 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
3200 /* no decref needed for a number */
3201 DUK_TVAL_SET_FASTINT_U32(tv
, result_len
);
3202 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
3204 /* XXX: shrink array allocation or entries compaction here? */
3210 * PUTPROP: Ecmascript property write.
3212 * Unlike Ecmascript primitive which returns nothing, returns 1 to indicate
3213 * success and 0 to indicate failure (assuming throw is not set).
3215 * This is an extremely tricky function. Some examples:
3217 * * Currently a decref may trigger a GC, which may compact an object's
3218 * property allocation. Consequently, any entry indices (e_idx) will
3219 * be potentially invalidated by a decref.
3221 * * Exotic behaviors (strings, arrays, arguments object) require,
3222 * among other things:
3224 * - Preprocessing before and postprocessing after an actual property
3225 * write. For example, array index write requires pre-checking the
3226 * array 'length' property for access control, and may require an
3227 * array 'length' update after the actual write has succeeded (but
3230 * - Deletion of multiple entries, as a result of array 'length' write.
3232 * * Input values are taken as pointers which may point to the valstack.
3233 * If valstack is resized because of the put (this may happen at least
3234 * when the array part is abandoned), the pointers can be invalidated.
3235 * (We currently make a copy of all of the input values to avoid issues.)
3238 DUK_INTERNAL duk_bool_t
duk_hobject_putprop(duk_hthread
*thr
, duk_tval
*tv_obj
, duk_tval
*tv_key
, duk_tval
*tv_val
, duk_bool_t throw_flag
) {
3239 duk_context
*ctx
= (duk_context
*) thr
;
3240 duk_tval tv_obj_copy
;
3241 duk_tval tv_key_copy
;
3242 duk_tval tv_val_copy
;
3243 duk_hobject
*orig
= NULL
; /* NULL if tv_obj is primitive */
3245 duk_hstring
*key
= NULL
;
3248 duk_uint32_t arr_idx
;
3252 duk_uint32_t new_array_length
= 0; /* 0 = no update */
3254 DUK_DDD(DUK_DDDPRINT("putprop: thr=%p, obj=%p, key=%p, val=%p, throw=%ld "
3255 "(obj -> %!T, key -> %!T, val -> %!T)",
3256 (void *) thr
, (void *) tv_obj
, (void *) tv_key
, (void *) tv_val
,
3257 (long) throw_flag
, (duk_tval
*) tv_obj
, (duk_tval
*) tv_key
, (duk_tval
*) tv_val
));
3259 DUK_ASSERT(thr
!= NULL
);
3260 DUK_ASSERT(thr
->heap
!= NULL
);
3261 DUK_ASSERT(ctx
!= NULL
);
3262 DUK_ASSERT(tv_obj
!= NULL
);
3263 DUK_ASSERT(tv_key
!= NULL
);
3264 DUK_ASSERT(tv_val
!= NULL
);
3266 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
3269 * Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
3270 * them being invalidated by a valstack resize.
3272 * XXX: this is an overkill for some paths, so optimize this later
3273 * (or maybe switch to a stack arguments model entirely).
3276 DUK_TVAL_SET_TVAL(&tv_obj_copy
, tv_obj
);
3277 DUK_TVAL_SET_TVAL(&tv_key_copy
, tv_key
);
3278 DUK_TVAL_SET_TVAL(&tv_val_copy
, tv_val
);
3279 tv_obj
= &tv_obj_copy
;
3280 tv_key
= &tv_key_copy
;
3281 tv_val
= &tv_val_copy
;
3284 * Coercion and fast path processing.
3287 switch (DUK_TVAL_GET_TAG(tv_obj
)) {
3288 case DUK_TAG_UNDEFINED
:
3289 case DUK_TAG_NULL
: {
3290 /* Note: unconditional throw */
3291 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject (object=%!iT)",
3292 (duk_tval
*) tv_obj
));
3293 #if defined(DUK_USE_PARANOID_ERRORS)
3294 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_BASE
);
3296 DUK_ERROR_FMT2(thr
, DUK_ERR_TYPE_ERROR
, "cannot write property %s of %s",
3297 duk_push_string_tval_readable(ctx
, tv_key
), duk_push_string_tval_readable(ctx
, tv_obj
));
3302 case DUK_TAG_BOOLEAN
: {
3303 DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
3304 curr
= thr
->builtins
[DUK_BIDX_BOOLEAN_PROTOTYPE
];
3308 case DUK_TAG_STRING
: {
3309 duk_hstring
*h
= DUK_TVAL_GET_STRING(tv_obj
);
3312 * Note: currently no fast path for array index writes.
3313 * They won't be possible anyway as strings are immutable.
3316 DUK_ASSERT(key
== NULL
);
3317 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3318 DUK_ASSERT(key
!= NULL
);
3320 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
3321 goto fail_not_writable
;
3324 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
3325 arr_idx
< DUK_HSTRING_GET_CHARLEN(h
)) {
3326 goto fail_not_writable
;
3329 DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
3330 curr
= thr
->builtins
[DUK_BIDX_STRING_PROTOTYPE
];
3331 goto lookup
; /* avoid double coercion */
3334 case DUK_TAG_OBJECT
: {
3335 orig
= DUK_TVAL_GET_OBJECT(tv_obj
);
3336 DUK_ASSERT(orig
!= NULL
);
3338 #if defined(DUK_USE_ROM_OBJECTS)
3339 /* With this check in place fast paths won't need read-only
3340 * object checks. This is technically incorrect if there are
3341 * setters that cause no writes to ROM objects, but current
3342 * built-ins don't have such setters.
3344 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) orig
)) {
3345 DUK_DD(DUK_DDPRINT("attempt to putprop on read-only target object"));
3346 goto fail_not_writable_no_pop
; /* Must avoid duk_pop() in exit path */
3350 /* The fast path for array property put is not fully compliant:
3351 * If one places conflicting number-indexed properties into
3352 * Array.prototype (for example, a non-writable Array.prototype[7])
3353 * the fast path will incorrectly ignore them.
3355 * This fast path could be made compliant by falling through
3356 * to the slow path if the previous value was UNUSED. This would
3357 * also remove the need to check for extensibility. Right now a
3358 * non-extensible array is slower than an extensible one as far
3359 * as writes are concerned.
3361 * The fast path behavior is documented in more detail here:
3362 * tests/ecmascript/test-misc-array-fast-write.js
3365 if (duk__putprop_shallow_fastpath_array_tval(thr
, orig
, tv_key
, tv_val
, &desc
) != 0) {
3366 DUK_DDD(DUK_DDDPRINT("array fast path success"));
3370 if (duk__putprop_fastpath_bufobj_tval(thr
, orig
, tv_key
, tv_val
) != 0) {
3371 DUK_DDD(DUK_DDDPRINT("base is bufobj, key is a number, bufferobject fast path"));
3375 #if defined(DUK_USE_ES6_PROXY)
3376 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(orig
))) {
3377 duk_hobject
*h_target
;
3378 duk_bool_t tmp_bool
;
3380 if (duk__proxy_check_prop(thr
, orig
, DUK_STRIDX_SET
, tv_key
, &h_target
)) {
3381 /* -> [ ... trap handler ] */
3382 DUK_DDD(DUK_DDDPRINT("-> proxy object 'set' for key %!T", (duk_tval
*) tv_key
));
3383 duk_push_hobject(ctx
, h_target
); /* target */
3384 duk_push_tval(ctx
, tv_key
); /* P */
3385 duk_push_tval(ctx
, tv_val
); /* V */
3386 duk_push_tval(ctx
, tv_obj
); /* Receiver: Proxy object */
3387 duk_call_method(ctx
, 4 /*nargs*/);
3388 tmp_bool
= duk_to_boolean(ctx
, -1);
3391 goto fail_proxy_rejected
;
3394 /* Target object must be checked for a conflicting
3395 * non-configurable property.
3397 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3398 DUK_ASSERT(key
!= NULL
);
3400 if (duk__get_own_propdesc_raw(thr
, h_target
, key
, arr_idx
, &desc
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
3401 duk_tval
*tv_targ
= duk_require_tval(ctx
, -1);
3402 duk_bool_t datadesc_reject
;
3403 duk_bool_t accdesc_reject
;
3405 DUK_DDD(DUK_DDDPRINT("proxy 'set': target has matching property %!O, check for "
3406 "conflicting property; tv_val=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
3407 "desc.get=%p, desc.set=%p",
3408 (duk_heaphdr
*) key
, (duk_tval
*) tv_val
, (duk_tval
*) tv_targ
,
3409 (unsigned long) desc
.flags
,
3410 (void *) desc
.get
, (void *) desc
.set
));
3412 datadesc_reject
= !(desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) &&
3413 !(desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) &&
3414 !(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) &&
3415 !duk_js_samevalue(tv_val
, tv_targ
);
3416 accdesc_reject
= (desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) &&
3417 !(desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) &&
3419 if (datadesc_reject
|| accdesc_reject
) {
3420 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
3427 return 1; /* success */
3430 orig
= h_target
; /* resume write to target */
3431 DUK_TVAL_SET_OBJECT(tv_obj
, orig
);
3433 #endif /* DUK_USE_ES6_PROXY */
3439 case DUK_TAG_BUFFER
: {
3440 duk_hbuffer
*h
= DUK_TVAL_GET_BUFFER(tv_obj
);
3441 duk_int_t pop_count
= 0;
3444 * Because buffer values may be looped over and read/written
3445 * from, an array index fast path is important.
3448 #if defined(DUK_USE_FASTINT)
3449 if (DUK_TVAL_IS_FASTINT(tv_key
)) {
3450 arr_idx
= duk__tval_fastint_to_arr_idx(tv_key
);
3451 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path fastint; arr_idx %ld", (long) arr_idx
));
3455 if (DUK_TVAL_IS_NUMBER(tv_key
)) {
3456 arr_idx
= duk__tval_number_to_arr_idx(tv_key
);
3457 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx
));
3460 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3461 DUK_ASSERT(key
!= NULL
);
3462 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
3463 "coercion key is %!T, arr_idx %ld",
3464 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
3468 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
3469 arr_idx
< DUK_HBUFFER_GET_SIZE(h
)) {
3471 DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx
));
3472 data
= (duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h
);
3474 /* XXX: duk_to_int() ensures we'll get 8 lowest bits as
3475 * as input is within duk_int_t range (capped outside it).
3477 #if defined(DUK_USE_FASTINT)
3478 /* Buffer writes are often integers. */
3479 if (DUK_TVAL_IS_FASTINT(tv_val
)) {
3480 data
[arr_idx
] = (duk_uint8_t
) DUK_TVAL_GET_FASTINT_U32(tv_val
);
3485 duk_push_tval(ctx
, tv_val
);
3486 data
[arr_idx
] = (duk_uint8_t
) duk_to_uint32(ctx
, -1);
3490 duk_pop_n(ctx
, pop_count
);
3491 DUK_DDD(DUK_DDDPRINT("result: success (buffer data write)"));
3495 if (pop_count
== 0) {
3496 /* This is a pretty awkward control flow, but we need to recheck the
3497 * key coercion here.
3499 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3500 DUK_ASSERT(key
!= NULL
);
3501 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
3502 "coercion key is %!T, arr_idx %ld",
3503 (duk_tval
*) duk_get_tval(ctx
, -1), (long) arr_idx
));
3506 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
) ||
3507 key
== DUK_HTHREAD_STRING_BYTE_LENGTH(thr
) ||
3508 key
== DUK_HTHREAD_STRING_BYTE_OFFSET(thr
) ||
3509 key
== DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr
)) {
3510 goto fail_not_writable
;
3513 DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
3514 curr
= thr
->builtins
[DUK_BIDX_BUFFER_PROTOTYPE
];
3515 goto lookup
; /* avoid double coercion */
3518 case DUK_TAG_POINTER
: {
3519 DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
3520 curr
= thr
->builtins
[DUK_BIDX_POINTER_PROTOTYPE
];
3524 case DUK_TAG_LIGHTFUNC
: {
3525 /* All lightfunc own properties are non-writable and the lightfunc
3526 * is considered non-extensible. However, the write may be captured
3527 * by an inherited setter which means we can't stop the lookup here.
3530 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3532 if (duk__key_is_lightfunc_ownprop(thr
, key
)) {
3533 goto fail_not_writable
;
3536 DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
3537 curr
= thr
->builtins
[DUK_BIDX_FUNCTION_PROTOTYPE
];
3538 goto lookup
; /* avoid double coercion */
3541 #if defined(DUK_USE_FASTINT)
3542 case DUK_TAG_FASTINT
:
3546 DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
3547 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj
));
3548 curr
= thr
->builtins
[DUK_BIDX_NUMBER_PROTOTYPE
];
3553 DUK_ASSERT(key
== NULL
);
3554 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
3555 DUK_ASSERT(key
!= NULL
);
3560 * Check whether the property already exists in the prototype chain.
3561 * Note that the actual write goes into the original base object
3562 * (except if an accessor property captures the write).
3567 DUK_ASSERT(curr
!= NULL
);
3568 sanity
= DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY
;
3570 if (!duk__get_own_propdesc_raw(thr
, curr
, key
, arr_idx
, &desc
, 0 /*flags*/)) { /* don't push value */
3574 if (desc
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
3576 * Found existing accessor property (own or inherited).
3577 * Call setter with 'this' set to orig, and value as the only argument.
3578 * Setter calls are OK even for ROM objects.
3580 * Note: no exotic arguments object behavior, because [[Put]] never
3581 * calls [[DefineOwnProperty]] (E5 Section 8.12.5, step 5.b).
3584 duk_hobject
*setter
;
3586 DUK_DD(DUK_DDPRINT("put to an own or inherited accessor, calling setter"));
3588 setter
= DUK_HOBJECT_E_GET_VALUE_SETTER(thr
->heap
, curr
, desc
.e_idx
);
3590 goto fail_no_setter
;
3592 duk_push_hobject(ctx
, setter
);
3593 duk_push_tval(ctx
, tv_obj
); /* note: original, uncoerced base */
3594 duk_push_tval(ctx
, tv_val
); /* [key setter this val] */
3595 #ifdef DUK_USE_NONSTD_SETTER_KEY_ARGUMENT
3597 duk_call_method(ctx
, 2); /* [key setter this val key] -> [key retval] */
3599 duk_call_method(ctx
, 1); /* [key setter this val] -> [key retval] */
3601 duk_pop(ctx
); /* ignore retval -> [key] */
3602 goto success_no_arguments_exotic
;
3607 * Found existing own or inherited plain property, but original
3608 * base is a primitive value.
3610 DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
3611 goto fail_base_primitive
;
3616 * Found existing inherited plain property.
3617 * Do an access control check, and if OK, write
3618 * new property to 'orig'.
3620 if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig
)) {
3621 DUK_DD(DUK_DDPRINT("found existing inherited plain property, but original object is not extensible"));
3622 goto fail_not_extensible
;
3624 if (!(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
3625 DUK_DD(DUK_DDPRINT("found existing inherited plain property, original object is extensible, but inherited property is not writable"));
3626 goto fail_not_writable
;
3628 DUK_DD(DUK_DDPRINT("put to new property, object extensible, inherited property found and is writable"));
3632 * Found existing own (non-inherited) plain property.
3633 * Do an access control check and update in place.
3636 if (!(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
3637 DUK_DD(DUK_DDPRINT("found existing own (non-inherited) plain property, but property is not writable"));
3638 goto fail_not_writable
;
3640 if (desc
.flags
& DUK_PROPDESC_FLAG_VIRTUAL
) {
3641 DUK_DD(DUK_DDPRINT("found existing own (non-inherited) virtual property, property is writable"));
3642 if (DUK_HOBJECT_IS_BUFFEROBJECT(curr
)) {
3643 duk_hbufferobject
*h_bufobj
;
3644 duk_uint_t byte_off
;
3645 duk_small_uint_t elem_size
;
3647 h_bufobj
= (duk_hbufferobject
*) curr
;
3648 DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj
);
3650 DUK_DD(DUK_DDPRINT("writable virtual property is in buffer object"));
3652 /* Careful with wrapping: arr_idx upshift may easily wrap, whereas
3653 * length downshift won't.
3655 if (arr_idx
< (h_bufobj
->length
>> h_bufobj
->shift
)) {
3657 DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx
));
3659 DUK_ASSERT(arr_idx
!= DUK__NO_ARRAY_INDEX
); /* index/length check guarantees */
3660 byte_off
= arr_idx
<< h_bufobj
->shift
; /* no wrap assuming h_bufobj->length is valid */
3661 elem_size
= 1 << h_bufobj
->shift
;
3663 /* Coerce to number before validating pointers etc so that the
3664 * number coercions in duk_hbufferobject_validated_write() are
3665 * guaranteed to be side effect free and not invalidate the
3666 * pointer checks we do here.
3668 duk_push_tval(ctx
, tv_val
);
3669 duk_to_number(ctx
, -1);
3671 if (h_bufobj
->buf
!= NULL
&& DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj
, byte_off
+ elem_size
)) {
3672 data
= (duk_uint8_t
*) DUK_HBUFFER_GET_DATA_PTR(thr
->heap
, h_bufobj
->buf
) + h_bufobj
->offset
+ byte_off
;
3673 duk_hbufferobject_validated_write(ctx
, h_bufobj
, data
, elem_size
);
3675 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (write skipped)"));
3678 goto success_no_arguments_exotic
;
3682 goto fail_internal
; /* should not happen */
3684 DUK_DD(DUK_DDPRINT("put to existing own plain property, property is writable"));
3690 /* XXX: option to pretend property doesn't exist if sanity limit is
3691 * hit might be useful.
3693 if (sanity
-- == 0) {
3694 DUK_ERROR_RANGE(thr
, DUK_STR_PROTOTYPE_CHAIN_LIMIT
);
3696 curr
= DUK_HOBJECT_GET_PROTOTYPE(thr
->heap
, curr
);
3700 * Property not found in prototype chain.
3703 DUK_DDD(DUK_DDDPRINT("property not found in prototype chain"));
3706 DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
3707 goto fail_base_primitive
;
3710 if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig
)) {
3711 DUK_DD(DUK_DDPRINT("put to a new property (not found in prototype chain), but original object not extensible"));
3712 goto fail_not_extensible
;
3720 * Update an existing property of the base object.
3725 DUK_DDD(DUK_DDDPRINT("update an existing property of the original object"));
3727 DUK_ASSERT(orig
!= NULL
);
3728 #if defined(DUK_USE_ROM_OBJECTS)
3729 /* This should not happen because DUK_TAG_OBJECT case checks
3730 * for this already, but check just in case.
3732 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) orig
)) {
3733 goto fail_not_writable
;
3737 /* Although there are writable virtual properties (e.g. plain buffer
3738 * and buffer object number indices), they are handled before we come
3741 DUK_ASSERT((desc
.flags
& DUK_PROPDESC_FLAG_VIRTUAL
) == 0);
3742 DUK_ASSERT(desc
.a_idx
>= 0 || desc
.e_idx
>= 0);
3744 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig
) &&
3745 key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
3747 * Write to 'length' of an array is a very complex case
3748 * handled in a helper which updates both the array elements
3749 * and writes the new 'length'. The write may result in an
3750 * unconditional RangeError or a partial write (indicated
3751 * by a return code).
3753 * Note: the helper has an unnecessary writability check
3754 * for 'length', we already know it is writable.
3757 DUK_DDD(DUK_DDDPRINT("writing existing 'length' property to array exotic, invoke complex helper"));
3759 /* XXX: the helper currently assumes stack top contains new
3760 * 'length' value and the whole calling convention is not very
3761 * compatible with what we need.
3764 duk_push_tval(ctx
, tv_val
); /* [key val] */
3765 rc
= duk__handle_put_array_length(thr
, orig
);
3766 duk_pop(ctx
); /* [key val] -> [key] */
3768 goto fail_array_length_partial
;
3771 /* key is 'length', cannot match argument exotic behavior */
3772 goto success_no_arguments_exotic
;
3775 if (desc
.e_idx
>= 0) {
3776 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, orig
, desc
.e_idx
);
3777 DUK_DDD(DUK_DDDPRINT("previous entry value: %!iT", (duk_tval
*) tv
));
3778 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv
, tv_val
); /* side effects */
3779 /* don't touch property attributes or hash part */
3780 DUK_DD(DUK_DDPRINT("put to an existing entry at index %ld -> new value %!iT",
3781 (long) desc
.e_idx
, (duk_tval
*) tv
));
3783 /* Note: array entries are always writable, so the writability check
3784 * above is pointless for them. The check could be avoided with some
3785 * refactoring but is probably not worth it.
3788 DUK_ASSERT(desc
.a_idx
>= 0);
3789 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, orig
, desc
.a_idx
);
3790 DUK_DDD(DUK_DDDPRINT("previous array value: %!iT", (duk_tval
*) tv
));
3791 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv
, tv_val
); /* side effects */
3792 DUK_DD(DUK_DDPRINT("put to an existing array entry at index %ld -> new value %!iT",
3793 (long) desc
.a_idx
, (duk_tval
*) tv
));
3796 /* Regardless of whether property is found in entry or array part,
3797 * it may have arguments exotic behavior (array indices may reside
3798 * in entry part for abandoned / non-existent array parts).
3800 goto success_with_arguments_exotic
;
3805 * Create a new property in the original object.
3807 * Exotic properties need to be reconsidered here from a write
3808 * perspective (not just property attributes perspective).
3809 * However, the property does not exist in the object already,
3810 * so this limits the kind of exotic properties that apply.
3815 DUK_DDD(DUK_DDDPRINT("create new property to original object"));
3817 DUK_ASSERT(orig
!= NULL
);
3819 #if defined(DUK_USE_ROM_OBJECTS)
3820 /* This should not happen because DUK_TAG_OBJECT case checks
3821 * for this already, but check just in case.
3823 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) orig
)) {
3824 goto fail_not_writable
;
3828 /* Not possible because array object 'length' is present
3829 * from its creation and cannot be deleted, and is thus
3830 * caught as an existing property above.
3832 DUK_ASSERT(!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig
) &&
3833 key
== DUK_HTHREAD_STRING_LENGTH(thr
)));
3835 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig
) &&
3836 arr_idx
!= DUK__NO_ARRAY_INDEX
) {
3837 /* automatic length update */
3838 duk_uint32_t old_len
;
3840 old_len
= duk__get_old_array_length(thr
, orig
, &desc
);
3842 if (arr_idx
>= old_len
) {
3843 DUK_DDD(DUK_DDDPRINT("write new array entry requires length update "
3844 "(arr_idx=%ld, old_len=%ld)",
3845 (long) arr_idx
, (long) old_len
));
3847 if (!(desc
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
3848 DUK_DD(DUK_DDPRINT("attempt to extend array, but array 'length' is not writable"));
3849 goto fail_not_writable
;
3852 /* Note: actual update happens once write has been completed
3853 * without error below. The write should always succeed
3854 * from a specification viewpoint, but we may e.g. run out
3855 * of memory. It's safer in this order.
3858 DUK_ASSERT(arr_idx
!= 0xffffffffUL
);
3859 new_array_length
= arr_idx
+ 1; /* flag for later write */
3861 DUK_DDD(DUK_DDDPRINT("write new array entry does not require length update "
3862 "(arr_idx=%ld, old_len=%ld)",
3863 (long) arr_idx
, (long) old_len
));
3867 /* write_to_array_part: */
3870 * Write to array part?
3872 * Note: array abandonding requires a property resize which uses
3873 * 'rechecks' valstack for temporaries and may cause any existing
3874 * valstack pointers to be invalidated. To protect against this,
3875 * tv_obj, tv_key, and tv_val are copies of the original inputs.
3878 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
3879 DUK_HOBJECT_HAS_ARRAY_PART(orig
)) {
3880 if (arr_idx
< DUK_HOBJECT_GET_ASIZE(orig
)) {
3881 goto no_array_growth
;
3885 * Array needs to grow, but we don't want it becoming too sparse.
3886 * If it were to become sparse, abandon array part, moving all
3887 * array entries into the entries part (for good).
3889 * Since we don't keep track of actual density (used vs. size) of
3890 * the array part, we need to estimate somehow. The check is made
3893 * - Check whether the resize need is small compared to the
3894 * current size (relatively); if so, resize without further
3895 * checking (essentially we assume that the original part is
3896 * "dense" so that the result would be dense enough).
3898 * - Otherwise, compute the resize using an actual density
3899 * measurement based on counting the used array entries.
3902 DUK_DDD(DUK_DDDPRINT("write to new array requires array resize, decide whether to do a "
3903 "fast resize without abandon check (arr_idx=%ld, old_size=%ld)",
3904 (long) arr_idx
, (long) DUK_HOBJECT_GET_ASIZE(orig
)));
3906 if (duk__abandon_array_slow_check_required(arr_idx
, DUK_HOBJECT_GET_ASIZE(orig
))) {
3907 duk_uint32_t old_used
;
3908 duk_uint32_t old_size
;
3910 DUK_DDD(DUK_DDDPRINT("=> fast check is NOT OK, do slow check for array abandon"));
3912 duk__compute_a_stats(thr
, orig
, &old_used
, &old_size
);
3914 DUK_DDD(DUK_DDDPRINT("abandon check, array stats: old_used=%ld, old_size=%ld, arr_idx=%ld",
3915 (long) old_used
, (long) old_size
, (long) arr_idx
));
3917 /* Note: intentionally use approximations to shave a few instructions:
3918 * a_used = old_used (accurate: old_used + 1)
3919 * a_size = arr_idx (accurate: arr_idx + 1)
3921 if (duk__abandon_array_density_check(old_used
, arr_idx
)) {
3922 DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
3923 "decided to abandon array part (would become too sparse)"));
3925 /* abandoning requires a props allocation resize and
3926 * 'rechecks' the valstack, invalidating any existing
3927 * valstack value pointers!
3929 duk__abandon_array_checked(thr
, orig
);
3930 DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig
));
3932 goto write_to_entry_part
;
3935 DUK_DDD(DUK_DDDPRINT("=> decided to keep array part"));
3937 DUK_DDD(DUK_DDDPRINT("=> fast resize is OK"));
3940 DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
3941 "decided to extend current allocation"));
3943 duk__grow_props_for_array_item(thr
, orig
, arr_idx
);
3947 /* Note: assume array part is comprehensive, so that either
3948 * the write goes to the array part, or we've abandoned the
3949 * array above (and will not come here).
3952 DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(orig
));
3953 DUK_ASSERT(arr_idx
< DUK_HOBJECT_GET_ASIZE(orig
));
3955 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, orig
, arr_idx
);
3956 /* prev value must be unused, no decref */
3957 DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv
));
3958 DUK_TVAL_SET_TVAL(tv
, tv_val
);
3959 DUK_TVAL_INCREF(thr
, tv
);
3960 DUK_DD(DUK_DDPRINT("put to new array entry: %ld -> %!T",
3961 (long) arr_idx
, (duk_tval
*) tv
));
3963 /* Note: array part values are [[Writable]], [[Enumerable]],
3964 * and [[Configurable]] which matches the required attributes
3970 write_to_entry_part
:
3973 * Write to entry part
3976 /* entry allocation updates hash part and increases the key
3977 * refcount; may need a props allocation resize but doesn't
3978 * 'recheck' the valstack.
3980 e_idx
= duk__alloc_entry_checked(thr
, orig
, key
);
3981 DUK_ASSERT(e_idx
>= 0);
3983 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, orig
, e_idx
);
3984 /* prev value can be garbage, no decref */
3985 DUK_TVAL_SET_TVAL(tv
, tv_val
);
3986 DUK_TVAL_INCREF(thr
, tv
);
3987 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, orig
, e_idx
, DUK_PROPDESC_FLAGS_WEC
);
3993 * Possible pending array length update, which must only be done
3994 * if the actual entry write succeeded.
3997 if (new_array_length
> 0) {
3999 * Note: zero works as a "no update" marker because the new length
4000 * can never be zero after a new property is written.
4002 * Note: must re-lookup because calls above (e.g. duk__alloc_entry_checked())
4003 * may realloc and compact properties and hence change e_idx.
4006 DUK_DDD(DUK_DDDPRINT("write successful, pending array length update to: %ld",
4007 (long) new_array_length
));
4009 rc
= duk__get_own_propdesc_raw(thr
, orig
, DUK_HTHREAD_STRING_LENGTH(thr
), DUK__NO_ARRAY_INDEX
, &desc
, 0 /*flags*/); /* don't push value */
4011 DUK_ASSERT(rc
!= 0);
4012 DUK_ASSERT(desc
.e_idx
>= 0);
4014 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, orig
, desc
.e_idx
);
4015 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
4016 /* no need for decref/incref because value is a number */
4017 DUK_TVAL_SET_FASTINT_U32(tv
, new_array_length
);
4021 * Arguments exotic behavior not possible for new properties: all
4022 * magically bound properties are initially present in the arguments
4023 * object, and if they are deleted, the binding is also removed from
4027 goto success_no_arguments_exotic
;
4029 success_with_arguments_exotic
:
4032 * Arguments objects have exotic [[DefineOwnProperty]] which updates
4033 * the internal 'map' of arguments for writes to currently mapped
4034 * arguments. More conretely, writes to mapped arguments generate
4035 * a write to a bound variable.
4037 * The [[Put]] algorithm invokes [[DefineOwnProperty]] for existing
4038 * data properties and new properties, but not for existing accessors.
4039 * Hence, in E5 Section 10.6 ([[DefinedOwnProperty]] algorithm), we
4040 * have a Desc with 'Value' (and possibly other properties too), and
4041 * we end up in step 5.b.i.
4044 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
4045 DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig
)) {
4046 /* Note: only numbered indices are relevant, so arr_idx fast reject
4047 * is good (this is valid unless there are more than 4**32-1 arguments).
4050 DUK_DDD(DUK_DDDPRINT("putprop successful, arguments exotic behavior needed"));
4052 /* Note: we can reuse 'desc' here */
4054 /* XXX: top of stack must contain value, which helper doesn't touch,
4055 * rework to use tv_val directly?
4058 duk_push_tval(ctx
, tv_val
);
4059 (void) duk__check_arguments_map_for_put(thr
, orig
, key
, &desc
, throw_flag
);
4064 success_no_arguments_exotic
:
4065 /* shared exit path now */
4066 DUK_DDD(DUK_DDDPRINT("result: success"));
4067 duk_pop(ctx
); /* remove key */
4070 #if defined(DUK_USE_ES6_PROXY)
4071 fail_proxy_rejected
:
4072 DUK_DDD(DUK_DDDPRINT("result: error, proxy rejects"));
4074 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
4076 /* Note: no key on stack */
4080 fail_base_primitive
:
4081 DUK_DDD(DUK_DDDPRINT("result: error, base primitive"));
4083 #if defined(DUK_USE_PARANOID_ERRORS)
4084 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_BASE
);
4086 DUK_ERROR_FMT2(thr
, DUK_ERR_TYPE_ERROR
, "cannot write property %s of %s",
4087 duk_push_string_tval_readable(ctx
, tv_key
), duk_push_string_tval_readable(ctx
, tv_obj
));
4090 duk_pop(ctx
); /* remove key */
4093 fail_not_extensible
:
4094 DUK_DDD(DUK_DDDPRINT("result: error, not extensible"));
4096 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_EXTENSIBLE
);
4098 duk_pop(ctx
); /* remove key */
4102 DUK_DDD(DUK_DDDPRINT("result: error, not writable"));
4104 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_WRITABLE
);
4106 duk_pop(ctx
); /* remove key */
4109 #if defined(DUK_USE_ROM_OBJECTS)
4110 fail_not_writable_no_pop
:
4111 DUK_DDD(DUK_DDDPRINT("result: error, not writable"));
4113 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_WRITABLE
);
4118 fail_array_length_partial
:
4119 DUK_DDD(DUK_DDDPRINT("result: error, array length write only partially successful"));
4121 DUK_ERROR_TYPE(thr
, DUK_STR_ARRAY_LENGTH_WRITE_FAILED
);
4123 duk_pop(ctx
); /* remove key */
4127 DUK_DDD(DUK_DDDPRINT("result: error, accessor property without setter"));
4129 DUK_ERROR_TYPE(thr
, DUK_STR_SETTER_UNDEFINED
);
4131 duk_pop(ctx
); /* remove key */
4135 DUK_DDD(DUK_DDDPRINT("result: error, internal"));
4137 DUK_ERROR_INTERNAL_DEFMSG(thr
);
4139 duk_pop(ctx
); /* remove key */
4144 * Ecmascript compliant [[Delete]](P, Throw).
4147 DUK_INTERNAL duk_bool_t
duk_hobject_delprop_raw(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_small_uint_t flags
) {
4150 duk_uint32_t arr_idx
;
4151 duk_bool_t throw_flag
;
4152 duk_bool_t force_flag
;
4154 throw_flag
= (flags
& DUK_DELPROP_FLAG_THROW
);
4155 force_flag
= (flags
& DUK_DELPROP_FLAG_FORCE
);
4157 DUK_DDD(DUK_DDDPRINT("delprop_raw: thr=%p, obj=%p, key=%p, throw=%ld, force=%ld (obj -> %!O, key -> %!O)",
4158 (void *) thr
, (void *) obj
, (void *) key
, (long) throw_flag
, (long) force_flag
,
4159 (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
));
4161 DUK_ASSERT(thr
!= NULL
);
4162 DUK_ASSERT(thr
->heap
!= NULL
);
4163 DUK_ASSERT(obj
!= NULL
);
4164 DUK_ASSERT(key
!= NULL
);
4166 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
4168 arr_idx
= DUK_HSTRING_GET_ARRIDX_FAST(key
);
4170 /* 0 = don't push current value */
4171 if (!duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &desc
, 0 /*flags*/)) { /* don't push value */
4172 DUK_DDD(DUK_DDDPRINT("property not found, succeed always"));
4176 #if defined(DUK_USE_ROM_OBJECTS)
4177 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)) {
4178 DUK_DD(DUK_DDPRINT("attempt to delprop on read-only target object"));
4179 goto fail_not_configurable
;
4183 if ((desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) == 0 && !force_flag
) {
4184 goto fail_not_configurable
;
4186 if (desc
.a_idx
< 0 && desc
.e_idx
< 0) {
4187 /* Currently there are no deletable virtual properties, but
4188 * with force_flag we might attempt to delete one.
4193 if (desc
.a_idx
>= 0) {
4194 DUK_ASSERT(desc
.e_idx
< 0);
4196 tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, desc
.a_idx
);
4197 DUK_TVAL_SET_UNUSED_UPDREF(thr
, tv
); /* side effects */
4200 duk_hobject
*h_get
= NULL
;
4201 duk_hobject
*h_set
= NULL
;
4204 DUK_ASSERT(desc
.a_idx
< 0);
4206 /* Set property slot to an empty state. Careful not to invoke
4207 * any side effects while using desc.e_idx so that it doesn't
4208 * get invalidated by a finalizer mutating our object.
4211 /* remove hash entry (no decref) */
4212 #if defined(DUK_USE_HOBJECT_HASH_PART)
4213 if (desc
.h_idx
>= 0) {
4214 duk_uint32_t
*h_base
= DUK_HOBJECT_H_GET_BASE(thr
->heap
, obj
);
4216 DUK_DDD(DUK_DDDPRINT("removing hash entry at h_idx %ld", (long) desc
.h_idx
));
4217 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj
) > 0);
4218 DUK_ASSERT((duk_uint32_t
) desc
.h_idx
< DUK_HOBJECT_GET_HSIZE(obj
));
4219 h_base
[desc
.h_idx
] = DUK__HASH_DELETED
;
4221 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj
) == 0);
4224 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj
) == 0);
4228 DUK_DDD(DUK_DDDPRINT("before removing value, e_idx %ld, key %p, key at slot %p",
4229 (long) desc
.e_idx
, (void *) key
, (void *) DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, desc
.e_idx
)));
4230 DUK_DDD(DUK_DDDPRINT("removing value at e_idx %ld", (long) desc
.e_idx
));
4231 DUK_MEMSET((void *) &tv_tmp
, 0, sizeof(tv_tmp
));
4232 DUK_TVAL_SET_UNDEFINED(&tv_tmp
);
4233 if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, desc
.e_idx
)) {
4234 h_get
= DUK_HOBJECT_E_GET_VALUE_GETTER(thr
->heap
, obj
, desc
.e_idx
);
4235 h_set
= DUK_HOBJECT_E_GET_VALUE_SETTER(thr
->heap
, obj
, desc
.e_idx
);
4236 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, desc
.e_idx
, NULL
);
4237 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, desc
.e_idx
, NULL
);
4239 tv
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, desc
.e_idx
);
4240 DUK_TVAL_SET_TVAL(&tv_tmp
, tv
);
4241 DUK_TVAL_SET_UNDEFINED(tv
);
4244 /* Not strictly necessary because if key == NULL, flag MUST be ignored. */
4245 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, desc
.e_idx
, 0);
4249 DUK_DDD(DUK_DDDPRINT("before removing key, e_idx %ld, key %p, key at slot %p",
4250 (long) desc
.e_idx
, (void *) key
, (void *) DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, desc
.e_idx
)));
4251 DUK_DDD(DUK_DDDPRINT("removing key at e_idx %ld", (long) desc
.e_idx
));
4252 DUK_ASSERT(key
== DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, desc
.e_idx
));
4253 DUK_HOBJECT_E_SET_KEY(thr
->heap
, obj
, desc
.e_idx
, NULL
);
4255 /* Do decrefs only with safe pointers to avoid side effects
4258 DUK_TVAL_DECREF(thr
, &tv_tmp
);
4259 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, h_get
);
4260 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, h_set
);
4261 DUK_HSTRING_DECREF(thr
, key
);
4269 * Argument exotic [[Delete]] behavior (E5 Section 10.6) is
4270 * a post-check, keeping arguments internal 'map' in sync with
4271 * any successful deletes (note that property does not need to
4272 * exist for delete to 'succeed').
4274 * Delete key from 'map'. Since 'map' only contains array index
4275 * keys, we can use arr_idx for a fast skip.
4278 DUK_DDD(DUK_DDDPRINT("delete successful, check for arguments exotic behavior"));
4280 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&& DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
)) {
4281 /* Note: only numbered indices are relevant, so arr_idx fast reject
4282 * is good (this is valid unless there are more than 4**32-1 arguments).
4285 DUK_DDD(DUK_DDDPRINT("delete successful, arguments exotic behavior needed"));
4287 /* Note: we can reuse 'desc' here */
4288 (void) duk__check_arguments_map_for_delete(thr
, obj
, key
, &desc
);
4291 DUK_DDD(DUK_DDDPRINT("delete successful"));
4295 DUK_DDD(DUK_DDDPRINT("delete failed: property found, force flag, but virtual"));
4298 DUK_ERROR_TYPE(thr
, DUK_STR_PROPERTY_IS_VIRTUAL
);
4302 fail_not_configurable
:
4303 DUK_DDD(DUK_DDDPRINT("delete failed: property found, not configurable"));
4306 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_CONFIGURABLE
);
4312 * DELPROP: Ecmascript property deletion.
4315 DUK_INTERNAL duk_bool_t
duk_hobject_delprop(duk_hthread
*thr
, duk_tval
*tv_obj
, duk_tval
*tv_key
, duk_bool_t throw_flag
) {
4316 duk_context
*ctx
= (duk_context
*) thr
;
4317 duk_hstring
*key
= NULL
;
4318 #if defined(DUK_USE_ES6_PROXY)
4321 duk_int_t entry_top
;
4322 duk_uint32_t arr_idx
= DUK__NO_ARRAY_INDEX
;
4325 DUK_DDD(DUK_DDDPRINT("delprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
4326 (void *) thr
, (void *) tv_obj
, (void *) tv_key
,
4327 (duk_tval
*) tv_obj
, (duk_tval
*) tv_key
));
4329 DUK_ASSERT(ctx
!= NULL
);
4330 DUK_ASSERT(thr
!= NULL
);
4331 DUK_ASSERT(thr
->heap
!= NULL
);
4332 DUK_ASSERT(tv_obj
!= NULL
);
4333 DUK_ASSERT(tv_key
!= NULL
);
4335 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
4337 /* Storing the entry top is cheaper here to ensure stack is correct at exit,
4338 * as there are several paths out.
4340 entry_top
= duk_get_top(ctx
);
4342 if (DUK_TVAL_IS_UNDEFINED(tv_obj
) ||
4343 DUK_TVAL_IS_NULL(tv_obj
)) {
4344 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
4345 goto fail_invalid_base_uncond
;
4348 duk_push_tval(ctx
, tv_obj
);
4349 duk_push_tval(ctx
, tv_key
);
4351 tv_obj
= DUK_GET_TVAL_NEGIDX(ctx
, -2);
4352 if (DUK_TVAL_IS_OBJECT(tv_obj
)) {
4353 duk_hobject
*obj
= DUK_TVAL_GET_OBJECT(tv_obj
);
4354 DUK_ASSERT(obj
!= NULL
);
4356 #if defined(DUK_USE_ES6_PROXY)
4357 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj
))) {
4358 duk_hobject
*h_target
;
4359 duk_bool_t tmp_bool
;
4361 /* Note: proxy handling must happen before key is string coerced. */
4363 if (duk__proxy_check_prop(thr
, obj
, DUK_STRIDX_DELETE_PROPERTY
, tv_key
, &h_target
)) {
4364 /* -> [ ... trap handler ] */
4365 DUK_DDD(DUK_DDDPRINT("-> proxy object 'deleteProperty' for key %!T", (duk_tval
*) tv_key
));
4366 duk_push_hobject(ctx
, h_target
); /* target */
4367 duk_push_tval(ctx
, tv_key
); /* P */
4368 duk_call_method(ctx
, 2 /*nargs*/);
4369 tmp_bool
= duk_to_boolean(ctx
, -1);
4372 goto fail_proxy_rejected
; /* retval indicates delete failed */
4375 /* Target object must be checked for a conflicting
4376 * non-configurable property.
4378 arr_idx
= duk__push_tval_to_hstring_arr_idx(ctx
, tv_key
, &key
);
4379 DUK_ASSERT(key
!= NULL
);
4381 if (duk__get_own_propdesc_raw(thr
, h_target
, key
, arr_idx
, &desc
, 0 /*flags*/)) { /* don't push value */
4384 DUK_DDD(DUK_DDDPRINT("proxy 'deleteProperty': target has matching property %!O, check for "
4385 "conflicting property; desc.flags=0x%08lx, "
4386 "desc.get=%p, desc.set=%p",
4387 (duk_heaphdr
*) key
, (unsigned long) desc
.flags
,
4388 (void *) desc
.get
, (void *) desc
.set
));
4390 desc_reject
= !(desc
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
);
4393 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
4396 rc
= 1; /* success */
4400 obj
= h_target
; /* resume delete to target */
4402 #endif /* DUK_USE_ES6_PROXY */
4404 duk_to_string(ctx
, -1);
4405 key
= duk_get_hstring(ctx
, -1);
4406 DUK_ASSERT(key
!= NULL
);
4408 rc
= duk_hobject_delprop_raw(thr
, obj
, key
, throw_flag
? DUK_DELPROP_FLAG_THROW
: 0);
4410 } else if (DUK_TVAL_IS_STRING(tv_obj
)) {
4411 /* XXX: unnecessary string coercion for array indices,
4412 * intentional to keep small.
4414 duk_hstring
*h
= DUK_TVAL_GET_STRING(tv_obj
);
4415 DUK_ASSERT(h
!= NULL
);
4417 duk_to_string(ctx
, -1);
4418 key
= duk_get_hstring(ctx
, -1);
4419 DUK_ASSERT(key
!= NULL
);
4421 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
4422 goto fail_not_configurable
;
4425 arr_idx
= DUK_HSTRING_GET_ARRIDX_FAST(key
);
4427 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
4428 arr_idx
< DUK_HSTRING_GET_CHARLEN(h
)) {
4429 goto fail_not_configurable
;
4431 } else if (DUK_TVAL_IS_BUFFER(tv_obj
)) {
4432 /* XXX: unnecessary string coercion for array indices,
4433 * intentional to keep small; some overlap with string
4436 duk_hbuffer
*h
= DUK_TVAL_GET_BUFFER(tv_obj
);
4437 DUK_ASSERT(h
!= NULL
);
4439 duk_to_string(ctx
, -1);
4440 key
= duk_get_hstring(ctx
, -1);
4441 DUK_ASSERT(key
!= NULL
);
4443 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
4444 goto fail_not_configurable
;
4447 arr_idx
= DUK_HSTRING_GET_ARRIDX_FAST(key
);
4449 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&&
4450 arr_idx
< DUK_HBUFFER_GET_SIZE(h
)) {
4451 goto fail_not_configurable
;
4453 } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj
)) {
4454 /* Lightfunc virtual properties are non-configurable, so
4455 * reject if match any of them.
4458 duk_to_string(ctx
, -1);
4459 key
= duk_get_hstring(ctx
, -1);
4460 DUK_ASSERT(key
!= NULL
);
4462 if (duk__key_is_lightfunc_ownprop(thr
, key
)) {
4463 goto fail_not_configurable
;
4467 /* non-object base, no offending virtual property */
4472 duk_set_top(ctx
, entry_top
);
4475 fail_invalid_base_uncond
:
4476 /* Note: unconditional throw */
4477 DUK_ASSERT(duk_get_top(ctx
) == entry_top
);
4478 #if defined(DUK_USE_PARANOID_ERRORS)
4479 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_BASE
);
4481 DUK_ERROR_FMT2(thr
, DUK_ERR_TYPE_ERROR
, "cannot delete property %s of %s",
4482 duk_push_string_tval_readable(ctx
, tv_key
), duk_push_string_tval_readable(ctx
, tv_obj
));
4486 #if defined(DUK_USE_ES6_PROXY)
4487 fail_proxy_rejected
:
4489 DUK_ERROR_TYPE(thr
, DUK_STR_PROXY_REJECTED
);
4491 duk_set_top(ctx
, entry_top
);
4495 fail_not_configurable
:
4497 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_CONFIGURABLE
);
4499 duk_set_top(ctx
, entry_top
);
4504 * Internal helper to define a property with specific flags, ignoring
4505 * normal semantics such as extensibility, write protection etc.
4506 * Overwrites any existing value and attributes unless caller requests
4507 * that value only be updated if it doesn't already exists.
4510 * - virtual properties (error if write attempted)
4511 * - getter/setter properties (error if write attempted)
4512 * - non-default (!= WEC) attributes for array entries (error if attempted)
4513 * - array abandoning: if array part exists, it is always extended
4514 * - array 'length' updating
4516 * Stack: [... in_val] -> []
4518 * Used for e.g. built-in initialization and environment record
4522 DUK_INTERNAL
void duk_hobject_define_property_internal(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_small_uint_t flags
) {
4523 duk_context
*ctx
= (duk_context
*) thr
;
4525 duk_uint32_t arr_idx
;
4527 duk_tval
*tv1
= NULL
;
4528 duk_tval
*tv2
= NULL
;
4529 duk_small_uint_t propflags
= flags
& DUK_PROPDESC_FLAGS_MASK
; /* mask out flags not actually stored */
4531 DUK_DDD(DUK_DDDPRINT("define new property (internal): thr=%p, obj=%!O, key=%!O, flags=0x%02lx, val=%!T",
4532 (void *) thr
, (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
,
4533 (unsigned long) flags
, (duk_tval
*) duk_get_tval(ctx
, -1)));
4535 DUK_ASSERT(thr
!= NULL
);
4536 DUK_ASSERT(thr
->heap
!= NULL
);
4537 DUK_ASSERT(obj
!= NULL
);
4538 DUK_ASSERT(key
!= NULL
);
4539 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
));
4540 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
4541 DUK_ASSERT(duk_is_valid_index(ctx
, -1)); /* contains value */
4543 arr_idx
= DUK_HSTRING_GET_ARRIDX_SLOW(key
);
4545 if (duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &desc
, 0 /*flags*/)) { /* don't push value */
4546 if (desc
.e_idx
>= 0) {
4547 if (flags
& DUK_PROPDESC_FLAG_NO_OVERWRITE
) {
4548 DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> skip as requested"));
4551 DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> update value and attributes"));
4552 if (DUK_UNLIKELY(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, desc
.e_idx
))) {
4553 DUK_D(DUK_DPRINT("existing property is an accessor, not supported"));
4554 goto error_internal
;
4557 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, desc
.e_idx
, propflags
);
4558 tv1
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, desc
.e_idx
);
4559 } else if (desc
.a_idx
>= 0) {
4560 if (flags
& DUK_PROPDESC_FLAG_NO_OVERWRITE
) {
4561 DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> skip as requested"));
4564 DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> update value (assert attributes)"));
4565 if (propflags
!= DUK_PROPDESC_FLAGS_WEC
) {
4566 DUK_D(DUK_DPRINT("existing property in array part, but propflags not WEC (0x%02lx)",
4567 (unsigned long) propflags
));
4568 goto error_internal
;
4571 tv1
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, desc
.a_idx
);
4573 if (flags
& DUK_PROPDESC_FLAG_NO_OVERWRITE
) {
4574 DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> skip as requested"));
4577 DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> failure"));
4584 if (DUK_HOBJECT_HAS_ARRAY_PART(obj
)) {
4585 if (arr_idx
!= DUK__NO_ARRAY_INDEX
) {
4586 DUK_DDD(DUK_DDDPRINT("property does not exist, object has array part -> possibly extend array part and write value (assert attributes)"));
4587 DUK_ASSERT(propflags
== DUK_PROPDESC_FLAGS_WEC
);
4589 /* always grow the array, no sparse / abandon support here */
4590 if (arr_idx
>= DUK_HOBJECT_GET_ASIZE(obj
)) {
4591 duk__grow_props_for_array_item(thr
, obj
, arr_idx
);
4594 DUK_ASSERT(arr_idx
< DUK_HOBJECT_GET_ASIZE(obj
));
4595 tv1
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, arr_idx
);
4600 DUK_DDD(DUK_DDDPRINT("property does not exist, object belongs in entry part -> allocate new entry and write value and attributes"));
4601 e_idx
= duk__alloc_entry_checked(thr
, obj
, key
); /* increases key refcount */
4602 DUK_ASSERT(e_idx
>= 0);
4603 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, e_idx
, propflags
);
4604 tv1
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, e_idx
);
4605 /* new entry: previous value is garbage; set to undefined to share write_value */
4606 DUK_TVAL_SET_UNDEFINED(tv1
);
4610 /* tv1 points to value storage */
4612 tv2
= duk_require_tval(ctx
, -1); /* late lookup, avoid side effects */
4613 DUK_DDD(DUK_DDDPRINT("writing/updating value: %!T -> %!T",
4614 (duk_tval
*) tv1
, (duk_tval
*) tv2
));
4616 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv1
, tv2
); /* side effects */
4620 duk_pop(ctx
); /* remove in_val */
4624 DUK_ERROR_INTERNAL_DEFMSG(thr
);
4628 DUK_ERROR_TYPE(thr
, DUK_STR_REDEFINE_VIRT_PROP
);
4633 * Fast path for defining array indexed values without interning the key.
4634 * This is used by e.g. code for Array prototype and traceback creation so
4635 * must avoid interning.
4638 DUK_INTERNAL
void duk_hobject_define_property_internal_arridx(duk_hthread
*thr
, duk_hobject
*obj
, duk_uarridx_t arr_idx
, duk_small_uint_t flags
) {
4639 duk_context
*ctx
= (duk_context
*) thr
;
4641 duk_tval
*tv1
, *tv2
;
4643 DUK_DDD(DUK_DDDPRINT("define new property (internal) arr_idx fast path: thr=%p, obj=%!O, "
4644 "arr_idx=%ld, flags=0x%02lx, val=%!T",
4645 (void *) thr
, obj
, (long) arr_idx
, (unsigned long) flags
,
4646 (duk_tval
*) duk_get_tval(ctx
, -1)));
4648 DUK_ASSERT(thr
!= NULL
);
4649 DUK_ASSERT(thr
->heap
!= NULL
);
4650 DUK_ASSERT(obj
!= NULL
);
4651 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
));
4653 if (DUK_HOBJECT_HAS_ARRAY_PART(obj
) &&
4654 arr_idx
!= DUK__NO_ARRAY_INDEX
&&
4655 flags
== DUK_PROPDESC_FLAGS_WEC
) {
4656 DUK_ASSERT((flags
& DUK_PROPDESC_FLAG_NO_OVERWRITE
) == 0); /* covered by comparison */
4658 DUK_DDD(DUK_DDDPRINT("define property to array part (property may or may not exist yet)"));
4660 /* always grow the array, no sparse / abandon support here */
4661 if (arr_idx
>= DUK_HOBJECT_GET_ASIZE(obj
)) {
4662 duk__grow_props_for_array_item(thr
, obj
, arr_idx
);
4665 DUK_ASSERT(arr_idx
< DUK_HOBJECT_GET_ASIZE(obj
));
4666 tv1
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, arr_idx
);
4667 tv2
= duk_require_tval(ctx
, -1);
4669 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv1
, tv2
); /* side effects */
4671 duk_pop(ctx
); /* [ ...val ] -> [ ... ] */
4675 DUK_DDD(DUK_DDDPRINT("define property fast path didn't work, use slow path"));
4677 duk_push_uint(ctx
, (duk_uint_t
) arr_idx
);
4678 key
= duk_to_hstring(ctx
, -1);
4679 DUK_ASSERT(key
!= NULL
);
4680 duk_insert(ctx
, -2); /* [ ... val key ] -> [ ... key val ] */
4682 duk_hobject_define_property_internal(thr
, obj
, key
, flags
);
4684 duk_pop(ctx
); /* [ ... key ] -> [ ... ] */
4688 * Internal helper for defining an accessor property, ignoring
4689 * normal semantics such as extensibility, write protection etc.
4690 * Overwrites any existing value and attributes. This is called
4691 * very rarely, so the implementation first sets a value to undefined
4692 * and then changes the entry to an accessor (this is to save code space).
4695 DUK_INTERNAL
void duk_hobject_define_accessor_internal(duk_hthread
*thr
, duk_hobject
*obj
, duk_hstring
*key
, duk_hobject
*getter
, duk_hobject
*setter
, duk_small_uint_t propflags
) {
4696 duk_context
*ctx
= (duk_context
*) thr
;
4700 DUK_DDD(DUK_DDDPRINT("define new accessor (internal): thr=%p, obj=%!O, key=%!O, "
4701 "getter=%!O, setter=%!O, flags=0x%02lx",
4702 (void *) thr
, (duk_heaphdr
*) obj
, (duk_heaphdr
*) key
,
4703 (duk_heaphdr
*) getter
, (duk_heaphdr
*) setter
,
4704 (unsigned long) propflags
));
4706 DUK_ASSERT(thr
!= NULL
);
4707 DUK_ASSERT(thr
->heap
!= NULL
);
4708 DUK_ASSERT(obj
!= NULL
);
4709 DUK_ASSERT(key
!= NULL
);
4710 DUK_ASSERT((propflags
& ~DUK_PROPDESC_FLAGS_MASK
) == 0);
4711 /* setter and/or getter may be NULL */
4712 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
));
4714 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
4716 /* force the property to 'undefined' to create a slot for it */
4717 duk_push_undefined(ctx
);
4718 duk_hobject_define_property_internal(thr
, obj
, key
, propflags
);
4719 duk_hobject_find_existing_entry(thr
->heap
, obj
, key
, &e_idx
, &h_idx
);
4720 DUK_DDD(DUK_DDDPRINT("accessor slot: e_idx=%ld, h_idx=%ld", (long) e_idx
, (long) h_idx
));
4721 DUK_ASSERT(e_idx
>= 0);
4722 DUK_ASSERT((duk_uint32_t
) e_idx
< DUK_HOBJECT_GET_ENEXT(obj
));
4724 /* no need to decref, as previous value is 'undefined' */
4725 DUK_HOBJECT_E_SLOT_SET_ACCESSOR(thr
->heap
, obj
, e_idx
);
4726 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, e_idx
, getter
);
4727 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, e_idx
, setter
);
4728 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, getter
);
4729 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, setter
);
4733 * Internal helpers for managing object 'length'
4736 /* XXX: awkward helpers */
4738 DUK_INTERNAL
void duk_hobject_set_length(duk_hthread
*thr
, duk_hobject
*obj
, duk_uint32_t length
) {
4739 duk_context
*ctx
= (duk_context
*) thr
;
4740 duk_push_hobject(ctx
, obj
);
4741 duk_push_hstring_stridx(ctx
, DUK_STRIDX_LENGTH
);
4742 duk_push_u32(ctx
, length
);
4743 (void) duk_hobject_putprop(thr
,
4744 DUK_GET_TVAL_NEGIDX(ctx
, -3),
4745 DUK_GET_TVAL_NEGIDX(ctx
, -2),
4746 DUK_GET_TVAL_NEGIDX(ctx
, -1),
4751 DUK_INTERNAL
void duk_hobject_set_length_zero(duk_hthread
*thr
, duk_hobject
*obj
) {
4752 duk_hobject_set_length(thr
, obj
, 0);
4755 DUK_INTERNAL duk_uint32_t
duk_hobject_get_length(duk_hthread
*thr
, duk_hobject
*obj
) {
4756 duk_context
*ctx
= (duk_context
*) thr
;
4758 duk_push_hobject(ctx
, obj
);
4759 duk_push_hstring_stridx(ctx
, DUK_STRIDX_LENGTH
);
4760 (void) duk_hobject_getprop(thr
,
4761 DUK_GET_TVAL_NEGIDX(ctx
, -2),
4762 DUK_GET_TVAL_NEGIDX(ctx
, -1));
4763 val
= duk_to_number(ctx
, -1);
4765 if (val
>= 0.0 && val
< DUK_DOUBLE_2TO32
) {
4766 return (duk_uint32_t
) val
;
4772 * Object.getOwnPropertyDescriptor() (E5 Sections 15.2.3.3, 8.10.4)
4774 * This is an actual function call.
4777 DUK_INTERNAL duk_ret_t
duk_hobject_object_get_own_property_descriptor(duk_context
*ctx
) {
4778 duk_hthread
*thr
= (duk_hthread
*) ctx
;
4784 DUK_ASSERT(ctx
!= NULL
);
4785 DUK_ASSERT(thr
!= NULL
);
4786 DUK_ASSERT(thr
->heap
!= NULL
);
4788 obj
= duk_require_hobject_or_lfunc_coerce(ctx
, 0);
4789 (void) duk_to_string(ctx
, 1);
4790 key
= duk_require_hstring(ctx
, 1);
4792 DUK_ASSERT(obj
!= NULL
);
4793 DUK_ASSERT(key
!= NULL
);
4795 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
4797 rc
= duk_hobject_get_own_propdesc(thr
, obj
, key
, &pd
, DUK_GETDESC_FLAG_PUSH_VALUE
);
4799 duk_push_undefined(ctx
);
4801 /* [obj key undefined] */
4805 duk_push_object(ctx
);
4807 /* [obj key value desc] */
4809 if (DUK_PROPDESC_IS_ACCESSOR(&pd
)) {
4810 /* If a setter/getter is missing (undefined), the descriptor must
4811 * still have the property present with the value 'undefined'.
4814 duk_push_hobject(ctx
, pd
.get
);
4816 duk_push_undefined(ctx
);
4818 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_GET
);
4820 duk_push_hobject(ctx
, pd
.set
);
4822 duk_push_undefined(ctx
);
4824 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_SET
);
4826 duk_dup(ctx
, -2); /* [obj key value desc value] */
4827 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_VALUE
);
4828 duk_push_boolean(ctx
, DUK_PROPDESC_IS_WRITABLE(&pd
));
4829 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_WRITABLE
);
4831 /* [obj key value desc] */
4833 duk_push_boolean(ctx
, DUK_PROPDESC_IS_ENUMERABLE(&pd
));
4834 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_ENUMERABLE
);
4835 duk_push_boolean(ctx
, DUK_PROPDESC_IS_CONFIGURABLE(&pd
));
4836 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_CONFIGURABLE
);
4838 /* [obj key value desc] */
4843 * NormalizePropertyDescriptor() related helper.
4845 * Internal helper which validates and normalizes a property descriptor
4846 * represented as an Ecmascript object (e.g. argument to defineProperty()).
4847 * The output of this conversion is a set of defprop_flags and possibly
4848 * some values pushed on the value stack; some subset of: property value,
4849 * getter, setter. Caller must manage stack top carefully because the
4850 * number of values pushed depends on the input property descriptor.
4852 * The original descriptor object must not be altered in the process.
4855 /* XXX: very basic optimization -> duk_get_prop_stridx_top */
4858 void duk_hobject_prepare_property_descriptor(duk_context
*ctx
,
4860 duk_uint_t
*out_defprop_flags
,
4861 duk_idx_t
*out_idx_value
,
4862 duk_hobject
**out_getter
,
4863 duk_hobject
**out_setter
) {
4864 duk_hthread
*thr
= (duk_hthread
*) ctx
;
4865 duk_idx_t idx_value
= -1;
4866 duk_hobject
*getter
= NULL
;
4867 duk_hobject
*setter
= NULL
;
4868 duk_bool_t is_data_desc
= 0;
4869 duk_bool_t is_acc_desc
= 0;
4870 duk_uint_t defprop_flags
= 0;
4872 DUK_ASSERT(ctx
!= NULL
);
4873 DUK_ASSERT(out_defprop_flags
!= NULL
);
4874 DUK_ASSERT(out_idx_value
!= NULL
);
4875 DUK_ASSERT(out_getter
!= NULL
);
4876 DUK_ASSERT(out_setter
!= NULL
);
4878 /* Must be an object, otherwise TypeError (E5.1 Section 8.10.5, step 1). */
4879 idx_in
= duk_require_normalize_index(ctx
, idx_in
);
4880 (void) duk_require_hobject(ctx
, idx_in
);
4882 /* The coercion order must match the ToPropertyDescriptor() algorithm
4883 * so that side effects in coercion happen in the correct order.
4884 * (This order also happens to be compatible with duk_def_prop(),
4885 * although it doesn't matter in practice.)
4888 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_VALUE
)) {
4890 defprop_flags
|= DUK_DEFPROP_HAVE_VALUE
;
4891 idx_value
= duk_get_top_index(ctx
);
4892 /* Leave 'value' on stack */
4897 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_WRITABLE
)) {
4899 if (duk_to_boolean(ctx
, -1)) {
4900 defprop_flags
|= DUK_DEFPROP_HAVE_WRITABLE
| DUK_DEFPROP_WRITABLE
;
4902 defprop_flags
|= DUK_DEFPROP_HAVE_WRITABLE
;
4907 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_GET
)) {
4908 duk_tval
*tv
= duk_require_tval(ctx
, -1);
4911 if (DUK_TVAL_IS_UNDEFINED(tv
)) {
4912 /* undefined is accepted */
4913 DUK_ASSERT(getter
== NULL
);
4915 /* NOTE: lightfuncs are coerced to full functions because
4916 * lightfuncs don't fit into a property value slot. This
4917 * has some side effects, see test-dev-lightfunc-accessor.js.
4919 h_get
= duk_get_hobject_or_lfunc_coerce(ctx
, -1);
4920 if (h_get
== NULL
|| !DUK_HOBJECT_IS_CALLABLE(h_get
)) {
4926 defprop_flags
|= DUK_DEFPROP_HAVE_GETTER
;
4927 /* Leave 'getter' on stack */
4932 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_SET
)) {
4933 duk_tval
*tv
= duk_require_tval(ctx
, -1);
4936 if (DUK_TVAL_IS_UNDEFINED(tv
)) {
4937 /* undefined is accepted */
4938 DUK_ASSERT(setter
== NULL
);
4940 /* NOTE: lightfuncs are coerced to full functions because
4941 * lightfuncs don't fit into a property value slot. This
4942 * has some side effects, see test-dev-lightfunc-accessor.js.
4944 h_set
= duk_get_hobject_or_lfunc_coerce(ctx
, -1);
4945 if (h_set
== NULL
|| !DUK_HOBJECT_IS_CALLABLE(h_set
)) {
4951 defprop_flags
|= DUK_DEFPROP_HAVE_SETTER
;
4952 /* Leave 'setter' on stack */
4957 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_ENUMERABLE
)) {
4958 if (duk_to_boolean(ctx
, -1)) {
4959 defprop_flags
|= DUK_DEFPROP_HAVE_ENUMERABLE
| DUK_DEFPROP_ENUMERABLE
;
4961 defprop_flags
|= DUK_DEFPROP_HAVE_ENUMERABLE
;
4966 if (duk_get_prop_stridx(ctx
, idx_in
, DUK_STRIDX_CONFIGURABLE
)) {
4967 if (duk_to_boolean(ctx
, -1)) {
4968 defprop_flags
|= DUK_DEFPROP_HAVE_CONFIGURABLE
| DUK_DEFPROP_CONFIGURABLE
;
4970 defprop_flags
|= DUK_DEFPROP_HAVE_CONFIGURABLE
;
4975 if (is_data_desc
&& is_acc_desc
) {
4979 *out_defprop_flags
= defprop_flags
;
4980 *out_idx_value
= idx_value
;
4981 *out_getter
= getter
;
4982 *out_setter
= setter
;
4984 /* [ ... value? getter? setter? ] */
4988 DUK_ERROR_TYPE(thr
, DUK_STR_INVALID_DESCRIPTOR
);
4992 * Object.defineProperty() related helper (E5 Section 15.2.3.6)
4994 * Inlines all [[DefineOwnProperty]] exotic behaviors.
4996 * Note: Ecmascript compliant [[DefineOwnProperty]](P, Desc, Throw) is not
4997 * implemented directly, but Object.defineProperty() serves its purpose.
4998 * We don't need the [[DefineOwnProperty]] internally and we don't have a
4999 * property descriptor with 'missing values' so it's easier to avoid it
5002 * Note: this is only called for actual objects, not primitive values.
5003 * This must support virtual properties for full objects (e.g. Strings)
5004 * but not for plain values (e.g. strings). Lightfuncs, even though
5005 * primitive in a sense, are treated like objects and accepted as target
5009 /* XXX: this is a major target for size optimization */
5011 void duk_hobject_define_property_helper(duk_context
*ctx
,
5012 duk_uint_t defprop_flags
,
5015 duk_idx_t idx_value
,
5018 duk_hthread
*thr
= (duk_hthread
*) ctx
;
5019 duk_uint32_t arr_idx
;
5021 duk_bool_t has_enumerable
;
5022 duk_bool_t has_configurable
;
5023 duk_bool_t has_writable
;
5024 duk_bool_t has_value
;
5027 duk_bool_t is_enumerable
;
5028 duk_bool_t is_configurable
;
5029 duk_bool_t is_writable
;
5030 duk_bool_t throw_flag
;
5031 duk_bool_t force_flag
;
5032 duk_small_uint_t new_flags
;
5034 duk_uint32_t arridx_new_array_length
; /* != 0 => post-update for array 'length' (used when key is an array index) */
5035 duk_uint32_t arrlen_old_len
;
5036 duk_uint32_t arrlen_new_len
;
5037 duk_bool_t pending_write_protect
;
5039 DUK_ASSERT(thr
!= NULL
);
5040 DUK_ASSERT(thr
->heap
!= NULL
);
5041 DUK_ASSERT(ctx
!= NULL
);
5042 DUK_ASSERT(obj
!= NULL
);
5043 DUK_ASSERT(key
!= NULL
);
5044 /* idx_value may be < 0 (no value), set and get may be NULL */
5046 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
5048 /* All the flags fit in 16 bits, so will fit into duk_bool_t. */
5050 has_writable
= (defprop_flags
& DUK_DEFPROP_HAVE_WRITABLE
);
5051 has_enumerable
= (defprop_flags
& DUK_DEFPROP_HAVE_ENUMERABLE
);
5052 has_configurable
= (defprop_flags
& DUK_DEFPROP_HAVE_CONFIGURABLE
);
5053 has_value
= (defprop_flags
& DUK_DEFPROP_HAVE_VALUE
);
5054 has_get
= (defprop_flags
& DUK_DEFPROP_HAVE_GETTER
);
5055 has_set
= (defprop_flags
& DUK_DEFPROP_HAVE_SETTER
);
5056 is_writable
= (defprop_flags
& DUK_DEFPROP_WRITABLE
);
5057 is_enumerable
= (defprop_flags
& DUK_DEFPROP_ENUMERABLE
);
5058 is_configurable
= (defprop_flags
& DUK_DEFPROP_CONFIGURABLE
);
5059 throw_flag
= 1; /* Object.defineProperty() calls [[DefineOwnProperty]] with Throw=true */
5060 force_flag
= (defprop_flags
& DUK_DEFPROP_FORCE
);
5062 arr_idx
= DUK_HSTRING_GET_ARRIDX_SLOW(key
);
5064 arridx_new_array_length
= 0;
5065 pending_write_protect
= 0;
5069 DUK_DDD(DUK_DDDPRINT("has_enumerable=%ld is_enumerable=%ld "
5070 "has_configurable=%ld is_configurable=%ld "
5071 "has_writable=%ld is_writable=%ld "
5072 "has_value=%ld value=%!T "
5073 "has_get=%ld get=%p=%!O "
5074 "has_set=%ld set=%p=%!O "
5076 (long) has_enumerable
, (long) is_enumerable
,
5077 (long) has_configurable
, (long) is_configurable
,
5078 (long) has_writable
, (long) is_writable
,
5079 (long) has_value
, (duk_tval
*) (idx_value
>= 0 ? duk_get_tval(ctx
, idx_value
) : NULL
),
5080 (long) has_get
, (void *) get
, (duk_heaphdr
*) get
,
5081 (long) has_set
, (void *) set
, (duk_heaphdr
*) set
,
5085 * Array exotic behaviors can be implemented at this point. The local variables
5086 * are essentially a 'value copy' of the input descriptor (Desc), which is modified
5087 * by the Array [[DefineOwnProperty]] (E5 Section 15.4.5.1).
5090 if (!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj
)) {
5091 goto skip_array_exotic
;
5094 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
)) {
5095 /* E5 Section 15.4.5.1, step 3, steps a - i are implemented here, j - n at the end */
5097 DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', but no value in descriptor -> normal behavior"));
5098 goto skip_array_exotic
;
5101 DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', value present in descriptor -> exotic behavior"));
5104 * Get old and new length
5107 /* Note: reuse 'curr' as a temp propdesc */
5108 arrlen_old_len
= duk__get_old_array_length(thr
, obj
, &curr
);
5110 duk_dup(ctx
, idx_value
);
5111 arrlen_new_len
= duk__to_new_array_length_checked(thr
);
5112 duk_push_u32(ctx
, arrlen_new_len
);
5113 duk_replace(ctx
, idx_value
); /* step 3.e: replace 'Desc.[[Value]]' */
5115 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) arrlen_old_len
, (long) arrlen_new_len
));
5117 if (arrlen_new_len
>= arrlen_old_len
) {
5118 /* standard behavior, step 3.f.i */
5119 DUK_DDD(DUK_DDDPRINT("new length is same or higher as previous => standard behavior"));
5120 goto skip_array_exotic
;
5122 DUK_DDD(DUK_DDDPRINT("new length is smaller than previous => exotic post behavior"));
5124 /* XXX: consolidated algorithm step 15.f -> redundant? */
5125 if (!(curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) && !force_flag
) {
5126 /* Note: 'curr' refers to 'length' propdesc */
5127 goto fail_not_writable_array_length
;
5130 /* steps 3.h and 3.i */
5131 if (has_writable
&& !is_writable
) {
5132 DUK_DDD(DUK_DDDPRINT("desc writable is false, force it back to true, and flag pending write protect"));
5134 pending_write_protect
= 1;
5137 /* remaining actual steps are carried out if standard DefineOwnProperty succeeds */
5138 } else if (arr_idx
!= DUK__NO_ARRAY_INDEX
) {
5139 /* XXX: any chance of unifying this with the 'length' key handling? */
5141 /* E5 Section 15.4.5.1, step 4 */
5142 duk_uint32_t old_len
;
5144 /* Note: use 'curr' as a temp propdesc */
5145 old_len
= duk__get_old_array_length(thr
, obj
, &curr
);
5147 if (arr_idx
>= old_len
) {
5148 DUK_DDD(DUK_DDDPRINT("defineProperty requires array length update "
5149 "(arr_idx=%ld, old_len=%ld)",
5150 (long) arr_idx
, (long) old_len
));
5152 if (!(curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
5153 /* Note: 'curr' refers to 'length' propdesc */
5154 goto fail_not_writable_array_length
;
5157 /* actual update happens once write has been completed without
5160 DUK_ASSERT(arr_idx
!= 0xffffffffUL
);
5161 arridx_new_array_length
= arr_idx
+ 1;
5163 DUK_DDD(DUK_DDDPRINT("defineProperty does not require length update "
5164 "(arr_idx=%ld, old_len=%ld) -> standard behavior",
5165 (long) arr_idx
, (long) old_len
));
5170 /* XXX: There is currently no support for writing buffer object
5171 * indexed elements here. Attempt to do so will succeed and
5172 * write a concrete property into the buffer object. This should
5173 * be fixed at some point but because buffers are a custom feature
5174 * anyway, this is relatively unimportant.
5178 * Actual Object.defineProperty() default algorithm.
5182 * First check whether property exists; if not, simple case. This covers
5186 if (!duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &curr
, DUK_GETDESC_FLAG_PUSH_VALUE
)) {
5187 DUK_DDD(DUK_DDDPRINT("property does not exist"));
5189 if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj
) && !force_flag
) {
5190 goto fail_not_extensible
;
5193 /* XXX: share final setting code for value and flags? difficult because
5194 * refcount code is different. Share entry allocation? But can't allocate
5195 * until array index checked.
5198 /* steps 4.a and 4.b are tricky */
5199 if (has_set
|| has_get
) {
5202 DUK_DDD(DUK_DDDPRINT("create new accessor property"));
5204 DUK_ASSERT(has_set
|| set
== NULL
);
5205 DUK_ASSERT(has_get
|| get
== NULL
);
5206 DUK_ASSERT(!has_value
);
5207 DUK_ASSERT(!has_writable
);
5209 new_flags
= DUK_PROPDESC_FLAG_ACCESSOR
; /* defaults, E5 Section 8.6.1, Table 7 */
5210 if (has_enumerable
&& is_enumerable
) {
5211 new_flags
|= DUK_PROPDESC_FLAG_ENUMERABLE
;
5213 if (has_configurable
&& is_configurable
) {
5214 new_flags
|= DUK_PROPDESC_FLAG_CONFIGURABLE
;
5217 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&& DUK_HOBJECT_HAS_ARRAY_PART(obj
)) {
5218 DUK_DDD(DUK_DDDPRINT("accessor cannot go to array part, abandon array"));
5219 duk__abandon_array_checked(thr
, obj
);
5222 /* write to entry part */
5223 e_idx
= duk__alloc_entry_checked(thr
, obj
, key
);
5224 DUK_ASSERT(e_idx
>= 0);
5226 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, e_idx
, get
);
5227 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, e_idx
, set
);
5228 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, get
);
5229 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, set
);
5231 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, e_idx
, new_flags
);
5232 goto success_exotics
;
5237 DUK_DDD(DUK_DDDPRINT("create new data property"));
5239 DUK_ASSERT(!has_set
);
5240 DUK_ASSERT(!has_get
);
5242 new_flags
= 0; /* defaults, E5 Section 8.6.1, Table 7 */
5243 if (has_writable
&& is_writable
) {
5244 new_flags
|= DUK_PROPDESC_FLAG_WRITABLE
;
5246 if (has_enumerable
&& is_enumerable
) {
5247 new_flags
|= DUK_PROPDESC_FLAG_ENUMERABLE
;
5249 if (has_configurable
&& is_configurable
) {
5250 new_flags
|= DUK_PROPDESC_FLAG_CONFIGURABLE
;
5253 duk_tval
*tv_tmp
= duk_require_tval(ctx
, idx_value
);
5254 DUK_TVAL_SET_TVAL(&tv
, tv_tmp
);
5256 DUK_TVAL_SET_UNDEFINED(&tv
); /* default value */
5259 if (arr_idx
!= DUK__NO_ARRAY_INDEX
&& DUK_HOBJECT_HAS_ARRAY_PART(obj
)) {
5260 if (new_flags
== DUK_PROPDESC_FLAGS_WEC
) {
5262 DUK_DDD(DUK_DDDPRINT("new data property attributes match array defaults, attempt to write to array part"));
5263 /* may become sparse...*/
5265 /* XXX: handling for array part missing now; this doesn't affect
5266 * compliance but causes array entry writes using defineProperty()
5267 * to always abandon array part.
5270 DUK_DDD(DUK_DDDPRINT("new data property cannot go to array part, abandon array"));
5271 duk__abandon_array_checked(thr
, obj
);
5275 /* write to entry part */
5276 e_idx
= duk__alloc_entry_checked(thr
, obj
, key
);
5277 DUK_ASSERT(e_idx
>= 0);
5278 tv2
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, e_idx
);
5279 DUK_TVAL_SET_TVAL(tv2
, &tv
);
5280 DUK_TVAL_INCREF(thr
, tv2
);
5282 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, e_idx
, new_flags
);
5283 goto success_exotics
;
5288 /* we currently assume virtual properties are not configurable (as none of them are) */
5289 DUK_ASSERT((curr
.e_idx
>= 0 || curr
.a_idx
>= 0) || !(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
));
5291 /* [obj key desc value get set curr_value] */
5294 * Property already exists. Steps 5-6 detect whether any changes need
5298 if (has_enumerable
) {
5299 if (is_enumerable
) {
5300 if (!(curr
.flags
& DUK_PROPDESC_FLAG_ENUMERABLE
)) {
5304 if (curr
.flags
& DUK_PROPDESC_FLAG_ENUMERABLE
) {
5309 if (has_configurable
) {
5310 if (is_configurable
) {
5311 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
)) {
5315 if (curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) {
5324 /* attempt to change from accessor to data property */
5325 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5329 tmp1
= duk_require_tval(ctx
, -1); /* curr value */
5330 tmp2
= duk_require_tval(ctx
, idx_value
); /* new value */
5331 if (!duk_js_samevalue(tmp1
, tmp2
)) {
5336 /* attempt to change from accessor to data property */
5337 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5342 if (!(curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
5346 if (curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) {
5352 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5353 if (set
!= curr
.set
) {
5361 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5362 if (get
!= curr
.get
) {
5370 /* property exists, either 'desc' is empty, or all values
5373 goto success_no_exotics
;
5378 * Some change(s) need to be made. Steps 7-11.
5381 /* shared checks for all descriptor types */
5382 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && !force_flag
) {
5383 if (has_configurable
&& is_configurable
) {
5384 goto fail_not_configurable
;
5386 if (has_enumerable
) {
5387 if (curr
.flags
& DUK_PROPDESC_FLAG_ENUMERABLE
) {
5388 if (!is_enumerable
) {
5389 goto fail_not_configurable
;
5392 if (is_enumerable
) {
5393 goto fail_not_configurable
;
5399 /* Reject attempt to change virtual properties: not part of the
5400 * standard algorithm, applies currently to e.g. virtual index
5401 * properties of buffer objects (which are virtual but writable).
5402 * (Cannot "force" modification of a virtual property.)
5404 if (curr
.flags
& DUK_PROPDESC_FLAG_VIRTUAL
) {
5408 /* Reject attempt to change a read-only object. */
5409 #if defined(DUK_USE_ROM_OBJECTS)
5410 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)) {
5411 DUK_DD(DUK_DDPRINT("attempt to define property on read-only target object"));
5412 goto fail_not_configurable
;
5416 /* descriptor type specific checks */
5417 if (has_set
|| has_get
) {
5418 /* IsAccessorDescriptor(desc) == true */
5419 DUK_ASSERT(!has_writable
);
5420 DUK_ASSERT(!has_value
);
5422 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5423 /* curr and desc are accessors */
5424 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && !force_flag
) {
5425 if (has_set
&& set
!= curr
.set
) {
5426 goto fail_not_configurable
;
5428 if (has_get
&& get
!= curr
.get
) {
5429 goto fail_not_configurable
;
5437 /* curr is data, desc is accessor */
5438 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && !force_flag
) {
5439 goto fail_not_configurable
;
5442 DUK_DDD(DUK_DDDPRINT("convert property to accessor property"));
5443 if (curr
.a_idx
>= 0) {
5444 DUK_DDD(DUK_DDDPRINT("property to convert is stored in an array entry, abandon array and re-lookup"));
5445 duk__abandon_array_checked(thr
, obj
);
5446 duk_pop(ctx
); /* remove old value */
5447 rc
= duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &curr
, DUK_GETDESC_FLAG_PUSH_VALUE
);
5449 DUK_ASSERT(rc
!= 0);
5450 DUK_ASSERT(curr
.e_idx
>= 0 && curr
.a_idx
< 0);
5453 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5455 /* Avoid side effects that might disturb curr.e_idx until
5456 * we're done editing the slot.
5458 tv1
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, curr
.e_idx
);
5459 DUK_TVAL_SET_TVAL(&tv_tmp
, tv1
);
5460 DUK_TVAL_SET_UNDEFINED(tv1
);
5462 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, curr
.e_idx
, NULL
);
5463 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, curr
.e_idx
, NULL
);
5464 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr
->heap
, obj
, curr
.e_idx
);
5465 DUK_HOBJECT_E_SLOT_SET_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
);
5467 DUK_DDD(DUK_DDDPRINT("flags after data->accessor conversion: 0x%02lx",
5468 (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr
->heap
, obj
, curr
.e_idx
)));
5470 DUK_TVAL_DECREF(thr
, &tv_tmp
); /* side effects */
5472 /* re-lookup to update curr.flags
5473 * XXX: would be faster to update directly
5475 duk_pop(ctx
); /* remove old value */
5476 rc
= duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &curr
, DUK_GETDESC_FLAG_PUSH_VALUE
);
5478 DUK_ASSERT(rc
!= 0);
5480 } else if (has_value
|| has_writable
) {
5481 /* IsDataDescriptor(desc) == true */
5482 DUK_ASSERT(!has_set
);
5483 DUK_ASSERT(!has_get
);
5485 if (curr
.flags
& DUK_PROPDESC_FLAG_ACCESSOR
) {
5490 /* curr is accessor, desc is data */
5491 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && !force_flag
) {
5492 goto fail_not_configurable
;
5495 /* curr is accessor -> cannot be in array part */
5496 DUK_ASSERT(curr
.e_idx
>= 0 && curr
.a_idx
< 0);
5498 DUK_DDD(DUK_DDDPRINT("convert property to data property"));
5500 /* Avoid side effects that might disturb curr.e_idx until
5501 * we're done editing the slot.
5503 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5504 h_get
= DUK_HOBJECT_E_GET_VALUE_GETTER(thr
->heap
, obj
, curr
.e_idx
);
5505 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, curr
.e_idx
, NULL
);
5506 h_set
= DUK_HOBJECT_E_GET_VALUE_SETTER(thr
->heap
, obj
, curr
.e_idx
);
5507 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, curr
.e_idx
, NULL
);
5509 DUK_TVAL_SET_UNDEFINED(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, curr
.e_idx
));
5510 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr
->heap
, obj
, curr
.e_idx
);
5511 DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
);
5513 DUK_DDD(DUK_DDDPRINT("flags after accessor->data conversion: 0x%02lx",
5514 (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr
->heap
, obj
, curr
.e_idx
)));
5516 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, h_get
); /* side effects */
5517 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, h_set
); /* side effects */
5519 /* re-lookup to update curr.flags
5520 * XXX: would be faster to update directly
5522 duk_pop(ctx
); /* remove old value */
5523 rc
= duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &curr
, DUK_GETDESC_FLAG_PUSH_VALUE
);
5525 DUK_ASSERT(rc
!= 0);
5527 /* curr and desc are data */
5528 if (!(curr
.flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) && !force_flag
) {
5529 if (!(curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) && has_writable
&& is_writable
) {
5530 goto fail_not_configurable
;
5532 /* Note: changing from writable to non-writable is OK */
5533 if (!(curr
.flags
& DUK_PROPDESC_FLAG_WRITABLE
) && has_value
) {
5534 duk_tval
*tmp1
= duk_require_tval(ctx
, -1); /* curr value */
5535 duk_tval
*tmp2
= duk_require_tval(ctx
, idx_value
); /* new value */
5536 if (!duk_js_samevalue(tmp1
, tmp2
)) {
5537 goto fail_not_configurable
;
5543 /* IsGenericDescriptor(desc) == true; this means in practice that 'desc'
5544 * only has [[Enumerable]] or [[Configurable]] flag updates, which are
5545 * allowed at this point.
5548 DUK_ASSERT(!has_value
&& !has_writable
&& !has_get
&& !has_set
);
5552 * Start doing property attributes updates. Steps 12-13.
5554 * Start by computing new attribute flags without writing yet.
5555 * Property type conversion is done above if necessary.
5558 new_flags
= curr
.flags
;
5560 if (has_enumerable
) {
5561 if (is_enumerable
) {
5562 new_flags
|= DUK_PROPDESC_FLAG_ENUMERABLE
;
5564 new_flags
&= ~DUK_PROPDESC_FLAG_ENUMERABLE
;
5567 if (has_configurable
) {
5568 if (is_configurable
) {
5569 new_flags
|= DUK_PROPDESC_FLAG_CONFIGURABLE
;
5571 new_flags
&= ~DUK_PROPDESC_FLAG_CONFIGURABLE
;
5576 new_flags
|= DUK_PROPDESC_FLAG_WRITABLE
;
5578 new_flags
&= ~DUK_PROPDESC_FLAG_WRITABLE
;
5582 /* XXX: write protect after flag? -> any chance of handling it here? */
5584 DUK_DDD(DUK_DDDPRINT("new flags that we want to write: 0x%02lx",
5585 (unsigned long) new_flags
));
5588 * Check whether we need to abandon an array part (if it exists)
5591 if (curr
.a_idx
>= 0) {
5594 DUK_ASSERT(curr
.e_idx
< 0);
5596 if (new_flags
== DUK_PROPDESC_FLAGS_WEC
) {
5597 duk_tval
*tv1
, *tv2
;
5599 DUK_DDD(DUK_DDDPRINT("array index, new property attributes match array defaults, update in-place"));
5601 DUK_ASSERT(curr
.flags
== DUK_PROPDESC_FLAGS_WEC
); /* must have been, since in array part */
5602 DUK_ASSERT(!has_set
);
5603 DUK_ASSERT(!has_get
);
5605 tv2
= duk_require_tval(ctx
, idx_value
);
5606 tv1
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, curr
.a_idx
);
5607 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv1
, tv2
); /* side effects */
5608 goto success_exotics
;
5611 DUK_DDD(DUK_DDDPRINT("array index, new property attributes do not match array defaults, abandon array and re-lookup"));
5612 duk__abandon_array_checked(thr
, obj
);
5613 duk_pop(ctx
); /* remove old value */
5614 rc
= duk__get_own_propdesc_raw(thr
, obj
, key
, arr_idx
, &curr
, DUK_GETDESC_FLAG_PUSH_VALUE
);
5616 DUK_ASSERT(rc
!= 0);
5617 DUK_ASSERT(curr
.e_idx
>= 0 && curr
.a_idx
< 0);
5620 DUK_DDD(DUK_DDDPRINT("updating existing property in entry part"));
5622 /* array case is handled comprehensively above */
5623 DUK_ASSERT(curr
.e_idx
>= 0 && curr
.a_idx
< 0);
5625 DUK_DDD(DUK_DDDPRINT("update existing property attributes"));
5626 DUK_HOBJECT_E_SET_FLAGS(thr
->heap
, obj
, curr
.e_idx
, new_flags
);
5631 DUK_DDD(DUK_DDDPRINT("update existing property setter"));
5632 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5634 tmp
= DUK_HOBJECT_E_GET_VALUE_SETTER(thr
->heap
, obj
, curr
.e_idx
);
5636 DUK_HOBJECT_E_SET_VALUE_SETTER(thr
->heap
, obj
, curr
.e_idx
, set
);
5637 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, set
);
5638 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, tmp
); /* side effects */
5643 DUK_DDD(DUK_DDDPRINT("update existing property getter"));
5644 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5646 tmp
= DUK_HOBJECT_E_GET_VALUE_GETTER(thr
->heap
, obj
, curr
.e_idx
);
5648 DUK_HOBJECT_E_SET_VALUE_GETTER(thr
->heap
, obj
, curr
.e_idx
, get
);
5649 DUK_HOBJECT_INCREF_ALLOWNULL(thr
, get
);
5650 DUK_HOBJECT_DECREF_ALLOWNULL(thr
, tmp
); /* side effects */
5653 duk_tval
*tv1
, *tv2
;
5655 DUK_DDD(DUK_DDDPRINT("update existing property value"));
5656 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5658 tv2
= duk_require_tval(ctx
, idx_value
);
5659 tv1
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, curr
.e_idx
);
5660 DUK_TVAL_SET_TVAL_UPDREF(thr
, tv1
, tv2
); /* side effects */
5664 * Standard algorithm succeeded without errors, check for exotic post-behaviors.
5666 * Arguments exotic behavior in E5 Section 10.6 occurs after the standard
5667 * [[DefineOwnProperty]] has completed successfully.
5669 * Array exotic behavior in E5 Section 15.4.5.1 is implemented partly
5670 * prior to the default [[DefineOwnProperty]], but:
5671 * - for an array index key (e.g. "10") the final 'length' update occurs here
5672 * - for 'length' key the element deletion and 'length' update occurs here
5677 /* [obj key desc value get set curr_value] */
5679 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj
)) {
5680 if (arridx_new_array_length
> 0) {
5685 * Note: zero works as a "no update" marker because the new length
5686 * can never be zero after a new property is written.
5689 /* E5 Section 15.4.5.1, steps 4.e.i - 4.e.ii */
5691 DUK_DDD(DUK_DDDPRINT("defineProperty successful, pending array length update to: %ld",
5692 (long) arridx_new_array_length
));
5694 /* Note: reuse 'curr' */
5695 rc
= duk__get_own_propdesc_raw(thr
, obj
, DUK_HTHREAD_STRING_LENGTH(thr
), DUK__NO_ARRAY_INDEX
, &curr
, 0 /*flags*/); /* don't push value */
5697 DUK_ASSERT(rc
!= 0);
5698 DUK_ASSERT(curr
.e_idx
>= 0);
5700 tmp
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, curr
.e_idx
);
5701 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp
));
5702 /* no need for decref/incref because value is a number */
5703 DUK_TVAL_SET_FASTINT_U32(tmp
, arridx_new_array_length
);
5705 if (key
== DUK_HTHREAD_STRING_LENGTH(thr
) && arrlen_new_len
< arrlen_old_len
) {
5707 * E5 Section 15.4.5.1, steps 3.k - 3.n. The order at the end combines
5708 * the error case 3.l.iii and the success case 3.m-3.n.
5710 * Note: 'length' is always in entries part, so no array abandon issues for
5711 * 'writable' update.
5714 /* XXX: investigate whether write protect can be handled above, if we
5715 * just update length here while ignoring its protected status
5719 duk_uint32_t result_len
;
5722 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key is 'length', exotic array behavior, "
5723 "doing array element deletion and length update"));
5725 rc
= duk__handle_put_array_length_smaller(thr
, obj
, arrlen_old_len
, arrlen_new_len
, force_flag
, &result_len
);
5727 /* update length (curr points to length, and we assume it's still valid) */
5728 DUK_ASSERT(result_len
>= arrlen_new_len
&& result_len
<= arrlen_old_len
);
5730 DUK_ASSERT(curr
.e_idx
>= 0);
5731 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr
->heap
, obj
, curr
.e_idx
));
5732 tmp
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr
->heap
, obj
, curr
.e_idx
);
5733 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp
));
5734 /* no decref needed for a number */
5735 DUK_TVAL_SET_FASTINT_U32(tmp
, result_len
);
5736 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp
));
5738 if (pending_write_protect
) {
5739 DUK_DDD(DUK_DDDPRINT("setting array length non-writable (pending writability update)"));
5740 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr
->heap
, obj
, curr
.e_idx
);
5744 * XXX: shrink array allocation or entries compaction here?
5748 goto fail_array_length_partial
;
5751 } else if (arr_idx
!= DUK__NO_ARRAY_INDEX
&& DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj
)) {
5753 duk_hobject
*varenv
;
5755 DUK_ASSERT(arridx_new_array_length
== 0);
5756 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj
)); /* traits are separate; in particular, arguments not an array */
5760 if (!duk__lookup_arguments_map(thr
, obj
, key
, &curr
, &map
, &varenv
)) {
5761 goto success_no_exotics
;
5763 DUK_ASSERT(map
!= NULL
);
5764 DUK_ASSERT(varenv
!= NULL
);
5766 /* [obj key desc value get set curr_value varname] */
5768 if (has_set
|| has_get
) {
5769 /* = IsAccessorDescriptor(Desc) */
5770 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map' "
5771 "changed to an accessor, delete arguments binding"));
5773 (void) duk_hobject_delprop_raw(thr
, map
, key
, 0); /* ignore result */
5775 /* Note: this order matters (final value before deleting map entry must be done) */
5776 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5777 "check for value update / binding deletion"));
5780 duk_hstring
*varname
;
5782 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5783 "update bound value (variable/argument)"));
5785 varname
= duk_require_hstring(ctx
, -1);
5786 DUK_ASSERT(varname
!= NULL
);
5788 DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
5789 "key=%!O, varname=%!O, value=%!T",
5790 (duk_heaphdr
*) key
,
5791 (duk_heaphdr
*) varname
,
5792 (duk_tval
*) duk_require_tval(ctx
, idx_value
)));
5794 /* strict flag for putvar comes from our caller (currently: fixed) */
5795 duk_js_putvar_envrec(thr
, varenv
, varname
, duk_require_tval(ctx
, idx_value
), throw_flag
);
5797 if (has_writable
&& !is_writable
) {
5798 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5799 "changed to non-writable, delete arguments binding"));
5801 (void) duk_hobject_delprop_raw(thr
, map
, key
, 0); /* ignore result */
5805 /* 'varname' is in stack in this else branch, leaving an unbalanced stack below,
5806 * but this doesn't matter now.
5814 DUK_ERROR_TYPE(thr
, DUK_STR_PROPERTY_IS_VIRTUAL
);
5817 fail_not_writable_array_length
:
5818 DUK_ERROR_TYPE(thr
, DUK_STR_ARRAY_LENGTH_NOT_WRITABLE
);
5821 fail_not_extensible
:
5822 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_EXTENSIBLE
);
5825 fail_not_configurable
:
5826 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_CONFIGURABLE
);
5829 fail_array_length_partial
:
5830 DUK_ERROR_TYPE(thr
, DUK_STR_ARRAY_LENGTH_WRITE_FAILED
);
5835 * Object.prototype.hasOwnProperty() and Object.prototype.propertyIsEnumerable().
5838 DUK_INTERNAL duk_bool_t
duk_hobject_object_ownprop_helper(duk_context
*ctx
, duk_small_uint_t required_desc_flags
) {
5839 duk_hthread
*thr
= (duk_hthread
*) ctx
;
5845 /* coercion order matters */
5846 h_v
= duk_to_hstring(ctx
, 0);
5847 DUK_ASSERT(h_v
!= NULL
);
5849 h_obj
= duk_push_this_coercible_to_object(ctx
);
5850 DUK_ASSERT(h_obj
!= NULL
);
5852 ret
= duk_hobject_get_own_propdesc(thr
, h_obj
, h_v
, &desc
, 0 /*flags*/); /* don't push value */
5854 duk_push_boolean(ctx
, ret
&& ((desc
.flags
& required_desc_flags
) == required_desc_flags
));
5859 * Object.seal() and Object.freeze() (E5 Sections 15.2.3.8 and 15.2.3.9)
5861 * Since the algorithms are similar, a helper provides both functions.
5862 * Freezing is essentially sealing + making plain properties non-writable.
5864 * Note: virtual (non-concrete) properties which are non-configurable but
5865 * writable would pose some problems, but such properties do not currently
5866 * exist (all virtual properties are non-configurable and non-writable).
5867 * If they did exist, the non-configurability does NOT prevent them from
5868 * becoming non-writable. However, this change should be recorded somehow
5869 * so that it would turn up (e.g. when getting the property descriptor),
5870 * requiring some additional flags in the object.
5873 DUK_INTERNAL
void duk_hobject_object_seal_freeze_helper(duk_hthread
*thr
, duk_hobject
*obj
, duk_bool_t is_freeze
) {
5874 duk_uint_fast32_t i
;
5876 DUK_ASSERT(thr
!= NULL
);
5877 DUK_ASSERT(thr
->heap
!= NULL
);
5878 DUK_ASSERT(obj
!= NULL
);
5880 DUK_ASSERT_VALSTACK_SPACE(thr
, DUK__VALSTACK_SPACE
);
5882 #if defined(DUK_USE_ROM_OBJECTS)
5883 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr
*) obj
)) {
5884 DUK_DD(DUK_DDPRINT("attempt to seal/freeze a readonly object, reject"));
5885 DUK_ERROR_TYPE(thr
, DUK_STR_NOT_CONFIGURABLE
);
5890 * Abandon array part because all properties must become non-configurable.
5891 * Note that this is now done regardless of whether this is always the case
5892 * (skips check, but performance problem if caller would do this many times
5893 * for the same object; not likely).
5896 duk__abandon_array_checked(thr
, obj
);
5897 DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj
) == 0);
5899 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
5902 /* since duk__abandon_array_checked() causes a resize, there should be no gaps in keys */
5903 DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
) != NULL
);
5905 /* avoid multiple computations of flags address; bypasses macros */
5906 fp
= DUK_HOBJECT_E_GET_FLAGS_PTR(thr
->heap
, obj
, i
);
5907 if (is_freeze
&& !((*fp
) & DUK_PROPDESC_FLAG_ACCESSOR
)) {
5908 *fp
&= ~(DUK_PROPDESC_FLAG_WRITABLE
| DUK_PROPDESC_FLAG_CONFIGURABLE
);
5910 *fp
&= ~DUK_PROPDESC_FLAG_CONFIGURABLE
;
5914 DUK_HOBJECT_CLEAR_EXTENSIBLE(obj
);
5916 /* no need to compact since we already did that in duk__abandon_array_checked()
5917 * (regardless of whether an array part existed or not.
5924 * Object.isSealed() and Object.isFrozen() (E5 Sections 15.2.3.11, 15.2.3.13)
5926 * Since the algorithms are similar, a helper provides both functions.
5927 * Freezing is essentially sealing + making plain properties non-writable.
5929 * Note: all virtual (non-concrete) properties are currently non-configurable
5930 * and non-writable (and there are no accessor virtual properties), so they don't
5931 * need to be considered here now.
5934 DUK_INTERNAL duk_bool_t
duk_hobject_object_is_sealed_frozen_helper(duk_hthread
*thr
, duk_hobject
*obj
, duk_bool_t is_frozen
) {
5935 duk_uint_fast32_t i
;
5937 DUK_ASSERT(obj
!= NULL
);
5940 /* Note: no allocation pressure, no need to check refcounts etc */
5942 /* must not be extensible */
5943 if (DUK_HOBJECT_HAS_EXTENSIBLE(obj
)) {
5947 /* all virtual properties are non-configurable and non-writable */
5949 /* entry part must not contain any configurable properties, or
5950 * writable properties (if is_frozen).
5952 for (i
= 0; i
< DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
5953 duk_small_uint_t flags
;
5955 if (!DUK_HOBJECT_E_GET_KEY(thr
->heap
, obj
, i
)) {
5959 /* avoid multiple computations of flags address; bypasses macros */
5960 flags
= (duk_small_uint_t
) DUK_HOBJECT_E_GET_FLAGS(thr
->heap
, obj
, i
);
5962 if (flags
& DUK_PROPDESC_FLAG_CONFIGURABLE
) {
5966 !(flags
& DUK_PROPDESC_FLAG_ACCESSOR
) &&
5967 (flags
& DUK_PROPDESC_FLAG_WRITABLE
)) {
5972 /* array part must not contain any non-unused properties, as they would
5973 * be configurable and writable.
5975 for (i
= 0; i
< DUK_HOBJECT_GET_ASIZE(obj
); i
++) {
5976 duk_tval
*tv
= DUK_HOBJECT_A_GET_VALUE_PTR(thr
->heap
, obj
, i
);
5977 if (!DUK_TVAL_IS_UNUSED(tv
)) {
5986 * Object.preventExtensions() and Object.isExtensible() (E5 Sections 15.2.3.10, 15.2.3.13)
5988 * Not needed, implemented by macros DUK_HOBJECT_{HAS,CLEAR,SET}_EXTENSIBLE
5989 * and the Object built-in bindings.
5992 /* Undefine local defines */
5994 #undef DUK__NO_ARRAY_INDEX
5995 #undef DUK__HASH_INITIAL
5996 #undef DUK__HASH_PROBE_STEP
5997 #undef DUK__HASH_UNUSED
5998 #undef DUK__HASH_DELETED
5999 #undef DUK__VALSTACK_SPACE