fix: 不可扩展对象设置原型返回失败

This commit is contained in:
2026-09-18 01:23:02 +00:00
parent a8b4191d0f
commit 4236290b45
6 changed files with 80 additions and 6 deletions
+14 -4
View File
@@ -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)
+7 -1
View File
@@ -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 {
+4 -1
View File
@@ -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) => {
+6
View File
@@ -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();