From c58a8997ca347448100541c440e695626e29a30d Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Sun, 25 Sep 2022 19:43:43 +0000 Subject: [PATCH] Implement `new.target` expression (#2299) This Pull Request changes the following: - Implement `new.target` expression --- boa_engine/src/bytecompiler/mod.rs | 5 +++ boa_engine/src/syntax/ast/node/mod.rs | 6 ++++ .../expression/left_hand_side/member.rs | 16 +++++++++- boa_engine/src/syntax/parser/mod.rs | 31 ++++++++++++++++--- boa_engine/src/vm/code_block.rs | 1 + boa_engine/src/vm/mod.rs | 16 ++++++++++ boa_engine/src/vm/opcode.rs | 9 ++++++ boa_interner/src/sym.rs | 4 +++ 8 files changed, 83 insertions(+), 5 deletions(-) diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index b176bd7dbc..f7faef4243 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -1301,6 +1301,11 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::Pop); } } + Node::NewTarget => { + if use_expr { + self.emit_opcode(Opcode::PushNewTarget); + } + } _ => unreachable!(), } Ok(()) diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index d6a98b48cf..f564880171 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -250,6 +250,9 @@ pub enum Node { /// A call of the super constructor. [More information](./super_call/struct.SuperCall.html). SuperCall(SuperCall), + /// The `new.target` pseudo-property expression. + NewTarget, + /// A FormalParameterList. /// /// This is only used in the parser itself. @@ -364,6 +367,7 @@ impl Node { Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation), Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation), Self::SuperCall(ref super_call) => super_call.to_interned_string(interner), + Self::NewTarget => "new.target".to_owned(), Self::FormalParameterList(_) => unreachable!(), } } @@ -1248,6 +1252,7 @@ impl Node { } } Node::Yield(_) if symbol == ContainsSymbol::YieldExpression => return true, + Node::NewTarget if symbol == ContainsSymbol::NewTarget => return true, _ => {} } false @@ -1261,6 +1266,7 @@ pub(crate) enum ContainsSymbol { SuperCall, YieldExpression, AwaitExpression, + NewTarget, } impl ToInternedString for Node { diff --git a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs b/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs index 61ccb79914..a62981512f 100644 --- a/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs +++ b/boa_engine/src/syntax/parser/expression/left_hand_side/member.rs @@ -73,7 +73,21 @@ where )); } TokenKind::Keyword((Keyword::New, false)) => { - let _next = cursor.next(interner).expect("new keyword disappeared"); + cursor.next(interner).expect("token disappeared"); + + if cursor.next_if(Punctuator::Dot, interner)?.is_some() { + let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?; + match token.kind() { + TokenKind::Identifier(Sym::TARGET) => return Ok(Node::NewTarget), + _ => { + return Err(ParseError::general( + "unexpected private identifier", + token.span().start(), + )); + } + } + } + let lhs = self.parse(cursor, interner)?; let args = match cursor.peek(0, interner)? { Some(next) if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => { diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index 03091989b8..b7f9a7d00b 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/boa_engine/src/syntax/parser/mod.rs @@ -146,7 +146,7 @@ impl Parser { where R: Read, { - let (in_method, in_derived_constructor) = if let Some(function_env) = context + let (in_function, in_method, in_derived_constructor) = if let Some(function_env) = context .realm .environments .get_this_environment() @@ -156,6 +156,7 @@ impl Parser { let has_super_binding = function_env_borrow.has_super_binding(); let function_object = function_env_borrow.function_object().borrow(); ( + true, has_super_binding, function_object .as_function() @@ -163,13 +164,14 @@ impl Parser { .is_derived_constructor(), ) } else { - (false, false) + (false, false, false) }; let statement_list = Script::new(direct).parse(&mut self.cursor, context)?; let mut contains_super_property = false; let mut contains_super_call = false; + let mut contains_new_target = false; if direct { for node in statement_list.items() { if !contains_super_property && node.contains(ContainsSymbol::SuperProperty) { @@ -179,9 +181,19 @@ impl Parser { if !contains_super_call && node.contains(ContainsSymbol::SuperCall) { contains_super_call = true; } + + if !contains_new_target && node.contains(ContainsSymbol::NewTarget) { + contains_new_target = true; + } } } + if !in_function && contains_new_target { + return Err(ParseError::general( + "invalid new.target usage", + Position::new(1, 1), + )); + } if !in_method && contains_super_property { return Err(ParseError::general( "invalid super usage", @@ -365,10 +377,11 @@ where let body = self::statement::StatementList::new(false, false, false, &[]) .parse(cursor, interner)?; - // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval code that is being processed by a direct eval. - // Additional early error rules for super within direct eval are defined in 19.2.1.1. if !self.direct_eval { for node in body.items() { + // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval + // code that is being processed by a direct eval. + // Additional early error rules for super within direct eval are defined in 19.2.1.1. if node.contains(ContainsSymbol::SuperCall) || node.contains(ContainsSymbol::SuperProperty) { @@ -377,6 +390,16 @@ where Position::new(1, 1), )); } + + // It is a Syntax Error if StatementList Contains NewTarget unless the source text containing NewTarget + // is eval code that is being processed by a direct eval. + // Additional early error rules for NewTarget in direct eval are defined in 19.2.1.1. + if node.contains(ContainsSymbol::NewTarget) { + return Err(ParseError::general( + "invalid new.target usage", + Position::new(1, 1), + )); + } } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index b57c6005be..7f527c8685 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -371,6 +371,7 @@ impl CodeBlock { | Opcode::PushClassField | Opcode::SuperCallDerived | Opcode::Await + | Opcode::PushNewTarget | Opcode::CallEvalSpread | Opcode::CallSpread | Opcode::NewSpread diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 161ec8439a..1b6d516ec2 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -2501,6 +2501,22 @@ impl Context { self.vm.push(JsValue::undefined()); return Ok(ShouldExit::Await); } + Opcode::PushNewTarget => { + if let Some(env) = self + .realm + .environments + .get_this_environment() + .as_function_slots() + { + if let Some(new_target) = env.borrow().new_target() { + self.vm.push(new_target.clone()); + } else { + self.vm.push(JsValue::undefined()); + } + } else { + self.vm.push(JsValue::undefined()); + } + } } Ok(ShouldExit::False) diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index e70767dc5d..dde32d9cb8 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -1183,6 +1183,13 @@ pub enum Opcode { /// Stack: promise **=>** Await, + /// Push the current new target to the stack. + /// + /// Operands: + /// + /// Stack: **=>** new_target + PushNewTarget, + /// No-operation instruction, does nothing. /// /// Operands: @@ -1364,6 +1371,7 @@ impl Opcode { Self::GeneratorNext => "GeneratorNext", Self::AsyncGeneratorNext => "AsyncGeneratorNext", Self::Await => "Await", + Self::PushNewTarget => "PushNewTarget", Self::GeneratorNextDelegate => "GeneratorNextDelegate", Self::Nop => "Nop", } @@ -1509,6 +1517,7 @@ impl Opcode { Self::Yield => "INST - Yield", Self::GeneratorNext => "INST - GeneratorNext", Self::AsyncGeneratorNext => "INST - AsyncGeneratorNext", + Self::PushNewTarget => "INST - PushNewTarget", Self::Await => "INST - Await", Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", Self::Nop => "INST - Nop", diff --git a/boa_interner/src/sym.rs b/boa_interner/src/sym.rs index c8150e3f4d..fa30cd2613 100644 --- a/boa_interner/src/sym.rs +++ b/boa_interner/src/sym.rs @@ -97,6 +97,9 @@ impl Sym { /// Symbol for the `"of"` string. pub const OF: Self = unsafe { Self::new_unchecked(27) }; + /// Symbol for the `"target"` string. + pub const TARGET: Self = unsafe { Self::new_unchecked(28) }; + /// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero. #[inline] pub(super) fn new(value: usize) -> Option { @@ -161,6 +164,7 @@ pub(super) static COMMON_STRINGS: phf::OrderedSet<&'static str> = { "false", "async", "of", + "target", }; // A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner` sa::const_assert!(COMMON_STRINGS.len() < usize::MAX);