Browse Source

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
pull/1568/head
raskad 3 years ago committed by GitHub
parent
commit
f1b5358834
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      boa/src/context.rs
  2. 8
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  3. 8
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  4. 8
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  5. 14
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  6. 42
      boa/src/syntax/ast/node/statement_list/mod.rs
  7. 118
      boa/src/syntax/ast/node/statement_list/tests.rs
  8. 10
      boa/src/syntax/parser/function/mod.rs
  9. 6
      boa/src/syntax/parser/mod.rs
  10. 24
      boa_tester/src/exec/mod.rs

61
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. /// Javascript context. It is the primary way to interact with the runtime.
/// ///
/// `Context`s constructed in a thread share the same runtime, therefore it /// `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 /// Whether or not to show trace of instructions being ran
pub trace: bool, pub trace: bool,
/// Whether or not strict mode is active.
strict: StrictType,
} }
impl Default for Context { impl Default for Context {
@ -287,6 +298,7 @@ impl Default for Context {
iterator_prototypes: IteratorPrototypes::default(), iterator_prototypes: IteratorPrototypes::default(),
standard_objects: Default::default(), standard_objects: Default::default(),
trace: false, trace: false,
strict: StrictType::Off,
}; };
// Add new builtIns to Context Realm // Add new builtIns to Context Realm
@ -323,6 +335,36 @@ impl Context {
&mut self.console &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 /// Sets up the default global objects within Global
#[inline] #[inline]
fn create_intrinsics(&mut self) { 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 /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function<N, P, B>( pub(crate) fn create_function<N, P>(
&mut self, &mut self,
name: N, name: N,
params: P, params: P,
body: B, mut body: StatementList,
flags: FunctionFlags, flags: FunctionFlags,
) -> JsResult<JsValue> ) -> JsResult<JsValue>
where where
N: Into<JsString>, N: Into<JsString>,
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{ {
let name = name.into(); let name = name.into();
let function_prototype: JsValue = let function_prototype: JsValue =
@ -538,11 +579,16 @@ impl Context {
// Every new function has a prototype property pre-made // Every new function has a prototype property pre-made
let prototype = self.construct_object(); 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 = params.into();
let params_len = params.len(); let params_len = params.len();
let func = Function::Ordinary { let func = Function::Ordinary {
flags, flags,
body: RcStatementList::from(body.into()), body: RcStatementList::from(body),
params, params,
environment: self.get_current_environment().clone(), environment: self.get_current_environment().clone(),
}; };
@ -790,7 +836,12 @@ impl Context {
.map_err(|e| e.to_string()); .map_err(|e| e.to_string());
let execution_result = match parsing_result { 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), Err(e) => self.throw_syntax_error(e),
}; };

8
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -49,8 +49,8 @@ impl ArrowFunctionDecl {
} }
/// Gets the body of the arrow function. /// Gets the body of the arrow function.
pub(crate) fn body(&self) -> &[Node] { pub(crate) fn body(&self) -> &StatementList {
self.body.items() &self.body
} }
/// Implements the display formatting with indentation. /// Implements the display formatting with indentation.
@ -61,7 +61,7 @@ impl ArrowFunctionDecl {
) -> fmt::Result { ) -> fmt::Result {
write!(f, "(")?; write!(f, "(")?;
join_nodes(f, &self.params)?; join_nodes(f, &self.params)?;
if self.body().is_empty() { if self.body().items().is_empty() {
f.write_str(") => {}") f.write_str(") => {}")
} else { } else {
f.write_str(") => {\n")?; f.write_str(") => {\n")?;
@ -76,7 +76,7 @@ impl Executable for ArrowFunctionDecl {
context.create_function( context.create_function(
"", "",
self.params().to_vec(), self.params().to_vec(),
self.body().to_vec(), self.body().clone(),
FunctionFlags::LEXICAL_THIS_MODE, FunctionFlags::LEXICAL_THIS_MODE,
) )
} }

8
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -63,8 +63,8 @@ impl FunctionDecl {
} }
/// Gets the body of the function declaration. /// Gets the body of the function declaration.
pub fn body(&self) -> &[Node] { pub fn body(&self) -> &StatementList {
self.body.items() &self.body
} }
/// Implements the display formatting with indentation. /// Implements the display formatting with indentation.
@ -75,7 +75,7 @@ impl FunctionDecl {
) -> fmt::Result { ) -> fmt::Result {
write!(f, "function {}(", self.name)?; write!(f, "function {}(", self.name)?;
join_nodes(f, &self.parameters)?; join_nodes(f, &self.parameters)?;
if self.body().is_empty() { if self.body().items().is_empty() {
f.write_str(") {}") f.write_str(") {}")
} else { } else {
f.write_str(") {\n")?; f.write_str(") {\n")?;
@ -91,7 +91,7 @@ impl Executable for FunctionDecl {
let val = context.create_function( let val = context.create_function(
self.name(), self.name(),
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().clone(),
FunctionFlags::CONSTRUCTABLE, FunctionFlags::CONSTRUCTABLE,
)?; )?;

8
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -60,8 +60,8 @@ impl FunctionExpr {
} }
/// Gets the body of the function declaration. /// Gets the body of the function declaration.
pub fn body(&self) -> &[Node] { pub fn body(&self) -> &StatementList {
self.body.items() &self.body
} }
/// Implements the display formatting with indentation. /// Implements the display formatting with indentation.
@ -87,7 +87,7 @@ impl FunctionExpr {
f: &mut fmt::Formatter<'_>, f: &mut fmt::Formatter<'_>,
indentation: usize, indentation: usize,
) -> fmt::Result { ) -> fmt::Result {
if self.body().is_empty() { if self.body().items().is_empty() {
f.write_str("{}") f.write_str("{}")
} else { } else {
f.write_str("{\n")?; f.write_str("{\n")?;
@ -102,7 +102,7 @@ impl Executable for FunctionExpr {
let val = context.create_function( let val = context.create_function(
self.name().unwrap_or(""), self.name().unwrap_or(""),
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().clone(),
FunctionFlags::CONSTRUCTABLE, FunctionFlags::CONSTRUCTABLE,
)?; )?;

14
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -90,13 +90,19 @@ impl Executable for UnaryOp {
JsValue::undefined() JsValue::undefined()
} }
op::UnaryOp::Delete => match *self.target() { op::UnaryOp::Delete => match *self.target() {
Node::GetConstField(ref get_const_field) => JsValue::new( Node::GetConstField(ref get_const_field) => {
get_const_field let delete_status = get_const_field
.obj() .obj()
.run(context)? .run(context)?
.to_object(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) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?; let obj = get_field.obj().run(context)?;
let field = &get_field.field().run(context)?; let field = &get_field.field().run(context)?;

42
boa/src/syntax/ast/node/statement_list/mod.rs

@ -1,6 +1,7 @@
//! Statement list node. //! Statement list node.
use crate::{ use crate::{
context::StrictType,
exec::{Executable, InterpreterState}, exec::{Executable, InterpreterState},
gc::{empty_trace, Finalize, Trace}, gc::{empty_trace, Finalize, Trace},
syntax::ast::node::{Declaration, Node}, syntax::ast::node::{Declaration, Node},
@ -11,6 +12,9 @@ use std::{collections::HashSet, fmt, ops::Deref, rc::Rc};
#[cfg(feature = "deser")] #[cfg(feature = "deser")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests;
/// List of statements. /// List of statements.
/// ///
/// Similar to `Node::Block` but without the braces. /// Similar to `Node::Block` but without the braces.
@ -24,14 +28,28 @@ use serde::{Deserialize, Serialize};
pub struct StatementList { pub struct StatementList {
#[cfg_attr(feature = "deser", serde(flatten))] #[cfg_attr(feature = "deser", serde(flatten))]
items: Box<[Node]>, items: Box<[Node]>,
strict: bool,
} }
impl StatementList { impl StatementList {
/// Gets the list of items. /// Gets the list of items.
#[inline]
pub fn items(&self) -> &[Node] { pub fn items(&self) -> &[Node] {
&self.items &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. /// Implements the display formatting with indentation.
pub(in crate::syntax::ast::node) fn display( pub(in crate::syntax::ast::node) fn display(
&self, &self,
@ -121,8 +139,23 @@ impl Executable for StatementList {
context context
.executor() .executor()
.set_current_state(InterpreterState::Executing); .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() { 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() { match context.executor().get_current_state() {
InterpreterState::Return => { InterpreterState::Return => {
// Early return. // Early return.
@ -145,6 +178,8 @@ impl Executable for StatementList {
} }
} }
context.set_strict(strict_before);
Ok(obj) Ok(obj)
} }
} }
@ -154,7 +189,10 @@ where
T: Into<Box<[Node]>>, T: Into<Box<[Node]>>,
{ {
fn from(stm: T) -> Self { fn from(stm: T) -> Self {
Self { items: stm.into() } Self {
items: stm.into(),
strict: false,
}
} }
} }

118
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");
}

10
boa/src/syntax/parser/function/mod.rs

@ -267,6 +267,8 @@ where
let _timer = BoaProfiler::global().start_event("FunctionStatementList", "Parsing"); let _timer = BoaProfiler::global().start_event("FunctionStatementList", "Parsing");
let global_strict_mode = cursor.strict_mode(); let global_strict_mode = cursor.strict_mode();
let mut strict = false;
if let Some(tk) = cursor.peek(0)? { if let Some(tk) = cursor.peek(0)? {
match tk.kind() { match tk.kind() {
TokenKind::Punctuator(Punctuator::CloseBlock) => { TokenKind::Punctuator(Punctuator::CloseBlock) => {
@ -274,12 +276,13 @@ where
} }
TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => { TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => {
cursor.set_strict_mode(true); cursor.set_strict_mode(true);
strict = true;
} }
_ => {} _ => {}
} }
} }
let stmlist = StatementList::new( let statement_list = StatementList::new(
self.allow_yield, self.allow_yield,
self.allow_await, self.allow_await,
true, true,
@ -290,6 +293,9 @@ where
// Reset strict mode back to the global scope. // Reset strict mode back to the global scope.
cursor.set_strict_mode(global_strict_mode); cursor.set_strict_mode(global_strict_mode);
stmlist
let mut statement_list = statement_list?;
statement_list.set_strict(strict);
Ok(statement_list)
} }
} }

6
boa/src/syntax/parser/mod.rs

@ -124,13 +124,17 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> { fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
match cursor.peek(0)? { match cursor.peek(0)? {
Some(tok) => { Some(tok) => {
let mut strict = false;
match tok.kind() { match tok.kind() {
TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => { TokenKind::StringLiteral(string) if string.as_ref() == "use strict" => {
cursor.set_strict_mode(true); 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())), None => Ok(StatementList::from(Vec::new())),
} }

24
boa_tester/src/exec/mod.rs

@ -139,6 +139,9 @@ impl Test {
match self.set_up_env(harness, strict) { match self.set_up_env(harness, strict) {
Ok(mut context) => { Ok(mut context) => {
if strict {
context.set_strict_mode_global();
}
let res = context.eval(&self.content.as_ref()); let res = context.eval(&self.content.as_ref());
let passed = res.is_ok(); let passed = res.is_ok();
@ -184,15 +187,20 @@ impl Test {
(false, format!("Uncaught {}", e)) (false, format!("Uncaught {}", e))
} else { } else {
match self.set_up_env(harness, strict) { match self.set_up_env(harness, strict) {
Ok(mut context) => match context.eval(&self.content.as_ref()) { Ok(mut context) => {
Ok(res) => (false, format!("{}", res.display())), if strict {
Err(e) => { context.set_strict_mode_global();
let passed = }
e.display().to_string().contains(error_type.as_ref()); match context.eval(&self.content.as_ref()) {
Ok(res) => (false, format!("{}", res.display())),
(passed, format!("Uncaught {}", e.display())) Err(e) => {
let passed =
e.display().to_string().contains(error_type.as_ref());
(passed, format!("Uncaught {}", e.display()))
}
} }
}, }
Err(e) => (false, e), Err(e) => (false, e),
} }
} }

Loading…
Cancel
Save