use boa_macros::js_str;
use crate::value::JsVariant;
use crate::vm::opcode::VaryingOperand;
use crate::{
Context, JsNativeError, JsResult,
builtins::function::set_function_name,
object::{internal_methods::InternalMethodPropertyContext, shape::slot::SlotAttributes},
property::{PropertyDescriptor, PropertyKey},
vm::opcode::Operation,
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertyByName;
impl SetPropertyByName {
#[inline(always)]
pub(crate) fn operation(
(value, receiver, object, index): (
VaryingOperand,
VaryingOperand,
VaryingOperand,
VaryingOperand,
),
context: &mut Context,
) -> JsResult<()> {
let value = context.vm.get_register(value.into()).clone();
let receiver = context.vm.get_register(receiver.into()).clone();
let object = context.vm.get_register(object.into()).clone();
let object = object.to_object(context)?;
let ic = &context.vm.frame().code_block().ic[usize::from(index)];
let object_borrowed = object.borrow();
if let Some((shape, slot)) = ic.match_or_reset(object_borrowed.shape()) {
let slot_index = slot.index as usize;
if slot.attributes.is_accessor_descriptor() {
let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = shape.prototype().expect("prototype should have value");
let prototype = prototype.borrow();
prototype.properties().storage[slot_index + 1].clone()
} else {
object_borrowed.properties().storage[slot_index + 1].clone()
};
drop(object_borrowed);
if slot.attributes.has_set() && result.is_object() {
result.as_object().expect("should contain getter").call(
&receiver,
std::slice::from_ref(&value),
context,
)?;
}
} else if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = shape.prototype().expect("prototype should have value");
let mut prototype = prototype.borrow_mut();
prototype.properties_mut().storage[slot_index] = value.clone();
} else {
drop(object_borrowed);
let mut object_borrowed = object.borrow_mut();
object_borrowed.properties_mut().storage[slot_index] = value.clone();
}
return Ok(());
}
drop(object_borrowed);
let name: PropertyKey = ic.name.clone().into();
let context = &mut InternalMethodPropertyContext::new(context);
let succeeded = object.__set__(name.clone(), value.clone(), receiver.clone(), context)?;
if !succeeded && context.vm.frame().code_block.strict() {
return Err(JsNativeError::typ()
.with_message(format!("cannot set non-writable property: {name}"))
.into());
}
let slot = *context.slot();
if succeeded && slot.is_cachable() {
let ic = &context.vm.frame().code_block.ic[usize::from(index)];
let object_borrowed = object.borrow();
let shape = object_borrowed.shape();
ic.set(shape, slot);
}
Ok(())
}
}
impl Operation for SetPropertyByName {
const NAME: &'static str = "SetPropertyByName";
const INSTRUCTION: &'static str = "INST - SetPropertyByName";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertyByValue;
impl SetPropertyByValue {
#[inline(always)]
pub(crate) fn operation(
(value, key, receiver, object): (
VaryingOperand,
VaryingOperand,
VaryingOperand,
VaryingOperand,
),
context: &mut Context,
) -> JsResult<()> {
let value = context.vm.get_register(value.into()).clone();
let key = context.vm.get_register(key.into()).clone();
let receiver = context.vm.get_register(receiver.into()).clone();
let object = context.vm.get_register(object.into()).clone();
let object = object.to_object(context)?;
let key = key.to_property_key(context)?;
'fast_path: {
if object.is_array()
&& let PropertyKey::Index(index) = &key
{
let mut object_borrowed = object.borrow_mut();
if !object_borrowed.extensible {
break 'fast_path;
}
if object_borrowed
.properties_mut()
.set_dense_property(index.get(), &value)
{
return Ok(());
}
}
}
let succeeded = object.__set__(
key.clone(),
value.clone(),
receiver.clone(),
&mut context.into(),
)?;
if !succeeded && context.vm.frame().code_block.strict() {
return Err(JsNativeError::typ()
.with_message(format!("cannot set non-writable property: {key}"))
.into());
}
Ok(())
}
}
impl Operation for SetPropertyByValue {
const NAME: &'static str = "SetPropertyByValue";
const INSTRUCTION: &'static str = "INST - SetPropertyByValue";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertyGetterByName;
impl SetPropertyGetterByName {
#[inline(always)]
pub(crate) fn operation(
(object, value, index): (VaryingOperand, VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let object = context.vm.get_register(object.into()).clone();
let value = context.vm.get_register(value.into()).clone();
let name = context
.vm
.frame()
.code_block()
.constant_string(index.into())
.into();
let object = object.to_object(context)?;
let set = object
.__get_own_property__(&name, &mut InternalMethodPropertyContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
object.__define_own_property__(
&name,
PropertyDescriptor::builder()
.maybe_get(Some(value.clone()))
.maybe_set(set)
.enumerable(true)
.configurable(true)
.build(),
&mut InternalMethodPropertyContext::new(context),
)?;
Ok(())
}
}
impl Operation for SetPropertyGetterByName {
const NAME: &'static str = "SetPropertyGetterByName";
const INSTRUCTION: &'static str = "INST - SetPropertyGetterByName";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertyGetterByValue;
impl SetPropertyGetterByValue {
#[inline(always)]
pub(crate) fn operation(
(value, key, object): (VaryingOperand, VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let value = context.vm.get_register(value.into()).clone();
let key = context.vm.get_register(key.into()).clone();
let object = context.vm.get_register(object.into()).clone();
let object = object.to_object(context)?;
let name = key.to_property_key(context)?;
let set = object
.__get_own_property__(&name, &mut InternalMethodPropertyContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
object.__define_own_property__(
&name,
PropertyDescriptor::builder()
.maybe_get(Some(value.clone()))
.maybe_set(set)
.enumerable(true)
.configurable(true)
.build(),
&mut InternalMethodPropertyContext::new(context),
)?;
Ok(())
}
}
impl Operation for SetPropertyGetterByValue {
const NAME: &'static str = "SetPropertyGetterByValue";
const INSTRUCTION: &'static str = "INST - SetPropertyGetterByValue";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertySetterByName;
impl SetPropertySetterByName {
#[inline(always)]
pub(crate) fn operation(
(object, value, index): (VaryingOperand, VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let object = context.vm.get_register(object.into()).clone();
let value = context.vm.get_register(value.into()).clone();
let name = context
.vm
.frame()
.code_block()
.constant_string(index.into())
.into();
let object = object.to_object(context)?;
let get = object
.__get_own_property__(&name, &mut InternalMethodPropertyContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
object.__define_own_property__(
&name,
PropertyDescriptor::builder()
.maybe_set(Some(value.clone()))
.maybe_get(get)
.enumerable(true)
.configurable(true)
.build(),
&mut InternalMethodPropertyContext::new(context),
)?;
Ok(())
}
}
impl Operation for SetPropertySetterByName {
const NAME: &'static str = "SetPropertySetterByName";
const INSTRUCTION: &'static str = "INST - SetPropertySetterByName";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetPropertySetterByValue;
impl SetPropertySetterByValue {
#[inline(always)]
pub(crate) fn operation(
(value, key, object): (VaryingOperand, VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let value = context.vm.get_register(value.into()).clone();
let key = context.vm.get_register(key.into()).clone();
let object = context.vm.get_register(object.into()).clone();
let object = object.to_object(context)?;
let name = key.to_property_key(context)?;
let get = object
.__get_own_property__(&name, &mut InternalMethodPropertyContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
object.__define_own_property__(
&name,
PropertyDescriptor::builder()
.maybe_set(Some(value.clone()))
.maybe_get(get)
.enumerable(true)
.configurable(true)
.build(),
&mut InternalMethodPropertyContext::new(context),
)?;
Ok(())
}
}
impl Operation for SetPropertySetterByValue {
const NAME: &'static str = "SetPropertySetterByValue";
const INSTRUCTION: &'static str = "INST - SetPropertySetterByValue";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetFunctionName;
impl SetFunctionName {
#[inline(always)]
pub(crate) fn operation(
(function, name, prefix): (VaryingOperand, VaryingOperand, VaryingOperand),
context: &mut Context,
) {
let function = context.vm.get_register(function.into()).clone();
let name = context.vm.get_register(name.into()).clone();
let name = match name.variant() {
JsVariant::String(name) => PropertyKey::from(name.clone()),
JsVariant::Symbol(name) => PropertyKey::from(name.clone()),
_ => unreachable!(),
};
let prefix = match u32::from(prefix) {
1 => Some(js_str!("get")),
2 => Some(js_str!("set")),
_ => None,
};
set_function_name(
&function.as_object().expect("function is not an object"),
&name,
prefix,
context,
);
}
}
impl Operation for SetFunctionName {
const NAME: &'static str = "SetFunctionName";
const INSTRUCTION: &'static str = "INST - SetFunctionName";
const COST: u8 = 4;
}