mirror of https://github.com/boa-dev/boa.git
Browse Source
This is an attempt to refactor the environments to be more performant at runtime. The idea is, to shift the dynamic hashmap environment lookups from runtime to compile time. Currently the environments hold hashmaps that contain binding identifiers, values and additional information that is needed to identify some errors. Because bindings in outer environments are accessible from inner environments, this can lead to a traversal through all environments (in the worst case to the global environment). This change to the environment structure pushes most of the work that is needed to access bindings to the compile time. At compile time, environments and bindings in the environments are being assigned indices. These indices are then stored instead of the `Sym` that is currently used to access bindings. At runtime, the indices are used to access bindings in a fixed size `Vec` per environment. This brings multiple benefits: - No hashmap access needed at runtime - The number of bindings per environment is known at compile time. Environments only need a single allocation, as their size is constant. - Potential for optimizations with `unsafe` https://doc.rust-lang.org/std/vec/struct.Vec.html#method.get_unchecked Additionally, this changes the global object to have it's bindings directly stored on the `Realm`. This should reduce some overhead from access trough gc objects and makes some optimizations for the global object possible. The benchmarks look not that great on the first sight. But if you look closer, I think it is apparent, that this is a positive change. The difference is most apparent on Mini and Clean as they are longer (still not near any real life js but less specific that most other benchmarks): | Test | Base | PR | % | |------|--------------|------------------|---| | Clean js (Compiler) | **1929.1±5.37ns** | 4.1±0.02µs | **+112.53%** | | Clean js (Execution) | 1487.4±7.50µs | **987.3±3.78µs** | **-33.62%** | The compile time is up in all benchmarks, as expected. The percentage is huge, but if we look at the real numbers, we can see that this is an issue of orders of magnitude. While compile is up `112.53%`, the real change is `~+2µs`. Execution is only down `33.62%`, but the real time changed by `~-500µs`. Co-authored-by: Iban Eguia <razican@protonmail.ch>pull/1850/head
raskad
3 years ago
35 changed files with 2673 additions and 2618 deletions
@ -1,343 +0,0 @@
|
||||
//! # Declarative Records
|
||||
//!
|
||||
//! Each declarative Environment Record is associated with an ECMAScript program scope containing variable,
|
||||
//! `constant`, `let`, `class`, `module`, `import`, and/or function declarations.
|
||||
//! A declarative Environment Record binds the set of identifiers defined by the declarations contained within its scope.
|
||||
//! More info: [ECMA-262 sec-declarative-environment-records](https://tc39.es/ecma262/#sec-declarative-environment-records)
|
||||
|
||||
use crate::{ |
||||
environment::{ |
||||
environment_record_trait::EnvironmentRecordTrait, |
||||
lexical_environment::{Environment, EnvironmentType}, |
||||
}, |
||||
gc::{self, Finalize, Gc, Trace}, |
||||
object::JsObject, |
||||
BoaProfiler, Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use rustc_hash::FxHashMap; |
||||
|
||||
/// Declarative Bindings have a few properties for book keeping purposes, such as mutability (const vs let).
|
||||
/// Can it be deleted? and strict mode.
|
||||
///
|
||||
/// So we need to create a struct to hold these values.
|
||||
/// From this point onwards, a binding is referring to one of these structures.
|
||||
#[derive(Trace, Finalize, Debug, Clone)] |
||||
pub struct DeclarativeEnvironmentRecordBinding { |
||||
pub value: Option<JsValue>, |
||||
pub can_delete: bool, |
||||
pub mutable: bool, |
||||
pub strict: bool, |
||||
} |
||||
|
||||
/// A declarative Environment Record binds the set of identifiers defined by the
|
||||
/// declarations contained within its scope.
|
||||
#[derive(Debug, Trace, Finalize, Clone)] |
||||
pub struct DeclarativeEnvironmentRecord { |
||||
pub env_rec: gc::Cell<FxHashMap<Sym, DeclarativeEnvironmentRecordBinding>>, |
||||
pub outer_env: Option<Environment>, |
||||
} |
||||
|
||||
impl DeclarativeEnvironmentRecord { |
||||
pub fn new(env: Option<Environment>) -> Self { |
||||
let _timer = BoaProfiler::global().start_event("new_declarative_environment", "env"); |
||||
Self { |
||||
env_rec: gc::Cell::new(FxHashMap::default()), |
||||
outer_env: env, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { |
||||
/// `9.1.1.1.1 HasBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
|
||||
fn has_binding(&self, name: Sym, _context: &mut Context) -> JsResult<bool> { |
||||
// 1. If envRec has a binding for the name that is the value of N, return true.
|
||||
// 2. Return false.
|
||||
Ok(self.env_rec.borrow().contains_key(&name)) |
||||
} |
||||
|
||||
/// `9.1.1.1.2 CreateMutableBinding ( N, D )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d
|
||||
fn create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
allow_name_reuse: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Assert: envRec does not already have a binding for N.
|
||||
if !allow_name_reuse { |
||||
assert!( |
||||
!self.env_rec.borrow().contains_key(&name), |
||||
"Identifier {} has already been declared", |
||||
context.interner().resolve_expect(name) |
||||
); |
||||
} |
||||
|
||||
// 2. Create a mutable binding in envRec for N and record that it is uninitialized.
|
||||
// If D is true, record that the newly created binding may be deleted by a subsequent DeleteBinding call.
|
||||
self.env_rec.borrow_mut().insert( |
||||
name, |
||||
DeclarativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: deletion, |
||||
mutable: true, |
||||
strict: false, |
||||
}, |
||||
); |
||||
|
||||
// 3. Return NormalCompletion(empty).
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.1.3 CreateImmutableBinding ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s
|
||||
fn create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Assert: envRec does not already have a binding for N.
|
||||
assert!( |
||||
!self.env_rec.borrow().contains_key(&name), |
||||
"Identifier {} has already been declared", |
||||
context.interner().resolve_expect(name) |
||||
); |
||||
|
||||
// 2. Create an immutable binding in envRec for N and record that it is uninitialized.
|
||||
// If S is true, record that the newly created binding is a strict binding.
|
||||
self.env_rec.borrow_mut().insert( |
||||
name, |
||||
DeclarativeEnvironmentRecordBinding { |
||||
value: None, |
||||
can_delete: true, |
||||
mutable: false, |
||||
strict, |
||||
}, |
||||
); |
||||
|
||||
// 3. Return NormalCompletion(empty).
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.1.4 InitializeBinding ( N, V )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v
|
||||
fn initialize_binding(&self, name: Sym, value: JsValue, context: &mut Context) -> JsResult<()> { |
||||
if let Some(ref mut record) = self.env_rec.borrow_mut().get_mut(&name) { |
||||
if record.value.is_none() { |
||||
// 2. Set the bound value for N in envRec to V.
|
||||
// 3. Record that the binding for N in envRec has been initialized.
|
||||
record.value = Some(value); |
||||
|
||||
// 4. Return NormalCompletion(empty).
|
||||
return Ok(()); |
||||
} |
||||
} |
||||
|
||||
// 1. Assert: envRec must have an uninitialized binding for N.
|
||||
panic!( |
||||
"record must have binding for {}", |
||||
context.interner().resolve_expect(name) |
||||
); |
||||
} |
||||
|
||||
/// `9.1.1.1.5 SetMutableBinding ( N, V, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s
|
||||
#[allow(clippy::else_if_without_else)] |
||||
fn set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
mut strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. If envRec does not have a binding for N, then
|
||||
if self.env_rec.borrow().get(&name).is_none() { |
||||
// a. If S is true, throw a ReferenceError exception.
|
||||
if strict { |
||||
return context.throw_reference_error(format!( |
||||
"{} not found", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
} |
||||
|
||||
// b. Perform envRec.CreateMutableBinding(N, true).
|
||||
self.create_mutable_binding(name, true, false, context)?; |
||||
// c. Perform envRec.InitializeBinding(N, V).
|
||||
self.initialize_binding(name, value, context)?; |
||||
|
||||
// d. Return NormalCompletion(empty).
|
||||
return Ok(()); |
||||
} |
||||
|
||||
let (binding_strict, binding_value_is_none, binding_mutable) = { |
||||
let env_rec = self.env_rec.borrow(); |
||||
let binding = env_rec.get(&name).unwrap(); |
||||
(binding.strict, binding.value.is_none(), binding.mutable) |
||||
}; |
||||
|
||||
// 2. If the binding for N in envRec is a strict binding, set S to true.
|
||||
if binding_strict { |
||||
strict = true; |
||||
} |
||||
|
||||
// 3. If the binding for N in envRec has not yet been initialized, throw a ReferenceError exception.
|
||||
if binding_value_is_none { |
||||
return context.throw_reference_error(format!( |
||||
"{} has not been initialized", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
// 4. Else if the binding for N in envRec is a mutable binding, change its bound value to V.
|
||||
} else if binding_mutable { |
||||
let mut env_rec = self.env_rec.borrow_mut(); |
||||
let binding = env_rec.get_mut(&name).unwrap(); |
||||
binding.value = Some(value); |
||||
// 5. Else,
|
||||
// a. Assert: This is an attempt to change the value of an immutable binding.
|
||||
// b. If S is true, throw a TypeError exception.
|
||||
} else if strict { |
||||
return context.throw_type_error(format!( |
||||
"Cannot mutate an immutable binding {}", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
} |
||||
|
||||
// 6. Return NormalCompletion(empty).
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.1.6 GetBindingValue ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-getbindingvalue-n-s
|
||||
fn get_binding_value( |
||||
&self, |
||||
name: Sym, |
||||
_strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Assert: envRec has a binding for N.
|
||||
// 2. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception.
|
||||
// 3. Return the value currently bound to N in envRec.
|
||||
if let Some(binding) = self.env_rec.borrow().get(&name) { |
||||
if let Some(ref val) = binding.value { |
||||
Ok(val.clone()) |
||||
} else { |
||||
context.throw_reference_error(format!( |
||||
"{} is an uninitialized binding", |
||||
context.interner().resolve_expect(name) |
||||
)) |
||||
} |
||||
} else { |
||||
panic!( |
||||
"Cannot get binding value for {}", |
||||
context.interner().resolve_expect(name) |
||||
); |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.1.7 DeleteBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-deletebinding-n
|
||||
fn delete_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Assert: envRec has a binding for the name that is the value of N.
|
||||
// 2. If the binding for N in envRec cannot be deleted, return false.
|
||||
// 3. Remove the binding for N from envRec.
|
||||
// 4. Return true.
|
||||
match self.env_rec.borrow().get(&name) { |
||||
Some(binding) => { |
||||
if binding.can_delete { |
||||
self.env_rec.borrow_mut().remove(&name); |
||||
Ok(true) |
||||
} else { |
||||
Ok(false) |
||||
} |
||||
} |
||||
None => panic!( |
||||
"env_rec has no binding for {}", |
||||
context.interner().resolve_expect(name) |
||||
), |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.1.8 HasThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hasthisbinding
|
||||
fn has_this_binding(&self) -> bool { |
||||
// 1. Return false.
|
||||
false |
||||
} |
||||
|
||||
fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
Ok(JsValue::undefined()) |
||||
} |
||||
|
||||
/// `9.1.1.1.9 HasSuperBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-hassuperbinding
|
||||
fn has_super_binding(&self) -> bool { |
||||
// 1. Return false.
|
||||
false |
||||
} |
||||
|
||||
/// `9.1.1.1.10 WithBaseObject ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-declarative-environment-records-withbaseobject
|
||||
fn with_base_object(&self) -> Option<JsObject> { |
||||
None |
||||
} |
||||
|
||||
fn get_outer_environment_ref(&self) -> Option<&Environment> { |
||||
self.outer_env.as_ref() |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.outer_env = Some(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
EnvironmentType::Declarative |
||||
} |
||||
} |
||||
|
||||
impl From<DeclarativeEnvironmentRecord> for Environment { |
||||
fn from(env: DeclarativeEnvironmentRecord) -> Self { |
||||
Gc::new(Box::new(env)) |
||||
} |
||||
} |
@ -1,222 +0,0 @@
|
||||
//! # Environment Records
|
||||
//!
|
||||
//! <https://tc39.es/ecma262/#sec-environment-records>
|
||||
//! <https://tc39.es/ecma262/#sec-lexical-environments>
|
||||
//!
|
||||
//! Some environments are stored as `JSObjects`. This is for GC, i.e we want to keep an environment if a variable is closed-over (a closure is returned).
|
||||
//! All of the logic to handle scope/environment records are stored in here.
|
||||
//!
|
||||
//! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait`
|
||||
//!
|
||||
|
||||
use crate::{ |
||||
environment::lexical_environment::VariableScope, |
||||
environment::lexical_environment::{Environment, EnvironmentType}, |
||||
gc::{Finalize, Trace}, |
||||
object::JsObject, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use std::fmt::Debug; |
||||
|
||||
/// <https://tc39.es/ecma262/#sec-environment-records>
|
||||
///
|
||||
/// In the ECMAScript specification Environment Records are hierachical and have a base class with abstract methods.
|
||||
/// In this implementation we have a trait which represents the behaviour of all `EnvironmentRecord` types.
|
||||
pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { |
||||
/// Determine if an Environment Record has a binding for the String value N.
|
||||
/// Return true if it does and false if it does not.
|
||||
fn has_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool>; |
||||
|
||||
/// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name.
|
||||
/// If the Boolean argument deletion is true the binding may be subsequently deleted.
|
||||
///
|
||||
/// * `allow_name_reuse` - specifies whether or not reusing binding names is allowed.
|
||||
///
|
||||
/// Most variable names cannot be reused, but functions in JavaScript are allowed to have multiple
|
||||
/// paraments with the same name.
|
||||
fn create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
allow_name_reuse: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()>; |
||||
|
||||
/// Create a new but uninitialized immutable binding in an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// If strict is true then attempts to set it after it has been initialized will always throw an exception,
|
||||
/// regardless of the strict mode setting of operations that reference that binding.
|
||||
fn create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()>; |
||||
|
||||
/// Set the value of an already existing but uninitialized binding in an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// V is the value for the binding and is a value of any ECMAScript language type.
|
||||
fn initialize_binding(&self, name: Sym, value: JsValue, context: &mut Context) -> JsResult<()>; |
||||
|
||||
/// Set the value of an already existing mutable binding in an Environment Record.
|
||||
///
|
||||
/// The String value `name` is the text of the bound name.
|
||||
/// value is the `value` for the binding and may be a value of any ECMAScript language type.
|
||||
/// `S` is a `Boolean` flag. If `strict` is true and the binding cannot be set throw a
|
||||
/// `TypeError` exception.
|
||||
fn set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()>; |
||||
|
||||
/// Returns the value of an already existing binding from an Environment Record.
|
||||
/// The String value N is the text of the bound name.
|
||||
/// S is used to identify references originating in strict mode code or that
|
||||
/// otherwise require strict mode reference semantics.
|
||||
fn get_binding_value( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue>; |
||||
|
||||
/// Delete a binding from an Environment Record.
|
||||
/// The String value name is the text of the bound name.
|
||||
/// If a binding for name exists, remove the binding and return true.
|
||||
/// If the binding exists but cannot be removed return false. If the binding does not exist return true.
|
||||
fn delete_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool>; |
||||
|
||||
/// Determine if an Environment Record establishes a this binding.
|
||||
/// Return true if it does and false if it does not.
|
||||
fn has_this_binding(&self) -> bool; |
||||
|
||||
/// Return the `this` binding from the environment
|
||||
fn get_this_binding(&self, context: &mut Context) -> JsResult<JsValue>; |
||||
|
||||
/// Determine if an Environment Record establishes a super method binding.
|
||||
/// Return true if it does and false if it does not.
|
||||
fn has_super_binding(&self) -> bool; |
||||
|
||||
/// If this Environment Record is associated with a with statement, return the with object.
|
||||
/// Otherwise, return None.
|
||||
fn with_base_object(&self) -> Option<JsObject>; |
||||
|
||||
/// Get the next environment up
|
||||
fn get_outer_environment_ref(&self) -> Option<&Environment>; |
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
self.get_outer_environment_ref().cloned() |
||||
} |
||||
|
||||
/// Set the next environment up
|
||||
fn set_outer_environment(&mut self, env: Environment); |
||||
|
||||
/// Get the type of environment this is
|
||||
fn get_environment_type(&self) -> EnvironmentType; |
||||
|
||||
/// Return the `this` binding from the environment or try to get it from outer environments
|
||||
fn recursive_get_this_binding(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
if self.has_this_binding() { |
||||
self.get_this_binding(context) |
||||
} else { |
||||
match self.get_outer_environment_ref() { |
||||
Some(outer) => outer.recursive_get_this_binding(context), |
||||
None => Ok(JsValue::undefined()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Create mutable binding while handling outer environments
|
||||
fn recursive_create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
match scope { |
||||
VariableScope::Block => self.create_mutable_binding(name, deletion, false, context), |
||||
VariableScope::Function => self |
||||
.get_outer_environment_ref() |
||||
.expect("No function or global environment") |
||||
.recursive_create_mutable_binding(name, deletion, scope, context), |
||||
} |
||||
} |
||||
|
||||
/// Create immutable binding while handling outer environments
|
||||
fn recursive_create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
match scope { |
||||
VariableScope::Block => self.create_immutable_binding(name, deletion, context), |
||||
VariableScope::Function => self |
||||
.get_outer_environment_ref() |
||||
.expect("No function or global environment") |
||||
.recursive_create_immutable_binding(name, deletion, scope, context), |
||||
} |
||||
} |
||||
|
||||
/// Set mutable binding while handling outer environments
|
||||
fn recursive_set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
if self.has_binding(name, context)? { |
||||
self.set_mutable_binding(name, value, strict, context) |
||||
} else { |
||||
self.get_outer_environment_ref() |
||||
.expect("Environment stack underflow") |
||||
.recursive_set_mutable_binding(name, value, strict, context) |
||||
} |
||||
} |
||||
|
||||
/// Initialize binding while handling outer environments
|
||||
fn recursive_initialize_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
if self.has_binding(name, context)? { |
||||
self.initialize_binding(name, value, context) |
||||
} else { |
||||
self.get_outer_environment_ref() |
||||
.expect("Environment stack underflow") |
||||
.recursive_initialize_binding(name, value, context) |
||||
} |
||||
} |
||||
|
||||
/// Check if a binding exists in current or any outer environment
|
||||
fn recursive_has_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
Ok(self.has_binding(name, context)? |
||||
|| match self.get_outer_environment_ref() { |
||||
Some(outer) => outer.recursive_has_binding(name, context)?, |
||||
None => false, |
||||
}) |
||||
} |
||||
|
||||
/// Retrieve binding from current or any outer environment
|
||||
fn recursive_get_binding_value(&self, name: Sym, context: &mut Context) -> JsResult<JsValue> { |
||||
if self.has_binding(name, context)? { |
||||
self.get_binding_value(name, false, context) |
||||
} else { |
||||
match self.get_outer_environment_ref() { |
||||
Some(outer) => outer.recursive_get_binding_value(name, context), |
||||
None => context.throw_reference_error(format!( |
||||
"{} is not defined", |
||||
context.interner().resolve_expect(name) |
||||
)), |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,282 +0,0 @@
|
||||
//! # Function Environment Records
|
||||
//!
|
||||
//! A function Environment Record is a declarative Environment Record that is used to represent
|
||||
//! the top-level scope of a function and, if the function is not an `ArrowFunction`,
|
||||
//! provides a `this` binding.
|
||||
//! If a function is not an `ArrowFunction` function and references super,
|
||||
//! its function Environment Record also contains the state that is used to perform super method invocations
|
||||
//! from within the function.
|
||||
//! More info: <https://tc39.es/ecma262/#sec-function-environment-records>
|
||||
|
||||
use crate::{ |
||||
environment::{ |
||||
declarative_environment_record::DeclarativeEnvironmentRecord, |
||||
environment_record_trait::EnvironmentRecordTrait, |
||||
lexical_environment::{Environment, EnvironmentType, VariableScope}, |
||||
}, |
||||
gc::{empty_trace, Finalize, Gc, Trace}, |
||||
object::{JsObject, JsPrototype}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
|
||||
/// Different binding status for `this`.
|
||||
/// Usually set on a function environment record
|
||||
#[derive(Copy, Finalize, Debug, Clone)] |
||||
pub enum BindingStatus { |
||||
/// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
|
||||
Lexical, |
||||
/// If initialized the function environment record has already been bound with a `this` value
|
||||
Initialized, |
||||
/// If uninitialized the function environment record has not been bouned with a `this` value
|
||||
Uninitialized, |
||||
} |
||||
|
||||
unsafe impl Trace for BindingStatus { |
||||
empty_trace!(); |
||||
} |
||||
|
||||
/// <https://tc39.es/ecma262/#table-16>
|
||||
#[derive(Debug, Trace, Finalize, Clone)] |
||||
pub struct FunctionEnvironmentRecord { |
||||
pub declarative_record: DeclarativeEnvironmentRecord, |
||||
/// This is the this value used for this invocation of the function.
|
||||
pub this_value: JsValue, |
||||
/// If the value is "lexical", this is an ArrowFunction and does not have a local this value.
|
||||
pub this_binding_status: BindingStatus, |
||||
/// The function object whose invocation caused this Environment Record to be created.
|
||||
pub function: JsObject, |
||||
/// If the associated function has super property accesses and is not an ArrowFunction,
|
||||
/// `[[HomeObject]]` is the object that the function is bound to as a method.
|
||||
/// The default value for `[[HomeObject]]` is undefined.
|
||||
pub home_object: JsValue, |
||||
/// If this Environment Record was created by the `[[Construct]]` internal method,
|
||||
/// `[[NewTarget]]` is the value of the `[[Construct]]` newTarget parameter.
|
||||
/// Otherwise, its value is undefined.
|
||||
pub new_target: JsValue, |
||||
} |
||||
|
||||
impl FunctionEnvironmentRecord { |
||||
pub fn new( |
||||
f: JsObject, |
||||
this: Option<JsValue>, |
||||
outer: Option<Environment>, |
||||
binding_status: BindingStatus, |
||||
new_target: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<Self> { |
||||
let mut func_env = Self { |
||||
declarative_record: DeclarativeEnvironmentRecord::new(outer), // the outer environment will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects
|
||||
function: f, |
||||
this_binding_status: binding_status, |
||||
home_object: JsValue::undefined(), |
||||
new_target, |
||||
this_value: JsValue::undefined(), |
||||
}; |
||||
// If a `this` value has been passed, bind it to the environment
|
||||
if let Some(v) = this { |
||||
func_env.bind_this_value(v, context)?; |
||||
} |
||||
Ok(func_env) |
||||
} |
||||
|
||||
/// `9.1.1.3.1 BindThisValue ( V )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue
|
||||
pub fn bind_this_value(&mut self, value: JsValue, context: &mut Context) -> JsResult<JsValue> { |
||||
match self.this_binding_status { |
||||
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
|
||||
BindingStatus::Lexical => { |
||||
panic!("Cannot bind to an arrow function!"); |
||||
} |
||||
// 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
|
||||
BindingStatus::Initialized => { |
||||
context.throw_reference_error("Cannot bind to an initialized function!") |
||||
} |
||||
BindingStatus::Uninitialized => { |
||||
// 3. Set envRec.[[ThisValue]] to V.
|
||||
self.this_value = value.clone(); |
||||
// 4. Set envRec.[[ThisBindingStatus]] to initialized.
|
||||
self.this_binding_status = BindingStatus::Initialized; |
||||
// 5. Return V.
|
||||
Ok(value) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.3.5 GetSuperBase ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getsuperbase
|
||||
pub fn get_super_base(&self, context: &mut Context) -> JsResult<Option<JsPrototype>> { |
||||
// 1. Let home be envRec.[[FunctionObject]].[[HomeObject]].
|
||||
let home = &self.home_object; |
||||
|
||||
// 2. If home has the value undefined, return undefined.
|
||||
if home.is_undefined() { |
||||
Ok(None) |
||||
} else { |
||||
// 3. Assert: Type(home) is Object.
|
||||
assert!(home.is_object()); |
||||
|
||||
// 4. Return ? home.[[GetPrototypeOf]]().
|
||||
Ok(Some( |
||||
home.as_object() |
||||
.expect("home_object must be an Object") |
||||
.__get_prototype_of__(context)?, |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for FunctionEnvironmentRecord { |
||||
fn has_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
self.declarative_record.has_binding(name, context) |
||||
} |
||||
|
||||
fn create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
allow_name_reuse: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.declarative_record |
||||
.create_mutable_binding(name, deletion, allow_name_reuse, context) |
||||
} |
||||
|
||||
fn create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.declarative_record |
||||
.create_immutable_binding(name, strict, context) |
||||
} |
||||
|
||||
fn initialize_binding(&self, name: Sym, value: JsValue, context: &mut Context) -> JsResult<()> { |
||||
self.declarative_record |
||||
.initialize_binding(name, value, context) |
||||
} |
||||
|
||||
fn set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.declarative_record |
||||
.set_mutable_binding(name, value, strict, context) |
||||
} |
||||
|
||||
fn get_binding_value( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
self.declarative_record |
||||
.get_binding_value(name, strict, context) |
||||
} |
||||
|
||||
fn delete_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
self.declarative_record.delete_binding(name, context) |
||||
} |
||||
|
||||
/// `9.1.1.3.2 HasThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
|
||||
fn has_this_binding(&self) -> bool { |
||||
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
|
||||
!matches!(self.this_binding_status, BindingStatus::Lexical) |
||||
} |
||||
|
||||
/// `9.1.1.3.3 HasSuperBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
|
||||
fn has_super_binding(&self) -> bool { |
||||
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false.
|
||||
// 2. If envRec.[[FunctionObject]].[[HomeObject]] has the value undefined, return false; otherwise, return true.
|
||||
if let BindingStatus::Lexical = self.this_binding_status { |
||||
false |
||||
} else { |
||||
!self.home_object.is_undefined() |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.3.4 GetThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
|
||||
fn get_this_binding(&self, context: &mut Context) -> JsResult<JsValue> { |
||||
match self.this_binding_status { |
||||
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
|
||||
BindingStatus::Lexical => { |
||||
panic!("There is no this for a lexical function record"); |
||||
} |
||||
// 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
|
||||
BindingStatus::Uninitialized => { |
||||
context.throw_reference_error("Uninitialized binding for this function") |
||||
} |
||||
// 3. Return envRec.[[ThisValue]].
|
||||
BindingStatus::Initialized => Ok(self.this_value.clone()), |
||||
} |
||||
} |
||||
|
||||
fn with_base_object(&self) -> Option<JsObject> { |
||||
None |
||||
} |
||||
|
||||
fn get_outer_environment_ref(&self) -> Option<&Environment> { |
||||
self.declarative_record.get_outer_environment_ref() |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.declarative_record.set_outer_environment(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
EnvironmentType::Function |
||||
} |
||||
|
||||
fn recursive_create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
_scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.create_mutable_binding(name, deletion, false, context) |
||||
} |
||||
|
||||
fn recursive_create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
_scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.create_immutable_binding(name, deletion, context) |
||||
} |
||||
} |
||||
|
||||
impl From<FunctionEnvironmentRecord> for Environment { |
||||
fn from(env: FunctionEnvironmentRecord) -> Self { |
||||
Gc::new(Box::new(env)) |
||||
} |
||||
} |
@ -1,571 +0,0 @@
|
||||
//! # Global Environment Records
|
||||
//!
|
||||
//! A global Environment Record is used to represent the outer most scope that is shared by all
|
||||
//! of the ECMAScript Script elements that are processed in a common realm.
|
||||
//! A global Environment Record provides the bindings for built-in globals (clause 18),
|
||||
//! properties of the global object, and for all top-level declarations (13.2.8, 13.2.10)
|
||||
//! that occur within a Script.
|
||||
//! More info: <https://tc39.es/ecma262/#sec-global-environment-records>
|
||||
|
||||
use crate::{ |
||||
environment::{ |
||||
declarative_environment_record::DeclarativeEnvironmentRecord, |
||||
environment_record_trait::EnvironmentRecordTrait, |
||||
lexical_environment::{Environment, EnvironmentType, VariableScope}, |
||||
object_environment_record::ObjectEnvironmentRecord, |
||||
}, |
||||
gc::{self, Finalize, Gc, Trace}, |
||||
object::JsObject, |
||||
property::PropertyDescriptor, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use rustc_hash::FxHashSet; |
||||
|
||||
#[derive(Debug, Trace, Finalize, Clone)] |
||||
pub struct GlobalEnvironmentRecord { |
||||
pub object_record: ObjectEnvironmentRecord, |
||||
pub global_this_binding: JsObject, |
||||
pub declarative_record: DeclarativeEnvironmentRecord, |
||||
pub var_names: gc::Cell<FxHashSet<Sym>>, |
||||
} |
||||
|
||||
impl GlobalEnvironmentRecord { |
||||
pub fn new(global: JsObject, this_value: JsObject) -> Self { |
||||
let obj_rec = ObjectEnvironmentRecord { |
||||
bindings: global, |
||||
outer_env: None, |
||||
/// Object Environment Records created for with statements (13.11)
|
||||
/// can provide their binding object as an implicit this value for use in function calls.
|
||||
/// The capability is controlled by a withEnvironment Boolean value that is associated
|
||||
/// with each object Environment Record. By default, the value of withEnvironment is false
|
||||
/// for any object Environment Record.
|
||||
with_environment: false, |
||||
}; |
||||
|
||||
let dcl_rec = DeclarativeEnvironmentRecord::new(None); |
||||
|
||||
Self { |
||||
object_record: obj_rec, |
||||
global_this_binding: this_value, |
||||
declarative_record: dcl_rec, |
||||
var_names: gc::Cell::new(FxHashSet::default()), |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.4.12 HasVarDeclaration ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasvardeclaration
|
||||
pub fn has_var_declaration(&self, name: Sym) -> bool { |
||||
// 1. Let varDeclaredNames be envRec.[[VarNames]].
|
||||
// 2. If varDeclaredNames contains N, return true.
|
||||
// 3. Return false.
|
||||
self.var_names.borrow().contains(&name) |
||||
} |
||||
|
||||
/// `9.1.1.4.13 HasLexicalDeclaration ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-haslexicaldeclaration
|
||||
pub fn has_lexical_declaration(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. Return DclRec.HasBinding(N).
|
||||
self.declarative_record.has_binding(name, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.14 HasRestrictedGlobalProperty ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
|
||||
pub fn has_restricted_global_property( |
||||
&self, |
||||
name: Sym, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
// 1. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 2. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
|
||||
let existing_prop = global_object |
||||
.__get_own_property__(&context.interner().resolve_expect(name).into(), context)?; |
||||
|
||||
if let Some(existing_prop) = existing_prop { |
||||
// 5. If existingProp.[[Configurable]] is true, return false.
|
||||
// 6. Return true.
|
||||
Ok(!existing_prop.expect_configurable()) |
||||
} else { |
||||
// 4. If existingProp is undefined, return false.
|
||||
Ok(false) |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.4.15 CanDeclareGlobalVar ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
|
||||
pub fn can_declare_global_var(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 2. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
|
||||
let key = context.interner().resolve_expect(name).to_owned(); |
||||
let has_property = global_object.has_own_property(key, context)?; |
||||
|
||||
// 4. If hasProperty is true, return true.
|
||||
if has_property { |
||||
return Ok(true); |
||||
} |
||||
|
||||
// 5. Return ? IsExtensible(globalObject).
|
||||
global_object.is_extensible(context) |
||||
} |
||||
|
||||
/// `9.1.1.4.16 CanDeclareGlobalFunction ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
|
||||
pub fn can_declare_global_function(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 2. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
|
||||
let existing_prop = global_object |
||||
.__get_own_property__(&context.interner().resolve_expect(name).into(), context)?; |
||||
|
||||
if let Some(existing_prop) = existing_prop { |
||||
// 5. If existingProp.[[Configurable]] is true, return true.
|
||||
// 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true.
|
||||
if existing_prop.expect_configurable() |
||||
|| matches!( |
||||
( |
||||
existing_prop.is_data_descriptor(), |
||||
existing_prop.writable(), |
||||
existing_prop.enumerable(), |
||||
), |
||||
(true, Some(true), Some(true)) |
||||
) |
||||
{ |
||||
Ok(true) |
||||
} else { |
||||
// 7. Return false.
|
||||
Ok(false) |
||||
} |
||||
} else { |
||||
// 4. If existingProp is undefined, return ? IsExtensible(globalObject).
|
||||
global_object.is_extensible(context) |
||||
} |
||||
} |
||||
|
||||
/// `9.1.1.4.17 CreateGlobalVarBinding ( N, D )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
|
||||
pub fn create_global_var_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 2. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
|
||||
let has_property = global_object |
||||
.has_own_property(context.interner().resolve_expect(name).to_owned(), context)?; |
||||
// 4. Let extensible be ? IsExtensible(globalObject).
|
||||
let extensible = global_object.is_extensible(context)?; |
||||
|
||||
// 5. If hasProperty is false and extensible is true, then
|
||||
if !has_property && extensible { |
||||
// a. Perform ? ObjRec.CreateMutableBinding(N, D).
|
||||
self.object_record |
||||
.create_mutable_binding(name, deletion, false, context)?; |
||||
// b. Perform ? ObjRec.InitializeBinding(N, undefined).
|
||||
self.object_record |
||||
.initialize_binding(name, JsValue::undefined(), context)?; |
||||
} |
||||
|
||||
// 6. Let varDeclaredNames be envRec.[[VarNames]].
|
||||
let mut var_declared_names = self.var_names.borrow_mut(); |
||||
// 7. If varDeclaredNames does not contain N, then
|
||||
if !var_declared_names.contains(&name) { |
||||
// a. Append N to varDeclaredNames.
|
||||
var_declared_names.insert(name); |
||||
} |
||||
|
||||
// 8. Return NormalCompletion(empty).
|
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
|
||||
pub fn create_global_function_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
deletion: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 2. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
|
||||
let existing_prop = global_object |
||||
.__get_own_property__(&context.interner().resolve_expect(name).into(), context)?; |
||||
|
||||
// 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
|
||||
let desc = if existing_prop.map_or(true, |f| f.expect_configurable()) { |
||||
// a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }.
|
||||
PropertyDescriptor::builder() |
||||
.value(value.clone()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(deletion) |
||||
.build() |
||||
// 5. Else,
|
||||
} else { |
||||
// a. Let desc be the PropertyDescriptor { [[Value]]: V }.
|
||||
PropertyDescriptor::builder().value(value.clone()).build() |
||||
}; |
||||
|
||||
let name_str = context.interner().resolve_expect(name).to_owned(); |
||||
|
||||
// 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc).
|
||||
global_object.define_property_or_throw(name_str.as_str(), desc, context)?; |
||||
// 7. Perform ? Set(globalObject, N, V, false).
|
||||
global_object.set(name_str, value, false, context)?; |
||||
|
||||
// 8. Let varDeclaredNames be envRec.[[VarNames]].
|
||||
// 9. If varDeclaredNames does not contain N, then
|
||||
if !self.var_names.borrow().contains(&name) { |
||||
// a. Append N to varDeclaredNames.
|
||||
self.var_names.borrow_mut().insert(name); |
||||
} |
||||
|
||||
// 10. Return NormalCompletion(empty).
|
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for GlobalEnvironmentRecord { |
||||
/// `9.1.1.4.1 HasBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n
|
||||
fn has_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, return true.
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
return Ok(true); |
||||
} |
||||
|
||||
// 3. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 4. Return ? ObjRec.HasBinding(N).
|
||||
self.object_record.has_binding(name, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.2 CreateMutableBinding ( N, D )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d
|
||||
fn create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
allow_name_reuse: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, throw a TypeError exception.
|
||||
if !allow_name_reuse && self.declarative_record.has_binding(name, context)? { |
||||
return context.throw_type_error(format!( |
||||
"Binding already exists for {}", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
} |
||||
|
||||
// 3. Return DclRec.CreateMutableBinding(N, D).
|
||||
self.declarative_record |
||||
.create_mutable_binding(name, deletion, allow_name_reuse, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.3 CreateImmutableBinding ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s
|
||||
fn create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, throw a TypeError exception.
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
return context.throw_type_error(format!( |
||||
"Binding already exists for {}", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
} |
||||
|
||||
// 3. Return DclRec.CreateImmutableBinding(N, S).
|
||||
self.declarative_record |
||||
.create_immutable_binding(name, strict, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.4 InitializeBinding ( N, V )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-initializebinding-n-v
|
||||
fn initialize_binding(&self, name: Sym, value: JsValue, context: &mut Context) -> JsResult<()> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, then
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
// a. Return DclRec.InitializeBinding(N, V).
|
||||
return self |
||||
.declarative_record |
||||
.initialize_binding(name, value, context); |
||||
} |
||||
|
||||
// 3. Assert: If the binding exists, it must be in the object Environment Record.
|
||||
assert!( |
||||
self.object_record.has_binding(name, context)?, |
||||
"Binding must be in object_record" |
||||
); |
||||
|
||||
// 4. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 5. Return ? ObjRec.InitializeBinding(N, V).
|
||||
self.object_record.initialize_binding(name, value, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.5 SetMutableBinding ( N, V, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-setmutablebinding-n-v-s
|
||||
fn set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, then
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
// a. Return DclRec.SetMutableBinding(N, V, S).
|
||||
return self |
||||
.declarative_record |
||||
.set_mutable_binding(name, value, strict, context); |
||||
} |
||||
|
||||
// 3. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 4. Return ? ObjRec.SetMutableBinding(N, V, S).
|
||||
self.object_record |
||||
.set_mutable_binding(name, value, strict, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.6 GetBindingValue ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getbindingvalue-n-s
|
||||
fn get_binding_value( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, then
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
// a. Return DclRec.GetBindingValue(N, S).
|
||||
return self |
||||
.declarative_record |
||||
.get_binding_value(name, strict, context); |
||||
} |
||||
|
||||
// 3. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 4. Return ? ObjRec.GetBindingValue(N, S).
|
||||
self.object_record.get_binding_value(name, strict, context) |
||||
} |
||||
|
||||
/// `9.1.1.4.7 DeleteBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-deletebinding-n
|
||||
fn delete_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let DclRec be envRec.[[DeclarativeRecord]].
|
||||
// 2. If DclRec.HasBinding(N) is true, then
|
||||
if self.declarative_record.has_binding(name, context)? { |
||||
// a. Return DclRec.DeleteBinding(N).
|
||||
return self.declarative_record.delete_binding(name, context); |
||||
} |
||||
|
||||
// 3. Let ObjRec be envRec.[[ObjectRecord]].
|
||||
// 4. Let globalObject be ObjRec.[[BindingObject]].
|
||||
let global_object = &self.object_record.bindings; |
||||
|
||||
// 5. Let existingProp be ? HasOwnProperty(globalObject, N).
|
||||
// 6. If existingProp is true, then
|
||||
if global_object |
||||
.has_own_property(context.interner().resolve_expect(name).to_owned(), context)? |
||||
{ |
||||
// a. Let status be ? ObjRec.DeleteBinding(N).
|
||||
let status = self.object_record.delete_binding(name, context)?; |
||||
|
||||
// b. If status is true, then
|
||||
if status { |
||||
// i. Let varNames be envRec.[[VarNames]].
|
||||
// ii. If N is an element of varNames, remove that element from the varNames.
|
||||
self.var_names.borrow_mut().remove(&name); |
||||
} |
||||
|
||||
// c. Return status.
|
||||
return Ok(status); |
||||
} |
||||
|
||||
// 7. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
/// `9.1.1.4.8 HasThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasthisbinding
|
||||
fn has_this_binding(&self) -> bool { |
||||
// 1. Return true.
|
||||
true |
||||
} |
||||
|
||||
/// `9.1.1.4.9 HasSuperBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hassuperbinding
|
||||
fn has_super_binding(&self) -> bool { |
||||
// 1. Return false.
|
||||
false |
||||
} |
||||
|
||||
/// `9.1.1.4.10 WithBaseObject ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-withbaseobject
|
||||
fn with_base_object(&self) -> Option<JsObject> { |
||||
// 1. Return undefined.
|
||||
None |
||||
} |
||||
|
||||
/// `9.1.1.4.11 GetThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getthisbinding
|
||||
fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
// 1. Return envRec.[[GlobalThisValue]].
|
||||
Ok(self.global_this_binding.clone().into()) |
||||
} |
||||
|
||||
fn get_outer_environment(&self) -> Option<Environment> { |
||||
None |
||||
} |
||||
|
||||
fn get_outer_environment_ref(&self) -> Option<&Environment> { |
||||
None |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, _env: Environment) { |
||||
// TODO: Implement
|
||||
todo!("Not implemented yet") |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
EnvironmentType::Global |
||||
} |
||||
|
||||
fn recursive_create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
_scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.create_mutable_binding(name, deletion, false, context) |
||||
} |
||||
|
||||
fn recursive_create_immutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
_scope: VariableScope, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.create_immutable_binding(name, deletion, context) |
||||
} |
||||
|
||||
fn recursive_set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.set_mutable_binding(name, value, strict, context) |
||||
} |
||||
|
||||
fn recursive_initialize_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
self.initialize_binding(name, value, context) |
||||
} |
||||
} |
||||
|
||||
impl From<GlobalEnvironmentRecord> for Environment { |
||||
fn from(env: GlobalEnvironmentRecord) -> Self { |
||||
Gc::new(Box::new(env)) |
||||
} |
||||
} |
@ -1,259 +0,0 @@
|
||||
//! # Lexical Environment
|
||||
//!
|
||||
//! <https://tc39.es/ecma262/#sec-lexical-environment-operations>
|
||||
//!
|
||||
//! The following operations are used to operate upon lexical environments
|
||||
//! This is the entrypoint to lexical environments.
|
||||
|
||||
use super::global_environment_record::GlobalEnvironmentRecord; |
||||
use crate::{ |
||||
environment::environment_record_trait::EnvironmentRecordTrait, gc::Gc, object::JsObject, |
||||
BoaProfiler, Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use std::{collections::VecDeque, error, fmt}; |
||||
|
||||
/// Environments are wrapped in a Box and then in a GC wrapper
|
||||
pub type Environment = Gc<Box<dyn EnvironmentRecordTrait>>; |
||||
|
||||
/// Give each environment an easy way to declare its own type
|
||||
/// This helps with comparisons
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
||||
pub enum EnvironmentType { |
||||
Declarative, |
||||
Function, |
||||
Global, |
||||
Object, |
||||
} |
||||
|
||||
/// The scope of a given variable
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
||||
pub enum VariableScope { |
||||
/// The variable declaration is scoped to the current block (`let` and `const`)
|
||||
Block, |
||||
/// The variable declaration is scoped to the current function (`var`)
|
||||
Function, |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
pub struct LexicalEnvironment { |
||||
environment_stack: VecDeque<Environment>, |
||||
} |
||||
|
||||
/// An error that occurred during lexing or compiling of the source input.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
||||
pub struct EnvironmentError { |
||||
details: String, |
||||
} |
||||
|
||||
impl EnvironmentError { |
||||
pub fn new(msg: &str) -> Self { |
||||
Self { |
||||
details: msg.to_string(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for EnvironmentError { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{}", self.details) |
||||
} |
||||
} |
||||
|
||||
impl error::Error for EnvironmentError {} |
||||
|
||||
impl LexicalEnvironment { |
||||
pub fn new(global: JsObject) -> Self { |
||||
let _timer = BoaProfiler::global().start_event("LexicalEnvironment::new", "env"); |
||||
let global_env = GlobalEnvironmentRecord::new(global.clone(), global); |
||||
let mut lexical_env = Self { |
||||
environment_stack: VecDeque::new(), |
||||
}; |
||||
|
||||
// lexical_env.push(global_env);
|
||||
lexical_env.environment_stack.push_back(global_env.into()); |
||||
lexical_env |
||||
} |
||||
} |
||||
|
||||
impl Context { |
||||
pub(crate) fn push_environment<T: Into<Environment>>(&mut self, env: T) { |
||||
self.realm |
||||
.environment |
||||
.environment_stack |
||||
.push_back(env.into()); |
||||
} |
||||
|
||||
pub(crate) fn pop_environment(&mut self) -> Option<Environment> { |
||||
self.realm.environment.environment_stack.pop_back() |
||||
} |
||||
|
||||
pub(crate) fn get_this_binding(&mut self) -> JsResult<JsValue> { |
||||
self.get_current_environment() |
||||
.recursive_get_this_binding(self) |
||||
} |
||||
|
||||
pub(crate) fn get_global_this_binding(&mut self) -> JsResult<JsValue> { |
||||
let global = self.realm.global_env.clone(); |
||||
global.get_this_binding(self) |
||||
} |
||||
|
||||
pub(crate) fn create_mutable_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
scope: VariableScope, |
||||
) -> JsResult<()> { |
||||
self.get_current_environment() |
||||
.recursive_create_mutable_binding(name, deletion, scope, self) |
||||
} |
||||
|
||||
pub(crate) fn create_immutable_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
scope: VariableScope, |
||||
) -> JsResult<()> { |
||||
self.get_current_environment() |
||||
.recursive_create_immutable_binding(name, deletion, scope, self) |
||||
} |
||||
|
||||
pub(crate) fn set_mutable_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
) -> JsResult<()> { |
||||
self.get_current_environment() |
||||
.recursive_set_mutable_binding(name, value, strict, self) |
||||
} |
||||
|
||||
pub(crate) fn initialize_binding(&mut self, name: Sym, value: JsValue) -> JsResult<()> { |
||||
let _timer = |
||||
BoaProfiler::global().start_event("LexicalEnvironment::initialize_binding", "env"); |
||||
self.get_current_environment() |
||||
.recursive_initialize_binding(name, value, self) |
||||
} |
||||
|
||||
/// When neededing to clone an environment (linking it with another environnment)
|
||||
/// cloning is more suited. The GC will remove the env once nothing is linking to it anymore
|
||||
pub(crate) fn get_current_environment(&mut self) -> Environment { |
||||
let _timer = |
||||
BoaProfiler::global().start_event("LexicalEnvironment::get_current_environment", "env"); |
||||
self.realm |
||||
.environment |
||||
.environment_stack |
||||
.back_mut() |
||||
.expect("Could not get mutable reference to back object") |
||||
.clone() |
||||
} |
||||
|
||||
pub(crate) fn has_binding(&mut self, name: Sym) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("LexicalEnvironment::has_binding", "env"); |
||||
self.get_current_environment() |
||||
.recursive_has_binding(name, self) |
||||
} |
||||
|
||||
pub(crate) fn get_binding_value(&mut self, name: Sym) -> JsResult<JsValue> { |
||||
let _timer = |
||||
BoaProfiler::global().start_event("LexicalEnvironment::get_binding_value", "env"); |
||||
self.get_current_environment() |
||||
.recursive_get_binding_value(name, self) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn let_is_blockscoped() { |
||||
let scenario = r#" |
||||
{ |
||||
let bar = "bar"; |
||||
} |
||||
|
||||
try{ |
||||
bar; |
||||
} catch (err) { |
||||
err.message |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn const_is_blockscoped() { |
||||
let scenario = r#" |
||||
{ |
||||
const bar = "bar"; |
||||
} |
||||
|
||||
try{ |
||||
bar; |
||||
} catch (err) { |
||||
err.message |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn var_not_blockscoped() { |
||||
let scenario = r#" |
||||
{ |
||||
var bar = "bar"; |
||||
} |
||||
bar == "bar"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn functions_use_declaration_scope() { |
||||
let scenario = r#" |
||||
function foo() { |
||||
try { |
||||
bar; |
||||
} catch (err) { |
||||
return err.message; |
||||
} |
||||
} |
||||
{ |
||||
let bar = "bar"; |
||||
foo(); |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn set_outer_var_in_blockscope() { |
||||
let scenario = r#" |
||||
var bar; |
||||
{ |
||||
bar = "foo"; |
||||
} |
||||
bar == "foo"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn set_outer_let_in_blockscope() { |
||||
let scenario = r#" |
||||
let bar; |
||||
{ |
||||
bar = "foo"; |
||||
} |
||||
bar == "foo"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
} |
@ -1,8 +0,0 @@
|
||||
//! Environment handling, lexical, object, function and declaritive records
|
||||
|
||||
pub mod declarative_environment_record; |
||||
pub mod environment_record_trait; |
||||
pub mod function_environment_record; |
||||
pub mod global_environment_record; |
||||
pub mod lexical_environment; |
||||
pub mod object_environment_record; |
@ -1,281 +0,0 @@
|
||||
//! # Object Records
|
||||
//!
|
||||
//! Each object Environment Record is associated with an object called its binding object.
|
||||
//! An object Environment Record binds the set of string identifier names that directly
|
||||
//! correspond to the property names of its binding object.
|
||||
//! Property keys that are not strings in the form of an `IdentifierName` are not included in the set of bound identifiers.
|
||||
//! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records)
|
||||
|
||||
use crate::{ |
||||
environment::{ |
||||
environment_record_trait::EnvironmentRecordTrait, |
||||
lexical_environment::{Environment, EnvironmentType}, |
||||
}, |
||||
gc::{Finalize, Gc, Trace}, |
||||
object::JsObject, |
||||
property::PropertyDescriptor, |
||||
symbol::WellKnownSymbols, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
|
||||
#[derive(Debug, Trace, Finalize, Clone)] |
||||
pub struct ObjectEnvironmentRecord { |
||||
pub bindings: JsObject, |
||||
pub with_environment: bool, |
||||
pub outer_env: Option<Environment>, |
||||
} |
||||
|
||||
impl ObjectEnvironmentRecord { |
||||
pub fn new(object: JsObject, environment: Option<Environment>) -> Self { |
||||
Self { |
||||
bindings: object, |
||||
outer_env: environment, |
||||
/// Object Environment Records created for with statements (13.11)
|
||||
/// can provide their binding object as an implicit this value for use in function calls.
|
||||
/// The capability is controlled by a withEnvironment Boolean value that is associated
|
||||
/// with each object Environment Record. By default, the value of withEnvironment is false
|
||||
/// for any object Environment Record.
|
||||
with_environment: false, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl EnvironmentRecordTrait for ObjectEnvironmentRecord { |
||||
/// `9.1.1.2.1 HasBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
|
||||
fn has_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let bindingObject be envRec.[[BindingObject]].
|
||||
// 2. Let foundBinding be ? HasProperty(bindingObject, N).
|
||||
// 3. If foundBinding is false, return false.
|
||||
if !self |
||||
.bindings |
||||
.has_property(context.interner().resolve_expect(name).to_owned(), context)? |
||||
{ |
||||
return Ok(false); |
||||
} |
||||
|
||||
// 4. If envRec.[[IsWithEnvironment]] is false, return true.
|
||||
if !self.with_environment { |
||||
return Ok(true); |
||||
} |
||||
|
||||
// 5. Let unscopables be ? Get(bindingObject, @@unscopables).
|
||||
// 6. If Type(unscopables) is Object, then
|
||||
if let Some(unscopables) = self |
||||
.bindings |
||||
.get(WellKnownSymbols::unscopables(), context)? |
||||
.as_object() |
||||
{ |
||||
// a. Let blocked be ! ToBoolean(? Get(unscopables, N)).
|
||||
// b. If blocked is true, return false.
|
||||
if unscopables |
||||
.get(context.interner().resolve_expect(name).to_owned(), context)? |
||||
.to_boolean() |
||||
{ |
||||
return Ok(false); |
||||
} |
||||
} |
||||
|
||||
// 7. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
/// `9.1.1.2.2 CreateMutableBinding ( N, D )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d
|
||||
fn create_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
deletion: bool, |
||||
_allow_name_reuse: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let bindingObject be envRec.[[BindingObject]].
|
||||
// 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }).
|
||||
self.bindings.define_property_or_throw( |
||||
context.interner().resolve_expect(name).to_owned(), |
||||
PropertyDescriptor::builder() |
||||
.value(JsValue::undefined()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(deletion), |
||||
context, |
||||
)?; |
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.2.3 CreateImmutableBinding ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s
|
||||
fn create_immutable_binding( |
||||
&self, |
||||
_name: Sym, |
||||
_strict: bool, |
||||
_context: &mut Context, |
||||
) -> JsResult<()> { |
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.2.4 InitializeBinding ( N, V )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
|
||||
fn initialize_binding(&self, name: Sym, value: JsValue, context: &mut Context) -> JsResult<()> { |
||||
// 1. Return ? envRec.SetMutableBinding(N, V, false).
|
||||
self.set_mutable_binding(name, value, false, context) |
||||
} |
||||
|
||||
/// `9.1.1.2.5 SetMutableBinding ( N, V, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s
|
||||
fn set_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
value: JsValue, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<()> { |
||||
// 1. Let bindingObject be envRec.[[BindingObject]].
|
||||
// 2. Let stillExists be ? HasProperty(bindingObject, N).
|
||||
let still_exists = self |
||||
.bindings |
||||
.has_property(context.interner().resolve_expect(name).to_owned(), context)?; |
||||
|
||||
// 3. If stillExists is false and S is true, throw a ReferenceError exception.
|
||||
if !still_exists && strict { |
||||
return context.throw_reference_error("Binding already exists"); |
||||
} |
||||
|
||||
// 4. Return ? Set(bindingObject, N, V, S).
|
||||
self.bindings.set( |
||||
context.interner().resolve_expect(name).to_owned(), |
||||
value, |
||||
strict, |
||||
context, |
||||
)?; |
||||
Ok(()) |
||||
} |
||||
|
||||
/// `9.1.1.2.6 GetBindingValue ( N, S )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s
|
||||
fn get_binding_value( |
||||
&self, |
||||
name: Sym, |
||||
strict: bool, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let bindingObject be envRec.[[BindingObject]].
|
||||
// 2. Let value be ? HasProperty(bindingObject, N).
|
||||
// 3. If value is false, then
|
||||
if !self |
||||
.bindings |
||||
.__has_property__(&context.interner().resolve_expect(name).into(), context)? |
||||
{ |
||||
// a. If S is false, return the value undefined; otherwise throw a ReferenceError exception.
|
||||
if !strict { |
||||
return Ok(JsValue::undefined()); |
||||
} |
||||
return context.throw_reference_error(format!( |
||||
"{} has no binding", |
||||
context.interner().resolve_expect(name) |
||||
)); |
||||
} |
||||
|
||||
// 4. Return ? Get(bindingObject, N).
|
||||
self.bindings |
||||
.get(context.interner().resolve_expect(name).to_owned(), context) |
||||
} |
||||
|
||||
/// `9.1.1.2.7 DeleteBinding ( N )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n
|
||||
fn delete_binding(&self, name: Sym, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Let bindingObject be envRec.[[BindingObject]].
|
||||
// 2. Return ? bindingObject.[[Delete]](N).
|
||||
self.bindings |
||||
.__delete__(&context.interner().resolve_expect(name).into(), context) |
||||
} |
||||
|
||||
/// `9.1.1.2.8 HasThisBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hasthisbinding
|
||||
fn has_this_binding(&self) -> bool { |
||||
// 1. Return false.
|
||||
false |
||||
} |
||||
|
||||
/// `9.1.1.2.9 HasSuperBinding ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding
|
||||
fn has_super_binding(&self) -> bool { |
||||
// 1. Return false.
|
||||
false |
||||
} |
||||
|
||||
/// `9.1.1.2.10 WithBaseObject ( )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-object-environment-records-hassuperbinding
|
||||
fn with_base_object(&self) -> Option<JsObject> { |
||||
// 1. If envRec.[[IsWithEnvironment]] is true, return envRec.[[BindingObject]].
|
||||
// 2. Otherwise, return undefined.
|
||||
if self.with_environment { |
||||
Some(self.bindings.clone()) |
||||
} else { |
||||
None |
||||
} |
||||
} |
||||
|
||||
fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> { |
||||
Ok(JsValue::undefined()) |
||||
} |
||||
|
||||
fn get_outer_environment_ref(&self) -> Option<&Environment> { |
||||
self.outer_env.as_ref() |
||||
} |
||||
|
||||
fn set_outer_environment(&mut self, env: Environment) { |
||||
self.outer_env = Some(env); |
||||
} |
||||
|
||||
fn get_environment_type(&self) -> EnvironmentType { |
||||
EnvironmentType::Function |
||||
} |
||||
} |
||||
|
||||
impl From<ObjectEnvironmentRecord> for Environment { |
||||
fn from(env: ObjectEnvironmentRecord) -> Self { |
||||
Gc::new(Box::new(env)) |
||||
} |
||||
} |
@ -0,0 +1,315 @@
|
||||
use crate::{ |
||||
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsResult, |
||||
JsString, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use rustc_hash::FxHashMap; |
||||
|
||||
/// A compile time binding represents a binding at bytecode compile time in a [`CompileTimeEnvironment`].
|
||||
///
|
||||
/// It contains the binding index and a flag to indicate if this is a mutable binding or not.
|
||||
#[derive(Debug)] |
||||
struct CompileTimeBinding { |
||||
index: usize, |
||||
mutable: bool, |
||||
} |
||||
|
||||
/// A compile time environment maps bound identifiers to their binding positions.
|
||||
///
|
||||
/// A compile time environment also indicates, if it is a function environment.
|
||||
#[derive(Debug)] |
||||
pub(crate) struct CompileTimeEnvironment { |
||||
bindings: FxHashMap<Sym, CompileTimeBinding>, |
||||
function_scope: bool, |
||||
} |
||||
|
||||
impl CompileTimeEnvironment { |
||||
/// Returns the number of bindings in this environment.
|
||||
#[inline] |
||||
pub(crate) fn num_bindings(&self) -> usize { |
||||
self.bindings.len() |
||||
} |
||||
} |
||||
|
||||
/// The compile time environment stack contains a stack of all environments at bytecode compile time.
|
||||
///
|
||||
/// The first environment on the stack represents the global environment.
|
||||
/// This is never being deleted and is tied to the existence of the realm.
|
||||
/// All other environments are being dropped once they are not needed anymore.
|
||||
#[derive(Debug)] |
||||
pub(crate) struct CompileTimeEnvironmentStack { |
||||
stack: Vec<CompileTimeEnvironment>, |
||||
} |
||||
|
||||
impl CompileTimeEnvironmentStack { |
||||
/// Creates a new compile time environment stack.
|
||||
///
|
||||
/// This function should only be used once, on realm creation.
|
||||
#[inline] |
||||
pub(crate) fn new() -> Self { |
||||
Self { |
||||
stack: vec![CompileTimeEnvironment { |
||||
bindings: FxHashMap::default(), |
||||
function_scope: true, |
||||
}], |
||||
} |
||||
} |
||||
|
||||
/// Get the number of bindings for the current last environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there are no environments on the stack.
|
||||
#[inline] |
||||
pub(crate) fn get_binding_number(&self) -> usize { |
||||
self.stack |
||||
.last() |
||||
.expect("global environment must always exist") |
||||
.num_bindings() |
||||
} |
||||
} |
||||
|
||||
impl Context { |
||||
/// Push either a new declarative or function environment on the compile time environment stack.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline] |
||||
pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) { |
||||
self.realm.compile_env.stack.push(CompileTimeEnvironment { |
||||
bindings: FxHashMap::default(), |
||||
function_scope, |
||||
}); |
||||
} |
||||
|
||||
/// Pop the last compile time environment from the stack.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there are no more environments that can be pop'ed.
|
||||
#[inline] |
||||
pub(crate) fn pop_compile_time_environment(&mut self) -> CompileTimeEnvironment { |
||||
assert!( |
||||
self.realm.compile_env.stack.len() > 1, |
||||
"cannot pop global environment" |
||||
); |
||||
self.realm |
||||
.compile_env |
||||
.stack |
||||
.pop() |
||||
.expect("len > 1 already checked") |
||||
} |
||||
|
||||
/// Get the number of bindings for the current compile time environment.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there are no environments on the compile time environment stack.
|
||||
#[inline] |
||||
pub(crate) fn get_binding_number(&self) -> usize { |
||||
self.realm |
||||
.compile_env |
||||
.stack |
||||
.last() |
||||
.expect("global environment must always exist") |
||||
.num_bindings() |
||||
} |
||||
|
||||
/// Get the binding locator of the binding at bytecode compile time.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline] |
||||
pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator { |
||||
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { |
||||
if let Some(binding) = env.bindings.get(&name) { |
||||
return BindingLocator::declarative(name, i, binding.index); |
||||
} |
||||
} |
||||
BindingLocator::global(name) |
||||
} |
||||
|
||||
/// Return if a declarative binding exists at bytecode compile time.
|
||||
/// This does not include bindings on the global object.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline] |
||||
pub(crate) fn has_binding(&self, name: Sym) -> bool { |
||||
for env in self.realm.compile_env.stack.iter().rev() { |
||||
if env.bindings.contains_key(&name) { |
||||
return true; |
||||
} |
||||
} |
||||
false |
||||
} |
||||
|
||||
/// Create a mutable binding at bytecode compile time.
|
||||
/// This function returns a syntax error, if the binding is a redeclaration.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment is not function scoped.
|
||||
#[inline] |
||||
pub(crate) fn create_mutable_binding( |
||||
&mut self, |
||||
name: Sym, |
||||
function_scope: bool, |
||||
allow_name_reuse: bool, |
||||
) -> JsResult<()> { |
||||
let name_str = JsString::from(self.interner().resolve_expect(name)); |
||||
|
||||
for (i, env) in self.realm.compile_env.stack.iter_mut().enumerate().rev() { |
||||
if !function_scope || env.function_scope { |
||||
if env.bindings.contains_key(&name) { |
||||
if allow_name_reuse { |
||||
return Ok(()); |
||||
} |
||||
return self |
||||
.throw_syntax_error(format!("Redeclaration of variable {}", name_str)); |
||||
} |
||||
|
||||
if i == 0 { |
||||
let desc = self |
||||
.realm |
||||
.global_property_map |
||||
.string_property_map() |
||||
.get(&name_str); |
||||
let non_configurable_binding_exists = match desc { |
||||
Some(desc) => !matches!(desc.configurable(), Some(true)), |
||||
None => false, |
||||
}; |
||||
if function_scope && desc.is_none() { |
||||
self.global_bindings_mut().insert( |
||||
name_str, |
||||
PropertyDescriptor::builder() |
||||
.value(JsValue::Undefined) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build(), |
||||
); |
||||
return Ok(()); |
||||
} else if function_scope { |
||||
return Ok(()); |
||||
} else if !function_scope |
||||
&& !allow_name_reuse |
||||
&& non_configurable_binding_exists |
||||
{ |
||||
return self |
||||
.throw_syntax_error(format!("Redeclaration of variable {}", name_str)); |
||||
} |
||||
} |
||||
|
||||
let binding_index = env.bindings.len(); |
||||
env.bindings.insert( |
||||
name, |
||||
CompileTimeBinding { |
||||
index: binding_index, |
||||
mutable: true, |
||||
}, |
||||
); |
||||
return Ok(()); |
||||
} |
||||
continue; |
||||
} |
||||
panic!("global environment must be function scoped") |
||||
} |
||||
|
||||
/// Initialize a mutable binding at bytecode compile time and return it's binding locator.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline] |
||||
pub(crate) fn initialize_mutable_binding( |
||||
&self, |
||||
name: Sym, |
||||
function_scope: bool, |
||||
) -> BindingLocator { |
||||
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { |
||||
if function_scope && !env.function_scope { |
||||
continue; |
||||
} |
||||
if let Some(binding) = env.bindings.get(&name) { |
||||
return BindingLocator::declarative(name, i, binding.index); |
||||
} |
||||
return BindingLocator::global(name); |
||||
} |
||||
BindingLocator::global(name) |
||||
} |
||||
|
||||
/// Create an immutable binding at bytecode compile time.
|
||||
/// This function returns a syntax error, if the binding is a redeclaration.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment does not exist.
|
||||
#[inline] |
||||
pub(crate) fn create_immutable_binding(&mut self, name: Sym) -> JsResult<()> { |
||||
let name_str = JsString::from(self.interner().resolve_expect(name)); |
||||
let exists_global = self.realm.compile_env.stack.len() == 1 |
||||
&& self.global_bindings().contains_key(&name_str); |
||||
|
||||
let env = self |
||||
.realm |
||||
.compile_env |
||||
.stack |
||||
.last_mut() |
||||
.expect("global environment must always exist"); |
||||
|
||||
if env.bindings.contains_key(&name) || exists_global { |
||||
self.throw_syntax_error(format!("Redeclaration of variable {}", name_str)) |
||||
} else { |
||||
let binding_index = env.bindings.len(); |
||||
env.bindings.insert( |
||||
name, |
||||
CompileTimeBinding { |
||||
index: binding_index, |
||||
mutable: false, |
||||
}, |
||||
); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Initialize an immutable binding at bytecode compile time and return it's binding locator.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment does not exist or a the binding was not created on the current environment.
|
||||
#[inline] |
||||
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator { |
||||
let environment_index = self.realm.compile_env.stack.len() - 1; |
||||
let env = self |
||||
.realm |
||||
.compile_env |
||||
.stack |
||||
.last() |
||||
.expect("global environment must always exist"); |
||||
|
||||
let binding = env.bindings.get(&name).expect("binding must exist"); |
||||
BindingLocator::declarative(name, environment_index, binding.index) |
||||
} |
||||
|
||||
/// Return the binding locator for a set operation on an existing binding.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline] |
||||
pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator { |
||||
for (i, env) in self.realm.compile_env.stack.iter().enumerate().rev() { |
||||
if let Some(binding) = env.bindings.get(&name) { |
||||
if binding.mutable { |
||||
return BindingLocator::declarative(name, i, binding.index); |
||||
} |
||||
return BindingLocator::mutate_immutable(name); |
||||
} |
||||
} |
||||
BindingLocator::global(name) |
||||
} |
||||
} |
@ -0,0 +1,36 @@
|
||||
//! This module implements ECMAScript `Environment Records`.
|
||||
//!
|
||||
//! Environments contain the bindings of identifiers to their values.
|
||||
//! The implementation differs from the methods defined by the specification,
|
||||
//! but the resulting behavior should be the same.
|
||||
//!
|
||||
//! To make the runtime more performant, environment specific behavior is split
|
||||
//! between bytecode compilation and the runtime.
|
||||
//! While the association of identifiers to values seems like a natural fit for a hashmap,
|
||||
//! lookups of the values at runtime are very expensive.
|
||||
//! Environments can also have outer environments.
|
||||
//! In the worst case, there are as many hashmap lookups, as there are environments.
|
||||
//!
|
||||
//! To avoid these costs, hashmaps are not used at runtime.
|
||||
//! At runtime, environments are represented as fixed size lists of binding values.
|
||||
//! The positions of the bindings in these lists is determined at bytecode compile time.
|
||||
//!
|
||||
//! A binding is uniquely identified by two indices:
|
||||
//! - An environment index, that identifies the environment in which the binding exists
|
||||
//! - A binding index, that identifies the binding in the environment
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-environment-records
|
||||
|
||||
mod compile; |
||||
mod runtime; |
||||
|
||||
pub(crate) use { |
||||
compile::CompileTimeEnvironmentStack, |
||||
runtime::{BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack}, |
||||
}; |
||||
|
||||
#[cfg(test)] |
||||
mod tests; |
@ -0,0 +1,332 @@
|
||||
use crate::{ |
||||
gc::{Finalize, Gc, Trace}, |
||||
Context, JsResult, JsValue, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use gc::GcCell; |
||||
|
||||
/// A declarative environment holds the bindings values at runtime.
|
||||
///
|
||||
/// Bindings are stored in a fixed size list of optional values.
|
||||
/// If a binding is not initialized, the value is `None`.
|
||||
///
|
||||
/// Optionally, an environment can hold a `this` value.
|
||||
/// The `this` value is present only if the environment is a function environment.
|
||||
#[derive(Debug, Trace, Finalize)] |
||||
pub(crate) struct DeclarativeEnvironment { |
||||
bindings: GcCell<Vec<Option<JsValue>>>, |
||||
this: Option<JsValue>, |
||||
} |
||||
|
||||
impl DeclarativeEnvironment { |
||||
/// Get the binding value from the environment by it's index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the binding value is out of range or not initialized.
|
||||
#[inline] |
||||
pub(crate) fn get(&self, index: usize) -> JsValue { |
||||
self.bindings |
||||
.borrow() |
||||
.get(index) |
||||
.expect("binding index must be in range") |
||||
.clone() |
||||
.expect("binding must be initialized") |
||||
} |
||||
|
||||
/// Set the binding value at the specified index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the binding value is out of range or not initialized.
|
||||
#[inline] |
||||
pub(crate) fn set(&self, index: usize, value: JsValue) { |
||||
let mut bindings = self.bindings.borrow_mut(); |
||||
let binding = bindings |
||||
.get_mut(index) |
||||
.expect("binding index must be in range"); |
||||
assert!(!binding.is_none(), "binding must be initialized"); |
||||
*binding = Some(value); |
||||
} |
||||
} |
||||
|
||||
/// A declarative environment stack holds all declarative environments at runtime.
|
||||
///
|
||||
/// Environments themselves are garbage collected,
|
||||
/// because they must be preserved for function calls.
|
||||
#[derive(Clone, Debug, Trace, Finalize)] |
||||
pub struct DeclarativeEnvironmentStack { |
||||
stack: Vec<Gc<DeclarativeEnvironment>>, |
||||
} |
||||
|
||||
impl DeclarativeEnvironmentStack { |
||||
/// Create a new environment stack with the most outer declarative environment.
|
||||
#[inline] |
||||
pub(crate) fn new() -> Self { |
||||
Self { |
||||
stack: vec![Gc::new(DeclarativeEnvironment { |
||||
bindings: GcCell::new(Vec::new()), |
||||
this: None, |
||||
})], |
||||
} |
||||
} |
||||
|
||||
/// Set the number of bindings on the global environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[inline] |
||||
pub(crate) fn set_global_binding_number(&mut self, binding_number: usize) { |
||||
let environment = self |
||||
.stack |
||||
.get(0) |
||||
.expect("global environment must always exist"); |
||||
let mut bindings = environment.bindings.borrow_mut(); |
||||
if bindings.len() < binding_number { |
||||
bindings.resize(binding_number, None); |
||||
} |
||||
} |
||||
|
||||
/// Get the `this` value of the most outer function environment.
|
||||
#[inline] |
||||
pub(crate) fn get_last_this(&self) -> Option<JsValue> { |
||||
for env in self.stack.iter().rev() { |
||||
if let Some(this) = &env.this { |
||||
return Some(this.clone()); |
||||
} |
||||
} |
||||
None |
||||
} |
||||
|
||||
/// Push a declarative environment on the environments stack.
|
||||
#[inline] |
||||
pub(crate) fn push_declarative(&mut self, num_bindings: usize) { |
||||
self.stack.push(Gc::new(DeclarativeEnvironment { |
||||
bindings: GcCell::new(vec![None; num_bindings]), |
||||
this: None, |
||||
})); |
||||
} |
||||
|
||||
/// Push a function environment on the environments stack.
|
||||
#[inline] |
||||
pub(crate) fn push_function(&mut self, num_bindings: usize, this: JsValue) { |
||||
self.stack.push(Gc::new(DeclarativeEnvironment { |
||||
bindings: GcCell::new(vec![None; num_bindings]), |
||||
this: Some(this), |
||||
})); |
||||
} |
||||
|
||||
/// Pop environment from the environments stack.
|
||||
#[inline] |
||||
pub(crate) fn pop(&mut self) { |
||||
debug_assert!(self.stack.len() > 1); |
||||
self.stack.pop(); |
||||
} |
||||
|
||||
/// Get the most outer environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[inline] |
||||
pub(crate) fn current(&mut self) -> Gc<DeclarativeEnvironment> { |
||||
self.stack |
||||
.last() |
||||
.expect("global environment must always exist") |
||||
.clone() |
||||
} |
||||
|
||||
/// Get the value of a binding.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[inline] |
||||
pub(crate) fn get_value_optional( |
||||
&self, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
) -> Option<JsValue> { |
||||
self.stack |
||||
.get(environment_index) |
||||
.expect("environment index must be in range") |
||||
.bindings |
||||
.borrow() |
||||
.get(binding_index) |
||||
.expect("binding index must be in range") |
||||
.clone() |
||||
} |
||||
|
||||
/// Set the value of a binding.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[inline] |
||||
pub(crate) fn put_value( |
||||
&mut self, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
value: JsValue, |
||||
) { |
||||
let mut bindings = self |
||||
.stack |
||||
.get(environment_index) |
||||
.expect("environment index must be in range") |
||||
.bindings |
||||
.borrow_mut(); |
||||
let binding = bindings |
||||
.get_mut(binding_index) |
||||
.expect("binding index must be in range"); |
||||
*binding = Some(value); |
||||
} |
||||
|
||||
/// Set the value of a binding if it is initialized.
|
||||
/// Return `true` if the value has been set.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[inline] |
||||
pub(crate) fn put_value_if_initialized( |
||||
&mut self, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
value: JsValue, |
||||
) -> bool { |
||||
let mut bindings = self |
||||
.stack |
||||
.get(environment_index) |
||||
.expect("environment index must be in range") |
||||
.bindings |
||||
.borrow_mut(); |
||||
let binding = bindings |
||||
.get_mut(binding_index) |
||||
.expect("binding index must be in range"); |
||||
if binding.is_none() { |
||||
false |
||||
} else { |
||||
*binding = Some(value); |
||||
true |
||||
} |
||||
} |
||||
|
||||
/// Set the value of a binding if it is uninitialized.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[inline] |
||||
pub(crate) fn put_value_if_uninitialized( |
||||
&mut self, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
value: JsValue, |
||||
) { |
||||
let mut bindings = self |
||||
.stack |
||||
.get(environment_index) |
||||
.expect("environment index must be in range") |
||||
.bindings |
||||
.borrow_mut(); |
||||
let binding = bindings |
||||
.get_mut(binding_index) |
||||
.expect("binding index must be in range"); |
||||
if binding.is_none() { |
||||
*binding = Some(value); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// A binding locator contains all information about a binding that is needed to resolve it at runtime.
|
||||
///
|
||||
/// Binding locators get created at bytecode compile time and are accessible at runtime via the [`crate::vm::CodeBlock`].
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||
pub(crate) struct BindingLocator { |
||||
name: Sym, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
global: bool, |
||||
mutate_immutable: bool, |
||||
} |
||||
|
||||
impl BindingLocator { |
||||
/// Creates a new declarative binding locator that has knows indices.
|
||||
#[inline] |
||||
pub(in crate::environments) fn declarative( |
||||
name: Sym, |
||||
environment_index: usize, |
||||
binding_index: usize, |
||||
) -> Self { |
||||
Self { |
||||
name, |
||||
environment_index, |
||||
binding_index, |
||||
global: false, |
||||
mutate_immutable: false, |
||||
} |
||||
} |
||||
|
||||
/// Creates a binding locator that indicates that the binding is on the global object.
|
||||
#[inline] |
||||
pub(in crate::environments) fn global(name: Sym) -> Self { |
||||
Self { |
||||
name, |
||||
environment_index: 0, |
||||
binding_index: 0, |
||||
global: true, |
||||
mutate_immutable: false, |
||||
} |
||||
} |
||||
|
||||
/// Creates a binding locator that indicates that it was attempted to mutate an immutable binding.
|
||||
/// At runtime this should always produce a type error.
|
||||
#[inline] |
||||
pub(in crate::environments) fn mutate_immutable(name: Sym) -> Self { |
||||
Self { |
||||
name, |
||||
environment_index: 0, |
||||
binding_index: 0, |
||||
global: false, |
||||
mutate_immutable: true, |
||||
} |
||||
} |
||||
|
||||
/// Returns the name of the binding.
|
||||
#[inline] |
||||
pub(crate) fn name(&self) -> Sym { |
||||
self.name |
||||
} |
||||
|
||||
/// Returns if the binding is located on the global object.
|
||||
#[inline] |
||||
pub(crate) fn is_global(&self) -> bool { |
||||
self.global |
||||
} |
||||
|
||||
/// Returns the environment index of the binding.
|
||||
#[inline] |
||||
pub(crate) fn environment_index(&self) -> usize { |
||||
self.environment_index |
||||
} |
||||
|
||||
/// Returns the binding index of the binding.
|
||||
#[inline] |
||||
pub(crate) fn binding_index(&self) -> usize { |
||||
self.binding_index |
||||
} |
||||
|
||||
/// Helper method to throws an error if the binding access is illegal.
|
||||
#[inline] |
||||
pub(crate) fn throw_mutate_immutable(&self, context: &mut Context) -> JsResult<()> { |
||||
if self.mutate_immutable { |
||||
context.throw_type_error(format!( |
||||
"cannot mutate an immutable binding '{}'", |
||||
context.interner().resolve_expect(self.name) |
||||
)) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,92 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn let_is_block_scoped() { |
||||
let scenario = r#" |
||||
{ |
||||
let bar = "bar"; |
||||
} |
||||
|
||||
try{ |
||||
bar; |
||||
} catch (err) { |
||||
err.message |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn const_is_block_scoped() { |
||||
let scenario = r#" |
||||
{ |
||||
const bar = "bar"; |
||||
} |
||||
|
||||
try{ |
||||
bar; |
||||
} catch (err) { |
||||
err.message |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn var_not_block_scoped() { |
||||
let scenario = r#" |
||||
{ |
||||
var bar = "bar"; |
||||
} |
||||
bar == "bar"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn functions_use_declaration_scope() { |
||||
let scenario = r#" |
||||
function foo() { |
||||
try { |
||||
bar; |
||||
} catch (err) { |
||||
return err.message; |
||||
} |
||||
} |
||||
{ |
||||
let bar = "bar"; |
||||
foo(); |
||||
} |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\""); |
||||
} |
||||
|
||||
#[test] |
||||
fn set_outer_var_in_block_scope() { |
||||
let scenario = r#" |
||||
var bar; |
||||
{ |
||||
bar = "foo"; |
||||
} |
||||
bar == "foo"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn set_outer_let_in_block_scope() { |
||||
let scenario = r#" |
||||
let bar; |
||||
{ |
||||
bar = "foo"; |
||||
} |
||||
bar == "foo"; |
||||
"#; |
||||
|
||||
assert_eq!(&exec(scenario), "true"); |
||||
} |
@ -0,0 +1,460 @@
|
||||
use crate::{ |
||||
object::{InternalObjectMethods, JsObject, ORDINARY_INTERNAL_METHODS}, |
||||
property::{DescriptorKind, PropertyDescriptor, PropertyKey}, |
||||
value::JsValue, |
||||
BoaProfiler, Context, JsResult, |
||||
}; |
||||
|
||||
/// Definitions of the internal object methods for global object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-global-object
|
||||
pub(crate) static GLOBAL_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { |
||||
__get_own_property__: global_get_own_property, |
||||
__is_extensible__: global_is_extensible, |
||||
__prevent_extensions__: global_prevent_extensions, |
||||
__define_own_property__: global_define_own_property, |
||||
__has_property__: global_has_property, |
||||
__get__: global_get, |
||||
__set__: global_set, |
||||
__delete__: global_delete, |
||||
..ORDINARY_INTERNAL_METHODS |
||||
}; |
||||
|
||||
/// Abstract operation `OrdinaryGetOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
|
||||
#[inline] |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn global_get_own_property( |
||||
_obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<Option<PropertyDescriptor>> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_get_own_property", "object"); |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. If O does not have an own property with key P, return undefined.
|
||||
// 3. Let D be a newly created Property Descriptor with no fields.
|
||||
// 4. Let X be O's own property whose key is P.
|
||||
// 5. If X is a data property, then
|
||||
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
|
||||
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
|
||||
// 6. Else,
|
||||
// a. Assert: X is an accessor property.
|
||||
// b. Set D.[[Get]] to the value of X's [[Get]] attribute.
|
||||
// c. Set D.[[Set]] to the value of X's [[Set]] attribute.
|
||||
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
|
||||
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
|
||||
// 9. Return D.
|
||||
Ok(context.realm.global_property_map.get(key).cloned()) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryIsExtensible`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
|
||||
#[inline] |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn global_is_extensible(_obj: &JsObject, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Return O.[[Extensible]].
|
||||
Ok(context.realm.global_extensible) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryPreventExtensions`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarypreventextensions
|
||||
#[inline] |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn global_prevent_extensions(_obj: &JsObject, context: &mut Context) -> JsResult<bool> { |
||||
// 1. Set O.[[Extensible]] to false.
|
||||
context.realm.global_extensible = false; |
||||
|
||||
// 2. Return true.
|
||||
Ok(true) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryDefineOwnProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
|
||||
#[inline] |
||||
#[allow(clippy::needless_pass_by_value)] |
||||
pub(crate) fn global_define_own_property( |
||||
obj: &JsObject, |
||||
key: PropertyKey, |
||||
desc: PropertyDescriptor, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_define_own_property", "object"); |
||||
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||
let current = global_get_own_property(obj, &key, context)?; |
||||
|
||||
// 2. Let extensible be ? IsExtensible(O).
|
||||
let extensible = obj.__is_extensible__(context)?; |
||||
|
||||
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
|
||||
Ok(validate_and_apply_property_descriptor( |
||||
&key, extensible, desc, current, context, |
||||
)) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryHasProperty`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
|
||||
#[inline] |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn global_has_property( |
||||
_obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_has_property", "object"); |
||||
Ok(context.realm.global_property_map.contains_key(key)) |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryGet`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget
|
||||
#[inline] |
||||
#[allow(clippy::needless_pass_by_value)] |
||||
pub(crate) fn global_get( |
||||
obj: &JsObject, |
||||
key: &PropertyKey, |
||||
receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_get", "object"); |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match global_get_own_property(obj, key, context)? { |
||||
// If desc is undefined, then
|
||||
None => { |
||||
// b. If parent is null, return undefined.
|
||||
Ok(JsValue::undefined()) |
||||
} |
||||
Some(ref desc) => match desc.kind() { |
||||
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
|
||||
DescriptorKind::Data { |
||||
value: Some(value), .. |
||||
} => Ok(value.clone()), |
||||
// 5. Assert: IsAccessorDescriptor(desc) is true.
|
||||
// 6. Let getter be desc.[[Get]].
|
||||
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { |
||||
// 8. Return ? Call(getter, Receiver).
|
||||
context.call(get, &receiver, &[]) |
||||
} |
||||
// 7. If getter is undefined, return undefined.
|
||||
_ => Ok(JsValue::undefined()), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `OrdinarySet`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset
|
||||
#[inline] |
||||
#[allow(clippy::needless_pass_by_value)] |
||||
pub(crate) fn global_set( |
||||
_obj: &JsObject, |
||||
key: PropertyKey, |
||||
value: JsValue, |
||||
_receiver: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
global_set_no_receiver(&key, value, context) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn global_set_no_receiver( |
||||
key: &PropertyKey, |
||||
value: JsValue, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_set", "object"); |
||||
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
|
||||
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
|
||||
|
||||
// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
|
||||
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor
|
||||
|
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
let own_desc = if let Some(desc) = context.realm.global_property_map.get(key).cloned() { |
||||
desc |
||||
} |
||||
// c. Else,
|
||||
else { |
||||
PropertyDescriptor::builder() |
||||
.value(value.clone()) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build() |
||||
}; |
||||
|
||||
// 3. If IsDataDescriptor(ownDesc) is true, then
|
||||
if own_desc.is_data_descriptor() { |
||||
// a. If ownDesc.[[Writable]] is false, return false.
|
||||
if !own_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
|
||||
// d. If existingDescriptor is not undefined, then
|
||||
let desc = if let Some(existing_desc) = context.realm.global_property_map.get(key) { |
||||
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
|
||||
if existing_desc.is_accessor_descriptor() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// ii. If existingDescriptor.[[Writable]] is false, return false.
|
||||
if !existing_desc.expect_writable() { |
||||
return Ok(false); |
||||
} |
||||
|
||||
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
|
||||
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
|
||||
PropertyDescriptor::builder().value(value).build() |
||||
} else { |
||||
// i. Assert: Receiver does not currently have a property P.
|
||||
// ii. Return ? CreateDataProperty(Receiver, P, V).
|
||||
PropertyDescriptor::builder() |
||||
.value(value) |
||||
.writable(true) |
||||
.enumerable(true) |
||||
.configurable(true) |
||||
.build() |
||||
}; |
||||
|
||||
// 1. Let current be ? O.[[GetOwnProperty]](P).
|
||||
let current = context.realm.global_property_map.get(key).cloned(); |
||||
|
||||
// 2. Let extensible be ? IsExtensible(O).
|
||||
let extensible = context.realm.global_extensible; |
||||
|
||||
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
|
||||
return Ok(validate_and_apply_property_descriptor( |
||||
key, extensible, desc, current, context, |
||||
)); |
||||
} |
||||
|
||||
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
|
||||
debug_assert!(own_desc.is_accessor_descriptor()); |
||||
|
||||
// 5. Let setter be ownDesc.[[Set]].
|
||||
match own_desc.set() { |
||||
Some(set) if !set.is_undefined() => { |
||||
// 7. Perform ? Call(setter, Receiver, « V »).
|
||||
context.call(set, &context.global_object().clone().into(), &[value])?; |
||||
|
||||
// 8. Return true.
|
||||
Ok(true) |
||||
} |
||||
// 6. If setter is undefined, return false.
|
||||
_ => Ok(false), |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `OrdinaryDelete`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
|
||||
#[inline] |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn global_delete( |
||||
_obj: &JsObject, |
||||
key: &PropertyKey, |
||||
context: &mut Context, |
||||
) -> JsResult<bool> { |
||||
let _timer = BoaProfiler::global().start_event("Object::global_delete", "object"); |
||||
// 1. Assert: IsPropertyKey(P) is true.
|
||||
// 2. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
match context.realm.global_property_map.get(key) { |
||||
// 4. If desc.[[Configurable]] is true, then
|
||||
Some(desc) if desc.expect_configurable() => { |
||||
// a. Remove the own property with name P from O.
|
||||
context.realm.global_property_map.remove(key); |
||||
// b. Return true.
|
||||
Ok(true) |
||||
} |
||||
// 5. Return false.
|
||||
Some(_) => Ok(false), |
||||
// 3. If desc is undefined, return true.
|
||||
None => Ok(true), |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
|
||||
#[inline] |
||||
pub(crate) fn validate_and_apply_property_descriptor( |
||||
key: &PropertyKey, |
||||
extensible: bool, |
||||
desc: PropertyDescriptor, |
||||
current: Option<PropertyDescriptor>, |
||||
context: &mut Context, |
||||
) -> bool { |
||||
let _timer = BoaProfiler::global().start_event( |
||||
"Object::global_validate_and_apply_property_descriptor", |
||||
"object", |
||||
); |
||||
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
|
||||
|
||||
let mut current = if let Some(own) = current { |
||||
own |
||||
} |
||||
// 2. If current is undefined, then
|
||||
else { |
||||
// a. If extensible is false, return false.
|
||||
if !extensible { |
||||
return false; |
||||
} |
||||
|
||||
// b. Assert: extensible is true.
|
||||
context.realm.global_property_map.insert( |
||||
key, |
||||
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
|
||||
if desc.is_generic_descriptor() || desc.is_data_descriptor() { |
||||
// i. If O is not undefined, create an own data property named P of
|
||||
// object O whose [[Value]], [[Writable]], [[Enumerable]], and
|
||||
// [[Configurable]] attribute values are described by Desc.
|
||||
// If the value of an attribute field of Desc is absent, the attribute
|
||||
// of the newly created property is set to its default value.
|
||||
desc.into_data_defaulted() |
||||
} |
||||
// d. Else,
|
||||
else { |
||||
// i. Assert: ! IsAccessorDescriptor(Desc) is true.
|
||||
|
||||
// ii. If O is not undefined, create an own accessor property named P
|
||||
// of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]
|
||||
// attribute values are described by Desc. If the value of an attribute field
|
||||
// of Desc is absent, the attribute of the newly created property is set to
|
||||
// its default value.
|
||||
desc.into_accessor_defaulted() |
||||
}, |
||||
); |
||||
|
||||
// e. Return true.
|
||||
return true; |
||||
}; |
||||
|
||||
// 3. If every field in Desc is absent, return true.
|
||||
if desc.is_empty() { |
||||
return true; |
||||
} |
||||
|
||||
// 4. If current.[[Configurable]] is false, then
|
||||
if !current.expect_configurable() { |
||||
// a. If Desc.[[Configurable]] is present and its value is true, return false.
|
||||
if matches!(desc.configurable(), Some(true)) { |
||||
return false; |
||||
} |
||||
|
||||
// b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]])
|
||||
// is false, return false.
|
||||
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable()) |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// 5. If ! IsGenericDescriptor(Desc) is true, then
|
||||
if desc.is_generic_descriptor() { |
||||
// a. NOTE: No further validation is required.
|
||||
} |
||||
// 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
|
||||
else if current.is_data_descriptor() != desc.is_data_descriptor() { |
||||
// a. If current.[[Configurable]] is false, return false.
|
||||
if !current.expect_configurable() { |
||||
return false; |
||||
} |
||||
|
||||
// b. If IsDataDescriptor(current) is true, then
|
||||
if current.is_data_descriptor() { |
||||
// i. If O is not undefined, convert the property named P of object O from a data
|
||||
// property to an accessor property. Preserve the existing values of the converted
|
||||
// property's [[Configurable]] and [[Enumerable]] attributes and set the rest of
|
||||
// the property's attributes to their default values.
|
||||
current = current.into_accessor_defaulted(); |
||||
} |
||||
// c. Else,
|
||||
else { |
||||
// i. If O is not undefined, convert the property named P of object O from an
|
||||
// accessor property to a data property. Preserve the existing values of the
|
||||
// converted property's [[Configurable]] and [[Enumerable]] attributes and set
|
||||
// the rest of the property's attributes to their default values.
|
||||
current = current.into_data_defaulted(); |
||||
} |
||||
} |
||||
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
|
||||
else if current.is_data_descriptor() && desc.is_data_descriptor() { |
||||
// a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
|
||||
if !current.expect_configurable() && !current.expect_writable() { |
||||
// i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
|
||||
if matches!(desc.writable(), Some(true)) { |
||||
return false; |
||||
} |
||||
// ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
|
||||
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value())) |
||||
{ |
||||
return false; |
||||
} |
||||
// iii. Return true.
|
||||
return true; |
||||
} |
||||
} |
||||
// 8. Else,
|
||||
// a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
|
||||
// b. If current.[[Configurable]] is false, then
|
||||
else if !current.expect_configurable() { |
||||
// i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
|
||||
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) { |
||||
return false; |
||||
} |
||||
|
||||
// ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
|
||||
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) { |
||||
return false; |
||||
} |
||||
// iii. Return true.
|
||||
return true; |
||||
} |
||||
|
||||
// 9. If O is not undefined, then
|
||||
// a. For each field of Desc that is present, set the corresponding attribute of the
|
||||
// property named P of object O to the value of the field.
|
||||
current.fill_with(&desc); |
||||
context.realm.global_property_map.insert(key, current); |
||||
|
||||
// 10. Return true.
|
||||
true |
||||
} |
Loading…
Reference in new issue