LibJS: Fix catch parameter and new.target regressions

- Restrict catch parameter conflict check to only direct children
  of the catch body block, not nested scopes
- Set new_target_is_valid for dynamic function compilation (new
  Function)
- Move check_parameters_post_body before flag restoration in
  parse_method_definition so generator methods inside static init
  blocks correctly allow 'await' as a parameter name
This commit is contained in:
Andreas Kling 2026-03-19 20:53:18 -05:00 committed by Andreas Kling
parent 5374f0a85c
commit f5eea4d232
5 changed files with 69 additions and 41 deletions

View file

@ -904,6 +904,7 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
};
let mut parser = Parser::new(body_slice, ProgramType::Script);
parser.flags.in_function_context = true;
parser.flags.new_target_is_valid = true;
match kind {
ast::FunctionKind::Async | ast::FunctionKind::AsyncGenerator => {
parser.flags.await_expression_is_valid = true;

View file

@ -311,10 +311,6 @@ pub struct Parser<'a> {
/// re-attempt inner positions during grouping expression re-parse.
arrow_function_failed_positions: HashSet<usize>,
/// Catch parameter names used to detect redeclarations in catch body.
/// Set while parsing a catch clause body, empty otherwise.
catch_parameter_names: Vec<Utf16String>,
/// Regex literals whose compilation is deferred until after parsing.
deferred_regexes: Vec<DeferredRegex>,
}
@ -364,7 +360,6 @@ impl<'a> Parser<'a> {
scope_collector: ScopeCollector::new(),
exported_names: HashSet::new(),
function_table: FunctionTable::new(),
catch_parameter_names: Vec::new(),
arrow_function_failed_positions: HashSet::new(),
deferred_regexes: Vec::new(),
}
@ -873,20 +868,6 @@ impl<'a> Parser<'a> {
}
}
/// Check if a name conflicts with a catch clause parameter.
/// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
pub(crate) fn check_catch_parameter_conflict(&mut self, name: &[u16]) {
for catch_name in &self.catch_parameter_names {
if catch_name.as_slice() == name {
let name_str = String::from_utf16_lossy(name);
self.syntax_error(&format!(
"Identifier '{name_str}' already declared as catch parameter"
));
return;
}
}
}
/// Check for duplicate parameter names in arrow functions.
/// Arrow functions always reject duplicates, regardless of strict mode.
pub(crate) fn check_arrow_duplicate_parameters(&mut self, parameter_info: &[ParamInfo]) {

View file

@ -164,7 +164,6 @@ impl Parser<'_> {
);
self.scope_collector
.register_identifier(id.clone(), &value, Some(kind));
self.check_catch_parameter_conflict(&value);
}
VariableDeclaratorTarget::Identifier(id)
@ -218,7 +217,6 @@ impl Parser<'_> {
for (name, id) in &bound_names {
self.scope_collector
.register_identifier(id.clone(), name, None);
self.check_catch_parameter_conflict(name);
}
}
@ -415,7 +413,6 @@ impl Parser<'_> {
declaration_line,
declaration_column,
);
self.check_catch_parameter_conflict(&fn_name);
let fn_name_for_scope = if fn_name.is_empty() {
None

View file

@ -2597,14 +2597,17 @@ impl Parser<'_> {
self.scope_collector.close_scope();
self.pattern_bound_names = saved_pattern_bound_names;
self.flags.in_class_static_init_block = saved_static_init;
self.flags.in_class_field_initializer = saved_field_init;
self.flags.new_target_is_valid = saved_new_target;
// Check parameters before restoring flags so that the method's
// context is used (e.g. in_class_static_init_block must remain
// false to allow `await` as a parameter name in generators).
if has_use_strict || fn_kind != FunctionKind::Normal {
self.check_parameters_post_body(&parsed.parameter_info, has_use_strict, fn_kind);
}
self.flags.in_class_static_init_block = saved_static_init;
self.flags.in_class_field_initializer = saved_field_init;
self.flags.new_target_is_valid = saved_new_target;
insights.might_need_arguments_object = self.flags.function_might_need_arguments_object;
self.flags.function_might_need_arguments_object = saved_might_need_arguments;

View file

@ -813,24 +813,70 @@ impl Parser<'_> {
None
};
// Store catch parameter names so that lexical and function
// declarations in the body can be checked for redeclaration.
let saved_catch_names = std::mem::take(&mut self.catch_parameter_names);
match &parameter {
Some(CatchBinding::Identifier(id)) => {
self.catch_parameter_names.push(id.name.clone());
}
Some(CatchBinding::BindingPattern(_)) => {
for (name, _) in &self.pattern_bound_names {
self.catch_parameter_names.push(name.clone());
}
}
None => {}
}
// Collect catch parameter names for post-body validation.
let catch_names: Vec<Utf16String> = match &parameter {
Some(CatchBinding::Identifier(id)) => vec![id.name.clone()],
Some(CatchBinding::BindingPattern(_)) => self
.pattern_bound_names
.iter()
.map(|(n, _)| n.clone())
.collect(),
None => Vec::new(),
};
let body = self.parse_block_statement();
self.catch_parameter_names = saved_catch_names;
// https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors
// It is a Syntax Error if any element of the BoundNames of
// CatchParameter also occurs in the LexicallyDeclaredNames of Block.
if !catch_names.is_empty()
&& let StatementKind::Block(ref scope) = body.inner
{
for child in &scope.borrow().children {
match &child.inner {
StatementKind::VariableDeclaration { kind, declarations }
if *kind != DeclarationKind::Var =>
{
for decl in declarations {
if let VariableDeclaratorTarget::Identifier(ref id) = decl.target {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
}
}
StatementKind::FunctionDeclaration { name: Some(id), .. } => {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
StatementKind::ClassDeclaration(data) => {
if let Some(ref id) = data.name {
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
));
}
}
}
}
_ => {}
}
}
}
self.scope_collector.close_scope();
CatchClause {