Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow local parameters if mapped arguments object is not used #4092

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/ast/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ pub struct FunctionScopes {
pub(crate) parameters_eval_scope: Option<Scope>,
pub(crate) parameters_scope: Option<Scope>,
pub(crate) lexical_scope: Option<Scope>,
pub(crate) mapped_arguments_object: bool,
}

impl FunctionScopes {
Expand Down Expand Up @@ -701,6 +702,7 @@ impl<'a> arbitrary::Arbitrary<'a> for FunctionScopes {
parameters_eval_scope: None,
parameters_scope: None,
lexical_scope: None,
mapped_arguments_object: false,
})
}
}
11 changes: 10 additions & 1 deletion core/ast/src/scope_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,14 @@ impl BindingEscapeAnalyzer<'_> {
std::mem::swap(&mut self.scope, &mut scope);
try_break!(self.visit_function_body_mut(body));
std::mem::swap(&mut self.scope, &mut scope);
if scopes.arguments_object_accessed() && scopes.mapped_arguments_object {
let parameter_names = bound_names(parameters);
for name in parameter_names {
scopes
.parameter_scope()
.access_binding(&name.to_js_string(self.interner), true);
}
}
scopes.reorder_binding_indices();
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
Expand Down Expand Up @@ -1813,6 +1821,7 @@ fn function_declaration_instantiation(
parameters_eval_scope: None,
parameters_scope: None,
lexical_scope: None,
mapped_arguments_object: false,
};

// 1. Let calleeContext be the running execution context.
Expand Down Expand Up @@ -1948,7 +1957,7 @@ fn function_declaration_instantiation(
// Because we do not track (yet) if the mapped arguments object escapes the function,
// we have to assume that the binding might escape trough the arguments object.
if arguments_object_needed && !strict && formals.is_simple() {
env.access_binding(&param_name, true);
scopes.mapped_arguments_object = true;
}

// Note: These steps are not necessary in our implementation.
Expand Down
239 changes: 239 additions & 0 deletions core/ast/tests/scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! Tests for the scope analysis of the AST.

#![allow(unused_crate_dependencies)]

use boa_ast::{
declaration::{LexicalDeclaration, Variable},
expression::Identifier,
function::{FormalParameter, FormalParameterList, FunctionBody, FunctionDeclaration},
scope::Scope,
statement::Block,
Declaration, Expression, Script, Statement, StatementList, StatementListItem,
};
use boa_interner::Interner;
use boa_string::JsString;

#[test]
fn empty_script_is_ok() {
let scope = &Scope::new_global();
let mut script = Script::default();
let ok = script.analyze_scope(scope, &Interner::new());
assert!(ok);
assert_eq!(scope.num_bindings(), 0);
}

#[test]
fn script_global_let() {
let scope = Scope::new_global();
let mut interner = Interner::new();
let a = interner.get_or_intern("a");
let mut script = Script::new(StatementList::new(
[Declaration::Lexical(LexicalDeclaration::Let(
vec![Variable::from_identifier(a.into(), None)]
.try_into()
.unwrap(),
))
.into()],
false,
));
let ok = script.analyze_scope(&scope, &interner);
assert!(ok);
assert_eq!(scope.num_bindings(), 1);
let a = scope.get_binding_reference(&JsString::from("a")).unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(!a.local());
}

#[test]
fn script_global_const() {
let scope = Scope::new_global();
let mut interner = Interner::new();
let a = interner.get_or_intern("a");
let mut script = Script::new(StatementList::new(
[Declaration::Lexical(LexicalDeclaration::Const(
vec![Variable::from_identifier(a.into(), None)]
.try_into()
.unwrap(),
))
.into()],
false,
));
let ok = script.analyze_scope(&scope, &interner);
assert!(ok);
assert_eq!(scope.num_bindings(), 1);
let a = scope.get_binding_reference(&JsString::from("a")).unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(!a.local());
}

#[test]
fn script_block_let() {
let scope = Scope::new_global();
let mut interner = Interner::new();
let a = interner.get_or_intern("a");
let mut script = Script::new(StatementList::new(
[Statement::Block(Block::from(vec![Declaration::Lexical(
LexicalDeclaration::Let(
vec![Variable::from_identifier(a.into(), None)]
.try_into()
.unwrap(),
),
)
.into()]))
.into()],
false,
));
let ok = script.analyze_scope(&scope, &interner);
assert!(ok);
assert_eq!(scope.num_bindings(), 0);
let StatementListItem::Statement(Statement::Block(block)) =
script.statements().first().unwrap()
else {
panic!("Expected a block statement");
};
let scope = block.scope().unwrap();
assert_eq!(scope.num_bindings(), 1);
let a = scope.get_binding_reference(&JsString::from("a")).unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(a.local());
}

#[test]
fn script_function_mapped_arguments_not_accessed() {
let scope = Scope::new_global();
let mut interner = Interner::new();
let f = interner.get_or_intern("f");
let a = interner.get_or_intern("a");
let mut script = Script::new(StatementList::new(
[Declaration::FunctionDeclaration(FunctionDeclaration::new(
f.into(),
FormalParameterList::from_parameters(vec![FormalParameter::new(
Variable::from_identifier(a.into(), None),
false,
)]),
FunctionBody::new(
[Declaration::Lexical(LexicalDeclaration::Let(
vec![Variable::from_identifier(a.into(), None)]
.try_into()
.unwrap(),
))
.into()],
false,
),
))
.into()],
false,
));
let ok = script.analyze_scope(&scope, &interner);
assert!(ok);
assert_eq!(scope.num_bindings(), 0);
let StatementListItem::Declaration(Declaration::FunctionDeclaration(f)) =
script.statements().first().unwrap()
else {
panic!("Expected a block statement");
};
assert_eq!(f.scopes().function_scope().num_bindings(), 2);
assert_eq!(f.scopes().parameters_eval_scope(), None);
assert_eq!(f.scopes().parameters_scope(), None);
assert_eq!(f.scopes().lexical_scope().unwrap().num_bindings(), 1);
let arguments = f
.scopes()
.function_scope()
.get_binding_reference(&JsString::from("arguments"))
.unwrap();
assert!(!f.scopes().arguments_object_accessed());
assert!(!arguments.is_global_object());
assert!(arguments.is_lexical());
assert!(arguments.local());
let a = f
.scopes()
.function_scope()
.get_binding_reference(&JsString::from("a"))
.unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(a.local());
let a = f
.scopes()
.lexical_scope()
.unwrap()
.get_binding_reference(&JsString::from("a"))
.unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(a.local());
}

#[test]
fn script_function_mapped_arguments_accessed() {
let scope = Scope::new_global();
let mut interner = Interner::new();
let f = interner.get_or_intern("f");
let a = interner.get_or_intern("a");
let arguments = interner.get_or_intern("arguments");
let mut script = Script::new(StatementList::new(
[Declaration::FunctionDeclaration(FunctionDeclaration::new(
f.into(),
FormalParameterList::from_parameters(vec![FormalParameter::new(
Variable::from_identifier(a.into(), None),
false,
)]),
FunctionBody::new(
[
Declaration::Lexical(LexicalDeclaration::Let(
vec![Variable::from_identifier(a.into(), None)]
.try_into()
.unwrap(),
))
.into(),
Statement::Expression(Expression::Identifier(Identifier::new(arguments)))
.into(),
],
false,
),
))
.into()],
false,
));
let ok = script.analyze_scope(&scope, &interner);
assert!(ok);
assert_eq!(scope.num_bindings(), 0);
let StatementListItem::Declaration(Declaration::FunctionDeclaration(f)) =
script.statements().first().unwrap()
else {
panic!("Expected a block statement");
};
assert!(f.scopes().arguments_object_accessed());
assert_eq!(f.scopes().function_scope().num_bindings(), 2);
assert_eq!(f.scopes().parameters_eval_scope(), None);
assert_eq!(f.scopes().parameters_scope(), None);
assert_eq!(f.scopes().lexical_scope().unwrap().num_bindings(), 1);
let arguments = f
.scopes()
.function_scope()
.get_binding_reference(&JsString::from("arguments"))
.unwrap();
assert!(!arguments.is_global_object());
assert!(arguments.is_lexical());
assert!(arguments.local());
let a = f
.scopes()
.function_scope()
.get_binding_reference(&JsString::from("a"))
.unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(!a.local());
let a = f
.scopes()
.lexical_scope()
.unwrap()
.get_binding_reference(&JsString::from("a"))
.unwrap();
assert!(!a.is_global_object());
assert!(a.is_lexical());
assert!(a.local());
}
Loading