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