use std::collections::{TryReserveError, hash_map::Entry};
use crate::{
ecmascript::{
Agent, ArgumentsList, Array, ArrayHeapData, BUILTIN_STRING_MEMORY, ExceptionType, JsResult,
Number, Object, OrdinaryObject, PropertyDescriptor, TryError, TryResult, Value, construct,
get, get_function_realm, is_array, is_constructor, same_value, to_number, to_uint32,
to_uint32_number,
},
engine::{Bindable, GcScope, NoGcScope, Scopable},
heap::{CreateHeapData, ElementStorageMut, Heap, WellKnownSymbols},
};
pub(crate) fn array_create<'a>(
agent: &mut Agent,
length: usize,
capacity: usize,
proto: Option<Object>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, Array<'a>> {
if length > (2usize.pow(32) - 1) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"invalid array length",
gc,
));
}
let object_index = if let Some(proto) = proto {
if proto
== agent
.current_realm_record()
.intrinsics()
.array_prototype()
.into()
{
None
} else {
Some(
OrdinaryObject::create_object(agent, Some(proto), &[])
.expect("Should perform GC here")
.bind(gc),
)
}
} else {
None
};
let mut elements = match agent.heap.elements.allocate_elements_with_length(capacity) {
Ok(e) => e,
Err(err) => {
return Err(agent.throw_allocation_exception(err, gc));
}
};
elements.len = length as u32;
let data = ArrayHeapData {
object_index,
elements: elements.bind(gc),
};
Ok(agent.heap.create(data))
}
pub(crate) fn array_species_create<'a>(
agent: &mut Agent,
original_array: Object,
length: usize,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Object<'a>> {
let nogc = gc.nogc();
let original_array = original_array.bind(nogc);
let original_is_array = is_array(agent, original_array, nogc).unbind()?;
if !original_is_array {
let new_array = array_create(agent, length, length, None, gc.into_nogc())?;
return Ok(new_array.into());
}
let mut c = get(
agent,
original_array.unbind(),
BUILTIN_STRING_MEMORY.constructor.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(c_func) = is_constructor(agent, c) {
let this_realm = agent.current_realm(gc.nogc());
let realm_c = get_function_realm(agent, c_func, gc.nogc())
.unbind()?
.bind(gc.nogc());
if this_realm != realm_c {
if same_value(
agent,
c,
agent.get_realm_record_by_id(realm_c).intrinsics().array(),
) {
c = Value::Undefined;
}
}
}
if let Ok(c_obj) = Object::try_from(c) {
c = get(
agent,
c_obj.unbind(),
WellKnownSymbols::Species.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if c.is_null() {
c = Value::Undefined;
}
}
if c.is_undefined() {
let new_array = array_create(agent, length, length, None, gc.into_nogc())?;
return Ok(new_array.into());
}
let Some(c) = is_constructor(agent, c) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Not a constructor",
gc.into_nogc(),
));
};
let length = Value::from_f64(agent, length as f64, gc.nogc());
construct(
agent,
c.unbind(),
Some(ArgumentsList::from_mut_value(&mut length.unbind())),
None,
gc,
)
}
pub(crate) fn array_set_length<'a>(
agent: &mut Agent,
a: Array,
desc: PropertyDescriptor,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let a = a.bind(gc.nogc());
let desc = desc.bind(gc.nogc());
let Some(desc_value) = desc.value else {
return Ok(array_set_length_no_value_field(agent, a, desc));
};
let PropertyDescriptor {
enumerable: desc_enumerable,
configurable: desc_configurable,
writable: desc_writable,
..
} = desc;
let a = a.scope(agent, gc.nogc());
let scoped_desc_value = desc_value.scope(agent, gc.nogc());
let new_len = to_uint32(agent, desc_value.unbind(), gc.reborrow()).unbind()?;
let number_len = to_number(
agent,
unsafe { scoped_desc_value.take(agent) },
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !Number::same_value_zero_(agent, number_len, new_len.into()) {
return Err(agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"invalid array length",
gc.into_nogc(),
));
}
let gc = gc.into_nogc();
let a = a.get(agent).bind(gc);
array_set_length_handling(
agent,
a,
new_len,
desc_configurable,
desc_enumerable,
desc_writable,
)
.map_err(|err| agent.throw_allocation_exception(err, gc))
}
pub(crate) fn array_try_set_length<'gc>(
agent: &mut Agent,
a: Array,
desc: PropertyDescriptor,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, bool> {
let Some(desc_value) = desc.value else {
return TryResult::Continue(array_set_length_no_value_field(agent, a, desc));
};
let Ok(number_len) = Number::try_from(desc_value) else {
return TryError::GcError.into();
};
let new_len = to_uint32_number(agent, number_len);
if !Number::same_value_zero_(agent, number_len, new_len.into()) {
return TryError::GcError.into();
}
match array_set_length_handling(
agent,
a,
new_len,
desc.configurable,
desc.enumerable,
desc.writable,
) {
Ok(b) => TryResult::Continue(b),
Err(err) => agent.throw_allocation_exception(err, gc).into(),
}
}
fn array_set_length_no_value_field(agent: &mut Agent, a: Array, desc: PropertyDescriptor) -> bool {
if !desc.has_fields() {
return true;
}
if desc.configurable == Some(true) || desc.enumerable == Some(true) {
return false;
}
if !desc.is_generic_descriptor() && desc.is_accessor_descriptor() {
return false;
}
if !a.length_writable(agent) {
if desc.writable == Some(true) {
return false;
}
} else if desc.writable == Some(false) {
a.set_length_readonly(agent);
}
true
}
pub(crate) fn array_set_length_handling(
agent: &mut Agent,
a: Array,
new_len: u32,
desc_configurable: Option<bool>,
desc_enumerable: Option<bool>,
desc_writable: Option<bool>,
) -> Result<bool, TryReserveError> {
let Heap {
arrays, elements, ..
} = &mut agent.heap;
let elems = a.get_elements_mut(arrays);
let (old_len, old_len_writable) = (elems.len(), elems.len_writable);
if new_len >= old_len {
if !array_ordinary_define_property_checks(
old_len_writable,
old_len,
desc_configurable,
desc_enumerable,
desc_writable,
new_len,
) {
return Ok(false);
}
elems.reserve(elements, new_len)?;
elems.len = new_len;
elems.len_writable = desc_writable.unwrap_or(old_len_writable);
return Ok(true);
}
debug_assert!(old_len > new_len);
if !old_len_writable {
return Ok(false);
}
let new_writable = desc_writable.unwrap_or(true);
if !array_ordinary_define_property_checks(
old_len_writable,
old_len,
desc_configurable,
desc_enumerable,
if desc_writable == Some(false) {
Some(true)
} else {
desc_writable
},
new_len,
) {
return Ok(false);
}
let ElementStorageMut {
values,
descriptors,
} = elements.get_element_storage_mut(elems);
if let Entry::Occupied(mut descriptors) = descriptors
&& !descriptors.get().is_empty()
{
let descs = descriptors.get_mut();
for i in (new_len..old_len).rev() {
let delete_succeeded = if let Some(d) = descs.get(&i) {
if d.is_configurable() {
descs.remove(&i);
true
} else {
false
}
} else {
true
};
if !delete_succeeded {
values[(i + 1) as usize..old_len as usize].fill(None);
elems.len = i + 1;
elems.len_writable &= new_writable;
return Ok(false);
}
}
if descs.is_empty() {
descriptors.remove();
}
}
values[new_len as usize..old_len as usize].fill(None);
elems.len = new_len;
elems.len_writable &= new_writable;
Ok(true)
}
fn array_ordinary_define_property_checks(
current_writable: bool,
current_value: u32,
desc_configurable: Option<bool>,
desc_enumerable: Option<bool>,
desc_writable: Option<bool>,
desc_value: u32,
) -> bool {
if desc_configurable == Some(true) {
return false;
}
if desc_enumerable == Some(true) {
return false;
}
if !current_writable {
if desc_writable == Some(true) {
return false;
}
if desc_value != current_value {
return false;
}
}
true
}