1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
use boa_gc::{custom_trace, Finalize, GcRefCell, Trace};
use crate::{JsNativeError, JsObject, JsResult, JsValue};
use super::PoisonableEnvironment;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct FunctionEnvironment {
inner: PoisonableEnvironment,
slots: FunctionSlots,
}
impl FunctionEnvironment {
/// Creates a new `FunctionEnvironment`.
pub(crate) fn new(bindings: u32, poisoned: bool, with: bool, slots: FunctionSlots) -> Self {
Self {
inner: PoisonableEnvironment::new(bindings, poisoned, with),
slots,
}
}
/// Gets the slots of this function environment.
pub(crate) const fn slots(&self) -> &FunctionSlots {
&self.slots
}
/// Gets the `poisonable_environment` of this function environment.
pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment {
&self.inner
}
/// Gets the binding value from the environment by it's index.
///
/// # Panics
///
/// Panics if the binding value is out of range or not initialized.
#[track_caller]
pub(crate) fn get(&self, index: u32) -> Option<JsValue> {
self.inner.get(index)
}
/// Sets the binding value from the environment by index.
///
/// # Panics
///
/// Panics if the binding value is out of range.
#[track_caller]
pub(crate) fn set(&self, index: u32, value: JsValue) {
self.inner.set(index, value);
}
/// `BindThisValue`
///
/// Sets the given value as the `this` binding of the environment.
/// Returns `false` if the `this` binding has already been initialized.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue
pub(crate) fn bind_this_value(&self, value: JsObject) -> JsResult<()> {
let mut this = self.slots.this.borrow_mut();
match &*this {
ThisBindingStatus::Lexical => {
unreachable!("1. Assert: envRec.[[ThisBindingStatus]] is not lexical.")
}
ThisBindingStatus::Initialized(_) => {
// 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
return Err(JsNativeError::reference()
.with_message("cannot reinitialize `this` binding")
.into());
}
ThisBindingStatus::Uninitialized => {
// 3. Set envRec.[[ThisValue]] to V.
// 4. Set envRec.[[ThisBindingStatus]] to initialized.
*this = ThisBindingStatus::Initialized(value.into());
}
}
// 5. Return V.
Ok(())
}
/// `HasSuperBinding`
///
/// Returns `true` if the environment has a `super` binding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
///
/// # Panics
///
/// Panics if the function object of the environment is not a function.
pub(crate) fn has_super_binding(&self) -> bool {
// 1.If envRec.[[ThisBindingStatus]] is lexical, return false.
if matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) {
return false;
}
// 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true.
self.slots
.function_object
.borrow()
.as_function()
.expect("function object must be function")
.get_home_object()
.is_some()
}
/// `HasThisBinding`
///
/// Returns `true` if the environment has a `this` binding.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
pub(crate) fn has_this_binding(&self) -> bool {
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
!matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical)
}
/// `GetThisBinding`
///
/// Returns the `this` binding of the current environment.
///
/// Differs slightly from the spec where lexical this (arrow functions) doesn't get asserted,
/// but instead is returned as `None`.
///
/// More information:
/// - [ECMAScript specification][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
pub(crate) fn get_this_binding(&self) -> JsResult<Option<JsValue>> {
match &*self.slots.this.borrow() {
ThisBindingStatus::Lexical => Ok(None),
// 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
ThisBindingStatus::Uninitialized => Err(JsNativeError::reference()
.with_message(
"Must call super constructor in derived \
class before accessing 'this' or returning from derived constructor",
)
.into()),
// 3. Return envRec.[[ThisValue]].
ThisBindingStatus::Initialized(this) => Ok(Some(this.clone())),
}
}
}
/// Describes the status of a `this` binding in function environments.
#[derive(Clone, Debug, Finalize)]
pub(crate) enum ThisBindingStatus {
/// Function doesn't have a `this` binding. (arrow functions and async arrow functions)
Lexical,
/// Function has a `this` binding, but is uninitialized. (derived constructors)
Uninitialized,
/// Funciton has an initialized `this` binding. (base constructors and most callable objects)
Initialized(JsValue),
}
unsafe impl Trace for ThisBindingStatus {
custom_trace!(this, {
match this {
Self::Initialized(obj) => mark(obj),
Self::Lexical | Self::Uninitialized => {}
}
});
}
/// Holds the internal slots of a function environment.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct FunctionSlots {
/// The `[[ThisValue]]` and `[[ThisBindingStatus]]` internal slots.
this: GcRefCell<ThisBindingStatus>,
/// The `[[FunctionObject]]` internal slot.
function_object: JsObject,
/// The `[[NewTarget]]` internal slot.
new_target: Option<JsObject>,
}
impl FunctionSlots {
/// Creates a new `FunctionSluts`.
pub(crate) fn new(
this: ThisBindingStatus,
function_object: JsObject,
new_target: Option<JsObject>,
) -> Self {
Self {
this: GcRefCell::new(this),
function_object,
new_target,
}
}
/// Returns the value of the `[[FunctionObject]]` internal slot.
pub(crate) const fn function_object(&self) -> &JsObject {
&self.function_object
}
/// Returns the value of the `[[NewTarget]]` internal slot.
pub(crate) const fn new_target(&self) -> Option<&JsObject> {
self.new_target.as_ref()
}
}