use super::error;
use super::ic::{IcEntry, IcKind, STRING_RECEIVER_TAG};
use super::{Cx, Throw};
use crate::heap::Heap;
use crate::heap::atom::{KeyKind, PropertyKey};
use crate::heap::object::{NONE, ObjectKind, flags};
use crate::runtime::{Intrinsic, Names};
use crate::shape::{PropertyFlags, STATIC_CELL};
use crate::value::{Kind, ObjectId, ValidityCellId, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Found {
Data(Value),
Accessor(Value, Value),
}
#[inline]
pub(crate) fn own(heap: &Heap, names: &Names, object: ObjectId, key: PropertyKey) -> Option<Found> {
own_or_pending(heap, names, object, key).expect("a materialized property")
}
#[inline]
fn own_or_pending(
heap: &Heap,
names: &Names,
object: ObjectId,
key: PropertyKey,
) -> Result<Option<Found>, Pending> {
match key.kind() {
KeyKind::Index(index) => {
let value = heap.get_element(object, index);
if !value.is_empty() {
return Ok(Some(Found::Data(value)));
}
if let Some(cell) = super::arguments::mapped_cell(heap, object, index) {
return Ok(Some(Found::Data(heap.scope_get(cell, 0, 0))));
}
}
KeyKind::String(id) if id == names.length => {
if heap.object_kind(object) == ObjectKind::Array {
return Ok(Some(Found::Data(length_value(heap.array_length(object)))));
}
}
_ => {}
}
let Some((slot, property)) = heap.lookup_own(object, key) else {
return Ok(None);
};
if property.has(PropertyFlags::AUTOINIT) {
return Err(Pending);
}
Ok(Some(slot_value(heap, object, slot, property)))
}
pub(crate) fn pending_value(
heap: &Heap,
units: crate::bytecode::Units<'_>,
names: &Names,
function: ObjectId,
key: PropertyKey,
) -> Option<Value> {
let unit = units.get(heap.bytecode_unit(heap.function_bytecode(function)?));
if key == Names::key(names.length) {
Some(Value::int32(i32::from(unit.length)))
} else if key == Names::key(names.name) {
Some(Value::string(unit.name_atom.unwrap_or(names.empty)))
} else {
None
}
}
pub(crate) fn peek_data(
heap: &Heap,
units: crate::bytecode::Units<'_>,
names: &Names,
object: ObjectId,
key: PropertyKey,
(slot, flags): (u32, PropertyFlags),
) -> Value {
if flags.has(PropertyFlags::AUTOINIT) {
return pending_value(heap, units, names, object, key).unwrap_or(Value::UNDEFINED);
}
heap.get_slot(object, slot)
}
#[derive(Debug)]
struct Pending;
#[inline(always)]
fn slot_value(heap: &Heap, object: ObjectId, slot: u32, property: PropertyFlags) -> Found {
if property.has(PropertyFlags::ACCESSOR) {
Found::Accessor(heap.get_slot(object, slot), heap.get_slot(object, slot + 1))
} else {
Found::Data(heap.get_slot(object, slot))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Lookup {
Hit(Found),
Miss,
Exotic(ObjectId),
}
#[inline]
pub(crate) fn lookup(heap: &Heap, names: &Names, object: ObjectId, key: PropertyKey) -> Lookup {
let mut current = object;
loop {
if heap.object_kind(current).is_exotic() {
return Lookup::Exotic(current);
}
match own_or_pending(heap, names, current, key) {
Ok(Some(found)) => return Lookup::Hit(found),
Ok(None) => {}
Err(Pending) => return Lookup::Exotic(current),
}
match heap.prototype_of(current) {
Some(prototype) => current = prototype,
None => return Lookup::Miss,
}
}
}
#[inline]
fn get_from(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
receiver: Value,
) -> Result<Value, Throw> {
match lookup(cx.heap, &cx.realm.names, object, key) {
Lookup::Hit(found) => value_of(cx, Some(found), receiver),
Lookup::Miss => Ok(Value::UNDEFINED),
Lookup::Exotic(holder) => super::object::get(cx, holder, key, receiver),
}
}
pub(crate) fn value_of(
cx: &mut Cx<'_>,
found: Option<Found>,
receiver: Value,
) -> Result<Value, Throw> {
match found {
None => Ok(Value::UNDEFINED),
Some(Found::Data(value)) => Ok(value),
Some(Found::Accessor(getter, _)) => call_getter(cx, getter, receiver),
}
}
#[inline(never)]
fn call_getter(cx: &mut Cx<'_>, getter: Value, receiver: Value) -> Result<Value, Throw> {
if getter.is_undefined() {
return Ok(Value::UNDEFINED);
}
super::call(cx, getter, receiver, &[])
}
#[inline]
pub(crate) fn get_value(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
) -> Result<Value, Throw> {
get_from(cx, object, key, Value::object(object))
}
pub(crate) fn length_value(length: u32) -> Value {
match i32::try_from(length) {
Ok(length) => Value::int32(length),
Err(_) => Value::number(f64::from(length)),
}
}
#[inline(never)]
pub(crate) fn get_computed(cx: &mut Cx<'_>, receiver: Value, key: Value) -> Result<Value, Throw> {
if receiver.is_null() || receiver.is_undefined() {
let base = if receiver.is_null() {
"null"
} else {
"undefined"
};
return Err(error::type_error(
cx,
&format!("cannot read property of {base}"),
));
}
let key = super::ops::to_property_key(cx, key)?;
get(cx, receiver, key)
}
#[inline(never)]
pub(crate) fn get(cx: &mut Cx<'_>, receiver: Value, key: PropertyKey) -> Result<Value, Throw> {
let object = match receiver.kind() {
Kind::Object => receiver.as_object().unwrap(),
Kind::String => {
let id = receiver.as_string().unwrap();
let length = cx.heap.string_len(id);
match key.kind() {
KeyKind::String(name) if name == cx.realm.names.length => {
return Ok(length_value(length as u32));
}
KeyKind::Index(index) if (index as usize) < length => {
let unit = cx.heap.string(id).repr().unit(index as usize);
let string = cx
.heap
.intern_units(&[unit])
.map_err(|_| error::oom(cx.realm))?;
return Ok(Value::string(string));
}
_ => cx.realm.intrinsic(Intrinsic::StringPrototype),
}
}
Kind::Number | Kind::Int32 => cx.realm.intrinsic(Intrinsic::NumberPrototype),
Kind::Bool => cx.realm.intrinsic(Intrinsic::BooleanPrototype),
Kind::Symbol => cx.realm.intrinsic(Intrinsic::SymbolPrototype),
Kind::BigInt => cx.realm.intrinsic(Intrinsic::BigIntPrototype),
Kind::Undefined | Kind::Null => {
let message = format!(
"cannot read property '{}' of {}",
atom_text(cx.heap, key),
if receiver.is_null() {
"null"
} else {
"undefined"
}
);
return Err(error::type_error(cx, &message));
}
_ => cx.realm.intrinsic(Intrinsic::ObjectPrototype),
};
get_from(cx, object, key, receiver)
}
#[inline(never)]
pub(crate) fn get_cached(
cx: &mut Cx<'_>,
receiver: Value,
key: PropertyKey,
site: usize,
fill: bool,
) -> Result<Value, Throw> {
if cx.heap.ics.is_megamorphic(site)
&& let Some(object) = receiver.as_object()
{
let shape = cx.heap.shape_of(object).index();
if let Some(entry) = cx.heap.ics.mega_entry(shape, key.raw()) {
let heap = &*cx.heap;
let hit = super::ic::get_hit(
&heap.objects,
&heap.cells,
&heap.values,
&heap.strings,
entry,
receiver,
);
if let Some(value) = hit {
cx.heap.ics.count_poly_hit(IcKind::Get);
return Ok(value);
}
}
}
if let Some(row) = cx.heap.ics.poly_row(site) {
let heap = &*cx.heap;
for &entry in row {
let hit = super::ic::get_hit(
&heap.objects,
&heap.cells,
&heap.values,
&heap.strings,
entry,
receiver,
);
if let Some(value) = hit {
cx.heap.ics.count_poly_hit(IcKind::Get);
return Ok(value);
}
}
}
let names = cx.realm.names;
let named = matches!(key.kind(), KeyKind::String(id) if id != names.length)
|| matches!(key.kind(), KeyKind::Symbol(_));
if fill && matches!(key.kind(), KeyKind::String(id) if id == names.length) {
let length = match receiver.kind() {
Kind::String => Some(cx.heap.string_len(receiver.as_string().unwrap())),
Kind::Object
if cx.heap.object_kind(receiver.as_object().unwrap()) == ObjectKind::Array =>
{
Some(cx.heap.array_length(receiver.as_object().unwrap()) as usize)
}
_ => None,
};
if let Some(length) = length {
let entry = IcEntry {
w0: super::ic::LENGTH_TAG,
..IcEntry::EMPTY
};
let filled = cx.heap.ics.record(site, entry);
cx.heap.ics.count_miss(site, filled);
return Ok(length_value(length as u32));
}
}
let (start, string) = match receiver.kind() {
Kind::Object if fill && !matches!(key.kind(), KeyKind::Index(_)) => {
(receiver.as_object().unwrap(), false)
}
Kind::String if fill && named => (cx.realm.intrinsic(Intrinsic::StringPrototype), true),
_ => {
cx.heap.ics.count_miss(site, false);
return get(cx, receiver, key);
}
};
let heap = &*cx.heap;
let mut holder = start;
let mut pending = false;
let walked = loop {
if heap.object_kind(holder).is_exotic() {
cx.heap.ics.count_miss(site, false);
return get(cx, receiver, key);
}
if !named && heap.object_kind(holder) == ObjectKind::Array {
break Err(Some(Found::Data(length_value(heap.array_length(holder)))));
}
if let Some((slot, property)) = heap.lookup_own(holder, key) {
if property.has(PropertyFlags::AUTOINIT) {
pending = true;
break Err(None);
}
if property.has(PropertyFlags::ACCESSOR)
|| (heap.is_dictionary(holder) && holder == start)
|| (!string && heap.is_dictionary(start))
{
break Err(Some(slot_value(heap, holder, slot, property)));
}
break Ok(slot);
}
match heap.prototype_of(holder) {
Some(prototype) => holder = prototype,
None => break Err(None),
}
};
if pending {
cx.heap.ics.count_miss(site, false);
super::object::materialize(cx, holder)?;
let (slot, _) = cx.heap.lookup_own(holder, key).expect("a closure property");
return Ok(cx.heap.get_slot(holder, slot));
}
let slot = match walked {
Ok(slot) => slot,
Err(found) => {
cx.heap.ics.count_miss(site, false);
return value_of(cx, found, receiver);
}
};
let cell = if string {
cx.heap.chain_cell(start)
} else if holder == start {
Ok(ValidityCellId(NONE))
} else {
cx.heap.validity_cell(start)
};
let heap = &mut *cx.heap;
let mut filled = false;
if let Ok(cell) = cell {
let shape = if string {
STRING_RECEIVER_TAG
} else {
heap.shape_of(start).index()
};
let holder = if holder == start && !string {
NONE
} else {
holder.index()
};
let fill = IcEntry {
w0: shape,
w1: holder,
w2: slot,
w3: cell.index(),
};
if heap.ics.is_megamorphic(site) && !string {
heap.ics.record_mega(key.raw(), fill);
filled = true;
} else {
filled = heap.ics.record(site, fill);
}
}
heap.ics.count_miss(site, filled);
Ok(heap.get_slot(holder, slot))
}
#[inline(never)]
pub(crate) fn set_cached(
cx: &mut Cx<'_>,
receiver: Value,
key: PropertyKey,
value: Value,
strict: bool,
site: usize,
fill: bool,
) -> Result<(), Throw> {
if let Some(row) = cx.heap.ics.poly_row(site) {
let row = *row;
let heap = &mut *cx.heap;
for entry in row {
if super::ic::set_hit(
&mut heap.objects,
&heap.cells,
&mut heap.values,
entry,
receiver,
value,
) {
heap.ics.count_poly_hit(IcKind::Set);
return Ok(());
}
}
}
let names = cx.realm.names;
let cacheable = fill
&& match key.kind() {
KeyKind::String(id) => id != names.length,
KeyKind::Symbol(_) => true,
KeyKind::Index(_) => false,
};
let Some(object) = receiver.as_object().filter(|_| cacheable) else {
cx.heap.ics.count_miss(site, false);
return set(cx, receiver, key, value, strict);
};
let heap = &*cx.heap;
let header = *heap.header(object);
let before = header.shape;
let plain = !header.has(flags::DICTIONARY);
let own = heap.lookup_own(object, key);
let inherited_plain = own.is_none() && chain_allows_add(heap, object, key);
set_on(cx, object, key, value, strict)?;
let heap = &mut *cx.heap;
let entry = match own {
Some((slot, property)) if plain && is_plain_data(property) => Some(IcEntry {
w0: before.index(),
w1: before.index(),
w2: slot,
w3: STATIC_CELL.index(),
}),
None if plain
&& inherited_plain
&& header.flags & (flags::EXTENSIBLE | flags::USED_AS_PROTOTYPE)
== flags::EXTENSIBLE =>
{
let after = heap.shape_of(object);
match heap.lookup_own(object, key) {
Some((slot, property))
if !heap.is_dictionary(object)
&& property == PropertyFlags::DEFAULT
&& heap.shape(after).parent == Some(before) =>
{
match heap.validity_cell(object) {
Ok(cell) => Some(IcEntry {
w0: before.index(),
w1: after.index(),
w2: slot,
w3: cell.index(),
}),
Err(_) => None,
}
}
_ => None,
}
}
_ => None,
};
let heap = &mut *cx.heap;
let filled = entry.is_some_and(|entry| heap.ics.record(site, entry));
heap.ics.count_miss(site, filled);
Ok(())
}
fn chain_allows_add(heap: &Heap, object: ObjectId, key: PropertyKey) -> bool {
let mut current = heap.prototype_of(object);
while let Some(prototype) = current {
if let Some((_, property)) = heap.lookup_own(prototype, key) {
return is_plain_data(property);
}
current = heap.prototype_of(prototype);
}
true
}
fn is_plain_data(property: PropertyFlags) -> bool {
property.bits() & (PropertyFlags::WRITABLE | PropertyFlags::ACCESSOR | PropertyFlags::AUTOINIT)
== PropertyFlags::WRITABLE
}
#[inline]
pub(crate) fn dense_index(heap: &Heap, object: ObjectId, index: u32) -> bool {
let header = heap.header(object);
if header.has(flags::SPARSE) || !header.kind.has_elements() {
return false;
}
let len = header.elements().len;
let reach = len
.max(256)
.saturating_mul(4)
.min(len.saturating_add(1 << 16));
index < reach
}
#[inline]
fn dense(heap: &Heap, object: ObjectId, index: u32) -> bool {
dense_index(heap, object, index)
}
#[inline(never)]
pub(crate) fn set(
cx: &mut Cx<'_>,
receiver: Value,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
match receiver.kind() {
Kind::Object => set_on(cx, receiver.as_object().unwrap(), key, value, strict),
Kind::Undefined | Kind::Null => {
let message = format!(
"cannot set property '{}' of {}",
atom_text(cx.heap, key),
if receiver.is_null() {
"null"
} else {
"undefined"
}
);
Err(error::type_error(cx, &message))
}
_ => {
let prototype = match receiver.kind() {
Kind::String => {
let length = cx.heap.string_len(receiver.as_string().unwrap());
let own = match key.kind() {
KeyKind::Index(index) => (index as usize) < length,
KeyKind::String(id) => id == cx.realm.names.length,
KeyKind::Symbol(_) => false,
};
if own {
if strict && matches!(key.kind(), KeyKind::Index(_)) {
return Err(error::type_error(cx, "not an object"));
}
return read_only(cx, key, strict);
}
cx.realm.intrinsic(Intrinsic::StringPrototype)
}
Kind::Number | Kind::Int32 => cx.realm.intrinsic(Intrinsic::NumberPrototype),
Kind::Bool => cx.realm.intrinsic(Intrinsic::BooleanPrototype),
Kind::Symbol => cx.realm.intrinsic(Intrinsic::SymbolPrototype),
Kind::BigInt => cx.realm.intrinsic(Intrinsic::BigIntPrototype),
_ => cx.realm.intrinsic(Intrinsic::ObjectPrototype),
};
match lookup(cx.heap, &cx.realm.names, prototype, key) {
Lookup::Hit(Found::Accessor(_, setter)) => {
return call_setter(cx, setter, receiver, value, strict);
}
Lookup::Exotic(holder) => {
let done = super::object::set(cx, holder, key, value, receiver)?;
if done || !strict {
return Ok(());
}
}
_ => {}
}
if strict {
return Err(primitive_set_error(cx, prototype, key));
}
Ok(())
}
}
}
#[inline(never)]
pub(crate) fn set_on(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
let heap = &mut *cx.heap;
let header = *heap.header(object);
if header.kind.is_exotic() {
return generic_set(cx, object, key, value, strict);
}
match key.kind() {
KeyKind::Index(index) => {
if let Some(cell) = super::arguments::mapped_cell(heap, object, index) {
let writable = heap
.lookup_own(object, key)
.is_none_or(|(_, flags)| flags.has(PropertyFlags::WRITABLE));
if writable {
heap.scope_set(cell, 0, 0, value);
return Ok(());
}
return read_only(cx, key, strict);
}
if !heap.get_element(object, index).is_empty() {
return heap
.set_element(object, index, value)
.map_err(|_| error::oom(cx.realm));
}
if heap.lookup_own(object, key).is_none()
&& dense(heap, object, index)
&& header.has(flags::EXTENSIBLE)
&& heap.holes_writable()
&& !(header.kind == ObjectKind::Array
&& header.has(flags::LENGTH_READONLY)
&& index >= header.payload)
{
return heap
.set_element(object, index, value)
.map_err(|_| error::oom(cx.realm));
}
}
KeyKind::String(id) if id == cx.realm.names.length => {
if header.kind == ObjectKind::Array {
if header.has(flags::LENGTH_READONLY) {
return read_only(cx, key, strict);
}
let descriptor = super::object::Descriptor {
value: Some(value),
..Default::default()
};
if !super::object::array_set_length(cx, object, &descriptor)? {
return read_only(cx, key, strict);
}
return Ok(());
}
}
_ => {}
}
if let Some((slot, property)) = heap.lookup_own(object, key) {
if property.has(PropertyFlags::AUTOINIT) {
return set_pending(cx, object, key, value, strict);
}
if property.has(PropertyFlags::ACCESSOR) {
let setter = heap.get_slot(object, slot + 1);
return call_setter(cx, setter, Value::object(object), value, strict);
}
if property.has(PropertyFlags::WRITABLE) {
heap.set_slot(object, slot, value);
return Ok(());
}
return read_only(cx, key, strict);
}
let mut current = heap.prototype_of(object);
while let Some(prototype) = current {
let kind = heap.object_kind(prototype);
if kind.is_exotic() {
return generic_set(cx, object, key, value, strict);
}
let inherited = match key.kind() {
KeyKind::Index(index) if !heap.get_element(prototype, index).is_empty() => {
Some(PropertyFlags::DEFAULT)
}
KeyKind::Index(index)
if super::arguments::mapped_cell(heap, prototype, index).is_some() =>
{
Some(PropertyFlags::DEFAULT)
}
KeyKind::String(id) if id == cx.realm.names.length && kind == ObjectKind::Array => {
let readonly = heap.header(prototype).has(flags::LENGTH_READONLY);
Some(PropertyFlags::data(!readonly, false, false))
}
_ => match heap.lookup_own(prototype, key) {
Some((slot, property)) if property.has(PropertyFlags::ACCESSOR) => {
let setter = heap.get_slot(prototype, slot + 1);
return call_setter(cx, setter, Value::object(object), value, strict);
}
found => found.map(|(_, property)| property),
},
};
if let Some(property) = inherited {
if !property.has(PropertyFlags::WRITABLE) {
return read_only(cx, key, strict);
}
break;
}
current = heap.prototype_of(prototype);
}
add(cx, object, key, value, strict)
}
#[cold]
#[inline(never)]
fn set_pending(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
if key == Names::key(cx.realm.names.prototype)
&& cx
.heap
.lookup_own(object, key)
.is_some_and(|(_, property)| property.has(PropertyFlags::WRITABLE))
{
return super::interp::materialize_closure(cx, object, Some(value));
}
super::object::materialize(cx, object)?;
set_on(cx, object, key, value, strict)
}
#[inline(never)]
fn generic_set(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
let mark = cx.heap.roots().mark();
super::object::root_keys(cx, &[key]);
let result =
super::object::set(cx, object, key, value, Value::object(object)).and_then(|done| {
if !done && strict {
return Err(set_failed(cx, object, key));
}
Ok(())
});
cx.heap.roots().truncate(mark);
result
}
#[cold]
#[inline(never)]
pub(crate) fn set_failed(cx: &mut Cx<'_>, object: ObjectId, key: PropertyKey) -> Throw {
let mut current = Some(object);
let mut first = None;
while let Some(mut holder) = current {
let kind = cx.heap.object_kind(holder);
if kind == ObjectKind::Proxy {
if cx.heap.vm.proxy_refused {
return error::type_error(cx, "proxy: cannot set property");
}
while cx.heap.object_kind(holder) == ObjectKind::Proxy {
holder = super::proxy::target(cx.heap, holder);
}
current = Some(holder);
continue;
}
first.get_or_insert(holder);
if kind.is_exotic() || cx.heap.lookup_own(holder, key).is_some() {
break;
}
current = cx.heap.prototype_of(holder);
}
if current.is_none()
&& let Some(first) = first
&& !cx.heap.header(first).has(flags::EXTENSIBLE)
{
return error::type_error(cx, "object is not extensible");
}
let message = error::atom_message(cx.heap, "'", key, "' is read-only");
error::throw_units(cx, error::ErrorKind::Type, &message)
}
#[cold]
#[inline(never)]
fn primitive_set_error(cx: &mut Cx<'_>, prototype: ObjectId, key: PropertyKey) -> Throw {
let mut current = Some(prototype);
while let Some(holder) = current {
if cx.heap.object_kind(holder).is_exotic() {
break;
}
if let Some((_, property)) = cx.heap.lookup_own(holder, key) {
if !property.has(PropertyFlags::ACCESSOR) && !property.has(PropertyFlags::WRITABLE) {
let message = error::atom_message(cx.heap, "'", key, "' is read-only");
return error::throw_units(cx, error::ErrorKind::Type, &message);
}
break;
}
current = cx.heap.prototype_of(holder);
}
error::type_error(cx, "not an object")
}
#[inline(never)]
fn call_setter(
cx: &mut Cx<'_>,
setter: Value,
receiver: Value,
value: Value,
strict: bool,
) -> Result<(), Throw> {
if setter.is_undefined() {
if strict {
return Err(error::type_error(cx, "no setter for property"));
}
return Ok(());
}
super::call(cx, setter, receiver, &[value]).map(|_| ())
}
fn read_only(cx: &mut Cx<'_>, key: PropertyKey, strict: bool) -> Result<(), Throw> {
if strict {
let message = error::atom_message(cx.heap, "'", key, "' is read-only");
Err(error::throw_units(cx, error::ErrorKind::Type, &message))
} else {
Ok(())
}
}
fn add(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
let heap = &mut *cx.heap;
if heap.is_pending_closure(object) {
return add_pending(cx, object, key, value, strict);
}
if !heap.header(object).has(flags::EXTENSIBLE) {
if strict {
return Err(error::type_error(cx, "object is not extensible"));
}
return Ok(());
}
if super::object::array_index(heap, key).is_some() {
let done = super::object::create_data_property(cx, object, key, value)?;
if !done && strict {
return Err(error::type_error(cx, "'length' is read-only"));
}
return Ok(());
}
heap.add_property(object, key, PropertyFlags::DEFAULT, value)
.map_err(|_| error::oom(cx.realm))?;
Ok(())
}
#[cold]
#[inline(never)]
fn add_pending(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
strict: bool,
) -> Result<(), Throw> {
super::object::materialize(cx, object)?;
add(cx, object, key, value, strict)
}
#[inline(never)]
pub(crate) fn define(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
) -> Result<(), Throw> {
let heap = &mut *cx.heap;
let header = heap.header(object);
if header.kind.is_exotic() || !header.has(flags::EXTENSIBLE) {
return define_slow(cx, object, key, value);
}
if let Some((slot, property)) = heap.lookup_own(object, key) {
if property == PropertyFlags::DEFAULT {
heap.set_slot(object, slot, value);
return Ok(());
}
return define_slow(cx, object, key, value);
}
if let KeyKind::Index(index) = key.kind()
&& dense(heap, object, index)
{
return heap
.set_element(object, index, value)
.map_err(|_| error::oom(cx.realm));
}
add(cx, object, key, value, true)
}
#[inline(never)]
fn define_slow(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
value: Value,
) -> Result<(), Throw> {
super::object::create_data_property_or_throw(cx, object, key, value)
}
#[inline(never)]
pub(crate) fn define_accessor(
cx: &mut Cx<'_>,
object: ObjectId,
key: PropertyKey,
function: Value,
setter: bool,
flags: PropertyFlags,
) -> Result<(), Throw> {
super::object::materialize(cx, object)?;
let heap = &mut *cx.heap;
if let Some((slot, property)) = heap.lookup_own(object, key)
&& property.has(PropertyFlags::ACCESSOR)
{
heap.set_slot(object, slot + u32::from(setter), function);
return Ok(());
}
let mark = heap.roots().mark();
heap.roots().push(function);
let result = (|| {
if heap.lookup_own(object, key).is_some() {
heap.delete_property(object, key)?;
}
let (getter, setter) = if setter {
(Value::UNDEFINED, function)
} else {
(function, Value::UNDEFINED)
};
heap.add_accessor(object, key, flags, getter, setter)
})();
heap.roots().truncate(mark);
result.map_err(|_| error::oom(cx.realm))
}
#[inline(never)]
pub(crate) fn delete(
cx: &mut Cx<'_>,
receiver: Value,
key: PropertyKey,
strict: bool,
) -> Result<bool, Throw> {
let object = match receiver.kind() {
Kind::Object => receiver.as_object().unwrap(),
Kind::Undefined | Kind::Null => {
return Err(error::type_error(cx, "cannot convert to object"));
}
Kind::String => {
let length = cx.heap.string_len(receiver.as_string().unwrap());
let own = match key.kind() {
KeyKind::String(id) => id == cx.realm.names.length,
KeyKind::Index(index) => (index as usize) < length,
KeyKind::Symbol(_) => false,
};
return non_configurable(cx, key, own, strict);
}
_ => return Ok(true),
};
if cx.heap.object_kind(object).is_exotic() {
let mark = cx.heap.roots().mark();
super::object::root_keys(cx, &[key]);
let result = super::object::delete(cx, object, key)
.and_then(|done| non_configurable(cx, key, !done, strict).map(|_| done));
cx.heap.roots().truncate(mark);
return result;
}
let heap = &mut *cx.heap;
match key.kind() {
KeyKind::Index(index) => {
if super::arguments::mapped_cell(heap, object, index).is_some() {
let done = super::object::ordinary_delete(cx, object, key)?;
return non_configurable(cx, key, !done, strict).map(|_| done);
}
if !heap.get_element(object, index).is_empty() {
heap.set_element(object, index, Value::EMPTY)
.map_err(|_| error::oom(cx.realm))?;
return Ok(true);
}
}
KeyKind::String(id) if id == cx.realm.names.length => {
if heap.object_kind(object) == ObjectKind::Array {
return non_configurable(cx, key, true, strict);
}
}
_ => {}
}
match heap.lookup_own(object, key) {
None => Ok(true),
Some((_, property)) if !property.has(PropertyFlags::CONFIGURABLE) => {
non_configurable(cx, key, true, strict)
}
Some(_) => {
super::object::materialize(cx, object)?;
cx.heap
.delete_property(object, key)
.map_err(|_| error::oom(cx.realm))?;
Ok(true)
}
}
}
fn non_configurable(
cx: &mut Cx<'_>,
_key: PropertyKey,
present: bool,
strict: bool,
) -> Result<bool, Throw> {
if !present {
return Ok(true);
}
if strict {
return Err(error::type_error(cx, "could not delete property"));
}
Ok(false)
}
pub(crate) fn has(cx: &mut Cx<'_>, object: ObjectId, key: PropertyKey) -> Result<bool, Throw> {
match lookup(cx.heap, &cx.realm.names, object, key) {
Lookup::Hit(_) => Ok(true),
Lookup::Miss => Ok(false),
Lookup::Exotic(holder) => super::object::has_property(cx, holder, key),
}
}
pub(crate) fn key_text(heap: &Heap, key: PropertyKey) -> String {
match key.kind() {
KeyKind::Index(index) => index.to_string(),
KeyKind::String(id) => heap.string_text(id),
KeyKind::Symbol(id) => match heap.symbol_description(id) {
Some(description) => format!("Symbol({})", heap.string_text(description)),
None => "Symbol()".into(),
},
}
}
#[cold]
#[inline(never)]
pub(crate) fn atom_text(heap: &Heap, key: PropertyKey) -> String {
match key.kind() {
KeyKind::Index(index) => index.to_string(),
KeyKind::String(id) => string_atom_text(heap, id),
KeyKind::Symbol(id) => match heap.symbol_description(id) {
Some(description) => string_atom_text(heap, description),
None => String::new(),
},
}
}
#[cold]
#[inline(never)]
pub(crate) fn string_atom_text(heap: &Heap, id: crate::value::StringId) -> String {
let units: Vec<u16> = heap.string(id).repr().units().collect();
units_atom_text(&units)
}
#[cold]
#[inline(never)]
pub(crate) fn str_atom_text(text: &str) -> String {
let units: Vec<u16> = text.encode_utf16().collect();
units_atom_text(&units)
}
fn units_atom_text(units: &[u16]) -> String {
if units.iter().all(|&unit| unit < 0x80) {
return String::from_utf16_lossy(units);
}
let mut written = 0;
let mut end = units.len();
for (index, &unit) in units.iter().enumerate() {
if written >= 64 - 6 {
end = index;
break;
}
written += match unit {
0..0x80 => 1,
0x80..0x800 => 2,
_ => 3,
};
}
String::from_utf16_lossy(&units[..end])
}
#[inline(never)]
pub(crate) fn for_in_start(cx: &mut Cx<'_>, value: Value) -> Result<Value, Throw> {
let object = match value.kind() {
Kind::Undefined | Kind::Null => None,
Kind::Object => value.as_object(),
_ => Some(super::object::to_object(cx, value)?),
};
let mark = cx.heap.roots().mark();
let result = (|| {
let (mode, count, keys) = match object {
None => (FOR_IN_KEYS, 0, Vec::new()),
Some(object) => {
cx.heap.roots().push(Value::object(object));
match fast_array_count(cx, object) {
Some(count) => {
let shape = cx.heap.header(object).shape;
let named = cx
.heap
.shape_entries(shape)
.into_iter()
.filter(|(key, _, _)| !matches!(key.kind(), KeyKind::Symbol(_)))
.map(|(key, _, _)| (key, false))
.collect();
(FOR_IN_ARRAY, count, named)
}
None => {
let keys = level_keys(cx, object)?;
(FOR_IN_KEYS, keys.len() as u32, keys)
}
}
}
};
let iterator = cx
.heap
.alloc_object_with_elements(ObjectKind::ForInIterator, None, 4, 2 * keys.len() as u32)
.map_err(|error| error::heap_error(cx, error))?;
let heap = &mut *cx.heap;
let elements = heap.header(iterator).elements();
for (index, &(key, enumerable)) in keys.iter().enumerate() {
heap.values.set(elements, 2 * index as u32, key.to_value());
heap.values
.set(elements, 2 * index as u32 + 1, Value::bool(enumerable));
}
let level = object.map_or(Value::UNDEFINED, Value::object);
heap.set_raw_slot(iterator, 0, level);
heap.set_raw_slot(iterator, 1, Value::int32(count as i32));
heap.set_raw_slot(iterator, 2, Value::UNDEFINED);
heap.set_raw_slot(iterator, 3, Value::int32(mode));
heap.set_payload(iterator, 0);
Ok(Value::object(iterator))
})();
cx.heap.roots().truncate(mark);
result
}
const FOR_IN_KEYS: i32 = 0;
const FOR_IN_ARRAY: i32 = 1;
const FOR_IN_CHAIN: i32 = 2;
fn fast_array_count(cx: &Cx<'_>, object: ObjectId) -> Option<u32> {
let heap = &*cx.heap;
let kind = heap.object_kind(object);
if !matches!(kind, ObjectKind::Array | ObjectKind::Arguments)
|| heap.header(object).has(crate::heap::object::flags::SPARSE)
|| heap.holey.contains(&object.0)
{
return None;
}
for key in heap.own_keys(object) {
if let Some((_, flags)) = heap.lookup_own(object, key)
&& flags.has(PropertyFlags::ENUMERABLE)
&& !matches!(key.kind(), KeyKind::Symbol(_))
{
return None;
}
}
let elements = heap.header(object).elements().len;
let length = if kind == ObjectKind::Array {
heap.array_length(object).min(elements)
} else {
elements
};
let mut count = 0;
while count < length
&& (!heap.get_element(object, count).is_empty()
|| super::arguments::mapped_cell(heap, object, count).is_some())
{
count += 1;
}
Some(count)
}
fn level_keys(cx: &mut Cx<'_>, object: ObjectId) -> Result<Vec<(PropertyKey, bool)>, Throw> {
let mut keys = Vec::new();
if cx.heap.object_kind(object).is_exotic() {
for key in super::object::own_property_keys(cx, object)? {
if matches!(key.kind(), KeyKind::Symbol(_)) {
continue;
}
let descriptor = super::object::get_own_property(cx, object, key)?;
let enumerable =
descriptor.is_some_and(|descriptor| descriptor.enumerable == Some(true));
keys.push((key, enumerable));
}
return Ok(keys);
}
let heap = &*cx.heap;
let is_array = heap.object_kind(object) == ObjectKind::Array;
for key in own_keys_with_arguments(heap, object) {
let enumerable = match key.kind() {
KeyKind::Symbol(_) => continue,
KeyKind::Index(_) => heap
.lookup_own(object, key)
.is_none_or(|(_, property)| property.has(PropertyFlags::ENUMERABLE)),
KeyKind::String(_) => {
if is_array
&& keys.last().is_none_or(|&(key, _): &(PropertyKey, bool)| {
matches!(key.kind(), KeyKind::Index(_))
})
{
keys.push((Names::key(cx.realm.names.length), false));
}
heap.lookup_own(object, key)
.is_some_and(|(_, property)| property.has(PropertyFlags::ENUMERABLE))
}
};
keys.push((key, enumerable));
}
if is_array
&& !keys
.iter()
.any(|&(key, _)| key == Names::key(cx.realm.names.length))
{
keys.push((Names::key(cx.realm.names.length), false));
}
Ok(keys)
}
fn has_own(cx: &mut Cx<'_>, object: ObjectId, key: PropertyKey) -> Result<bool, Throw> {
if cx.heap.object_kind(object).is_exotic() {
return Ok(super::object::get_own_property(cx, object, key)?.is_some());
}
super::object::materialize(cx, object)?;
Ok(super::object::ordinary_get_own(cx, object, key).is_some())
}
fn own_keys_with_arguments(heap: &Heap, object: ObjectId) -> Vec<PropertyKey> {
let mut keys = heap.own_keys(object);
if heap.object_kind(object) == ObjectKind::Arguments && heap.header(object).payload != NONE {
let mut mapped: Vec<PropertyKey> = (0..super::arguments::mapped_count(heap, object))
.filter(|&index| super::arguments::mapped_cell(heap, object, index).is_some())
.filter_map(PropertyKey::index)
.collect();
if !mapped.is_empty() {
mapped.extend(keys);
mapped.sort_by_key(|key| match key.kind() {
KeyKind::Index(index) => (0, index),
_ => (1, 0),
});
keys = mapped;
}
}
keys
}
#[inline(never)]
pub(crate) fn for_in_next(cx: &mut Cx<'_>, iterator: Value) -> Result<Option<Value>, Throw> {
let iterator = iterator.as_object().expect("a for-in enumeration");
loop {
let heap = &mut *cx.heap;
let cursor = heap.header(iterator).payload;
let count = heap.raw_slot(iterator, 1).as_int32().unwrap_or(0) as u32;
let mode = heap.raw_slot(iterator, 3).as_int32().unwrap_or(FOR_IN_KEYS);
let Some(object) = heap.raw_slot(iterator, 0).as_object() else {
return Ok(None);
};
if cursor >= count {
if !for_in_next_level(cx, iterator, object, mode)? {
cx.heap.set_raw_slot(iterator, 0, Value::UNDEFINED);
return Ok(None);
}
continue;
}
heap.set_payload(iterator, cursor + 1);
let key = if mode == FOR_IN_ARRAY {
PropertyKey::index(cursor).expect("an index key")
} else {
let key = heap.get_element(iterator, 2 * cursor);
let enumerable = heap.get_element(iterator, 2 * cursor + 1) == Value::TRUE;
let key = match key.as_int32() {
Some(index) => PropertyKey::index(index as u32).expect("an index key"),
None => PropertyKey::atom(key.as_string().expect("a key atom")),
};
if mode == FOR_IN_CHAIN {
let visited = heap.raw_slot(iterator, 2).as_object().expect("a seen set");
if super::object::ordinary_get_own(cx, visited, key).is_some() {
continue;
}
super::object::create_data_property(cx, visited, key, Value::NULL)?;
}
if !enumerable {
continue;
}
key
};
if !has_own(cx, object, key)? {
continue;
}
return match key.kind() {
KeyKind::Index(index) => {
super::ops::to_string(cx, Value::int32(index as i32)).map(Some)
}
_ => Ok(Some(key.to_value())),
};
}
}
#[cold]
#[inline(never)]
fn for_in_next_level(
cx: &mut Cx<'_>,
iterator: ObjectId,
object: ObjectId,
mode: i32,
) -> Result<bool, Throw> {
if mode != FOR_IN_CHAIN {
let mut current = object;
loop {
let prototype = super::object::get_prototype_of(cx, current)?;
let Some(prototype) = prototype.as_object() else {
return Ok(false);
};
let mark = cx.heap.roots().mark();
cx.heap.roots().push(Value::object(prototype));
let keys = level_keys(cx, prototype);
cx.heap.roots().truncate(mark);
if keys?.iter().any(|&(_, enumerable)| enumerable) {
break;
}
current = prototype;
}
let heap = &*cx.heap;
let count = heap.raw_slot(iterator, 1).as_int32().unwrap_or(0) as u32;
let named = heap.header(iterator).elements().len / 2;
let seen = if mode == FOR_IN_ARRAY {
let mut seen: Vec<(PropertyKey, bool)> = (0..count)
.filter_map(PropertyKey::index)
.map(|key| (key, false))
.collect();
seen.push((Names::key(cx.realm.names.length), false));
seen.extend((0..named).map(|index| {
let key = heap.get_element(iterator, 2 * index);
let key = match key.as_int32() {
Some(index) => PropertyKey::index(index as u32).expect("an index key"),
None => PropertyKey::atom(key.as_string().expect("a key atom")),
};
(key, false)
}));
let mut present = Vec::with_capacity(seen.len());
for (key, enumerable) in seen {
if has_own(cx, object, key)? {
present.push((key, enumerable));
}
}
present
} else {
(0..count)
.map(|index| {
let key = heap.get_element(iterator, 2 * index);
let key = match key.as_int32() {
Some(index) => PropertyKey::index(index as u32).expect("an index key"),
None => PropertyKey::atom(key.as_string().expect("a key atom")),
};
(key, false)
})
.collect()
};
let visited = cx
.heap
.alloc_object(ObjectKind::Ordinary, None, seen.len() as u32)
.map_err(|error| error::heap_error(cx, error))?;
cx.heap.set_raw_slot(iterator, 2, Value::object(visited));
for (key, _) in seen {
super::object::create_data_property(cx, visited, key, Value::NULL)?;
}
cx.heap
.set_raw_slot(iterator, 3, Value::int32(FOR_IN_CHAIN));
}
let prototype = super::object::get_prototype_of(cx, object)?;
let Some(prototype) = prototype.as_object() else {
return Ok(false);
};
cx.heap.set_raw_slot(iterator, 0, Value::object(prototype));
let mark = cx.heap.roots().mark();
let result = (|| {
let keys = level_keys(cx, prototype)?;
for (index, &(key, enumerable)) in keys.iter().enumerate() {
let index = 2 * index as u32;
cx.heap
.set_element(iterator, index, key.to_value())
.and_then(|()| {
cx.heap
.set_element(iterator, index + 1, Value::bool(enumerable))
})
.map_err(|error| error::heap_error(cx, error))?;
}
cx.heap
.set_raw_slot(iterator, 1, Value::int32(keys.len() as i32));
cx.heap.set_payload(iterator, 0);
Ok(true)
})();
cx.heap.roots().truncate(mark);
result
}