From 17e5992dccc8cc1c4a62e29602c5e1d447ef2a9d Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 10 Oct 2026 07:17:35 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BC=96=E8=AF=91=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E9=94=AE=E9=80=92=E5=BD=92=E7=BA=B3=E5=85=A5=E8=A7=A3=E6=9E=84?= =?UTF-8?q?=E5=BD=A2=E6=80=81=E7=BB=91=E5=AE=9A=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++++---- Cargo.toml | 2 +- crates/oxide_compiler/src/hash/expression.rs | 5 +- crates/oxide_compiler/src/hash/mod.rs | 63 ++++++++++-- crates/oxide_compiler/src/hash/statement.rs | 21 +++- crates/oxide_compiler/src/hash/target.rs | 96 ++++++++++++++++++- .../tests/hash_collision_tests.rs | 30 ++++++ 7 files changed, 217 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e59f89b9..22ccee07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.14.3" +version = "0.14.4" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.14.3" +version = "0.14.4" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.14.3" +version = "0.14.4" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.14.3" +version = "0.14.4" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.14.3" +version = "0.14.4" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.14.3" +version = "0.14.4" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.14.3" +version = "0.14.4" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.14.3" +version = "0.14.4" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.14.3" +version = "0.14.4" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.14.3" +version = "0.14.4" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.14.3" +version = "0.14.4" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 452a8f48..29a08b22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.14.3" +version = "0.14.4" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_compiler/src/hash/expression.rs b/crates/oxide_compiler/src/hash/expression.rs index b9c9431a..cbfb3dc7 100644 --- a/crates/oxide_compiler/src/hash/expression.rs +++ b/crates/oxide_compiler/src/hash/expression.rs @@ -58,9 +58,8 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i } Expression::AssignmentExpression(assign) => { std::mem::discriminant(&assign.operator).hash(h); - if let Some(target) = assign.left.as_simple_assignment_target() { - target::hash_simple_assignment_target(target, h, include_binding_names); - } + // 全量分派:简单目标与数组/对象解构目标都计入,左值整体不得漏哈希。 + target::hash_assignment_target(&assign.left, h, include_binding_names); hash_expression(&assign.right, h, include_binding_names); } Expression::UpdateExpression(update) => { diff --git a/crates/oxide_compiler/src/hash/mod.rs b/crates/oxide_compiler/src/hash/mod.rs index fa1fcf02..486f102a 100644 --- a/crates/oxide_compiler/src/hash/mod.rs +++ b/crates/oxide_compiler/src/hash/mod.rs @@ -2,15 +2,18 @@ //! `Program` 的稳定哈希,用作编译缓存键(code cache)。 //! //! `structural_hash` 为粗粒度键,忽略变量声明名、标识符读、import 本地名与 -//! 函数参数等大部分绑定名;`compiled_module_hash` 为精确键,额外纳入标识符形态的绑定名 -//! (变量声明、参数与 catch 的解构形态均不贡献哈希)。 +//! 函数参数等大部分绑定名(含声明/参数位置的解构形态);`compiled_module_hash` +//! 为精确键,额外纳入绑定名:标识符形态直接计入,解构形态(数组/对象/默认值 +//! pattern)递归计入键、名、默认值表达式与 rest 的完整结构。 //! 函数声明名、类名与 catch 参数标识名是编译产物物化名,两种粒度下恒计入哈希; -//! 变量声明、函数/箭头/方法参数与 catch 参数为解构形态时均不贡献哈希。 +//! 解构赋值目标的完整结构(元素数、键、rest、默认值表达式)两种粒度下均计入, +//! 其内部绑定名仅精确键计入。 use oxide_parser::{ - ArrayExpressionElement, BindingPattern, ChainElement, Class, ClassElement, Declaration, + ArrayAssignmentTarget, ArrayExpressionElement, AssignmentTarget, AssignmentTargetMaybeDefault, + AssignmentTargetProperty, BindingPattern, ChainElement, Class, ClassElement, Declaration, ExportDefaultDeclarationKind, Expression, ForStatementInit, ForStatementLeft, Function, ImportDeclarationSpecifier, - ModuleExportName, ObjectPropertyKind, PropertyKey, SimpleAssignmentTarget, Statement, + ModuleExportName, ObjectAssignmentTarget, ObjectPropertyKind, PropertyKey, SimpleAssignmentTarget, Statement, }; use std::hash::Hash; @@ -75,12 +78,52 @@ fn hash_program(program: &oxide_parser::Program, include_binding_names: bool) -> h.finish() } -/// 哈希 `BindingIdentifier` 的绑定名;只由 `include_binding_names` 为真的调用点 -/// 使用(变量声明、函数与箭头参数、方法参数、catch 参数),非标识符模式 -/// (数组/对象解构)不贡献哈希。 +/// 哈希 `BindingPattern` 的完整结构:标识符形态计绑定名,解构形态(数组/对象/ +/// 默认值 pattern)递归计入键、名、默认值表达式与 rest 的完整结构。 +/// +/// # 边界与前提 +/// - 只由 `include_binding_names` 为真的调用点使用(变量声明、函数与箭头参数、 +/// 方法参数、catch 参数),故嵌套调用对 `hash_expression` / `hash_property_key` +/// 硬编码传 `true`。 +/// +/// # 注意事项 +/// - 数组省略位与 rest 有无须以哨兵区分:位置敏感,`[a, , b]` 与 `[a, b, ]` +/// 同长不同位,`[a, b]` 与 `[a, b, ...r]` 结构不同。 fn hash_binding_pattern(pattern: &BindingPattern, h: &mut rustc_hash::FxHasher) { - if let BindingPattern::BindingIdentifier(ident) = pattern { - ident.name.as_str().hash(h); + match pattern { + BindingPattern::BindingIdentifier(ident) => { + ident.name.as_str().hash(h); + } + BindingPattern::ArrayPattern(ap) => { + (ap.elements.len() as u32).hash(h); + for elem in &ap.elements { + match elem { + Some(bp) => hash_binding_pattern(bp, h), + // 省略位(如 `[a, , b]`)与有值位须区分:位置敏感。 + None => 0u8.hash(h), + } + } + if let Some(rest) = &ap.rest { + 1u8.hash(h); + hash_binding_pattern(&rest.argument, h); + } + } + BindingPattern::ObjectPattern(op) => { + (op.properties.len() as u32).hash(h); + for prop in &op.properties { + property::hash_property_key(&prop.key, h, true); + hash_binding_pattern(&prop.value, h); + } + if let Some(rest) = &op.rest { + 1u8.hash(h); + hash_binding_pattern(&rest.argument, h); + } + } + BindingPattern::AssignmentPattern(ap) => { + hash_binding_pattern(&ap.left, h); + // 默认值表达式是字节码依赖(发射时求值入池),须计入,不能只哈希左值名。 + expression::hash_expression(&ap.right, h, true); + } } } diff --git a/crates/oxide_compiler/src/hash/statement.rs b/crates/oxide_compiler/src/hash/statement.rs index 1bf9583b..ad8d5a82 100644 --- a/crates/oxide_compiler/src/hash/statement.rs +++ b/crates/oxide_compiler/src/hash/statement.rs @@ -252,10 +252,23 @@ fn hash_module_export_name(name: &ModuleExportName<'_>, h: &mut rustc_hash::FxHa fn hash_for_statement_left(left: &ForStatementLeft<'_>, h: &mut rustc_hash::FxHasher, include_binding_names: bool) { std::mem::discriminant(left).hash(h); - if let ForStatementLeft::VariableDeclaration(decl) = left { - hash_variable_declaration(decl, h, include_binding_names); - } else if let Some(target) = left.as_simple_assignment_target() { - target::hash_simple_assignment_target(target, h, include_binding_names); + match left { + ForStatementLeft::VariableDeclaration(decl) => { + hash_variable_declaration(decl, h, include_binding_names); + } + // 解构赋值头与赋值目标同构,分派到共享助手。 + ForStatementLeft::ArrayAssignmentTarget(ap) => { + target::hash_array_assignment_target(ap, h, include_binding_names); + } + ForStatementLeft::ObjectAssignmentTarget(op) => { + target::hash_object_assignment_target(op, h, include_binding_names); + } + // 简单目标(标识符/成员/TS 断言)走既有分派。 + _ => { + if let Some(simple) = left.as_simple_assignment_target() { + target::hash_simple_assignment_target(simple, h, include_binding_names); + } + } } } diff --git a/crates/oxide_compiler/src/hash/target.rs b/crates/oxide_compiler/src/hash/target.rs index 769995a6..b9f11e52 100644 --- a/crates/oxide_compiler/src/hash/target.rs +++ b/crates/oxide_compiler/src/hash/target.rs @@ -1,4 +1,4 @@ -//! 赋值目标(SimpleAssignmentTarget)的结构哈希。 +//! 赋值目标(SimpleAssignmentTarget / AssignmentTarget)的结构哈希。 use super::*; @@ -36,3 +36,97 @@ pub(super) fn hash_simple_assignment_target( } }); } + +/// 哈希全量 `AssignmentTarget`:简单目标(标识符/成员/TS 断言)复用既有分派, +/// 数组/对象解构目标递归计入元素、键、默认值表达式与 rest 的完整结构。 +/// +/// # 边界与前提 +/// - `include_binding_names` 透传至简单目标、默认值表达式与解构目标内部绑定名; +/// 解构目标的结构(元素数、属性键、rest)两种粒度下恒计入。 +pub(super) fn hash_assignment_target( + target: &AssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool, +) { + if let Some(simple) = target.as_simple_assignment_target() { + hash_simple_assignment_target(simple, h, include_binding_names); + return; + } + match target { + AssignmentTarget::ArrayAssignmentTarget(ap) => { + hash_array_assignment_target(ap, h, include_binding_names); + } + AssignmentTarget::ObjectAssignmentTarget(op) => { + hash_object_assignment_target(op, h, include_binding_names); + } + _ => {} + } +} + +/// 哈希数组赋值目标:元素逐个哈希(省略位以哨兵区分),rest 有无以哨兵区分。 +pub(super) fn hash_array_assignment_target( + target: &ArrayAssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool, +) { + (target.elements.len() as u32).hash(h); + for elem in &target.elements { + match elem { + Some(td) => hash_assignment_target_maybe_default(td, h, include_binding_names), + // 省略位与有值位须区分:位置敏感。 + None => 0u8.hash(h), + } + } + if let Some(rest) = &target.rest { + 1u8.hash(h); + hash_assignment_target(&rest.target, h, include_binding_names); + } +} + +/// 哈希对象赋值目标:属性逐个哈希(键与绑定),rest 有无以哨兵区分。 +pub(super) fn hash_object_assignment_target( + target: &ObjectAssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool, +) { + (target.properties.len() as u32).hash(h); + for prop in &target.properties { + hash_assignment_target_property(prop, h, include_binding_names); + } + if let Some(rest) = &target.rest { + 1u8.hash(h); + hash_assignment_target(&rest.target, h, include_binding_names); + } +} + +/// 哈希 `AssignmentTargetMaybeDefault`:带默认值形态计绑定递归加默认值表达式, +/// 其余变体(继承自 `AssignmentTarget`)转全量分派。 +fn hash_assignment_target_maybe_default( + target: &AssignmentTargetMaybeDefault, h: &mut rustc_hash::FxHasher, include_binding_names: bool, +) { + match target { + AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(d) => { + hash_assignment_target(&d.binding, h, include_binding_names); + // 默认值表达式是字节码依赖(发射时求值入池),须计入。 + expression::hash_expression(&d.init, h, include_binding_names); + } + other => { + if let Some(t) = other.as_assignment_target() { + hash_assignment_target(t, h, include_binding_names); + } + } + } +} + +/// 哈希对象赋值目标属性:标识符形态计绑定名与默认值(若有), +/// 属性形态计键与绑定递归。 +fn hash_assignment_target_property( + prop: &AssignmentTargetProperty, h: &mut rustc_hash::FxHasher, include_binding_names: bool, +) { + match prop { + AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => { + id.binding.name.as_str().hash(h); + if let Some(init) = &id.init { + expression::hash_expression(init, h, include_binding_names); + } + } + AssignmentTargetProperty::AssignmentTargetPropertyProperty(pp) => { + property::hash_property_key(&pp.name, h, include_binding_names); + hash_assignment_target_maybe_default(&pp.binding, h, include_binding_names); + } + } +} diff --git a/crates/oxide_compiler/tests/hash_collision_tests.rs b/crates/oxide_compiler/tests/hash_collision_tests.rs index 429db4c1..29d0b1b4 100644 --- a/crates/oxide_compiler/tests/hash_collision_tests.rs +++ b/crates/oxide_compiler/tests/hash_collision_tests.rs @@ -107,6 +107,36 @@ fn import_defer_distinguished() { ); } +#[test] +fn destructuring_binding_names_distinguished() { + // 声明侧:解构绑定名、键、rest 与嵌套结构全量计入精确键。 + assert_ne!(compiled("var [a, b] = [1, 2]"), compiled("var [p, q] = [1, 2]")); + assert_ne!(compiled("var {x: a} = o"), compiled("var {y: b} = o")); + assert_ne!(compiled("var [a, ...r] = o"), compiled("var [a, ...s] = o")); + assert_ne!( + compiled("var [{p: a}, b] = o"), + compiled("var [a, b] = o"), + "nested pattern structure must be part of the hash" + ); + // 默认值侧:默认值表达式计入哈希。 + assert_ne!(compiled("var [a = f()] = o"), compiled("var [a = g()] = o")); + + // 赋值目标侧:解构赋值左值整体不得漏哈希。 + assert_ne!(compiled("[a, b] = [1, 2]"), compiled("[p, q] = [1, 2]")); + assert_ne!(compiled("({a} = o)"), compiled("({b} = o)")); + + // for 头侧:声明形态与赋值形态的解构头都计入哈希。 + assert_ne!(compiled("for (var [a, b] of xs) {}"), compiled("for (var [p, q] of xs) {}")); + assert_ne!(compiled("for ({a} of xs) {}"), compiled("for ({b} of xs) {}")); + + // 函数参数与 catch 侧:解构参数名计入哈希。 + assert_ne!(compiled("function f([a, b]) {}"), compiled("function f([p, q]) {}")); + assert_ne!(compiled("try {} catch ([a]) {}"), compiled("try {} catch ([p]) {}")); + + // 同源等价回归。 + assert_eq!(compiled("var [a, b] = [1, 2]"), compiled("var [a, b] = [1, 2]")); +} + #[test] fn structural_hash_ignores_binding_names() { // 结构哈希忽略绑定名:仅参数/变量改名后哈希相等。