diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 536a915834..016287475a 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -124,12 +124,14 @@ impl Function { // Create binding local_env .borrow_mut() - .create_mutable_binding(param.name().to_owned(), false); + .create_mutable_binding(param.name().to_owned(), false) + .expect("Failed to create binding for rest param"); // Set Binding to value local_env .borrow_mut() - .initialize_binding(param.name(), array); + .initialize_binding(param.name(), array) + .expect("Failed to initialize rest param"); } // Adds an argument to the environment @@ -142,12 +144,14 @@ impl Function { // Create binding local_env .borrow_mut() - .create_mutable_binding(param.name().to_owned(), false); + .create_mutable_binding(param.name().to_owned(), false) + .expect("Failed to create binding"); // Set Binding to value local_env .borrow_mut() - .initialize_binding(param.name(), value); + .initialize_binding(param.name(), value) + .expect("Failed to intialize binding"); } /// Returns true if the function object is callable. diff --git a/boa/src/context.rs b/boa/src/context.rs index bd988e3581..b5a05e46fb 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -342,7 +342,7 @@ impl Context { #[inline] pub fn construct_range_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { // Runs a `new RangeError(message)`. New::from(Call::new( @@ -357,7 +357,7 @@ impl Context { #[inline] pub fn throw_range_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_range_error(message)) } @@ -366,7 +366,7 @@ impl Context { #[inline] pub fn construct_type_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { // Runs a `new TypeError(message)`. New::from(Call::new( @@ -381,7 +381,7 @@ impl Context { #[inline] pub fn throw_type_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_type_error(message)) } @@ -390,11 +390,11 @@ impl Context { #[inline] pub fn construct_reference_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { New::from(Call::new( Identifier::from("ReferenceError"), - vec![Const::from(message.into() + " is not defined").into()], + vec![Const::from(message.into()).into()], )) .run(self) .expect("Into used as message") @@ -404,7 +404,7 @@ impl Context { #[inline] pub fn throw_reference_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_reference_error(message)) } @@ -413,7 +413,7 @@ impl Context { #[inline] pub fn construct_syntax_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { New::from(Call::new( Identifier::from("SyntaxError"), @@ -427,7 +427,7 @@ impl Context { #[inline] pub fn throw_syntax_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_syntax_error(message)) } @@ -435,7 +435,7 @@ impl Context { /// Constructs a `EvalError` with the specified message. pub fn construct_eval_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { New::from(Call::new( Identifier::from("EvalError"), @@ -448,7 +448,7 @@ impl Context { /// Constructs a `URIError` with the specified message. pub fn construct_uri_error(&mut self, message: M) -> Value where - M: Into, + M: Into>, { New::from(Call::new( Identifier::from("URIError"), @@ -461,7 +461,7 @@ impl Context { /// Throws a `EvalError` with the specified message. pub fn throw_eval_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_eval_error(message)) } @@ -469,7 +469,7 @@ impl Context { /// Throws a `URIError` with the specified message. pub fn throw_uri_error(&mut self, message: M) -> Result where - M: Into, + M: Into>, { Err(self.construct_uri_error(message)) } @@ -619,7 +619,8 @@ impl Context { Node::Identifier(ref name) => { self.realm .environment - .set_mutable_binding(name.as_ref(), value.clone(), true); + .set_mutable_binding(name.as_ref(), value.clone(), true) + .map_err(|e| e.to_error(self))?; Ok(value) } Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node diff --git a/boa/src/environment/declarative_environment_record.rs b/boa/src/environment/declarative_environment_record.rs index 656ad26027..8133428a80 100644 --- a/boa/src/environment/declarative_environment_record.rs +++ b/boa/src/environment/declarative_environment_record.rs @@ -5,6 +5,7 @@ //! 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 super::ErrorKind; use crate::{ environment::{ environment_record_trait::EnvironmentRecordTrait, @@ -41,11 +42,12 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { self.env_rec.contains_key(name) } - fn create_mutable_binding(&mut self, name: String, deletion: bool) { - if self.env_rec.contains_key(&name) { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been declared", name); - } + fn create_mutable_binding(&mut self, name: String, deletion: bool) -> Result<(), ErrorKind> { + assert!( + !self.env_rec.contains_key(&name), + "Identifier {} has already been declared", + name + ); self.env_rec.insert( name, @@ -56,13 +58,15 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { strict: false, }, ); + Ok(()) } - fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool { - if self.env_rec.contains_key(&name) { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been declared", name); - } + fn create_immutable_binding(&mut self, name: String, strict: bool) -> Result<(), ErrorKind> { + assert!( + !self.env_rec.contains_key(&name), + "Identifier {} has already been declared", + name + ); self.env_rec.insert( name, @@ -73,32 +77,37 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { strict, }, ); - - true + Ok(()) } - fn initialize_binding(&mut self, name: &str, value: Value) { + fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> { if let Some(ref mut record) = self.env_rec.get_mut(name) { if record.value.is_none() { record.value = Some(value); - } else { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been defined", name); + return Ok(()); } } + panic!("record must have binding for {}", name); } #[allow(clippy::else_if_without_else)] - fn set_mutable_binding(&mut self, name: &str, value: Value, mut strict: bool) { + fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + mut strict: bool, + ) -> Result<(), ErrorKind> { if self.env_rec.get(name).is_none() { if strict { - // TODO: change this when error handling comes into play - panic!("Reference Error: Cannot set mutable binding for {}", name); + return Err(ErrorKind::new_reference_error(format!( + "{} not found", + name + ))); } - self.create_mutable_binding(name.to_owned(), true); - self.initialize_binding(name, value); - return; + self.create_mutable_binding(name.to_owned(), true)?; + self.initialize_binding(name, value)?; + return Ok(()); } let record: &mut DeclarativeEnvironmentRecordBinding = self.env_rec.get_mut(name).unwrap(); @@ -106,28 +115,35 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { strict = true } if record.value.is_none() { - // TODO: change this when error handling comes into play - panic!("Reference Error: Cannot set mutable binding for {}", name); + return Err(ErrorKind::new_reference_error(format!( + "{} has not been initialized", + name + ))); } - if record.mutable { record.value = Some(value); } else if strict { - // TODO: change this when error handling comes into play - panic!("TypeError: Cannot mutate an immutable binding {}", name); + return Err(ErrorKind::new_type_error(format!( + "Cannot mutate an immutable binding {}", + name + ))); } + + Ok(()) } - fn get_binding_value(&self, name: &str, _strict: bool) -> Value { + fn get_binding_value(&self, name: &str, _strict: bool) -> Result { if let Some(binding) = self.env_rec.get(name) { - binding - .value - .as_ref() - .expect("Could not get record as reference") - .clone() + if let Some(ref val) = binding.value { + Ok(val.clone()) + } else { + Err(ErrorKind::new_reference_error(format!( + "{} is an uninitialized binding", + name + ))) + } } else { - // TODO: change this when error handling comes into play - panic!("ReferenceError: Cannot get binding value for {}", name); + panic!("Cannot get binding value for {}", name); } } @@ -141,7 +157,7 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { false } } - None => false, + None => panic!("env_rec has no binding for {}", name), } } @@ -149,8 +165,8 @@ impl EnvironmentRecordTrait for DeclarativeEnvironmentRecord { false } - fn get_this_binding(&self) -> Value { - Value::undefined() + fn get_this_binding(&self) -> Result { + Ok(Value::undefined()) } fn has_super_binding(&self) -> bool { diff --git a/boa/src/environment/environment_record_trait.rs b/boa/src/environment/environment_record_trait.rs index cde825aed2..b2fc15eafe 100644 --- a/boa/src/environment/environment_record_trait.rs +++ b/boa/src/environment/environment_record_trait.rs @@ -8,6 +8,7 @@ //! //! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait` //! +use super::ErrorKind; use crate::{ environment::lexical_environment::{Environment, EnvironmentType}, gc::{Finalize, Trace}, @@ -25,30 +26,35 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { /// 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. - fn create_mutable_binding(&mut self, name: String, deletion: bool); + fn create_mutable_binding(&mut self, name: String, deletion: bool) -> Result<(), ErrorKind>; /// 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(&mut self, name: String, strict: bool) -> bool; + fn create_immutable_binding(&mut self, name: String, strict: bool) -> Result<(), ErrorKind>; /// 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(&mut self, name: &str, value: Value); + fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind>; /// 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(&mut self, name: &str, value: Value, strict: bool); + fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + strict: bool, + ) -> Result<(), ErrorKind>; /// 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: &str, strict: bool) -> Value; + fn get_binding_value(&self, name: &str, strict: bool) -> Result; /// Delete a binding from an Environment Record. /// The String value name is the text of the bound name. @@ -61,7 +67,7 @@ pub trait EnvironmentRecordTrait: Debug + Trace + Finalize { fn has_this_binding(&self) -> bool; /// Return the `this` binding from the environment - fn get_this_binding(&self) -> Value; + fn get_this_binding(&self) -> Result; /// Determine if an Environment Record establishes a super method binding. /// Return true if it does and false if it does not. diff --git a/boa/src/environment/function_environment_record.rs b/boa/src/environment/function_environment_record.rs index b3eb157204..1bf0472252 100644 --- a/boa/src/environment/function_environment_record.rs +++ b/boa/src/environment/function_environment_record.rs @@ -8,6 +8,7 @@ //! from within the function. //! More info: +use super::ErrorKind; use crate::{ environment::{ declarative_environment_record::DeclarativeEnvironmentRecordBinding, @@ -60,39 +61,49 @@ pub struct FunctionEnvironmentRecord { } impl FunctionEnvironmentRecord { - pub fn bind_this_value(&mut self, value: Value) { + pub fn bind_this_value(&mut self, value: Value) -> Result { match self.this_binding_status { // You can not bind an arrow function, their `this` value comes from the lexical scope above BindingStatus::Lexical => { - // TODO: change this when error handling comes into play panic!("Cannot bind to an arrow function!"); } // You can not bind a function twice - BindingStatus::Initialized => { - // TODO: change this when error handling comes into play - panic!("Reference Error: Cannot bind to an initialised function!"); - } + BindingStatus::Initialized => Err(ErrorKind::new_reference_error( + "Cannot bind to an initialised function!", + )), BindingStatus::Uninitialized => { - self.this_value = value; + self.this_value = value.clone(); self.this_binding_status = BindingStatus::Initialized; + Ok(value) } } } + + pub fn get_super_base(&self) -> Value { + let home = &self.home_object; + if home.is_undefined() { + Value::Undefined + } else { + assert!(home.is_object()); + home.as_object() + .expect("home_object must be an Object") + .prototype_instance() + } + } } impl EnvironmentRecordTrait for FunctionEnvironmentRecord { - // TODO: get_super_base can't implement until GetPrototypeof is implemented on object - fn has_binding(&self, name: &str) -> bool { self.env_rec.contains_key(name) } - fn create_mutable_binding(&mut self, name: String, deletion: bool) { - if self.env_rec.contains_key(&name) { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been declared", name); - } + fn create_mutable_binding(&mut self, name: String, deletion: bool) -> Result<(), ErrorKind> { + assert!( + !self.env_rec.contains_key(&name), + "Identifier {} has already been declared", + name + ); self.env_rec.insert( name, @@ -103,28 +114,28 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { strict: false, }, ); + Ok(()) } - fn get_this_binding(&self) -> Value { + fn get_this_binding(&self) -> Result { match self.this_binding_status { BindingStatus::Lexical => { - // TODO: change this when error handling comes into play panic!("There is no this for a lexical function record"); } - BindingStatus::Uninitialized => { - // TODO: change this when error handling comes into play - panic!("Reference Error: Uninitialised binding for this function"); - } + BindingStatus::Uninitialized => Err(ErrorKind::new_reference_error( + "Uninitialised binding for this function", + )), - BindingStatus::Initialized => self.this_value.clone(), + BindingStatus::Initialized => Ok(self.this_value.clone()), } } - fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool { - if self.env_rec.contains_key(&name) { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been declared", name); - } + fn create_immutable_binding(&mut self, name: String, strict: bool) -> Result<(), ErrorKind> { + assert!( + !self.env_rec.contains_key(&name), + "Identifier {} has already been declared", + name + ); self.env_rec.insert( name, @@ -135,63 +146,73 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { strict, }, ); - - true + Ok(()) } - fn initialize_binding(&mut self, name: &str, value: Value) { + fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> { if let Some(ref mut record) = self.env_rec.get_mut(name) { - match record.value { - Some(_) => { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been defined", name); - } - None => record.value = Some(value), + if record.value.is_none() { + record.value = Some(value); + return Ok(()); } } + panic!("record must have binding for {}", name) } #[allow(clippy::else_if_without_else)] - fn set_mutable_binding(&mut self, name: &str, value: Value, mut strict: bool) { + fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + mut strict: bool, + ) -> Result<(), ErrorKind> { if self.env_rec.get(name).is_none() { if strict { - // TODO: change this when error handling comes into play - panic!("Reference Error: Cannot set mutable binding for {}", name); + return Err(ErrorKind::new_reference_error(format!( + "{} not found", + name + ))); } - self.create_mutable_binding(name.to_owned(), true); - self.initialize_binding(name, value); - return; + self.create_mutable_binding(name.to_owned(), true)?; + self.initialize_binding(name, value)?; + return Ok(()); } let record: &mut DeclarativeEnvironmentRecordBinding = self.env_rec.get_mut(name).unwrap(); if record.strict { strict = true } - if record.value.is_none() { - // TODO: change this when error handling comes into play - panic!("Reference Error: Cannot set mutable binding for {}", name); + return Err(ErrorKind::new_reference_error(format!( + "{} has not been initialized", + name + ))); } - if record.mutable { record.value = Some(value); } else if strict { - // TODO: change this when error handling comes into play - panic!("TypeError: Cannot mutate an immutable binding {}", name); + return Err(ErrorKind::new_type_error(format!( + "Cannot mutate an immutable binding {}", + name + ))); } + + Ok(()) } - fn get_binding_value(&self, name: &str, _strict: bool) -> Value { + fn get_binding_value(&self, name: &str, _strict: bool) -> Result { if let Some(binding) = self.env_rec.get(name) { - binding - .value - .as_ref() - .expect("Could not get record as reference") - .clone() + if let Some(ref val) = binding.value { + Ok(val.clone()) + } else { + Err(ErrorKind::new_reference_error(format!( + "{} is an uninitialized binding", + name + ))) + } } else { - // TODO: change this when error handling comes into play - panic!("ReferenceError: Cannot get binding value for {}", name); + panic!("Cannot get binding value for {}", name); } } @@ -205,7 +226,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord { false } } - None => false, + None => panic!("env_rec has no binding for {}", name), } } diff --git a/boa/src/environment/global_environment_record.rs b/boa/src/environment/global_environment_record.rs index c2739d7cb5..d6f8e27070 100644 --- a/boa/src/environment/global_environment_record.rs +++ b/boa/src/environment/global_environment_record.rs @@ -7,6 +7,7 @@ //! that occur within a Script. //! More info: +use super::ErrorKind; use crate::{ environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, @@ -51,20 +52,49 @@ impl GlobalEnvironmentRecord { } } - pub fn create_global_var_binding(&mut self, name: String, deletion: bool) { + pub fn can_declare_global_var(&self, name: &str) -> bool { + let global_object = &self.object_record.bindings; + if global_object.has_field(name) { + true + } else { + global_object.is_extensible() + } + } + + pub fn can_declare_global_function(&self, name: &str) -> bool { + let global_object = &self.object_record.bindings; + let existing_prop = global_object.get_property(name); + match existing_prop { + Some(prop) => { + if prop.configurable() { + true + } else { + prop.is_data_descriptor() && prop.attributes().writable() && prop.enumerable() + } + } + None => global_object.is_extensible(), + } + } + + pub fn create_global_var_binding( + &mut self, + name: String, + deletion: bool, + ) -> Result<(), ErrorKind> { let obj_rec = &mut self.object_record; let global_object = &obj_rec.bindings; let has_property = global_object.has_field(name.as_str()); let extensible = global_object.is_extensible(); if !has_property && extensible { - obj_rec.create_mutable_binding(name.clone(), deletion); - obj_rec.initialize_binding(&name, Value::undefined()); + obj_rec.create_mutable_binding(name.clone(), deletion)?; + obj_rec.initialize_binding(&name, Value::undefined())?; } let var_declared_names = &mut self.var_names; if !var_declared_names.contains(&name) { var_declared_names.insert(name); } + Ok(()) } pub fn create_global_function_binding(&mut self, name: &str, value: Value, deletion: bool) { @@ -90,8 +120,8 @@ impl GlobalEnvironmentRecord { } impl EnvironmentRecordTrait for GlobalEnvironmentRecord { - fn get_this_binding(&self) -> Value { - self.global_this_binding.clone() + fn get_this_binding(&self) -> Result { + Ok(self.global_this_binding.clone()) } fn has_binding(&self, name: &str) -> bool { @@ -101,36 +131,48 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord { self.object_record.has_binding(name) } - fn create_mutable_binding(&mut self, name: String, deletion: bool) { + fn create_mutable_binding(&mut self, name: String, deletion: bool) -> Result<(), ErrorKind> { if self.declarative_record.has_binding(&name) { - // TODO: change to exception - panic!("Binding already exists!"); + return Err(ErrorKind::new_type_error(format!( + "Binding already exists for {}", + name + ))); } self.declarative_record .create_mutable_binding(name, deletion) } - fn create_immutable_binding(&mut self, name: String, strict: bool) -> bool { + fn create_immutable_binding(&mut self, name: String, strict: bool) -> Result<(), ErrorKind> { if self.declarative_record.has_binding(&name) { - // TODO: change to exception - panic!("Binding already exists!"); + return Err(ErrorKind::new_type_error(format!( + "Binding already exists for {}", + name + ))); } self.declarative_record .create_immutable_binding(name, strict) } - fn initialize_binding(&mut self, name: &str, value: Value) { + fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> { if self.declarative_record.has_binding(&name) { - // TODO: assert binding is in the object environment record return self.declarative_record.initialize_binding(name, value); } - panic!("Should not initialized binding without creating first."); + assert!( + self.object_record.has_binding(name), + "Binding must be in object_record" + ); + self.object_record.initialize_binding(name, value) } - fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) { + fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + strict: bool, + ) -> Result<(), ErrorKind> { if self.declarative_record.has_binding(&name) { return self .declarative_record @@ -139,7 +181,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord { self.object_record.set_mutable_binding(name, value, strict) } - fn get_binding_value(&self, name: &str, strict: bool) -> Value { + fn get_binding_value(&self, name: &str, strict: bool) -> Result { if self.declarative_record.has_binding(&name) { return self.declarative_record.get_binding_value(name, strict); } diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index b7163ae621..8e4563c9e8 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -5,6 +5,7 @@ //! The following operations are used to operate upon lexical environments //! This is the entrypoint to lexical environments. +use super::ErrorKind; use crate::{ environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, @@ -113,14 +114,19 @@ impl LexicalEnvironment { .get_global_object() } - pub fn get_this_binding(&self) -> Value { + pub fn get_this_binding(&self) -> Result { self.environments() .find(|env| env.borrow().has_this_binding()) .map(|env| env.borrow().get_this_binding()) - .unwrap_or_else(Value::undefined) + .unwrap_or_else(|| Ok(Value::Undefined)) } - pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) { + pub fn create_mutable_binding( + &mut self, + name: String, + deletion: bool, + scope: VariableScope, + ) -> Result<(), ErrorKind> { match scope { VariableScope::Block => self .get_current_environment() @@ -138,7 +144,7 @@ impl LexicalEnvironment { }) .expect("No function or global environment"); - env.borrow_mut().create_mutable_binding(name, deletion); + env.borrow_mut().create_mutable_binding(name, deletion) } } } @@ -148,7 +154,7 @@ impl LexicalEnvironment { name: String, deletion: bool, scope: VariableScope, - ) -> bool { + ) -> Result<(), ErrorKind> { match scope { VariableScope::Block => self .get_current_environment() @@ -171,24 +177,43 @@ impl LexicalEnvironment { } } - pub fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) { + pub fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + strict: bool, + ) -> Result<(), ErrorKind> { // Find the first environment which has the given binding let env = self .environments() - .find(|env| env.borrow().has_binding(name)) - .expect("Binding does not exists"); // TODO graceful error handling - - env.borrow_mut().set_mutable_binding(name, value, strict); + .find(|env| env.borrow().has_binding(name)); + + let env = if let Some(env) = env { + env + } else { + // global_env doesn't need has_binding to be satisfied in non strict mode + self.environment_stack + .get(0) + .expect("Environment stack underflow") + }; + env.borrow_mut().set_mutable_binding(name, value, strict) } - pub fn initialize_binding(&mut self, name: &str, value: Value) { + pub fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> { // Find the first environment which has the given binding let env = self .environments() - .find(|env| env.borrow().has_binding(name)) - .expect("Binding does not exists"); // TODO graceful error handling - - env.borrow_mut().initialize_binding(name, value); + .find(|env| env.borrow().has_binding(name)); + + let env = if let Some(env) = env { + env + } else { + // global_env doesn't need has_binding to be satisfied in non strict mode + self.environment_stack + .get(0) + .expect("Environment stack underflow") + }; + env.borrow_mut().initialize_binding(name, value) } /// get_current_environment_ref is used when you only need to borrow the environment @@ -212,10 +237,16 @@ impl LexicalEnvironment { .any(|env| env.borrow().has_binding(name)) } - pub fn get_binding_value(&self, name: &str) -> Option { + pub fn get_binding_value(&self, name: &str) -> Result { self.environments() .find(|env| env.borrow().has_binding(name)) .map(|env| env.borrow().get_binding_value(name, false)) + .unwrap_or_else(|| { + Err(ErrorKind::new_reference_error(format!( + "{} is not defined", + name + ))) + }) } } @@ -246,7 +277,7 @@ pub fn new_function_environment( }; // If a `this` value has been passed, bind it to the environment if let Some(v) = this { - func_env.bind_this_value(v); + func_env.bind_this_value(v).unwrap(); } Gc::new(GcCell::new(Box::new(func_env))) } diff --git a/boa/src/environment/mod.rs b/boa/src/environment/mod.rs index 3e4d89bbeb..ceb1773c38 100644 --- a/boa/src/environment/mod.rs +++ b/boa/src/environment/mod.rs @@ -6,3 +6,35 @@ pub mod function_environment_record; pub mod global_environment_record; pub mod lexical_environment; pub mod object_environment_record; + +#[derive(Debug)] +pub enum ErrorKind { + ReferenceError(Box), + TypeError(Box), +} + +use crate::value::Value; +use crate::Context; + +impl ErrorKind { + pub fn to_error(&self, ctx: &mut Context) -> Value { + match self { + ErrorKind::ReferenceError(msg) => ctx.construct_reference_error(msg.clone()), + ErrorKind::TypeError(msg) => ctx.construct_type_error(msg.clone()), + } + } + + pub fn new_reference_error(msg: M) -> Self + where + M: Into>, + { + Self::ReferenceError(msg.into()) + } + + pub fn new_type_error(msg: M) -> Self + where + M: Into>, + { + Self::TypeError(msg.into()) + } +} diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index cad73cfb93..eeb2b49bbc 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -6,6 +6,7 @@ //! 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 super::*; use crate::{ environment::{ environment_record_trait::EnvironmentRecordTrait, @@ -35,7 +36,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { } } - fn create_mutable_binding(&mut self, name: String, deletion: bool) { + fn create_mutable_binding(&mut self, name: String, deletion: bool) -> Result<(), ErrorKind> { // TODO: could save time here and not bother generating a new undefined object, // only for it to be replace with the real value later. We could just add the name to a Vector instead let bindings = &mut self.bindings; @@ -46,13 +47,14 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { prop.set_configurable(deletion); bindings.set_property(name, prop); + Ok(()) } - fn create_immutable_binding(&mut self, _name: String, _strict: bool) -> bool { - true + fn create_immutable_binding(&mut self, _name: String, _strict: bool) -> Result<(), ErrorKind> { + Ok(()) } - fn initialize_binding(&mut self, name: &str, value: Value) { + fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> { // We should never need to check if a binding has been created, // As all calls to create_mutable_binding are followed by initialized binding // The below is just a check. @@ -60,26 +62,32 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { self.set_mutable_binding(name, value, false) } - fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) { + fn set_mutable_binding( + &mut self, + name: &str, + value: Value, + strict: bool, + ) -> Result<(), ErrorKind> { debug_assert!(value.is_object() || value.is_function()); - let mut property = DataDescriptor::new(value, Attribute::ENUMERABLE); property.set_configurable(strict); self.bindings .as_object() .expect("binding object") .insert(name, property); + Ok(()) } - fn get_binding_value(&self, name: &str, strict: bool) -> Value { + fn get_binding_value(&self, name: &str, strict: bool) -> Result { if self.bindings.has_field(name) { - self.bindings.get_field(name) + Ok(self.bindings.get_field(name)) + } else if strict { + Err(ErrorKind::new_reference_error(format!( + "{} has no binding", + name + ))) } else { - if strict { - // TODO: throw error here - // Error handling not implemented yet - } - Value::undefined() + Ok(Value::undefined()) } } @@ -92,8 +100,8 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { false } - fn get_this_binding(&self) -> Value { - Value::undefined() + fn get_this_binding(&self) -> Result { + Ok(Value::undefined()) } fn has_super_binding(&self) -> bool { diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index a0d74cb6ab..7cff8de68c 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -162,10 +162,12 @@ impl GcObject { let arguments_obj = create_unmapped_arguments_object(args); local_env .borrow_mut() - .create_mutable_binding("arguments".to_string(), false); + .create_mutable_binding("arguments".to_string(), false) + .map_err(|e| e.to_error(context))?; local_env .borrow_mut() - .initialize_binding("arguments", arguments_obj); + .initialize_binding("arguments", arguments_obj) + .map_err(|e| e.to_error(context))?; context.realm_mut().environment.push(local_env); @@ -257,10 +259,12 @@ impl GcObject { let arguments_obj = create_unmapped_arguments_object(args); local_env .borrow_mut() - .create_mutable_binding("arguments".to_string(), false); + .create_mutable_binding("arguments".to_string(), false) + .map_err(|e| e.to_error(context))?; local_env .borrow_mut() - .initialize_binding("arguments", arguments_obj); + .initialize_binding("arguments", arguments_obj) + .map_err(|e| e.to_error(context))?; context.realm_mut().environment.push(local_env); @@ -285,7 +289,7 @@ impl GcObject { // local_env gets dropped here, its no longer needed let binding = context.realm_mut().environment.get_this_binding(); - Ok(binding) + binding.map_err(|e| e.to_error(context)) } } } diff --git a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs index ff94ed4911..9c392854ce 100644 --- a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs @@ -43,17 +43,17 @@ impl Executable for ConstDeclList { } else { return context.throw_syntax_error("missing = in const declaration"); }; - - context.realm_mut().environment.create_immutable_binding( - decl.name().to_owned(), - false, - VariableScope::Block, - ); + context + .realm_mut() + .environment + .create_immutable_binding(decl.name().to_owned(), false, VariableScope::Block) + .map_err(|e| e.to_error(context))?; context .realm_mut() .environment - .initialize_binding(decl.name(), val); + .initialize_binding(decl.name(), val) + .map_err(|e| e.to_error(context))?; } Ok(Value::undefined()) } diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index b31252e7a7..5838cf4961 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -94,16 +94,17 @@ impl Executable for FunctionDecl { // Set the name and assign it in the current environment val.set_field("name", self.name()); - context.realm_mut().environment.create_mutable_binding( - self.name().to_owned(), - false, - VariableScope::Function, - ); + context + .realm_mut() + .environment + .create_mutable_binding(self.name().to_owned(), false, VariableScope::Function) + .map_err(|e| e.to_error(context))?; context .realm_mut() .environment - .initialize_binding(self.name(), val); + .initialize_binding(self.name(), val) + .map_err(|e| e.to_error(context))?; Ok(Value::undefined()) } diff --git a/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs index c21d4401d5..2596df660c 100644 --- a/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs @@ -41,15 +41,16 @@ impl Executable for LetDeclList { Some(v) => v.run(context)?, None => Value::undefined(), }; - context.realm_mut().environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - ); context .realm_mut() .environment - .initialize_binding(var.name(), val); + .create_mutable_binding(var.name().to_owned(), false, VariableScope::Block) + .map_err(|e| e.to_error(context))?; + context + .realm_mut() + .environment + .initialize_binding(var.name(), val) + .map_err(|e| e.to_error(context))?; } Ok(Value::undefined()) } diff --git a/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs index dceba1378e..061a94c4cd 100644 --- a/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs @@ -46,15 +46,18 @@ impl Executable for VarDeclList { if environment.has_binding(var.name()) { if var.init().is_some() { - environment.set_mutable_binding(var.name(), val, true); + environment + .set_mutable_binding(var.name(), val, true) + .map_err(|e| e.to_error(context))?; } } else { - environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Function, - ); - environment.initialize_binding(var.name(), val); + environment + .create_mutable_binding(var.name().to_owned(), false, VariableScope::Function) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), val) + .map_err(|e| e.to_error(context))?; } } Ok(Value::undefined()) diff --git a/boa/src/syntax/ast/node/identifier/mod.rs b/boa/src/syntax/ast/node/identifier/mod.rs index eb38cdc90d..25f46b6647 100644 --- a/boa/src/syntax/ast/node/identifier/mod.rs +++ b/boa/src/syntax/ast/node/identifier/mod.rs @@ -40,7 +40,7 @@ impl Executable for Identifier { .realm() .environment .get_binding_value(self.as_ref()) - .ok_or_else(|| context.construct_reference_error(self.as_ref())) + .map_err(|e| e.to_error(context)) } } diff --git a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs index a912e50d10..5e40fc1e34 100644 --- a/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs +++ b/boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs @@ -90,14 +90,21 @@ impl Executable for ForOfLoop { if environment.has_binding(name.as_ref()) { // Binding already exists - environment.set_mutable_binding(name.as_ref(), next_result.clone(), true); + environment + .set_mutable_binding(name.as_ref(), next_result.clone(), true) + .map_err(|e| e.to_error(context))?; } else { - environment.create_mutable_binding( - name.as_ref().to_owned(), - true, - VariableScope::Function, - ); - environment.initialize_binding(name.as_ref(), next_result.clone()); + environment + .create_mutable_binding( + name.as_ref().to_owned(), + true, + VariableScope::Function, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(name.as_ref(), next_result.clone()) + .map_err(|e| e.to_error(context))?; } } Node::VarDeclList(ref list) => match list.as_ref() { @@ -109,14 +116,21 @@ impl Executable for ForOfLoop { } if environment.has_binding(var.name()) { - environment.set_mutable_binding(var.name(), next_result, true); + environment + .set_mutable_binding(var.name(), next_result, true) + .map_err(|e| e.to_error(context))?; } else { - environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Function, - ); - environment.initialize_binding(var.name(), next_result); + environment + .create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Function, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; } } _ => { @@ -133,12 +147,18 @@ impl Executable for ForOfLoop { return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); } - environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - ); - environment.initialize_binding(var.name(), next_result); + environment + .create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Block, + ) + .map_err(|e| e.to_error(context))?; + + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; } _ => { return context.throw_syntax_error( @@ -154,12 +174,17 @@ impl Executable for ForOfLoop { return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); } - environment.create_immutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - ); - environment.initialize_binding(var.name(), next_result); + environment + .create_immutable_binding( + var.name().to_owned(), + false, + VariableScope::Block, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(var.name(), next_result) + .map_err(|e| e.to_error(context))?; } _ => { return context.throw_syntax_error( diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index a8146cda1f..efc32fbb6d 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -312,7 +312,11 @@ impl Executable for Node { Node::Spread(ref spread) => spread.run(context), Node::This => { // Will either return `this` binding or undefined - Ok(context.realm().environment.get_this_binding()) + context + .realm() + .environment + .get_this_binding() + .map_err(|e| e.to_error(context)) } Node::Try(ref try_node) => try_node.run(context), Node::Break(ref break_node) => break_node.run(context), diff --git a/boa/src/syntax/ast/node/operator/assign/mod.rs b/boa/src/syntax/ast/node/operator/assign/mod.rs index 0e4f870826..1f3fb637ee 100644 --- a/boa/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa/src/syntax/ast/node/operator/assign/mod.rs @@ -62,14 +62,21 @@ impl Executable for Assign { if environment.has_binding(name.as_ref()) { // Binding already exists - environment.set_mutable_binding(name.as_ref(), val.clone(), true); + environment + .set_mutable_binding(name.as_ref(), val.clone(), true) + .map_err(|e| e.to_error(context))?; } else { - environment.create_mutable_binding( - name.as_ref().to_owned(), - true, - VariableScope::Function, - ); - environment.initialize_binding(name.as_ref(), val.clone()); + environment + .create_mutable_binding( + name.as_ref().to_owned(), + true, + VariableScope::Function, + ) + .map_err(|e| e.to_error(context))?; + let environment = &mut context.realm_mut().environment; + environment + .initialize_binding(name.as_ref(), val.clone()) + .map_err(|e| e.to_error(context))?; } } Node::GetConstField(ref get_const_field) => { diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 625f100b8a..9bbe73b2e0 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -174,14 +174,15 @@ impl Executable for BinOp { .realm() .environment .get_binding_value(name.as_ref()) - .ok_or_else(|| context.construct_reference_error(name.as_ref()))?; + .map_err(|e| e.to_error(context))?; + let v_b = self.rhs().run(context)?; let value = Self::run_assign(op, v_a, v_b, context)?; - context.realm_mut().environment.set_mutable_binding( - name.as_ref(), - value.clone(), - true, - ); + context + .realm_mut() + .environment + .set_mutable_binding(name.as_ref(), value.clone(), true) + .map_err(|e| e.to_error(context))?; Ok(value) } Node::GetConstField(ref get_const_field) => { diff --git a/boa/src/syntax/ast/node/try_node/mod.rs b/boa/src/syntax/ast/node/try_node/mod.rs index 16c3770293..ea90741a5e 100644 --- a/boa/src/syntax/ast/node/try_node/mod.rs +++ b/boa/src/syntax/ast/node/try_node/mod.rs @@ -108,9 +108,11 @@ impl Executable for Try { param.to_owned(), false, VariableScope::Block, - ); - - env.initialize_binding(param, err); + ) + .map_err(|e| e.to_error(context))?; + let env = &mut context.realm_mut().environment; + env.initialize_binding(param, err) + .map_err(|e| e.to_error(context))?; } } diff --git a/boa/src/value/operations.rs b/boa/src/value/operations.rs index 73edf9ae99..44cc173f8b 100644 --- a/boa/src/value/operations.rs +++ b/boa/src/value/operations.rs @@ -320,7 +320,7 @@ impl Value { a.as_inner() .clone() .shift_left(b.as_inner().clone()) - .map_err(|msg| context.construct_range_error(&msg))?, + .map_err(|msg| context.construct_range_error(msg))?, ), // Slow path: @@ -332,7 +332,7 @@ impl Value { x.as_inner() .clone() .shift_left(y.as_inner().clone()) - .map_err(|msg| context.construct_range_error(&msg))?, + .map_err(|msg| context.construct_range_error(msg))?, ), (_, _) => { return context.throw_type_error( @@ -362,7 +362,7 @@ impl Value { a.as_inner() .clone() .shift_right(b.as_inner().clone()) - .map_err(|msg| context.construct_range_error(&msg))?, + .map_err(|msg| context.construct_range_error(msg))?, ), // Slow path: @@ -374,7 +374,7 @@ impl Value { x.as_inner() .clone() .shift_right(y.as_inner().clone()) - .map_err(|msg| context.construct_range_error(&msg))?, + .map_err(|msg| context.construct_range_error(msg))?, ), (_, _) => { return context.throw_type_error(