fix: 修复不符合 JS 语义约束的错误行为测试

This commit is contained in:
2026-08-05 15:44:22 +08:00
parent c250f5cb2b
commit 2f7fe3cedf
5 changed files with 38 additions and 7 deletions
+7
View File
@@ -257,6 +257,9 @@ fn make_pair(
ctor.set_data_meta(0u32, oxide_types::object::PropAttributes::new(false, false, false)); ctor.set_data_meta(0u32, oxide_types::object::PropAttributes::new(false, false, false));
// .name (ctor[1]) = writable:false, enumerable:false, configurable:true // .name (ctor[1]) = writable:false, enumerable:false, configurable:true
ctor.set_data_meta(1u32, oxide_types::object::PropAttributes::new(false, false, true)); ctor.set_data_meta(1u32, oxide_types::object::PropAttributes::new(false, false, true));
// .constructor (proto[0]) = writable:true, enumerable:false, configurable:true
// (JS spec: prototype.constructor is non-enumerable so it doesn't leak into for-in)
proto.set_data_meta(0u32, oxide_types::object::PropAttributes::new(true, false, true));
(P::new(proto), P::new(ctor)) (P::new(proto), P::new(ctor))
} }
@@ -1175,6 +1178,10 @@ impl BuiltinWorld {
let new_shape = shape_forge.make_shape(proto.shape_id(), si); let new_shape = shape_forge.make_shape(proto.shape_id(), si);
proto.set_shape_id(new_shape); proto.set_shape_id(new_shape);
proto.ensure_hash_props().push(wrapper_val); proto.ensure_hash_props().push(wrapper_val);
// Built-in prototype methods are non-enumerable per the ES spec; otherwise
// they leak into for-in enumeration (e.g. array push/pop showing up in `for k in []`).
let method_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
proto.set_data_meta(method_pos, oxide_types::object::PropAttributes::new(true, false, true));
proto.bump_generation(); proto.bump_generation();
Ok(()) Ok(())
} }
+23
View File
@@ -300,6 +300,29 @@ impl Vm {
bumpalo::collections::Vec::new_in(self.epoch.bump()); bumpalo::collections::Vec::new_in(self.epoch.bump());
let mut seen = std::collections::HashSet::new(); let mut seen = std::collections::HashSet::new();
let mut current = obj_val; let mut current = obj_val;
// Arrays store integer-indexed elements in prop_vec, which is not part of
// the shape chain. Enumerate them (ES: array indices are enumerable string
// keys, ordered ascending before other own keys).
if current.is_object() {
let arr = unsafe { &*current.as_js_object_ptr() };
if arr.is_array() {
for i in 0..arr.prop_vec_len() {
let is_enum = arr
.prop_meta_at(i)
.map(|m| m.attributes.enumerable())
.unwrap_or(PropAttributes::DEFAULT_DATA.enumerable());
if is_enum {
let idx = self.kernel_core.perm_interner().intern(&i.to_string()).0;
keys_vec.push((
JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(idx)),
idx,
));
}
}
}
}
let mut depth = 0usize; let mut depth = 0usize;
loop { loop {
@@ -166,7 +166,8 @@ fn object_proto_has_own_property_call_works() {
#[test] #[test]
fn object_proto_property_is_enumerable_call_works() { fn object_proto_property_is_enumerable_call_works() {
let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap(); let (_vm, result) = eval("Object.prototype.propertyIsEnumerable.call(Object, 'assign')").unwrap();
assert_eq!(result, JsValue::bool(true)); // Built-in methods are non-enumerable per the ES spec.
assert_eq!(result, JsValue::bool(false));
} }
#[test] #[test]
+4 -4
View File
@@ -159,12 +159,12 @@ fn eval_do_while_continue() {
#[test] #[test]
fn eval_for_in_enumerates_keys() { fn eval_for_in_enumerates_keys() {
assert_eq!(eval("var n=0; for (k in {a:1,b:2}) { n=n+1; } n"), "3"); assert_eq!(eval("var n=0; for (k in {a:1,b:2}) { n=n+1; } n"), "2");
} }
#[test] #[test]
fn eval_for_in_empty_object() { fn eval_for_in_empty_object() {
assert_eq!(eval("var r=0; for (k in {}) { r=1; } r"), "1"); assert_eq!(eval("var r=0; for (k in {}) { r=1; } r"), "0");
} }
#[test] #[test]
@@ -175,7 +175,7 @@ fn eval_for_in_non_object_throws() {
#[test] #[test]
fn eval_for_in_nested() { fn eval_for_in_nested() {
assert_eq!(eval("var r=0; for (k in {a:{x:1}}) { for (j in {y:2}) { r=r+1; } } r"), "2"); assert_eq!(eval("var r=0; for (k in {a:{x:1}}) { for (j in {y:2}) { r=r+1; } } r"), "1");
} }
#[test] #[test]
@@ -185,7 +185,7 @@ fn eval_for_in_break() {
#[test] #[test]
fn eval_for_in_continue() { fn eval_for_in_continue() {
assert_eq!(eval("var r=0; for (k in {a:1,b:2}) { if (k=='a') continue; r=r+1; } r"), "2"); assert_eq!(eval("var r=0; for (k in {a:1,b:2}) { if (k=='a') continue; r=r+1; } r"), "1");
} }
#[test] #[test]
+2 -2
View File
@@ -104,8 +104,8 @@ fn regression_throw_statement_preserves_syntax_error_kind() {
fn regression_for_in_prototype_chain() { fn regression_for_in_prototype_chain() {
assert_eq!( assert_eq!(
eval("var c=0;for(var k in {a:1}){c=c+1;}c"), eval("var c=0;for(var k in {a:1}){c=c+1;}c"),
"2", "1",
"for-in should include inherited constructor from prototype" "for-in must not include non-enumerable prototype properties like constructor"
); );
} }