You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

280 lines
7.9 KiB

#include "duk_internal.h"
/* Coerce a new .length candidate to a number and check that it's a valid
* .length.
*/
DUK_INTERNAL duk_uint32_t duk_harray_to_array_length_checked(duk_hthread *thr, duk_tval *tv) {
duk_uint32_t res;
duk_double_t d;
#if !defined(DUK_USE_PREFER_SIZE)
#if defined(DUK_USE_FASTINT)
/* When fastints are enabled, the most interesting case is assigning
* a fastint to .length (e.g. arr.length = 0).
*/
if (DUK_TVAL_IS_FASTINT(tv)) {
/* Very common case. */
duk_int64_t fi;
fi = DUK_TVAL_GET_FASTINT(tv);
if (fi < 0 || fi > DUK_I64_CONSTANT(0xffffffff)) {
goto fail_range;
}
return (duk_uint32_t) fi;
}
#else /* DUK_USE_FASTINT */
/* When fastints are not enabled, the most interesting case is any
* number.
*/
if (DUK_TVAL_IS_DOUBLE(tv)) {
d = DUK_TVAL_GET_NUMBER(tv);
}
#endif /* DUK_USE_FASTINT */
else
#endif /* !DUK_USE_PREFER_SIZE */
{
/* In all other cases, and when doing a size optimized build,
* fall back to the comprehensive handler.
*/
d = duk_js_tonumber(thr, tv);
}
/* Refuse to update an Array's 'length' to a value outside the
* 32-bit range. Negative zero is accepted as zero.
*/
res = duk_double_to_uint32_t(d);
if (!duk_double_equals((duk_double_t) res, d)) {
goto fail_range;
}
return res;
fail_range:
DUK_ERROR_RANGE(thr, DUK_STR_INVALID_ARRAY_LENGTH);
DUK_WO_NORETURN(return 0;);
}
/* Delete elements required by a smaller length, taking into account
* potentially non-configurable elements. Returns non-zero if all
* elements could be deleted, and zero if all or some elements could
* not be deleted.
*/
DUK_INTERNAL duk_bool_t duk_harray_put_array_length_u32_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 target_len;
duk_uint_fast32_t i, n;
duk_tval *tv;
duk_bool_t rc;
duk_harray *h_arr;
DUK_DDD(DUK_DDDPRINT("new array length smaller than old (%ld -> %ld), "
"probably need to remove elements",
(long) old_len,
(long) new_len));
/*
* New length is smaller than old length, need to delete properties above
* the new length.
*
* If array items exists, this is straightforward: array entries cannot
* be non-configurable so this is guaranteed to work.
*
* If array items does not exist, array-indexed values are scattered
* in the idxprops part, and some may not be configurable (preventing length
* from becoming lower than their index + 1). To handle the algorithm
* in E5 Section 15.4.5.1, step l correctly, we scan the entire property
* set twice.
*/
DUK_ASSERT(thr != NULL);
DUK_ASSERT(obj != NULL);
DUK_ASSERT(new_len < old_len);
DUK_ASSERT_VALSTACK_SPACE(thr, DUK_HOBJECT_PROP_VALSTACK_SPACE);
DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj));
DUK_ASSERT(DUK_HOBJECT_IS_ARRAY(obj));
h_arr = (duk_harray *) obj;
if (DUK_LIKELY(DUK_HOBJECT_HAS_ARRAY_ITEMS(obj))) {
/*
* All defined array-indexed properties are in the array items
* (we assume the array items is comprehensive), and all array
* entries are writable, configurable, and enumerable. Thus,
* nothing can prevent array entries from being deleted.
*/
DUK_DDD(DUK_DDDPRINT("have array items, easy case"));
if (old_len < DUK_HARRAY_GET_ITEMS_LENGTH(h_arr)) {
/* XXX: assertion that entries >= old_len are already unused */
i = old_len;
} else {
i = DUK_HARRAY_GET_ITEMS_LENGTH(h_arr);
}
DUK_ASSERT(i <= DUK_HARRAY_GET_ITEMS_LENGTH(h_arr));
while (i > new_len) {
i--;
tv = DUK_HARRAY_GET_ITEMS(thr->heap, h_arr) + i;
DUK_TVAL_DECREF_NORZ(thr, tv);
DUK_TVAL_SET_UNUSED(tv);
}
DUK_HARRAY_SET_LENGTH(h_arr, new_len);
DUK_HARRAY_ASSERT_VALID(thr->heap, h_arr);
DUK_REFZERO_CHECK_FAST(thr);
DUK_HARRAY_ASSERT_VALID(thr->heap, h_arr);
return 1;
} else {
/*
* Entries part is a bit more complex.
*/
/* The approach here works when the length reduction is large
* in proportion to the array size, as a full scan is done.
* For small reductions just iterating the indices one-by-one
* and updating the length for each would be better.
*/
duk_propvalue *val_base;
duk_uarridx_t *key_base;
duk_uint8_t *attr_base;
/* Stage 1: find highest preventing non-configurable entry (if any).
* When forcing, ignore non-configurability.
*/
duk_hobject_get_idxprops_key_attr(thr->heap, obj, &val_base, &key_base, &attr_base);
target_len = new_len;
if (force_flag) {
goto skip_stage1;
}
for (i = 0, n = obj->i_next; i < n; i++) {
duk_uarridx_t key;
duk_uint8_t attrs;
key = key_base[i];
if (key < (duk_uarridx_t) new_len) {
continue;
}
if (key == DUK_ARRIDX_NONE) {
continue;
}
attrs = attr_base[i];
if (attrs & DUK_PROPDESC_FLAG_CONFIGURABLE) {
continue;
}
/* Relevant array index is non-configurable, blocks write.
* Update length target (= length we can achieve).
*/
if (key >= target_len) {
target_len = key + 1;
}
}
skip_stage1:
/* Stage 2: delete configurable entries above target length. */
for (i = 0, n = obj->i_next; i < n; i++) {
duk_uarridx_t key;
duk_uint8_t attrs;
duk_propvalue *pv;
key = key_base[i];
if (key < target_len) {
continue;
}
if (key == DUK_ARRIDX_NONE) {
continue;
}
key_base[i] = DUK_ARRIDX_NONE;
attrs = attr_base[i];
pv = val_base + i;
if (attrs & DUK_PROPDESC_FLAG_ACCESSOR) {
duk_hobject *tmp;
tmp = pv->a.get;
DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, tmp);
tmp = pv->a.set;
DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, tmp);
} else {
duk_tval *tmp;
tmp = &pv->v;
DUK_TVAL_DECREF_NORZ(thr, tmp);
}
}
DUK_HARRAY_ASSERT_VALID(thr->heap, h_arr);
DUK_HARRAY_SET_LENGTH(h_arr, target_len);
DUK_HARRAY_ASSERT_VALID(thr->heap, h_arr);
DUK_REFZERO_CHECK_SLOW(thr);
if (target_len == new_len) {
return 1;
}
}
return 0;
}
DUK_INTERNAL duk_bool_t duk_harray_put_array_length_u32(duk_hthread *thr,
duk_hobject *obj,
duk_uint32_t new_len,
duk_bool_t force_flag) {
duk_harray *a;
duk_uint32_t old_len;
duk_bool_t rc;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(obj != NULL);
DUK_ASSERT_VALSTACK_SPACE(thr, DUK_HOBJECT_PROP_VALSTACK_SPACE);
DUK_ASSERT(DUK_HOBJECT_HAS_EXOTIC_ARRAY(obj));
DUK_ASSERT(DUK_HOBJECT_IS_ARRAY(obj));
a = (duk_harray *) obj;
DUK_HARRAY_ASSERT_VALID(thr->heap, a);
old_len = DUK_HARRAY_GET_LENGTH(a);
if (DUK_UNLIKELY(DUK_HARRAY_LENGTH_NONWRITABLE(a) && !force_flag)) {
DUK_DDD(DUK_DDDPRINT("length is not writable, fail"));
return 0;
}
if (new_len >= old_len) {
DUK_DDD(DUK_DDDPRINT("new length is same or higher than old length, just update length, no deletions"));
DUK_HARRAY_SET_LENGTH(a, new_len);
return 1;
}
DUK_DDD(DUK_DDDPRINT("new length is lower than old length, probably must delete entries"));
/*
* New length lower than old length => delete elements, then
* update length.
*
* Note: even though a bunch of elements have been deleted, the 'desc' is
* still valid as properties haven't been resized (and entries compacted).
*/
rc = duk_harray_put_array_length_u32_smaller(thr, obj, old_len, new_len, force_flag);
/* XXX: shrink array allocation or entries compaction here? */
return rc;
}
DUK_INTERNAL duk_bool_t duk_harray_put_array_length_top(duk_hthread *thr, duk_hobject *obj, duk_bool_t force_flag) {
duk_uint32_t new_len;
DUK_ASSERT(duk_is_valid_index(thr, -1));
new_len = duk_harray_to_array_length_checked(thr, DUK_GET_TVAL_NEGIDX(thr, -1));
return duk_harray_put_array_length_u32(thr, obj, new_len, force_flag);
}