diff --git a/RELEASES.rst b/RELEASES.rst index 4c605352d7..a92b90c32d 100644 --- a/RELEASES.rst +++ b/RELEASES.rst @@ -3459,6 +3459,11 @@ Planned * Add --no-auto-complete option to 'duk' to disable linenoise auto completion (GH-2131) +* Add support for keeping an array's internal array part in + Object.defineProperty(), previously the array part was always + abandoned if an array index was defined using Object.defineProperty() + (even if property attributes were correct) (GH-2146) + * Fix incorrect parsing of post-increment/post-decrement followed by division (e.g. "z++ / 20"), the slash was interpreted as beginning a regexp (GH-2140) @@ -3490,8 +3495,8 @@ Planned backstop when call handling triggers a Proxy trap (GH-2032, GH-2108) * Fix several assertion failures with possible memory unsafe behavior - (GH-2022, GH-2024, GH-2025, GH-2026, GH-2031, GH-2033, GH-2035, GH-2036, - GH-2065, GH-2115, GH-2138) + (GH-2022, GH-2023, GH-2024, GH-2025, GH-2026, GH-2031, GH-2033, GH-2035, + GH-2036, GH-2065, GH-2115, GH-2138, GH-2146) * Fix incorrect assertion with no underlying bug for resolving bound function chains with a Proxy object (rather than a plain function) diff --git a/examples/cmdline/duk_cmdline.c b/examples/cmdline/duk_cmdline.c index 60faa4061c..f5c148a739 100644 --- a/examples/cmdline/duk_cmdline.c +++ b/examples/cmdline/duk_cmdline.c @@ -229,7 +229,7 @@ static duk_ret_t wrapped_compile_execute(duk_context *ctx, void *udata) { memcpy(buf, (const void *) src_data, src_len); duk_load_function(ctx); } else { - duk_type_error(ctx, "bytecode input rejected (use -b to allow bytecode inputs)"); + (void) duk_type_error(ctx, "bytecode input rejected (use -b to allow bytecode inputs)"); } } else { /* Source code. */ diff --git a/src-input/duk_hobject_props.c b/src-input/duk_hobject_props.c index bd0709c344..8be66b788c 100644 --- a/src-input/duk_hobject_props.c +++ b/src-input/duk_hobject_props.c @@ -76,6 +76,9 @@ DUK_LOCAL_DECL duk_bool_t duk__handle_put_array_length(duk_hthread *thr, duk_hob 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); 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); +DUK_LOCAL_DECL void duk__abandon_array_part(duk_hthread *thr, duk_hobject *obj); +DUK_LOCAL_DECL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx); + /* * Misc helpers */ @@ -234,8 +237,6 @@ DUK_LOCAL duk_uint32_t duk__get_default_h_size(duk_uint32_t e_size) { DUK_LOCAL duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) { duk_uint32_t res; - DUK_ASSERT(e_size <= DUK_HOBJECT_MAX_PROPERTIES); - res = (e_size + DUK_USE_HOBJECT_ENTRY_MINGROW_ADD) / DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR; DUK_ASSERT(res >= 1); /* important for callers */ return res; @@ -245,8 +246,6 @@ DUK_LOCAL duk_uint32_t duk__get_min_grow_e(duk_uint32_t e_size) { DUK_LOCAL duk_uint32_t duk__get_min_grow_a(duk_uint32_t a_size) { duk_uint32_t res; - DUK_ASSERT((duk_size_t) a_size <= DUK_HOBJECT_MAX_PROPERTIES); - res = (a_size + DUK_USE_HOBJECT_ARRAY_MINGROW_ADD) / DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR; DUK_ASSERT(res >= 1); /* important for callers */ return res; @@ -351,6 +350,105 @@ DUK_LOCAL duk_bool_t duk__abandon_array_slow_check_required(duk_uint32_t arr_idx return (arr_idx > DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3)); } +DUK_LOCAL duk_bool_t duk__abandon_array_check(duk_hthread *thr, duk_uint32_t arr_idx, duk_hobject *obj) { + duk_uint32_t min_size; + duk_uint32_t old_used; + duk_uint32_t old_size; + + if (!duk__abandon_array_slow_check_required(arr_idx, DUK_HOBJECT_GET_ASIZE(obj))) { + DUK_DDD(DUK_DDDPRINT("=> fast resize is OK")); + return 0; + } + + duk__compute_a_stats(thr, obj, &old_used, &old_size); + + DUK_DDD(DUK_DDDPRINT("abandon check, array stats: old_used=%ld, old_size=%ld, arr_idx=%ld", + (long) old_used, (long) old_size, (long) arr_idx)); + + min_size = arr_idx + 1; +#if defined(DUK_USE_OBJSIZES16) + if (min_size > DUK_UINT16_MAX) { + goto do_abandon; + } +#endif + DUK_UNREF(min_size); + + /* Note: intentionally use approximations to shave a few instructions: + * a_used = old_used (accurate: old_used + 1) + * a_size = arr_idx (accurate: arr_idx + 1) + */ + if (duk__abandon_array_density_check(old_used, arr_idx)) { + DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, " + "decided to abandon array part (would become too sparse)")); + + /* Abandoning requires a props allocation resize and + * 'rechecks' the valstack, invalidating any existing + * valstack value pointers. + */ + goto do_abandon; + } + + DUK_DDD(DUK_DDDPRINT("=> decided to keep array part")); + return 0; + + do_abandon: + duk__abandon_array_part(thr, obj); + DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(obj)); + return 1; +} + +DUK_LOCAL duk_tval *duk__obtain_arridx_slot_slowpath(duk_hthread *thr, duk_uint32_t arr_idx, duk_hobject *obj) { + /* + * Array needs to grow, but we don't want it becoming too sparse. + * If it were to become sparse, abandon array part, moving all + * array entries into the entries part (for good). + * + * Since we don't keep track of actual density (used vs. size) of + * the array part, we need to estimate somehow. The check is made + * in two parts: + * + * - Check whether the resize need is small compared to the + * current size (relatively); if so, resize without further + * checking (essentially we assume that the original part is + * "dense" so that the result would be dense enough). + * + * - Otherwise, compute the resize using an actual density + * measurement based on counting the used array entries. + */ + + DUK_DDD(DUK_DDDPRINT("write to new array requires array resize, decide whether to do a " + "fast resize without abandon check (arr_idx=%ld, old_size=%ld)", + (long) arr_idx, (long) DUK_HOBJECT_GET_ASIZE(obj))); + + if (DUK_UNLIKELY(duk__abandon_array_check(thr, arr_idx, obj) != 0)) { + DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(obj)); + return NULL; + } + + DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, " + "decided to extend current allocation")); + + /* In principle it's possible to run out of memory extending the + * array but with the allocation going through if we were to abandon + * the array part and try again. In practice this should be rare + * because abandoned arrays have a higher per-entry footprint. + */ + + duk__grow_props_for_array_item(thr, obj, arr_idx); + + DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(obj)); + DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(obj)); + return DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx); +} + +DUK_LOCAL DUK_INLINE duk_tval *duk__obtain_arridx_slot(duk_hthread *thr, duk_uint32_t arr_idx, duk_hobject *obj) { + if (DUK_LIKELY(arr_idx < DUK_HOBJECT_GET_ASIZE(obj))) { + return DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx); + } else { + return duk__obtain_arridx_slot_slowpath(thr, arr_idx, obj); + } +} + /* * Proxy helpers */ @@ -605,6 +703,16 @@ DUK_INTERNAL void duk_hobject_realloc_props(duk_hthread *thr, DUK_ERROR_ALLOC_FAILED(thr); DUK_WO_NORETURN(return;); } +#if defined(DUK_USE_OBJSIZES16) + if (new_e_size_adjusted > DUK_UINT16_MAX || new_a_size > DUK_UINT16_MAX) { + /* If caller gave us sizes larger than what we can store, + * fail memory safely with an internal error rather than + * truncating the sizes. + */ + DUK_ERROR_INTERNAL(thr); + DUK_WO_NORETURN(return;); + } +#endif /* * Compute new alloc size and alloc new area. @@ -980,34 +1088,10 @@ DUK_INTERNAL void duk_hobject_resize_entrypart(duk_hthread *thr, duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); } -#if 0 /*unused */ -DUK_INTERNAL void duk_hobject_resize_arraypart(duk_hthread *thr, - duk_hobject *obj, - duk_uint32_t new_a_size) { - duk_uint32_t old_a_size; - duk_uint32_t new_e_size; - duk_uint32_t new_h_size; - - DUK_ASSERT(thr != NULL); - DUK_ASSERT(obj != NULL); - - if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) { - return; - } - old_a_size = DUK_HOBJECT_GET_ASIZE(obj); - if (old_a_size > new_a_size) { - new_a_size = old_a_size; - } - new_e_size = DUK_HOBJECT_GET_ESIZE(obj); - new_h_size = DUK_HOBJECT_GET_HSIZE(obj); - - duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); -} -#endif - /* Grow entry part allocation for one additional entry. */ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject *obj) { duk_uint32_t old_e_used; /* actually used, non-NULL entries */ + duk_uint32_t new_e_size_minimum; duk_uint32_t new_e_size; duk_uint32_t new_a_size; duk_uint32_t new_h_size; @@ -1023,6 +1107,7 @@ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject */ old_e_used = duk__count_used_e_keys(thr, obj); + new_e_size_minimum = old_e_used + 1; new_e_size = old_e_used + duk__get_min_grow_e(old_e_used); #if defined(DUK_USE_HOBJECT_HASH_PART) new_h_size = duk__get_default_h_size(new_e_size); @@ -1030,7 +1115,24 @@ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject new_h_size = 0; #endif new_a_size = DUK_HOBJECT_GET_ASIZE(obj); - DUK_ASSERT(new_e_size >= old_e_used + 1); /* duk__get_min_grow_e() is always >= 1 */ + +#if defined(DUK_USE_OBJSIZES16) + if (new_e_size > DUK_UINT16_MAX) { + new_e_size = DUK_UINT16_MAX; + } + if (new_h_size > DUK_UINT16_MAX) { + new_h_size = DUK_UINT16_MAX; + } + if (new_a_size > DUK_UINT16_MAX) { + new_a_size = DUK_UINT16_MAX; + } +#endif + DUK_ASSERT(new_h_size == 0 || new_h_size >= new_e_size); + + if (!(new_e_size >= new_e_size_minimum)) { + DUK_ERROR_ALLOC_FAILED(thr); + DUK_WO_NORETURN(return;); + } duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); } @@ -1039,19 +1141,36 @@ DUK_LOCAL void duk__grow_props_for_new_entry_item(duk_hthread *thr, duk_hobject DUK_LOCAL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj, duk_uint32_t highest_arr_idx) { duk_uint32_t new_e_size; duk_uint32_t new_a_size; + duk_uint32_t new_a_size_minimum; duk_uint32_t new_h_size; DUK_ASSERT(thr != NULL); DUK_ASSERT(obj != NULL); DUK_ASSERT(highest_arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)); - /* minimum new length is highest_arr_idx + 1 */ - new_e_size = DUK_HOBJECT_GET_ESIZE(obj); new_h_size = DUK_HOBJECT_GET_HSIZE(obj); + new_a_size_minimum = highest_arr_idx + 1; new_a_size = highest_arr_idx + duk__get_min_grow_a(highest_arr_idx); DUK_ASSERT(new_a_size >= highest_arr_idx + 1); /* duk__get_min_grow_a() is always >= 1 */ +#if defined(DUK_USE_OBJSIZES16) + if (new_e_size > DUK_UINT16_MAX) { + new_e_size = DUK_UINT16_MAX; + } + if (new_h_size > DUK_UINT16_MAX) { + new_h_size = DUK_UINT16_MAX; + } + if (new_a_size > DUK_UINT16_MAX) { + new_a_size = DUK_UINT16_MAX; + } +#endif + + if (!(new_a_size >= new_a_size_minimum)) { + DUK_ERROR_ALLOC_FAILED(thr); + DUK_WO_NORETURN(return;); + } + duk_hobject_realloc_props(thr, obj, new_e_size, new_a_size, new_h_size, 0); } @@ -1060,7 +1179,8 @@ DUK_LOCAL void duk__grow_props_for_array_item(duk_hthread *thr, duk_hobject *obj * We also compact the entries part while we're at it, although * this is not strictly required. */ -DUK_LOCAL void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) { +DUK_LOCAL void duk__abandon_array_part(duk_hthread *thr, duk_hobject *obj) { + duk_uint32_t new_e_size_minimum; duk_uint32_t new_e_size; duk_uint32_t new_a_size; duk_uint32_t new_h_size; @@ -1080,8 +1200,8 @@ DUK_LOCAL void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) { * of space right away. */ - new_e_size = e_used + a_used; - new_e_size = new_e_size + duk__get_min_grow_e(new_e_size); + new_e_size_minimum = e_used + a_used; + new_e_size = new_e_size_minimum + duk__get_min_grow_e(new_e_size_minimum); new_a_size = 0; #if defined(DUK_USE_HOBJECT_HASH_PART) new_h_size = duk__get_default_h_size(new_e_size); @@ -1089,6 +1209,23 @@ DUK_LOCAL void duk__abandon_array_checked(duk_hthread *thr, duk_hobject *obj) { new_h_size = 0; #endif +#if defined(DUK_USE_OBJSIZES16) + if (new_e_size > DUK_UINT16_MAX) { + new_e_size = DUK_UINT16_MAX; + } + if (new_h_size > DUK_UINT16_MAX) { + new_h_size = DUK_UINT16_MAX; + } + if (new_a_size > DUK_UINT16_MAX) { + new_a_size = DUK_UINT16_MAX; + } +#endif + + if (!(new_e_size >= new_e_size_minimum)) { + DUK_ERROR_ALLOC_FAILED(thr); + DUK_WO_NORETURN(return;); + } + DUK_DD(DUK_DDPRINT("abandon array part for hobject %p, " "array stats before: e_used=%ld, a_used=%ld, a_size=%ld; " "resize to e_size=%ld, a_size=%ld, h_size=%ld", @@ -3990,84 +4127,13 @@ DUK_INTERNAL duk_bool_t duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, * tv_obj, tv_key, and tv_val are copies of the original inputs. */ - if (arr_idx != DUK__NO_ARRAY_INDEX && - DUK_HOBJECT_HAS_ARRAY_PART(orig)) { - if (arr_idx < DUK_HOBJECT_GET_ASIZE(orig)) { - goto no_array_growth; - } - - /* - * Array needs to grow, but we don't want it becoming too sparse. - * If it were to become sparse, abandon array part, moving all - * array entries into the entries part (for good). - * - * Since we don't keep track of actual density (used vs. size) of - * the array part, we need to estimate somehow. The check is made - * in two parts: - * - * - Check whether the resize need is small compared to the - * current size (relatively); if so, resize without further - * checking (essentially we assume that the original part is - * "dense" so that the result would be dense enough). - * - * - Otherwise, compute the resize using an actual density - * measurement based on counting the used array entries. - */ - - DUK_DDD(DUK_DDDPRINT("write to new array requires array resize, decide whether to do a " - "fast resize without abandon check (arr_idx=%ld, old_size=%ld)", - (long) arr_idx, (long) DUK_HOBJECT_GET_ASIZE(orig))); - - if (duk__abandon_array_slow_check_required(arr_idx, DUK_HOBJECT_GET_ASIZE(orig))) { - duk_uint32_t old_used; - duk_uint32_t old_size; - - DUK_DDD(DUK_DDDPRINT("=> fast check is NOT OK, do slow check for array abandon")); - - duk__compute_a_stats(thr, orig, &old_used, &old_size); - - DUK_DDD(DUK_DDDPRINT("abandon check, array stats: old_used=%ld, old_size=%ld, arr_idx=%ld", - (long) old_used, (long) old_size, (long) arr_idx)); - - /* Note: intentionally use approximations to shave a few instructions: - * a_used = old_used (accurate: old_used + 1) - * a_size = arr_idx (accurate: arr_idx + 1) - */ - if (duk__abandon_array_density_check(old_used, arr_idx)) { - DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, " - "decided to abandon array part (would become too sparse)")); - - /* abandoning requires a props allocation resize and - * 'rechecks' the valstack, invalidating any existing - * valstack value pointers! - */ - duk__abandon_array_checked(thr, orig); - DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig)); - - goto write_to_entry_part; - } - - DUK_DDD(DUK_DDDPRINT("=> decided to keep array part")); - } else { - DUK_DDD(DUK_DDDPRINT("=> fast resize is OK")); + if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(orig)) { + tv = duk__obtain_arridx_slot(thr, arr_idx, orig); + if (tv == NULL) { + DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(orig)); + goto write_to_entry_part; } - DUK_DD(DUK_DDPRINT("write to new array entry beyond current length, " - "decided to extend current allocation")); - - duk__grow_props_for_array_item(thr, orig, arr_idx); - - no_array_growth: - - /* Note: assume array part is comprehensive, so that either - * the write goes to the array part, or we've abandoned the - * array above (and will not come here). - */ - - DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(orig)); - DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(orig)); - - tv = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, orig, arr_idx); /* prev value must be unused, no decref */ DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv)); DUK_TVAL_SET_TVAL(tv, tv_val); @@ -4680,8 +4746,8 @@ DUK_INTERNAL void duk_hobject_define_property_internal(duk_hthread *thr, duk_hob #endif new_len = duk__to_new_array_length_checked(thr, DUK_GET_TVAL_NEGIDX(thr, -1)); ((duk_harray *) obj)->length = new_len; - DUK_D(DUK_DPRINT("internal define property for array .length: %ld -> %ld", - (long) prev_len, (long) ((duk_harray *) obj)->length)); + DUK_DD(DUK_DDPRINT("internal define property for array .length: %ld -> %ld", + (long) prev_len, (long) ((duk_harray *) obj)->length)); goto pop_exit; } DUK_DD(DUK_DDPRINT("property already exists but is virtual -> failure")); @@ -4696,17 +4762,18 @@ DUK_INTERNAL void duk_hobject_define_property_internal(duk_hthread *thr, duk_hob DUK_DDD(DUK_DDDPRINT("property does not exist, object has array part -> possibly extend array part and write value (assert attributes)")); DUK_ASSERT(propflags == DUK_PROPDESC_FLAGS_WEC); - /* always grow the array, no sparse / abandon support here */ - if (arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)) { - duk__grow_props_for_array_item(thr, obj, arr_idx); + tv1 = duk__obtain_arridx_slot(thr, arr_idx, obj); + if (tv1 == NULL) { + DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(obj)); + goto write_to_entry_part; } - DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(obj)); tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx); goto write_value; } } + write_to_entry_part: DUK_DDD(DUK_DDDPRINT("property does not exist, object belongs in entry part -> allocate new entry and write value and attributes")); e_idx = duk__hobject_alloc_entry_checked(thr, obj, key); /* increases key refcount */ DUK_ASSERT(e_idx >= 0); @@ -4763,13 +4830,11 @@ DUK_INTERNAL void duk_hobject_define_property_internal_arridx(duk_hthread *thr, DUK_DDD(DUK_DDDPRINT("define property to array part (property may or may not exist yet)")); - /* always grow the array, no sparse / abandon support here */ - if (arr_idx >= DUK_HOBJECT_GET_ASIZE(obj)) { - duk__grow_props_for_array_item(thr, obj, arr_idx); + tv1 = duk__obtain_arridx_slot(thr, arr_idx, obj); + if (tv1 == NULL) { + DUK_ASSERT(!DUK_HOBJECT_HAS_ARRAY_PART(obj)); + goto write_slow; } - - DUK_ASSERT(arr_idx < DUK_HOBJECT_GET_ASIZE(obj)); - tv1 = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, obj, arr_idx); tv2 = duk_require_tval(thr, -1); DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */ @@ -4778,6 +4843,7 @@ DUK_INTERNAL void duk_hobject_define_property_internal_arridx(duk_hthread *thr, return; } + write_slow: DUK_DDD(DUK_DDDPRINT("define property fast path didn't work, use slow path")); key = duk_push_uint_to_hstring(thr, (duk_uint_t) arr_idx); @@ -5306,7 +5372,7 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) { DUK_DDD(DUK_DDDPRINT("accessor cannot go to array part, abandon array")); - duk__abandon_array_checked(thr, obj); + duk__abandon_array_part(thr, obj); } /* write to entry part */ @@ -5348,17 +5414,22 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, if (arr_idx != DUK__NO_ARRAY_INDEX && DUK_HOBJECT_HAS_ARRAY_PART(obj)) { if (new_flags == DUK_PROPDESC_FLAGS_WEC) { -#if 0 DUK_DDD(DUK_DDDPRINT("new data property attributes match array defaults, attempt to write to array part")); - /* may become sparse...*/ -#endif - /* XXX: handling for array part missing now; this doesn't affect - * compliance but causes array entry writes using defineProperty() - * to always abandon array part. - */ + tv2 = duk__obtain_arridx_slot(thr, arr_idx, obj); + if (tv2 == NULL) { + DUK_DDD(DUK_DDDPRINT("failed writing to array part, abandoned array")); + } else { + DUK_DDD(DUK_DDDPRINT("success in writing to array part")); + DUK_ASSERT(DUK_HOBJECT_HAS_ARRAY_PART(obj)); + DUK_ASSERT(DUK_TVAL_IS_UNUSED(tv2)); + DUK_TVAL_SET_TVAL(tv2, &tv); + DUK_TVAL_INCREF(thr, tv2); + goto success_exotics; + } + } else { + DUK_DDD(DUK_DDDPRINT("new data property cannot go to array part, abandon array")); + duk__abandon_array_part(thr, obj); } - DUK_DDD(DUK_DDDPRINT("new data property cannot go to array part, abandon array")); - duk__abandon_array_checked(thr, obj); /* fall through */ } @@ -5535,7 +5606,7 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, DUK_DDD(DUK_DDDPRINT("convert property to accessor property")); if (curr.a_idx >= 0) { DUK_DDD(DUK_DDDPRINT("property to convert is stored in an array entry, abandon array and re-lookup")); - duk__abandon_array_checked(thr, obj); + duk__abandon_array_part(thr, obj); duk_pop_unsafe(thr); /* remove old value */ rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE); DUK_UNREF(rc); @@ -5691,7 +5762,7 @@ duk_bool_t duk_hobject_define_property_helper(duk_hthread *thr, } DUK_DDD(DUK_DDDPRINT("array index, new property attributes do not match array defaults, abandon array and re-lookup")); - duk__abandon_array_checked(thr, obj); + duk__abandon_array_part(thr, obj); duk_pop_unsafe(thr); /* remove old value */ rc = duk__get_own_propdesc_raw(thr, obj, key, arr_idx, &curr, DUK_GETDESC_FLAG_PUSH_VALUE); DUK_UNREF(rc); @@ -6012,13 +6083,13 @@ DUK_INTERNAL void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_ho * for the same object; not likely). */ - duk__abandon_array_checked(thr, obj); + duk__abandon_array_part(thr, obj); DUK_ASSERT(DUK_HOBJECT_GET_ASIZE(obj) == 0); for (i = 0; i < DUK_HOBJECT_GET_ENEXT(obj); i++) { duk_uint8_t *fp; - /* since duk__abandon_array_checked() causes a resize, there should be no gaps in keys */ + /* since duk__abandon_array_part() causes a resize, there should be no gaps in keys */ DUK_ASSERT(DUK_HOBJECT_E_GET_KEY(thr->heap, obj, i) != NULL); /* avoid multiple computations of flags address; bypasses macros */ @@ -6032,7 +6103,7 @@ DUK_INTERNAL void duk_hobject_object_seal_freeze_helper(duk_hthread *thr, duk_ho DUK_HOBJECT_CLEAR_EXTENSIBLE(obj); - /* no need to compact since we already did that in duk__abandon_array_checked() + /* no need to compact since we already did that in duk__abandon_array_part() * (regardless of whether an array part existed or not. */ diff --git a/src-input/duk_js_executor.c b/src-input/duk_js_executor.c index b0f6763b8b..a11909c4c2 100644 --- a/src-input/duk_js_executor.c +++ b/src-input/duk_js_executor.c @@ -2952,7 +2952,7 @@ DUK_INTERNAL void duk_js_execute_bytecode(duk_hthread *exec_thr) { #endif DUK_DDD(DUK_DDDPRINT("after setjmp, delayed catch setup: %ld\n", (long) delayed_catch_setup)); - if (DUK_UNLIKELY(delayed_catch_setup)) { + if (DUK_UNLIKELY(delayed_catch_setup != 0)) { duk_hthread *thr = entry_thread->heap->curr_thread; delayed_catch_setup = 0; diff --git a/tests/ecmascript/test-bi-array-proto-map-sparse.js b/tests/ecmascript/test-bi-array-proto-map-sparse.js new file mode 100644 index 0000000000..8daaf8621a --- /dev/null +++ b/tests/ecmascript/test-bi-array-proto-map-sparse.js @@ -0,0 +1,21 @@ +/*=== +10000001 +10000001 +25 +call count: 1 +done +===*/ + +try { + var callCount = 0; + var a = []; a[1e7] = 5; + print(a.length); + var b = a.map(function (v) { callCount++; return v*v; }); + print(b.length); + print(b[1e7]); + print('call count: ' + callCount); +} catch (e) { + print(e.stack || e); +} + +print('done'); diff --git a/tests/ecmascript/test-bi-object-defineproperty-arridx.js b/tests/ecmascript/test-bi-object-defineproperty-arridx.js new file mode 100644 index 0000000000..583bf94ec3 --- /dev/null +++ b/tests/ecmascript/test-bi-object-defineproperty-arridx.js @@ -0,0 +1,68 @@ +/* + * Object.defineProperty() array part behavior coverage. + */ + +/*=== +1000 +foo foo undefined undefined +1000000001 +foo foo undefined bar +1000 +foo foo undefined undefined +1001 +foo foo bar undefined +999 +foo undefined foo undefined undefined undefined +done +===*/ + +function test() { + var A; + var i; + + // These maintain the array part. + A = []; + for (i = 0; i < 1000; i++) { + Object.defineProperty(A, i, { value: 'foo', writable: true, enumerable: true, configurable: true }); + } + print(A.length); + print(A[0], A[999], A[1000], A[1e9]); + //print(JSON.stringify(Duktape.info(A))); + + // This abandons it, array would become too sparse. + Object.defineProperty(A, 1e9, { value: 'bar', writable: true, enumerable: true, configurable: true }); + print(A.length); + print(A[0], A[999], A[1000], A[1e9]); + //print(JSON.stringify(Duktape.info(A))); + + // These maintain the array part. + A = []; + for (i = 0; i < 1000; i++) { + Object.defineProperty(A, i, { value: 'foo', writable: true, enumerable: true, configurable: true }); + } + print(A.length); + print(A[0], A[999], A[1000], A[1e9]); + //print(JSON.stringify(Duktape.info(A))); + + // This abandons it, non-standard attributes. + Object.defineProperty(A, 1000, { value: 'bar', writable: true, enumerable: false, configurable: true }); + print(A.length); + print(A[0], A[999], A[1000], A[1e9]); + //print(JSON.stringify(Duktape.info(A))); + + // These maintain the array part, despite there being gaps. + A = []; + for (i = 0; i < 1000; i += 2) { + Object.defineProperty(A, i, { value: 'foo', writable: true, enumerable: true, configurable: true }); + } + print(A.length); + print(A[0], A[1], A[998], A[999], A[1000], A[1e9]); + //print(JSON.stringify(Duktape.info(A))); +} + +try { + test(); +} catch (e) { + print(e.stack || e); +} +print('done'); diff --git a/tests/ecmascript/test-bug-props-asize-gh2023-2.js b/tests/ecmascript/test-bug-props-asize-gh2023-2.js new file mode 100644 index 0000000000..d29f106a28 --- /dev/null +++ b/tests/ecmascript/test-bug-props-asize-gh2023-2.js @@ -0,0 +1,15 @@ +/* + * https://github.com/svaarala/duktape/issues/2023 + * + * Modified variant. + */ + +/*=== +done +===*/ + +var input = []; +input[65536] = 0; +var output = input.map(Math.cos); + +print('done'); diff --git a/tests/ecmascript/test-bug-props-asize-gh2023.js b/tests/ecmascript/test-bug-props-asize-gh2023.js new file mode 100644 index 0000000000..bf6794d56d --- /dev/null +++ b/tests/ecmascript/test-bug-props-asize-gh2023.js @@ -0,0 +1,16 @@ +/* + * https://github.com/svaarala/duktape/issues/2023 + */ + +/*=== +done +===*/ + +function mapchar ( v ) { } +var input ; +var round ; +input = [ ] ; +input[ 65536 ] = 0 ; +input.map( mapchar ).join( '' ); + +print('done');