mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
fix: sloppy 模式属性赋值静默 no-op——写失败按写方严格模式分派,内置写路径一律按严格
This commit is contained in:
@@ -124,8 +124,8 @@ fn object_define_property_rejects_mixed_descriptor() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_define_property_non_writable_assignment_throws() {
|
||||
let err = match eval("var o={}; Object.defineProperty(o,'x',{value:1}); o.x=2") {
|
||||
fn object_define_property_non_writable_assignment_throws_in_strict() {
|
||||
let err = match eval("var o={}; Object.defineProperty(o,'x',{value:1}); (function(){'use strict'; o.x=2})()") {
|
||||
Ok(_) => panic!("expected assignment to fail"),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
@@ -90,16 +90,18 @@ fn proto_setter_called_both_write_paths() {
|
||||
assert_eq!(r, "2:0", "setter 被调 2 次(IC_SET 与 member 各 1),o 无 own 属性");
|
||||
}
|
||||
|
||||
/// 原型只读 data:两条写路径都按 ordinary_set 抛 TypeError。
|
||||
/// 严格模式原型只读 data:两条写路径都按 ordinary_set 抛 TypeError。
|
||||
#[test]
|
||||
fn readonly_inherited_data_raises() {
|
||||
fn readonly_inherited_data_raises_in_strict() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var p = {};
|
||||
Object.defineProperty(p, 'x', { value: 1, writable: false });
|
||||
var o = Object.create(p);
|
||||
var msg = '';
|
||||
try { o.x = 5; } catch (e) { msg = e.message; }
|
||||
try { o.x += 1; } catch (e) { msg += '|' + e.message; }
|
||||
(function() { 'use strict';
|
||||
try { o.x = 5; } catch (e) { msg = e.message; }
|
||||
try { o.x += 1; } catch (e) { msg += '|' + e.message; }
|
||||
})();
|
||||
msg"#,
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -108,6 +110,21 @@ fn readonly_inherited_data_raises() {
|
||||
);
|
||||
}
|
||||
|
||||
/// sloppy 原型只读 data:两条写路径都静默 no-op(值不变、不抛)。
|
||||
#[test]
|
||||
fn readonly_inherited_data_silently_fails_in_sloppy() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var p = {};
|
||||
Object.defineProperty(p, 'x', { value: 1, writable: false });
|
||||
var o = Object.create(p);
|
||||
var threw = false;
|
||||
try { o.x = 5; } catch (e) { threw = true; }
|
||||
try { o.x += 1; } catch (e) { threw = true; }
|
||||
threw + ':' + o.x + ':' + o.hasOwnProperty('x')"#,
|
||||
);
|
||||
assert_eq!(r, "false:1:false", "sloppy 只读继承属性写静默失败,值保持 1,不创建 own 属性");
|
||||
}
|
||||
|
||||
/// 写新属性(shape 转换):快路径建 shape + 写回,循环内同 shape 写全命中。
|
||||
#[test]
|
||||
fn write_new_prop_then_hit() {
|
||||
|
||||
@@ -89,16 +89,26 @@ fn accessor_getter_and_setter_still_trigger() {
|
||||
);
|
||||
}
|
||||
|
||||
// 非写属性(writable:false)写入:fast path 不命中,走完整协议抛 TypeError。
|
||||
// 非写属性(writable:false)写入(严格模式):fast path 不命中,走完整协议抛 TypeError。
|
||||
#[test]
|
||||
fn non_writable_index_write_throws() {
|
||||
fn non_writable_index_write_throws_in_strict() {
|
||||
let r = eval_str(
|
||||
"var a=[1,2,3]; Object.defineProperty(a,0,{writable:false,value:1}); try { a[0]=9; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"
|
||||
"var a=[1,2,3]; Object.defineProperty(a,0,{writable:false,value:1}); var r='no'; (function(){'use strict'; try { a[0]=9; } catch(e) { r = (e instanceof TypeError ? 'throws' : 'wrong'); } })(); r"
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r, "throws", "expected TypeError, got {r:?}");
|
||||
}
|
||||
|
||||
// 非写属性(writable:false)写入(sloppy):静默 no-op,值不变。
|
||||
#[test]
|
||||
fn non_writable_index_write_silently_fails_in_sloppy() {
|
||||
let r = eval_str(
|
||||
"var a=[1,2,3]; Object.defineProperty(a,0,{writable:false,value:1}); try { a[0]=9; 'no' } catch(e) { 'threw' }; a[0]"
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r, "1", "sloppy 只读元素写应静默 no-op,值保持 1,实际 {r:?}");
|
||||
}
|
||||
|
||||
// 非规范数字串(前导零)不映射整数键,走字符串键慢路径(不命中 fast path)。
|
||||
#[test]
|
||||
fn non_canonical_index_string_uses_slow_path() {
|
||||
|
||||
@@ -33,17 +33,30 @@ fn assert_err_contains(result: Result<JsValue, String>, expected: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 向不可写自身属性赋值抛 TypeError ──
|
||||
// ── 严格模式向不可写自身属性赋值抛 TypeError ──
|
||||
#[test]
|
||||
fn assign_to_non_writable_throws() {
|
||||
fn assign_to_non_writable_throws_in_strict() {
|
||||
let result = eval_many(&[
|
||||
"var obj = {}",
|
||||
"Object.defineProperty(obj, 'x', {value: 1, writable: false})",
|
||||
"obj.x = 2",
|
||||
"(function() { 'use strict'; obj.x = 2 })()",
|
||||
]);
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// ── sloppy 向不可写自身属性赋值静默失败(值不变、不抛) ──
|
||||
#[test]
|
||||
fn assign_to_non_writable_silently_fails_in_sloppy() {
|
||||
let r = eval_many(&[
|
||||
"var obj = {}",
|
||||
"Object.defineProperty(obj, 'x', {value: 1, writable: false})",
|
||||
"obj.x = 2",
|
||||
"obj.x",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 1, "sloppy 只读属性写应静默 no-op,值保持 1,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 向可写自身属性赋值成功 ──
|
||||
#[test]
|
||||
fn assign_to_writable_succeeds() {
|
||||
@@ -57,46 +70,87 @@ fn assign_to_writable_succeeds() {
|
||||
assert!(r.is_int() && r.as_int() == 2, "assign to writable should set value to 2, got {:?}", r);
|
||||
}
|
||||
|
||||
// ── 向原型上不可写的继承属性赋值抛错 ──
|
||||
// ── 严格模式向原型上不可写的继承属性赋值抛错 ──
|
||||
#[test]
|
||||
fn assign_to_inherited_non_writable_throws() {
|
||||
fn assign_to_inherited_non_writable_throws_in_strict() {
|
||||
let result = eval_many(&[
|
||||
"var proto = {}",
|
||||
"Object.defineProperty(proto, 'x', {value: 1, writable: false, enumerable: true, configurable: true})",
|
||||
"var child = Object.create(proto)",
|
||||
"child.x = 2",
|
||||
"(function() { 'use strict'; child.x = 2 })()",
|
||||
]);
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// ── 原型属性不可写但子对象无自有属性时,赋值会在子对象上创建自有属性
|
||||
// ponytail: spec allows shadowing when proto prop is configurable; engine doesn't yet.
|
||||
// 引擎对不可写原型属性正确抛错;完整遮蔽作为后续工作。
|
||||
// ── 继承只读数据属性:sloppy 赋值静默失败,不创建自有属性(不遮蔽) ──
|
||||
#[test]
|
||||
fn assign_non_writable_proto_configurable_prop_throws() {
|
||||
fn assign_to_inherited_non_writable_silently_fails_in_sloppy() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var proto = {}",
|
||||
"Object.defineProperty(proto, 'x', {value: 1, writable: false, enumerable: true, configurable: true})",
|
||||
"var child = Object.create(proto)",
|
||||
"child.x = 99",
|
||||
"[child.x, child.hasOwnProperty('x')]",
|
||||
]
|
||||
.join("; "),
|
||||
)
|
||||
.unwrap();
|
||||
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||
assert!(
|
||||
arr.get_prop_at(0).is_int() && arr.get_prop_at(0).as_int() == 1,
|
||||
"sloppy 写只读继承属性应静默 no-op,值保持 1"
|
||||
);
|
||||
assert!(
|
||||
arr.get_prop_at(1).is_bool() && !arr.get_prop_at(1).as_bool(),
|
||||
"静默失败不得在子对象上创建自有属性"
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格模式:原型只读可配置属性赋值抛错(不可写数据属性不可遮蔽) ──
|
||||
#[test]
|
||||
fn assign_non_writable_proto_configurable_prop_throws_in_strict() {
|
||||
let result = eval_many(&[
|
||||
"var proto = {}",
|
||||
"Object.defineProperty(proto, 'x', {value: 1, writable: false, enumerable: true, configurable: true})",
|
||||
"var child = Object.create(proto)",
|
||||
"child.x = 99",
|
||||
"(function() { 'use strict'; child.x = 99 })()",
|
||||
]);
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// ── Object.freeze 后写已有属性抛 TypeError ──
|
||||
// ── 严格模式 Object.freeze 后写已有属性抛 TypeError ──
|
||||
#[test]
|
||||
fn frozen_write_existing_prop_throws() {
|
||||
let result = eval_many(&["var o = Object.freeze({a: 1})", "o.a = 2"]);
|
||||
fn frozen_write_existing_prop_throws_in_strict() {
|
||||
let result = eval_many(&["var o = Object.freeze({a: 1})", "(function() { 'use strict'; o.a = 2 })()"]);
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// ── Object.freeze 后写新属性抛 TypeError ──
|
||||
// ── sloppy Object.freeze 后写已有属性静默失败 ──
|
||||
#[test]
|
||||
fn frozen_write_new_prop_throws() {
|
||||
let result = eval_many(&["var o = Object.freeze({a: 1})", "o.b = 2"]);
|
||||
fn frozen_write_existing_prop_silently_fails_in_sloppy() {
|
||||
let r = eval_many(&["var o = Object.freeze({a: 1})", "o.a = 2", "o.a"]).unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 1, "sloppy 冻结对象写应静默 no-op,值保持 1,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 严格模式 Object.freeze 后写新属性抛 TypeError ──
|
||||
#[test]
|
||||
fn frozen_write_new_prop_throws_in_strict() {
|
||||
let result = eval_many(&["var o = Object.freeze({a: 1})", "(function() { 'use strict'; o.b = 2 })()"]);
|
||||
assert_err_contains(result, "not extensible");
|
||||
}
|
||||
|
||||
// ── sloppy Object.freeze 后写新属性静默失败(属性不创建) ──
|
||||
#[test]
|
||||
fn frozen_write_new_prop_silently_fails_in_sloppy() {
|
||||
let r = eval_many(&["var o = Object.freeze({a: 1})", "o.b = 2", "o.b === undefined"]).unwrap();
|
||||
assert!(
|
||||
r.is_bool() && r.as_bool(),
|
||||
"sloppy 冻结对象写新属性应静默 no-op,属性不创建,实际 {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── Object.freeze 后 defineProperty 抛 TypeError ──
|
||||
#[test]
|
||||
fn frozen_define_property_throws() {
|
||||
@@ -104,13 +158,14 @@ fn frozen_define_property_throws() {
|
||||
assert_err_contains(result, "not extensible");
|
||||
}
|
||||
|
||||
// ── Object.seal 后写新属性抛错、删属性返回 false、改值生效 ──
|
||||
// ── 严格模式 Object.seal 后写新属性抛错、删属性返回 false、改值生效 ──
|
||||
#[test]
|
||||
fn sealed_semantics() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var o = Object.seal({a: 1})",
|
||||
"var new_throws = false; try { o.b = 2 } catch (e) { new_throws = true }",
|
||||
"var new_throws = false",
|
||||
"(function() { 'use strict'; try { o.b = 2 } catch (e) { new_throws = true } })()",
|
||||
"var del = delete o.a",
|
||||
"o.a = 5",
|
||||
"[new_throws, del, o.a, o.hasOwnProperty('a')]",
|
||||
@@ -121,30 +176,30 @@ fn sealed_semantics() {
|
||||
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||
assert!(
|
||||
arr.get_prop_at(0).is_bool() && arr.get_prop_at(0).as_bool(),
|
||||
"new prop write should throw"
|
||||
"strict new prop write should throw"
|
||||
);
|
||||
assert!(arr.get_prop_at(1).is_bool() && !arr.get_prop_at(1).as_bool(), "delete should be false");
|
||||
assert!(arr.get_prop_at(2).is_int() && arr.get_prop_at(2).as_int() == 5, "value write should work");
|
||||
assert!(arr.get_prop_at(3).is_bool() && arr.get_prop_at(3).as_bool(), "prop should remain own");
|
||||
}
|
||||
|
||||
// ── Object.preventExtensions 后新属性不生效(sloppy 抛错),已有属性可写 ──
|
||||
// ── Object.preventExtensions 后新属性写静默失效(sloppy),已有属性可写 ──
|
||||
#[test]
|
||||
fn prevent_extensions_semantics() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var o = Object.preventExtensions({a: 1})",
|
||||
"var new_throws = false; try { o.b = 3 } catch (e) { new_throws = true }",
|
||||
"var new_threw = false; try { o.b = 3 } catch (e) { new_threw = true }",
|
||||
"o.a = 2",
|
||||
"[new_throws, o.a, o.b === undefined]",
|
||||
"[new_threw, o.a, o.b === undefined]",
|
||||
]
|
||||
.join("; "),
|
||||
)
|
||||
.unwrap();
|
||||
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||
assert!(
|
||||
arr.get_prop_at(0).is_bool() && arr.get_prop_at(0).as_bool(),
|
||||
"new prop write should throw"
|
||||
arr.get_prop_at(0).is_bool() && !arr.get_prop_at(0).as_bool(),
|
||||
"sloppy new prop write should silently fail"
|
||||
);
|
||||
assert!(
|
||||
arr.get_prop_at(1).is_int() && arr.get_prop_at(1).as_int() == 2,
|
||||
@@ -153,15 +208,15 @@ fn prevent_extensions_semantics() {
|
||||
assert!(arr.get_prop_at(2).is_bool() && arr.get_prop_at(2).as_bool(), "new prop should be absent");
|
||||
}
|
||||
|
||||
// ── Object.freeze 数组:元素写与 length 收缩均被拦截 ──
|
||||
// ── 严格模式 Object.freeze 数组:元素写与 length 收缩均抛错 ──
|
||||
#[test]
|
||||
fn frozen_array_write_and_length_throws() {
|
||||
fn frozen_array_write_and_length_throws_in_strict() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var a = Object.freeze([1, 2])",
|
||||
"var e1 = false, e2 = false",
|
||||
"try { a[0] = 9 } catch (e) { e1 = true }",
|
||||
"try { a.length = 0 } catch (e) { e2 = true }",
|
||||
"(function() { 'use strict'; try { a[0] = 9 } catch (e) { e1 = true } })()",
|
||||
"(function() { 'use strict'; try { a.length = 0 } catch (e) { e2 = true } })()",
|
||||
"[e1, e2, a[0], a.length, Object.getOwnPropertyDescriptor(a, '0').writable]",
|
||||
]
|
||||
.join("; "),
|
||||
@@ -178,6 +233,27 @@ fn frozen_array_write_and_length_throws() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── sloppy Object.freeze 数组:元素写静默失败(length 冻结检查仍抛错) ──
|
||||
#[test]
|
||||
fn frozen_array_element_write_silently_fails_in_sloppy() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var a = Object.freeze([1, 2])",
|
||||
"var e1 = false",
|
||||
"try { a[0] = 9 } catch (e) { e1 = true }",
|
||||
"[e1, a[0]]",
|
||||
]
|
||||
.join("; "),
|
||||
)
|
||||
.unwrap();
|
||||
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||
assert!(
|
||||
arr.get_prop_at(0).is_bool() && !arr.get_prop_at(0).as_bool(),
|
||||
"sloppy 冻结数组元素写应静默 no-op"
|
||||
);
|
||||
assert!(arr.get_prop_at(1).is_int() && arr.get_prop_at(1).as_int() == 1, "element value unchanged");
|
||||
}
|
||||
|
||||
// ── isFrozen/isSealed:手动 defineProperty 全部冻结(未调 freeze)也应判 true ──
|
||||
#[test]
|
||||
fn is_frozen_manual_define_props() {
|
||||
@@ -212,19 +288,40 @@ fn reflect_ops_on_frozen_return_false() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── freeze/seal 后 IC 直写路径失效:循环写仍被拦截 ──
|
||||
// ── 严格模式 freeze 后 IC 直写路径失效:循环写仍被拦截 ──
|
||||
#[test]
|
||||
fn frozen_ic_path_still_blocks_write() {
|
||||
fn frozen_ic_path_still_blocks_write_in_strict() {
|
||||
let r = eval_many(&[
|
||||
"var o = Object.freeze({a: 1})",
|
||||
"var threw = false",
|
||||
"for (var i = 0; i < 3; i++) { try { o.a = i } catch (e) { threw = true } }",
|
||||
"(function() { 'use strict'; for (var i = 0; i < 3; i++) { try { o.a = i } catch (e) { threw = true } } })()",
|
||||
"threw",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "IC path should still throw on frozen write");
|
||||
}
|
||||
|
||||
// ── sloppy freeze 后循环写静默失效(值不变、不抛) ──
|
||||
#[test]
|
||||
fn frozen_ic_path_silently_fails_in_sloppy() {
|
||||
let (_vm, r) = eval_keep_vm(
|
||||
&[
|
||||
"var o = Object.freeze({a: 1})",
|
||||
"var threw = false",
|
||||
"for (var i = 0; i < 3; i++) { try { o.a = i } catch (e) { threw = true } }",
|
||||
"[threw, o.a]",
|
||||
]
|
||||
.join("; "),
|
||||
)
|
||||
.unwrap();
|
||||
let arr = unsafe { &*r.as_js_object_ptr() };
|
||||
assert!(
|
||||
arr.get_prop_at(0).is_bool() && !arr.get_prop_at(0).as_bool(),
|
||||
"sloppy frozen write should silently fail"
|
||||
);
|
||||
assert!(arr.get_prop_at(1).is_int() && arr.get_prop_at(1).as_int() == 1, "value unchanged");
|
||||
}
|
||||
|
||||
// ── sealed 对象 IC 路径:已有属性写正常 ──
|
||||
#[test]
|
||||
fn sealed_ic_path_allows_existing_write() {
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
//! sloppy 模式赋值静默失败语义测试:ordinary_set 失败分支(自身/继承只读数据、
|
||||
//! 无 setter 访问器、不可扩展新属性)在 sloppy 静默 no-op,严格模式抛 TypeError。
|
||||
//! 覆盖脚本顶层(`top_level_strict`)、函数级(帧 `strict`)与内联内置回调
|
||||
//! (forEach 等,`inline_strict` + 帧基线)三种执行上下文。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&module)
|
||||
}
|
||||
|
||||
fn eval_many(lines: &[&str]) -> Result<JsValue, String> {
|
||||
eval(&lines.join("; "))
|
||||
}
|
||||
|
||||
fn assert_err_contains(result: Result<JsValue, String>, expected: &str) {
|
||||
match result {
|
||||
Ok(_) => panic!("expected error containing '{}', got Ok", expected),
|
||||
Err(e) => assert!(e.contains(expected), "expected error containing '{}', got: {}", expected, e),
|
||||
}
|
||||
}
|
||||
|
||||
/// 求值并把结果按字符串提取(`JsValue` Display 不携带字符串内容)。
|
||||
fn eval_str(source: &str) -> String {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let mut vm = Vm::new();
|
||||
let result = vm.run(&module).expect("run");
|
||||
vm.lookup_str(result).unwrap_or_else(|| format!("{result}"))
|
||||
}
|
||||
|
||||
// ── sloppy 脚本顶层:无 setter 访问器赋值静默 no-op(值不变、不抛) ──
|
||||
#[test]
|
||||
fn sloppy_script_top_level_accessor_no_setter_silently_fails() {
|
||||
let r = eval_many(&["var o = { get h() { return 1 } }", "o.h = 2", "o.h"]).unwrap();
|
||||
assert!(
|
||||
r.is_int() && r.as_int() == 1,
|
||||
"sloppy 无 setter 访问器写应静默 no-op,值保持 1,实际 {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格脚本(顶层指令):无 setter 访问器赋值抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_script_top_level_accessor_no_setter_throws() {
|
||||
let result = eval("'use strict'; var o = { get h() { return 1 } }; o.h = 2");
|
||||
assert_err_contains(result, "no setter");
|
||||
}
|
||||
|
||||
// ── sloppy 脚本顶层:只读自身属性赋值静默失败 ──
|
||||
#[test]
|
||||
fn sloppy_script_top_level_readonly_silently_fails() {
|
||||
let r = eval("var o = {}; Object.defineProperty(o, 'x', {value: 1, writable: false}); o.x = 2; o.x").unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 1, "sloppy 只读属性写应静默 no-op,值保持 1,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 严格脚本(顶层指令):只读自身属性赋值抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_script_top_level_readonly_throws() {
|
||||
let result = eval("'use strict'; var o = {}; Object.defineProperty(o, 'x', {value: 1, writable: false}); o.x = 2");
|
||||
assert_err_contains(result, "read-only");
|
||||
}
|
||||
|
||||
// ── sloppy 函数级(非严格函数):不可扩展对象写新属性静默失败 ──
|
||||
#[test]
|
||||
fn sloppy_function_write_new_prop_on_non_extensible_silently_fails() {
|
||||
let r = eval_many(&[
|
||||
"var o = Object.preventExtensions({})",
|
||||
"function w() { o.n = 3 }",
|
||||
"w()",
|
||||
"o.n === undefined",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(
|
||||
r.is_bool() && r.as_bool(),
|
||||
"sloppy 函数写不可扩展对象新属性应静默 no-op,属性不创建,实际 {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 严格函数:不可扩展对象写新属性抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_function_write_new_prop_on_non_extensible_throws() {
|
||||
let result = eval_many(&["var o = Object.preventExtensions({})", "(function() { 'use strict'; o.n = 3 })()"]);
|
||||
assert_err_contains(result, "not extensible");
|
||||
}
|
||||
|
||||
// ── 继承只读数据属性:sloppy 赋值静默失败且不创建自有属性 ──
|
||||
#[test]
|
||||
fn sloppy_inherited_readonly_silently_fails_without_shadow() {
|
||||
let s = eval_str(
|
||||
"var p = {}; Object.defineProperty(p, 'x', {value: 1, writable: false, configurable: true}); \
|
||||
var c = Object.create(p); c.x = 9; c.x + ':' + c.hasOwnProperty('x')",
|
||||
);
|
||||
assert_eq!(s, "1:false", "sloppy 只读继承属性写应静默 no-op,值保持 1 且不遮蔽");
|
||||
}
|
||||
|
||||
// ── 内联回调(forEach):sloppy 顶层 + sloppy 回调,只读属性写静默失败 ──
|
||||
#[test]
|
||||
fn sloppy_inline_callback_write_to_readonly_silently_fails() {
|
||||
let r = eval_many(&[
|
||||
"var o = {}",
|
||||
"Object.defineProperty(o, 'p', {value: 0, writable: false, configurable: true})",
|
||||
"[1].forEach(function() { o.p = 1 })",
|
||||
"o.p",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(r.is_int() && r.as_int() == 0, "sloppy 内联回调内只读属性写应静默 no-op,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 内联回调:严格函数上下文(回调继承严格),只读属性写抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_inline_callback_write_to_readonly_throws() {
|
||||
let r = eval_many(&[
|
||||
"var o = {}",
|
||||
"Object.defineProperty(o, 'p', {value: 0, writable: false, configurable: true})",
|
||||
"var threw = false",
|
||||
"(function() { 'use strict'; [1].forEach(function() { try { o.p = 1 } catch (e) { threw = true } }) })()",
|
||||
"threw",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(r.is_bool() && r.as_bool(), "严格内联回调内只读属性写应抛 TypeError,实际 {:?}", r);
|
||||
}
|
||||
|
||||
// ── 嵌套内联:sloppy 回调内调用严格动态函数,写只读属性抛 TypeError
|
||||
// (严格性随内联快照逐层保存/恢复,内层取内层目标) ──
|
||||
#[test]
|
||||
fn nested_inline_strict_target_write_throws() {
|
||||
let r = eval_many(&[
|
||||
"var o = {}",
|
||||
"Object.defineProperty(o, 'p', {value: 0, writable: false, configurable: true})",
|
||||
"var strictWrite = new Function('o', \"'use strict'; o.p = 1\")",
|
||||
"var threw = false",
|
||||
"[1].forEach(function() { try { strictWrite(o) } catch (e) { threw = true } })",
|
||||
"threw",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(
|
||||
r.is_bool() && r.as_bool(),
|
||||
"嵌套内联中严格目标函数的只读属性写应抛 TypeError,实际 {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 嵌套内联反向:严格回调内调用 sloppy 动态函数,写只读属性静默失败 ──
|
||||
#[test]
|
||||
fn nested_inline_sloppy_target_write_silently_fails() {
|
||||
let r = eval_many(&[
|
||||
"var o = {}",
|
||||
"Object.defineProperty(o, 'p', {value: 0, writable: false, configurable: true})",
|
||||
"var sloppyWrite = new Function('o', 'o.p = 1')",
|
||||
"(function() { 'use strict'; [1].forEach(function() { sloppyWrite(o) }) })()",
|
||||
"o.p",
|
||||
])
|
||||
.unwrap();
|
||||
assert!(
|
||||
r.is_int() && r.as_int() == 0,
|
||||
"嵌套内联中 sloppy 目标函数的只读属性写应静默 no-op,实际 {:?}",
|
||||
r
|
||||
);
|
||||
}
|
||||
|
||||
// ── 链式赋值中间对象只读(冻结):sloppy 对中间对象写新属性静默失败 ──
|
||||
#[test]
|
||||
fn sloppy_chained_write_through_readonly_middle_silently_fails() {
|
||||
let s = eval_str(
|
||||
"var holder = Object.freeze({}); var o = { b: holder }; o.b.c = 1; \
|
||||
Object.keys(o.b).length + ':' + (o.b.c === undefined)",
|
||||
);
|
||||
assert_eq!(s, "0:true", "sloppy 链式中间冻结对象写新属性应静默 no-op,c 不创建");
|
||||
}
|
||||
|
||||
// ── 链式赋值中间对象只读(冻结):严格模式抛 TypeError ──
|
||||
#[test]
|
||||
fn strict_chained_write_through_readonly_middle_throws() {
|
||||
let result = eval_many(&[
|
||||
"var holder = Object.freeze({})",
|
||||
"var o = { b: holder }",
|
||||
"(function() { 'use strict'; o.b.c = 1 })()",
|
||||
]);
|
||||
assert_err_contains(result, "not extensible");
|
||||
}
|
||||
|
||||
// ── 顶层脚本 sloppy 赋值只读全局属性(undefined)不抛错 ──
|
||||
#[test]
|
||||
fn sloppy_top_level_assign_to_readonly_global_no_throw() {
|
||||
let r = eval_many(&["undefined = 1", "typeof undefined"]).unwrap();
|
||||
assert!(r.is_string(), "sloppy 顶层对只读全局属性赋值不得抛错,实际 {:?}", r);
|
||||
}
|
||||
Reference in New Issue
Block a user