From f1b53588345632f5e037496dd9ea1b16a117d977 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Thu, 9 Sep 2021 15:59:01 +0200 Subject: [PATCH] Add strict mode flag to `Context` (#1550) * Add strict mode flag to `Context` * Add strict mode handling of `delete` * Add strict mode test * Handle non-strict functions in strict functions * Enable strict mode for functions defined in a strict context --- boa/src/context.rs | 61 ++++++++- .../declaration/arrow_function_decl/mod.rs | 8 +- .../ast/node/declaration/function_decl/mod.rs | 8 +- .../ast/node/declaration/function_expr/mod.rs | 8 +- .../syntax/ast/node/operator/unary_op/mod.rs | 14 ++- boa/src/syntax/ast/node/statement_list/mod.rs | 42 ++++++- .../syntax/ast/node/statement_list/tests.rs | 118 ++++++++++++++++++ boa/src/syntax/parser/function/mod.rs | 10 +- boa/src/syntax/parser/mod.rs | 6 +- boa_tester/src/exec/mod.rs | 24 ++-- 10 files changed, 265 insertions(+), 34 deletions(-) create mode 100644 boa/src/syntax/ast/node/statement_list/tests.rs diff --git a/boa/src/context.rs b/boa/src/context.rs index c7da7d06a5..5cb0792cee 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -212,6 +212,14 @@ impl StandardObjects { } } +/// Internal representation of the strict mode types. +#[derive(Debug, Copy, Clone)] +pub(crate) enum StrictType { + Off, + Global, + Function, +} + /// Javascript context. It is the primary way to interact with the runtime. /// /// `Context`s constructed in a thread share the same runtime, therefore it @@ -273,6 +281,9 @@ pub struct Context { /// Whether or not to show trace of instructions being ran pub trace: bool, + + /// Whether or not strict mode is active. + strict: StrictType, } impl Default for Context { @@ -287,6 +298,7 @@ impl Default for Context { iterator_prototypes: IteratorPrototypes::default(), standard_objects: Default::default(), trace: false, + strict: StrictType::Off, }; // Add new builtIns to Context Realm @@ -323,6 +335,36 @@ impl Context { &mut self.console } + /// Returns if strict mode is currently active. + #[inline] + pub fn strict(&self) -> bool { + matches!(self.strict, StrictType::Global | StrictType::Function) + } + + /// Returns the strict mode type. + #[inline] + pub(crate) fn strict_type(&self) -> StrictType { + self.strict + } + + /// Set strict type. + #[inline] + pub(crate) fn set_strict(&mut self, strict: StrictType) { + self.strict = strict; + } + + /// Disable the strict mode. + #[inline] + pub fn set_strict_mode_off(&mut self) { + self.strict = StrictType::Off; + } + + /// Enable the global strict mode. + #[inline] + pub fn set_strict_mode_global(&mut self) { + self.strict = StrictType::Global; + } + /// Sets up the default global objects within Global #[inline] fn create_intrinsics(&mut self) { @@ -519,17 +561,16 @@ impl Context { } /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions - pub(crate) fn create_function( + pub(crate) fn create_function( &mut self, name: N, params: P, - body: B, + mut body: StatementList, flags: FunctionFlags, ) -> JsResult where N: Into, P: Into>, - B: Into, { let name = name.into(); let function_prototype: JsValue = @@ -538,11 +579,16 @@ impl Context { // Every new function has a prototype property pre-made let prototype = self.construct_object(); + // If a function is defined within a strict context, it is strict. + if self.strict() { + body.set_strict(true); + } + let params = params.into(); let params_len = params.len(); let func = Function::Ordinary { flags, - body: RcStatementList::from(body.into()), + body: RcStatementList::from(body), params, environment: self.get_current_environment().clone(), }; @@ -790,7 +836,12 @@ impl Context { .map_err(|e| e.to_string()); let execution_result = match parsing_result { - Ok(statement_list) => statement_list.run(self), + Ok(statement_list) => { + if statement_list.strict() { + self.set_strict_mode_global(); + } + statement_list.run(self) + } Err(e) => self.throw_syntax_error(e), }; diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 3e9e9f825e..64a460225e 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -49,8 +49,8 @@ impl ArrowFunctionDecl { } /// Gets the body of the arrow function. - pub(crate) fn body(&self) -> &[Node] { - self.body.items() + pub(crate) fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -61,7 +61,7 @@ impl ArrowFunctionDecl { ) -> fmt::Result { write!(f, "(")?; join_nodes(f, &self.params)?; - if self.body().is_empty() { + if self.body().items().is_empty() { f.write_str(") => {}") } else { f.write_str(") => {\n")?; @@ -76,7 +76,7 @@ impl Executable for ArrowFunctionDecl { context.create_function( "", self.params().to_vec(), - self.body().to_vec(), + self.body().clone(), FunctionFlags::LEXICAL_THIS_MODE, ) } 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 b3c39c17bc..1d2fe9fc7c 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -63,8 +63,8 @@ impl FunctionDecl { } /// Gets the body of the function declaration. - pub fn body(&self) -> &[Node] { - self.body.items() + pub fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -75,7 +75,7 @@ impl FunctionDecl { ) -> fmt::Result { write!(f, "function {}(", self.name)?; join_nodes(f, &self.parameters)?; - if self.body().is_empty() { + if self.body().items().is_empty() { f.write_str(") {}") } else { f.write_str(") {\n")?; @@ -91,7 +91,7 @@ impl Executable for FunctionDecl { let val = context.create_function( self.name(), self.parameters().to_vec(), - self.body().to_vec(), + self.body().clone(), FunctionFlags::CONSTRUCTABLE, )?; diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index 77472fec75..57833ccbfd 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -60,8 +60,8 @@ impl FunctionExpr { } /// Gets the body of the function declaration. - pub fn body(&self) -> &[Node] { - self.body.items() + pub fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -87,7 +87,7 @@ impl FunctionExpr { f: &mut fmt::Formatter<'_>, indentation: usize, ) -> fmt::Result { - if self.body().is_empty() { + if self.body().items().is_empty() { f.write_str("{}") } else { f.write_str("{\n")?; @@ -102,7 +102,7 @@ impl Executable for FunctionExpr { let val = context.create_function( self.name().unwrap_or(""), self.parameters().to_vec(), - self.body().to_vec(), + self.body().clone(), FunctionFlags::CONSTRUCTABLE, )?; diff --git a/boa/src/syntax/ast/node/operator/unary_op/mod.rs b/boa/src/syntax/ast/node/operator/unary_op/mod.rs index 051a66ce99..0153025f30 100644 --- a/boa/src/syntax/ast/node/operator/unary_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/unary_op/mod.rs @@ -90,13 +90,19 @@ impl Executable for UnaryOp { JsValue::undefined() } op::UnaryOp::Delete => match *self.target() { - Node::GetConstField(ref get_const_field) => JsValue::new( - get_const_field + Node::GetConstField(ref get_const_field) => { + let delete_status = get_const_field .obj() .run(context)? .to_object(context)? - .__delete__(&get_const_field.field().into(), context)?, - ), + .__delete__(&get_const_field.field().into(), context)?; + + if !delete_status && context.strict() { + return context.throw_type_error("Cannot delete property"); + } else { + JsValue::new(delete_status) + } + } Node::GetField(ref get_field) => { let obj = get_field.obj().run(context)?; let field = &get_field.field().run(context)?; diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index a6d70f85db..5d9c138d73 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -1,6 +1,7 @@ //! Statement list node. use crate::{ + context::StrictType, exec::{Executable, InterpreterState}, gc::{empty_trace, Finalize, Trace}, syntax::ast::node::{Declaration, Node}, @@ -11,6 +12,9 @@ use std::{collections::HashSet, fmt, ops::Deref, rc::Rc}; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// List of statements. /// /// Similar to `Node::Block` but without the braces. @@ -24,14 +28,28 @@ use serde::{Deserialize, Serialize}; pub struct StatementList { #[cfg_attr(feature = "deser", serde(flatten))] items: Box<[Node]>, + strict: bool, } impl StatementList { /// Gets the list of items. + #[inline] pub fn items(&self) -> &[Node] { &self.items } + /// Get the strict mode. + #[inline] + pub fn strict(&self) -> bool { + self.strict + } + + /// Set the strict mode. + #[inline] + pub fn set_strict(&mut self, strict: bool) { + self.strict = strict; + } + /// Implements the display formatting with indentation. pub(in crate::syntax::ast::node) fn display( &self, @@ -121,8 +139,23 @@ impl Executable for StatementList { context .executor() .set_current_state(InterpreterState::Executing); + + let strict_before = context.strict_type(); + + match context.strict_type() { + StrictType::Off if self.strict => context.set_strict(StrictType::Function), + StrictType::Function if !self.strict => context.set_strict_mode_off(), + _ => {} + } + for (i, item) in self.items().iter().enumerate() { - let val = item.run(context)?; + let val = match item.run(context) { + Ok(val) => val, + Err(e) => { + context.set_strict(strict_before); + return Err(e); + } + }; match context.executor().get_current_state() { InterpreterState::Return => { // Early return. @@ -145,6 +178,8 @@ impl Executable for StatementList { } } + context.set_strict(strict_before); + Ok(obj) } } @@ -154,7 +189,10 @@ where T: Into>, { fn from(stm: T) -> Self { - Self { items: stm.into() } + Self { + items: stm.into(), + strict: false, + } } } diff --git a/boa/src/syntax/ast/node/statement_list/tests.rs b/boa/src/syntax/ast/node/statement_list/tests.rs new file mode 100644 index 0000000000..71df252646 --- /dev/null +++ b/boa/src/syntax/ast/node/statement_list/tests.rs @@ -0,0 +1,118 @@ +use crate::exec; + +#[test] +fn strict_mode_global() { + let scenario = r#" + 'use strict'; + let throws = false; + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + throws + "#; + + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn strict_mode_function() { + let scenario = r#" + let throws = false; + function t() { + 'use strict'; + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + } + t() + throws + "#; + + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn strict_mode_function_after() { + let scenario = r#" + function t() { + 'use strict'; + } + t() + let throws = false; + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + throws + "#; + + assert_eq!(&exec(scenario), "false"); +} + +#[test] +fn strict_mode_global_active_in_function() { + let scenario = r#" + 'use strict' + let throws = false; + function a(){ + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + } + a(); + throws + "#; + + assert_eq!(&exec(scenario), "true"); +} + +#[test] +fn strict_mode_function_in_function() { + let scenario = r#" + let throws = false; + function a(){ + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + } + function b(){ + 'use strict'; + a(); + } + b(); + throws + "#; + + assert_eq!(&exec(scenario), "false"); +} + +#[test] +fn strict_mode_function_return() { + let scenario = r#" + let throws = false; + function a() { + 'use strict'; + + return function () { + try { + delete Boolean.prototype; + } catch (e) { + throws = true; + } + } + } + a()(); + throws + "#; + + assert_eq!(&exec(scenario), "true"); +} diff --git a/boa/src/syntax/parser/function/mod.rs b/boa/src/syntax/parser/function/mod.rs index abc2f780cc..7367f6dbd9 100644 --- a/boa/src/syntax/parser/function/mod.rs +++ b/boa/src/syntax/parser/function/mod.rs @@ -267,6 +267,8 @@ where let _timer = BoaProfiler::global().start_event("FunctionStatementList", "Parsing"); let global_strict_mode = cursor.strict_mode(); + let mut strict = false; + if let Some(tk) = cursor.peek(0)? { match tk.kind() { TokenKind::Punctuator(Punctuator::CloseBlock) => { @@ -274,12 +276,13 @@ where } TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => { cursor.set_strict_mode(true); + strict = true; } _ => {} } } - let stmlist = StatementList::new( + let statement_list = StatementList::new( self.allow_yield, self.allow_await, true, @@ -290,6 +293,9 @@ where // Reset strict mode back to the global scope. cursor.set_strict_mode(global_strict_mode); - stmlist + + let mut statement_list = statement_list?; + statement_list.set_strict(strict); + Ok(statement_list) } } diff --git a/boa/src/syntax/parser/mod.rs b/boa/src/syntax/parser/mod.rs index 3058cf5501..2a06f7afdd 100644 --- a/boa/src/syntax/parser/mod.rs +++ b/boa/src/syntax/parser/mod.rs @@ -124,13 +124,17 @@ where fn parse(self, cursor: &mut Cursor) -> Result { match cursor.peek(0)? { Some(tok) => { + let mut strict = false; match tok.kind() { TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => { cursor.set_strict_mode(true); + strict = true; } _ => {} } - ScriptBody.parse(cursor) + let mut statement_list = ScriptBody.parse(cursor)?; + statement_list.set_strict(strict); + Ok(statement_list) } None => Ok(StatementList::from(Vec::new())), } diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 8ee29a790a..b77b617678 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -139,6 +139,9 @@ impl Test { match self.set_up_env(harness, strict) { Ok(mut context) => { + if strict { + context.set_strict_mode_global(); + } let res = context.eval(&self.content.as_ref()); let passed = res.is_ok(); @@ -184,15 +187,20 @@ impl Test { (false, format!("Uncaught {}", e)) } else { match self.set_up_env(harness, strict) { - Ok(mut context) => match context.eval(&self.content.as_ref()) { - Ok(res) => (false, format!("{}", res.display())), - Err(e) => { - let passed = - e.display().to_string().contains(error_type.as_ref()); - - (passed, format!("Uncaught {}", e.display())) + Ok(mut context) => { + if strict { + context.set_strict_mode_global(); + } + match context.eval(&self.content.as_ref()) { + Ok(res) => (false, format!("{}", res.display())), + Err(e) => { + let passed = + e.display().to_string().contains(error_type.as_ref()); + + (passed, format!("Uncaught {}", e.display())) + } } - }, + } Err(e) => (false, e), } }