Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/perry-runtime/src/array/indexing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ pub(crate) fn array_iteration_is_exotic(arr: *const ArrayHeader) -> bool {
if ARRAY_PROTO_HAS_INDEX.load(Ordering::Relaxed) {
return true;
}
if OBJECT_PROTO_HAS_INDEX.load(Ordering::Relaxed) {
return true;
}
// Live indices beyond the dense backing store are stored in the sparse
// named-property map, which the raw element loop never reads.
unsafe { (*arr).length > (*arr).capacity }
Expand Down
35 changes: 16 additions & 19 deletions crates/perry-runtime/src/array/iter_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ pub extern "C" fn js_array_join(
}

let elements_ptr = (arr as *const u8).add(std::mem::size_of::<ArrayHeader>()) as *const f64;
let exotic = crate::array::array_iteration_is_exotic(arr);

// Get separator string
let sep_str = if separator.is_null() {
Expand All @@ -750,27 +751,23 @@ pub extern "C" fn js_array_join(
if i > 0 {
result.push_str(sep_str);
}
let element_bits = (*elements_ptr.add(i)).to_bits();
let element_bits = if exotic {
if !crate::array::array_spec_has_index(arr, i as u32) {
// absent slot (own or inherited) → empty string per spec
continue;
}
crate::array::array_spec_get(arr, i as u32).to_bits()
} else {
let bits = (*elements_ptr.add(i)).to_bits();
// Issue #907: `Array(n)` initializes slots to TAG_HOLE; per
// ES2015 §22.1.3.13 holes stringify to the empty string.
if bits == crate::value::TAG_HOLE {
continue;
}
bits
};
let jsvalue = JSValue::from_bits(element_bits);

// Issue #907: `Array(n)` initializes slots to TAG_HOLE
// (see `js_array_alloc_with_length`). Per ES2015 §22.1.3.13
// (Array.prototype.join), holes go through Get which returns
// undefined → the spec's ToString step turns them into the
// empty string. Without this check the catch-all below
// emitted "[object Object]", so `Array(3).join("0")` returned
// `"[object Object]0[object Object]0[object Object]"` instead
// of `"00"`. dayjs's `m(t,e,n)` pad utility builds the UTC
// offset string via `Array(e+1-r.length).join(n)` and the
// result silently corrupted `b.z(this)` (the format `i`
// capture), which downstream triggered
// `TypeError: (number).replace is not a function` once the
// catch-all fallthrough reached `i.replace(":","")`.
if element_bits == crate::value::TAG_HOLE {
// hole → empty string per spec
continue;
}

// Convert element to string based on its type
if jsvalue.is_string() {
let str_ptr = jsvalue.as_string_ptr();
Expand Down
37 changes: 30 additions & 7 deletions crates/perry-runtime/src/array/splice_slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,24 +255,47 @@ pub extern "C" fn js_array_slice(
let is_plain = crate::array::species::species_result_is_plain_array(result_box);
let result = crate::value::js_nanbox_get_pointer(result_box) as *mut ArrayHeader;

// Copy elements
// Copy elements — use spec-generic Get when the source is exotic
// (has Array.prototype or Object.prototype indexed properties) so
// inherited indices appear in the result just as [[Get]] would return
// them (ECMA-262 §23.1.3.25 step 8b "If HasProperty(O, from)…").
let src_elements = (arr as *const u8).add(std::mem::size_of::<ArrayHeader>()) as *const f64;
let src_exotic = crate::array::array_iteration_is_exotic(arr);
if is_plain {
(*result).length = slice_len;
let dst_elements =
(result as *mut u8).add(std::mem::size_of::<ArrayHeader>()) as *mut f64;
for i in 0..slice_len as usize {
// GC_STORE_AUDIT(BARRIERED): slice result init is followed by layout/barrier rebuild.
ptr::write(
dst_elements.add(i),
ptr::read(src_elements.add(start_idx as usize + i)),
);
let src_idx = start_idx as usize + i;
let v = if src_exotic {
if crate::array::array_spec_has_index(arr, src_idx as u32) {
crate::array::array_spec_get(arr, src_idx as u32)
} else {
f64::from_bits(crate::value::TAG_HOLE)
}
} else {
// GC_STORE_AUDIT(BARRIERED): slice result init is followed by layout/barrier rebuild.
ptr::read(src_elements.add(src_idx))
};
ptr::write(dst_elements.add(i), v);
}
rebuild_array_layout(result);
} else {
// Custom species container: CreateDataPropertyOrThrow per element.
for i in 0..slice_len as usize {
let v = ptr::read(src_elements.add(start_idx as usize + i));
let src_idx = start_idx as usize + i;
let v = if src_exotic {
if !crate::array::array_spec_has_index(arr, src_idx as u32) {
continue; // absent slot → no property created in result
}
crate::array::array_spec_get(arr, src_idx as u32)
} else {
let v = ptr::read(src_elements.add(src_idx));
if v.to_bits() == crate::value::TAG_HOLE {
continue; // hole → no property created in result
}
v
};
crate::array::species::species_result_set(result_box, i, v);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Expand Down
Loading