]> git.proxmox.com Git - ceph.git/blame - ceph/src/civetweb/src/third_party/duktape-1.8.0/src-separate/duk_hobject_props.c
buildsys: switch source download to quincy
[ceph.git] / ceph / src / civetweb / src / third_party / duktape-1.8.0 / src-separate / duk_hobject_props.c
CommitLineData
7c673cae
FG
1/*
2 * Hobject property set/get functionality.
3 *
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
7 * side effect issues.
8 *
9 * Notes:
10 *
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.
15 *
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.
23 *
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.
31 *
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.
35 */
36
37/*
38 * XXX: array indices are mostly typed as duk_uint32_t here; duk_uarridx_t
39 * might be more appropriate.
40 */
41
42/*
43 * XXX: duk_uint_fast32_t should probably be used in many places here.
44 */
45
46#include "duk_internal.h"
47
48/*
49 * Local defines
50 */
51
52#define DUK__NO_ARRAY_INDEX DUK_HSTRING_NO_ARRAY_INDEX
53
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))
57
58/* marker values for hash part */
59#define DUK__HASH_UNUSED DUK_HOBJECT_HASHIDX_UNUSED
60#define DUK__HASH_DELETED DUK_HOBJECT_HASHIDX_DELETED
61
62/* valstack space that suffices for all local calls, including recursion
63 * of other than Duktape calls (getters etc)
64 */
65#define DUK__VALSTACK_SPACE 10
66
67/* valstack space allocated especially for proxy lookup which does a
68 * recursive property lookup
69 */
70#define DUK__VALSTACK_PROXY_LOOKUP 20
71
72/*
73 * Local prototypes
74 */
75
7c673cae
FG
76DUK_LOCAL_DECL duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);
77DUK_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);
78DUK_LOCAL_DECL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc);
79
80DUK_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);
81DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj);
82
11fdf7f2
TL
83DUK_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);
84DUK_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);
7c673cae
FG
85DUK_LOCAL duk_uint32_t duk__get_old_array_length(duk_hthread *thr, duk_hobject *obj, duk_propdesc *temp_desc);
86
87/*
88 * Misc helpers
89 */
90
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
93 * index.
94 */
95/* XXX: for fastints, could use a variant which assumes a double duk_tval
96 * (and doesn't need to check for fastint again).
97 */
98DUK_LOCAL duk_uint32_t duk__tval_number_to_arr_idx(duk_tval *tv) {
99 duk_double_t dbl;
100 duk_uint32_t idx;
101
102 DUK_ASSERT(tv != NULL);
103 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
104
11fdf7f2
TL
105 /* -0 is accepted here as index 0 because ToString(-0) == "0" which is
106 * in canonical form and thus an array index.
107 */
7c673cae
FG
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.
113 */
114 return idx;
115 }
116 return DUK__NO_ARRAY_INDEX;
117}
118
119#if defined(DUK_USE_FASTINT)
120/* Convert a duk_tval fastint (caller checks) to a 32-bit index. */
121DUK_LOCAL duk_uint32_t duk__tval_fastint_to_arr_idx(duk_tval *tv) {
122 duk_int64_t t;
123
124 DUK_ASSERT(tv != NULL);
125 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv));
126
127 t = DUK_TVAL_GET_FASTINT(tv);
128 if ((t & ~0xffffffffULL) != 0) {
129 /* Catches >0x100000000 and negative values. */
130 return DUK__NO_ARRAY_INDEX;
131 }
132
133 /* If the value happens to be 0xFFFFFFFF, it's not a valid array index
134 * but will then match DUK__NO_ARRAY_INDEX.
135 */
136 return (duk_uint32_t) t;
137}
138#endif /* DUK_USE_FASTINT */
139
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).
142 */
143DUK_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;
145 duk_hstring *h;
146
147 DUK_ASSERT(ctx != NULL);
148 DUK_ASSERT(tv != NULL);
149 DUK_ASSERT(out_h != NULL);
150
151 duk_push_tval(ctx, tv);
152 duk_to_string(ctx, -1);
153 h = duk_get_hstring(ctx, -1);
154 DUK_ASSERT(h != NULL);
155 *out_h = h;
156
157 arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(h);
158 return arr_idx;
159}
160
161/* String is an own (virtual) property of a lightfunc. */
162DUK_LOCAL duk_bool_t duk__key_is_lightfunc_ownprop(duk_hthread *thr, duk_hstring *key) {
11fdf7f2 163 DUK_UNREF(thr);
7c673cae
FG
164 return (key == DUK_HTHREAD_STRING_LENGTH(thr) ||
165 key == DUK_HTHREAD_STRING_NAME(thr));
166}
167
168/*
169 * Helpers for managing property storage size
170 */
171
172/* Get default hash part size for a certain entry part size. */
173#if defined(DUK_USE_HOBJECT_HASH_PART)
174DUK_LOCAL duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) {
175 DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
176
177 if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
178 duk_uint32_t res;
179
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);
182
183 /* if fails, e_size will be zero = not an issue, except performance-wise */
184 DUK_ASSERT(res == 0 || res > e_size);
185 return res;
186 } else {
187 return 0;
188 }
189}
190#endif /* USE_PROP_HASH_PART */
191
192/* Get minimum entry part growth for a certain size. */
193DUK_LOCAL duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) {
194 duk_uint32_t res;
195
196 DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES);
197
198 res = (e_size + DUK_HOBJECT_E_MIN_GROW_ADD) / DUK_HOBJECT_E_MIN_GROW_DIVISOR;
199 DUK_ASSERT(res >= 1); /* important for callers */
200 return res;
201}
202
203/* Get minimum array part growth for a certain size. */
204DUK_LOCAL duk_uint32_t duk__get_min_grow_a(duk_uint32_t a_size) {
205 duk_uint32_t res;
206
207 DUK_ASSERT((duk_size_t) a_size <= DUK_HOBJECT_MAX_PROPERTIES);
208
209 res = (a_size + DUK_HOBJECT_A_MIN_GROW_ADD) / DUK_HOBJECT_A_MIN_GROW_DIVISOR;
210 DUK_ASSERT(res >= 1); /* important for callers */
211 return res;
212}
213
214/* Count actually used entry part entries (non-NULL keys). */
215DUK_LOCAL duk_uint32_t duk__count_used_e_keys(duk_hthread *thr, duk_hobject *obj) {
216 duk_uint_fast32_t i;
217 duk_uint_fast32_t n = 0;
218 duk_hstring **e;
219
220 DUK_ASSERT(obj != NULL);
221 DUK_UNREF(thr);
222
223 e = DUK_HOBJECT_E_GET_KEY_BASE(thr->heap, obj);
224 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
225 if (*e++) {
226 n++;
227 }
228 }
229 return (duk_uint32_t) n;
230}
231
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
235 * not needed now.
236 */
237DUK_LOCAL void duk__compute_a_stats(duk_hthread *thr, duk_hobject *obj, duk_uint32_t *out_used, duk_uint32_t *out_min_size) {
238 duk_uint_fast32_t i;
239 duk_uint_fast32_t used = 0;
240 duk_uint_fast32_t highest_idx = (duk_uint_fast32_t) -1; /* see below */
241 duk_tval *a;
242
243 DUK_ASSERT(obj != NULL);
244 DUK_ASSERT(out_used != NULL);
245 DUK_ASSERT(out_min_size != NULL);
246 DUK_UNREF(thr);
247
248 a = DUK_HOBJECT_A_GET_BASE(thr->heap, obj);
249 for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
250 duk_tval *tv = a++;
11fdf7f2 251 if (!DUK_TVAL_IS_UNUSED(tv)) {
7c673cae
FG
252 used++;
253 highest_idx = i;
254 }
255 }
256
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.
260 */
261
262 *out_used = used;
263 *out_min_size = highest_idx + 1; /* 0 if no used entries */
264}
265
266/* Check array density and indicate whether or not the array part should be abandoned. */
267DUK_LOCAL duk_bool_t duk__abandon_array_density_check(duk_uint32_t a_used, duk_uint32_t a_size) {
268 /*
269 * Array abandon check; abandon if:
270 *
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)
276 *
277 * Here, new_used = a_used, new_size = a_size.
278 *
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.
282 */
283
284 return (a_used < DUK_HOBJECT_A_ABANDON_LIMIT * (a_size >> 3));
285}
286
287/* Fast check for extending array: check whether or not a slow density check is required. */
288DUK_LOCAL duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx, duk_uint32_t old_size) {
289 /*
290 * In a fast check we assume old_size equals old_used (i.e., existing
291 * array is fully dense).
292 *
293 * Slow check if:
294 *
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)
303 *
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):
306 *
307 * arr_idx > limit'' * ((old_size + 7) / 8)
308 */
309
310 return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
311}
312
313/*
314 * Proxy helpers
315 */
316
317#if defined(DUK_USE_ES6_PROXY)
318DUK_INTERNAL duk_bool_t duk_hobject_proxy_check(duk_hthread *thr, duk_hobject *obj, duk_hobject **out_target, duk_hobject **out_handler) {
319 duk_tval *tv_target;
320 duk_tval *tv_handler;
321 duk_hobject *h_target;
322 duk_hobject *h_handler;
323
324 DUK_ASSERT(thr != NULL);
325 DUK_ASSERT(obj != NULL);
326 DUK_ASSERT(out_target != NULL);
327 DUK_ASSERT(out_handler != NULL);
328
329 /* Caller doesn't need to check exotic proxy behavior (but does so for
330 * some fast paths).
331 */
332 if (DUK_LIKELY(!DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
333 return 0;
334 }
335
336 tv_handler = duk_hobject_find_existing_entry_tval_ptr(thr->heap, obj, DUK_HTHREAD_STRING_INT_HANDLER(thr));
337 if (!tv_handler) {
11fdf7f2 338 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REVOKED);
7c673cae
FG
339 return 0;
340 }
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 */
346
347 tv_target = duk_hobject_find_existing_entry_tval_ptr(thr->heap, obj, DUK_HTHREAD_STRING_INT_TARGET(thr));
348 if (!tv_target) {
11fdf7f2 349 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REVOKED);
7c673cae
FG
350 return 0;
351 }
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 */
357
358 return 1;
359}
360#endif /* DUK_USE_ES6_PROXY */
361
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.
364 */
365#if defined(DUK_USE_ES6_PROXY)
366DUK_INTERNAL duk_hobject *duk_hobject_resolve_proxy_target(duk_hthread *thr, duk_hobject *obj) {
367 duk_hobject *h_target;
368 duk_hobject *h_handler;
369
370 DUK_ASSERT(thr != NULL);
371 DUK_ASSERT(obj != NULL);
372
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.
376 */
377
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);
381 obj = h_target;
382 } else {
383 break;
384 }
385 }
386
387 DUK_ASSERT(obj != NULL);
388 return obj;
389}
390#endif /* DUK_USE_ES6_PROXY */
391
392#if defined(DUK_USE_ES6_PROXY)
393DUK_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;
396
397 DUK_ASSERT(thr != NULL);
398 DUK_ASSERT(obj != NULL);
399 DUK_ASSERT(tv_key != NULL);
400 DUK_ASSERT(out_target != NULL);
401
402 if (!duk_hobject_proxy_check(thr, obj, out_target, &h_handler)) {
403 return 0;
404 }
405 DUK_ASSERT(*out_target != NULL);
406 DUK_ASSERT(h_handler != NULL);
407
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.
416 */
417
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"));
423 return 0;
424 }
425 }
426
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).
432 *
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.)
436 */
437
438 /* XXX: C recursion limit if proxies are allowed as handler/target values */
439
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 ] */
445
446 /* stack prepped for func call: [ ... trap handler ] */
447 return 1;
448 } else {
449 duk_pop_2(ctx);
450 return 0;
451 }
452}
453#endif /* DUK_USE_ES6_PROXY */
454
455/*
456 * Reallocate property allocation, moving properties to the new allocation.
457 *
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.
462 *
463 * There is no support for in-place reallocation or just compacting keys
464 * without resizing the property allocation. This is intentional to keep
465 * code size minimal.
466 *
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.
471 *
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).
477 *
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
480 * everything else.
481 *
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.
485 */
486
487DUK_LOCAL
488void duk__realloc_props(duk_hthread *thr,
489 duk_hobject *obj,
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;
497#endif
498 duk_uint32_t new_alloc_size;
499 duk_uint32_t new_e_size_adjusted;
500 duk_uint8_t *new_p;
501 duk_hstring **new_e_k;
502 duk_propvalue *new_e_pv;
503 duk_uint8_t *new_e_f;
504 duk_tval *new_a;
505 duk_uint32_t *new_h;
506 duk_uint32_t new_e_next;
507 duk_uint_fast32_t i;
508
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
516 */
11fdf7f2 517 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
7c673cae
FG
518 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
519
520 /*
521 * Pre resize assertions.
522 */
523
524#ifdef DUK_USE_ASSERTIONS
525 /* XXX: pre-checks (such as no duplicate keys) */
526#endif
527
528 /*
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).
532 *
533 * Property layout 2 does not require this tweaking and is preferred
534 * on low RAM platforms requiring alignment.
535 */
536
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);
548#else
549#error invalid hobject layout defines
550#endif
551
552 /*
553 * Debug logging after adjustment.
554 */
555
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",
558 (void *) obj,
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,
569 (long) new_a_size,
570 (long) new_h_size,
571 (long) abandon_array,
572 (long) new_e_size));
573
574 /*
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.
580 *
581 * Since this works based on allocation size (not actually used size),
582 * the limit is a bit approximate but good enough in practice.
583 */
584
585 if (new_e_size_adjusted + new_a_size > DUK_HOBJECT_MAX_PROPERTIES) {
11fdf7f2 586 DUK_ERROR_ALLOC_DEFMSG(thr);
7c673cae
FG
587 }
588
589 /*
590 * Compute new alloc size and alloc new area.
591 *
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
594 * the end.
595 *
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.
601 */
602
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 */
608#endif
609
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));
11fdf7f2
TL
612 new_e_next = 0;
613 new_e_k = NULL;
7c673cae
FG
614 if (new_alloc_size == 0) {
615 /* for zero size, don't push anything on valstack */
616 DUK_ASSERT(new_e_size_adjusted == 0);
617 DUK_ASSERT(new_a_size == 0);
618 DUK_ASSERT(new_h_size == 0);
619 new_p = NULL;
620 } else {
621 /* This may trigger mark-and-sweep with arbitrary side effects,
622 * including an attempted resize of the object we're resizing,
623 * executing a finalizer which may add or remove properties of
624 * the object we're resizing etc.
625 */
626
11fdf7f2
TL
627#if 0 /* XXX: inject test */
628 if (1) {
629 goto alloc_failed;
630 }
631#endif
632 new_p = (duk_uint8_t *) DUK_ALLOC(thr->heap, new_alloc_size);
633 if (new_p == NULL) {
634 /* NULL always indicates alloc failure because
635 * new_alloc_size > 0.
636 */
637 goto alloc_failed;
638 }
7c673cae
FG
639 }
640
641 /* Set up pointers to the new property area: this is hidden behind a macro
642 * because it is memory layout specific.
643 */
644 DUK_HOBJECT_P_SET_REALLOC_PTRS(new_p, new_e_k, new_e_pv, new_e_f, new_a, new_h,
645 new_e_size_adjusted, new_a_size, new_h_size);
646 DUK_UNREF(new_h); /* happens when hash part dropped */
7c673cae
FG
647
648 /* if new_p == NULL, all of these pointers are NULL */
649 DUK_ASSERT((new_p != NULL) ||
650 (new_e_k == NULL && new_e_pv == NULL && new_e_f == NULL &&
651 new_a == NULL && new_h == NULL));
652
653 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",
654 (long) new_alloc_size, (void *) new_e_k, (void *) new_e_pv, (void *) new_e_f,
655 (void *) new_a, (void *) new_h));
656
657 /*
658 * Migrate array to start of entries if requested.
659 *
660 * Note: from an enumeration perspective the order of entry keys matters.
661 * Array keys should appear wherever they appeared before the array abandon
662 * operation.
663 */
664
665 if (abandon_array) {
666 /*
667 * Note: assuming new_a_size == 0, and that entry part contains
668 * no conflicting keys, refcounts do not need to be adjusted for
669 * the values, as they remain exactly the same.
670 *
671 * The keys, however, need to be interned, incref'd, and be
672 * reachable for GC. Any intern attempt may trigger a GC and
673 * claim any non-reachable strings, so every key must be reachable
674 * at all times.
675 *
676 * A longjmp must not occur here, as the new_p allocation would
677 * be freed without these keys being decref'd, hence the messy
678 * decref handling if intern fails.
679 */
680 DUK_ASSERT(new_a_size == 0);
681
682 for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
683 duk_tval *tv1;
684 duk_tval *tv2;
685 duk_hstring *key;
686
687 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
688
689 tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
11fdf7f2 690 if (DUK_TVAL_IS_UNUSED(tv1)) {
7c673cae
FG
691 continue;
692 }
693
694 DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
695 new_e_pv != NULL && new_e_f != NULL);
696
697 /*
698 * Intern key via the valstack to ensure reachability behaves
699 * properly. We must avoid longjmp's here so use non-checked
700 * primitives.
701 *
702 * Note: duk_check_stack() potentially reallocs the valstack,
703 * invalidating any duk_tval pointers to valstack. Callers
704 * must be careful.
705 */
706
707 /* never shrinks; auto-adds DUK_VALSTACK_INTERNAL_EXTRA, which is generous */
708 if (!duk_check_stack(ctx, 1)) {
709 goto abandon_error;
710 }
711 DUK_ASSERT_VALSTACK_SPACE(thr, 1);
712 key = duk_heap_string_intern_u32(thr->heap, i);
713 if (!key) {
714 goto abandon_error;
715 }
716 duk_push_hstring(ctx, key); /* keep key reachable for GC etc; guaranteed not to fail */
717
718 /* key is now reachable in the valstack */
719
720 DUK_HSTRING_INCREF(thr, key); /* second incref for the entry reference */
721 new_e_k[new_e_next] = key;
722 tv2 = &new_e_pv[new_e_next].v; /* array entries are all plain values */
723 DUK_TVAL_SET_TVAL(tv2, tv1);
724 new_e_f[new_e_next] = DUK_PROPDESC_FLAG_WRITABLE |
725 DUK_PROPDESC_FLAG_ENUMERABLE |
726 DUK_PROPDESC_FLAG_CONFIGURABLE;
727 new_e_next++;
728
729 /* Note: new_e_next matches pushed temp key count, and nothing can
730 * fail above between the push and this point.
731 */
732 }
733
734 DUK_DDD(DUK_DDDPRINT("abandon array: pop %ld key temps from valstack", (long) new_e_next));
735 duk_pop_n(ctx, new_e_next);
736 }
737
738 /*
739 * Copy keys and values in the entry part (compacting them at the same time).
740 */
741
742 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
743 duk_hstring *key;
744
745 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
746
747 key = DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i);
748 if (!key) {
749 continue;
750 }
751
752 DUK_ASSERT(new_p != NULL && new_e_k != NULL &&
753 new_e_pv != NULL && new_e_f != NULL);
754
755 new_e_k[new_e_next] = key;
756 new_e_pv[new_e_next] = DUK_HOBJECT_E_GET_VALUE(thr->heap, obj, i);
757 new_e_f[new_e_next] = DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, i);
758 new_e_next++;
759 }
760 /* the entries [new_e_next, new_e_size_adjusted[ are left uninitialized on purpose (ok, not gc reachable) */
761
762 /*
763 * Copy array elements to new array part.
764 */
765
766 if (new_a_size > DUK_HOBJECT_GET_ASIZE(obj)) {
767 /* copy existing entries as is */
768 DUK_ASSERT(new_p != NULL && new_a != NULL);
769 if (DUK_HOBJECT_GET_ASIZE(obj) > 0) {
770 /* Avoid zero copy with an invalid pointer. If obj->p is NULL,
771 * the 'new_a' pointer will be invalid which is not allowed even
772 * when copy size is zero.
773 */
774 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
775 DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) > 0);
776 DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * DUK_HOBJECT_GET_ASIZE(obj));
777 }
778
779 /* fill new entries with -unused- (required, gc reachable) */
780 for (i = DUK_HOBJECT_GET_ASIZE(obj); i < new_a_size; i++) {
781 duk_tval *tv = &new_a[i];
11fdf7f2 782 DUK_TVAL_SET_UNUSED(tv);
7c673cae
FG
783 }
784 } else {
785#ifdef DUK_USE_ASSERTIONS
786 /* caller must have decref'd values above new_a_size (if that is necessary) */
787 if (!abandon_array) {
788 for (i = new_a_size; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
789 duk_tval *tv;
790 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
791
792 /* current assertion is quite strong: decref's and set to unused */
11fdf7f2 793 DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv));
7c673cae
FG
794 }
795 }
796#endif
797 if (new_a_size > 0) {
798 /* Avoid zero copy with an invalid pointer. If obj->p is NULL,
799 * the 'new_a' pointer will be invalid which is not allowed even
800 * when copy size is zero.
801 */
802 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
803 DUK_ASSERT(new_a_size > 0);
804 DUK_MEMCPY((void *) new_a, (void *) DUK_HOBJECT_A_GET_BASE(thr->heap, obj), sizeof(duk_tval) * new_a_size);
805 }
806 }
807
808 /*
809 * Rebuild the hash part always from scratch (guaranteed to finish).
810 *
811 * Any resize of hash part requires rehashing. In addition, by rehashing
812 * get rid of any elements marked deleted (DUK__HASH_DELETED) which is critical
813 * to ensuring the hash part never fills up.
814 */
815
816#if defined(DUK_USE_HOBJECT_HASH_PART)
817 if (DUK_UNLIKELY(new_h_size > 0)) {
818 DUK_ASSERT(new_h != NULL);
819
820 /* fill new_h with u32 0xff = UNUSED */
821 DUK_ASSERT(DUK_HOBJECT_GET_PROPS(thr->heap, obj) != NULL);
822 DUK_ASSERT(new_h_size > 0);
823 DUK_MEMSET(new_h, 0xff, sizeof(duk_uint32_t) * new_h_size);
824
825 DUK_ASSERT(new_e_next <= new_h_size); /* equality not actually possible */
826 for (i = 0; i < new_e_next; i++) {
827 duk_hstring *key = new_e_k[i];
828 duk_uint32_t j, step;
829
830 DUK_ASSERT(key != NULL);
831 j = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size);
832 step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
833
834 for (;;) {
835 DUK_ASSERT(new_h[j] != DUK__HASH_DELETED); /* should never happen */
836 if (new_h[j] == DUK__HASH_UNUSED) {
837 DUK_DDD(DUK_DDDPRINT("rebuild hit %ld -> %ld", (long) j, (long) i));
838 new_h[j] = i;
839 break;
840 }
841 DUK_DDD(DUK_DDDPRINT("rebuild miss %ld, step %ld", (long) j, (long) step));
842 j = (j + step) % new_h_size;
843
844 /* guaranteed to finish */
845 DUK_ASSERT(j != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), new_h_size));
846 }
847 }
848 } else {
849 DUK_DDD(DUK_DDDPRINT("no hash part, no rehash"));
850 }
851#endif /* DUK_USE_HOBJECT_HASH_PART */
852
853 /*
854 * Nice debug log.
855 */
856
857 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 "
858 "{p=%p,e_size=%ld,e_next=%ld,a_size=%ld,h_size=%ld}, abandon_array=%ld, unadjusted new_e_size=%ld",
859 (void *) obj,
860 (long) DUK_HOBJECT_P_COMPUTE_SIZE(DUK_HOBJECT_GET_ESIZE(obj),
861 DUK_HOBJECT_GET_ASIZE(obj),
862 DUK_HOBJECT_GET_HSIZE(obj)),
863 (long) new_alloc_size,
864 (void *) DUK_HOBJECT_GET_PROPS(thr->heap, obj),
865 (long) DUK_HOBJECT_GET_ESIZE(obj),
866 (long) DUK_HOBJECT_GET_ENEXT(obj),
867 (long) DUK_HOBJECT_GET_ASIZE(obj),
868 (long) DUK_HOBJECT_GET_HSIZE(obj),
869 (void *) new_p,
870 (long) new_e_size_adjusted,
871 (long) new_e_next,
872 (long) new_a_size,
873 (long) new_h_size,
874 (long) abandon_array,
875 (long) new_e_size));
876
877 /*
878 * All done, switch properties ('p') allocation to new one.
879 */
880
881 DUK_FREE(thr->heap, DUK_HOBJECT_GET_PROPS(thr->heap, obj)); /* NULL obj->p is OK */
882 DUK_HOBJECT_SET_PROPS(thr->heap, obj, new_p);
883 DUK_HOBJECT_SET_ESIZE(obj, new_e_size_adjusted);
884 DUK_HOBJECT_SET_ENEXT(obj, new_e_next);
885 DUK_HOBJECT_SET_ASIZE(obj, new_a_size);
886 DUK_HOBJECT_SET_HSIZE(obj, new_h_size);
887
7c673cae
FG
888 /* clear array part flag only after switching */
889 if (abandon_array) {
890 DUK_HOBJECT_CLEAR_ARRAY_PART(obj);
891 }
892
893 DUK_DDD(DUK_DDDPRINT("resize result: %!O", (duk_heaphdr *) obj));
894
895#ifdef DUK_USE_MARK_AND_SWEEP
896 thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
897#endif
898
899 /*
900 * Post resize assertions.
901 */
902
903#ifdef DUK_USE_ASSERTIONS
904 /* XXX: post-checks (such as no duplicate keys) */
905#endif
906 return;
907
908 /*
909 * Abandon array failed, need to decref keys already inserted
910 * into the beginning of new_e_k before unwinding valstack.
911 */
912
913 abandon_error:
11fdf7f2
TL
914 alloc_failed:
915 DUK_D(DUK_DPRINT("object property table resize failed"));
916
7c673cae
FG
917 i = new_e_next;
918 while (i > 0) {
919 i--;
920 DUK_ASSERT(new_e_k != NULL);
921 DUK_ASSERT(new_e_k[i] != NULL);
11fdf7f2 922 DUK_HSTRING_DECREF(thr, new_e_k[i]); /* side effects */
7c673cae
FG
923 }
924
11fdf7f2
TL
925 DUK_FREE(thr->heap, new_p); /* OK for NULL. */
926
7c673cae
FG
927#ifdef DUK_USE_MARK_AND_SWEEP
928 thr->heap->mark_and_sweep_base_flags = prev_mark_and_sweep_base_flags;
929#endif
930
11fdf7f2 931 DUK_ERROR_ALLOC_DEFMSG(thr);
7c673cae
FG
932}
933
934/*
935 * Helpers to resize properties allocation on specific needs.
936 */
937
938/* Grow entry part allocation for one additional entry. */
939DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) {
940 duk_uint32_t old_e_used; /* actually used, non-NULL entries */
941 duk_uint32_t new_e_size;
942 duk_uint32_t new_a_size;
943 duk_uint32_t new_h_size;
944
945 DUK_ASSERT(thr != NULL);
946 DUK_ASSERT(obj != NULL);
947
948 /* Duktape 0.11.0 and prior tried to optimize the resize by not
949 * counting the number of actually used keys prior to the resize.
950 * This worked mostly well but also caused weird leak-like behavior
951 * as in: test-bug-object-prop-alloc-unbounded.js. So, now we count
952 * the keys explicitly to compute the new entry part size.
953 */
954
955 old_e_used = duk__count_used_e_keys(thr, obj);
956 new_e_size = old_e_used + duk__get_min_grow_e(old_e_used);
957#if defined(DUK_USE_HOBJECT_HASH_PART)
958 new_h_size = duk__get_default_h_size(new_e_size);
959#else
960 new_h_size = 0;
961#endif
962 new_a_size = DUK_HOBJECT_GET_ASIZE(obj);
963 DUK_ASSERT(new_e_size >= old_e_used + 1); /* duk__get_min_grow_e() is always >= 1 */
964
965 duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
966}
967
968/* Grow array part for a new highest array index. */
969DUK_LOCAL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx) {
970 duk_uint32_t new_e_size;
971 duk_uint32_t new_a_size;
972 duk_uint32_t new_h_size;
973
974 DUK_ASSERT(thr != NULL);
975 DUK_ASSERT(obj != NULL);
976 DUK_ASSERT(highest_arr_idx >= DUK_HOBJECT_GET_ASIZE(obj));
977
978 /* minimum new length is highest_arr_idx + 1 */
979
980 new_e_size = DUK_HOBJECT_GET_ESIZE(obj);
981 new_h_size = DUK_HOBJECT_GET_HSIZE(obj);
982 new_a_size = highest_arr_idx + duk__get_min_grow_a(highest_arr_idx);
983 DUK_ASSERT(new_a_size >= highest_arr_idx + 1); /* duk__get_min_grow_a() is always >= 1 */
984
985 duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0);
986}
987
988/* Abandon array part, moving array entries into entries part.
989 * This requires a props resize, which is a heavy operation.
990 * We also compact the entries part while we're at it, although
991 * this is not strictly required.
992 */
993DUK_LOCAL void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) {
994 duk_uint32_t new_e_size;
995 duk_uint32_t new_a_size;
996 duk_uint32_t new_h_size;
997 duk_uint32_t e_used; /* actually used, non-NULL keys */
998 duk_uint32_t a_used;
999 duk_uint32_t a_size;
1000
1001 DUK_ASSERT(thr != NULL);
1002 DUK_ASSERT(obj != NULL);
1003
1004 e_used = duk__count_used_e_keys(thr, obj);
1005 duk__compute_a_stats(thr, obj, &a_used, &a_size);
1006
1007 /*
1008 * Must guarantee all actually used array entries will fit into
1009 * new entry part. Add one growth step to ensure we don't run out
1010 * of space right away.
1011 */
1012
1013 new_e_size = e_used + a_used;
1014 new_e_size = new_e_size + duk__get_min_grow_e(new_e_size);
1015 new_a_size = 0;
1016#if defined(DUK_USE_HOBJECT_HASH_PART)
1017 new_h_size = duk__get_default_h_size(new_e_size);
1018#else
1019 new_h_size = 0;
1020#endif
1021
1022 DUK_DD(DUK_DDPRINT("abandon array part for hobject %p, "
1023 "array stats before: e_used=%ld, a_used=%ld, a_size=%ld; "
1024 "resize to e_size=%ld, a_size=%ld, h_size=%ld",
1025 (void *) obj, (long) e_used, (long) a_used, (long) a_size,
1026 (long) new_e_size, (long) new_a_size, (long) new_h_size));
1027
1028 duk__realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 1);
1029}
1030
1031/*
1032 * Compact an object. Minimizes allocation size for objects which are
1033 * not likely to be extended. This is useful for internal and non-
1034 * extensible objects, but can also be called for non-extensible objects.
1035 * May abandon the array part if it is computed to be too sparse.
1036 *
1037 * This call is relatively expensive, as it needs to scan both the
1038 * entries and the array part.
1039 *
1040 * The call may fail due to allocation error.
1041 */
1042
1043DUK_INTERNAL void duk_hobject_compact_props(duk_hthread *thr, duk_hobject *obj) {
1044 duk_uint32_t e_size; /* currently used -> new size */
1045 duk_uint32_t a_size; /* currently required */
1046 duk_uint32_t a_used; /* actually used */
1047 duk_uint32_t h_size;
1048 duk_bool_t abandon_array;
1049
1050 DUK_ASSERT(thr != NULL);
1051 DUK_ASSERT(obj != NULL);
1052
11fdf7f2
TL
1053#if defined(DUK_USE_ROM_OBJECTS)
1054 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) {
1055 DUK_DD(DUK_DDPRINT("ignore attempt to compact a rom object"));
1056 return;
1057 }
1058#endif
1059
7c673cae
FG
1060 e_size = duk__count_used_e_keys(thr, obj);
1061 duk__compute_a_stats(thr, obj, &a_used, &a_size);
1062
1063 DUK_DD(DUK_DDPRINT("compacting hobject, used e keys %ld, used a keys %ld, min a size %ld, "
1064 "resized array density would be: %ld/%ld = %lf",
1065 (long) e_size, (long) a_used, (long) a_size,
1066 (long) a_used, (long) a_size,
1067 (double) a_used / (double) a_size));
1068
1069 if (duk__abandon_array_density_check(a_used, a_size)) {
1070 DUK_DD(DUK_DDPRINT("decided to abandon array during compaction, a_used=%ld, a_size=%ld",
1071 (long) a_used, (long) a_size));
1072 abandon_array = 1;
1073 e_size += a_used;
1074 a_size = 0;
1075 } else {
1076 DUK_DD(DUK_DDPRINT("decided to keep array during compaction"));
1077 abandon_array = 0;
1078 }
1079
1080#if defined(DUK_USE_HOBJECT_HASH_PART)
1081 if (e_size >= DUK_HOBJECT_E_USE_HASH_LIMIT) {
1082 h_size = duk__get_default_h_size(e_size);
1083 } else {
1084 h_size = 0;
1085 }
1086#else
1087 h_size = 0;
1088#endif
1089
1090 DUK_DD(DUK_DDPRINT("compacting hobject -> new e_size %ld, new a_size=%ld, new h_size=%ld, abandon_array=%ld",
1091 (long) e_size, (long) a_size, (long) h_size, (long) abandon_array));
1092
1093 duk__realloc_props(thr, obj, e_size, a_size, h_size, abandon_array);
1094}
1095
1096/*
1097 * Find an existing key from entry part either by linear scan or by
1098 * using the hash index (if it exists).
1099 *
1100 * Sets entry index (and possibly the hash index) to output variables,
1101 * which allows the caller to update the entry and hash entries in-place.
1102 * If entry is not found, both values are set to -1. If entry is found
1103 * but there is no hash part, h_idx is set to -1.
1104 */
1105
1106DUK_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) {
1107 DUK_ASSERT(obj != NULL);
1108 DUK_ASSERT(key != NULL);
1109 DUK_ASSERT(e_idx != NULL);
1110 DUK_ASSERT(h_idx != NULL);
1111 DUK_UNREF(heap);
1112
1113 if (DUK_LIKELY(DUK_HOBJECT_GET_HSIZE(obj) == 0))
1114 {
1115 /* Linear scan: more likely because most objects are small.
1116 * This is an important fast path.
1117 *
1118 * XXX: this might be worth inlining for property lookups.
1119 */
1120 duk_uint_fast32_t i;
1121 duk_uint_fast32_t n;
1122 duk_hstring **h_keys_base;
1123 DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using linear scan for lookup"));
1124
1125 h_keys_base = DUK_HOBJECT_E_GET_KEY_BASE(heap, obj);
1126 n = DUK_HOBJECT_GET_ENEXT(obj);
1127 for (i = 0; i < n; i++) {
1128 if (h_keys_base[i] == key) {
1129 *e_idx = i;
1130 *h_idx = -1;
1131 return;
1132 }
1133 }
1134 }
1135#if defined(DUK_USE_HOBJECT_HASH_PART)
1136 else
1137 {
1138 /* hash lookup */
1139 duk_uint32_t n;
1140 duk_uint32_t i, step;
1141 duk_uint32_t *h_base;
1142
1143 DUK_DDD(DUK_DDDPRINT("duk_hobject_find_existing_entry() using hash part for lookup"));
1144
1145 h_base = DUK_HOBJECT_H_GET_BASE(heap, obj);
1146 n = DUK_HOBJECT_GET_HSIZE(obj);
1147 i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
1148 step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
1149
1150 for (;;) {
1151 duk_uint32_t t;
1152
1153 DUK_ASSERT_DISABLE(i >= 0); /* unsigned */
1154 DUK_ASSERT(i < DUK_HOBJECT_GET_HSIZE(obj));
1155 t = h_base[i];
1156 DUK_ASSERT(t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED ||
1157 (t < DUK_HOBJECT_GET_ESIZE(obj))); /* t >= 0 always true, unsigned */
1158
1159 if (t == DUK__HASH_UNUSED) {
1160 break;
1161 } else if (t == DUK__HASH_DELETED) {
1162 DUK_DDD(DUK_DDDPRINT("lookup miss (deleted) i=%ld, t=%ld",
1163 (long) i, (long) t));
1164 } else {
1165 DUK_ASSERT(t < DUK_HOBJECT_GET_ESIZE(obj));
1166 if (DUK_HOBJECT_E_GET_KEY(heap, obj, t) == key) {
1167 DUK_DDD(DUK_DDDPRINT("lookup hit i=%ld, t=%ld -> key %p",
1168 (long) i, (long) t, (void *) key));
1169 *e_idx = t;
1170 *h_idx = i;
1171 return;
1172 }
1173 DUK_DDD(DUK_DDDPRINT("lookup miss i=%ld, t=%ld",
1174 (long) i, (long) t));
1175 }
1176 i = (i + step) % n;
1177
1178 /* guaranteed to finish, as hash is never full */
1179 DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n));
1180 }
1181 }
1182#endif /* DUK_USE_HOBJECT_HASH_PART */
1183
1184 /* not found */
1185 *e_idx = -1;
1186 *h_idx = -1;
1187}
1188
1189/* For internal use: get non-accessor entry value */
1190DUK_INTERNAL duk_tval *duk_hobject_find_existing_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_hstring *key) {
1191 duk_int_t e_idx;
1192 duk_int_t h_idx;
1193
1194 DUK_ASSERT(obj != NULL);
1195 DUK_ASSERT(key != NULL);
1196 DUK_UNREF(heap);
1197
1198 duk_hobject_find_existing_entry(heap, obj, key, &e_idx, &h_idx);
1199 if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx)) {
1200 return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx);
1201 } else {
1202 return NULL;
1203 }
1204}
1205
1206/* For internal use: get non-accessor entry value and attributes */
1207DUK_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) {
1208 duk_int_t e_idx;
1209 duk_int_t h_idx;
1210
1211 DUK_ASSERT(obj != NULL);
1212 DUK_ASSERT(key != NULL);
1213 DUK_ASSERT(out_attrs != NULL);
1214 DUK_UNREF(heap);
1215
1216 duk_hobject_find_existing_entry(heap, obj, key, &e_idx, &h_idx);
1217 if (e_idx >= 0 && !DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx)) {
1218 *out_attrs = DUK_HOBJECT_E_GET_FLAGS(heap, obj, e_idx);
1219 return DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx);
1220 } else {
1221 *out_attrs = 0;
1222 return NULL;
1223 }
1224}
1225
1226/* For internal use: get array part value */
1227DUK_INTERNAL duk_tval *duk_hobject_find_existing_array_entry_tval_ptr(duk_heap *heap, duk_hobject *obj, duk_uarridx_t i) {
1228 duk_tval *tv;
1229
1230 DUK_ASSERT(obj != NULL);
1231 DUK_UNREF(heap);
1232
1233 if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
1234 return NULL;
1235 }
1236 if (i >= DUK_HOBJECT_GET_ASIZE(obj)) {
1237 return NULL;
1238 }
1239 tv = DUK_HOBJECT_A_GET_VALUE_PTR(heap, obj, i);
1240 return tv;
1241}
1242
1243/*
1244 * Allocate and initialize a new entry, resizing the properties allocation
1245 * if necessary. Returns entry index (e_idx) or throws an error if alloc fails.
1246 *
1247 * Sets the key of the entry (increasing the key's refcount), and updates
1248 * the hash part if it exists. Caller must set value and flags, and update
1249 * the entry value refcount. A decref for the previous value is not necessary.
1250 */
1251
1252DUK_LOCAL duk_bool_t duk__alloc_entry_checked(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
1253 duk_uint32_t idx;
1254
1255 DUK_ASSERT(thr != NULL);
1256 DUK_ASSERT(obj != NULL);
1257 DUK_ASSERT(key != NULL);
1258 DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj) <= DUK_HOBJECT_GET_ESIZE(obj));
1259
1260#ifdef DUK_USE_ASSERTIONS
1261 /* key must not already exist in entry part */
1262 {
1263 duk_uint_fast32_t i;
1264 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
1265 DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i) != key);
1266 }
1267 }
1268#endif
1269
1270 if (DUK_HOBJECT_GET_ENEXT(obj) >= DUK_HOBJECT_GET_ESIZE(obj)) {
1271 /* only need to guarantee 1 more slot, but allocation growth is in chunks */
1272 DUK_DDD(DUK_DDDPRINT("entry part full, allocate space for one more entry"));
1273 duk__grow_props_for_new_entry_item(thr, obj);
1274 }
1275 DUK_ASSERT(DUK_HOBJECT_GET_ENEXT(obj) < DUK_HOBJECT_GET_ESIZE(obj));
1276 idx = DUK_HOBJECT_POSTINC_ENEXT(obj);
1277
1278 /* previous value is assumed to be garbage, so don't touch it */
1279 DUK_HOBJECT_E_SET_KEY(thr->heap, obj, idx, key);
1280 DUK_HSTRING_INCREF(thr, key);
1281
1282#if defined(DUK_USE_HOBJECT_HASH_PART)
1283 if (DUK_UNLIKELY(DUK_HOBJECT_GET_HSIZE(obj) > 0)) {
1284 duk_uint32_t n;
1285 duk_uint32_t i, step;
1286 duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(thr->heap, obj);
1287
1288 n = DUK_HOBJECT_GET_HSIZE(obj);
1289 i = DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), n);
1290 step = DUK__HASH_PROBE_STEP(DUK_HSTRING_GET_HASH(key));
1291
1292 for (;;) {
1293 duk_uint32_t t = h_base[i];
1294 if (t == DUK__HASH_UNUSED || t == DUK__HASH_DELETED) {
1295 DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() inserted key into hash part, %ld -> %ld",
1296 (long) i, (long) idx));
1297 DUK_ASSERT_DISABLE(i >= 0); /* unsigned */
1298 DUK_ASSERT(i < DUK_HOBJECT_GET_HSIZE(obj));
1299 DUK_ASSERT_DISABLE(idx >= 0);
1300 DUK_ASSERT(idx < DUK_HOBJECT_GET_ESIZE(obj));
1301 h_base[i] = idx;
1302 break;
1303 }
1304 DUK_DDD(DUK_DDDPRINT("duk__alloc_entry_checked() miss %ld", (long) i));
1305 i = (i + step) % n;
1306
1307 /* guaranteed to find an empty slot */
1308 DUK_ASSERT(i != (duk_uint32_t) DUK__HASH_INITIAL(DUK_HSTRING_GET_HASH(key), DUK_HOBJECT_GET_HSIZE(obj)));
1309 }
1310 }
1311#endif /* DUK_USE_HOBJECT_HASH_PART */
1312
1313 /* Note: we could return the hash index here too, but it's not
1314 * needed right now.
1315 */
1316
1317 DUK_ASSERT_DISABLE(idx >= 0);
1318 DUK_ASSERT(idx < DUK_HOBJECT_GET_ESIZE(obj));
1319 DUK_ASSERT(idx < DUK_HOBJECT_GET_ENEXT(obj));
1320 return idx;
1321}
1322
1323/*
1324 * Object internal value
1325 *
1326 * Returned value is guaranteed to be reachable / incref'd, caller does not need
1327 * to incref OR decref. No proxies or accessors are invoked, no prototype walk.
1328 */
1329
1330DUK_INTERNAL duk_bool_t duk_hobject_get_internal_value(duk_heap *heap, duk_hobject *obj, duk_tval *tv_out) {
1331 duk_int_t e_idx;
1332 duk_int_t h_idx;
1333
1334 DUK_ASSERT(heap != NULL);
1335 DUK_ASSERT(obj != NULL);
1336 DUK_ASSERT(tv_out != NULL);
1337
7c673cae
FG
1338 /* always in entry part, no need to look up parents etc */
1339 duk_hobject_find_existing_entry(heap, obj, DUK_HEAP_STRING_INT_VALUE(heap), &e_idx, &h_idx);
1340 if (e_idx >= 0) {
1341 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(heap, obj, e_idx));
1342 DUK_TVAL_SET_TVAL(tv_out, DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(heap, obj, e_idx));
1343 return 1;
1344 }
11fdf7f2 1345 DUK_TVAL_SET_UNDEFINED(tv_out);
7c673cae
FG
1346 return 0;
1347}
1348
1349DUK_INTERNAL duk_hstring *duk_hobject_get_internal_value_string(duk_heap *heap, duk_hobject *obj) {
1350 duk_tval tv;
1351
1352 DUK_ASSERT(heap != NULL);
1353 DUK_ASSERT(obj != NULL);
1354
11fdf7f2
TL
1355 /* This is not strictly necessary, but avoids compiler warnings; e.g.
1356 * gcc won't reliably detect that no uninitialized data is read below.
1357 */
1358 DUK_MEMZERO((void *) &tv, sizeof(duk_tval));
1359
7c673cae
FG
1360 if (duk_hobject_get_internal_value(heap, obj, &tv)) {
1361 duk_hstring *h;
1362 DUK_ASSERT(DUK_TVAL_IS_STRING(&tv));
1363 h = DUK_TVAL_GET_STRING(&tv);
1364 return h;
1365 }
1366
1367 return NULL;
1368}
1369
1370/*
1371 * Arguments handling helpers (argument map mainly).
1372 *
1373 * An arguments object has exotic behavior for some numeric indices.
1374 * Accesses may translate to identifier operations which may have
1375 * arbitrary side effects (potentially invalidating any duk_tval
1376 * pointers).
1377 */
1378
1379/* Lookup 'key' from arguments internal 'map', perform a variable lookup
1380 * if mapped, and leave the result on top of stack (and return non-zero).
1381 * Used in E5 Section 10.6 algorithms [[Get]] and [[GetOwnProperty]].
1382 */
1383DUK_LOCAL
1384duk_bool_t duk__lookup_arguments_map(duk_hthread *thr,
1385 duk_hobject *obj,
1386 duk_hstring *key,
1387 duk_propdesc *temp_desc,
1388 duk_hobject **out_map,
1389 duk_hobject **out_varenv) {
1390 duk_context *ctx = (duk_context *) thr;
1391 duk_hobject *map;
1392 duk_hobject *varenv;
1393 duk_bool_t rc;
1394
1395 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1396
1397 DUK_DDD(DUK_DDDPRINT("arguments map lookup: thr=%p, obj=%p, key=%p, temp_desc=%p "
1398 "(obj -> %!O, key -> %!O)",
1399 (void *) thr, (void *) obj, (void *) key, (void *) temp_desc,
1400 (duk_heaphdr *) obj, (duk_heaphdr *) key));
1401
11fdf7f2 1402 if (!duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
1403 DUK_DDD(DUK_DDDPRINT("-> no 'map'"));
1404 return 0;
1405 }
1406
1407 map = duk_require_hobject(ctx, -1);
1408 DUK_ASSERT(map != NULL);
1409 duk_pop(ctx); /* map is reachable through obj */
1410
11fdf7f2 1411 if (!duk_hobject_get_own_propdesc(thr, map, key, temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
1412 DUK_DDD(DUK_DDDPRINT("-> 'map' exists, but key not in map"));
1413 return 0;
1414 }
1415
1416 /* [... varname] */
1417 DUK_DDD(DUK_DDDPRINT("-> 'map' exists, and contains key, key is mapped to argument/variable binding %!T",
1418 (duk_tval *) duk_get_tval(ctx, -1)));
1419 DUK_ASSERT(duk_is_string(ctx, -1)); /* guaranteed when building arguments */
1420
1421 /* get varenv for varname (callee's declarative lexical environment) */
11fdf7f2 1422 rc = duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_VARENV(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
1423 DUK_UNREF(rc);
1424 DUK_ASSERT(rc != 0); /* arguments MUST have an initialized lexical environment reference */
1425 varenv = duk_require_hobject(ctx, -1);
1426 DUK_ASSERT(varenv != NULL);
1427 duk_pop(ctx); /* varenv remains reachable through 'obj' */
1428
1429 DUK_DDD(DUK_DDDPRINT("arguments varenv is: %!dO", (duk_heaphdr *) varenv));
1430
1431 /* success: leave varname in stack */
1432 *out_map = map;
1433 *out_varenv = varenv;
1434 return 1; /* [... varname] */
1435}
1436
1437/* Lookup 'key' from arguments internal 'map', and leave replacement value
1438 * on stack top if mapped (and return non-zero).
1439 * Used in E5 Section 10.6 algorithm for [[GetOwnProperty]] (used by [[Get]]).
1440 */
1441DUK_LOCAL duk_bool_t duk__check_arguments_map_for_get(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
1442 duk_context *ctx = (duk_context *) thr;
1443 duk_hobject *map;
1444 duk_hobject *varenv;
1445 duk_hstring *varname;
1446
1447 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1448
1449 if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
1450 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic get behavior"));
1451 return 0;
1452 }
1453
1454 /* [... varname] */
1455
1456 varname = duk_require_hstring(ctx, -1);
1457 DUK_ASSERT(varname != NULL);
1458 duk_pop(ctx); /* varname is still reachable */
1459
1460 DUK_DDD(DUK_DDDPRINT("arguments object automatic getvar for a bound variable; "
1461 "key=%!O, varname=%!O",
1462 (duk_heaphdr *) key,
1463 (duk_heaphdr *) varname));
1464
1465 (void) duk_js_getvar_envrec(thr, varenv, varname, 1 /*throw*/);
1466
1467 /* [... value this_binding] */
1468
1469 duk_pop(ctx);
1470
1471 /* leave result on stack top */
1472 return 1;
1473}
1474
1475/* Lookup 'key' from arguments internal 'map', perform a variable write if mapped.
1476 * Used in E5 Section 10.6 algorithm for [[DefineOwnProperty]] (used by [[Put]]).
1477 * Assumes stack top contains 'put' value (which is NOT popped).
1478 */
1479DUK_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) {
1480 duk_context *ctx = (duk_context *) thr;
1481 duk_hobject *map;
1482 duk_hobject *varenv;
1483 duk_hstring *varname;
1484
1485 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1486
1487 if (!duk__lookup_arguments_map(thr, obj, key, temp_desc, &map, &varenv)) {
1488 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic put behavior"));
1489 return;
1490 }
1491
1492 /* [... put_value varname] */
1493
1494 varname = duk_require_hstring(ctx, -1);
1495 DUK_ASSERT(varname != NULL);
1496 duk_pop(ctx); /* varname is still reachable */
1497
1498 DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
1499 "key=%!O, varname=%!O, value=%!T",
1500 (duk_heaphdr *) key,
1501 (duk_heaphdr *) varname,
1502 (duk_tval *) duk_require_tval(ctx, -1)));
1503
1504 /* [... put_value] */
1505
1506 /*
1507 * Note: although arguments object variable mappings are only established
1508 * for non-strict functions (and a call to a non-strict function created
1509 * the arguments object in question), an inner strict function may be doing
1510 * the actual property write. Hence the throw_flag applied here comes from
1511 * the property write call.
1512 */
1513
1514 duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, -1), throw_flag);
1515
1516 /* [... put_value] */
1517}
1518
1519/* Lookup 'key' from arguments internal 'map', delete mapping if found.
1520 * Used in E5 Section 10.6 algorithm for [[Delete]]. Note that the
1521 * variable/argument itself (where the map points) is not deleted.
1522 */
1523DUK_LOCAL void duk__check_arguments_map_for_delete(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_propdesc *temp_desc) {
1524 duk_context *ctx = (duk_context *) thr;
1525 duk_hobject *map;
1526
1527 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1528
11fdf7f2 1529 if (!duk_hobject_get_own_propdesc(thr, obj, DUK_HTHREAD_STRING_INT_MAP(thr), temp_desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
1530 DUK_DDD(DUK_DDDPRINT("arguments: key not mapped, no exotic delete behavior"));
1531 return;
1532 }
1533
1534 map = duk_require_hobject(ctx, -1);
1535 DUK_ASSERT(map != NULL);
1536 duk_pop(ctx); /* map is reachable through obj */
1537
1538 DUK_DDD(DUK_DDDPRINT("-> have 'map', delete key %!O from map (if exists)); ignore result",
1539 (duk_heaphdr *) key));
1540
1541 /* Note: no recursion issue, we can trust 'map' to behave */
1542 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_BEHAVIOR(map));
1543 DUK_DDD(DUK_DDDPRINT("map before deletion: %!O", (duk_heaphdr *) map));
1544 (void) duk_hobject_delprop_raw(thr, map, key, 0); /* ignore result */
1545 DUK_DDD(DUK_DDDPRINT("map after deletion: %!O", (duk_heaphdr *) map));
1546}
1547
1548/*
1549 * Ecmascript compliant [[GetOwnProperty]](P), for internal use only.
1550 *
1551 * If property is found:
1552 * - Fills descriptor fields to 'out_desc'
11fdf7f2 1553 * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the
7c673cae
FG
1554 * property onto the stack ('undefined' for accessor properties).
1555 * - Returns non-zero
1556 *
1557 * If property is not found:
1558 * - 'out_desc' is left in untouched state (possibly garbage)
11fdf7f2 1559 * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE
7c673cae
FG
1560 * set)
1561 * - Returns zero
1562 *
1563 * Notes:
1564 *
1565 * - Getting a property descriptor may cause an allocation (and hence
1566 * GC) to take place, hence reachability and refcount of all related
1567 * values matter. Reallocation of value stack, properties, etc may
1568 * invalidate many duk_tval pointers (concretely, those which reside
1569 * in memory areas subject to reallocation). However, heap object
1570 * pointers are never affected (heap objects have stable pointers).
1571 *
1572 * - The value of a plain property is always reachable and has a non-zero
1573 * reference count.
1574 *
1575 * - The value of a virtual property is not necessarily reachable from
1576 * elsewhere and may have a refcount of zero. Hence we push it onto
1577 * the valstack for the caller, which ensures it remains reachable
1578 * while it is needed.
1579 *
1580 * - There are no virtual accessor properties. Hence, all getters and
1581 * setters are always related to concretely stored properties, which
1582 * ensures that the get/set functions in the resulting descriptor are
1583 * reachable and have non-zero refcounts. Should there be virtual
1584 * accessor properties later, this would need to change.
1585 */
1586
11fdf7f2 1587DUK_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) {
7c673cae
FG
1588 duk_context *ctx = (duk_context *) thr;
1589 duk_tval *tv;
1590
11fdf7f2 1591 DUK_DDD(DUK_DDDPRINT("duk_hobject_get_own_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, "
7c673cae
FG
1592 "arr_idx=%ld (obj -> %!O, key -> %!O)",
1593 (void *) thr, (void *) obj, (void *) key, (void *) out_desc,
1594 (long) flags, (long) arr_idx,
1595 (duk_heaphdr *) obj, (duk_heaphdr *) key));
1596
1597 DUK_ASSERT(ctx != NULL);
1598 DUK_ASSERT(thr != NULL);
1599 DUK_ASSERT(thr->heap != NULL);
1600 DUK_ASSERT(obj != NULL);
1601 DUK_ASSERT(key != NULL);
1602 DUK_ASSERT(out_desc != NULL);
1603 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1604
1605 /* XXX: optimize this filling behavior later */
1606 out_desc->flags = 0;
1607 out_desc->get = NULL;
1608 out_desc->set = NULL;
1609 out_desc->e_idx = -1;
1610 out_desc->h_idx = -1;
1611 out_desc->a_idx = -1;
1612
1613 /*
1614 * Array part
1615 */
1616
1617 if (DUK_HOBJECT_HAS_ARRAY_PART(obj) && arr_idx != DUK__NO_ARRAY_INDEX) {
1618 if (arr_idx < DUK_HOBJECT_GET_ASIZE(obj)) {
1619 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx);
11fdf7f2 1620 if (!DUK_TVAL_IS_UNUSED(tv)) {
7c673cae 1621 DUK_DDD(DUK_DDDPRINT("-> found in array part"));
11fdf7f2 1622 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1623 duk_push_tval(ctx, tv);
1624 }
1625 /* implicit attributes */
1626 out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE |
1627 DUK_PROPDESC_FLAG_CONFIGURABLE |
1628 DUK_PROPDESC_FLAG_ENUMERABLE;
1629 out_desc->a_idx = arr_idx;
1630 goto prop_found;
1631 }
1632 }
1633 /* assume array part is comprehensive (contains all array indexed elements
1634 * or none of them); hence no need to check the entries part here.
1635 */
1636 DUK_DDD(DUK_DDDPRINT("-> not found as a concrete property (has array part, "
1637 "should be there if present)"));
1638 goto prop_not_found_concrete;
1639 }
1640
1641 /*
1642 * Entries part
1643 */
1644
1645 duk_hobject_find_existing_entry(thr->heap, obj, key, &out_desc->e_idx, &out_desc->h_idx);
1646 if (out_desc->e_idx >= 0) {
1647 duk_int_t e_idx = out_desc->e_idx;
1648 out_desc->flags = DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, e_idx);
1649 if (out_desc->flags & DUK_PROPDESC_FLAG_ACCESSOR) {
1650 DUK_DDD(DUK_DDDPRINT("-> found accessor property in entry part"));
1651 out_desc->get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, e_idx);
1652 out_desc->set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, e_idx);
11fdf7f2 1653 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1654 /* a dummy undefined value is pushed to make valstack
1655 * behavior uniform for caller
1656 */
1657 duk_push_undefined(ctx);
1658 }
1659 } else {
1660 DUK_DDD(DUK_DDDPRINT("-> found plain property in entry part"));
1661 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, e_idx);
11fdf7f2 1662 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1663 duk_push_tval(ctx, tv);
1664 }
1665 }
1666 goto prop_found;
1667 }
1668
1669 /*
1670 * Not found as a concrete property, check whether a String object
1671 * virtual property matches.
1672 */
1673
1674 prop_not_found_concrete:
1675
1676 if (DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj)) {
1677 DUK_DDD(DUK_DDDPRINT("string object exotic property get for key: %!O, arr_idx: %ld",
1678 (duk_heaphdr *) key, (long) arr_idx));
1679
1680 if (arr_idx != DUK__NO_ARRAY_INDEX) {
1681 duk_hstring *h_val;
1682
1683 DUK_DDD(DUK_DDDPRINT("array index exists"));
1684
1685 h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
1686 DUK_ASSERT(h_val);
1687 if (arr_idx < DUK_HSTRING_GET_CHARLEN(h_val)) {
1688 DUK_DDD(DUK_DDDPRINT("-> found, array index inside string"));
11fdf7f2 1689 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1690 duk_push_hstring(ctx, h_val);
1691 duk_substring(ctx, -1, arr_idx, arr_idx + 1); /* [str] -> [substr] */
1692 }
1693 out_desc->flags = DUK_PROPDESC_FLAG_ENUMERABLE | /* E5 Section 15.5.5.2 */
1694 DUK_PROPDESC_FLAG_VIRTUAL;
1695
1696 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
1697 return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
1698 } else {
1699 /* index is above internal string length -> property is fully normal */
1700 DUK_DDD(DUK_DDDPRINT("array index outside string -> normal property"));
1701 }
1702 } else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
1703 duk_hstring *h_val;
1704
1705 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1706
1707 h_val = duk_hobject_get_internal_value_string(thr->heap, obj);
1708 DUK_ASSERT(h_val != NULL);
11fdf7f2 1709 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1710 duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h_val));
1711 }
1712 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; /* E5 Section 15.5.5.1 */
1713
1714 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
1715 return 1; /* cannot be arguments exotic */
1716 }
1717 } else if (DUK_HOBJECT_IS_BUFFEROBJECT(obj)) {
1718 duk_hbufferobject *h_bufobj;
1719 duk_uint_t byte_off;
1720 duk_small_uint_t elem_size;
1721
1722 h_bufobj = (duk_hbufferobject *) obj;
1723 DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj);
1724 DUK_DDD(DUK_DDDPRINT("bufferobject property get for key: %!O, arr_idx: %ld",
1725 (duk_heaphdr *) key, (long) arr_idx));
1726
1727 if (arr_idx != DUK__NO_ARRAY_INDEX) {
1728 DUK_DDD(DUK_DDDPRINT("array index exists"));
1729
1730 /* Careful with wrapping: arr_idx upshift may easily wrap, whereas
1731 * length downshift won't.
1732 */
1733 if (arr_idx < (h_bufobj->length >> h_bufobj->shift)) {
1734 byte_off = arr_idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */
1735 elem_size = 1 << h_bufobj->shift;
11fdf7f2 1736 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1737 duk_uint8_t *data;
1738
1739 if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) {
1740 data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off;
1741 duk_hbufferobject_push_validated_read(ctx, h_bufobj, data, elem_size);
1742 } else {
1743 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)"));
1744 duk_push_uint(ctx, 0);
1745 }
1746 }
1747 out_desc->flags = DUK_PROPDESC_FLAG_WRITABLE |
1748 DUK_PROPDESC_FLAG_ENUMERABLE |
1749 DUK_PROPDESC_FLAG_VIRTUAL;
1750
1751 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
1752 return 1; /* cannot be e.g. arguments exotic, since exotic 'traits' are mutually exclusive */
1753 } else {
1754 /* index is above internal buffer length -> property is fully normal */
1755 DUK_DDD(DUK_DDDPRINT("array index outside buffer -> normal property"));
1756 }
1757 } else if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
1758 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1759
11fdf7f2 1760 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1761 /* Length in elements: take into account shift, but
1762 * intentionally don't check the underlying buffer here.
1763 */
1764 duk_push_uint(ctx, h_bufobj->length >> h_bufobj->shift);
1765 }
1766 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;
1767
1768 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
1769 return 1; /* cannot be arguments exotic */
1770 } else if (key == DUK_HTHREAD_STRING_BYTE_LENGTH(thr)) {
1771 /* If neutered must return 0; length is zeroed during
1772 * neutering.
1773 */
11fdf7f2 1774 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1775 duk_push_uint(ctx, h_bufobj->length);
1776 }
1777 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;
1778 return 1; /* cannot be arguments exotic */
1779 } else if (key == DUK_HTHREAD_STRING_BYTE_OFFSET(thr)) {
1780 /* If neutered must return 0; offset is zeroed during
1781 * neutering.
1782 */
11fdf7f2 1783 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1784 duk_push_uint(ctx, h_bufobj->offset);
1785 }
1786 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;
1787 return 1; /* cannot be arguments exotic */
1788 } else if (key == DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr)) {
11fdf7f2 1789 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1790 duk_push_uint(ctx, 1 << h_bufobj->shift);
1791 }
1792 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL;
1793 return 1; /* cannot be arguments exotic */
1794 }
1795 } else if (DUK_HOBJECT_HAS_EXOTIC_DUKFUNC(obj)) {
1796 DUK_DDD(DUK_DDDPRINT("duktape/c object exotic property get for key: %!O, arr_idx: %ld",
1797 (duk_heaphdr *) key, (long) arr_idx));
1798
1799 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
1800 DUK_DDD(DUK_DDDPRINT("-> found, key is 'length', length exotic behavior"));
1801
11fdf7f2 1802 if (flags & DUK_GETDESC_FLAG_PUSH_VALUE) {
7c673cae
FG
1803 duk_int16_t func_nargs = ((duk_hnativefunction *) obj)->nargs;
1804 duk_push_int(ctx, func_nargs == DUK_HNATIVEFUNCTION_NARGS_VARARGS ? 0 : func_nargs);
1805 }
1806 out_desc->flags = DUK_PROPDESC_FLAG_VIRTUAL; /* not enumerable */
1807
1808 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj));
1809 return 1; /* cannot be arguments exotic */
1810 }
1811 }
1812
1813 /* Array properties have exotic behavior but they are concrete,
1814 * so no special handling here.
1815 *
1816 * Arguments exotic behavior (E5 Section 10.6, [[GetOwnProperty]]
1817 * is only relevant as a post-check implemented below; hence no
1818 * check here.
1819 */
1820
1821 /*
1822 * Not found as concrete or virtual
1823 */
1824
1825 DUK_DDD(DUK_DDDPRINT("-> not found (virtual, entry part, or array part)"));
1826 return 0;
1827
1828 /*
1829 * Found
1830 *
1831 * Arguments object has exotic post-processing, see E5 Section 10.6,
1832 * description of [[GetOwnProperty]] variant for arguments.
1833 */
1834
1835 prop_found:
1836 DUK_DDD(DUK_DDDPRINT("-> property found, checking for arguments exotic post-behavior"));
1837
1838 /* Notes:
1839 * - only numbered indices are relevant, so arr_idx fast reject is good
1840 * (this is valid unless there are more than 4**32-1 arguments).
1841 * - since variable lookup has no side effects, this can be skipped if
11fdf7f2 1842 * DUK_GETDESC_FLAG_PUSH_VALUE is not set.
7c673cae
FG
1843 */
1844
1845 if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) &&
1846 arr_idx != DUK__NO_ARRAY_INDEX &&
11fdf7f2 1847 (flags & DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
1848 duk_propdesc temp_desc;
1849
1850 /* Magically bound variable cannot be an accessor. However,
1851 * there may be an accessor property (or a plain property) in
1852 * place with magic behavior removed. This happens e.g. when
1853 * a magic property is redefined with defineProperty().
1854 * Cannot assert for "not accessor" here.
1855 */
1856
1857 /* replaces top of stack with new value if necessary */
11fdf7f2 1858 DUK_ASSERT((flags & DUK_GETDESC_FLAG_PUSH_VALUE) != 0);
7c673cae
FG
1859
1860 if (duk__check_arguments_map_for_get(thr, obj, key, &temp_desc)) {
1861 DUK_DDD(DUK_DDDPRINT("-> arguments exotic behavior overrides result: %!T -> %!T",
1862 (duk_tval *) duk_get_tval(ctx, -2),
1863 (duk_tval *) duk_get_tval(ctx, -1)));
1864 /* [... old_result result] -> [... result] */
1865 duk_remove(ctx, -2);
1866 }
1867 }
1868
1869 return 1;
1870}
1871
11fdf7f2 1872DUK_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) {
7c673cae
FG
1873 DUK_ASSERT(thr != NULL);
1874 DUK_ASSERT(obj != NULL);
1875 DUK_ASSERT(key != NULL);
1876 DUK_ASSERT(out_desc != NULL);
1877 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1878
11fdf7f2 1879 return duk__get_own_propdesc_raw(thr, obj, key, DUK_HSTRING_GET_ARRIDX_SLOW(key), out_desc, flags);
7c673cae
FG
1880}
1881
1882/*
1883 * Ecmascript compliant [[GetProperty]](P), for internal use only.
1884 *
1885 * If property is found:
1886 * - Fills descriptor fields to 'out_desc'
11fdf7f2 1887 * - If DUK_GETDESC_FLAG_PUSH_VALUE is set, pushes a value related to the
7c673cae
FG
1888 * property onto the stack ('undefined' for accessor properties).
1889 * - Returns non-zero
1890 *
1891 * If property is not found:
1892 * - 'out_desc' is left in untouched state (possibly garbage)
11fdf7f2 1893 * - Nothing is pushed onto the stack (not even with DUK_GETDESC_FLAG_PUSH_VALUE
7c673cae
FG
1894 * set)
1895 * - Returns zero
1896 *
1897 * May cause arbitrary side effects and invalidate (most) duk_tval
1898 * pointers.
1899 */
1900
11fdf7f2 1901DUK_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) {
7c673cae
FG
1902 duk_hobject *curr;
1903 duk_uint32_t arr_idx;
1904 duk_uint_t sanity;
1905
1906 DUK_ASSERT(thr != NULL);
1907 DUK_ASSERT(thr->heap != NULL);
1908 DUK_ASSERT(obj != NULL);
1909 DUK_ASSERT(key != NULL);
1910 DUK_ASSERT(out_desc != NULL);
1911 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
1912
1913 arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
1914
11fdf7f2 1915 DUK_DDD(DUK_DDDPRINT("duk__get_propdesc: thr=%p, obj=%p, key=%p, out_desc=%p, flags=%lx, "
7c673cae
FG
1916 "arr_idx=%ld (obj -> %!O, key -> %!O)",
1917 (void *) thr, (void *) obj, (void *) key, (void *) out_desc,
1918 (long) flags, (long) arr_idx,
1919 (duk_heaphdr *) obj, (duk_heaphdr *) key));
1920
1921 curr = obj;
1922 DUK_ASSERT(curr != NULL);
1923 sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
1924 do {
11fdf7f2 1925 if (duk__get_own_propdesc_raw(thr, curr, key, arr_idx, out_desc, flags)) {
7c673cae
FG
1926 /* stack contains value (if requested), 'out_desc' is set */
1927 return 1;
1928 }
1929
1930 /* not found in 'curr', next in prototype chain; impose max depth */
1931 if (sanity-- == 0) {
11fdf7f2 1932 if (flags & DUK_GETDESC_FLAG_IGNORE_PROTOLOOP) {
7c673cae
FG
1933 /* treat like property not found */
1934 break;
1935 } else {
11fdf7f2 1936 DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
7c673cae
FG
1937 }
1938 }
1939 curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
1940 } while (curr);
1941
1942 /* out_desc is left untouched (possibly garbage), caller must use return
1943 * value to determine whether out_desc can be looked up
1944 */
1945
1946 return 0;
1947}
1948
1949/*
1950 * Shallow fast path checks for accessing array elements with numeric
1951 * indices. The goal is to try to avoid coercing an array index to an
1952 * (interned) string for the most common lookups, in particular, for
1953 * standard Array objects.
1954 *
1955 * Interning is avoided but only for a very narrow set of cases:
1956 * - Object has array part, index is within array allocation, and
1957 * value is not unused (= key exists)
1958 * - Object has no interfering exotic behavior (e.g. arguments or
1959 * string object exotic behaviors interfere, array exotic
1960 * behavior does not).
1961 *
1962 * Current shortcoming: if key does not exist (even if it is within
1963 * the array allocation range) a slow path lookup with interning is
1964 * always required. This can probably be fixed so that there is a
1965 * quick fast path for non-existent elements as well, at least for
1966 * standard Array objects.
1967 */
1968
1969DUK_LOCAL duk_tval *duk__getprop_shallow_fastpath_array_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key) {
1970 duk_tval *tv;
1971 duk_uint32_t idx;
1972
1973 DUK_UNREF(thr);
1974
1975 if (!(DUK_HOBJECT_HAS_ARRAY_PART(obj) &&
1976 !DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj) &&
1977 !DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(obj) &&
1978 !DUK_HOBJECT_IS_BUFFEROBJECT(obj) &&
1979 !DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
1980 /* Must have array part and no conflicting exotic behaviors.
1981 * Doesn't need to have array special behavior, e.g. Arguments
1982 * object has array part.
1983 */
1984 return NULL;
1985 }
1986
1987 /* Arrays never have other exotic behaviors. */
1988
1989 DUK_DDD(DUK_DDDPRINT("fast path attempt (no exotic string/arguments/buffer "
1990 "behavior, object has array part)"));
1991
1992#if defined(DUK_USE_FASTINT)
1993 if (DUK_TVAL_IS_FASTINT(tv_key)) {
1994 idx = duk__tval_fastint_to_arr_idx(tv_key);
1995 } else
1996#endif
1997 if (DUK_TVAL_IS_DOUBLE(tv_key)) {
1998 idx = duk__tval_number_to_arr_idx(tv_key);
1999 } else {
2000 DUK_DDD(DUK_DDDPRINT("key is not a number"));
2001 return NULL;
2002 }
2003
2004 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2005 * is 0xffffffffUL. We don't need to check for that explicitly
2006 * because 0xffffffffUL will never be inside object 'a_size'.
2007 */
2008
2009 if (idx >= DUK_HOBJECT_GET_ASIZE(obj)) {
2010 DUK_DDD(DUK_DDDPRINT("key is not an array index or outside array part"));
2011 return NULL;
2012 }
2013 DUK_ASSERT(idx != 0xffffffffUL);
2014 DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX);
2015
2016 /* XXX: for array instances we could take a shortcut here and assume
2017 * Array.prototype doesn't contain an array index property.
2018 */
2019
2020 DUK_DDD(DUK_DDDPRINT("key is a valid array index and inside array part"));
2021 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, idx);
11fdf7f2 2022 if (!DUK_TVAL_IS_UNUSED(tv)) {
7c673cae
FG
2023 DUK_DDD(DUK_DDDPRINT("-> fast path successful"));
2024 return tv;
2025 }
2026
2027 DUK_DDD(DUK_DDDPRINT("fast path attempt failed, fall back to slow path"));
2028 return NULL;
2029}
2030
2031DUK_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) {
2032 duk_tval *tv;
2033 duk_uint32_t idx;
7c673cae
FG
2034 duk_uint32_t old_len, new_len;
2035
2036 if (!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj) &&
2037 DUK_HOBJECT_HAS_ARRAY_PART(obj) &&
2038 DUK_HOBJECT_HAS_EXTENSIBLE(obj))) {
2039 return 0;
2040 }
11fdf7f2 2041 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); /* caller ensures */
7c673cae
FG
2042
2043#if defined(DUK_USE_FASTINT)
2044 if (DUK_TVAL_IS_FASTINT(tv_key)) {
2045 idx = duk__tval_fastint_to_arr_idx(tv_key);
2046 } else
2047#endif
2048 if (DUK_TVAL_IS_DOUBLE(tv_key)) {
2049 idx = duk__tval_number_to_arr_idx(tv_key);
2050 } else {
2051 DUK_DDD(DUK_DDDPRINT("key is not a number"));
2052 return 0;
2053 }
2054
2055 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2056 * is 0xffffffffUL. We don't need to check for that explicitly
2057 * because 0xffffffffUL will never be inside object 'a_size'.
2058 */
2059
2060 if (idx >= DUK_HOBJECT_GET_ASIZE(obj)) { /* for resizing of array part, use slow path */
2061 return 0;
2062 }
2063 DUK_ASSERT(idx != 0xffffffffUL);
2064 DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX);
2065
2066 old_len = duk__get_old_array_length(thr, obj, temp_desc);
2067
2068 if (idx >= old_len) {
2069 DUK_DDD(DUK_DDDPRINT("write new array entry requires length update "
2070 "(arr_idx=%ld, old_len=%ld)",
2071 (long) idx, (long) old_len));
2072 if (!(temp_desc->flags & DUK_PROPDESC_FLAG_WRITABLE)) {
11fdf7f2 2073 DUK_ERROR_TYPE(thr, DUK_STR_NOT_WRITABLE);
7c673cae
FG
2074 return 0; /* not reachable */
2075 }
2076 new_len = idx + 1;
2077
2078 /* No resize has occurred so temp_desc->e_idx is still OK */
2079 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, temp_desc->e_idx);
2080 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
7c673cae 2081 DUK_TVAL_SET_FASTINT_U32(tv, new_len); /* no need for decref/incref because value is a number */
7c673cae
FG
2082 } else {
2083 ;
2084 }
2085
2086 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, idx);
11fdf7f2 2087 DUK_TVAL_SET_TVAL_UPDREF(thr, tv, tv_val); /* side effects */
7c673cae
FG
2088
2089 DUK_DDD(DUK_DDDPRINT("array fast path success for index %ld", (long) idx));
2090 return 1;
2091}
2092
2093/*
2094 * Fast path for bufferobject getprop/putprop
2095 */
2096
2097DUK_LOCAL duk_bool_t duk__getprop_fastpath_bufobj_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key) {
2098 duk_context *ctx;
2099 duk_uint32_t idx;
2100 duk_hbufferobject *h_bufobj;
2101 duk_uint_t byte_off;
2102 duk_small_uint_t elem_size;
2103 duk_uint8_t *data;
2104
2105 ctx = (duk_context *) thr;
2106
2107 if (!DUK_HOBJECT_IS_BUFFEROBJECT(obj)) {
2108 return 0;
2109 }
2110 h_bufobj = (duk_hbufferobject *) obj;
2111
2112#if defined(DUK_USE_FASTINT)
2113 if (DUK_TVAL_IS_FASTINT(tv_key)) {
2114 idx = duk__tval_fastint_to_arr_idx(tv_key);
2115 } else
2116#endif
2117 if (DUK_TVAL_IS_DOUBLE(tv_key)) {
2118 idx = duk__tval_number_to_arr_idx(tv_key);
2119 } else {
2120 return 0;
2121 }
2122
2123 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2124 * is 0xffffffffUL. We don't need to check for that explicitly
2125 * because 0xffffffffUL will never be inside bufferobject length.
2126 */
2127
2128 /* Careful with wrapping (left shifting idx would be unsafe). */
2129 if (idx >= (h_bufobj->length >> h_bufobj->shift)) {
2130 return 0;
2131 }
2132 DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX);
2133
2134 byte_off = idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */
2135 elem_size = 1 << h_bufobj->shift;
2136
2137 if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) {
2138 data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off;
2139 duk_hbufferobject_push_validated_read(ctx, h_bufobj, data, elem_size);
2140 } else {
2141 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (read zero)"));
2142 duk_push_uint(ctx, 0);
2143 }
2144
2145 return 1;
2146}
2147
2148DUK_LOCAL duk_bool_t duk__putprop_fastpath_bufobj_tval(duk_hthread *thr, duk_hobject *obj, duk_tval *tv_key, duk_tval *tv_val) {
2149 duk_context *ctx;
2150 duk_uint32_t idx;
2151 duk_hbufferobject *h_bufobj;
2152 duk_uint_t byte_off;
2153 duk_small_uint_t elem_size;
2154 duk_uint8_t *data;
2155
2156 ctx = (duk_context *) thr;
2157
2158 if (!(DUK_HOBJECT_IS_BUFFEROBJECT(obj) &&
2159 DUK_TVAL_IS_NUMBER(tv_val))) {
2160 return 0;
2161 }
11fdf7f2 2162 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)); /* caller ensures; rom objects are never bufferobjects now */
7c673cae 2163
11fdf7f2 2164 h_bufobj = (duk_hbufferobject *) obj;
7c673cae
FG
2165#if defined(DUK_USE_FASTINT)
2166 if (DUK_TVAL_IS_FASTINT(tv_key)) {
2167 idx = duk__tval_fastint_to_arr_idx(tv_key);
2168 } else
2169#endif
2170 if (DUK_TVAL_IS_DOUBLE(tv_key)) {
2171 idx = duk__tval_number_to_arr_idx(tv_key);
2172 } else {
2173 return 0;
2174 }
2175
2176 /* If index is not valid, idx will be DUK__NO_ARRAY_INDEX which
2177 * is 0xffffffffUL. We don't need to check for that explicitly
2178 * because 0xffffffffUL will never be inside bufferobject length.
2179 */
2180
2181 /* Careful with wrapping (left shifting idx would be unsafe). */
2182 if (idx >= (h_bufobj->length >> h_bufobj->shift)) {
2183 return 0;
2184 }
2185 DUK_ASSERT(idx != DUK__NO_ARRAY_INDEX);
2186
2187 byte_off = idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */
2188 elem_size = 1 << h_bufobj->shift;
2189
2190 /* Value is required to be a number in the fast path so there
2191 * are no side effects in write coercion.
2192 */
2193 duk_push_tval(ctx, tv_val);
2194 DUK_ASSERT(duk_is_number(ctx, -1));
2195
2196 if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) {
2197 data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off;
2198 duk_hbufferobject_validated_write(ctx, h_bufobj, data, elem_size);
2199 } else {
2200 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (write skipped)"));
2201 }
2202
2203 duk_pop(ctx);
2204 return 1;
2205}
2206
2207/*
2208 * GETPROP: Ecmascript property read.
2209 */
2210
2211DUK_INTERNAL duk_bool_t duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
2212 duk_context *ctx = (duk_context *) thr;
2213 duk_tval tv_obj_copy;
2214 duk_tval tv_key_copy;
2215 duk_hobject *curr = NULL;
2216 duk_hstring *key = NULL;
2217 duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX;
2218 duk_propdesc desc;
2219 duk_uint_t sanity;
2220
2221 DUK_DDD(DUK_DDDPRINT("getprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
2222 (void *) thr, (void *) tv_obj, (void *) tv_key,
2223 (duk_tval *) tv_obj, (duk_tval *) tv_key));
2224
2225 DUK_ASSERT(ctx != NULL);
2226 DUK_ASSERT(thr != NULL);
2227 DUK_ASSERT(thr->heap != NULL);
2228 DUK_ASSERT(tv_obj != NULL);
2229 DUK_ASSERT(tv_key != NULL);
2230
2231 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
2232
2233 /*
2234 * Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
2235 * them being invalidated by a valstack resize.
2236 *
2237 * XXX: this is now an overkill for many fast paths. Rework this
2238 * to be faster (although switching to a valstack discipline might
2239 * be a better solution overall).
2240 */
2241
2242 DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
2243 DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
2244 tv_obj = &tv_obj_copy;
2245 tv_key = &tv_key_copy;
2246
2247 /*
2248 * Coercion and fast path processing
2249 */
2250
2251 switch (DUK_TVAL_GET_TAG(tv_obj)) {
2252 case DUK_TAG_UNDEFINED:
2253 case DUK_TAG_NULL: {
2254 /* Note: unconditional throw */
2255 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
11fdf7f2
TL
2256#if defined(DUK_USE_PARANOID_ERRORS)
2257 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE);
2258#else
2259 DUK_ERROR_FMT2(thr, DUK_ERR_TYPE_ERROR, "cannot read property %s of %s",
2260 duk_push_string_tval_readable(ctx, tv_key), duk_push_string_tval_readable(ctx, tv_obj));
2261#endif
7c673cae
FG
2262 return 0;
2263 }
2264
2265 case DUK_TAG_BOOLEAN: {
2266 DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
2267 curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
2268 break;
2269 }
2270
2271 case DUK_TAG_STRING: {
2272 duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
2273 duk_int_t pop_count;
2274
2275#if defined(DUK_USE_FASTINT)
2276 if (DUK_TVAL_IS_FASTINT(tv_key)) {
2277 arr_idx = duk__tval_fastint_to_arr_idx(tv_key);
2278 DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path fastint; arr_idx %ld", (long) arr_idx));
2279 pop_count = 0;
2280 } else
2281#endif
2282 if (DUK_TVAL_IS_NUMBER(tv_key)) {
2283 arr_idx = duk__tval_number_to_arr_idx(tv_key);
2284 DUK_DDD(DUK_DDDPRINT("base object string, key is a fast-path number; arr_idx %ld", (long) arr_idx));
2285 pop_count = 0;
2286 } else {
2287 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2288 DUK_ASSERT(key != NULL);
2289 DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
2290 "coercion key is %!T, arr_idx %ld",
2291 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
2292 pop_count = 1;
2293 }
2294
2295 if (arr_idx != DUK__NO_ARRAY_INDEX &&
2296 arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
2297 duk_pop_n(ctx, pop_count);
2298 duk_push_hstring(ctx, h);
2299 duk_substring(ctx, -1, arr_idx, arr_idx + 1); /* [str] -> [substr] */
2300
2301 DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is an index inside string length "
2302 "after coercion -> return char)",
2303 (duk_tval *) duk_get_tval(ctx, -1)));
2304 return 1;
2305 }
2306
2307 if (pop_count == 0) {
2308 /* This is a pretty awkward control flow, but we need to recheck the
2309 * key coercion here.
2310 */
2311 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2312 DUK_ASSERT(key != NULL);
2313 DUK_DDD(DUK_DDDPRINT("base object string, key is a non-fast-path number; after "
2314 "coercion key is %!T, arr_idx %ld",
2315 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
2316 }
2317
2318 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
2319 duk_pop(ctx); /* [key] -> [] */
2320 duk_push_uint(ctx, (duk_uint_t) DUK_HSTRING_GET_CHARLEN(h)); /* [] -> [res] */
2321
2322 DUK_DDD(DUK_DDDPRINT("-> %!T (base is string, key is 'length' after coercion -> "
2323 "return string length)",
2324 (duk_tval *) duk_get_tval(ctx, -1)));
2325 return 1;
2326 }
2327 DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
2328 curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
2329 goto lookup; /* avoid double coercion */
2330 }
2331
2332 case DUK_TAG_OBJECT: {
2333 duk_tval *tmp;
2334
2335 curr = DUK_TVAL_GET_OBJECT(tv_obj);
2336 DUK_ASSERT(curr != NULL);
2337
2338 tmp = duk__getprop_shallow_fastpath_array_tval(thr, curr, tv_key);
2339 if (tmp) {
2340 duk_push_tval(ctx, tmp);
2341
2342 DUK_DDD(DUK_DDDPRINT("-> %!T (base is object, key is a number, array part "
2343 "fast path)",
2344 (duk_tval *) duk_get_tval(ctx, -1)));
2345 return 1;
2346 }
2347
2348 if (duk__getprop_fastpath_bufobj_tval(thr, curr, tv_key) != 0) {
2349 /* Read value pushed on stack. */
2350 DUK_DDD(DUK_DDDPRINT("-> %!T (base is bufobj, key is a number, bufferobject "
2351 "fast path)",
2352 (duk_tval *) duk_get_tval(ctx, -1)));
2353 return 1;
2354 }
2355
2356#if defined(DUK_USE_ES6_PROXY)
2357 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(curr))) {
2358 duk_hobject *h_target;
2359
2360 if (duk__proxy_check_prop(thr, curr, DUK_STRIDX_GET, tv_key, &h_target)) {
2361 /* -> [ ... trap handler ] */
2362 DUK_DDD(DUK_DDDPRINT("-> proxy object 'get' for key %!T", (duk_tval *) tv_key));
2363 duk_push_hobject(ctx, h_target); /* target */
2364 duk_push_tval(ctx, tv_key); /* P */
2365 duk_push_tval(ctx, tv_obj); /* Receiver: Proxy object */
2366 duk_call_method(ctx, 3 /*nargs*/);
2367
2368 /* Target object must be checked for a conflicting
2369 * non-configurable property.
2370 */
2371 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2372 DUK_ASSERT(key != NULL);
2373
11fdf7f2 2374 if (duk__get_own_propdesc_raw(thr, h_target, key, arr_idx, &desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
2375 duk_tval *tv_hook = duk_require_tval(ctx, -3); /* value from hook */
2376 duk_tval *tv_targ = duk_require_tval(ctx, -1); /* value from target */
2377 duk_bool_t datadesc_reject;
2378 duk_bool_t accdesc_reject;
2379
2380 DUK_DDD(DUK_DDDPRINT("proxy 'get': target has matching property %!O, check for "
2381 "conflicting property; tv_hook=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
2382 "desc.get=%p, desc.set=%p",
2383 (duk_heaphdr *) key, (duk_tval *) tv_hook, (duk_tval *) tv_targ,
2384 (unsigned long) desc.flags,
2385 (void *) desc.get, (void *) desc.set));
2386
2387 datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
2388 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
2389 !(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
2390 !duk_js_samevalue(tv_hook, tv_targ);
2391 accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
2392 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
2393 (desc.get == NULL) &&
2394 !DUK_TVAL_IS_UNDEFINED(tv_hook);
2395 if (datadesc_reject || accdesc_reject) {
11fdf7f2 2396 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
2397 }
2398
2399 duk_pop_2(ctx);
2400 } else {
2401 duk_pop(ctx);
2402 }
2403 return 1; /* return value */
2404 }
2405
2406 curr = h_target; /* resume lookup from target */
2407 DUK_TVAL_SET_OBJECT(tv_obj, curr);
2408 }
2409#endif /* DUK_USE_ES6_PROXY */
2410
2411 if (DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(curr)) {
2412 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2413 DUK_ASSERT(key != NULL);
2414
2415 if (duk__check_arguments_map_for_get(thr, curr, key, &desc)) {
2416 DUK_DDD(DUK_DDDPRINT("-> %!T (base is object with arguments exotic behavior, "
2417 "key matches magically bound property -> skip standard "
2418 "Get with replacement value)",
2419 (duk_tval *) duk_get_tval(ctx, -1)));
2420
2421 /* no need for 'caller' post-check, because 'key' must be an array index */
2422
2423 duk_remove(ctx, -2); /* [key result] -> [result] */
2424 return 1;
2425 }
2426
2427 goto lookup; /* avoid double coercion */
2428 }
2429 break;
2430 }
2431
2432 /* Buffer has virtual properties similar to string, but indexed values
2433 * are numbers, not 1-byte buffers/strings which would perform badly.
2434 */
2435 case DUK_TAG_BUFFER: {
2436 duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj);
2437 duk_int_t pop_count;
2438
2439 /*
2440 * Because buffer values are often looped over, a number fast path
2441 * is important.
2442 */
2443
2444#if defined(DUK_USE_FASTINT)
2445 if (DUK_TVAL_IS_FASTINT(tv_key)) {
2446 arr_idx = duk__tval_fastint_to_arr_idx(tv_key);
2447 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path fastint; arr_idx %ld", (long) arr_idx));
2448 pop_count = 0;
2449 }
2450 else
2451#endif
2452 if (DUK_TVAL_IS_NUMBER(tv_key)) {
2453 arr_idx = duk__tval_number_to_arr_idx(tv_key);
2454 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx));
2455 pop_count = 0;
2456 } else {
2457 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2458 DUK_ASSERT(key != NULL);
2459 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
2460 "coercion key is %!T, arr_idx %ld",
2461 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
2462 pop_count = 1;
2463 }
2464
2465 if (arr_idx != DUK__NO_ARRAY_INDEX &&
2466 arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
2467 duk_pop_n(ctx, pop_count);
2468 duk_push_uint(ctx, ((duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h))[arr_idx]);
2469
2470 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is an index inside buffer length "
2471 "after coercion -> return byte as number)",
2472 (duk_tval *) duk_get_tval(ctx, -1)));
2473 return 1;
2474 }
2475
2476 if (pop_count == 0) {
2477 /* This is a pretty awkward control flow, but we need to recheck the
2478 * key coercion here.
2479 */
2480 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2481 DUK_ASSERT(key != NULL);
2482 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
2483 "coercion key is %!T, arr_idx %ld",
2484 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
2485 }
2486
2487 if (key == DUK_HTHREAD_STRING_LENGTH(thr) ||
2488 key == DUK_HTHREAD_STRING_BYTE_LENGTH(thr)) {
2489 duk_pop(ctx); /* [key] -> [] */
2490 duk_push_uint(ctx, (duk_uint_t) DUK_HBUFFER_GET_SIZE(h)); /* [] -> [res] */
2491
2492 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'length' or 'byteLength' "
2493 "after coercion -> return buffer length)",
2494 (duk_tval *) duk_get_tval(ctx, -1)));
2495 return 1;
2496 } else if (key == DUK_HTHREAD_STRING_BYTE_OFFSET(thr)) {
2497 duk_pop(ctx); /* [key] -> [] */
2498 duk_push_uint(ctx, 0); /* [] -> [res] */
2499
2500 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'byteOffset' after coercion -> "
2501 "return 0 for consistency with Buffer objects)",
2502 (duk_tval *) duk_get_tval(ctx, -1)));
2503 return 1;
2504 } else if (key == DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr)) {
2505 duk_pop(ctx); /* [key] -> [] */
2506 duk_push_uint(ctx, 1); /* [] -> [res] */
2507
2508 DUK_DDD(DUK_DDDPRINT("-> %!T (base is buffer, key is 'BYTES_PER_ELEMENT' after coercion -> "
2509 "return 1 for consistency with Buffer objects)",
2510 (duk_tval *) duk_get_tval(ctx, -1)));
2511 return 1;
2512 }
2513
2514 DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
2515 curr = thr->builtins[DUK_BIDX_BUFFER_PROTOTYPE];
2516 goto lookup; /* avoid double coercion */
2517 }
2518
2519 case DUK_TAG_POINTER: {
2520 DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
2521 curr = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
2522 break;
2523 }
2524
2525 case DUK_TAG_LIGHTFUNC: {
2526 duk_int_t lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_obj);
2527
2528 /* Must coerce key: if key is an object, it may coerce to e.g. 'length'. */
2529 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2530
2531 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
2532 duk_int_t lf_len = DUK_LFUNC_FLAGS_GET_LENGTH(lf_flags);
2533 duk_pop(ctx);
2534 duk_push_int(ctx, lf_len);
2535 return 1;
2536 } else if (key == DUK_HTHREAD_STRING_NAME(thr)) {
2537 duk_pop(ctx);
2538 duk_push_lightfunc_name(ctx, tv_obj);
2539 return 1;
2540 }
2541
2542 DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
2543 curr = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
2544 goto lookup; /* avoid double coercion */
2545 }
2546
2547#if defined(DUK_USE_FASTINT)
2548 case DUK_TAG_FASTINT:
2549#endif
2550 default: {
2551 /* number */
2552 DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
11fdf7f2 2553 DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_obj));
7c673cae
FG
2554 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
2555 curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
2556 break;
2557 }
2558 }
2559
2560 /* key coercion (unless already coerced above) */
2561 DUK_ASSERT(key == NULL);
2562 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2563 DUK_ASSERT(key != NULL);
2564
2565 /*
2566 * Property lookup
2567 */
2568
2569 lookup:
2570 /* [key] (coerced) */
2571 DUK_ASSERT(curr != NULL);
2572 DUK_ASSERT(key != NULL);
2573
2574 sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
2575 do {
11fdf7f2 2576 if (!duk__get_own_propdesc_raw(thr, curr, key, arr_idx, &desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
2577 goto next_in_chain;
2578 }
2579
2580 if (desc.get != NULL) {
2581 /* accessor with defined getter */
2582 DUK_ASSERT((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) != 0);
2583
2584 duk_pop(ctx); /* [key undefined] -> [key] */
2585 duk_push_hobject(ctx, desc.get);
2586 duk_push_tval(ctx, tv_obj); /* note: original, uncoerced base */
2587#ifdef DUK_USE_NONSTD_GETTER_KEY_ARGUMENT
2588 duk_dup(ctx, -3);
2589 duk_call_method(ctx, 1); /* [key getter this key] -> [key retval] */
2590#else
2591 duk_call_method(ctx, 0); /* [key getter this] -> [key retval] */
2592#endif
2593 } else {
2594 /* [key value] or [key undefined] */
2595
2596 /* data property or accessor without getter */
2597 DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
2598 (desc.get == NULL));
2599
2600 /* if accessor without getter, return value is undefined */
2601 DUK_ASSERT(((desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) == 0) ||
2602 duk_is_undefined(ctx, -1));
2603
2604 /* Note: for an accessor without getter, falling through to
2605 * check for "caller" exotic behavior is unnecessary as
2606 * "undefined" will never activate the behavior. But it does
2607 * no harm, so we'll do it anyway.
2608 */
2609 }
2610
2611 goto found; /* [key result] */
2612
2613 next_in_chain:
2614 /* XXX: option to pretend property doesn't exist if sanity limit is
2615 * hit might be useful.
2616 */
2617 if (sanity-- == 0) {
11fdf7f2 2618 DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
7c673cae
FG
2619 }
2620 curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
2621 } while (curr);
2622
2623 /*
2624 * Not found
2625 */
2626
2627 duk_to_undefined(ctx, -1); /* [key] -> [undefined] (default value) */
2628
2629 DUK_DDD(DUK_DDDPRINT("-> %!T (not found)", (duk_tval *) duk_get_tval(ctx, -1)));
2630 return 0;
2631
2632 /*
2633 * Found; post-processing (Function and arguments objects)
2634 */
2635
2636 found:
2637 /* [key result] */
2638
2639#if !defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
2640 /* Special behavior for 'caller' property of (non-bound) function objects
2641 * and non-strict Arguments objects: if 'caller' -value- (!) is a strict
2642 * mode function, throw a TypeError (E5 Sections 15.3.5.4, 10.6).
2643 * Quite interestingly, a non-strict function with no formal arguments
2644 * will get an arguments object -without- special 'caller' behavior!
2645 *
2646 * The E5.1 spec is a bit ambiguous if this special behavior applies when
2647 * a bound function is the base value (not the 'caller' value): Section
2648 * 15.3.4.5 (describing bind()) states that [[Get]] for bound functions
2649 * matches that of Section 15.3.5.4 ([[Get]] for Function instances).
2650 * However, Section 13.3.5.4 has "NOTE: Function objects created using
2651 * Function.prototype.bind use the default [[Get]] internal method."
2652 * The current implementation assumes this means that bound functions
2653 * should not have the special [[Get]] behavior.
2654 *
2655 * The E5.1 spec is also a bit unclear if the TypeError throwing is
2656 * applied if the 'caller' value is a strict bound function. The
2657 * current implementation will throw even for both strict non-bound
2658 * and strict bound functions.
2659 *
2660 * See test-dev-strict-func-as-caller-prop-value.js for quite extensive
2661 * tests.
2662 *
2663 * This exotic behavior is disabled when the non-standard 'caller' property
2664 * is enabled, as it conflicts with the free use of 'caller'.
2665 */
2666 if (key == DUK_HTHREAD_STRING_CALLER(thr) &&
2667 DUK_TVAL_IS_OBJECT(tv_obj)) {
2668 duk_hobject *orig = DUK_TVAL_GET_OBJECT(tv_obj);
2669 DUK_ASSERT(orig != NULL);
2670
2671 if (DUK_HOBJECT_IS_NONBOUND_FUNCTION(orig) ||
2672 DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig)) {
2673 duk_hobject *h;
2674
2675 /* XXX: The TypeError is currently not applied to bound
2676 * functions because the 'strict' flag is not copied by
2677 * bind(). This may or may not be correct, the specification
2678 * only refers to the value being a "strict mode Function
2679 * object" which is ambiguous.
2680 */
2681 DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(orig));
2682
2683 h = duk_get_hobject(ctx, -1); /* NULL if not an object */
2684 if (h &&
2685 DUK_HOBJECT_IS_FUNCTION(h) &&
2686 DUK_HOBJECT_HAS_STRICT(h)) {
2687 /* XXX: sufficient to check 'strict', assert for 'is function' */
11fdf7f2 2688 DUK_ERROR_TYPE(thr, DUK_STR_STRICT_CALLER_READ);
7c673cae
FG
2689 }
2690 }
2691 }
2692#endif /* !DUK_USE_NONSTD_FUNC_CALLER_PROPERTY */
2693
2694 duk_remove(ctx, -2); /* [key result] -> [result] */
2695
2696 DUK_DDD(DUK_DDDPRINT("-> %!T (found)", (duk_tval *) duk_get_tval(ctx, -1)));
2697 return 1;
2698}
2699
2700/*
2701 * HASPROP: Ecmascript property existence check ("in" operator).
2702 *
2703 * Interestingly, the 'in' operator does not do any coercion of
2704 * the target object.
2705 */
2706
2707DUK_INTERNAL duk_bool_t duk_hobject_hasprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
2708 duk_context *ctx = (duk_context *) thr;
2709 duk_tval tv_key_copy;
2710 duk_hobject *obj;
2711 duk_hstring *key;
2712 duk_uint32_t arr_idx;
2713 duk_bool_t rc;
2714 duk_propdesc desc;
2715
2716 DUK_DDD(DUK_DDDPRINT("hasprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
2717 (void *) thr, (void *) tv_obj, (void *) tv_key,
2718 (duk_tval *) tv_obj, (duk_tval *) tv_key));
2719
2720 DUK_ASSERT(thr != NULL);
2721 DUK_ASSERT(thr->heap != NULL);
2722 DUK_ASSERT(tv_obj != NULL);
2723 DUK_ASSERT(tv_key != NULL);
2724 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
2725
2726 DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
2727 tv_key = &tv_key_copy;
2728
2729 /*
2730 * The 'in' operator requires an object as its right hand side,
2731 * throwing a TypeError unconditionally if this is not the case.
2732 *
2733 * However, lightfuncs need to behave like fully fledged objects
2734 * here to be maximally transparent, so we need to handle them
2735 * here.
2736 */
2737
2738 /* XXX: Refactor key coercion so that it's only called once. It can't
2739 * be trivially lifted here because the object must be type checked
2740 * first.
2741 */
2742
2743 if (DUK_TVAL_IS_OBJECT(tv_obj)) {
2744 obj = DUK_TVAL_GET_OBJECT(tv_obj);
2745 DUK_ASSERT(obj != NULL);
2746
2747 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2748 } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj)) {
2749 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
2750 if (duk__key_is_lightfunc_ownprop(thr, key)) {
2751 /* FOUND */
2752 rc = 1;
2753 goto pop_and_return;
2754 }
2755
2756 /* If not found, resume existence check from Function.prototype.
2757 * We can just substitute the value in this case; nothing will
2758 * need the original base value (as would be the case with e.g.
2759 * setters/getters.
2760 */
2761 obj = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
2762 } else {
2763 /* Note: unconditional throw */
2764 DUK_DDD(DUK_DDDPRINT("base object is not an object -> reject"));
11fdf7f2 2765 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE);
7c673cae
FG
2766 }
2767
2768 /* XXX: fast path for arrays? */
2769
2770 DUK_ASSERT(key != NULL);
2771 DUK_ASSERT(obj != NULL);
2772 DUK_UNREF(arr_idx);
2773
2774#if defined(DUK_USE_ES6_PROXY)
2775 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
2776 duk_hobject *h_target;
2777 duk_bool_t tmp_bool;
2778
2779 /* XXX: the key in 'key in obj' is string coerced before we're called
2780 * (which is the required behavior in E5/E5.1/E6) so the key is a string
2781 * here already.
2782 */
2783
2784 if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_HAS, tv_key, &h_target)) {
2785 /* [ ... key trap handler ] */
2786 DUK_DDD(DUK_DDDPRINT("-> proxy object 'has' for key %!T", (duk_tval *) tv_key));
2787 duk_push_hobject(ctx, h_target); /* target */
2788 duk_push_tval(ctx, tv_key); /* P */
2789 duk_call_method(ctx, 2 /*nargs*/);
2790 tmp_bool = duk_to_boolean(ctx, -1);
2791 if (!tmp_bool) {
2792 /* Target object must be checked for a conflicting
2793 * non-configurable property.
2794 */
2795
11fdf7f2 2796 if (duk__get_own_propdesc_raw(thr, h_target, key, arr_idx, &desc, 0 /*flags*/)) { /* don't push value */
7c673cae
FG
2797 DUK_DDD(DUK_DDDPRINT("proxy 'has': target has matching property %!O, check for "
2798 "conflicting property; desc.flags=0x%08lx, "
2799 "desc.get=%p, desc.set=%p",
2800 (duk_heaphdr *) key, (unsigned long) desc.flags,
2801 (void *) desc.get, (void *) desc.set));
2802 /* XXX: Extensibility check for target uses IsExtensible(). If we
2803 * implemented the isExtensible trap and didn't reject proxies as
2804 * proxy targets, it should be respected here.
2805 */
2806 if (!((desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && /* property is configurable and */
2807 DUK_HOBJECT_HAS_EXTENSIBLE(h_target))) { /* ... target is extensible */
11fdf7f2 2808 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
2809 }
2810 }
2811 }
2812
2813 duk_pop_2(ctx); /* [ key trap_result ] -> [] */
2814 return tmp_bool;
2815 }
2816
2817 obj = h_target; /* resume check from proxy target */
2818 }
2819#endif /* DUK_USE_ES6_PROXY */
2820
2821 /* XXX: inline into a prototype walking loop? */
2822
11fdf7f2 2823 rc = duk__get_propdesc(thr, obj, key, &desc, 0 /*flags*/); /* don't push value */
7c673cae
FG
2824 /* fall through */
2825
2826 pop_and_return:
2827 duk_pop(ctx); /* [ key ] -> [] */
2828 return rc;
2829}
2830
2831/*
2832 * HASPROP variant used internally.
2833 *
2834 * This primitive must never throw an error, callers rely on this.
2835 * In particular, don't throw an error for prototype loops; instead,
2836 * pretend like the property doesn't exist if a prototype sanity limit
2837 * is reached.
2838 *
2839 * Does not implement proxy behavior: if applied to a proxy object,
2840 * returns key existence on the proxy object itself.
2841 */
2842
2843DUK_INTERNAL duk_bool_t duk_hobject_hasprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key) {
2844 duk_propdesc dummy;
2845
2846 DUK_ASSERT(thr != NULL);
2847 DUK_ASSERT(thr->heap != NULL);
2848 DUK_ASSERT(obj != NULL);
2849 DUK_ASSERT(key != NULL);
2850
2851 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
2852
11fdf7f2 2853 return duk__get_propdesc(thr, obj, key, &dummy, DUK_GETDESC_FLAG_IGNORE_PROTOLOOP); /* don't push value */
7c673cae
FG
2854}
2855
2856/*
2857 * Helper: handle Array object 'length' write which automatically
2858 * deletes properties, see E5 Section 15.4.5.1, step 3. This is
2859 * quite tricky to get right.
2860 *
2861 * Used by duk_hobject_putprop().
2862 */
2863
2864DUK_LOCAL duk_uint32_t duk__get_old_array_length(duk_hthread *thr, duk_hobject *obj, duk_propdesc *temp_desc) {
2865 duk_bool_t rc;
2866 duk_tval *tv;
2867 duk_uint32_t res;
2868
2869 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
2870
2871 /* This function is only called for objects with array exotic behavior.
2872 * The [[DefineOwnProperty]] algorithm for arrays requires that
2873 * 'length' can never have a value outside the unsigned 32-bit range,
2874 * attempt to write such a value is a RangeError. Here we can thus
2875 * assert for this. When Duktape internals go around the official
2876 * property write interface (doesn't happen often) this assumption is
2877 * easy to accidentally break, so such code must be written carefully.
2878 * See test-bi-array-push-maxlen.js.
2879 */
2880
11fdf7f2 2881 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 */
7c673cae
FG
2882 DUK_UNREF(rc);
2883 DUK_ASSERT(rc != 0); /* arrays MUST have a 'length' property */
2884 DUK_ASSERT(temp_desc->e_idx >= 0);
2885
2886 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, temp_desc->e_idx);
2887 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* array 'length' is always a number, as we coerce it */
2888 DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) >= 0.0);
2889 DUK_ASSERT(DUK_TVAL_GET_NUMBER(tv) <= (double) 0xffffffffUL);
2890 DUK_ASSERT((duk_double_t) (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv) == DUK_TVAL_GET_NUMBER(tv));
2891#if defined(DUK_USE_FASTINT)
2892 /* Downgrade checks are not made everywhere, so 'length' is not always
2893 * a fastint (it is a number though). This can be removed once length
2894 * is always guaranteed to be a fastint.
2895 */
2896 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv) || DUK_TVAL_IS_DOUBLE(tv));
2897 if (DUK_TVAL_IS_FASTINT(tv)) {
2898 res = (duk_uint32_t) DUK_TVAL_GET_FASTINT_U32(tv);
2899 } else {
2900 res = (duk_uint32_t) DUK_TVAL_GET_DOUBLE(tv);
2901 }
2902#else
2903 res = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv);
2904#endif /* DUK_USE_FASTINT */
2905
2906 return res;
2907}
2908
2909DUK_LOCAL duk_uint32_t duk__to_new_array_length_checked(duk_hthread *thr) {
2910 duk_context *ctx = (duk_context *) thr;
2911 duk_uint32_t res;
2912 duk_double_t d;
2913
2914 /* Input value should be on stack top and will be coerced and
2915 * popped. Refuse to update an Array's 'length' to a value
2916 * outside the 32-bit range. Negative zero is accepted as zero.
2917 */
2918
2919 /* XXX: fastint */
2920
2921 d = duk_to_number(ctx, -1);
2922 res = (duk_uint32_t) d;
2923 if ((duk_double_t) res != d) {
11fdf7f2 2924 DUK_ERROR_RANGE(thr, DUK_STR_INVALID_ARRAY_LENGTH);
7c673cae
FG
2925 }
2926 duk_pop(ctx);
2927 return res;
2928}
2929
2930/* Delete elements required by a smaller length, taking into account
2931 * potentially non-configurable elements. Returns non-zero if all
2932 * elements could be deleted, and zero if all or some elements could
2933 * not be deleted. Also writes final "target length" to 'out_result_len'.
2934 * This is the length value that should go into the 'length' property
2935 * (must be set by the caller). Never throws an error.
2936 */
2937DUK_LOCAL
2938duk_bool_t duk__handle_put_array_length_smaller(duk_hthread *thr,
2939 duk_hobject *obj,
2940 duk_uint32_t old_len,
2941 duk_uint32_t new_len,
2942 duk_bool_t force_flag,
2943 duk_uint32_t *out_result_len) {
2944 duk_uint32_t target_len;
2945 duk_uint_fast32_t i;
2946 duk_uint32_t arr_idx;
2947 duk_hstring *key;
2948 duk_tval *tv;
7c673cae
FG
2949 duk_bool_t rc;
2950
2951 DUK_DDD(DUK_DDDPRINT("new array length smaller than old (%ld -> %ld), "
2952 "probably need to remove elements",
2953 (long) old_len, (long) new_len));
2954
2955 /*
2956 * New length is smaller than old length, need to delete properties above
2957 * the new length.
2958 *
2959 * If array part exists, this is straightforward: array entries cannot
2960 * be non-configurable so this is guaranteed to work.
2961 *
2962 * If array part does not exist, array-indexed values are scattered
2963 * in the entry part, and some may not be configurable (preventing length
2964 * from becoming lower than their index + 1). To handle the algorithm
2965 * in E5 Section 15.4.5.1, step l correctly, we scan the entire property
2966 * set twice.
2967 */
2968
2969 DUK_ASSERT(thr != NULL);
2970 DUK_ASSERT(obj != NULL);
2971 DUK_ASSERT(new_len < old_len);
2972 DUK_ASSERT(out_result_len != NULL);
2973 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
2974
2975 if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
2976 /*
2977 * All defined array-indexed properties are in the array part
2978 * (we assume the array part is comprehensive), and all array
2979 * entries are writable, configurable, and enumerable. Thus,
2980 * nothing can prevent array entries from being deleted.
2981 */
2982
2983 DUK_DDD(DUK_DDDPRINT("have array part, easy case"));
2984
2985 if (old_len < DUK_HOBJECT_GET_ASIZE(obj)) {
2986 /* XXX: assertion that entries >= old_len are already unused */
2987 i = old_len;
2988 } else {
2989 i = DUK_HOBJECT_GET_ASIZE(obj);
2990 }
2991 DUK_ASSERT(i <= DUK_HOBJECT_GET_ASIZE(obj));
2992
2993 while (i > new_len) {
2994 i--;
2995 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
11fdf7f2 2996 DUK_TVAL_SET_UNUSED_UPDREF(thr, tv); /* side effects */
7c673cae
FG
2997 }
2998
2999 *out_result_len = new_len;
3000 return 1;
3001 } else {
3002 /*
3003 * Entries part is a bit more complex
3004 */
3005
3006 /* Stage 1: find highest preventing non-configurable entry (if any).
3007 * When forcing, ignore non-configurability.
3008 */
3009
3010 DUK_DDD(DUK_DDDPRINT("no array part, slow case"));
3011
3012 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 1: find target_len "
3013 "(highest preventing non-configurable entry (if any))"));
3014
3015 target_len = new_len;
3016 if (force_flag) {
3017 DUK_DDD(DUK_DDDPRINT("array length write, no array part; force flag -> skip stage 1"));
3018 goto skip_stage1;
3019 }
3020 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
3021 key = DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i);
3022 if (!key) {
3023 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i));
3024 continue;
3025 }
3026 if (!DUK_HSTRING_HAS_ARRIDX(key)) {
3027 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i));
3028 continue;
3029 }
3030
3031 DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key)); /* XXX: macro checks for array index flag, which is unnecessary here */
3032 arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
3033 DUK_ASSERT(arr_idx != DUK__NO_ARRAY_INDEX);
3034 DUK_ASSERT(arr_idx < old_len); /* consistency requires this */
3035
3036 if (arr_idx < new_len) {
3037 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below new_len",
3038 (long) i, (long) arr_idx));
3039 continue;
3040 }
3041 if (DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(thr->heap, obj, i)) {
3042 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is a relevant array index %ld, but configurable",
3043 (long) i, (long) arr_idx));
3044 continue;
3045 }
3046
3047 /* relevant array index is non-configurable, blocks write */
3048 if (arr_idx >= target_len) {
3049 DUK_DDD(DUK_DDDPRINT("entry at index %ld has arr_idx %ld, is not configurable, "
3050 "update target_len %ld -> %ld",
3051 (long) i, (long) arr_idx, (long) target_len,
3052 (long) (arr_idx + 1)));
3053 target_len = arr_idx + 1;
3054 }
3055 }
3056 skip_stage1:
3057
3058 /* stage 2: delete configurable entries above target length */
3059
3060 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld, target_len=%ld",
3061 (long) old_len, (long) new_len, (long) target_len));
3062
3063 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 2: remove "
3064 "entries >= target_len"));
3065
3066 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
3067 key = DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i);
3068 if (!key) {
3069 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: null key", (long) i));
3070 continue;
3071 }
3072 if (!DUK_HSTRING_HAS_ARRIDX(key)) {
3073 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key not an array index", (long) i));
3074 continue;
3075 }
3076
3077 DUK_ASSERT(DUK_HSTRING_HAS_ARRIDX(key)); /* XXX: macro checks for array index flag, which is unnecessary here */
3078 arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
3079 DUK_ASSERT(arr_idx != DUK__NO_ARRAY_INDEX);
3080 DUK_ASSERT(arr_idx < old_len); /* consistency requires this */
3081
3082 if (arr_idx < target_len) {
3083 DUK_DDD(DUK_DDDPRINT("skip entry index %ld: key is array index %ld, below target_len",
3084 (long) i, (long) arr_idx));
3085 continue;
3086 }
3087 DUK_ASSERT(force_flag || DUK_HOBJECT_E_SLOT_IS_CONFIGURABLE(thr->heap, obj, i)); /* stage 1 guarantees */
3088
3089 DUK_DDD(DUK_DDDPRINT("delete entry index %ld: key is array index %ld",
3090 (long) i, (long) arr_idx));
3091
3092 /*
3093 * Slow delete, but we don't care as we're already in a very slow path.
3094 * The delete always succeeds: key has no exotic behavior, property
3095 * is configurable, and no resize occurs.
3096 */
3097 rc = duk_hobject_delprop_raw(thr, obj, key, force_flag ? DUK_DELPROP_FLAG_FORCE : 0);
3098 DUK_UNREF(rc);
3099 DUK_ASSERT(rc != 0);
3100 }
3101
3102 /* stage 3: update length (done by caller), decide return code */
3103
3104 DUK_DDD(DUK_DDDPRINT("array length write, no array part, stage 3: update length (done by caller)"));
3105
3106 *out_result_len = target_len;
3107
3108 if (target_len == new_len) {
3109 DUK_DDD(DUK_DDDPRINT("target_len matches new_len, return success"));
3110 return 1;
3111 }
3112 DUK_DDD(DUK_DDDPRINT("target_len does not match new_len (some entry prevented "
3113 "full length adjustment), return error"));
3114 return 0;
3115 }
3116
3117 DUK_UNREACHABLE();
3118}
3119
3120/* XXX: is valstack top best place for argument? */
3121DUK_LOCAL duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hobject *obj) {
3122 duk_context *ctx = (duk_context *) thr;
3123 duk_propdesc desc;
3124 duk_uint32_t old_len;
3125 duk_uint32_t new_len;
3126 duk_uint32_t result_len;
3127 duk_tval *tv;
3128 duk_bool_t rc;
3129
3130 DUK_DDD(DUK_DDDPRINT("handling a put operation to array 'length' exotic property, "
3131 "new val: %!T",
3132 (duk_tval *) duk_get_tval(ctx, -1)));
3133
3134 DUK_ASSERT(thr != NULL);
3135 DUK_ASSERT(ctx != NULL);
3136 DUK_ASSERT(obj != NULL);
3137
3138 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
3139
3140 DUK_ASSERT(duk_is_valid_index(ctx, -1));
3141
3142 /*
3143 * Get old and new length
3144 */
3145
3146 old_len = duk__get_old_array_length(thr, obj, &desc);
3147 duk_dup(ctx, -1); /* [in_val in_val] */
3148 new_len = duk__to_new_array_length_checked(thr); /* -> [in_val] */
3149 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) old_len, (long) new_len));
3150
3151 /*
3152 * Writability check
3153 */
3154
3155 if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
3156 DUK_DDD(DUK_DDDPRINT("length is not writable, fail"));
3157 return 0;
3158 }
3159
3160 /*
3161 * New length not lower than old length => no changes needed
3162 * (not even array allocation).
3163 */
3164
3165 if (new_len >= old_len) {
3166 DUK_DDD(DUK_DDDPRINT("new length is higher than old length, just update length, no deletions"));
3167
3168 DUK_ASSERT(desc.e_idx >= 0);
3169 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, desc.e_idx));
3170 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, desc.e_idx);
3171 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
3172 /* no decref needed for a number */
7c673cae 3173 DUK_TVAL_SET_FASTINT_U32(tv, new_len);
7c673cae
FG
3174 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
3175 return 1;
3176 }
3177
3178 DUK_DDD(DUK_DDDPRINT("new length is lower than old length, probably must delete entries"));
3179
3180 /*
3181 * New length lower than old length => delete elements, then
3182 * update length.
3183 *
3184 * Note: even though a bunch of elements have been deleted, the 'desc' is
3185 * still valid as properties haven't been resized (and entries compacted).
3186 */
3187
3188 rc = duk__handle_put_array_length_smaller(thr, obj, old_len, new_len, 0 /*force_flag*/, &result_len);
3189 DUK_ASSERT(result_len >= new_len && result_len <= old_len);
3190
3191 DUK_ASSERT(desc.e_idx >= 0);
3192 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, desc.e_idx));
3193 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, desc.e_idx);
3194 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
3195 /* no decref needed for a number */
7c673cae 3196 DUK_TVAL_SET_FASTINT_U32(tv, result_len);
7c673cae
FG
3197 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
3198
3199 /* XXX: shrink array allocation or entries compaction here? */
3200
3201 return rc;
3202}
3203
3204/*
3205 * PUTPROP: Ecmascript property write.
3206 *
3207 * Unlike Ecmascript primitive which returns nothing, returns 1 to indicate
3208 * success and 0 to indicate failure (assuming throw is not set).
3209 *
3210 * This is an extremely tricky function. Some examples:
3211 *
3212 * * Currently a decref may trigger a GC, which may compact an object's
3213 * property allocation. Consequently, any entry indices (e_idx) will
3214 * be potentially invalidated by a decref.
3215 *
3216 * * Exotic behaviors (strings, arrays, arguments object) require,
3217 * among other things:
3218 *
3219 * - Preprocessing before and postprocessing after an actual property
3220 * write. For example, array index write requires pre-checking the
3221 * array 'length' property for access control, and may require an
3222 * array 'length' update after the actual write has succeeded (but
3223 * not if it fails).
3224 *
3225 * - Deletion of multiple entries, as a result of array 'length' write.
3226 *
3227 * * Input values are taken as pointers which may point to the valstack.
3228 * If valstack is resized because of the put (this may happen at least
3229 * when the array part is abandoned), the pointers can be invalidated.
3230 * (We currently make a copy of all of the input values to avoid issues.)
3231 */
3232
3233DUK_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) {
3234 duk_context *ctx = (duk_context *) thr;
3235 duk_tval tv_obj_copy;
3236 duk_tval tv_key_copy;
3237 duk_tval tv_val_copy;
3238 duk_hobject *orig = NULL; /* NULL if tv_obj is primitive */
3239 duk_hobject *curr;
3240 duk_hstring *key = NULL;
3241 duk_propdesc desc;
3242 duk_tval *tv;
3243 duk_uint32_t arr_idx;
3244 duk_bool_t rc;
3245 duk_int_t e_idx;
3246 duk_uint_t sanity;
3247 duk_uint32_t new_array_length = 0; /* 0 = no update */
3248
3249 DUK_DDD(DUK_DDDPRINT("putprop: thr=%p, obj=%p, key=%p, val=%p, throw=%ld "
3250 "(obj -> %!T, key -> %!T, val -> %!T)",
3251 (void *) thr, (void *) tv_obj, (void *) tv_key, (void *) tv_val,
3252 (long) throw_flag, (duk_tval *) tv_obj, (duk_tval *) tv_key, (duk_tval *) tv_val));
3253
3254 DUK_ASSERT(thr != NULL);
3255 DUK_ASSERT(thr->heap != NULL);
3256 DUK_ASSERT(ctx != NULL);
3257 DUK_ASSERT(tv_obj != NULL);
3258 DUK_ASSERT(tv_key != NULL);
3259 DUK_ASSERT(tv_val != NULL);
3260
3261 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
3262
3263 /*
3264 * Make a copy of tv_obj, tv_key, and tv_val to avoid any issues of
3265 * them being invalidated by a valstack resize.
3266 *
3267 * XXX: this is an overkill for some paths, so optimize this later
3268 * (or maybe switch to a stack arguments model entirely).
3269 */
3270
3271 DUK_TVAL_SET_TVAL(&tv_obj_copy, tv_obj);
3272 DUK_TVAL_SET_TVAL(&tv_key_copy, tv_key);
3273 DUK_TVAL_SET_TVAL(&tv_val_copy, tv_val);
3274 tv_obj = &tv_obj_copy;
3275 tv_key = &tv_key_copy;
3276 tv_val = &tv_val_copy;
3277
3278 /*
3279 * Coercion and fast path processing.
3280 */
3281
3282 switch (DUK_TVAL_GET_TAG(tv_obj)) {
3283 case DUK_TAG_UNDEFINED:
3284 case DUK_TAG_NULL: {
3285 /* Note: unconditional throw */
3286 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject (object=%!iT)",
3287 (duk_tval *) tv_obj));
11fdf7f2
TL
3288#if defined(DUK_USE_PARANOID_ERRORS)
3289 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE);
3290#else
3291 DUK_ERROR_FMT2(thr, DUK_ERR_TYPE_ERROR, "cannot write property %s of %s",
3292 duk_push_string_tval_readable(ctx, tv_key), duk_push_string_tval_readable(ctx, tv_obj));
3293#endif
7c673cae
FG
3294 return 0;
3295 }
3296
3297 case DUK_TAG_BOOLEAN: {
3298 DUK_DDD(DUK_DDDPRINT("base object is a boolean, start lookup from boolean prototype"));
3299 curr = thr->builtins[DUK_BIDX_BOOLEAN_PROTOTYPE];
3300 break;
3301 }
3302
3303 case DUK_TAG_STRING: {
3304 duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
3305
3306 /*
3307 * Note: currently no fast path for array index writes.
3308 * They won't be possible anyway as strings are immutable.
3309 */
3310
3311 DUK_ASSERT(key == NULL);
3312 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3313 DUK_ASSERT(key != NULL);
3314
3315 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
3316 goto fail_not_writable;
3317 }
3318
3319 if (arr_idx != DUK__NO_ARRAY_INDEX &&
3320 arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
3321 goto fail_not_writable;
3322 }
3323
3324 DUK_DDD(DUK_DDDPRINT("base object is a string, start lookup from string prototype"));
3325 curr = thr->builtins[DUK_BIDX_STRING_PROTOTYPE];
3326 goto lookup; /* avoid double coercion */
3327 }
3328
3329 case DUK_TAG_OBJECT: {
3330 orig = DUK_TVAL_GET_OBJECT(tv_obj);
3331 DUK_ASSERT(orig != NULL);
3332
11fdf7f2
TL
3333#if defined(DUK_USE_ROM_OBJECTS)
3334 /* With this check in place fast paths won't need read-only
3335 * object checks. This is technically incorrect if there are
3336 * setters that cause no writes to ROM objects, but current
3337 * built-ins don't have such setters.
3338 */
3339 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) orig)) {
3340 DUK_DD(DUK_DDPRINT("attempt to putprop on read-only target object"));
3341 goto fail_not_writable_no_pop; /* Must avoid duk_pop() in exit path */
3342 }
3343#endif
3344
7c673cae
FG
3345 /* The fast path for array property put is not fully compliant:
3346 * If one places conflicting number-indexed properties into
3347 * Array.prototype (for example, a non-writable Array.prototype[7])
3348 * the fast path will incorrectly ignore them.
3349 *
3350 * This fast path could be made compliant by falling through
11fdf7f2
TL
3351 * to the slow path if the previous value was UNUSED. This would
3352 * also remove the need to check for extensibility. Right now a
3353 * non-extensible array is slower than an extensible one as far
3354 * as writes are concerned.
7c673cae
FG
3355 *
3356 * The fast path behavior is documented in more detail here:
3357 * tests/ecmascript/test-misc-array-fast-write.js
3358 */
3359
3360 if (duk__putprop_shallow_fastpath_array_tval(thr, orig, tv_key, tv_val, &desc) != 0) {
3361 DUK_DDD(DUK_DDDPRINT("array fast path success"));
3362 return 1;
3363 }
3364
3365 if (duk__putprop_fastpath_bufobj_tval(thr, orig, tv_key, tv_val) != 0) {
3366 DUK_DDD(DUK_DDDPRINT("base is bufobj, key is a number, bufferobject fast path"));
3367 return 1;
3368 }
3369
3370#if defined(DUK_USE_ES6_PROXY)
3371 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(orig))) {
3372 duk_hobject *h_target;
3373 duk_bool_t tmp_bool;
3374
3375 if (duk__proxy_check_prop(thr, orig, DUK_STRIDX_SET, tv_key, &h_target)) {
3376 /* -> [ ... trap handler ] */
3377 DUK_DDD(DUK_DDDPRINT("-> proxy object 'set' for key %!T", (duk_tval *) tv_key));
3378 duk_push_hobject(ctx, h_target); /* target */
3379 duk_push_tval(ctx, tv_key); /* P */
3380 duk_push_tval(ctx, tv_val); /* V */
3381 duk_push_tval(ctx, tv_obj); /* Receiver: Proxy object */
3382 duk_call_method(ctx, 4 /*nargs*/);
3383 tmp_bool = duk_to_boolean(ctx, -1);
3384 duk_pop(ctx);
3385 if (!tmp_bool) {
3386 goto fail_proxy_rejected;
3387 }
3388
3389 /* Target object must be checked for a conflicting
3390 * non-configurable property.
3391 */
3392 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3393 DUK_ASSERT(key != NULL);
3394
11fdf7f2 3395 if (duk__get_own_propdesc_raw(thr, h_target, key, arr_idx, &desc, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
3396 duk_tval *tv_targ = duk_require_tval(ctx, -1);
3397 duk_bool_t datadesc_reject;
3398 duk_bool_t accdesc_reject;
3399
3400 DUK_DDD(DUK_DDDPRINT("proxy 'set': target has matching property %!O, check for "
3401 "conflicting property; tv_val=%!T, tv_targ=%!T, desc.flags=0x%08lx, "
3402 "desc.get=%p, desc.set=%p",
3403 (duk_heaphdr *) key, (duk_tval *) tv_val, (duk_tval *) tv_targ,
3404 (unsigned long) desc.flags,
3405 (void *) desc.get, (void *) desc.set));
3406
3407 datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
3408 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
3409 !(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
3410 !duk_js_samevalue(tv_val, tv_targ);
3411 accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
3412 !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
3413 (desc.set == NULL);
3414 if (datadesc_reject || accdesc_reject) {
11fdf7f2 3415 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
3416 }
3417
3418 duk_pop_2(ctx);
3419 } else {
3420 duk_pop(ctx);
3421 }
3422 return 1; /* success */
3423 }
3424
3425 orig = h_target; /* resume write to target */
3426 DUK_TVAL_SET_OBJECT(tv_obj, orig);
3427 }
3428#endif /* DUK_USE_ES6_PROXY */
3429
3430 curr = orig;
3431 break;
3432 }
3433
3434 case DUK_TAG_BUFFER: {
3435 duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj);
3436 duk_int_t pop_count = 0;
3437
3438 /*
3439 * Because buffer values may be looped over and read/written
3440 * from, an array index fast path is important.
3441 */
3442
3443#if defined(DUK_USE_FASTINT)
3444 if (DUK_TVAL_IS_FASTINT(tv_key)) {
3445 arr_idx = duk__tval_fastint_to_arr_idx(tv_key);
3446 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path fastint; arr_idx %ld", (long) arr_idx));
3447 pop_count = 0;
3448 } else
3449#endif
3450 if (DUK_TVAL_IS_NUMBER(tv_key)) {
3451 arr_idx = duk__tval_number_to_arr_idx(tv_key);
3452 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a fast-path number; arr_idx %ld", (long) arr_idx));
3453 pop_count = 0;
3454 } else {
3455 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3456 DUK_ASSERT(key != NULL);
3457 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
3458 "coercion key is %!T, arr_idx %ld",
3459 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
3460 pop_count = 1;
3461 }
3462
3463 if (arr_idx != DUK__NO_ARRAY_INDEX &&
3464 arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
3465 duk_uint8_t *data;
3466 DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx));
3467 data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h);
3468
3469 /* XXX: duk_to_int() ensures we'll get 8 lowest bits as
3470 * as input is within duk_int_t range (capped outside it).
3471 */
3472#if defined(DUK_USE_FASTINT)
3473 /* Buffer writes are often integers. */
3474 if (DUK_TVAL_IS_FASTINT(tv_val)) {
3475 data[arr_idx] = (duk_uint8_t) DUK_TVAL_GET_FASTINT_U32(tv_val);
3476 }
3477 else
3478#endif
3479 {
3480 duk_push_tval(ctx, tv_val);
3481 data[arr_idx] = (duk_uint8_t) duk_to_uint32(ctx, -1);
3482 pop_count++;
3483 }
3484
3485 duk_pop_n(ctx, pop_count);
3486 DUK_DDD(DUK_DDDPRINT("result: success (buffer data write)"));
3487 return 1;
3488 }
3489
3490 if (pop_count == 0) {
3491 /* This is a pretty awkward control flow, but we need to recheck the
3492 * key coercion here.
3493 */
3494 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3495 DUK_ASSERT(key != NULL);
3496 DUK_DDD(DUK_DDDPRINT("base object buffer, key is a non-fast-path number; after "
3497 "coercion key is %!T, arr_idx %ld",
3498 (duk_tval *) duk_get_tval(ctx, -1), (long) arr_idx));
3499 }
3500
3501 if (key == DUK_HTHREAD_STRING_LENGTH(thr) ||
3502 key == DUK_HTHREAD_STRING_BYTE_LENGTH(thr) ||
3503 key == DUK_HTHREAD_STRING_BYTE_OFFSET(thr) ||
3504 key == DUK_HTHREAD_STRING_BYTES_PER_ELEMENT(thr)) {
3505 goto fail_not_writable;
3506 }
3507
3508 DUK_DDD(DUK_DDDPRINT("base object is a buffer, start lookup from buffer prototype"));
3509 curr = thr->builtins[DUK_BIDX_BUFFER_PROTOTYPE];
3510 goto lookup; /* avoid double coercion */
3511 }
3512
3513 case DUK_TAG_POINTER: {
3514 DUK_DDD(DUK_DDDPRINT("base object is a pointer, start lookup from pointer prototype"));
3515 curr = thr->builtins[DUK_BIDX_POINTER_PROTOTYPE];
3516 break;
3517 }
3518
3519 case DUK_TAG_LIGHTFUNC: {
3520 /* All lightfunc own properties are non-writable and the lightfunc
3521 * is considered non-extensible. However, the write may be captured
3522 * by an inherited setter which means we can't stop the lookup here.
3523 */
3524
3525 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3526
3527 if (duk__key_is_lightfunc_ownprop(thr, key)) {
3528 goto fail_not_writable;
3529 }
3530
3531 DUK_DDD(DUK_DDDPRINT("base object is a lightfunc, start lookup from function prototype"));
3532 curr = thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE];
3533 goto lookup; /* avoid double coercion */
3534 }
3535
3536#if defined(DUK_USE_FASTINT)
3537 case DUK_TAG_FASTINT:
3538#endif
3539 default: {
3540 /* number */
3541 DUK_DDD(DUK_DDDPRINT("base object is a number, start lookup from number prototype"));
3542 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_obj));
3543 curr = thr->builtins[DUK_BIDX_NUMBER_PROTOTYPE];
3544 break;
3545 }
3546 }
3547
3548 DUK_ASSERT(key == NULL);
3549 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
3550 DUK_ASSERT(key != NULL);
3551
3552 lookup:
3553
3554 /*
3555 * Check whether the property already exists in the prototype chain.
3556 * Note that the actual write goes into the original base object
3557 * (except if an accessor property captures the write).
3558 */
3559
3560 /* [key] */
3561
3562 DUK_ASSERT(curr != NULL);
3563 sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY;
3564 do {
11fdf7f2 3565 if (!duk__get_own_propdesc_raw(thr, curr, key, arr_idx, &desc, 0 /*flags*/)) { /* don't push value */
7c673cae
FG
3566 goto next_in_chain;
3567 }
3568
3569 if (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
3570 /*
3571 * Found existing accessor property (own or inherited).
3572 * Call setter with 'this' set to orig, and value as the only argument.
11fdf7f2 3573 * Setter calls are OK even for ROM objects.
7c673cae
FG
3574 *
3575 * Note: no exotic arguments object behavior, because [[Put]] never
3576 * calls [[DefineOwnProperty]] (E5 Section 8.12.5, step 5.b).
3577 */
3578
3579 duk_hobject *setter;
3580
3581 DUK_DD(DUK_DDPRINT("put to an own or inherited accessor, calling setter"));
3582
3583 setter = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, curr, desc.e_idx);
3584 if (!setter) {
3585 goto fail_no_setter;
3586 }
3587 duk_push_hobject(ctx, setter);
3588 duk_push_tval(ctx, tv_obj); /* note: original, uncoerced base */
3589 duk_push_tval(ctx, tv_val); /* [key setter this val] */
3590#ifdef DUK_USE_NONSTD_SETTER_KEY_ARGUMENT
3591 duk_dup(ctx, -4);
3592 duk_call_method(ctx, 2); /* [key setter this val key] -> [key retval] */
3593#else
3594 duk_call_method(ctx, 1); /* [key setter this val] -> [key retval] */
3595#endif
3596 duk_pop(ctx); /* ignore retval -> [key] */
3597 goto success_no_arguments_exotic;
3598 }
3599
3600 if (orig == NULL) {
3601 /*
3602 * Found existing own or inherited plain property, but original
3603 * base is a primitive value.
3604 */
3605 DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
3606 goto fail_base_primitive;
3607 }
3608
3609 if (curr != orig) {
3610 /*
3611 * Found existing inherited plain property.
3612 * Do an access control check, and if OK, write
3613 * new property to 'orig'.
3614 */
3615 if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
3616 DUK_DD(DUK_DDPRINT("found existing inherited plain property, but original object is not extensible"));
3617 goto fail_not_extensible;
3618 }
3619 if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
3620 DUK_DD(DUK_DDPRINT("found existing inherited plain property, original object is extensible, but inherited property is not writable"));
3621 goto fail_not_writable;
3622 }
3623 DUK_DD(DUK_DDPRINT("put to new property, object extensible, inherited property found and is writable"));
3624 goto create_new;
3625 } else {
3626 /*
3627 * Found existing own (non-inherited) plain property.
3628 * Do an access control check and update in place.
3629 */
3630
3631 if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
3632 DUK_DD(DUK_DDPRINT("found existing own (non-inherited) plain property, but property is not writable"));
3633 goto fail_not_writable;
3634 }
3635 if (desc.flags & DUK_PROPDESC_FLAG_VIRTUAL) {
3636 DUK_DD(DUK_DDPRINT("found existing own (non-inherited) virtual property, property is writable"));
3637 if (DUK_HOBJECT_IS_BUFFEROBJECT(curr)) {
3638 duk_hbufferobject *h_bufobj;
3639 duk_uint_t byte_off;
3640 duk_small_uint_t elem_size;
3641
3642 h_bufobj = (duk_hbufferobject *) curr;
3643 DUK_ASSERT_HBUFFEROBJECT_VALID(h_bufobj);
3644
3645 DUK_DD(DUK_DDPRINT("writable virtual property is in buffer object"));
3646
3647 /* Careful with wrapping: arr_idx upshift may easily wrap, whereas
3648 * length downshift won't.
3649 */
3650 if (arr_idx < (h_bufobj->length >> h_bufobj->shift)) {
3651 duk_uint8_t *data;
3652 DUK_DDD(DUK_DDDPRINT("writing to buffer data at index %ld", (long) arr_idx));
3653
3654 DUK_ASSERT(arr_idx != DUK__NO_ARRAY_INDEX); /* index/length check guarantees */
3655 byte_off = arr_idx << h_bufobj->shift; /* no wrap assuming h_bufobj->length is valid */
3656 elem_size = 1 << h_bufobj->shift;
3657
3658 /* Coerce to number before validating pointers etc so that the
3659 * number coercions in duk_hbufferobject_validated_write() are
3660 * guaranteed to be side effect free and not invalidate the
3661 * pointer checks we do here.
3662 */
3663 duk_push_tval(ctx, tv_val);
3664 duk_to_number(ctx, -1);
3665
3666 if (h_bufobj->buf != NULL && DUK_HBUFFEROBJECT_VALID_BYTEOFFSET_EXCL(h_bufobj, byte_off + elem_size)) {
3667 data = (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_bufobj->buf) + h_bufobj->offset + byte_off;
3668 duk_hbufferobject_validated_write(ctx, h_bufobj, data, elem_size);
3669 } else {
3670 DUK_D(DUK_DPRINT("bufferobject access out of underlying buffer, ignoring (write skipped)"));
3671 }
3672 duk_pop(ctx);
3673 goto success_no_arguments_exotic;
3674 }
3675 }
3676
3677 goto fail_internal; /* should not happen */
3678 }
3679 DUK_DD(DUK_DDPRINT("put to existing own plain property, property is writable"));
3680 goto update_old;
3681 }
3682 DUK_UNREACHABLE();
3683
3684 next_in_chain:
3685 /* XXX: option to pretend property doesn't exist if sanity limit is
3686 * hit might be useful.
3687 */
3688 if (sanity-- == 0) {
11fdf7f2 3689 DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT);
7c673cae
FG
3690 }
3691 curr = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, curr);
3692 } while (curr);
3693
3694 /*
3695 * Property not found in prototype chain.
3696 */
3697
3698 DUK_DDD(DUK_DDDPRINT("property not found in prototype chain"));
3699
3700 if (orig == NULL) {
3701 DUK_DD(DUK_DDPRINT("attempt to create a new property in a primitive base object"));
3702 goto fail_base_primitive;
3703 }
3704
3705 if (!DUK_HOBJECT_HAS_EXTENSIBLE(orig)) {
3706 DUK_DD(DUK_DDPRINT("put to a new property (not found in prototype chain), but original object not extensible"));
3707 goto fail_not_extensible;
3708 }
3709
3710 goto create_new;
3711
3712 update_old:
3713
3714 /*
3715 * Update an existing property of the base object.
3716 */
3717
3718 /* [key] */
3719
3720 DUK_DDD(DUK_DDDPRINT("update an existing property of the original object"));
3721
3722 DUK_ASSERT(orig != NULL);
11fdf7f2
TL
3723#if defined(DUK_USE_ROM_OBJECTS)
3724 /* This should not happen because DUK_TAG_OBJECT case checks
3725 * for this already, but check just in case.
3726 */
3727 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) orig)) {
3728 goto fail_not_writable;
3729 }
3730#endif
7c673cae
FG
3731
3732 /* Although there are writable virtual properties (e.g. plain buffer
3733 * and buffer object number indices), they are handled before we come
3734 * here.
3735 */
3736 DUK_ASSERT((desc.flags & DUK_PROPDESC_FLAG_VIRTUAL) == 0);
3737 DUK_ASSERT(desc.a_idx >= 0 || desc.e_idx >= 0);
3738
3739 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
3740 key == DUK_HTHREAD_STRING_LENGTH(thr)) {
3741 /*
3742 * Write to 'length' of an array is a very complex case
3743 * handled in a helper which updates both the array elements
3744 * and writes the new 'length'. The write may result in an
3745 * unconditional RangeError or a partial write (indicated
3746 * by a return code).
3747 *
3748 * Note: the helper has an unnecessary writability check
3749 * for 'length', we already know it is writable.
3750 */
3751
3752 DUK_DDD(DUK_DDDPRINT("writing existing 'length' property to array exotic, invoke complex helper"));
3753
3754 /* XXX: the helper currently assumes stack top contains new
3755 * 'length' value and the whole calling convention is not very
3756 * compatible with what we need.
3757 */
3758
3759 duk_push_tval(ctx, tv_val); /* [key val] */
3760 rc = duk__handle_put_array_length(thr, orig);
3761 duk_pop(ctx); /* [key val] -> [key] */
3762 if (!rc) {
3763 goto fail_array_length_partial;
3764 }
3765
3766 /* key is 'length', cannot match argument exotic behavior */
3767 goto success_no_arguments_exotic;
3768 }
3769
3770 if (desc.e_idx >= 0) {
7c673cae
FG
3771 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, orig, desc.e_idx);
3772 DUK_DDD(DUK_DDDPRINT("previous entry value: %!iT", (duk_tval *) tv));
11fdf7f2 3773 DUK_TVAL_SET_TVAL_UPDREF(thr, tv, tv_val); /* side effects */
7c673cae
FG
3774 /* don't touch property attributes or hash part */
3775 DUK_DD(DUK_DDPRINT("put to an existing entry at index %ld -> new value %!iT",
3776 (long) desc.e_idx, (duk_tval *) tv));
3777 } else {
3778 /* Note: array entries are always writable, so the writability check
3779 * above is pointless for them. The check could be avoided with some
3780 * refactoring but is probably not worth it.
3781 */
7c673cae
FG
3782
3783 DUK_ASSERT(desc.a_idx >= 0);
3784 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, orig, desc.a_idx);
3785 DUK_DDD(DUK_DDDPRINT("previous array value: %!iT", (duk_tval *) tv));
11fdf7f2 3786 DUK_TVAL_SET_TVAL_UPDREF(thr, tv, tv_val); /* side effects */
7c673cae
FG
3787 DUK_DD(DUK_DDPRINT("put to an existing array entry at index %ld -> new value %!iT",
3788 (long) desc.a_idx, (duk_tval *) tv));
3789 }
3790
3791 /* Regardless of whether property is found in entry or array part,
3792 * it may have arguments exotic behavior (array indices may reside
3793 * in entry part for abandoned / non-existent array parts).
3794 */
3795 goto success_with_arguments_exotic;
3796
3797 create_new:
3798
3799 /*
3800 * Create a new property in the original object.
3801 *
3802 * Exotic properties need to be reconsidered here from a write
3803 * perspective (not just property attributes perspective).
3804 * However, the property does not exist in the object already,
3805 * so this limits the kind of exotic properties that apply.
3806 */
3807
3808 /* [key] */
3809
3810 DUK_DDD(DUK_DDDPRINT("create new property to original object"));
3811
3812 DUK_ASSERT(orig != NULL);
3813
11fdf7f2
TL
3814#if defined(DUK_USE_ROM_OBJECTS)
3815 /* This should not happen because DUK_TAG_OBJECT case checks
3816 * for this already, but check just in case.
3817 */
3818 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) orig)) {
3819 goto fail_not_writable;
3820 }
3821#endif
3822
7c673cae
FG
3823 /* Not possible because array object 'length' is present
3824 * from its creation and cannot be deleted, and is thus
3825 * caught as an existing property above.
3826 */
3827 DUK_ASSERT(!(DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
3828 key == DUK_HTHREAD_STRING_LENGTH(thr)));
3829
3830 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(orig) &&
3831 arr_idx != DUK__NO_ARRAY_INDEX) {
3832 /* automatic length update */
3833 duk_uint32_t old_len;
3834
3835 old_len = duk__get_old_array_length(thr, orig, &desc);
3836
3837 if (arr_idx >= old_len) {
3838 DUK_DDD(DUK_DDDPRINT("write new array entry requires length update "
3839 "(arr_idx=%ld, old_len=%ld)",
3840 (long) arr_idx, (long) old_len));
3841
3842 if (!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
3843 DUK_DD(DUK_DDPRINT("attempt to extend array, but array 'length' is not writable"));
3844 goto fail_not_writable;
3845 }
3846
3847 /* Note: actual update happens once write has been completed
3848 * without error below. The write should always succeed
3849 * from a specification viewpoint, but we may e.g. run out
3850 * of memory. It's safer in this order.
3851 */
3852
3853 DUK_ASSERT(arr_idx != 0xffffffffUL);
3854 new_array_length = arr_idx + 1; /* flag for later write */
3855 } else {
3856 DUK_DDD(DUK_DDDPRINT("write new array entry does not require length update "
3857 "(arr_idx=%ld, old_len=%ld)",
3858 (long) arr_idx, (long) old_len));
3859 }
3860 }
3861
3862 /* write_to_array_part: */
3863
3864 /*
3865 * Write to array part?
3866 *
3867 * Note: array abandonding requires a property resize which uses
3868 * 'rechecks' valstack for temporaries and may cause any existing
3869 * valstack pointers to be invalidated. To protect against this,
3870 * tv_obj, tv_key, and tv_val are copies of the original inputs.
3871 */
3872
3873 if (arr_idx != DUK__NO_ARRAY_INDEX &&
3874 DUK_HOBJECT_HAS_ARRAY_PART(orig)) {
3875 if (arr_idx < DUK_HOBJECT_GET_ASIZE(orig)) {
3876 goto no_array_growth;
3877 }
3878
3879 /*
3880 * Array needs to grow, but we don't want it becoming too sparse.
3881 * If it were to become sparse, abandon array part, moving all
3882 * array entries into the entries part (for good).
3883 *
3884 * Since we don't keep track of actual density (used vs. size) of
3885 * the array part, we need to estimate somehow. The check is made
3886 * in two parts:
3887 *
3888 * - Check whether the resize need is small compared to the
3889 * current size (relatively); if so, resize without further
3890 * checking (essentially we assume that the original part is
3891 * "dense" so that the result would be dense enough).
3892 *
3893 * - Otherwise, compute the resize using an actual density
3894 * measurement based on counting the used array entries.
3895 */
3896
3897 DUK_DDD(DUK_DDDPRINT("write to new array requires array resize, decide whether to do a "
3898 "fast resize without abandon check (arr_idx=%ld, old_size=%ld)",
3899 (long) arr_idx, (long) DUK_HOBJECT_GET_ASIZE(orig)));
3900
3901 if (duk__abandon_array_slow_check_required(arr_idx, DUK_HOBJECT_GET_ASIZE(orig))) {
3902 duk_uint32_t old_used;
3903 duk_uint32_t old_size;
3904
3905 DUK_DDD(DUK_DDDPRINT("=> fast check is NOT OK, do slow check for array abandon"));
3906
3907 duk__compute_a_stats(thr, orig, &old_used, &old_size);
3908
3909 DUK_DDD(DUK_DDDPRINT("abandon check, array stats: old_used=%ld, old_size=%ld, arr_idx=%ld",
3910 (long) old_used, (long) old_size, (long) arr_idx));
3911
3912 /* Note: intentionally use approximations to shave a few instructions:
3913 * a_used = old_used (accurate: old_used + 1)
3914 * a_size = arr_idx (accurate: arr_idx + 1)
3915 */
3916 if (duk__abandon_array_density_check(old_used, arr_idx)) {
3917 DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
3918 "decided to abandon array part (would become too sparse)"));
3919
3920 /* abandoning requires a props allocation resize and
3921 * 'rechecks' the valstack, invalidating any existing
3922 * valstack value pointers!
3923 */
3924 duk__abandon_array_checked(thr, orig);
3925 DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig));
3926
3927 goto write_to_entry_part;
3928 }
3929
3930 DUK_DDD(DUK_DDDPRINT("=> decided to keep array part"));
3931 } else {
3932 DUK_DDD(DUK_DDDPRINT("=> fast resize is OK"));
3933 }
3934
3935 DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, "
3936 "decided to extend current allocation"));
3937
3938 duk__grow_props_for_array_item(thr, orig, arr_idx);
3939
3940 no_array_growth:
3941
3942 /* Note: assume array part is comprehensive, so that either
3943 * the write goes to the array part, or we've abandoned the
3944 * array above (and will not come here).
3945 */
3946
3947 DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(orig));
3948 DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(orig));
3949
3950 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, orig, arr_idx);
3951 /* prev value must be unused, no decref */
11fdf7f2 3952 DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv));
7c673cae
FG
3953 DUK_TVAL_SET_TVAL(tv, tv_val);
3954 DUK_TVAL_INCREF(thr, tv);
3955 DUK_DD(DUK_DDPRINT("put to new array entry: %ld -> %!T",
3956 (long) arr_idx, (duk_tval *) tv));
3957
3958 /* Note: array part values are [[Writable]], [[Enumerable]],
3959 * and [[Configurable]] which matches the required attributes
3960 * here.
3961 */
3962 goto entry_updated;
3963 }
3964
3965 write_to_entry_part:
3966
3967 /*
3968 * Write to entry part
3969 */
3970
3971 /* entry allocation updates hash part and increases the key
3972 * refcount; may need a props allocation resize but doesn't
3973 * 'recheck' the valstack.
3974 */
3975 e_idx = duk__alloc_entry_checked(thr, orig, key);
3976 DUK_ASSERT(e_idx >= 0);
3977
3978 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, orig, e_idx);
3979 /* prev value can be garbage, no decref */
3980 DUK_TVAL_SET_TVAL(tv, tv_val);
3981 DUK_TVAL_INCREF(thr, tv);
3982 DUK_HOBJECT_E_SET_FLAGS(thr->heap, orig, e_idx, DUK_PROPDESC_FLAGS_WEC);
3983 goto entry_updated;
3984
3985 entry_updated:
3986
3987 /*
3988 * Possible pending array length update, which must only be done
3989 * if the actual entry write succeeded.
3990 */
3991
3992 if (new_array_length > 0) {
3993 /*
3994 * Note: zero works as a "no update" marker because the new length
3995 * can never be zero after a new property is written.
3996 *
3997 * Note: must re-lookup because calls above (e.g. duk__alloc_entry_checked())
3998 * may realloc and compact properties and hence change e_idx.
3999 */
4000
4001 DUK_DDD(DUK_DDDPRINT("write successful, pending array length update to: %ld",
4002 (long) new_array_length));
4003
11fdf7f2 4004 rc = duk__get_own_propdesc_raw(thr, orig, DUK_HTHREAD_STRING_LENGTH(thr), DUK__NO_ARRAY_INDEX, &desc, 0 /*flags*/); /* don't push value */
7c673cae
FG
4005 DUK_UNREF(rc);
4006 DUK_ASSERT(rc != 0);
4007 DUK_ASSERT(desc.e_idx >= 0);
4008
4009 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, orig, desc.e_idx);
4010 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
4011 /* no need for decref/incref because value is a number */
7c673cae 4012 DUK_TVAL_SET_FASTINT_U32(tv, new_array_length);
7c673cae
FG
4013 }
4014
4015 /*
4016 * Arguments exotic behavior not possible for new properties: all
4017 * magically bound properties are initially present in the arguments
4018 * object, and if they are deleted, the binding is also removed from
4019 * parameter map.
4020 */
4021
4022 goto success_no_arguments_exotic;
4023
4024 success_with_arguments_exotic:
4025
4026 /*
4027 * Arguments objects have exotic [[DefineOwnProperty]] which updates
4028 * the internal 'map' of arguments for writes to currently mapped
4029 * arguments. More conretely, writes to mapped arguments generate
4030 * a write to a bound variable.
4031 *
4032 * The [[Put]] algorithm invokes [[DefineOwnProperty]] for existing
4033 * data properties and new properties, but not for existing accessors.
4034 * Hence, in E5 Section 10.6 ([[DefinedOwnProperty]] algorithm), we
4035 * have a Desc with 'Value' (and possibly other properties too), and
4036 * we end up in step 5.b.i.
4037 */
4038
4039 if (arr_idx != DUK__NO_ARRAY_INDEX &&
4040 DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(orig)) {
4041 /* Note: only numbered indices are relevant, so arr_idx fast reject
4042 * is good (this is valid unless there are more than 4**32-1 arguments).
4043 */
4044
4045 DUK_DDD(DUK_DDDPRINT("putprop successful, arguments exotic behavior needed"));
4046
4047 /* Note: we can reuse 'desc' here */
4048
4049 /* XXX: top of stack must contain value, which helper doesn't touch,
4050 * rework to use tv_val directly?
4051 */
4052
4053 duk_push_tval(ctx, tv_val);
4054 (void) duk__check_arguments_map_for_put(thr, orig, key, &desc, throw_flag);
4055 duk_pop(ctx);
4056 }
4057 /* fall thru */
4058
4059 success_no_arguments_exotic:
4060 /* shared exit path now */
4061 DUK_DDD(DUK_DDDPRINT("result: success"));
4062 duk_pop(ctx); /* remove key */
4063 return 1;
4064
11fdf7f2 4065#if defined(DUK_USE_ES6_PROXY)
7c673cae
FG
4066 fail_proxy_rejected:
4067 DUK_DDD(DUK_DDDPRINT("result: error, proxy rejects"));
4068 if (throw_flag) {
11fdf7f2 4069 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
4070 }
4071 /* Note: no key on stack */
4072 return 0;
11fdf7f2 4073#endif
7c673cae
FG
4074
4075 fail_base_primitive:
4076 DUK_DDD(DUK_DDDPRINT("result: error, base primitive"));
4077 if (throw_flag) {
11fdf7f2
TL
4078#if defined(DUK_USE_PARANOID_ERRORS)
4079 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE);
4080#else
4081 DUK_ERROR_FMT2(thr, DUK_ERR_TYPE_ERROR, "cannot write property %s of %s",
4082 duk_push_string_tval_readable(ctx, tv_key), duk_push_string_tval_readable(ctx, tv_obj));
4083#endif
7c673cae
FG
4084 }
4085 duk_pop(ctx); /* remove key */
4086 return 0;
4087
4088 fail_not_extensible:
4089 DUK_DDD(DUK_DDDPRINT("result: error, not extensible"));
4090 if (throw_flag) {
11fdf7f2 4091 DUK_ERROR_TYPE(thr, DUK_STR_NOT_EXTENSIBLE);
7c673cae
FG
4092 }
4093 duk_pop(ctx); /* remove key */
4094 return 0;
4095
4096 fail_not_writable:
4097 DUK_DDD(DUK_DDDPRINT("result: error, not writable"));
4098 if (throw_flag) {
11fdf7f2 4099 DUK_ERROR_TYPE(thr, DUK_STR_NOT_WRITABLE);
7c673cae
FG
4100 }
4101 duk_pop(ctx); /* remove key */
4102 return 0;
4103
11fdf7f2
TL
4104#if defined(DUK_USE_ROM_OBJECTS)
4105 fail_not_writable_no_pop:
4106 DUK_DDD(DUK_DDDPRINT("result: error, not writable"));
4107 if (throw_flag) {
4108 DUK_ERROR_TYPE(thr, DUK_STR_NOT_WRITABLE);
4109 }
4110 return 0;
4111#endif
4112
7c673cae
FG
4113 fail_array_length_partial:
4114 DUK_DDD(DUK_DDDPRINT("result: error, array length write only partially successful"));
4115 if (throw_flag) {
11fdf7f2 4116 DUK_ERROR_TYPE(thr, DUK_STR_ARRAY_LENGTH_WRITE_FAILED);
7c673cae
FG
4117 }
4118 duk_pop(ctx); /* remove key */
4119 return 0;
4120
4121 fail_no_setter:
4122 DUK_DDD(DUK_DDDPRINT("result: error, accessor property without setter"));
4123 if (throw_flag) {
11fdf7f2 4124 DUK_ERROR_TYPE(thr, DUK_STR_SETTER_UNDEFINED);
7c673cae
FG
4125 }
4126 duk_pop(ctx); /* remove key */
4127 return 0;
4128
4129 fail_internal:
4130 DUK_DDD(DUK_DDDPRINT("result: error, internal"));
4131 if (throw_flag) {
11fdf7f2 4132 DUK_ERROR_INTERNAL_DEFMSG(thr);
7c673cae
FG
4133 }
4134 duk_pop(ctx); /* remove key */
4135 return 0;
4136}
4137
4138/*
4139 * Ecmascript compliant [[Delete]](P, Throw).
4140 */
4141
4142DUK_INTERNAL duk_bool_t duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags) {
4143 duk_propdesc desc;
4144 duk_tval *tv;
7c673cae
FG
4145 duk_uint32_t arr_idx;
4146 duk_bool_t throw_flag;
4147 duk_bool_t force_flag;
4148
4149 throw_flag = (flags & DUK_DELPROP_FLAG_THROW);
4150 force_flag = (flags & DUK_DELPROP_FLAG_FORCE);
4151
4152 DUK_DDD(DUK_DDDPRINT("delprop_raw: thr=%p, obj=%p, key=%p, throw=%ld, force=%ld (obj -> %!O, key -> %!O)",
4153 (void *) thr, (void *) obj, (void *) key, (long) throw_flag, (long) force_flag,
4154 (duk_heaphdr *) obj, (duk_heaphdr *) key));
4155
4156 DUK_ASSERT(thr != NULL);
4157 DUK_ASSERT(thr->heap != NULL);
4158 DUK_ASSERT(obj != NULL);
4159 DUK_ASSERT(key != NULL);
4160
4161 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
4162
4163 arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
4164
4165 /* 0 = don't push current value */
11fdf7f2 4166 if (!duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &desc, 0 /*flags*/)) { /* don't push value */
7c673cae
FG
4167 DUK_DDD(DUK_DDDPRINT("property not found, succeed always"));
4168 goto success;
4169 }
4170
11fdf7f2
TL
4171#if defined(DUK_USE_ROM_OBJECTS)
4172 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) {
4173 DUK_DD(DUK_DDPRINT("attempt to delprop on read-only target object"));
4174 goto fail_not_configurable;
4175 }
4176#endif
4177
7c673cae
FG
4178 if ((desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) == 0 && !force_flag) {
4179 goto fail_not_configurable;
4180 }
4181 if (desc.a_idx < 0 && desc.e_idx < 0) {
4182 /* Currently there are no deletable virtual properties, but
4183 * with force_flag we might attempt to delete one.
4184 */
4185 goto fail_virtual;
4186 }
4187
4188 if (desc.a_idx >= 0) {
4189 DUK_ASSERT(desc.e_idx < 0);
4190
4191 tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, desc.a_idx);
11fdf7f2 4192 DUK_TVAL_SET_UNUSED_UPDREF(thr, tv); /* side effects */
7c673cae
FG
4193 goto success;
4194 } else {
11fdf7f2
TL
4195 duk_hobject *h_get = NULL;
4196 duk_hobject *h_set = NULL;
4197 duk_tval tv_tmp;
4198
7c673cae
FG
4199 DUK_ASSERT(desc.a_idx < 0);
4200
11fdf7f2
TL
4201 /* Set property slot to an empty state. Careful not to invoke
4202 * any side effects while using desc.e_idx so that it doesn't
4203 * get invalidated by a finalizer mutating our object.
4204 */
4205
7c673cae
FG
4206 /* remove hash entry (no decref) */
4207#if defined(DUK_USE_HOBJECT_HASH_PART)
4208 if (desc.h_idx >= 0) {
4209 duk_uint32_t *h_base = DUK_HOBJECT_H_GET_BASE(thr->heap, obj);
4210
4211 DUK_DDD(DUK_DDDPRINT("removing hash entry at h_idx %ld", (long) desc.h_idx));
4212 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj) > 0);
4213 DUK_ASSERT((duk_uint32_t) desc.h_idx < DUK_HOBJECT_GET_HSIZE(obj));
4214 h_base[desc.h_idx] = DUK__HASH_DELETED;
4215 } else {
4216 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj) == 0);
4217 }
4218#else
4219 DUK_ASSERT(DUK_HOBJECT_GET_HSIZE(obj) == 0);
4220#endif
4221
4222 /* remove value */
4223 DUK_DDD(DUK_DDDPRINT("before removing value, e_idx %ld, key %p, key at slot %p",
4224 (long) desc.e_idx, (void *) key, (void *) DUK_HOBJECT_E_GET_KEY(thr->heap, obj, desc.e_idx)));
4225 DUK_DDD(DUK_DDDPRINT("removing value at e_idx %ld", (long) desc.e_idx));
11fdf7f2
TL
4226 DUK_MEMSET((void *) &tv_tmp, 0, sizeof(tv_tmp));
4227 DUK_TVAL_SET_UNDEFINED(&tv_tmp);
7c673cae 4228 if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, desc.e_idx)) {
11fdf7f2
TL
4229 h_get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, desc.e_idx);
4230 h_set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, desc.e_idx);
7c673cae 4231 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, desc.e_idx, NULL);
7c673cae 4232 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, desc.e_idx, NULL);
7c673cae
FG
4233 } else {
4234 tv = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, desc.e_idx);
4235 DUK_TVAL_SET_TVAL(&tv_tmp, tv);
11fdf7f2 4236 DUK_TVAL_SET_UNDEFINED(tv);
7c673cae 4237 }
11fdf7f2
TL
4238#if 0
4239 /* Not strictly necessary because if key == NULL, flag MUST be ignored. */
7c673cae 4240 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, desc.e_idx, 0);
11fdf7f2 4241#endif
7c673cae
FG
4242
4243 /* remove key */
4244 DUK_DDD(DUK_DDDPRINT("before removing key, e_idx %ld, key %p, key at slot %p",
4245 (long) desc.e_idx, (void *) key, (void *) DUK_HOBJECT_E_GET_KEY(thr->heap, obj, desc.e_idx)));
4246 DUK_DDD(DUK_DDDPRINT("removing key at e_idx %ld", (long) desc.e_idx));
4247 DUK_ASSERT(key == DUK_HOBJECT_E_GET_KEY(thr->heap, obj, desc.e_idx));
4248 DUK_HOBJECT_E_SET_KEY(thr->heap, obj, desc.e_idx, NULL);
11fdf7f2
TL
4249
4250 /* Do decrefs only with safe pointers to avoid side effects
4251 * disturbing e_idx.
4252 */
4253 DUK_TVAL_DECREF(thr, &tv_tmp);
4254 DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_get);
4255 DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_set);
7c673cae
FG
4256 DUK_HSTRING_DECREF(thr, key);
4257 goto success;
4258 }
4259
4260 DUK_UNREACHABLE();
4261
4262 success:
4263 /*
4264 * Argument exotic [[Delete]] behavior (E5 Section 10.6) is
4265 * a post-check, keeping arguments internal 'map' in sync with
4266 * any successful deletes (note that property does not need to
4267 * exist for delete to 'succeed').
4268 *
4269 * Delete key from 'map'. Since 'map' only contains array index
4270 * keys, we can use arr_idx for a fast skip.
4271 */
4272
4273 DUK_DDD(DUK_DDDPRINT("delete successful, check for arguments exotic behavior"));
4274
4275 if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) {
4276 /* Note: only numbered indices are relevant, so arr_idx fast reject
4277 * is good (this is valid unless there are more than 4**32-1 arguments).
4278 */
4279
4280 DUK_DDD(DUK_DDDPRINT("delete successful, arguments exotic behavior needed"));
4281
4282 /* Note: we can reuse 'desc' here */
4283 (void) duk__check_arguments_map_for_delete(thr, obj, key, &desc);
4284 }
4285
4286 DUK_DDD(DUK_DDDPRINT("delete successful"));
4287 return 1;
4288
4289 fail_virtual:
4290 DUK_DDD(DUK_DDDPRINT("delete failed: property found, force flag, but virtual"));
4291
4292 if (throw_flag) {
11fdf7f2 4293 DUK_ERROR_TYPE(thr, DUK_STR_PROPERTY_IS_VIRTUAL);
7c673cae
FG
4294 }
4295 return 0;
4296
4297 fail_not_configurable:
4298 DUK_DDD(DUK_DDDPRINT("delete failed: property found, not configurable"));
4299
4300 if (throw_flag) {
11fdf7f2 4301 DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE);
7c673cae
FG
4302 }
4303 return 0;
4304}
4305
4306/*
4307 * DELPROP: Ecmascript property deletion.
4308 */
4309
4310DUK_INTERNAL duk_bool_t duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, duk_bool_t throw_flag) {
4311 duk_context *ctx = (duk_context *) thr;
4312 duk_hstring *key = NULL;
4313#if defined(DUK_USE_ES6_PROXY)
4314 duk_propdesc desc;
4315#endif
4316 duk_int_t entry_top;
4317 duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX;
4318 duk_bool_t rc;
4319
4320 DUK_DDD(DUK_DDDPRINT("delprop: thr=%p, obj=%p, key=%p (obj -> %!T, key -> %!T)",
4321 (void *) thr, (void *) tv_obj, (void *) tv_key,
4322 (duk_tval *) tv_obj, (duk_tval *) tv_key));
4323
4324 DUK_ASSERT(ctx != NULL);
4325 DUK_ASSERT(thr != NULL);
4326 DUK_ASSERT(thr->heap != NULL);
4327 DUK_ASSERT(tv_obj != NULL);
4328 DUK_ASSERT(tv_key != NULL);
4329
4330 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
4331
4332 /* Storing the entry top is cheaper here to ensure stack is correct at exit,
4333 * as there are several paths out.
4334 */
4335 entry_top = duk_get_top(ctx);
4336
4337 if (DUK_TVAL_IS_UNDEFINED(tv_obj) ||
4338 DUK_TVAL_IS_NULL(tv_obj)) {
4339 DUK_DDD(DUK_DDDPRINT("base object is undefined or null -> reject"));
4340 goto fail_invalid_base_uncond;
4341 }
4342
4343 duk_push_tval(ctx, tv_obj);
4344 duk_push_tval(ctx, tv_key);
4345
11fdf7f2 4346 tv_obj = DUK_GET_TVAL_NEGIDX(ctx, -2);
7c673cae
FG
4347 if (DUK_TVAL_IS_OBJECT(tv_obj)) {
4348 duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_obj);
4349 DUK_ASSERT(obj != NULL);
4350
4351#if defined(DUK_USE_ES6_PROXY)
4352 if (DUK_UNLIKELY(DUK_HOBJECT_HAS_EXOTIC_PROXYOBJ(obj))) {
4353 duk_hobject *h_target;
4354 duk_bool_t tmp_bool;
4355
4356 /* Note: proxy handling must happen before key is string coerced. */
4357
4358 if (duk__proxy_check_prop(thr, obj, DUK_STRIDX_DELETE_PROPERTY, tv_key, &h_target)) {
11fdf7f2 4359 /* -> [ ... obj key trap handler ] */
7c673cae
FG
4360 DUK_DDD(DUK_DDDPRINT("-> proxy object 'deleteProperty' for key %!T", (duk_tval *) tv_key));
4361 duk_push_hobject(ctx, h_target); /* target */
11fdf7f2 4362 duk_dup(ctx, -4); /* P */
7c673cae
FG
4363 duk_call_method(ctx, 2 /*nargs*/);
4364 tmp_bool = duk_to_boolean(ctx, -1);
4365 duk_pop(ctx);
4366 if (!tmp_bool) {
4367 goto fail_proxy_rejected; /* retval indicates delete failed */
4368 }
4369
4370 /* Target object must be checked for a conflicting
4371 * non-configurable property.
4372 */
11fdf7f2 4373 tv_key = DUK_GET_TVAL_NEGIDX(ctx, -1);
7c673cae
FG
4374 arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
4375 DUK_ASSERT(key != NULL);
4376
11fdf7f2 4377 if (duk__get_own_propdesc_raw(thr, h_target, key, arr_idx, &desc, 0 /*flags*/)) { /* don't push value */
7c673cae
FG
4378 int desc_reject;
4379
4380 DUK_DDD(DUK_DDDPRINT("proxy 'deleteProperty': target has matching property %!O, check for "
4381 "conflicting property; desc.flags=0x%08lx, "
4382 "desc.get=%p, desc.set=%p",
4383 (duk_heaphdr *) key, (unsigned long) desc.flags,
4384 (void *) desc.get, (void *) desc.set));
4385
4386 desc_reject = !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE);
4387 if (desc_reject) {
4388 /* unconditional */
11fdf7f2 4389 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
4390 }
4391 }
4392 rc = 1; /* success */
4393 goto done_rc;
4394 }
4395
4396 obj = h_target; /* resume delete to target */
4397 }
4398#endif /* DUK_USE_ES6_PROXY */
4399
4400 duk_to_string(ctx, -1);
4401 key = duk_get_hstring(ctx, -1);
4402 DUK_ASSERT(key != NULL);
4403
4404 rc = duk_hobject_delprop_raw(thr, obj, key, throw_flag ? DUK_DELPROP_FLAG_THROW : 0);
4405 goto done_rc;
4406 } else if (DUK_TVAL_IS_STRING(tv_obj)) {
4407 /* XXX: unnecessary string coercion for array indices,
4408 * intentional to keep small.
4409 */
4410 duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
4411 DUK_ASSERT(h != NULL);
4412
4413 duk_to_string(ctx, -1);
4414 key = duk_get_hstring(ctx, -1);
4415 DUK_ASSERT(key != NULL);
4416
4417 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
4418 goto fail_not_configurable;
4419 }
4420
4421 arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
4422
4423 if (arr_idx != DUK__NO_ARRAY_INDEX &&
4424 arr_idx < DUK_HSTRING_GET_CHARLEN(h)) {
4425 goto fail_not_configurable;
4426 }
4427 } else if (DUK_TVAL_IS_BUFFER(tv_obj)) {
4428 /* XXX: unnecessary string coercion for array indices,
4429 * intentional to keep small; some overlap with string
4430 * handling.
4431 */
4432 duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv_obj);
4433 DUK_ASSERT(h != NULL);
4434
4435 duk_to_string(ctx, -1);
4436 key = duk_get_hstring(ctx, -1);
4437 DUK_ASSERT(key != NULL);
4438
4439 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
4440 goto fail_not_configurable;
4441 }
4442
4443 arr_idx = DUK_HSTRING_GET_ARRIDX_FAST(key);
4444
4445 if (arr_idx != DUK__NO_ARRAY_INDEX &&
4446 arr_idx < DUK_HBUFFER_GET_SIZE(h)) {
4447 goto fail_not_configurable;
4448 }
4449 } else if (DUK_TVAL_IS_LIGHTFUNC(tv_obj)) {
4450 /* Lightfunc virtual properties are non-configurable, so
4451 * reject if match any of them.
4452 */
4453
4454 duk_to_string(ctx, -1);
4455 key = duk_get_hstring(ctx, -1);
4456 DUK_ASSERT(key != NULL);
4457
4458 if (duk__key_is_lightfunc_ownprop(thr, key)) {
4459 goto fail_not_configurable;
4460 }
4461 }
4462
4463 /* non-object base, no offending virtual property */
4464 rc = 1;
4465 goto done_rc;
4466
4467 done_rc:
4468 duk_set_top(ctx, entry_top);
4469 return rc;
4470
4471 fail_invalid_base_uncond:
4472 /* Note: unconditional throw */
4473 DUK_ASSERT(duk_get_top(ctx) == entry_top);
11fdf7f2
TL
4474#if defined(DUK_USE_PARANOID_ERRORS)
4475 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BASE);
4476#else
4477 DUK_ERROR_FMT2(thr, DUK_ERR_TYPE_ERROR, "cannot delete property %s of %s",
4478 duk_push_string_tval_readable(ctx, tv_key), duk_push_string_tval_readable(ctx, tv_obj));
4479#endif
7c673cae
FG
4480 return 0;
4481
11fdf7f2 4482#if defined(DUK_USE_ES6_PROXY)
7c673cae
FG
4483 fail_proxy_rejected:
4484 if (throw_flag) {
11fdf7f2 4485 DUK_ERROR_TYPE(thr, DUK_STR_PROXY_REJECTED);
7c673cae
FG
4486 }
4487 duk_set_top(ctx, entry_top);
4488 return 0;
11fdf7f2 4489#endif
7c673cae
FG
4490
4491 fail_not_configurable:
4492 if (throw_flag) {
11fdf7f2 4493 DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE);
7c673cae
FG
4494 }
4495 duk_set_top(ctx, entry_top);
4496 return 0;
4497}
4498
4499/*
4500 * Internal helper to define a property with specific flags, ignoring
4501 * normal semantics such as extensibility, write protection etc.
4502 * Overwrites any existing value and attributes unless caller requests
4503 * that value only be updated if it doesn't already exists.
4504 *
4505 * Does not support:
4506 * - virtual properties (error if write attempted)
4507 * - getter/setter properties (error if write attempted)
4508 * - non-default (!= WEC) attributes for array entries (error if attempted)
4509 * - array abandoning: if array part exists, it is always extended
4510 * - array 'length' updating
4511 *
4512 * Stack: [... in_val] -> []
4513 *
4514 * Used for e.g. built-in initialization and environment record
4515 * operations.
4516 */
4517
4518DUK_INTERNAL void duk_hobject_define_property_internal(duk_hthread *thr, duk_hobject *obj, duk_hstring *key, duk_small_uint_t flags) {
4519 duk_context *ctx = (duk_context *) thr;
4520 duk_propdesc desc;
4521 duk_uint32_t arr_idx;
4522 duk_int_t e_idx;
7c673cae
FG
4523 duk_tval *tv1 = NULL;
4524 duk_tval *tv2 = NULL;
4525 duk_small_uint_t propflags = flags & DUK_PROPDESC_FLAGS_MASK; /* mask out flags not actually stored */
4526
4527 DUK_DDD(DUK_DDDPRINT("define new property (internal): thr=%p, obj=%!O, key=%!O, flags=0x%02lx, val=%!T",
4528 (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key,
4529 (unsigned long) flags, (duk_tval *) duk_get_tval(ctx, -1)));
4530
4531 DUK_ASSERT(thr != NULL);
4532 DUK_ASSERT(thr->heap != NULL);
4533 DUK_ASSERT(obj != NULL);
4534 DUK_ASSERT(key != NULL);
11fdf7f2 4535 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
7c673cae
FG
4536 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
4537 DUK_ASSERT(duk_is_valid_index(ctx, -1)); /* contains value */
4538
4539 arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
4540
11fdf7f2 4541 if (duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &desc, 0 /*flags*/)) { /* don't push value */
7c673cae
FG
4542 if (desc.e_idx >= 0) {
4543 if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
4544 DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> skip as requested"));
4545 goto pop_exit;
4546 }
4547 DUK_DDD(DUK_DDDPRINT("property already exists in the entry part -> update value and attributes"));
4548 if (DUK_UNLIKELY(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, desc.e_idx))) {
4549 DUK_D(DUK_DPRINT("existing property is an accessor, not supported"));
4550 goto error_internal;
4551 }
4552
4553 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, desc.e_idx, propflags);
4554 tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, desc.e_idx);
4555 } else if (desc.a_idx >= 0) {
4556 if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
4557 DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> skip as requested"));
4558 goto pop_exit;
4559 }
4560 DUK_DDD(DUK_DDDPRINT("property already exists in the array part -> update value (assert attributes)"));
4561 if (propflags != DUK_PROPDESC_FLAGS_WEC) {
4562 DUK_D(DUK_DPRINT("existing property in array part, but propflags not WEC (0x%02lx)",
4563 (unsigned long) propflags));
4564 goto error_internal;
4565 }
4566
4567 tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, desc.a_idx);
4568 } else {
4569 if (flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) {
4570 DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> skip as requested"));
4571 goto pop_exit;
4572 }
4573 DUK_DDD(DUK_DDDPRINT("property already exists but is virtual -> failure"));
4574 goto error_virtual;
4575 }
4576
4577 goto write_value;
4578 }
4579
4580 if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
4581 if (arr_idx != DUK__NO_ARRAY_INDEX) {
4582 DUK_DDD(DUK_DDDPRINT("property does not exist, object has array part -> possibly extend array part and write value (assert attributes)"));
4583 DUK_ASSERT(propflags == DUK_PROPDESC_FLAGS_WEC);
4584
4585 /* always grow the array, no sparse / abandon support here */
4586 if (arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)) {
4587 duk__grow_props_for_array_item(thr, obj, arr_idx);
4588 }
4589
4590 DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(obj));
4591 tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx);
4592 goto write_value;
4593 }
4594 }
4595
4596 DUK_DDD(DUK_DDDPRINT("property does not exist, object belongs in entry part -> allocate new entry and write value and attributes"));
4597 e_idx = duk__alloc_entry_checked(thr, obj, key); /* increases key refcount */
4598 DUK_ASSERT(e_idx >= 0);
4599 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, e_idx, propflags);
4600 tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, e_idx);
4601 /* new entry: previous value is garbage; set to undefined to share write_value */
11fdf7f2 4602 DUK_TVAL_SET_UNDEFINED(tv1);
7c673cae
FG
4603 goto write_value;
4604
4605 write_value:
4606 /* tv1 points to value storage */
4607
4608 tv2 = duk_require_tval(ctx, -1); /* late lookup, avoid side effects */
4609 DUK_DDD(DUK_DDDPRINT("writing/updating value: %!T -> %!T",
4610 (duk_tval *) tv1, (duk_tval *) tv2));
4611
11fdf7f2 4612 DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */
7c673cae
FG
4613 goto pop_exit;
4614
4615 pop_exit:
4616 duk_pop(ctx); /* remove in_val */
4617 return;
4618
4619 error_internal:
11fdf7f2 4620 DUK_ERROR_INTERNAL_DEFMSG(thr);
7c673cae
FG
4621 return;
4622
4623 error_virtual:
11fdf7f2 4624 DUK_ERROR_TYPE(thr, DUK_STR_REDEFINE_VIRT_PROP);
7c673cae
FG
4625 return;
4626}
4627
4628/*
4629 * Fast path for defining array indexed values without interning the key.
4630 * This is used by e.g. code for Array prototype and traceback creation so
4631 * must avoid interning.
4632 */
4633
4634DUK_INTERNAL void duk_hobject_define_property_internal_arridx(duk_hthread *thr, duk_hobject *obj, duk_uarridx_t arr_idx, duk_small_uint_t flags) {
4635 duk_context *ctx = (duk_context *) thr;
4636 duk_hstring *key;
4637 duk_tval *tv1, *tv2;
7c673cae
FG
4638
4639 DUK_DDD(DUK_DDDPRINT("define new property (internal) arr_idx fast path: thr=%p, obj=%!O, "
4640 "arr_idx=%ld, flags=0x%02lx, val=%!T",
4641 (void *) thr, obj, (long) arr_idx, (unsigned long) flags,
4642 (duk_tval *) duk_get_tval(ctx, -1)));
4643
4644 DUK_ASSERT(thr != NULL);
4645 DUK_ASSERT(thr->heap != NULL);
4646 DUK_ASSERT(obj != NULL);
11fdf7f2 4647 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
7c673cae
FG
4648
4649 if (DUK_HOBJECT_HAS_ARRAY_PART(obj) &&
4650 arr_idx != DUK__NO_ARRAY_INDEX &&
4651 flags == DUK_PROPDESC_FLAGS_WEC) {
4652 DUK_ASSERT((flags & DUK_PROPDESC_FLAG_NO_OVERWRITE) == 0); /* covered by comparison */
4653
4654 DUK_DDD(DUK_DDDPRINT("define property to array part (property may or may not exist yet)"));
4655
4656 /* always grow the array, no sparse / abandon support here */
4657 if (arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)) {
4658 duk__grow_props_for_array_item(thr, obj, arr_idx);
4659 }
4660
4661 DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(obj));
4662 tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx);
4663 tv2 = duk_require_tval(ctx, -1);
4664
11fdf7f2 4665 DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */
7c673cae
FG
4666
4667 duk_pop(ctx); /* [ ...val ] -> [ ... ] */
4668 return;
4669 }
4670
4671 DUK_DDD(DUK_DDDPRINT("define property fast path didn't work, use slow path"));
4672
4673 duk_push_uint(ctx, (duk_uint_t) arr_idx);
4674 key = duk_to_hstring(ctx, -1);
4675 DUK_ASSERT(key != NULL);
4676 duk_insert(ctx, -2); /* [ ... val key ] -> [ ... key val ] */
4677
4678 duk_hobject_define_property_internal(thr, obj, key, flags);
4679
4680 duk_pop(ctx); /* [ ... key ] -> [ ... ] */
4681}
4682
4683/*
4684 * Internal helper for defining an accessor property, ignoring
4685 * normal semantics such as extensibility, write protection etc.
4686 * Overwrites any existing value and attributes. This is called
4687 * very rarely, so the implementation first sets a value to undefined
4688 * and then changes the entry to an accessor (this is to save code space).
4689 */
4690
4691DUK_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) {
4692 duk_context *ctx = (duk_context *) thr;
4693 duk_int_t e_idx;
4694 duk_int_t h_idx;
4695
4696 DUK_DDD(DUK_DDDPRINT("define new accessor (internal): thr=%p, obj=%!O, key=%!O, "
4697 "getter=%!O, setter=%!O, flags=0x%02lx",
4698 (void *) thr, (duk_heaphdr *) obj, (duk_heaphdr *) key,
4699 (duk_heaphdr *) getter, (duk_heaphdr *) setter,
4700 (unsigned long) propflags));
4701
4702 DUK_ASSERT(thr != NULL);
4703 DUK_ASSERT(thr->heap != NULL);
4704 DUK_ASSERT(obj != NULL);
4705 DUK_ASSERT(key != NULL);
4706 DUK_ASSERT((propflags & ~DUK_PROPDESC_FLAGS_MASK) == 0);
4707 /* setter and/or getter may be NULL */
11fdf7f2 4708 DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj));
7c673cae
FG
4709
4710 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
4711
4712 /* force the property to 'undefined' to create a slot for it */
4713 duk_push_undefined(ctx);
4714 duk_hobject_define_property_internal(thr, obj, key, propflags);
4715 duk_hobject_find_existing_entry(thr->heap, obj, key, &e_idx, &h_idx);
4716 DUK_DDD(DUK_DDDPRINT("accessor slot: e_idx=%ld, h_idx=%ld", (long) e_idx, (long) h_idx));
4717 DUK_ASSERT(e_idx >= 0);
4718 DUK_ASSERT((duk_uint32_t) e_idx < DUK_HOBJECT_GET_ENEXT(obj));
4719
4720 /* no need to decref, as previous value is 'undefined' */
4721 DUK_HOBJECT_E_SLOT_SET_ACCESSOR(thr->heap, obj, e_idx);
4722 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, e_idx, getter);
4723 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, e_idx, setter);
4724 DUK_HOBJECT_INCREF_ALLOWNULL(thr, getter);
4725 DUK_HOBJECT_INCREF_ALLOWNULL(thr, setter);
4726}
4727
4728/*
4729 * Internal helpers for managing object 'length'
4730 */
4731
4732/* XXX: awkward helpers */
4733
4734DUK_INTERNAL void duk_hobject_set_length(duk_hthread *thr, duk_hobject *obj, duk_uint32_t length) {
4735 duk_context *ctx = (duk_context *) thr;
4736 duk_push_hobject(ctx, obj);
4737 duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
4738 duk_push_u32(ctx, length);
11fdf7f2
TL
4739 (void) duk_hobject_putprop(thr,
4740 DUK_GET_TVAL_NEGIDX(ctx, -3),
4741 DUK_GET_TVAL_NEGIDX(ctx, -2),
4742 DUK_GET_TVAL_NEGIDX(ctx, -1),
4743 0);
7c673cae
FG
4744 duk_pop_n(ctx, 3);
4745}
4746
4747DUK_INTERNAL void duk_hobject_set_length_zero(duk_hthread *thr, duk_hobject *obj) {
4748 duk_hobject_set_length(thr, obj, 0);
4749}
4750
4751DUK_INTERNAL duk_uint32_t duk_hobject_get_length(duk_hthread *thr, duk_hobject *obj) {
4752 duk_context *ctx = (duk_context *) thr;
4753 duk_double_t val;
4754 duk_push_hobject(ctx, obj);
4755 duk_push_hstring_stridx(ctx, DUK_STRIDX_LENGTH);
11fdf7f2
TL
4756 (void) duk_hobject_getprop(thr,
4757 DUK_GET_TVAL_NEGIDX(ctx, -2),
4758 DUK_GET_TVAL_NEGIDX(ctx, -1));
7c673cae
FG
4759 val = duk_to_number(ctx, -1);
4760 duk_pop_n(ctx, 3);
4761 if (val >= 0.0 && val < DUK_DOUBLE_2TO32) {
4762 return (duk_uint32_t) val;
4763 }
4764 return 0;
4765}
4766
4767/*
4768 * Object.getOwnPropertyDescriptor() (E5 Sections 15.2.3.3, 8.10.4)
4769 *
4770 * This is an actual function call.
4771 */
4772
4773DUK_INTERNAL duk_ret_t duk_hobject_object_get_own_property_descriptor(duk_context *ctx) {
4774 duk_hthread *thr = (duk_hthread *) ctx;
4775 duk_hobject *obj;
4776 duk_hstring *key;
4777 duk_propdesc pd;
4778 duk_bool_t rc;
4779
4780 DUK_ASSERT(ctx != NULL);
4781 DUK_ASSERT(thr != NULL);
4782 DUK_ASSERT(thr->heap != NULL);
4783
4784 obj = duk_require_hobject_or_lfunc_coerce(ctx, 0);
4785 (void) duk_to_string(ctx, 1);
4786 key = duk_require_hstring(ctx, 1);
4787
4788 DUK_ASSERT(obj != NULL);
4789 DUK_ASSERT(key != NULL);
4790
4791 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
4792
11fdf7f2 4793 rc = duk_hobject_get_own_propdesc(thr, obj, key, &pd, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
4794 if (!rc) {
4795 duk_push_undefined(ctx);
4796
4797 /* [obj key undefined] */
4798 return 1;
4799 }
4800
4801 duk_push_object(ctx);
4802
4803 /* [obj key value desc] */
4804
4805 if (DUK_PROPDESC_IS_ACCESSOR(&pd)) {
4806 /* If a setter/getter is missing (undefined), the descriptor must
4807 * still have the property present with the value 'undefined'.
4808 */
4809 if (pd.get) {
4810 duk_push_hobject(ctx, pd.get);
4811 } else {
4812 duk_push_undefined(ctx);
4813 }
4814 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_GET);
4815 if (pd.set) {
4816 duk_push_hobject(ctx, pd.set);
4817 } else {
4818 duk_push_undefined(ctx);
4819 }
4820 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_SET);
4821 } else {
4822 duk_dup(ctx, -2); /* [obj key value desc value] */
4823 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_VALUE);
4824 duk_push_boolean(ctx, DUK_PROPDESC_IS_WRITABLE(&pd));
4825 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_WRITABLE);
4826
4827 /* [obj key value desc] */
4828 }
4829 duk_push_boolean(ctx, DUK_PROPDESC_IS_ENUMERABLE(&pd));
4830 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_ENUMERABLE);
4831 duk_push_boolean(ctx, DUK_PROPDESC_IS_CONFIGURABLE(&pd));
4832 duk_put_prop_stridx(ctx, -2, DUK_STRIDX_CONFIGURABLE);
4833
4834 /* [obj key value desc] */
4835 return 1;
4836}
4837
4838/*
4839 * NormalizePropertyDescriptor() related helper.
4840 *
4841 * Internal helper which validates and normalizes a property descriptor
4842 * represented as an Ecmascript object (e.g. argument to defineProperty()).
4843 * The output of this conversion is a set of defprop_flags and possibly
4844 * some values pushed on the value stack; some subset of: property value,
4845 * getter, setter. Caller must manage stack top carefully because the
4846 * number of values pushed depends on the input property descriptor.
4847 *
4848 * The original descriptor object must not be altered in the process.
4849 */
4850
4851/* XXX: very basic optimization -> duk_get_prop_stridx_top */
4852
4853DUK_INTERNAL
4854void duk_hobject_prepare_property_descriptor(duk_context *ctx,
4855 duk_idx_t idx_in,
4856 duk_uint_t *out_defprop_flags,
4857 duk_idx_t *out_idx_value,
4858 duk_hobject **out_getter,
4859 duk_hobject **out_setter) {
4860 duk_hthread *thr = (duk_hthread *) ctx;
4861 duk_idx_t idx_value = -1;
4862 duk_hobject *getter = NULL;
4863 duk_hobject *setter = NULL;
4864 duk_bool_t is_data_desc = 0;
4865 duk_bool_t is_acc_desc = 0;
4866 duk_uint_t defprop_flags = 0;
4867
4868 DUK_ASSERT(ctx != NULL);
4869 DUK_ASSERT(out_defprop_flags != NULL);
4870 DUK_ASSERT(out_idx_value != NULL);
4871 DUK_ASSERT(out_getter != NULL);
4872 DUK_ASSERT(out_setter != NULL);
4873
4874 /* Must be an object, otherwise TypeError (E5.1 Section 8.10.5, step 1). */
4875 idx_in = duk_require_normalize_index(ctx, idx_in);
4876 (void) duk_require_hobject(ctx, idx_in);
4877
4878 /* The coercion order must match the ToPropertyDescriptor() algorithm
4879 * so that side effects in coercion happen in the correct order.
4880 * (This order also happens to be compatible with duk_def_prop(),
4881 * although it doesn't matter in practice.)
4882 */
4883
4884 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_VALUE)) {
4885 is_data_desc = 1;
4886 defprop_flags |= DUK_DEFPROP_HAVE_VALUE;
4887 idx_value = duk_get_top_index(ctx);
4888 /* Leave 'value' on stack */
4889 } else {
4890 duk_pop(ctx);
4891 }
4892
4893 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_WRITABLE)) {
4894 is_data_desc = 1;
4895 if (duk_to_boolean(ctx, -1)) {
4896 defprop_flags |= DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE;
4897 } else {
4898 defprop_flags |= DUK_DEFPROP_HAVE_WRITABLE;
4899 }
4900 }
4901 duk_pop(ctx);
4902
4903 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_GET)) {
4904 duk_tval *tv = duk_require_tval(ctx, -1);
4905 duk_hobject *h_get;
4906
4907 if (DUK_TVAL_IS_UNDEFINED(tv)) {
4908 /* undefined is accepted */
4909 DUK_ASSERT(getter == NULL);
4910 } else {
4911 /* NOTE: lightfuncs are coerced to full functions because
4912 * lightfuncs don't fit into a property value slot. This
4913 * has some side effects, see test-dev-lightfunc-accessor.js.
4914 */
4915 h_get = duk_get_hobject_or_lfunc_coerce(ctx, -1);
4916 if (h_get == NULL || !DUK_HOBJECT_IS_CALLABLE(h_get)) {
4917 goto type_error;
4918 }
4919 getter = h_get;
4920 }
4921 is_acc_desc = 1;
4922 defprop_flags |= DUK_DEFPROP_HAVE_GETTER;
4923 /* Leave 'getter' on stack */
4924 } else {
4925 duk_pop(ctx);
4926 }
4927
4928 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_SET)) {
4929 duk_tval *tv = duk_require_tval(ctx, -1);
4930 duk_hobject *h_set;
4931
7c673cae
FG
4932 if (DUK_TVAL_IS_UNDEFINED(tv)) {
4933 /* undefined is accepted */
4934 DUK_ASSERT(setter == NULL);
4935 } else {
4936 /* NOTE: lightfuncs are coerced to full functions because
4937 * lightfuncs don't fit into a property value slot. This
4938 * has some side effects, see test-dev-lightfunc-accessor.js.
4939 */
4940 h_set = duk_get_hobject_or_lfunc_coerce(ctx, -1);
4941 if (h_set == NULL || !DUK_HOBJECT_IS_CALLABLE(h_set)) {
4942 goto type_error;
4943 }
4944 setter = h_set;
4945 }
4946 is_acc_desc = 1;
4947 defprop_flags |= DUK_DEFPROP_HAVE_SETTER;
4948 /* Leave 'setter' on stack */
4949 } else {
4950 duk_pop(ctx);
4951 }
4952
4953 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_ENUMERABLE)) {
4954 if (duk_to_boolean(ctx, -1)) {
4955 defprop_flags |= DUK_DEFPROP_HAVE_ENUMERABLE | DUK_DEFPROP_ENUMERABLE;
4956 } else {
4957 defprop_flags |= DUK_DEFPROP_HAVE_ENUMERABLE;
4958 }
4959 }
4960 duk_pop(ctx);
4961
4962 if (duk_get_prop_stridx(ctx, idx_in, DUK_STRIDX_CONFIGURABLE)) {
4963 if (duk_to_boolean(ctx, -1)) {
4964 defprop_flags |= DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE;
4965 } else {
4966 defprop_flags |= DUK_DEFPROP_HAVE_CONFIGURABLE;
4967 }
4968 }
4969 duk_pop(ctx);
4970
4971 if (is_data_desc && is_acc_desc) {
4972 goto type_error;
4973 }
4974
4975 *out_defprop_flags = defprop_flags;
4976 *out_idx_value = idx_value;
4977 *out_getter = getter;
4978 *out_setter = setter;
4979
4980 /* [ ... value? getter? setter? ] */
4981 return;
4982
4983 type_error:
11fdf7f2 4984 DUK_ERROR_TYPE(thr, DUK_STR_INVALID_DESCRIPTOR);
7c673cae
FG
4985}
4986
4987/*
4988 * Object.defineProperty() related helper (E5 Section 15.2.3.6)
4989 *
4990 * Inlines all [[DefineOwnProperty]] exotic behaviors.
4991 *
4992 * Note: Ecmascript compliant [[DefineOwnProperty]](P, Desc, Throw) is not
4993 * implemented directly, but Object.defineProperty() serves its purpose.
4994 * We don't need the [[DefineOwnProperty]] internally and we don't have a
4995 * property descriptor with 'missing values' so it's easier to avoid it
4996 * entirely.
4997 *
4998 * Note: this is only called for actual objects, not primitive values.
4999 * This must support virtual properties for full objects (e.g. Strings)
5000 * but not for plain values (e.g. strings). Lightfuncs, even though
5001 * primitive in a sense, are treated like objects and accepted as target
5002 * values.
5003 */
5004
5005/* XXX: this is a major target for size optimization */
5006DUK_INTERNAL
5007void duk_hobject_define_property_helper(duk_context *ctx,
5008 duk_uint_t defprop_flags,
5009 duk_hobject *obj,
5010 duk_hstring *key,
5011 duk_idx_t idx_value,
5012 duk_hobject *get,
5013 duk_hobject *set) {
5014 duk_hthread *thr = (duk_hthread *) ctx;
5015 duk_uint32_t arr_idx;
5016 duk_tval tv;
5017 duk_bool_t has_enumerable;
5018 duk_bool_t has_configurable;
5019 duk_bool_t has_writable;
5020 duk_bool_t has_value;
5021 duk_bool_t has_get;
5022 duk_bool_t has_set;
5023 duk_bool_t is_enumerable;
5024 duk_bool_t is_configurable;
5025 duk_bool_t is_writable;
5026 duk_bool_t throw_flag;
5027 duk_bool_t force_flag;
5028 duk_small_uint_t new_flags;
5029 duk_propdesc curr;
5030 duk_uint32_t arridx_new_array_length; /* != 0 => post-update for array 'length' (used when key is an array index) */
5031 duk_uint32_t arrlen_old_len;
5032 duk_uint32_t arrlen_new_len;
5033 duk_bool_t pending_write_protect;
5034
5035 DUK_ASSERT(thr != NULL);
5036 DUK_ASSERT(thr->heap != NULL);
5037 DUK_ASSERT(ctx != NULL);
5038 DUK_ASSERT(obj != NULL);
5039 DUK_ASSERT(key != NULL);
5040 /* idx_value may be < 0 (no value), set and get may be NULL */
5041
5042 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
5043
5044 /* All the flags fit in 16 bits, so will fit into duk_bool_t. */
5045
5046 has_writable = (defprop_flags & DUK_DEFPROP_HAVE_WRITABLE);
5047 has_enumerable = (defprop_flags & DUK_DEFPROP_HAVE_ENUMERABLE);
5048 has_configurable = (defprop_flags & DUK_DEFPROP_HAVE_CONFIGURABLE);
5049 has_value = (defprop_flags & DUK_DEFPROP_HAVE_VALUE);
5050 has_get = (defprop_flags & DUK_DEFPROP_HAVE_GETTER);
5051 has_set = (defprop_flags & DUK_DEFPROP_HAVE_SETTER);
5052 is_writable = (defprop_flags & DUK_DEFPROP_WRITABLE);
5053 is_enumerable = (defprop_flags & DUK_DEFPROP_ENUMERABLE);
5054 is_configurable = (defprop_flags & DUK_DEFPROP_CONFIGURABLE);
5055 throw_flag = 1; /* Object.defineProperty() calls [[DefineOwnProperty]] with Throw=true */
5056 force_flag = (defprop_flags & DUK_DEFPROP_FORCE);
5057
5058 arr_idx = DUK_HSTRING_GET_ARRIDX_SLOW(key);
5059
5060 arridx_new_array_length = 0;
5061 pending_write_protect = 0;
5062 arrlen_old_len = 0;
5063 arrlen_new_len = 0;
5064
5065 DUK_DDD(DUK_DDDPRINT("has_enumerable=%ld is_enumerable=%ld "
5066 "has_configurable=%ld is_configurable=%ld "
5067 "has_writable=%ld is_writable=%ld "
5068 "has_value=%ld value=%!T "
5069 "has_get=%ld get=%p=%!O "
5070 "has_set=%ld set=%p=%!O "
5071 "arr_idx=%ld",
5072 (long) has_enumerable, (long) is_enumerable,
5073 (long) has_configurable, (long) is_configurable,
5074 (long) has_writable, (long) is_writable,
5075 (long) has_value, (duk_tval *) (idx_value >= 0 ? duk_get_tval(ctx, idx_value) : NULL),
5076 (long) has_get, (void *) get, (duk_heaphdr *) get,
5077 (long) has_set, (void *) set, (duk_heaphdr *) set,
5078 (long) arr_idx));
5079
5080 /*
5081 * Array exotic behaviors can be implemented at this point. The local variables
5082 * are essentially a 'value copy' of the input descriptor (Desc), which is modified
5083 * by the Array [[DefineOwnProperty]] (E5 Section 15.4.5.1).
5084 */
5085
5086 if (!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj)) {
5087 goto skip_array_exotic;
5088 }
5089
5090 if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
5091 /* E5 Section 15.4.5.1, step 3, steps a - i are implemented here, j - n at the end */
5092 if (!has_value) {
5093 DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', but no value in descriptor -> normal behavior"));
5094 goto skip_array_exotic;
5095 }
5096
5097 DUK_DDD(DUK_DDDPRINT("exotic array behavior for 'length', value present in descriptor -> exotic behavior"));
5098
5099 /*
5100 * Get old and new length
5101 */
5102
5103 /* Note: reuse 'curr' as a temp propdesc */
5104 arrlen_old_len = duk__get_old_array_length(thr, obj, &curr);
5105
5106 duk_dup(ctx, idx_value);
5107 arrlen_new_len = duk__to_new_array_length_checked(thr);
5108 duk_push_u32(ctx, arrlen_new_len);
5109 duk_replace(ctx, idx_value); /* step 3.e: replace 'Desc.[[Value]]' */
5110
5111 DUK_DDD(DUK_DDDPRINT("old_len=%ld, new_len=%ld", (long) arrlen_old_len, (long) arrlen_new_len));
5112
5113 if (arrlen_new_len >= arrlen_old_len) {
5114 /* standard behavior, step 3.f.i */
5115 DUK_DDD(DUK_DDDPRINT("new length is same or higher as previous => standard behavior"));
5116 goto skip_array_exotic;
5117 }
5118 DUK_DDD(DUK_DDDPRINT("new length is smaller than previous => exotic post behavior"));
5119
5120 /* XXX: consolidated algorithm step 15.f -> redundant? */
5121 if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && !force_flag) {
5122 /* Note: 'curr' refers to 'length' propdesc */
5123 goto fail_not_writable_array_length;
5124 }
5125
5126 /* steps 3.h and 3.i */
5127 if (has_writable && !is_writable) {
5128 DUK_DDD(DUK_DDDPRINT("desc writable is false, force it back to true, and flag pending write protect"));
5129 is_writable = 1;
5130 pending_write_protect = 1;
5131 }
5132
5133 /* remaining actual steps are carried out if standard DefineOwnProperty succeeds */
5134 } else if (arr_idx != DUK__NO_ARRAY_INDEX) {
5135 /* XXX: any chance of unifying this with the 'length' key handling? */
5136
5137 /* E5 Section 15.4.5.1, step 4 */
5138 duk_uint32_t old_len;
5139
5140 /* Note: use 'curr' as a temp propdesc */
5141 old_len = duk__get_old_array_length(thr, obj, &curr);
5142
5143 if (arr_idx >= old_len) {
5144 DUK_DDD(DUK_DDDPRINT("defineProperty requires array length update "
5145 "(arr_idx=%ld, old_len=%ld)",
5146 (long) arr_idx, (long) old_len));
5147
5148 if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
5149 /* Note: 'curr' refers to 'length' propdesc */
5150 goto fail_not_writable_array_length;
5151 }
5152
5153 /* actual update happens once write has been completed without
5154 * error below.
5155 */
5156 DUK_ASSERT(arr_idx != 0xffffffffUL);
5157 arridx_new_array_length = arr_idx + 1;
5158 } else {
5159 DUK_DDD(DUK_DDDPRINT("defineProperty does not require length update "
5160 "(arr_idx=%ld, old_len=%ld) -> standard behavior",
5161 (long) arr_idx, (long) old_len));
5162 }
5163 }
5164 skip_array_exotic:
5165
5166 /* XXX: There is currently no support for writing buffer object
5167 * indexed elements here. Attempt to do so will succeed and
5168 * write a concrete property into the buffer object. This should
5169 * be fixed at some point but because buffers are a custom feature
5170 * anyway, this is relatively unimportant.
5171 */
5172
5173 /*
5174 * Actual Object.defineProperty() default algorithm.
5175 */
5176
5177 /*
5178 * First check whether property exists; if not, simple case. This covers
5179 * steps 1-4.
5180 */
5181
11fdf7f2 5182 if (!duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE)) {
7c673cae
FG
5183 DUK_DDD(DUK_DDDPRINT("property does not exist"));
5184
5185 if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj) && !force_flag) {
5186 goto fail_not_extensible;
5187 }
5188
5189 /* XXX: share final setting code for value and flags? difficult because
5190 * refcount code is different. Share entry allocation? But can't allocate
5191 * until array index checked.
5192 */
5193
5194 /* steps 4.a and 4.b are tricky */
5195 if (has_set || has_get) {
5196 duk_int_t e_idx;
5197
5198 DUK_DDD(DUK_DDDPRINT("create new accessor property"));
5199
5200 DUK_ASSERT(has_set || set == NULL);
5201 DUK_ASSERT(has_get || get == NULL);
5202 DUK_ASSERT(!has_value);
5203 DUK_ASSERT(!has_writable);
5204
5205 new_flags = DUK_PROPDESC_FLAG_ACCESSOR; /* defaults, E5 Section 8.6.1, Table 7 */
5206 if (has_enumerable && is_enumerable) {
5207 new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
5208 }
5209 if (has_configurable && is_configurable) {
5210 new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
5211 }
5212
5213 if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
5214 DUK_DDD(DUK_DDDPRINT("accessor cannot go to array part, abandon array"));
5215 duk__abandon_array_checked(thr, obj);
5216 }
5217
5218 /* write to entry part */
5219 e_idx = duk__alloc_entry_checked(thr, obj, key);
5220 DUK_ASSERT(e_idx >= 0);
5221
5222 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, e_idx, get);
5223 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, e_idx, set);
5224 DUK_HOBJECT_INCREF_ALLOWNULL(thr, get);
5225 DUK_HOBJECT_INCREF_ALLOWNULL(thr, set);
5226
5227 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, e_idx, new_flags);
5228 goto success_exotics;
5229 } else {
5230 duk_int_t e_idx;
5231 duk_tval *tv2;
5232
5233 DUK_DDD(DUK_DDDPRINT("create new data property"));
5234
5235 DUK_ASSERT(!has_set);
5236 DUK_ASSERT(!has_get);
5237
5238 new_flags = 0; /* defaults, E5 Section 8.6.1, Table 7 */
5239 if (has_writable && is_writable) {
5240 new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
5241 }
5242 if (has_enumerable && is_enumerable) {
5243 new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
5244 }
5245 if (has_configurable && is_configurable) {
5246 new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
5247 }
5248 if (has_value) {
5249 duk_tval *tv_tmp = duk_require_tval(ctx, idx_value);
5250 DUK_TVAL_SET_TVAL(&tv, tv_tmp);
5251 } else {
11fdf7f2 5252 DUK_TVAL_SET_UNDEFINED(&tv); /* default value */
7c673cae
FG
5253 }
5254
5255 if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
5256 if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
5257#if 0
5258 DUK_DDD(DUK_DDDPRINT("new data property attributes match array defaults, attempt to write to array part"));
5259 /* may become sparse...*/
5260#endif
5261 /* XXX: handling for array part missing now; this doesn't affect
5262 * compliance but causes array entry writes using defineProperty()
5263 * to always abandon array part.
5264 */
5265 }
5266 DUK_DDD(DUK_DDDPRINT("new data property cannot go to array part, abandon array"));
5267 duk__abandon_array_checked(thr, obj);
5268 /* fall through */
5269 }
5270
5271 /* write to entry part */
5272 e_idx = duk__alloc_entry_checked(thr, obj, key);
5273 DUK_ASSERT(e_idx >= 0);
5274 tv2 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, e_idx);
5275 DUK_TVAL_SET_TVAL(tv2, &tv);
5276 DUK_TVAL_INCREF(thr, tv2);
5277
5278 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, e_idx, new_flags);
5279 goto success_exotics;
5280 }
5281 DUK_UNREACHABLE();
5282 }
5283
5284 /* we currently assume virtual properties are not configurable (as none of them are) */
5285 DUK_ASSERT((curr.e_idx >= 0 || curr.a_idx >= 0) || !(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE));
5286
5287 /* [obj key desc value get set curr_value] */
5288
5289 /*
5290 * Property already exists. Steps 5-6 detect whether any changes need
5291 * to be made.
5292 */
5293
5294 if (has_enumerable) {
5295 if (is_enumerable) {
5296 if (!(curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE)) {
5297 goto need_check;
5298 }
5299 } else {
5300 if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
5301 goto need_check;
5302 }
5303 }
5304 }
5305 if (has_configurable) {
5306 if (is_configurable) {
5307 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE)) {
5308 goto need_check;
5309 }
5310 } else {
5311 if (curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
5312 goto need_check;
5313 }
5314 }
5315 }
5316 if (has_value) {
5317 duk_tval *tmp1;
5318 duk_tval *tmp2;
5319
5320 /* attempt to change from accessor to data property */
5321 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5322 goto need_check;
5323 }
5324
5325 tmp1 = duk_require_tval(ctx, -1); /* curr value */
5326 tmp2 = duk_require_tval(ctx, idx_value); /* new value */
5327 if (!duk_js_samevalue(tmp1, tmp2)) {
5328 goto need_check;
5329 }
5330 }
5331 if (has_writable) {
5332 /* attempt to change from accessor to data property */
5333 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5334 goto need_check;
5335 }
5336
5337 if (is_writable) {
5338 if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE)) {
5339 goto need_check;
5340 }
5341 } else {
5342 if (curr.flags & DUK_PROPDESC_FLAG_WRITABLE) {
5343 goto need_check;
5344 }
5345 }
5346 }
5347 if (has_set) {
5348 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5349 if (set != curr.set) {
5350 goto need_check;
5351 }
5352 } else {
5353 goto need_check;
5354 }
5355 }
5356 if (has_get) {
5357 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5358 if (get != curr.get) {
5359 goto need_check;
5360 }
5361 } else {
5362 goto need_check;
5363 }
5364 }
5365
5366 /* property exists, either 'desc' is empty, or all values
5367 * match (SameValue)
5368 */
5369 goto success_no_exotics;
5370
5371 need_check:
5372
5373 /*
5374 * Some change(s) need to be made. Steps 7-11.
5375 */
5376
5377 /* shared checks for all descriptor types */
5378 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
5379 if (has_configurable && is_configurable) {
5380 goto fail_not_configurable;
5381 }
5382 if (has_enumerable) {
5383 if (curr.flags & DUK_PROPDESC_FLAG_ENUMERABLE) {
5384 if (!is_enumerable) {
5385 goto fail_not_configurable;
5386 }
5387 } else {
5388 if (is_enumerable) {
5389 goto fail_not_configurable;
5390 }
5391 }
5392 }
5393 }
5394
5395 /* Reject attempt to change virtual properties: not part of the
5396 * standard algorithm, applies currently to e.g. virtual index
5397 * properties of buffer objects (which are virtual but writable).
5398 * (Cannot "force" modification of a virtual property.)
5399 */
5400 if (curr.flags & DUK_PROPDESC_FLAG_VIRTUAL) {
5401 goto fail_virtual;
5402 }
5403
11fdf7f2
TL
5404 /* Reject attempt to change a read-only object. */
5405#if defined(DUK_USE_ROM_OBJECTS)
5406 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) {
5407 DUK_DD(DUK_DDPRINT("attempt to define property on read-only target object"));
5408 goto fail_not_configurable;
5409 }
5410#endif
5411
7c673cae
FG
5412 /* descriptor type specific checks */
5413 if (has_set || has_get) {
5414 /* IsAccessorDescriptor(desc) == true */
5415 DUK_ASSERT(!has_writable);
5416 DUK_ASSERT(!has_value);
5417
5418 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5419 /* curr and desc are accessors */
5420 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
5421 if (has_set && set != curr.set) {
5422 goto fail_not_configurable;
5423 }
5424 if (has_get && get != curr.get) {
5425 goto fail_not_configurable;
5426 }
5427 }
5428 } else {
5429 duk_bool_t rc;
7c673cae 5430 duk_tval *tv1;
11fdf7f2 5431 duk_tval tv_tmp;
7c673cae
FG
5432
5433 /* curr is data, desc is accessor */
5434 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
5435 goto fail_not_configurable;
5436 }
5437
5438 DUK_DDD(DUK_DDDPRINT("convert property to accessor property"));
5439 if (curr.a_idx >= 0) {
5440 DUK_DDD(DUK_DDDPRINT("property to convert is stored in an array entry, abandon array and re-lookup"));
5441 duk__abandon_array_checked(thr, obj);
5442 duk_pop(ctx); /* remove old value */
11fdf7f2 5443 rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
5444 DUK_UNREF(rc);
5445 DUK_ASSERT(rc != 0);
5446 DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
5447 }
5448
5449 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
5450
11fdf7f2
TL
5451 /* Avoid side effects that might disturb curr.e_idx until
5452 * we're done editing the slot.
5453 */
7c673cae
FG
5454 tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx);
5455 DUK_TVAL_SET_TVAL(&tv_tmp, tv1);
11fdf7f2 5456 DUK_TVAL_SET_UNDEFINED(tv1);
7c673cae
FG
5457
5458 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, curr.e_idx, NULL);
5459 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, curr.e_idx, NULL);
5460 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr->heap, obj, curr.e_idx);
5461 DUK_HOBJECT_E_SLOT_SET_ACCESSOR(thr->heap, obj, curr.e_idx);
5462
5463 DUK_DDD(DUK_DDDPRINT("flags after data->accessor conversion: 0x%02lx",
5464 (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, curr.e_idx)));
5465
11fdf7f2
TL
5466 DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */
5467
7c673cae
FG
5468 /* re-lookup to update curr.flags
5469 * XXX: would be faster to update directly
5470 */
5471 duk_pop(ctx); /* remove old value */
11fdf7f2 5472 rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
5473 DUK_UNREF(rc);
5474 DUK_ASSERT(rc != 0);
5475 }
5476 } else if (has_value || has_writable) {
5477 /* IsDataDescriptor(desc) == true */
5478 DUK_ASSERT(!has_set);
5479 DUK_ASSERT(!has_get);
5480
5481 if (curr.flags & DUK_PROPDESC_FLAG_ACCESSOR) {
5482 duk_bool_t rc;
11fdf7f2
TL
5483 duk_hobject *h_get;
5484 duk_hobject *h_set;
7c673cae
FG
5485
5486 /* curr is accessor, desc is data */
5487 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
5488 goto fail_not_configurable;
5489 }
5490
5491 /* curr is accessor -> cannot be in array part */
5492 DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
5493
5494 DUK_DDD(DUK_DDDPRINT("convert property to data property"));
5495
11fdf7f2
TL
5496 /* Avoid side effects that might disturb curr.e_idx until
5497 * we're done editing the slot.
5498 */
7c673cae 5499 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
11fdf7f2 5500 h_get = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, curr.e_idx);
7c673cae 5501 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, curr.e_idx, NULL);
11fdf7f2 5502 h_set = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, curr.e_idx);
7c673cae 5503 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, curr.e_idx, NULL);
7c673cae 5504
11fdf7f2 5505 DUK_TVAL_SET_UNDEFINED(DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx));
7c673cae
FG
5506 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr->heap, obj, curr.e_idx);
5507 DUK_HOBJECT_E_SLOT_CLEAR_ACCESSOR(thr->heap, obj, curr.e_idx);
5508
5509 DUK_DDD(DUK_DDDPRINT("flags after accessor->data conversion: 0x%02lx",
5510 (unsigned long) DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, curr.e_idx)));
5511
11fdf7f2
TL
5512 DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_get); /* side effects */
5513 DUK_HOBJECT_DECREF_ALLOWNULL(thr, h_set); /* side effects */
5514
7c673cae
FG
5515 /* re-lookup to update curr.flags
5516 * XXX: would be faster to update directly
5517 */
5518 duk_pop(ctx); /* remove old value */
11fdf7f2 5519 rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
5520 DUK_UNREF(rc);
5521 DUK_ASSERT(rc != 0);
5522 } else {
5523 /* curr and desc are data */
5524 if (!(curr.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) && !force_flag) {
5525 if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_writable && is_writable) {
5526 goto fail_not_configurable;
5527 }
5528 /* Note: changing from writable to non-writable is OK */
5529 if (!(curr.flags & DUK_PROPDESC_FLAG_WRITABLE) && has_value) {
5530 duk_tval *tmp1 = duk_require_tval(ctx, -1); /* curr value */
5531 duk_tval *tmp2 = duk_require_tval(ctx, idx_value); /* new value */
5532 if (!duk_js_samevalue(tmp1, tmp2)) {
5533 goto fail_not_configurable;
5534 }
5535 }
5536 }
5537 }
5538 } else {
5539 /* IsGenericDescriptor(desc) == true; this means in practice that 'desc'
5540 * only has [[Enumerable]] or [[Configurable]] flag updates, which are
5541 * allowed at this point.
5542 */
5543
5544 DUK_ASSERT(!has_value && !has_writable && !has_get && !has_set);
5545 }
5546
5547 /*
5548 * Start doing property attributes updates. Steps 12-13.
5549 *
5550 * Start by computing new attribute flags without writing yet.
5551 * Property type conversion is done above if necessary.
5552 */
5553
5554 new_flags = curr.flags;
5555
5556 if (has_enumerable) {
5557 if (is_enumerable) {
5558 new_flags |= DUK_PROPDESC_FLAG_ENUMERABLE;
5559 } else {
5560 new_flags &= ~DUK_PROPDESC_FLAG_ENUMERABLE;
5561 }
5562 }
5563 if (has_configurable) {
5564 if (is_configurable) {
5565 new_flags |= DUK_PROPDESC_FLAG_CONFIGURABLE;
5566 } else {
5567 new_flags &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
5568 }
5569 }
5570 if (has_writable) {
5571 if (is_writable) {
5572 new_flags |= DUK_PROPDESC_FLAG_WRITABLE;
5573 } else {
5574 new_flags &= ~DUK_PROPDESC_FLAG_WRITABLE;
5575 }
5576 }
5577
5578 /* XXX: write protect after flag? -> any chance of handling it here? */
5579
5580 DUK_DDD(DUK_DDDPRINT("new flags that we want to write: 0x%02lx",
5581 (unsigned long) new_flags));
5582
5583 /*
5584 * Check whether we need to abandon an array part (if it exists)
5585 */
5586
5587 if (curr.a_idx >= 0) {
5588 duk_bool_t rc;
5589
5590 DUK_ASSERT(curr.e_idx < 0);
5591
5592 if (new_flags == DUK_PROPDESC_FLAGS_WEC) {
5593 duk_tval *tv1, *tv2;
7c673cae
FG
5594
5595 DUK_DDD(DUK_DDDPRINT("array index, new property attributes match array defaults, update in-place"));
5596
5597 DUK_ASSERT(curr.flags == DUK_PROPDESC_FLAGS_WEC); /* must have been, since in array part */
5598 DUK_ASSERT(!has_set);
5599 DUK_ASSERT(!has_get);
5600
5601 tv2 = duk_require_tval(ctx, idx_value);
5602 tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, curr.a_idx);
11fdf7f2 5603 DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */
7c673cae
FG
5604 goto success_exotics;
5605 }
5606
5607 DUK_DDD(DUK_DDDPRINT("array index, new property attributes do not match array defaults, abandon array and re-lookup"));
5608 duk__abandon_array_checked(thr, obj);
5609 duk_pop(ctx); /* remove old value */
11fdf7f2 5610 rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE);
7c673cae
FG
5611 DUK_UNREF(rc);
5612 DUK_ASSERT(rc != 0);
5613 DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
5614 }
5615
5616 DUK_DDD(DUK_DDDPRINT("updating existing property in entry part"));
5617
5618 /* array case is handled comprehensively above */
5619 DUK_ASSERT(curr.e_idx >= 0 && curr.a_idx < 0);
5620
5621 DUK_DDD(DUK_DDDPRINT("update existing property attributes"));
5622 DUK_HOBJECT_E_SET_FLAGS(thr->heap, obj, curr.e_idx, new_flags);
5623
5624 if (has_set) {
5625 duk_hobject *tmp;
5626
5627 DUK_DDD(DUK_DDDPRINT("update existing property setter"));
5628 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
5629
5630 tmp = DUK_HOBJECT_E_GET_VALUE_SETTER(thr->heap, obj, curr.e_idx);
5631 DUK_UNREF(tmp);
5632 DUK_HOBJECT_E_SET_VALUE_SETTER(thr->heap, obj, curr.e_idx, set);
5633 DUK_HOBJECT_INCREF_ALLOWNULL(thr, set);
11fdf7f2 5634 DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); /* side effects */
7c673cae
FG
5635 }
5636 if (has_get) {
5637 duk_hobject *tmp;
5638
5639 DUK_DDD(DUK_DDDPRINT("update existing property getter"));
5640 DUK_ASSERT(DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
5641
5642 tmp = DUK_HOBJECT_E_GET_VALUE_GETTER(thr->heap, obj, curr.e_idx);
5643 DUK_UNREF(tmp);
5644 DUK_HOBJECT_E_SET_VALUE_GETTER(thr->heap, obj, curr.e_idx, get);
5645 DUK_HOBJECT_INCREF_ALLOWNULL(thr, get);
11fdf7f2 5646 DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); /* side effects */
7c673cae
FG
5647 }
5648 if (has_value) {
5649 duk_tval *tv1, *tv2;
7c673cae
FG
5650
5651 DUK_DDD(DUK_DDDPRINT("update existing property value"));
5652 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
5653
5654 tv2 = duk_require_tval(ctx, idx_value);
5655 tv1 = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx);
11fdf7f2 5656 DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */
7c673cae
FG
5657 }
5658
5659 /*
5660 * Standard algorithm succeeded without errors, check for exotic post-behaviors.
5661 *
5662 * Arguments exotic behavior in E5 Section 10.6 occurs after the standard
5663 * [[DefineOwnProperty]] has completed successfully.
5664 *
5665 * Array exotic behavior in E5 Section 15.4.5.1 is implemented partly
5666 * prior to the default [[DefineOwnProperty]], but:
5667 * - for an array index key (e.g. "10") the final 'length' update occurs here
5668 * - for 'length' key the element deletion and 'length' update occurs here
5669 */
5670
5671 success_exotics:
5672
5673 /* [obj key desc value get set curr_value] */
5674
5675 if (DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj)) {
5676 if (arridx_new_array_length > 0) {
5677 duk_tval *tmp;
5678 duk_bool_t rc;
5679
5680 /*
5681 * Note: zero works as a "no update" marker because the new length
5682 * can never be zero after a new property is written.
5683 */
5684
5685 /* E5 Section 15.4.5.1, steps 4.e.i - 4.e.ii */
5686
5687 DUK_DDD(DUK_DDDPRINT("defineProperty successful, pending array length update to: %ld",
5688 (long) arridx_new_array_length));
5689
5690 /* Note: reuse 'curr' */
11fdf7f2 5691 rc = duk__get_own_propdesc_raw(thr, obj, DUK_HTHREAD_STRING_LENGTH(thr), DUK__NO_ARRAY_INDEX, &curr, 0 /*flags*/); /* don't push value */
7c673cae
FG
5692 DUK_UNREF(rc);
5693 DUK_ASSERT(rc != 0);
5694 DUK_ASSERT(curr.e_idx >= 0);
5695
5696 tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx);
5697 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
5698 /* no need for decref/incref because value is a number */
7c673cae 5699 DUK_TVAL_SET_FASTINT_U32(tmp, arridx_new_array_length);
7c673cae
FG
5700 }
5701 if (key == DUK_HTHREAD_STRING_LENGTH(thr) && arrlen_new_len < arrlen_old_len) {
5702 /*
5703 * E5 Section 15.4.5.1, steps 3.k - 3.n. The order at the end combines
5704 * the error case 3.l.iii and the success case 3.m-3.n.
5705 *
5706 * Note: 'length' is always in entries part, so no array abandon issues for
5707 * 'writable' update.
5708 */
5709
5710 /* XXX: investigate whether write protect can be handled above, if we
5711 * just update length here while ignoring its protected status
5712 */
5713
5714 duk_tval *tmp;
5715 duk_uint32_t result_len;
5716 duk_bool_t rc;
5717
5718 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key is 'length', exotic array behavior, "
5719 "doing array element deletion and length update"));
5720
5721 rc = duk__handle_put_array_length_smaller(thr, obj, arrlen_old_len, arrlen_new_len, force_flag, &result_len);
5722
5723 /* update length (curr points to length, and we assume it's still valid) */
5724 DUK_ASSERT(result_len >= arrlen_new_len && result_len <= arrlen_old_len);
5725
5726 DUK_ASSERT(curr.e_idx >= 0);
5727 DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, obj, curr.e_idx));
5728 tmp = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, obj, curr.e_idx);
5729 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
5730 /* no decref needed for a number */
7c673cae 5731 DUK_TVAL_SET_FASTINT_U32(tmp, result_len);
7c673cae
FG
5732 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tmp));
5733
5734 if (pending_write_protect) {
5735 DUK_DDD(DUK_DDDPRINT("setting array length non-writable (pending writability update)"));
5736 DUK_HOBJECT_E_SLOT_CLEAR_WRITABLE(thr->heap, obj, curr.e_idx);
5737 }
5738
5739 /*
5740 * XXX: shrink array allocation or entries compaction here?
5741 */
5742
5743 if (!rc) {
5744 goto fail_array_length_partial;
5745 }
5746 }
5747 } else if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(obj)) {
5748 duk_hobject *map;
5749 duk_hobject *varenv;
5750
5751 DUK_ASSERT(arridx_new_array_length == 0);
5752 DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj)); /* traits are separate; in particular, arguments not an array */
5753
5754 map = NULL;
5755 varenv = NULL;
5756 if (!duk__lookup_arguments_map(thr, obj, key, &curr, &map, &varenv)) {
5757 goto success_no_exotics;
5758 }
5759 DUK_ASSERT(map != NULL);
5760 DUK_ASSERT(varenv != NULL);
5761
5762 /* [obj key desc value get set curr_value varname] */
5763
5764 if (has_set || has_get) {
5765 /* = IsAccessorDescriptor(Desc) */
5766 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map' "
5767 "changed to an accessor, delete arguments binding"));
5768
5769 (void) duk_hobject_delprop_raw(thr, map, key, 0); /* ignore result */
5770 } else {
5771 /* Note: this order matters (final value before deleting map entry must be done) */
5772 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5773 "check for value update / binding deletion"));
5774
5775 if (has_value) {
5776 duk_hstring *varname;
5777
5778 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5779 "update bound value (variable/argument)"));
5780
5781 varname = duk_require_hstring(ctx, -1);
5782 DUK_ASSERT(varname != NULL);
5783
5784 DUK_DDD(DUK_DDDPRINT("arguments object automatic putvar for a bound variable; "
5785 "key=%!O, varname=%!O, value=%!T",
5786 (duk_heaphdr *) key,
5787 (duk_heaphdr *) varname,
5788 (duk_tval *) duk_require_tval(ctx, idx_value)));
5789
5790 /* strict flag for putvar comes from our caller (currently: fixed) */
5791 duk_js_putvar_envrec(thr, varenv, varname, duk_require_tval(ctx, idx_value), throw_flag);
5792 }
5793 if (has_writable && !is_writable) {
5794 DUK_DDD(DUK_DDDPRINT("defineProperty successful, key mapped to arguments 'map', "
5795 "changed to non-writable, delete arguments binding"));
5796
5797 (void) duk_hobject_delprop_raw(thr, map, key, 0); /* ignore result */
5798 }
5799 }
5800
5801 /* 'varname' is in stack in this else branch, leaving an unbalanced stack below,
5802 * but this doesn't matter now.
5803 */
5804 }
5805
5806 success_no_exotics:
5807 return;
5808
5809 fail_virtual:
11fdf7f2 5810 DUK_ERROR_TYPE(thr, DUK_STR_PROPERTY_IS_VIRTUAL);
7c673cae
FG
5811 return;
5812
5813 fail_not_writable_array_length:
11fdf7f2 5814 DUK_ERROR_TYPE(thr, DUK_STR_ARRAY_LENGTH_NOT_WRITABLE);
7c673cae
FG
5815 return;
5816
5817 fail_not_extensible:
11fdf7f2 5818 DUK_ERROR_TYPE(thr, DUK_STR_NOT_EXTENSIBLE);
7c673cae
FG
5819 return;
5820
5821 fail_not_configurable:
11fdf7f2 5822 DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE);
7c673cae
FG
5823 return;
5824
5825 fail_array_length_partial:
11fdf7f2 5826 DUK_ERROR_TYPE(thr, DUK_STR_ARRAY_LENGTH_WRITE_FAILED);
7c673cae
FG
5827 return;
5828}
5829
5830/*
5831 * Object.prototype.hasOwnProperty() and Object.prototype.propertyIsEnumerable().
5832 */
5833
5834DUK_INTERNAL duk_bool_t duk_hobject_object_ownprop_helper(duk_context *ctx, duk_small_uint_t required_desc_flags) {
5835 duk_hthread *thr = (duk_hthread *) ctx;
5836 duk_hstring *h_v;
5837 duk_hobject *h_obj;
5838 duk_propdesc desc;
5839 duk_bool_t ret;
5840
5841 /* coercion order matters */
5842 h_v = duk_to_hstring(ctx, 0);
5843 DUK_ASSERT(h_v != NULL);
5844
5845 h_obj = duk_push_this_coercible_to_object(ctx);
5846 DUK_ASSERT(h_obj != NULL);
5847
11fdf7f2 5848 ret = duk_hobject_get_own_propdesc(thr, h_obj, h_v, &desc, 0 /*flags*/); /* don't push value */
7c673cae
FG
5849
5850 duk_push_boolean(ctx, ret && ((desc.flags & required_desc_flags) == required_desc_flags));
5851 return 1;
5852}
5853
5854/*
5855 * Object.seal() and Object.freeze() (E5 Sections 15.2.3.8 and 15.2.3.9)
5856 *
5857 * Since the algorithms are similar, a helper provides both functions.
5858 * Freezing is essentially sealing + making plain properties non-writable.
5859 *
5860 * Note: virtual (non-concrete) properties which are non-configurable but
5861 * writable would pose some problems, but such properties do not currently
5862 * exist (all virtual properties are non-configurable and non-writable).
5863 * If they did exist, the non-configurability does NOT prevent them from
5864 * becoming non-writable. However, this change should be recorded somehow
5865 * so that it would turn up (e.g. when getting the property descriptor),
5866 * requiring some additional flags in the object.
5867 */
5868
5869DUK_INTERNAL void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_freeze) {
5870 duk_uint_fast32_t i;
5871
5872 DUK_ASSERT(thr != NULL);
5873 DUK_ASSERT(thr->heap != NULL);
5874 DUK_ASSERT(obj != NULL);
5875
5876 DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
5877
11fdf7f2
TL
5878#if defined(DUK_USE_ROM_OBJECTS)
5879 if (DUK_HEAPHDR_HAS_READONLY((duk_heaphdr *) obj)) {
5880 DUK_DD(DUK_DDPRINT("attempt to seal/freeze a readonly object, reject"));
5881 DUK_ERROR_TYPE(thr, DUK_STR_NOT_CONFIGURABLE);
5882 }
5883#endif
5884
7c673cae
FG
5885 /*
5886 * Abandon array part because all properties must become non-configurable.
5887 * Note that this is now done regardless of whether this is always the case
5888 * (skips check, but performance problem if caller would do this many times
5889 * for the same object; not likely).
5890 */
5891
5892 duk__abandon_array_checked(thr, obj);
5893 DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) == 0);
5894
5895 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
5896 duk_uint8_t *fp;
5897
5898 /* since duk__abandon_array_checked() causes a resize, there should be no gaps in keys */
5899 DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i) != NULL);
5900
5901 /* avoid multiple computations of flags address; bypasses macros */
5902 fp = DUK_HOBJECT_E_GET_FLAGS_PTR(thr->heap, obj, i);
5903 if (is_freeze && !((*fp) & DUK_PROPDESC_FLAG_ACCESSOR)) {
5904 *fp &= ~(DUK_PROPDESC_FLAG_WRITABLE | DUK_PROPDESC_FLAG_CONFIGURABLE);
5905 } else {
5906 *fp &= ~DUK_PROPDESC_FLAG_CONFIGURABLE;
5907 }
5908 }
5909
5910 DUK_HOBJECT_CLEAR_EXTENSIBLE(obj);
5911
5912 /* no need to compact since we already did that in duk__abandon_array_checked()
5913 * (regardless of whether an array part existed or not.
5914 */
5915
5916 return;
5917}
5918
5919/*
5920 * Object.isSealed() and Object.isFrozen() (E5 Sections 15.2.3.11, 15.2.3.13)
5921 *
5922 * Since the algorithms are similar, a helper provides both functions.
5923 * Freezing is essentially sealing + making plain properties non-writable.
5924 *
5925 * Note: all virtual (non-concrete) properties are currently non-configurable
5926 * and non-writable (and there are no accessor virtual properties), so they don't
5927 * need to be considered here now.
5928 */
5929
5930DUK_INTERNAL duk_bool_t duk_hobject_object_is_sealed_frozen_helper(duk_hthread *thr, duk_hobject *obj, duk_bool_t is_frozen) {
5931 duk_uint_fast32_t i;
5932
5933 DUK_ASSERT(obj != NULL);
5934 DUK_UNREF(thr);
5935
5936 /* Note: no allocation pressure, no need to check refcounts etc */
5937
5938 /* must not be extensible */
5939 if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
5940 return 0;
5941 }
5942
5943 /* all virtual properties are non-configurable and non-writable */
5944
5945 /* entry part must not contain any configurable properties, or
5946 * writable properties (if is_frozen).
5947 */
5948 for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) {
5949 duk_small_uint_t flags;
5950
5951 if (!DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i)) {
5952 continue;
5953 }
5954
5955 /* avoid multiple computations of flags address; bypasses macros */
5956 flags = (duk_small_uint_t) DUK_HOBJECT_E_GET_FLAGS(thr->heap, obj, i);
5957
5958 if (flags & DUK_PROPDESC_FLAG_CONFIGURABLE) {
5959 return 0;
5960 }
5961 if (is_frozen &&
5962 !(flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
5963 (flags & DUK_PROPDESC_FLAG_WRITABLE)) {
5964 return 0;
5965 }
5966 }
5967
5968 /* array part must not contain any non-unused properties, as they would
5969 * be configurable and writable.
5970 */
5971 for (i = 0; i < DUK_HOBJECT_GET_ASIZE(obj); i++) {
5972 duk_tval *tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, i);
11fdf7f2 5973 if (!DUK_TVAL_IS_UNUSED(tv)) {
7c673cae
FG
5974 return 0;
5975 }
5976 }
5977
5978 return 1;
5979}
5980
5981/*
5982 * Object.preventExtensions() and Object.isExtensible() (E5 Sections 15.2.3.10, 15.2.3.13)
5983 *
5984 * Not needed, implemented by macros DUK_HOBJECT_{HAS,CLEAR,SET}_EXTENSIBLE
5985 * and the Object built-in bindings.
5986 */
5987
5988/* Undefine local defines */
5989
5990#undef DUK__NO_ARRAY_INDEX
5991#undef DUK__HASH_INITIAL
5992#undef DUK__HASH_PROBE_STEP
5993#undef DUK__HASH_UNUSED
5994#undef DUK__HASH_DELETED
5995#undef DUK__VALSTACK_SPACE