From f1d676e15b883cd2260c8a7ee6a997e354fe6fec Mon Sep 17 00:00:00 2001 From: morrien <72392743+morrien@users.noreply.github.com> Date: Wed, 28 Oct 2020 14:51:06 +0100 Subject: [PATCH] Implementation of `instanceof` operator (#908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrej Onufrák --- boa/src/builtins/symbol/mod.rs | 2 +- boa/src/object/gcobject.rs | 61 +++++++++++++++++++ boa/src/object/mod.rs | 3 + boa/src/object/tests.rs | 19 ++++++ .../syntax/ast/node/operator/bin_op/mod.rs | 23 +++++-- boa/src/syntax/ast/node/operator/tests.rs | 34 +++++++++++ 6 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 boa/src/object/tests.rs diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 4a6070fd7a..c15ce59f0f 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -113,7 +113,7 @@ impl WellKnownSymbols { /// Called by the semantics of the instanceof operator. #[inline] pub fn has_instance_symbol(&self) -> RcSymbol { - self.async_iterator.clone() + self.has_instance.clone() } /// The `Symbol.isConcatSpreadable` well known symbol. diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 0af3bd9deb..361fb92a91 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -676,6 +676,67 @@ impl GcObject { pub fn is_native_object(&self) -> bool { self.borrow().is_native_object() } + + /// Retrieves value of specific property, when the value of the property is expected to be a function. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-getmethod + #[inline] + pub fn get_method(&self, context: &mut Context, key: K) -> Result> + where + K: Into, + { + let key = key.into(); + let value = self.get(&key); + + if value.is_null_or_undefined() { + return Ok(None); + } + + match value.as_object() { + Some(object) if object.is_callable() => Ok(Some(object)), + _ => Err(context + .construct_type_error("value returned for property of object is not a function")), + } + } + + /// Determines if `value` inherits from the instance object inheritance path. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance + #[inline] + pub fn ordinary_has_instance(&self, context: &mut Context, value: &Value) -> Result { + if !self.is_callable() { + return Ok(false); + } + + // TODO: If C has a [[BoundTargetFunction]] internal slot, then + // Let BC be C.[[BoundTargetFunction]]. + // Return ? InstanceofOperator(O, BC). + + if let Some(object) = value.as_object() { + if let Some(prototype) = self.get(&"prototype".into()).as_object() { + let mut object = object.get_prototype_of(); + while let Some(object_prototype) = object.as_object() { + if GcObject::equals(&prototype, &object_prototype) { + return Ok(true); + } + object = object_prototype.get_prototype_of(); + } + + Ok(false) + } else { + Err(context + .construct_type_error("function has non-object prototype in instanceof check")) + } + } else { + Ok(false) + } + } } impl AsRef> for GcObject { diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index dd5af7b383..e496d185d3 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -22,6 +22,9 @@ use std::{ ops::{Deref, DerefMut}, }; +#[cfg(test)] +mod tests; + mod gcobject; mod internal_methods; mod iter; diff --git a/boa/src/object/tests.rs b/boa/src/object/tests.rs new file mode 100644 index 0000000000..8cbfe2cefc --- /dev/null +++ b/boa/src/object/tests.rs @@ -0,0 +1,19 @@ +use crate::exec; + +#[test] +fn ordinary_has_instance_nonobject_prototype() { + let scenario = r#" + try { + function C() {} + C.prototype = 1 + String instanceof C + } catch (err) { + err.toString() + } + "#; + + assert_eq!( + &exec(scenario), + "\"TypeError: function has non-object prototype in instanceof check\"" + ); +} 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 62ae6a2297..13d8de3c1f 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -125,17 +125,28 @@ impl Executable for BinOp { interpreter.has_property(&y, &key) } CompOp::InstanceOf => { - if !y.is_object() { + if let Some(object) = y.as_object() { + let key = interpreter.well_known_symbols().has_instance_symbol(); + + match object.get_method(interpreter, key)? { + Some(instance_of_handler) => instance_of_handler + .call(&y, &[x], interpreter)? + .to_boolean(), + None if object.is_callable() => { + object.ordinary_has_instance(interpreter, &x)? + } + None => { + return interpreter.throw_type_error( + "right-hand side of 'instanceof' is not callable", + ); + } + } + } else { return interpreter.throw_type_error(format!( "right-hand side of 'instanceof' should be an object, got {}", y.get_type().as_str() )); } - - // TODO: implement the instanceof operator - // spec: https://tc39.es/ecma262/#sec-instanceofoperator - - false } })) } diff --git a/boa/src/syntax/ast/node/operator/tests.rs b/boa/src/syntax/ast/node/operator/tests.rs index f567b79571..bf5dea8c76 100644 --- a/boa/src/syntax/ast/node/operator/tests.rs +++ b/boa/src/syntax/ast/node/operator/tests.rs @@ -26,3 +26,37 @@ fn assignmentoperator_rhs_throws_error() { assert_eq!(&exec(scenario), "\"ReferenceError: b is not defined\""); } + +#[test] +fn instanceofoperator_rhs_not_object() { + let scenario = r#" + try { + let s = new String(); + s instanceof 1 + } catch (err) { + err.toString() + } + "#; + + assert_eq!( + &exec(scenario), + "\"TypeError: right-hand side of 'instanceof' should be an object, got number\"" + ); +} + +#[test] +fn instanceofoperator_rhs_not_callable() { + let scenario = r#" + try { + let s = new String(); + s instanceof {} + } catch (err) { + err.toString() + } + "#; + + assert_eq!( + &exec(scenario), + "\"TypeError: right-hand side of 'instanceof' is not callable\"" + ); +}