diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index b4a1cf00..0639022f 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -270,25 +270,16 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { (*wrapper).set_native_arg_count(bound_arg_count as u8); } - // length/name/caller/arguments 先定义占 shape 槽位 0..3(length/name 数据 + - // caller/arguments 访问器占位);绑定状态随后以独立状态对象存为第 4 号 shape - // 属性(保留键),不与用户 own 属性共享下标空间。 + // length/name 先定义占 shape 槽位 0..1;绑定状态随后以独立状态对象存为 + // 第 2 号 shape 属性(保留键),不与用户 own 属性共享下标空间。bound 函数 + // 无 own caller/arguments(BoundFunctionCreate 语义),经原型链继承 FP + // 受限访问器,访问即抛 TypeError。 let length_si = vm.kernel_core().perm_interner().intern("length").0; let name_si = vm.kernel_core().perm_interner().intern("name").0; - let caller_si = vm.kernel_core().perm_interner().intern("caller").0; - let arguments_si = vm.kernel_core().perm_interner().intern("arguments").0; let attrs = PropAttributes::new(false, false, true); - // bound 函数的 caller/arguments 是受限访问器:读写一律抛 TypeError(poisoned)。 - let thrower = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - unsafe { - (*thrower).set_function(true); - (*thrower).set_native_fn(Some(NativeFnPtr::from_raw(bound_restricted_thrower:: as *const ()))); - } - let thrower_val = JsValue::from_js_object(thrower); - - // 全部 shape 属性(length/name/caller/arguments)须先于状态数据定义完成, - // 保证 shape 槽位与 dense 下标对齐,再 push [target, thisArg, ...boundArgs]。 + // 全部 shape 属性(length/name)须先于状态数据定义完成,保证 shape 槽位 + // 与 dense 下标对齐,再 push [target, thisArg, ...boundArgs]。 unsafe { let wrapper_ref = &mut *wrapper; let target_obj = &*target_val.as_js_object_ptr(); @@ -319,17 +310,11 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { if let Err(e) = vm.define_data_property(wrapper_ref, name_si, name_val, attrs) { return NativeResult::Err(crate::error::create_type_error(vm, &e)); } - if let Err(e) = vm.define_accessor_property(wrapper_ref, caller_si, thrower_val, thrower_val, attrs) { - return NativeResult::Err(crate::error::create_type_error(vm, &e)); - } - if let Err(e) = vm.define_accessor_property(wrapper_ref, arguments_si, thrower_val, thrower_val, attrs) { - return NativeResult::Err(crate::error::create_type_error(vm, &e)); - } } // 绑定状态([target, thisArg, ...boundArgs])存独立状态对象:命名属性存储与 - // shape 槽位共享下标,状态值若直接裸推包装器存储槽 4+,用户新增 own 属性时 + // shape 槽位共享下标,状态值若直接裸推包装器存储槽 2+,用户新增 own 属性时 // shape 槽位与存储下标错位(读写互串)。状态对象内部存储不占 shape 槽位, - // 经保留键作为包装器固定第 4 号 shape 属性暴露,用户代码不可见。 + // 经保留键作为包装器固定第 2 号 shape 属性暴露,用户代码不可见。 let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let state = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); unsafe { @@ -348,43 +333,27 @@ pub fn function_bind(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(wrapper)) } -/// 读 bound 包装器固定第 4 号 shape 属性处的状态对象,返回其存储 +/// 读 bound 包装器固定第 2 号 shape 属性处的状态对象,返回其存储 /// [target, thisArg, ...boundArgs](缺位时返回空 vec)。 pub fn bound_state_values(wrapper: &JsObject) -> Vec { wrapper .hash_props_vec() - .and_then(|props| props.get(4).copied()) + .and_then(|props| props.get(2).copied()) .filter(|v| v.is_object() && !v.as_js_object_ptr().is_null()) .and_then(|sv| unsafe { &*sv.as_js_object_ptr() }.hash_props_vec().cloned()) .unwrap_or_default() } -/// bound 函数 caller/arguments 的受限访问器:任何访问(get/set)都抛 TypeError。 -fn bound_restricted_thrower(vm: &mut H, _args: &[u8]) -> NativeResult { +/// %ThrowTypeError%:受限属性访问器(Function.prototype 的 caller/arguments、 +/// unmapped arguments.callee)共用的内置函数,任何调用恒抛 TypeError。 +pub fn throw_type_error(vm: &mut H, _args: &[u8]) -> NativeResult { NativeResult::Err(crate::error::create_type_error( vm, - "'caller' and 'arguments' are restricted on bound functions", + "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode \ + functions or the arguments objects for calls to them", )) } -/// %ThrowTypeError%:Function.prototype 的 caller/arguments 受限访问器共用。 -/// -/// 按接收者受限性分流:严格函数对象与生成器 / 异步 / 异步生成器函数对象 -/// 的 get/set 一律抛 TypeError;非严格普通函数对象读不抛、返回 undefined -/// (写不抛、丢弃写入),与规范"非严格函数 caller 可读"的残面一致。 -pub fn function_restricted_thrower(vm: &mut H, args: &[u8]) -> NativeResult { - let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); - let obj = match vm.checked_object_ptr(this_val, "restricted property access") { - Ok(Some(obj)) => obj, - Ok(None) => return NativeResult::Err(crate::error::create_type_error(vm, "restricted property access")), - Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)), - }; - if !vm.function_is_restricted(unsafe { &*obj }) { - return NativeResult::Ok(JsValue::undefined()); - } - NativeResult::Err(crate::error::create_type_error(vm, "restricted property access")) -} - /// `Function.prototype.toString`:返回 `function name() { [native code] }` /// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。 pub fn function_to_string(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index 7d547676..5ad34415 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -68,6 +68,9 @@ pub struct CompiledModule { pub function_name: Option, /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 pub function_length: u32, + /// 形参列表是否 simple(全部无初始值标识符且无 rest):arguments 对象 + /// callee 形态(数据属性 vs 受限访问器)按 strict ‖ !simple 分流。 + pub has_simple_params: bool, /// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。 pub is_class_constructor: bool, /// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。 @@ -104,6 +107,7 @@ impl CompiledModule { captured_this_const_idx: 0, function_name: None, function_length: 0, + has_simple_params: false, is_class_constructor: false, is_derived_constructor: false, needs_home_object: false, @@ -138,6 +142,7 @@ impl Clone for CompiledModule { captured_this_const_idx: self.captured_this_const_idx, function_name: self.function_name.clone(), function_length: self.function_length, + has_simple_params: self.has_simple_params, is_class_constructor: self.is_class_constructor, is_derived_constructor: self.is_derived_constructor, needs_home_object: self.needs_home_object, diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index b2ccdca1..884e6b5a 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -120,6 +120,9 @@ pub struct CompileCtx { /// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。 /// emit_params_prologue 前由编译入口从 param_specs 计算。 pub(crate) function_length: u32, + /// 形参列表是否 simple(全部无初始值标识符且无 rest):arguments 对象 + /// callee 形态(数据属性 vs 受限访问器)按 strict ‖ !simple 分流。 + pub(crate) has_simple_params: bool, pub(crate) const_overflow: bool, /// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。 /// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。 @@ -250,6 +253,7 @@ impl CompileCtx { implicit_global_reads: HashSet::new(), implicit_global_writes: HashSet::new(), function_length: 0, + has_simple_params: false, const_overflow: false, with_stack: Vec::new(), open_try_handlers: Vec::new(), @@ -819,6 +823,7 @@ impl CompileCtx { captured_this_const_idx: 0, function_name: None, function_length: self.function_length, + has_simple_params: self.has_simple_params, is_top_level: parent_ctx.is_none(), const_overflow: self.const_overflow, nested: std::mem::take(&mut self.nested), diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 32c91c05..45767cf6 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -319,6 +319,14 @@ impl Emitter { }) .count() as u32; + // simple 参数列表:全部形参为无初始值标识符且无 rest(规范 + // simpleParameterList)。arguments 对象 callee 形态(数据属性 vs + // 受限访问器)按 strict ‖ !simple 分流,length 口径不可代用 + // (f({x}) 的 length 与 n_args 相等但非 simple)。 + ctx.has_simple_params = param_specs + .iter() + .all(|spec| matches!(spec, ParamSpec::Identifier { initializer: None, .. })); + // 继承父内置寄存器映射:子模块寄存器文件中,内置标识符(Math、Object 等) // 解析到父预先分配的槽位。 ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone(); diff --git a/crates/oxide_ir/src/lib.rs b/crates/oxide_ir/src/lib.rs index def2f310..dfbd78e6 100644 --- a/crates/oxide_ir/src/lib.rs +++ b/crates/oxide_ir/src/lib.rs @@ -61,6 +61,9 @@ pub struct IRFunction { pub function_name: Option, /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 pub function_length: u32, + /// 形参列表是否 simple(全部无初始值标识符且无 rest):arguments 对象 + /// callee 形态(数据属性 vs 受限访问器)按 strict ‖ !simple 分流。 + pub has_simple_params: bool, /// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE /// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由 /// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。 @@ -93,6 +96,7 @@ impl IRFunction { captured_this_const_idx: 0, function_name: None, function_length: 0, + has_simple_params: false, is_top_level: false, const_overflow: false, nested: Vec::new(), diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index c0624a1b..6501855a 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -90,6 +90,7 @@ pub fn lower(f: &IRFunction) -> Result { captured_this_const_idx: f.captured_this_const_idx, function_name: f.function_name.clone(), function_length: f.function_length, + has_simple_params: f.has_simple_params, is_class_constructor: f.is_class_constructor, is_derived_constructor: f.is_derived_constructor, needs_home_object: f.needs_home_object, diff --git a/crates/oxide_kernel/src/builtin/construct.rs b/crates/oxide_kernel/src/builtin/construct.rs index 19497b66..7b7a8bda 100644 --- a/crates/oxide_kernel/src/builtin/construct.rs +++ b/crates/oxide_kernel/src/builtin/construct.rs @@ -560,10 +560,13 @@ impl BuiltinWorld { let async_disposable_stack_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); // 默认迭代器指针初值空(绑定层随后经 bind_string 捕获写入)。 let string_default_iterator = std::cell::Cell::new(std::ptr::null()); + // %ThrowTypeError% 指针初值空(绑定层随后经 bind_function 捕获写入)。 + let throw_type_error = std::cell::Cell::new(std::ptr::null()); let world = Self { object_proto, string_default_iterator, + throw_type_error, array_proto, function_proto, string_proto, diff --git a/crates/oxide_kernel/src/builtin/rebuild.rs b/crates/oxide_kernel/src/builtin/rebuild.rs index 7b7be010..8cf5d8af 100644 --- a/crates/oxide_kernel/src/builtin/rebuild.rs +++ b/crates/oxide_kernel/src/builtin/rebuild.rs @@ -494,6 +494,13 @@ impl BuiltinWorld { } else { current.string_default_iterator.get() }), + // %ThrowTypeError% 原型链挂 Function.prototype:function 家族脏时 + // 置空由绑定层重建,未脏沿用旧对象(跨重建恒等)。 + throw_type_error: std::cell::Cell::new(if dirty.function { + std::ptr::null() + } else { + current.throw_type_error.get() + }), array_proto, function_proto, string_proto, diff --git a/crates/oxide_kernel/src/builtin/world.rs b/crates/oxide_kernel/src/builtin/world.rs index 2371b9fa..9388d0ca 100644 --- a/crates/oxide_kernel/src/builtin/world.rs +++ b/crates/oxide_kernel/src/builtin/world.rs @@ -132,6 +132,11 @@ pub struct BuiltinWorld { /// 同一对象),无法像集合迭代器那样复用"与迭代器原型槽比较"的判法, /// 故独立存一份指针。`Cell` 供绑定层经 `&Arc` 共享引用写入。 pub string_default_iterator: std::cell::Cell<*const JsObject>, + /// `%ThrowTypeError%` 函数对象裸指针:Function.prototype 的 caller/arguments + /// 受限访问器与 unmapped arguments.callee 访问器共享同一对象(跨形态恒等)。 + /// 绑定层安装访问器时写入,运行期创建 arguments 对象时读取;wrapper 本体归 + /// `leaked_objects` 释放登记表所有,session 收尾统一释放。 + pub throw_type_error: std::cell::Cell<*const JsObject>, /// `%RegExpStringIteratorPrototype%`:matchAll 返回的迭代器。 pub regexp_string_iterator_proto: P, /// `%AsyncIteratorPrototype%`:异步迭代器原型(链到 Object.prototype), diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index d2864628..9f2472c0 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -66,8 +66,14 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m } /// 在 Function.prototype 上绑定 caller/arguments 受限访问器:两属性的 -/// get/set 共用同一 %ThrowTypeError% 函数对象(name="get caller"、length=0), -/// 描述符 { enumerable:false, configurable:true }。 +/// get/set 共用同一 %ThrowTypeError% 函数对象(匿名、length=0、冻结、 +/// 不可扩展),描述符 { enumerable:false, configurable:true }。 +/// +/// # 步骤 +/// 1. 取共享 %ThrowTypeError% 对象:选择性重建复用(同键旧对象迁移), +/// 未命中时新建(length 先于 name、描述符三标志全 false)。 +/// 2. 对象指针写入 world Cell(运行期创建 arguments 对象时读取,跨形态恒等)。 +/// 3. 为 caller/arguments 各开 shape 槽位并写入访问器 meta。 /// /// # 副作用 /// - 为 caller/arguments 各开 shape 槽位并写入访问器 meta;thrower 函数对象 @@ -81,14 +87,13 @@ fn bind_function_proto_restricted(core: &Arc, session: &KernelSessio let si_length = string_forge.intern("length").0; let si_caller = string_forge.intern("caller").0; let si_arguments = string_forge.intern("arguments").0; - let si_label = string_forge.intern("get caller").0; + let si_label = string_forge.intern("ThrowTypeError").0; - // 构造单一 thrower 函数对象,供两属性 get/set 共用(选择性重建复用: - // 同家族同槽旧 thrower 迁移到新 proto 的访问器槽,不再新建对象)。 + // 取共享 %ThrowTypeError% 对象,供两属性 get/set 与 unmapped arguments.callee + // 访问器共用(选择性重建复用:同键旧对象迁移,不新建,跨形态恒等)。 // SAFETY: thrower 函数项指针转为 *const ()。 - let thrower_fn_ptr = unsafe { - NativeFnPtr::from_raw(oxide_builtins::function::function_restricted_thrower:: as *const ()) - }; + let thrower_fn_ptr = + unsafe { NativeFnPtr::from_raw(oxide_builtins::function::throw_type_error:: as *const ()) }; let reuse_key = FnWrapperKey::new(family, si_label, si_caller, si_name); let thrower_ptr = match world.find_fn_wrapper(reuse_key, thrower_fn_ptr, 0) { Some(ptr) => ptr, @@ -98,23 +103,29 @@ fn bind_function_proto_restricted(core: &Arc, session: &KernelSessio thrower.set_function(true); thrower.set_native_fn(Some(thrower_fn_ptr)); thrower.set_native_arg_count(0); - let name_shape = shape_forge.make_shape(thrower.shape_id(), si_name); - thrower.set_shape_id(name_shape); - thrower - .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(si_label))); - let name_pos = thrower.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - thrower.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + // length 先于 name(ownNames 序 ["length","name"]),描述符 + // { writable:false, enumerable:false, configurable:false }。 let length_shape = shape_forge.make_shape(thrower.shape_id(), si_length); thrower.set_shape_id(length_shape); thrower.ensure_hash_props().push(JsValue::int(0)); let length_pos = thrower.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - thrower.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + thrower.set_data_meta(length_pos, PropAttributes::new(false, false, false)); + let name_shape = shape_forge.make_shape(thrower.shape_id(), si_name); + thrower.set_shape_id(name_shape); + thrower + .ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern("").0))); + let name_pos = thrower.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + thrower.set_data_meta(name_pos, PropAttributes::new(false, false, false)); + thrower.set_extensible(false); + thrower.set_frozen(true); let ptr = Box::into_raw(thrower); world.track_fn_wrapper(ptr, reuse_key); ptr } }; + // 指针写入 world Cell:运行期创建 arguments 对象时读取,保证跨形态恒等。 + world.throw_type_error.set(thrower_ptr); let thrower_val = JsValue::from_js_object(thrower_ptr); let attrs = PropAttributes::new(false, false, true); for key in [si_caller, si_arguments] { diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index ec88c673..83cf93d5 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -265,13 +265,13 @@ impl Vm { } /// 创建 arguments 对象:索引属性取当前帧(或 inline 同步调用)的完整实参, - /// 附 length / callee / @@iterator 属性。第一版为 unmapped(非严格)语义, - /// 索引与形参不同步。 + /// 附 length / callee / @@iterator 属性。索引与形参不同步(mapped 同步语义 + /// 未实现);callee 按 strict ‖ !simple 分流为受限访问器或数据属性。 /// /// # 边界与前提 /// - 实参区由统一压帧入口 `push_bytecode_frame` 在推帧时写入 spill 栈; /// frames 为空(inline 同步调用)时读 `inline_args_*`。 - /// - 索引属性用 shape 槽存储(普通对象),length/callee 为不可枚举数据属性。 + /// - 索引属性用 shape 槽存储(普通对象),length 为不可枚举数据属性。 pub(crate) fn dispatch_create_arguments(&mut self, rd: usize) -> Result<(), String> { let (base, count) = match self.frames.last() { Some(frame) => (frame.arguments_base, frame.arguments_count), @@ -301,13 +301,66 @@ impl Vm { return self.raise_error_kind("TypeError", &msg); } - // callee:当前执行函数,可写、不可枚举、可配置(严格模式应抛 TypeError,未支持)。 + // callee:按 strict 与 simple 参数列表四分流(规范 CreateMappedArgumentsObject / + // CreateUnmappedArgumentsObject):strict ‖ !simple → 受限访问器(get/set + // 共享 %ThrowTypeError%,访问即抛);sloppy simple → 数据属性(值 = 当前 + // 函数本身,可写、不可枚举、可配置)。 let callee_si = self.kernel_core.perm_interner().intern("callee").0; let callee = self.current_callee().unwrap_or(JsValue::undefined()); - if let Err(msg) = self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true)) { + let strict = self.current_strict(); + let simple = self.active_module().is_some_and(|m| m.has_simple_params); + if strict || !simple { + // SAFETY: 指针由绑定层在 session 构造期写入,session 存活期内有效。 + let thrower_ptr = self.session.builtin_world().throw_type_error.get(); + if thrower_ptr.is_null() { + // 绑定层尚未写入共享对象(正常路径不出现):退化为数据属性保调用可用。 + if let Err(msg) = + self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true)) + { + return self.raise_error_kind("TypeError", &msg); + } + } else { + // SAFETY: thrower_ptr 由绑定层写入的存活对象,转 *mut 供 from_js_object 消费。 + let thrower_val = JsValue::from_js_object(unsafe { &*thrower_ptr } as *const JsObject as *mut JsObject); + if let Err(msg) = self.define_accessor_property( + obj, + callee_si, + thrower_val, + thrower_val, + PropAttributes::new(false, false, false), + ) { + return self.raise_error_kind("TypeError", &msg); + } + } + } else if let Err(msg) = + self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true)) + { return self.raise_error_kind("TypeError", &msg); } + // sloppy 函数:own caller/arguments 数据属性(ES5 遗留,node 行为)—— + // caller = 调用方函数(帧栈次顶帧 callee),arguments = 本对象。strict + // 函数无 own caller/arguments,经原型链解析到 FP 受限访问器(访问即抛)。 + if !strict && callee.is_object() { + let caller = self + .frames + .get(self.frames.len().saturating_sub(2)) + .map(|f| f.callee) + .unwrap_or(JsValue::undefined()); + let callee_obj = unsafe { &mut *callee.as_js_object_ptr() }; + let caller_si = self.kernel_core.perm_interner().intern("caller").0; + let arguments_si = self.kernel_core.perm_interner().intern("arguments").0; + let data_attrs = PropAttributes::new(true, false, true); + if let Err(msg) = self.define_data_property(callee_obj, caller_si, caller, data_attrs) { + return self.raise_error_kind("TypeError", &msg); + } + if let Err(msg) = + self.define_data_property(callee_obj, arguments_si, JsValue::from_js_object(obj_ptr), data_attrs) + { + return self.raise_error_kind("TypeError", &msg); + } + } + // @@iterator:与 Array.prototype[Symbol.iterator] 共享同一函数对象, // 使 arguments 对象可迭代(可写、不可枚举、可配置)。 let sym_iter_si = make_well_known_symbol_key(0);