From ef3b95f6d309cbb820e6037362e4fd955d6d72b6 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 11 Sep 2026 12:44:24 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20for-in/for-of=20=E5=B7=A6=E4=BE=A7?= =?UTF-8?q?=E6=9C=AA=E5=A3=B0=E6=98=8E=E5=86=99=E6=94=B9=E8=AF=BB=E5=86=99?= =?UTF-8?q?=E7=99=BB=E8=AE=B0=E9=9B=86=E5=B9=B6=E9=9B=86=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=AF=BB=E4=BE=A7=E7=99=BB=E8=AE=B0=E7=9A=84?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E6=A7=BD=E6=BC=8F=E5=85=A8=E5=B1=80=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=B1=9E=E6=80=A7=E5=86=99=E4=B8=8E=20strict=20?= =?UTF-8?q?=E6=BC=8F=E6=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 左侧标识符先读后写时全局槽由读侧登记,写侧单集判定不命中:sloppy 写穿透漏发(循环后读陈旧值)、strict 漏抛 ReferenceError。与其余 8 写点统一为 is_implicit_global_reg;补 3 枚判别性钉(sloppy for-in / for-of 穿透 + strict for-in 漏抛,回退单集判定必红)。 --- .../oxide_emit/src/stmt/iteration/for_in.rs | 2 +- .../oxide_emit/src/stmt/iteration/for_of.rs | 2 +- .../tests/implicit_global_write_tests.rs | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index dee29822..b5e46ee1 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -89,7 +89,7 @@ impl Emitter { self.emit_throw_error("TypeError", "cannot assign to read-only property", ctx)?; } } else { - let is_implicit = ctx.implicit_global_writes.contains(&var_reg); + let is_implicit = ctx.is_implicit_global_reg(var_reg); if is_implicit && ctx.is_strict { // 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。 self.emit_strict_undeclared_write(name, ctx)?; diff --git a/crates/oxide_emit/src/stmt/iteration/for_of.rs b/crates/oxide_emit/src/stmt/iteration/for_of.rs index 1028b628..941b95b3 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_of.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_of.rs @@ -116,7 +116,7 @@ impl Emitter { self.emit_throw_error("TypeError", "cannot assign to read-only property", ctx)?; } } else { - let is_implicit = ctx.implicit_global_writes.contains(&var_reg); + let is_implicit = ctx.is_implicit_global_reg(var_reg); if is_implicit && ctx.is_strict { // 严格模式未声明写:抛 ReferenceError,跳过寄存器写(值无关)。 self.emit_strict_undeclared_write(name, ctx)?; diff --git a/crates/oxide_vm/tests/implicit_global_write_tests.rs b/crates/oxide_vm/tests/implicit_global_write_tests.rs index d3dc524b..11831b2b 100644 --- a/crates/oxide_vm/tests/implicit_global_write_tests.rs +++ b/crates/oxide_vm/tests/implicit_global_write_tests.rs @@ -188,9 +188,28 @@ fn undeclared_read_then_destructuring_write_reaches_global() { assert!(r.is_bool() && r.as_bool(), "先读后解构写应穿透全局对象,实际 {:?}", r); } +// ── for-in/for-of 左侧先读后写:全局槽由读侧登记,LHS 写同样须穿透全局对象 ── +#[test] +fn undeclared_read_then_for_in_write_reaches_global() { + let r = eval_many(&["globalThis.k = 0", "var seen = k", "for (k in { a: 1 }) {}", "globalThis.k === 'a'"]).unwrap(); + assert!(r.is_bool() && r.as_bool(), "先读后 for-in 左侧写应穿透全局对象,实际 {:?}", r); +} + +#[test] +fn undeclared_read_then_for_of_write_reaches_global() { + let r = eval_many(&["globalThis.v = 0", "var seen = v", "for (v of [1, 2]) {}", "globalThis.v === 2"]).unwrap(); + assert!(r.is_bool() && r.as_bool(), "先读后 for-of 左侧写应穿透全局对象,实际 {:?}", r); +} + // ── 严格模式:读侧已登记的全局槽同属未解析引用,后写抛 ReferenceError ── #[test] fn strict_undeclared_read_then_write_throws_reference() { let result = eval("'use strict'; globalThis.s = 0; var seen = s; s = 5"); assert_err_contains(result, "s is not defined"); } + +#[test] +fn strict_undeclared_read_then_for_in_write_throws_reference() { + let result = eval("'use strict'; globalThis.k = 0; var seen = k; for (k in { a: 1 }) {}"); + assert_err_contains(result, "k is not defined"); +}