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