diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index ec62f7ac..8b14b537 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -7,42 +7,17 @@ use oxide_runtime_api::{NativeResult, VmHost}; pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30; /// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach; -/// `max_byte_length` 为 0 即定长缓冲;`immutable` 为字节缓冲写守卫标志。 +/// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存 +/// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。 /// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 三自由函数 /// (clone/size/drop)对整结构体操作,克隆须连标志位一并拷贝。 #[derive(Clone)] pub(crate) struct ArrayBufferPayload { pub(crate) data: Option>, - // 状态位:resizable 上限与 immutable 写守卫,当前读路径尚未消费。 - #[cfg_attr( - not(test), - expect( - dead_code, - reason = "状态位随构造器写入与克隆拷贝,消费方在 options/resize/detach 语义路径" - ) - )] pub(crate) max_byte_length: usize, - #[cfg_attr( - not(test), - expect( - dead_code, - reason = "状态位随构造器写入与克隆拷贝,消费方在 options/resize/detach 语义路径" - ) - )] pub(crate) immutable: bool, } -impl ArrayBufferPayload { - /// 构造定长已附着载荷(构造器/切片路径的默认形态)。 - fn attached(data: Vec) -> Self { - ArrayBufferPayload { - data: Some(data), - max_byte_length: 0, - immutable: false, - } - } -} - macro_rules! native_try { ($expr:expr) => { match $expr { @@ -56,15 +31,37 @@ fn type_error(vm: &mut H, msg: &str) -> NativeResult { NativeResult::Err(crate::error::create_type_error(vm, msg)) } +/// ToIndex:undefined → 0;ToInteger 传播式(NaN → +0、trunc、±Infinity +/// 保留);负值与 ToLength 夹取后不可同值表示(≥ 2^53,含 +Infinity)→ +/// RangeError。分配期上界(引擎上限)不在本阶段校验。 fn to_index(vm: &mut H, value: JsValue) -> Result { - let n = vm.coerce_number_bounded(value).unwrap_or(f64::NAN); - if n.is_nan() { + if value.is_undefined() { return Ok(0); } - if !n.is_finite() || n < 0.0 || n > MAX_ARRAY_BUFFER_LENGTH as f64 { + let n = match vm.coerce_number_bounded(value) { + Ok(n) => n, + Err(err) => return Err(crate::iterator::engine_error(vm, &err)), + }; + // ToInteger:NaN → +0,±Infinity 原样,有限值截断。 + let integer = if n.is_nan() { 0.0 } else { n.trunc() }; + if integer < 0.0 { return Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); } - Ok(n.trunc() as usize) + // ToLength 夹取到 2^53-1:≥ 2^53 的整数与夹取值不可同值,拒绝。 + if integer >= 9_007_199_254_740_992.0_f64 { + return Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + Ok(integer as usize) +} + +/// ToIntegerOrInfinity:ToNumber 传播式(用户转换异常原值恢复),NaN → +0、 +/// trunc,±Infinity 保留——界判由调用方上界比较收口。 +fn to_integer_or_infinity(vm: &mut H, value: JsValue) -> Result { + let n = match vm.coerce_number_bounded(value) { + Ok(n) => n, + Err(err) => return Err(crate::iterator::engine_error(vm, &err)), + }; + Ok(if n.is_nan() { 0.0 } else { n.trunc() }) } fn normalize_index(vm: &mut H, value: JsValue, len: usize) -> usize { @@ -85,12 +82,24 @@ fn normalize_index(vm: &mut H, value: JsValue, len: usize) -> usize { } } -pub(crate) fn new_array_buffer(vm: &mut H, data: Vec) -> *mut JsObject { - let proto = vm.session().builtin_world().array_buffer_proto.as_ptr() as *mut JsObject; - let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)); +/// 默认缓冲区原型 %ArrayBuffer.prototype%(构造器回落路径与各定长建点共用)。 +pub(crate) fn default_array_buffer_proto(vm: &H) -> JsValue { + JsValue::from_js_object(vm.session().builtin_world().array_buffer_proto.as_ptr() as *mut JsObject) +} + +/// 分配携给定 proto 与载荷形态(`max_byte_length` 为存储态:0 定长,非 0 可 +/// resize)的 ArrayBuffer 对象;byteLength 经原型访问器读(不写 own 数据属性)。 +pub(crate) fn new_array_buffer( + vm: &mut H, data: Vec, max_byte_length: usize, proto: JsValue, +) -> *mut JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; - // byteLength 经原型访问器读(构造器不写 own 数据属性,实例零命名属性)。 - let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload::attached(data))); + let payload = ArrayBufferPayload { + data: Some(data), + max_byte_length, + immutable: false, + }; + let payload_ptr = Box::into_raw(Box::new(payload)); // SAFETY: ArrayBuffer 对象不可调用,故 native_fn 槽复用作不透明载荷盒 // 指针,与既有 RegExp 存储模式一致。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); @@ -174,17 +183,96 @@ pub(crate) fn array_buffer_payload( Ok(payload_ptr) } -/// `ArrayBuffer(length)` 构造逻辑:分配 length 字节(0 填充)的新缓冲区。 +/// `ArrayBuffer(length [ , options ])` 构造逻辑。 +/// +/// # 步骤 +/// 1. 非构造形态(普通调用,new.target 缺失)→ TypeError。 +/// 2. length = ToIndex(length) 传播式;undefined/缺省 → 0。 +/// 3. GetArrayBufferMaxByteLengthOption:options 非对象 → 0;Get +/// "maxByteLength" 传播式;undefined → 0;否则 ToIndex 传播式。 +/// 4. length > max → RangeError(先于原型读与对象创建)。 +/// 5. GetPrototypeFromConstructor:经 ordinary_get 读 new.target 的 +/// "prototype"(访问器触发,异常原值传播);非对象结果回落 +/// %ArrayBuffer.prototype%。 +/// 6. 对象创建(定长/可 resize 由选项是否在场决定);分配期上界校验 +/// (length 与 max 各一条,超引擎上限 → RangeError)。 +/// 7. 选项在场 → own maxByteLength 数据属性 {w:0, e:0, c:0}(值为 ToIndex +/// 后整数)。 pub fn array_buffer_constructor(vm: &mut H, args: &[u8]) -> NativeResult { - let length = if args.len() > 1 { - match to_index(vm, vm.reg(args[1])) { - Ok(length) => length, - Err(err) => return NativeResult::Err(err), + if !vm.constructing_native() { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer must be called with new")); + } + let length = if args.len() > 1 { native_try!(to_index(vm, vm.reg(args[1]))) } else { 0 }; + // GetArrayBufferMaxByteLengthOption:None = 空(定长),Some(v) = 请求的 + // 上限(v 可为 0,与"空"语义相异:length > v 的界判与 own 属性均须生效)。 + let max_opt = if args.len() > 2 { + let options = vm.reg(args[2]); + if !options.is_object() { + None + } else { + let options_ptr = options.as_js_object_ptr(); + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let options_obj = unsafe { &*options_ptr }; + let si = vm.kernel_core().perm_interner().intern("maxByteLength").0; + let max_val = match vm.ordinary_get(options_obj, si, options) { + Ok(v) => v, + Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), + }; + if max_val.is_undefined() { + None + } else { + Some(native_try!(to_index(vm, max_val))) + } } } else { - 0 + None }; - NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, vec![0; length]))) + if let Some(max) = max_opt { + if length > max { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + } + // GetPrototypeFromConstructor:prototype getter 可抛错,异常原值传播; + // 结果非对象时回落默认原型。 + let default_proto = default_array_buffer_proto(vm); + let new_target = vm.reg(255); + let proto = if new_target.is_object() { + let nt_ptr = new_target.as_js_object_ptr(); + // SAFETY: is_object 保证指针非空且对象本 session 存活。 + let nt_obj = unsafe { &*nt_ptr }; + let proto_si = vm.kernel_core().perm_interner().intern("prototype").0; + let proto_val = match vm.ordinary_get(nt_obj, proto_si, new_target) { + Ok(v) => v, + Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), + }; + if proto_val.is_object() { + proto_val + } else { + default_proto + } + } else { + default_proto + }; + // 分配期上界:length 与 max 超引擎上限一律拒绝(ToIndex 阶段不校验)。 + if length > MAX_ARRAY_BUFFER_LENGTH || max_opt.is_some_and(|m| m > MAX_ARRAY_BUFFER_LENGTH) { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + // 存储态上限 = 请求上限 + 1(上限 0 与定长可分;定长存 0)。 + let obj_ptr = new_array_buffer(vm, vec![0; length], max_opt.map_or(0, |m| m + 1), proto); + // SAFETY: obj_ptr 为 alloc_object 新建对象,无别名。 + let obj = unsafe { &mut *obj_ptr }; + if let Some(max) = max_opt { + let si = vm.kernel_core().perm_interner().intern("maxByteLength").0; + if let Err(err) = vm.define_data_property( + obj, + si, + JsValue::int(max as i32), + oxide_types::object::PropAttributes::new(false, false, false), + ) { + return NativeResult::Err(crate::iterator::engine_error(vm, &err)); + } + } + NativeResult::Ok(JsValue::from_js_object(obj_ptr)) } /// `ArrayBuffer.prototype.byteLength` getter:返回缓冲区字节数。 @@ -198,6 +286,69 @@ pub fn array_buffer_byte_length(vm: &mut H, args: &[u8]) -> NativeRes NativeResult::Ok(JsValue::int(data.len() as i32)) } +/// `ArrayBuffer.prototype.resizable` getter:返回缓冲区是否可 resize +/// (载荷存储态上限非 0:0 = 定长,非 0 = 请求上限 + 1)。 +pub fn array_buffer_resizable(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷。 + let resizable = unsafe { &*payload_ptr }.max_byte_length != 0; + NativeResult::Ok(JsValue::bool(resizable)) +} + +/// `ArrayBuffer.prototype.resize(newLength)`:可 resize 缓冲区原地调整长度, +/// 前缀保留、增补零填充、缩小截断。 +/// +/// # 步骤 +/// 1. this 品牌校验(非对象/非 ArrayBuffer → TypeError)。 +/// 2. 载荷三状态位(detached / immutable / max)先于任何 JS 调用拷入局部 +/// 标量。 +/// 3. detached → TypeError(detach 路径由 transfer 族激活)。 +/// 4. immutable → TypeError。 +/// 5. newByteLength = ToIntegerOrInfinity(newLength) 传播式(NaN → 0, +/// ±Infinity 保留)。 +/// 6. 定长(存储态上限 = 0)→ TypeError,先于界判。 +/// 7. newByteLength < 0 或 > 真实上限(存储态 − 1)→ RangeError。 +/// 8. 重取载荷指针:步 5 的 JS 调用可触发 epoch 晋升,旧指针悬垂。 +/// 9. `Vec::resize` 一次调用收口目标长度。 +/// 10. 返回 undefined。 +pub fn array_buffer_resize(vm: &mut H, args: &[u8]) -> NativeResult { + let this_reg = if args.is_empty() { 0 } else { args[0] }; + let this_val = vm.reg(this_reg); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: payload_ptr 经 array_buffer_payload 校验为合法 ArrayBuffer 载荷; + // 三状态位拷出后本次借用即结束,不跨 JS 调用点。 + let (detached, immutable, max) = unsafe { + let payload = &*payload_ptr; + (payload.data.is_none(), payload.immutable, payload.max_byte_length) + }; + if detached { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer internal state invalid")); + } + if immutable { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer is immutable")); + } + let new_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let new_length = native_try!(to_integer_or_infinity(vm, new_val)); + if max == 0 { + return NativeResult::Err(crate::error::create_type_error(vm, "ArrayBuffer is not resizable")); + } + // 存储态上限 +1 编码:真实上限 = max - 1(上限 0 的缓冲区只许 resize(0))。 + let real_max = max - 1; + if new_length < 0.0 || new_length > real_max as f64 { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid ArrayBuffer length")); + } + // 步 5 的 JS 调用可能已把本缓冲区晋升进 session:接收者寄存器经晋升 + // 重写,须重新读取后再取载荷指针(旧 this 值可能指向已释放 epoch 对象)。 + let this_val = vm.reg(this_reg); + let payload_ptr = native_try!(array_buffer_payload(vm, this_val)); + // SAFETY: 重取的 payload_ptr 指向存活载荷盒;detached 已在步 3 排除, + // data 必为 Some。 + let payload = unsafe { &mut *payload_ptr }; + payload.data.as_mut().expect("detached 已排除").resize(new_length as usize, 0); + NativeResult::Ok(JsValue::undefined()) +} + /// `ArrayBuffer.prototype.slice(start, end)`:复制字节区间生成新 ArrayBuffer。 pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); @@ -210,7 +361,9 @@ pub fn array_buffer_slice(vm: &mut H, args: &[u8]) -> NativeResult { let start = if args.len() > 1 { normalize_index(vm, vm.reg(args[1]), len) } else { 0 }; let end = if args.len() > 2 { normalize_index(vm, vm.reg(args[2]), len) } else { len }; let end = end.max(start); - NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, data[start..end].to_vec()))) + // 切片产物恒定长,原型取默认 %ArrayBuffer.prototype%。 + let proto = default_array_buffer_proto(vm); + NativeResult::Ok(JsValue::from_js_object(new_array_buffer(vm, data[start..end].to_vec(), 0, proto))) } /// `ArrayBuffer.isView(value)`:参数是 DataView 或 TypedArray 才返回 true。 @@ -307,7 +460,11 @@ mod tests { #[test] fn attached_size_and_drop() { - let mut obj = ab_object_with_payload(ArrayBufferPayload::attached(vec![0u8; 8])); + let mut obj = ab_object_with_payload(ArrayBufferPayload { + data: Some(vec![0u8; 8]), + max_byte_length: 0, + immutable: false, + }); let bytes = (std::mem::size_of::() + 8) as u64; assert_eq!(array_buffer_native_size(&obj), bytes); assert_eq!(drop_array_buffer_native(&mut obj), bytes); @@ -318,7 +475,11 @@ mod tests { fn payload_ptr_brand_guard() { // 非 ArrayBuffer 类型标签(PLAIN)对象取载荷指针恒 None。 let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); - let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload::attached(vec![0u8; 4]))); + let payload_ptr = Box::into_raw(Box::new(ArrayBufferPayload { + data: Some(vec![0u8; 4]), + max_byte_length: 0, + immutable: false, + })); // SAFETY: 同 ab_object_with_payload 的载荷盒形态。 obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); assert!(array_buffer_payload_ptr(&obj).is_none()); diff --git a/crates/oxide_vm/src/bindings/bind_array_buffer.rs b/crates/oxide_vm/src/bindings/bind_array_buffer.rs index 99695c4f..72dec513 100644 --- a/crates/oxide_vm/src/bindings/bind_array_buffer.rs +++ b/crates/oxide_vm/src/bindings/bind_array_buffer.rs @@ -5,7 +5,7 @@ use crate::bindings::{ apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, configure_native_constructor, }; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; /// 把 ArrayBuffer 构造器与原型方法绑定到 global。 pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global: &mut JsObject) { @@ -47,6 +47,11 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global proto, core, &[ + ( + "resize", + oxide_builtins::array_buffer::array_buffer_resize:: as *const (), + 1, + ), ("slice", oxide_builtins::array_buffer::array_buffer_slice:: as *const (), 2), ( "toString", @@ -65,6 +70,15 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global oxide_builtins::array_buffer::array_buffer_byte_length:: as *const (), ); + // resizable 原型访问器(set 恒 undefined):读载荷 max_byte_length 状态位。 + bind_accessor_getter( + core, + session, + proto, + "resizable", + oxide_builtins::array_buffer::array_buffer_resizable:: as *const (), + ); + bind_constructor!( core, global, @@ -74,4 +88,9 @@ pub fn bind_array_buffer(core: &Arc, session: &KernelSession, global 1, hash: true ); + + // 宏推入的 length 槽默认可写可枚举,补钉规范描述符 + // { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }。 + let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); } diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 5350aa0e..9569b74d 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -743,7 +743,10 @@ fn session_gc_keeps_shared_array_buffer_alive_through_view_native_edges() { let root = plain_object(&mut vm); vm.regs[1] = JsValue::int(8); + // 构造形态标记夹持:原生直调构造器须模拟 NEW 臂的置位/恢复。 + vm.constructing_native = true; let buffer = native_ok(array_buffer::array_buffer_constructor(&mut vm, &[0, 1])); + vm.constructing_native = false; vm.regs[1] = buffer; let typed = native_ok(typed_array::int32array_constructor(&mut vm, &[0, 1])); @@ -803,7 +806,10 @@ fn session_gc_keeps_shared_array_buffer_alive_through_view_native_edges() { fn session_gc_rewrites_buffer_retained_only_by_data_view_native_edge() { let mut vm = vm_with_low_threshold(); vm.regs[1] = JsValue::int(8); + // 构造形态标记夹持:原生直调构造器须模拟 NEW 臂的置位/恢复。 + vm.constructing_native = true; let buffer = native_ok(array_buffer::array_buffer_constructor(&mut vm, &[0, 1])); + vm.constructing_native = false; vm.regs[1] = buffer; data_view_this(&mut vm, 0); let view = native_ok(data_view::data_view_constructor(&mut vm, &[0, 1])); diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index afc6bafb..bcd75c4b 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -69,3 +69,351 @@ fn ab_instance_has_no_own_bytelength() { let result = eval(&mut vm, "Object.getOwnPropertyNames(new ArrayBuffer(8)).length === 0").unwrap(); assert!(result.as_bool()); } + +/// 可 resize 实例 own maxByteLength 数据属性钉:描述符 {value, w:0, e:0, c:0}; +/// 定长实例无该 own 属性。 +#[test] +fn ab_ctor_options_own_max_property() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, {maxByteLength: 8}); \ + var d = Object.getOwnPropertyDescriptor(ab, 'maxByteLength'); \ + d !== undefined && d.value === 8 && d.writable === false \ + && d.enumerable === false && d.configurable === false \ + && Object.getOwnPropertyDescriptor(new ArrayBuffer(4), 'maxByteLength') === undefined", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// options 非对象钉:null/boolean/symbol/bigint/string/number/undefined 一律定长。 +#[test] +fn ab_ctor_options_non_object() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "[null, true, Symbol(3), 1n, 'string', 9, undefined] \ + .every(function (o) { return new ArrayBuffer(0, o).resizable === false; })", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// options.maxByteLength 为 undefined 或 options 为空对象 → 定长。 +#[test] +fn ab_ctor_options_max_undefined() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "new ArrayBuffer(0, {}).resizable === false \ + && new ArrayBuffer(0, {maxByteLength: undefined}).resizable === false", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// length > maxByteLength → RangeError。 +#[test] +fn ab_ctor_len_gt_max_range_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false; \ + try { new ArrayBuffer(1, {maxByteLength: 0}); } catch (e) { t1 = e instanceof RangeError; } \ + try { new ArrayBuffer(5, {maxByteLength: 4}); } catch (e) { t2 = e instanceof RangeError; } \ + t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// maxByteLength: 0 是合法可 resize 上限(非定长):resizable true、 +/// resize(0) 为 no-op、resize(1) 越界 RangeError。 +#[test] +fn ab_ctor_max_zero_is_resizable() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(0, {maxByteLength: 0}); \ + var ok1 = ab.resizable === true && ab.maxByteLength === 0; \ + ab.resize(0); \ + var ok2 = ab.byteLength === 0 && ab.resizable === true; \ + var threw = false; \ + try { ab.resize(1); } catch (e) { threw = e instanceof RangeError; } \ + ok1 && ok2 && threw", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// options.maxByteLength 经 ToIndex 强转:字符串 '8' → 8,分数 4.5 → 截断 4。 +#[test] +fn ab_ctor_max_coerced() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var s = new ArrayBuffer(4, {maxByteLength: '8'}); \ + var f = new ArrayBuffer(4, {maxByteLength: 4.5}); \ + s.resizable === true && s.maxByteLength === 8 \ + && f.resizable === true && f.maxByteLength === 4", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// options.maxByteLength 访问器抛错 → 原异常值传播。 +#[test] +fn ab_ctor_max_poisoned_propagates() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var options = { get maxByteLength() { throw new TypeError('poisoned'); } }; \ + var threw = false; \ + try { new ArrayBuffer(0, options); } catch (e) { threw = e instanceof TypeError && e.message === 'poisoned'; } \ + threw", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// options.maxByteLength 为 Symbol → TypeError;对象形强转序 valueOf → toString。 +#[test] +fn ab_ctor_max_symbol_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var threw = false; \ + try { new ArrayBuffer(0, {maxByteLength: Symbol()}); } catch (e) { threw = e instanceof TypeError; } \ + var log = []; \ + var opts = { maxByteLength: { \ + toString: function() { log.push('toString'); return {}; }, \ + valueOf: function() { log.push('valueOf'); return {}; } } }; \ + try { new ArrayBuffer(0, opts); } catch (e) {} \ + threw && log.length === 2 && log[0] === 'valueOf' && log[1] === 'toString'", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// length 参 ToIndex 语义钉:负分数截 0、字符串/NaN/分数按截断。 +#[test] +fn ab_ctor_length_toindex() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "new ArrayBuffer(-0.1).byteLength === 0 \ + && new ArrayBuffer('42').byteLength === 42 \ + && new ArrayBuffer(NaN).byteLength === 0 \ + && new ArrayBuffer(0.9).byteLength === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// length 参强转期抛错 → 原异常值传播。 +#[test] +fn ab_ctor_length_abort_propagates() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var len = { valueOf: function() { throw new RangeError('abort'); } }; \ + var threw = false; \ + try { new ArrayBuffer(len); } catch (e) { threw = e instanceof RangeError && e.message === 'abort'; } \ + threw", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 分配期上界钉:2^32(过 ToIndex 后撞引擎上限)与 2^53(ToLength 夹取失配)均 RangeError。 +#[test] +fn ab_ctor_length_alloc_limit() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false; \ + try { new ArrayBuffer(2 ** 32); } catch (e) { t1 = e instanceof RangeError; } \ + try { new ArrayBuffer(2 ** 53); } catch (e) { t2 = e instanceof RangeError; } \ + t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// GetPrototypeFromConstructor 钉:newTarget 为 Object → 原型取 Object.prototype; +/// newTarget.prototype 非对象 → 回落 %ArrayBuffer.prototype%。 +#[test] +fn ab_ctor_newtarget_proto() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "Object.getPrototypeOf(Reflect.construct(ArrayBuffer, [8], Object)) === Object.prototype \ + && (function () { function nt() {} nt.prototype = undefined; \ + return Object.getPrototypeOf(Reflect.construct(ArrayBuffer, [1], nt)) === ArrayBuffer.prototype; })()", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 普通调用形态(new.target 缺失)→ TypeError。 +#[test] +fn ab_ctor_call_throws() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false; \ + try { ArrayBuffer(); } catch (e) { t1 = e instanceof TypeError; } \ + try { ArrayBuffer(10); } catch (e) { t2 = e instanceof TypeError; } \ + t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 语义序钉:原型读先于分配期上界拒绝(7PiB length + 抛错 prototype getter)。 +#[test] +fn ab_ctor_proto_before_alloc() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "function DummyError() {} \ + var newTarget = function() {}.bind(null); \ + Object.defineProperty(newTarget, 'prototype', { get: function() { throw new DummyError(); } }); \ + var threw = false; \ + try { Reflect.construct(ArrayBuffer, [7 * 1125899906842624], newTarget); } catch (e) { threw = e instanceof DummyError; } \ + threw", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resize grow 钉:前缀保留 + 零填充,视图 live 见新长度。 +#[test] +fn ab_resize_grow_preserves_and_zeros() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(2, {maxByteLength: 5}); \ + var view = new Uint8Array(ab); view[0] = 1; view[1] = 2; \ + ab.resize(5); \ + var v2 = new Uint8Array(ab); \ + ab.byteLength === 5 && v2.length === 5 \ + && v2[0] === 1 && v2[1] === 2 && v2[2] === 0 && v2[3] === 0 && v2[4] === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resize shrink 钉:截断丢尾字节,再 grow 前缀保留 + 零填充。 +#[test] +fn ab_resize_shrink_truncate() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, {maxByteLength: 8}); \ + var view = new Uint8Array(ab); view[0] = 1; view[1] = 2; \ + ab.resize(1); \ + var ok1 = ab.byteLength === 1 && new Uint8Array(ab).length === 1; \ + ab.resize(4); \ + var v2 = new Uint8Array(ab); \ + ok1 && ab.byteLength === 4 && v2[0] === 1 && v2[1] === 0 && v2[2] === 0 && v2[3] === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resize(0) 不 detach 钉:byteLength 0、resizable 保持、slice 不抛、可再 grow。 +#[test] +fn ab_resize_zero_stays_attached() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(2, {maxByteLength: 4}); \ + ab.resize(0); \ + var ok1 = ab.byteLength === 0 && ab.resizable === true && ab.slice().byteLength === 0; \ + ab.resize(2); \ + ok1 && ab.byteLength === 2", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resize 界判钉:负值/超 max → RangeError;分数截断、NaN → 0。 +#[test] +fn ab_resize_range_checks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4, {maxByteLength: 8}); \ + var t1 = false, t2 = false; \ + try { ab.resize(-1); } catch (e) { t1 = e instanceof RangeError; } \ + try { ab.resize(9); } catch (e) { t2 = e instanceof RangeError; } \ + ab.resize(2.5); \ + var t3 = ab.byteLength === 2; \ + ab.resize(NaN); \ + var t4 = ab.byteLength === 0; \ + ab.resize(-0.5); \ + t1 && t2 && t3 && t4 && ab.byteLength === 0", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// 定长缓冲 resize 钉:0/3/4/5 四形均 TypeError,长度不变。 +#[test] +fn ab_resize_non_resizable_type_error() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var ab = new ArrayBuffer(4); \ + var t0 = false, t3 = false, t4 = false, t5 = false; \ + try { ab.resize(0); } catch (e) { t0 = e instanceof TypeError; } \ + try { ab.resize(3); } catch (e) { t3 = e instanceof TypeError; } \ + try { ab.resize(4); } catch (e) { t4 = e instanceof TypeError; } \ + try { ab.resize(5); } catch (e) { t5 = e instanceof TypeError; } \ + t0 && t3 && t4 && t5 && ab.byteLength === 4", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resize this 品牌校验钉:原型/undefined/{}/[] 四形均 TypeError。 +#[test] +fn ab_resize_this_checks() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var t1 = false, t2 = false, t3 = false, t4 = false; \ + try { ArrayBuffer.prototype.resize(); } catch (e) { t1 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.resize.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.resize.call({}); } catch (e) { t3 = e instanceof TypeError; } \ + try { ArrayBuffer.prototype.resize.call([]); } catch (e) { t4 = e instanceof TypeError; } \ + t1 && t2 && t3 && t4", + ) + .unwrap(); + assert!(result.as_bool()); +} + +/// resizable 访问器钉:描述符 {get 函数, set undefined, e:0, c:1}、name/length、 +/// 定长 false / 可 resize true、this 校验两形。 +#[test] +fn ab_resizable_accessor_pins() { + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var d = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'resizable'); \ + var ok = d.set === undefined && typeof d.get === 'function' \ + && d.enumerable === false && d.configurable === true \ + && d.get.name === 'get resizable' && d.get.length === 0; \ + var fixed = new ArrayBuffer(1); \ + var resizable = new ArrayBuffer(1, {maxByteLength: 1}); \ + var getter = d.get; \ + var t1 = false, t2 = false; \ + try { getter.call({}); } catch (e) { t1 = e instanceof TypeError; } \ + try { getter.call(undefined); } catch (e) { t2 = e instanceof TypeError; } \ + ok && fixed.resizable === false && resizable.resizable === true && t1 && t2", + ) + .unwrap(); + assert!(result.as_bool()); +}