From a01b1fc78a2e65a98419e1b49b8805d2ec1d95fe Mon Sep 17 00:00:00 2001 From: yush-1018 Date: Sat, 18 Apr 2026 18:48:30 +0530 Subject: [PATCH 1/4] fix: deep clone scope in FunctionEnvironment to prevent variable leakage across eval invocations (#5332) When eval('var x = ...') is called inside a function, Boa mutates the compile-time Scope (shared via Rc) to register the new binding. Because the same Scope was reused across invocations, the binding leaked into subsequent calls. Fix: add Scope::deep_clone() that creates a fresh copy of the bindings Vec, and use it in FunctionEnvironment::new() so each invocation gets its own isolated scope. Closes #5332 --- core/ast/src/scope.rs | 18 ++++++++++++++++++ .../runtime/declarative/function.rs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/ast/src/scope.rs b/core/ast/src/scope.rs index c172877479f..7cff358fe81 100644 --- a/core/ast/src/scope.rs +++ b/core/ast/src/scope.rs @@ -142,6 +142,24 @@ impl Scope { } } + /// Creates a deep clone of the scope, copying the bindings vector + /// so that runtime mutations (e.g. from `eval`) do not leak back + /// into the shared compile-time scope. + #[must_use] + pub fn deep_clone(&self) -> Self { + Self { + inner: Rc::new(Inner { + unique_id: self.inner.unique_id, + outer: self.inner.outer.clone(), + index: self.inner.index.clone(), + bindings: RefCell::new(self.inner.bindings.borrow().clone()), + function: self.inner.function, + this_escaped: self.inner.this_escaped.clone(), + context: self.inner.context.clone(), + }), + } + } + /// Checks if the scope has only local bindings. #[must_use] pub fn all_bindings_local(&self) -> bool { diff --git a/core/engine/src/environments/runtime/declarative/function.rs b/core/engine/src/environments/runtime/declarative/function.rs index 9e198882740..38aba0b6d74 100644 --- a/core/engine/src/environments/runtime/declarative/function.rs +++ b/core/engine/src/environments/runtime/declarative/function.rs @@ -19,7 +19,7 @@ impl FunctionEnvironment { Self { bindings: GcRefCell::new(vec![None; bindings_count as usize]), slots: Box::new(slots), - scope, + scope: scope.deep_clone(), } } From c0ac0e8b8e37f907a89e9d263159f9277f7eb479 Mon Sep 17 00:00:00 2001 From: yush-1018 Date: Mon, 20 Apr 2026 10:21:28 +0530 Subject: [PATCH 2/4] fix(macros): collapse nested if into match guard to fix clippy::collapsible_match --- core/macros/src/module.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/macros/src/module.rs b/core/macros/src/module.rs index 22711d01459..7cac475a03a 100644 --- a/core/macros/src/module.rs +++ b/core/macros/src/module.rs @@ -185,14 +185,14 @@ fn module_impl_impl(_args: ModuleArguments, mut mod_: ItemMod) -> SpannedResult< | Item::TraitAlias(ItemTraitAlias { attrs, .. }) | Item::Type(ItemType { attrs, .. }) | Item::Union(ItemUnion { attrs, .. }) - | Item::Use(ItemUse { attrs, .. }) => { - if take_path_attr(attrs, "skip") { - original_module_decl = quote! { - #original_module_decl - #item - }; - continue; - } + | Item::Use(ItemUse { attrs, .. }) + if take_path_attr(attrs, "skip") => + { + original_module_decl = quote! { + #original_module_decl + #item + }; + continue; } _ => {} } From 17074a695e1902c7ad376d235fc13546c70c17a5 Mon Sep 17 00:00:00 2001 From: yush-1018 Date: Mon, 20 Apr 2026 10:33:03 +0530 Subject: [PATCH 3/4] fix(macros): use allow attribute for collapsible_match instead of match guard (mutation not allowed in guards) --- core/macros/src/module.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/macros/src/module.rs b/core/macros/src/module.rs index 7cac475a03a..b05c36532b3 100644 --- a/core/macros/src/module.rs +++ b/core/macros/src/module.rs @@ -170,6 +170,7 @@ fn module_impl_impl(_args: ModuleArguments, mut mod_: ItemMod) -> SpannedResult< for item in mod_.content.map_or_else(Vec::new, |c| c.1).as_mut_slice() { // Check for skip attributes. + #[allow(clippy::collapsible_match)] match item { Item::Const(ItemConst { attrs, .. }) | Item::Enum(ItemEnum { attrs, .. }) @@ -185,14 +186,14 @@ fn module_impl_impl(_args: ModuleArguments, mut mod_: ItemMod) -> SpannedResult< | Item::TraitAlias(ItemTraitAlias { attrs, .. }) | Item::Type(ItemType { attrs, .. }) | Item::Union(ItemUnion { attrs, .. }) - | Item::Use(ItemUse { attrs, .. }) - if take_path_attr(attrs, "skip") => - { - original_module_decl = quote! { - #original_module_decl - #item - }; - continue; + | Item::Use(ItemUse { attrs, .. }) => { + if take_path_attr(attrs, "skip") { + original_module_decl = quote! { + #original_module_decl + #item + }; + continue; + } } _ => {} } From 8ed8fe7d875a8add8737cb6b6d6361f494b149ee Mon Sep 17 00:00:00 2001 From: yush-1018 Date: Tue, 12 May 2026 10:35:23 +0530 Subject: [PATCH 4/4] fix: allow needless_pass_by_value on FunctionEnvironment::new --- core/engine/src/environments/runtime/declarative/function.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/engine/src/environments/runtime/declarative/function.rs b/core/engine/src/environments/runtime/declarative/function.rs index 38aba0b6d74..b872d62a432 100644 --- a/core/engine/src/environments/runtime/declarative/function.rs +++ b/core/engine/src/environments/runtime/declarative/function.rs @@ -15,6 +15,7 @@ pub(crate) struct FunctionEnvironment { impl FunctionEnvironment { /// Creates a new `FunctionEnvironment`. + #[allow(clippy::needless_pass_by_value)] pub(crate) fn new(bindings_count: u32, slots: FunctionSlots, scope: Scope) -> Self { Self { bindings: GcRefCell::new(vec![None; bindings_count as usize]),