mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
11 KiB
330 lines
11 KiB
use crate::{ |
|
builtins::function::OrdinaryFunction, |
|
error::JsNativeError, |
|
object::internal_methods::InternalMethodContext, |
|
vm::{opcode::Operation, CallFrameFlags, CompletionType}, |
|
Context, JsResult, JsValue, |
|
}; |
|
|
|
/// `This` implements the Opcode Operation for `Opcode::This` |
|
/// |
|
/// Operation: |
|
/// - Pushes `this` value. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct This; |
|
|
|
impl Operation for This { |
|
const NAME: &'static str = "This"; |
|
const INSTRUCTION: &'static str = "INST - This"; |
|
const COST: u8 = 1; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
let frame = context.vm.frame_mut(); |
|
let this_index = frame.fp(); |
|
if frame.has_this_value_cached() { |
|
let this = context.vm.stack[this_index as usize].clone(); |
|
context.vm.push(this); |
|
return Ok(CompletionType::Normal); |
|
} |
|
|
|
let this = context.vm.environments.get_this_binding()?; |
|
context.vm.frame_mut().flags |= CallFrameFlags::THIS_VALUE_CACHED; |
|
context.vm.stack[this_index as usize] = this.clone(); |
|
context.vm.push(this); |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
/// `Super` implements the Opcode Operation for `Opcode::Super` |
|
/// |
|
/// Operation: |
|
/// - Pushes the current `super` value to the stack. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct Super; |
|
|
|
impl Operation for Super { |
|
const NAME: &'static str = "Super"; |
|
const INSTRUCTION: &'static str = "INST - Super"; |
|
const COST: u8 = 3; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
let home_object = { |
|
let env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super access must be in a function environment"); |
|
let this = env |
|
.get_this_binding()? |
|
.expect("`get_this_environment` ensures this returns `Some`"); |
|
|
|
env.slots() |
|
.function_object() |
|
.downcast_ref::<OrdinaryFunction>() |
|
.expect("must be function object") |
|
.get_home_object() |
|
.or(this.as_object()) |
|
.cloned() |
|
}; |
|
|
|
let value = home_object |
|
.map(|o| o.__get_prototype_of__(&mut InternalMethodContext::new(context))) |
|
.transpose()? |
|
.flatten() |
|
.map_or_else(JsValue::null, JsValue::from); |
|
|
|
context.vm.push(value); |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
/// `SuperCallPrepare` implements the Opcode Operation for `Opcode::SuperCallPrepare` |
|
/// |
|
/// Operation: |
|
/// - Get the super constructor and the new target of the current environment. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct SuperCallPrepare; |
|
|
|
impl Operation for SuperCallPrepare { |
|
const NAME: &'static str = "SuperCallPrepare"; |
|
const INSTRUCTION: &'static str = "INST - SuperCallPrepare"; |
|
const COST: u8 = 3; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
let this_env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super call must be in function environment"); |
|
let active_function = this_env.slots().function_object().clone(); |
|
let super_constructor = active_function |
|
.__get_prototype_of__(&mut InternalMethodContext::new(context)) |
|
.expect("function object must have prototype"); |
|
|
|
context |
|
.vm |
|
.push(super_constructor.map_or_else(JsValue::null, JsValue::from)); |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
/// `SuperCall` implements the Opcode Operation for `Opcode::SuperCall` |
|
/// |
|
/// Operation: |
|
/// - Execute the `super()` method. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct SuperCall; |
|
|
|
impl SuperCall { |
|
fn operation(context: &mut Context, argument_count: usize) -> JsResult<CompletionType> { |
|
let super_constructor_index = context.vm.stack.len() - argument_count - 1; |
|
let super_constructor = context.vm.stack[super_constructor_index].clone(); |
|
let Some(super_constructor) = super_constructor.as_constructor() else { |
|
return Err(JsNativeError::typ() |
|
.with_message("super constructor object must be constructor") |
|
.into()); |
|
}; |
|
|
|
let this_env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super call must be in function environment"); |
|
|
|
let new_target = this_env |
|
.slots() |
|
.new_target() |
|
.expect("must have new.target") |
|
.clone(); |
|
|
|
context.vm.push(new_target); |
|
|
|
super_constructor |
|
.__construct__(argument_count) |
|
.resolve(context)?; |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
impl Operation for SuperCall { |
|
const NAME: &'static str = "SuperCall"; |
|
const INSTRUCTION: &'static str = "INST - SuperCall"; |
|
const COST: u8 = 3; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
let value_count = context.vm.read::<u8>() as usize; |
|
Self::operation(context, value_count) |
|
} |
|
|
|
fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> { |
|
let value_count = context.vm.read::<u16>() as usize; |
|
Self::operation(context, value_count) |
|
} |
|
|
|
fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> { |
|
let value_count = context.vm.read::<u32>() as usize; |
|
Self::operation(context, value_count) |
|
} |
|
} |
|
|
|
/// `SuperCallSpread` implements the Opcode Operation for `Opcode::SuperCallSpread` |
|
/// |
|
/// Operation: |
|
/// - Execute the `super()` method where the arguments contain spreads. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct SuperCallSpread; |
|
|
|
impl Operation for SuperCallSpread { |
|
const NAME: &'static str = "SuperCallSpread"; |
|
const INSTRUCTION: &'static str = "INST - SuperCallSpread"; |
|
const COST: u8 = 3; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
// Get the arguments that are stored as an array object on the stack. |
|
let arguments_array = context.vm.pop(); |
|
let arguments_array_object = arguments_array |
|
.as_object() |
|
.expect("arguments array in call spread function must be an object"); |
|
let arguments = arguments_array_object |
|
.borrow() |
|
.properties() |
|
.to_dense_indexed_properties() |
|
.expect("arguments array in call spread function must be dense"); |
|
|
|
let super_constructor = context.vm.pop(); |
|
|
|
let Some(super_constructor) = super_constructor.as_constructor() else { |
|
return Err(JsNativeError::typ() |
|
.with_message("super constructor object must be constructor") |
|
.into()); |
|
}; |
|
|
|
context.vm.push(super_constructor.clone()); |
|
|
|
context.vm.push_values(&arguments); |
|
|
|
let this_env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super call must be in function environment"); |
|
|
|
let new_target = this_env |
|
.slots() |
|
.new_target() |
|
.expect("must have new.target") |
|
.clone(); |
|
|
|
context.vm.push(new_target); |
|
|
|
super_constructor |
|
.__construct__(arguments.len()) |
|
.resolve(context)?; |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
/// `SuperCallDerived` implements the Opcode Operation for `Opcode::SuperCallDerived` |
|
/// |
|
/// Operation: |
|
/// - Execute the `super()` method when no constructor of the class is defined. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct SuperCallDerived; |
|
|
|
impl Operation for SuperCallDerived { |
|
const NAME: &'static str = "SuperCallDerived"; |
|
const INSTRUCTION: &'static str = "INST - SuperCallDerived"; |
|
const COST: u8 = 3; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
let argument_count = context.vm.frame().argument_count as usize; |
|
|
|
let this_env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super call must be in function environment"); |
|
let new_target = this_env |
|
.slots() |
|
.new_target() |
|
.expect("must have new target") |
|
.clone(); |
|
let active_function = this_env.slots().function_object().clone(); |
|
let super_constructor = active_function |
|
.__get_prototype_of__(&mut InternalMethodContext::new(context)) |
|
.expect("function object must have prototype") |
|
.expect("function object must have prototype"); |
|
|
|
if !super_constructor.is_constructor() { |
|
return Err(JsNativeError::typ() |
|
.with_message("super constructor object must be constructor") |
|
.into()); |
|
} |
|
|
|
let arguments_start_index = context.vm.stack.len() - argument_count; |
|
context |
|
.vm |
|
.stack |
|
.insert(arguments_start_index, super_constructor.clone().into()); |
|
|
|
context.vm.push(new_target); |
|
|
|
super_constructor |
|
.__construct__(argument_count) |
|
.resolve(context)?; |
|
Ok(CompletionType::Normal) |
|
} |
|
} |
|
|
|
/// `BindThisValue` implements the Opcode Operation for `Opcode::BindThisValue` |
|
/// |
|
/// Operation: |
|
/// - Binds `this` value and initializes the instance elements. |
|
#[derive(Debug, Clone, Copy)] |
|
pub(crate) struct BindThisValue; |
|
|
|
impl Operation for BindThisValue { |
|
const NAME: &'static str = "BindThisValue"; |
|
const INSTRUCTION: &'static str = "INST - BindThisValue"; |
|
const COST: u8 = 6; |
|
|
|
fn execute(context: &mut Context) -> JsResult<CompletionType> { |
|
// Taken from `SuperCall : super Arguments` steps 7-12. |
|
// |
|
// <https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation> |
|
|
|
let result = context |
|
.vm |
|
.pop() |
|
.as_object() |
|
.expect("construct result should be an object") |
|
.clone(); |
|
|
|
// 7. Let thisER be GetThisEnvironment(). |
|
let this_env = context |
|
.vm |
|
.environments |
|
.get_this_environment() |
|
.as_function() |
|
.expect("super call must be in function environment"); |
|
|
|
// 8. Perform ? thisER.BindThisValue(result). |
|
this_env.bind_this_value(result.clone())?; |
|
|
|
// 9. Let F be thisER.[[FunctionObject]]. |
|
// SKIP: 10. Assert: F is an ECMAScript function object. |
|
let active_function = this_env.slots().function_object().clone(); |
|
|
|
// 11. Perform ? InitializeInstanceElements(result, F). |
|
result.initialize_instance_elements(&active_function, context)?; |
|
|
|
// 12. Return result. |
|
context.vm.push(result); |
|
Ok(CompletionType::Normal) |
|
} |
|
}
|
|
|