Browse Source

[ReferenceError] complete solution (#488)

pull/502/head
croraf 5 years ago committed by GitHub
parent
commit
4ae939ac52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      boa/src/builtins/error/mod.rs
  2. 91
      boa/src/builtins/error/reference.rs
  3. 3
      boa/src/builtins/mod.rs
  4. 20
      boa/src/environment/lexical_environment.rs
  5. 9
      boa/src/exec/exception.rs
  6. 21
      boa/src/exec/identifier/mod.rs
  7. 9
      boa/src/exec/mod.rs
  8. 79
      boa/src/exec/tests.rs

3
boa/src/builtins/error/mod.rs

@ -22,13 +22,14 @@ use crate::{
// mod eval;
pub(crate) mod range;
// mod reference;
pub(crate) mod reference;
// mod syntax;
pub(crate) mod r#type;
// mod uri;
pub(crate) use self::r#type::TypeError;
pub(crate) use self::range::RangeError;
pub(crate) use self::reference::ReferenceError;
/// Built-in `Error` object.
#[derive(Debug, Clone, Copy)]

91
boa/src/builtins/error/reference.rs

@ -0,0 +1,91 @@
//! This module implements the global `ReferenceError` object.
//!
//! Indicates an error that occurs when de-referencing an invalid reference
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-referenceerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError
use crate::{
builtins::{
function::make_builtin_fn,
function::make_constructor_fn,
object::ObjectData,
value::{ResultValue, Value},
},
exec::Interpreter,
profiler::BoaProfiler,
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReferenceError;
impl ReferenceError {
/// The name of the object.
pub(crate) const NAME: &'static str = "ReferenceError";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.set_field(
"message",
Value::from(
args.get(0)
.expect("failed getting error message")
.to_string(),
),
);
}
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
Err(this.clone())
}
/// `Error.prototype.toString()`
///
/// The toString() method returns a string representing the specified Error object.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name");
let message = this.get_field("message");
Ok(Value::from(format!("{}: {}", name, message)))
}
/// Create a new `ReferenceError` object.
pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global));
prototype.set_field("message", Value::from(""));
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_error,
global,
prototype,
true,
)
}
/// Initialise the global object with the `ReferenceError` object.
pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
(Self::NAME, Self::create(global))
}
}

3
boa/src/builtins/mod.rs

@ -23,7 +23,7 @@ pub(crate) use self::{
array::Array,
bigint::BigInt,
boolean::Boolean,
error::{Error, RangeError, TypeError},
error::{Error, RangeError, ReferenceError, TypeError},
global_this::GlobalThis,
infinity::Infinity,
json::Json,
@ -56,6 +56,7 @@ pub fn init(global: &Value) {
// Global error types.
Error::init(global),
RangeError::init(global),
ReferenceError::init(global),
TypeError::init(global),
// Global properties.
NaN::init(global),

20
boa/src/environment/lexical_environment.rs

@ -295,10 +295,15 @@ mod tests {
{
let bar = "bar";
}
bar == undefined;
try{
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "true");
assert_eq!(&exec(scenario), "bar is not defined");
}
#[test]
@ -307,10 +312,15 @@ mod tests {
{
const bar = "bar";
}
bar == undefined;
"#;
assert_eq!(&exec(scenario), "true");
try{
bar;
} catch (err) {
err.message
}
"#;
// awaiting agreement on error throw testing
assert_eq!(&exec(scenario), "bar is not defined");
}
#[test]

9
boa/src/exec/exception.rs

@ -53,11 +53,16 @@ impl Interpreter {
}
/// Constructs a `ReferenceError` with the specified message.
pub fn construct_reference_error<M>(&mut self, _message: M) -> Value
pub fn construct_reference_error<M>(&mut self, message: M) -> Value
where
M: Into<String>,
{
unimplemented!("ReferenceError: is not implemented");
New::from(Call::new(
Identifier::from("ReferenceError"),
vec![Const::from(message.into() + " is not defined").into()],
))
.run(self)
.expect_err("ReferenceError should always throw")
}
/// Throws a `ReferenceError` with the specified message.

21
boa/src/exec/identifier/mod.rs

@ -0,0 +1,21 @@
use super::{Executable, Interpreter};
use crate::{
builtins::value::{ResultValue, Value, ValueData},
syntax::ast::node::identifier::Identifier,
};
impl Executable for Identifier {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let reference = resolve_binding(interpreter, self.as_ref());
match reference.data() {
ValueData::Undefined => Err(interpreter
.throw_reference_error(self.as_ref())
.expect_err("throw_reference_error() must return an error")),
_ => Ok(reference),
}
}
}
pub(crate) fn resolve_binding(interpreter: &mut Interpreter, name: &str) -> Value {
interpreter.realm().environment.get_binding_value(name)
}

9
boa/src/exec/mod.rs

@ -8,6 +8,7 @@ mod declaration;
mod exception;
mod expression;
mod field;
mod identifier;
mod iteration;
mod object;
mod operator;
@ -582,13 +583,7 @@ impl Executable for Node {
Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())),
Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)),
Node::Block(ref block) => block.run(interpreter),
Node::Identifier(ref name) => {
let val = interpreter
.realm()
.environment
.get_binding_value(name.as_ref());
Ok(val)
}
Node::Identifier(ref identifier) => identifier.run(interpreter),
Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(interpreter),
Node::GetField(ref get_field) => get_field.run(interpreter),
Node::Call(ref expr) => expr.run(interpreter),

79
boa/src/exec/tests.rs

@ -9,16 +9,6 @@ fn function_declaration_returns_undefined() {
assert_eq!(&exec(scenario), "undefined");
}
#[test]
fn empty_var_decl_undefined() {
let scenario = r#"
let b;
b === undefined;
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn property_accessor_member_expression_dot_notation_on_string_literal() {
let scenario = r#"
@ -58,10 +48,11 @@ fn property_accessor_member_expression_bracket_notation_on_function() {
}
#[test]
#[ignore] // will be solved with undefined added to global property
fn empty_let_decl_undefined() {
let scenario = r#"
let a;
a == undefined;
a === undefined;
"#;
assert_eq!(&exec(scenario), "true");
@ -78,6 +69,30 @@ fn semicolon_expression_stop() {
assert_eq!(&exec(scenario), "1");
}
#[test]
#[ignore] // will be fixed with undefined added as global property
fn empty_var_decl_undefined() {
let scenario = r#"
let b;
b === undefined;
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn identifier_on_global_object_undefined() {
let scenario = r#"
try {
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "bar is not defined");
}
#[test]
fn object_field_set() {
let scenario = r#"
@ -342,7 +357,7 @@ fn do_while_post_inc() {
}
#[test]
fn test_for_loop() {
fn for_loop() {
let simple = r#"
const a = ['h', 'e', 'l', 'l', 'o'];
let b = '';
@ -374,13 +389,21 @@ fn test_for_loop() {
a
"#;
assert_eq!(&exec(body_should_not_execute_on_false_condition), "0");
}
#[test]
fn for_loop_iteration_variable_does_not_leak() {
let inner_scope = r#"
for (let i = 0;false;) {}
i
try {
i
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(inner_scope), "undefined");
// awaiting agreement on unhandled error handling
assert_eq!(&exec(inner_scope), "i is not defined");
}
#[test]
@ -427,55 +450,80 @@ fn unary_pre() {
}
#[test]
fn unary_typeof() {
fn typeof_string() {
let typeof_string = r#"
const a = String();
typeof a;
"#;
assert_eq!(&exec(typeof_string), "string");
}
#[test]
fn typeof_int() {
let typeof_int = r#"
let a = 5;
typeof a;
"#;
assert_eq!(&exec(typeof_int), "number");
}
#[test]
fn typeof_rational() {
let typeof_rational = r#"
let a = 0.5;
typeof a;
"#;
assert_eq!(&exec(typeof_rational), "number");
}
#[test]
#[ignore] // Will be fixed when global property undefined is added
fn typeof_undefined() {
let typeof_undefined = r#"
let a = undefined;
typeof a;
"#;
assert_eq!(&exec(typeof_undefined), "undefined");
}
#[test]
fn typeof_boolean() {
let typeof_boolean = r#"
let a = true;
typeof a;
"#;
assert_eq!(&exec(typeof_boolean), "boolean");
}
#[test]
fn typeof_null() {
let typeof_null = r#"
let a = null;
typeof a;
"#;
assert_eq!(&exec(typeof_null), "object");
}
#[test]
fn typeof_object() {
let typeof_object = r#"
let a = {};
typeof a;
"#;
assert_eq!(&exec(typeof_object), "object");
}
#[test]
fn typeof_symbol() {
let typeof_symbol = r#"
let a = Symbol();
typeof a;
"#;
assert_eq!(&exec(typeof_symbol), "symbol");
}
#[test]
fn typeof_function() {
let typeof_function = r#"
let a = function(){};
typeof a;
@ -708,6 +756,7 @@ mod in_operator {
}
#[test]
#[ignore] // maybe will be solved when undefined added to global property
fn var_decl_hoisting() {
let scenario = r#"
x = 5;

Loading…
Cancel
Save