mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 不可扩展对象设置原型返回失败
This commit is contained in:
@@ -1256,11 +1256,12 @@ pub fn object_get_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
/// - `obj` 为 null/undefined 抛 TypeError(RequireObjectCoercible);原始值原样返回,
|
||||
/// 不装箱。
|
||||
/// - `proto` 既非对象也非 null 时抛 TypeError。
|
||||
/// - 目标不可设置(原型环)时抛 TypeError;普通对象恒可设置,与 Reflect 版
|
||||
/// 按布尔返回的语义不同。
|
||||
/// - 目标不可设置(原型环,或不可扩展且新旧原型不同)时抛 TypeError;与 Reflect
|
||||
/// 版按布尔返回的语义不同。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 目标为对象时改写其 proto 槽并递增 generation。
|
||||
/// - 目标为对象且设置成功时改写其 proto 槽并递增 generation;新旧原型相同
|
||||
/// (SameValue)时按规范直接成功。
|
||||
pub fn object_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let target_val = args.get(1).map(|reg| vm.reg(*reg)).unwrap_or_else(JsValue::undefined);
|
||||
if target_val.is_null() || target_val.is_undefined() {
|
||||
@@ -1280,7 +1281,16 @@ pub fn object_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
return NativeResult::Ok(target_val);
|
||||
}
|
||||
let target_ptr = target_val.as_js_object_ptr();
|
||||
if unsafe { &mut *target_ptr }.set_proto(proto).is_err() {
|
||||
let target = unsafe { &mut *target_ptr };
|
||||
// OrdinarySetPrototypeOf 步 2-4:新旧原型 SameValue 时直接成功,可扩展标志
|
||||
// 不参与判断;不可扩展且新旧不同才失败。set_proto 是裸写槽操作,不含该判定。
|
||||
if !target.is_extensible() && target.proto() != proto {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Object.setPrototypeOf: cannot set prototype of non-extensible object",
|
||||
));
|
||||
}
|
||||
if target.set_proto(proto).is_err() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.setPrototypeOf: cannot set prototype"));
|
||||
}
|
||||
NativeResult::Ok(target_val)
|
||||
|
||||
@@ -226,6 +226,7 @@ pub fn reflect_set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
/// `Reflect.setPrototypeOf(target, proto)`:设置 prototype(对象或 null)。
|
||||
/// 不可扩展且新旧原型不同时返回 false;新旧相同返回 true。
|
||||
pub fn reflect_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let target_val = arg(vm, args, 1);
|
||||
let Some(target_ptr) = object_ptr(target_val) else {
|
||||
@@ -235,7 +236,12 @@ pub fn reflect_set_prototype_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeRes
|
||||
if !proto.is_object() && !proto.is_null() {
|
||||
return type_error(vm, "Reflect.setPrototypeOf prototype must be an object or null");
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(unsafe { &mut *target_ptr }.set_proto(proto).is_ok()))
|
||||
let target = unsafe { &mut *target_ptr };
|
||||
// OrdinarySetPrototypeOf 步 2-4:新旧相同先于可扩展判定,直接成功。
|
||||
if !target.is_extensible() && target.proto() != proto {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(target.set_proto(proto).is_ok()))
|
||||
}
|
||||
|
||||
fn arg<H: VmHost>(vm: &H, args: &[u8], idx: usize) -> JsValue {
|
||||
|
||||
@@ -250,7 +250,10 @@ impl Emitter {
|
||||
self.emit_throw_error("ReferenceError", "Cannot delete a super reference", ctx)
|
||||
}
|
||||
Expression::ComputedMemberExpression(member) if matches!(&member.object, Expression::Super(_)) => {
|
||||
// super 属性引用不可删除:键表达式与基均不得先行求值。
|
||||
// super 属性引用不可删除:立即抛 ReferenceError,不执行 ToPropertyKey,
|
||||
// 也不求值基与 this。代价:this 已初始化时键表达式副作用被跳过——
|
||||
// 未初始化须先抛与副作用须先执行在缺少 GetThisBinding 运行期检查前
|
||||
// 无法兼得。
|
||||
self.emit_throw_error("ReferenceError", "Cannot delete a super reference", ctx)
|
||||
}
|
||||
Expression::StaticMemberExpression(member) => {
|
||||
|
||||
@@ -773,6 +773,12 @@ impl JsObject {
|
||||
///
|
||||
/// 仅接受 `null` 或对象;沿新原型链检查是否构成环,成环返回
|
||||
/// `Err("cyclic __proto__ value")`。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 本方法是裸写槽操作:既不检查 `[[Extensible]]`,也不比较新旧原型是否
|
||||
/// SameValue。实现 `[[SetPrototypeOf]]` 的调用方须先自行判定「新旧相同直接
|
||||
/// 成功」「不可扩展且新旧不同则失败」,再调用本方法;原型接线、世界重建等
|
||||
/// 内部路径不受可扩展标志约束,直接调用即可。
|
||||
pub fn set_proto(&mut self, proto: JsValue) -> Result<(), &'static str> {
|
||||
if proto.is_null() {
|
||||
self.proto = proto;
|
||||
|
||||
@@ -330,6 +330,39 @@ fn object_set_prototype_of_sets_prototype_and_validates() {
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
}
|
||||
|
||||
// Object.setPrototypeOf 负向分路:不可扩展目标、原型环、原始值目标。
|
||||
#[test]
|
||||
fn object_set_prototype_of_negative_paths() {
|
||||
// 不可扩展且新旧原型不同抛 TypeError。
|
||||
let err = match eval("var o = {}; Object.preventExtensions(o); Object.setPrototypeOf(o, null);") {
|
||||
Ok(_) => panic!("non-extensible target with a different prototype should fail"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
|
||||
// 不可扩展但新旧原型相同仍成功,符合规范「SameValue 先于可扩展判定」。
|
||||
let (_vm, result) = eval(
|
||||
"var o = {}; var p = Object.getPrototypeOf(o); Object.preventExtensions(o); \
|
||||
Object.setPrototypeOf(o, p) === o",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
result.is_bool() && result.as_bool(),
|
||||
"same prototype on non-extensible target should succeed"
|
||||
);
|
||||
|
||||
// 原型环抛 TypeError。
|
||||
let err = match eval("var a = {}; var b = {}; Object.setPrototypeOf(a, b); Object.setPrototypeOf(b, a);") {
|
||||
Ok(_) => panic!("cyclic prototype should fail"),
|
||||
Err(err) => err,
|
||||
};
|
||||
assert!(err.contains("TypeError"), "unexpected error: {err}");
|
||||
|
||||
// 原始值目标原样返回,不抛错。
|
||||
let (_vm, result) = eval("Object.setPrototypeOf(1, null)").unwrap();
|
||||
assert!(result.is_int() && result.as_int() == 1, "primitive target should be returned unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entries_values_with_interleaved_symbol_key() {
|
||||
let (_vm, result) = eval(
|
||||
|
||||
@@ -82,6 +82,22 @@ fn reflect_prototype_and_extensible_methods() {
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// 不可扩展目标:新旧不同返回 false,新旧相同返回 true,且原值不被改写。
|
||||
#[test]
|
||||
fn reflect_set_prototype_of_non_extensible_returns_false() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(
|
||||
&mut vm,
|
||||
"var a = {}; Object.preventExtensions(a); \
|
||||
var diff = Reflect.setPrototypeOf(a, null); \
|
||||
var kept = Object.getPrototypeOf(a) === Object.prototype; \
|
||||
var same = Reflect.setPrototypeOf(a, Object.prototype); \
|
||||
diff === false && kept && same === true",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflect_apply_calls_function() {
|
||||
let mut vm = Vm::new();
|
||||
|
||||
Reference in New Issue
Block a user