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