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.
///
/// `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<N, P, B>(
pub(crate) fn create_function<N, P>(
&mut self,
name: N,
params: P,
body: B,
mut body: StatementList,
flags: FunctionFlags,
) -> JsResult<JsValue>
where
N: Into<JsString>,
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
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),
};

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.
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,
)
}

8
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,
)?;

8
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,
)?;

14
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)?;

42
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<Box<[Node]>>,
{
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 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)
}
}

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

@ -124,13 +124,17 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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())),
}

24
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),
}
}

Loading…
Cancel
Save