diff --git a/crates/oxide_builtins/src/global/uri.rs b/crates/oxide_builtins/src/global/uri.rs index 501a2335..7a2ccc29 100644 --- a/crates/oxide_builtins/src/global/uri.rs +++ b/crates/oxide_builtins/src/global/uri.rs @@ -24,7 +24,11 @@ fn encode_uri_units(input: &[u16], safe: &str) -> Result { } let cp = 0x10000 + ((unit as u32 - 0xD800) << 10) + (next as u32 - 0xDC00); let mut buf = [0u8; 4]; - for byte in char::from_u32(cp).expect("合成码点必在超平面区间内").encode_utf8(&mut buf).as_bytes() { + for byte in char::from_u32(cp) + .expect("合成码点必在超平面区间内") + .encode_utf8(&mut buf) + .as_bytes() + { out.push_str(&format!("%{byte:02X}")); } i += 2; diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index 21010922..449d7af6 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -309,12 +309,7 @@ impl Emitter { // 同上:只读三常量已在拦截臂跳过,其余内置名属性可写。 self.emit_tier_global_write(name, key_reg, ctx); } else { - ctx.inst(Inst::new( - OpCode::STORE_VAR, - Operand::Reg(var_reg), - Operand::Reg(key_reg), - Operand::None, - )); + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(key_reg), Operand::None)); if is_implicit { self.emit_implicit_global_write(name, var_reg, ctx); } else if ctx.targets_writable_builtin(name, var_reg) { diff --git a/crates/oxide_kernel/src/builtin/rebuild.rs b/crates/oxide_kernel/src/builtin/rebuild.rs index 37a2230a..7b7be010 100644 --- a/crates/oxide_kernel/src/builtin/rebuild.rs +++ b/crates/oxide_kernel/src/builtin/rebuild.rs @@ -8,8 +8,7 @@ use oxide_types::value::JsValue; use super::construct::{ builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_boolean_proto, tag_number_proto, - tag_string_proto, - wire_builtin_world_links, ErrorSubtypeProtos, TypedArrayFamily, + tag_string_proto, wire_builtin_world_links, ErrorSubtypeProtos, TypedArrayFamily, }; use super::BuiltinWorld; use crate::kernel::BuiltinDirtySet; diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index c94ecc42..3968e54c 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -764,9 +764,9 @@ pub(crate) fn async_iterator_async_dispose(vm: &mut Vm, args: &[u8]) -> NativeRe match vm.ordinary_get(obj, si, o) { Ok(r) => Some(r), Err(_) => { - let exc = vm - .take_uncaught_value() - .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, "AsyncIterator return getter failed")); + let exc = vm.take_uncaught_value().unwrap_or_else(|| { + oxide_builtins::error::create_from_text(vm, "AsyncIterator return getter failed") + }); let _ = vm.reject_promise(promise, exc); return NativeResult::Ok(promise); } @@ -786,9 +786,9 @@ pub(crate) fn async_iterator_async_dispose(vm: &mut Vm, args: &[u8]) -> NativeRe let result = match vm.call_function_sync(ret, o, &[JsValue::undefined()]) { Ok(r) => r, Err(_) => { - let exc = vm - .take_uncaught_value() - .unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, "AsyncIterator return method call failed")); + let exc = vm.take_uncaught_value().unwrap_or_else(|| { + oxide_builtins::error::create_from_text(vm, "AsyncIterator return method call failed") + }); let _ = vm.reject_promise(promise, exc); return NativeResult::Ok(promise); } @@ -886,8 +886,7 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { let world = vm.session.builtin_world(); // %AsyncGeneratorPrototype% 链到 %AsyncIteratorPrototype%(规范原型链), // 方法 next/return/throw 挂其自身。 - let async_iterator_proto_val = - JsValue::from_js_object(world.async_iterator_proto.as_ptr() as *mut JsObject); + let async_iterator_proto_val = JsValue::from_js_object(world.async_iterator_proto.as_ptr() as *mut JsObject); // %AsyncGeneratorPrototype%:proto = %AsyncIteratorPrototype%。 let mut ag_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, async_iterator_proto_val)); diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index ae87bc95..79997a39 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use crate::bindings::{apply_binding_table, bind_accessor_getter, bind_well_known_method, configure_native_constructor}; +use crate::bindings::{ + apply_binding_table, bind_accessor_getter, bind_well_known_method, configure_native_constructor, +}; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::{make_well_known_symbol_key, WELL_KNOWN_SYMBOL_TO_PRIMITIVE}; diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 85194e17..5fca53c3 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -571,8 +571,9 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession) { // (经 return 方法关闭迭代器)。幂等:键槽已存在时跳过。 let aiter_proto_ptr = world.async_iterator_proto.as_ptr() as *mut JsObject; let aiter_proto = unsafe { &mut *aiter_proto_ptr }; - let sym_aiter = - oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_ITERATOR); + let sym_aiter = oxide_types::private_key::make_well_known_symbol_key( + oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_ITERATOR, + ); if core.shape_forge().lookup_position(aiter_proto.shape_id(), sym_aiter).is_none() { bind_well_known_method( world, @@ -586,7 +587,11 @@ fn bind_iterator_protos(core: &Arc, session: &KernelSession) { } let sym_adispose = oxide_types::private_key::make_well_known_symbol_key(oxide_types::private_key::WELL_KNOWN_SYMBOL_ASYNC_DISPOSE); - if core.shape_forge().lookup_position(aiter_proto.shape_id(), sym_adispose).is_none() { + if core + .shape_forge() + .lookup_position(aiter_proto.shape_id(), sym_adispose) + .is_none() + { bind_well_known_method( world, core, diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index f5330dd0..3564a547 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -31,13 +31,24 @@ impl Vm { } } - /// SET 路径的接收者解析:对象直取;非对象经 ToObject 自动装箱 - /// (null/undefined 抛 TypeError,其余原始值写盒后丢弃盒,与规范 - /// OrdinarySetWithOwnDescriptor 的 ToObject 基语义一致)。 + /// SET 路径的接收者解析:对象直取;非对象按严格性分流——strict 抛 + /// TypeError(规范 OrdinarySetWithOwnDescriptor 的 ToObject 基语义在 + /// strict 下不 auto-box),sloppy 经 ToObject 自动装箱(null/undefined + /// 抛 TypeError,其余原始值写盒后丢弃盒)。 fn set_prop_base(&mut self, receiver: JsValue) -> Result, String> { if receiver.is_object() { return self.checked_object_ptr(receiver, "Cannot create property on non-object"); } + if self.current_strict() { + self.raise_type_error("Cannot create property on a primitive value")?; + return Ok(None); + } + // null/undefined:ToObject 抛可捕获 TypeError(经 raise 走 JS 异常, + // 非 Rust Err 传播,try/catch 可接)。 + if receiver.is_null() || receiver.is_undefined() { + self.raise_type_error("Cannot convert null or undefined to object")?; + return Ok(None); + } let boxed = coercion::to_object(receiver, self)?; Ok(Some(boxed.as_js_object_ptr())) } diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index 45e3d554..6d55125c 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -427,9 +427,9 @@ fn error_to_string_tag_is_error() { ) .unwrap(); assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string())); - // Error.prototype 自身同样判定为 Error。 + // Error.prototype 本体无 OBJ_TYPE_ERROR 标(普通对象),按规范品牌为 Object。 let r = eval_in(&mut vm, "Object.prototype.toString.call(Error.prototype)").unwrap(); - assert_eq!(vm.lookup_str(r), Some("[object Error]".to_string())); + assert_eq!(vm.lookup_str(r), Some("[object Object]".to_string())); } #[test] @@ -573,9 +573,10 @@ fn suppressed_error_call_without_new_creates_object() { #[test] fn suppressed_error_prototype_chain_and_shape() { let mut vm = make_vm(); - // 原型仅挂 constructor/name(name 与既有子类型一致排在 constructor 前),无 error/suppressed。 + // 原型挂 name/message/constructor(name 与既有子类型一致排在 constructor 前, + // message 为规范空串自身属性),无 error/suppressed。 let r = eval_in(&mut vm, "Object.getOwnPropertyNames(SuppressedError.prototype).join(',')").unwrap(); - assert_eq!(vm.lookup_str(r), Some("name,constructor".to_string())); + assert_eq!(vm.lookup_str(r), Some("name,message,constructor".to_string())); // instanceof Error 走原型链,原型 [[Prototype]] = Error.prototype。 assert_eq!( format!("{}", eval_in(&mut vm, "new SuppressedError('e','s') instanceof Error").unwrap()), diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs index 7da7b6df..6ef0f5cc 100644 --- a/crates/oxide_vm/tests/coercion_tests.rs +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -102,9 +102,19 @@ fn test_to_int32_and_to_uint32() { } #[test] -fn primitive_property_write_throws_clean_type_error() { +fn primitive_property_write_sloppy_auto_boxes() { + // sloppy 模式:原始值接收者 auto-box,写盒后丢弃盒,不抛 TypeError。 for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] { - let err = eval(source); + let result = eval(source); + assert_eq!(result, "1", "sloppy auto-box should return 1 for {source}, got {result}"); + } +} + +#[test] +fn primitive_property_write_strict_throws_clean_type_error() { + // strict 模式:原始值接收者抛 clean TypeError(非内部 opcode 消息)。 + for source in ["'abc'.x = 1", "(5).foo = 1", "true.foo = 1"] { + let err = eval(&format!("\"use strict\"; {source}")); assert!(err.contains("TypeError"), "expected TypeError for {source}, got {err}"); assert!( !err.contains("IC_SET_PROP on non-object")