fix: SET 原始值接收者按严格性分流并补脏重建本体标

This commit is contained in:
2026-09-24 19:37:26 +00:00
parent f9e6eb3b2b
commit 4787f2180f
9 changed files with 56 additions and 30 deletions
+5 -1
View File
@@ -24,7 +24,11 @@ fn encode_uri_units(input: &[u16], safe: &str) -> Result<String, ()> {
}
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;
@@ -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) {
+1 -2
View File
@@ -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;
+7 -8
View File
@@ -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));
+3 -1
View File
@@ -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};
+8 -3
View File
@@ -571,8 +571,9 @@ fn bind_iterator_protos(core: &Arc<KernelCore>, 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<KernelCore>, 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,
+14 -3
View File
@@ -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<Option<*mut JsObject>, 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()))
}
@@ -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()),
+12 -2
View File
@@ -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")