diff --git a/src/engine/builtins/array.rs b/src/engine/builtins/array.rs index 1ab475c7..7b379d2b 100644 --- a/src/engine/builtins/array.rs +++ b/src/engine/builtins/array.rs @@ -10,6 +10,7 @@ use crate::engine::builtins::native::{ NativeFunctionId, }; use crate::engine::heap::{AutoInitProperty, ContextId, HeapError, ObjectData, PropertySlot}; +use crate::engine::object::builtin_properties::NativeBuiltinProperty; use crate::engine::object::operations::{ ArrayLengthConversion, ArrayOwnKey, InternalDefineResult, PropertyDefineOutcome, }; @@ -357,30 +358,11 @@ impl Runtime { array_iterator_prototype: &ObjectRef, global_object: &ObjectRef, ) -> Result<(), RuntimeError> { - self.define_native_builtin_auto_init( - array_prototype, - realm, - NativeFunctionId::ArrayPrototypeAt, - "at", - 1, - 1, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, - NativeFunctionId::ArrayPrototypeWith, - "with", - 2, - 2, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, - NativeFunctionId::ArrayPrototypeConcat, - "concat", - 1, - 0, - )?; + let mut methods = vec![ + NativeBuiltinProperty::new(NativeFunctionId::ArrayPrototypeAt, "at", 1, 1), + NativeBuiltinProperty::new(NativeFunctionId::ArrayPrototypeWith, "with", 2, 2), + NativeBuiltinProperty::new(NativeFunctionId::ArrayPrototypeConcat, "concat", 1, 0), + ]; for (kind, name) in [ (ArrayIterationKind::Every, "every"), (ArrayIterationKind::Some, "some"), @@ -388,89 +370,73 @@ impl Runtime { (ArrayIterationKind::Map, "map"), (ArrayIterationKind::Filter, "filter"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeIteration(kind), name, 1, 1, - )?; + )); } for (kind, name) in [ (ArrayReduceKind::Reduce, "reduce"), (ArrayReduceKind::ReduceRight, "reduceRight"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeReduce(kind), name, 1, 1, - )?; + )); } - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeFill, "fill", 1, 1, - )?; + )); for (kind, name) in [ (ArrayFindKind::Find, "find"), (ArrayFindKind::FindIndex, "findIndex"), (ArrayFindKind::FindLast, "findLast"), (ArrayFindKind::FindLastIndex, "findLastIndex"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeFind(kind), name, 1, 1, - )?; + )); } for (kind, name) in [ (ArraySearchKind::IndexOf, "indexOf"), (ArraySearchKind::LastIndexOf, "lastIndexOf"), (ArraySearchKind::Includes, "includes"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeSearch(kind), name, 1, 1, - )?; + )); } - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeJoin(ArrayJoinKind::Join), "join", 1, 1, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeToString, "toString", 0, 0, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeJoin(ArrayJoinKind::ToLocaleString), "toLocaleString", 0, 0, - )?; + )); for (target, name, length) in [ ( NativeFunctionId::ArrayPrototypePop(ArrayPopKind::Pop), @@ -493,97 +459,81 @@ impl Runtime { 1, ), ] { - self.define_native_builtin_auto_init(array_prototype, realm, target, name, length, 0)?; + methods.push(NativeBuiltinProperty::new(target, name, length, 0)); } - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeReverse, "reverse", 0, 0, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeToReversed, "toReversed", 0, 0, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeSort, "sort", 1, 1, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeToSorted, "toSorted", 1, 1, - )?; + )); for (kind, name) in [ (ArraySliceKind::Slice, "slice"), (ArraySliceKind::Splice, "splice"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeSlice(kind), name, 2, 2, - )?; + )); } - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeToSpliced, "toSpliced", 2, 2, - )?; - self.define_native_builtin_auto_init( - array_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeCopyWithin, "copyWithin", 2, 2, - )?; + )); for (kind, name, length, min_readable_args) in [ (ArrayFlattenKind::FlatMap, "flatMap", 1, 1), (ArrayFlattenKind::Flat, "flat", 0, 0), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeFlatten(kind), name, length, min_readable_args, - )?; + )); } for (kind, name) in [ (ArrayIteratorKind::Value, "values"), (ArrayIteratorKind::Key, "keys"), (ArrayIteratorKind::KeyAndValue, "entries"), ] { - self.define_native_builtin_auto_init( - array_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::ArrayPrototypeIterator(kind), name, 0, 0, - )?; + )); } + self.define_native_builtin_auto_init_batch(array_prototype, realm, methods)?; + self.define_native_builtin_auto_init( array_iterator_prototype, realm, @@ -619,30 +569,13 @@ impl Runtime { "Array", 1, )?; - self.define_native_builtin_auto_init( - constructor.as_object(), - realm, - NativeFunctionId::ArrayIsArray, - "isArray", - 1, - 1, - )?; - self.define_native_builtin_auto_init( - constructor.as_object(), - realm, - NativeFunctionId::ArrayFrom, - "from", - 1, - 3, - )?; - self.define_native_builtin_auto_init( - constructor.as_object(), - realm, - NativeFunctionId::ArrayOf, - "of", - 0, - 0, - )?; + let methods = [ + NativeBuiltinProperty::new(NativeFunctionId::ArrayIsArray, "isArray", 1, 1), + NativeBuiltinProperty::new(NativeFunctionId::ArrayFrom, "from", 1, 3), + NativeBuiltinProperty::new(NativeFunctionId::ArrayOf, "of", 0, 0), + ]; + self.define_native_builtin_auto_init_batch(constructor.as_object(), realm, methods)?; + self.define_constructor_relationship(&constructor, array_prototype)?; let getter = self.new_native_builtin( diff --git a/src/engine/builtins/array_buffer/typed_array.rs b/src/engine/builtins/array_buffer/typed_array.rs index d14c3cae..97c59fb0 100644 --- a/src/engine/builtins/array_buffer/typed_array.rs +++ b/src/engine/builtins/array_buffer/typed_array.rs @@ -14,6 +14,8 @@ use crate::engine::heap::{ ArrayBufferViewData, ObjectData, ObjectPayload, TypedArrayData, TypedArrayRealmData, }; +use crate::engine::object::builtin_properties::NativeBuiltinProperty; + use super::*; mod copying; @@ -89,22 +91,23 @@ impl Runtime { "length", "get length", )?; - self.define_native_builtin_auto_init( - &base_prototype, - realm, - NativeFunctionId::TypedArray(TypedArrayNativeKind::At), - "at", - 1, - 1, - )?; - self.define_native_builtin_auto_init( - &base_prototype, - realm, - NativeFunctionId::TypedArray(TypedArrayNativeKind::With), - "with", - 2, - 2, - )?; + // Publish before the next getter group to preserve own-key order. + let methods = [ + NativeBuiltinProperty::new( + NativeFunctionId::TypedArray(TypedArrayNativeKind::At), + "at", + 1, + 1, + ), + NativeBuiltinProperty::new( + NativeFunctionId::TypedArray(TypedArrayNativeKind::With), + "with", + 2, + 2, + ), + ]; + self.define_native_builtin_auto_init_batch(&base_prototype, realm, methods)?; + for (kind, name) in [ (TypedArrayNativeKind::Buffer, "buffer"), (TypedArrayNativeKind::ByteLength, "byteLength"), @@ -119,36 +122,30 @@ impl Runtime { &format!("get {name}"), )?; } - self.define_native_builtin_auto_init( - &base_prototype, - realm, + let mut methods = vec![NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Set), "set", 1, 2, - )?; + )]; for (kind, name) in [ (ArrayIteratorKind::Value, "values"), (ArrayIteratorKind::Key, "keys"), (ArrayIteratorKind::KeyAndValue, "entries"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Iterator(kind)), name, 0, 0, - )?; + )); } - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::CopyWithin), "copyWithin", 2, 2, - )?; + )); for (kind, name) in [ (ArrayIterationKind::Every, "every"), (ArrayIterationKind::Some, "some"), @@ -156,121 +153,103 @@ impl Runtime { (ArrayIterationKind::Map, "map"), (ArrayIterationKind::Filter, "filter"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Iteration(kind)), name, 1, 1, - )?; + )); } for (kind, name) in [ (ArrayReduceKind::Reduce, "reduce"), (ArrayReduceKind::ReduceRight, "reduceRight"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Reduce(kind)), name, 1, 1, - )?; + )); } - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Fill), "fill", 1, 1, - )?; + )); for (kind, name) in [ (ArrayFindKind::Find, "find"), (ArrayFindKind::FindIndex, "findIndex"), (ArrayFindKind::FindLast, "findLast"), (ArrayFindKind::FindLastIndex, "findLastIndex"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Find(kind)), name, 1, 1, - )?; + )); } - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Reverse), "reverse", 0, 0, - )?; - self.define_native_builtin_auto_init( - &base_prototype, - realm, + )); + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::ToReversed), "toReversed", 0, 0, - )?; + )); for (kind, name) in [ (TypedArrayNativeKind::Slice, "slice"), (TypedArrayNativeKind::Subarray, "subarray"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(kind), name, 2, 2, - )?; + )); } for (kind, name) in [ (TypedArrayNativeKind::Sort, "sort"), (TypedArrayNativeKind::ToSorted, "toSorted"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(kind), name, 1, 1, - )?; + )); } for (kind, name, length) in [ (ArrayJoinKind::Join, "join", 1), (ArrayJoinKind::ToLocaleString, "toLocaleString", 0), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Join(kind)), name, length, length, - )?; + )); } for (kind, name) in [ (ArraySearchKind::IndexOf, "indexOf"), (ArraySearchKind::LastIndexOf, "lastIndexOf"), (ArraySearchKind::Includes, "includes"), ] { - self.define_native_builtin_auto_init( - &base_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Search(kind)), name, 1, 1, - )?; + )); } + self.define_native_builtin_auto_init_batch(&base_prototype, realm, methods)?; + let base_constructor = self.new_native_builtin( function_prototype, realm, @@ -279,22 +258,22 @@ impl Runtime { "TypedArray", 0, )?; - self.define_native_builtin_auto_init( - base_constructor.as_object(), - realm, - NativeFunctionId::TypedArray(TypedArrayNativeKind::From), - "from", - 1, - 3, - )?; - self.define_native_builtin_auto_init( - base_constructor.as_object(), - realm, - NativeFunctionId::TypedArray(TypedArrayNativeKind::Of), - "of", - 0, - 0, - )?; + let methods = [ + NativeBuiltinProperty::new( + NativeFunctionId::TypedArray(TypedArrayNativeKind::From), + "from", + 1, + 3, + ), + NativeBuiltinProperty::new( + NativeFunctionId::TypedArray(TypedArrayNativeKind::Of), + "of", + 0, + 0, + ), + ]; + self.define_native_builtin_auto_init_batch(base_constructor.as_object(), realm, methods)?; + let species_getter = self.new_native_builtin( function_prototype, realm, @@ -333,21 +312,21 @@ impl Runtime { false, )?; if element == TypedArrayElementKind::Uint8 { + let mut methods = Vec::new(); for (kind, name, length, min_readable_args) in [ (Uint8ArrayCodecKind::ToBase64, "toBase64", 0, 1), (Uint8ArrayCodecKind::ToHex, "toHex", 0, 0), (Uint8ArrayCodecKind::SetFromBase64, "setFromBase64", 1, 2), (Uint8ArrayCodecKind::SetFromHex, "setFromHex", 1, 1), ] { - self.define_native_builtin_auto_init( - &prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Uint8Codec(kind)), name, length, min_readable_args, - )?; + )); } + self.define_native_builtin_auto_init_batch(&prototype, realm, methods)?; } let constructor = self.new_native_builtin( base_constructor.as_object(), @@ -365,19 +344,23 @@ impl Runtime { false, )?; if element == TypedArrayElementKind::Uint8 { + let mut methods = Vec::new(); for (kind, name, min_readable_args) in [ (Uint8ArrayCodecKind::FromBase64, "fromBase64", 2), (Uint8ArrayCodecKind::FromHex, "fromHex", 1), ] { - self.define_native_builtin_auto_init( - constructor.as_object(), - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::TypedArray(TypedArrayNativeKind::Uint8Codec(kind)), name, 1, min_readable_args, - )?; + )); } + self.define_native_builtin_auto_init_batch( + constructor.as_object(), + realm, + methods, + )?; } self.define_constructor_relationship(&constructor, &prototype)?; self.define_function_data_property( diff --git a/src/engine/builtins/date/mod.rs b/src/engine/builtins/date/mod.rs index 1fe133ed..32824522 100644 --- a/src/engine/builtins/date/mod.rs +++ b/src/engine/builtins/date/mod.rs @@ -10,6 +10,7 @@ mod parse; mod prototype; use super::*; +use crate::engine::object::builtin_properties::NativeBuiltinProperty; /// Side-effect-free ISO rendering used by the qjs diagnostic value printer. /// Invalid dates deliberately return `None`: pinned `JS_PrintValue` then falls @@ -87,6 +88,10 @@ impl Runtime { }; self.define_function_data_property(date_prototype, "toGMTString", utc_string, true, true)?; + // The UTC/GMT alias has already been materialized. The following + // table has no intermediate reads and can publish one final layout. + // The toUTCString read and toGMTString alias above are a publication boundary. + let mut methods = Vec::new(); for (kind, name) in [ ( DateNativeKind::String(DateStringMethod::IsoString), @@ -115,76 +120,64 @@ impl Runtime { (DateNativeKind::TimezoneOffset, "getTimezoneOffset"), (DateNativeKind::TimeValue, "getTime"), ] { - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(kind), name, kind.length(), kind.length(), - )?; + )); } for kind in DateGetFieldKind::ALL { let target = DateNativeKind::GetField(kind); - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(target), kind.name(), target.length(), target.length(), - )?; + )); } let set_time = DateNativeKind::SetTime; - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(set_time), "setTime", set_time.length(), set_time.length(), - )?; + )); for kind in DateSetFieldKind::ALL.into_iter().take(12) { let target = DateNativeKind::SetField(kind); - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(target), kind.name(), target.length(), target.length(), - )?; + )); } let set_year = DateNativeKind::SetYear; - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(set_year), "setYear", set_year.length(), set_year.length(), - )?; + )); for kind in [DateSetFieldKind::FullYear, DateSetFieldKind::UtcFullYear] { let target = DateNativeKind::SetField(kind); - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(target), kind.name(), target.length(), target.length(), - )?; + )); } let to_json = DateNativeKind::ToJson; - self.define_native_builtin_auto_init( - date_prototype, - realm, + methods.push(NativeBuiltinProperty::new( NativeFunctionId::Date(to_json), "toJSON", to_json.length(), to_json.length(), - )?; + )); + + self.define_native_builtin_auto_init_batch(date_prototype, realm, methods)?; let constructor_kind = DateNativeKind::Constructor; let constructor = self.new_native_builtin( diff --git a/src/engine/heap/runtime/README.md b/src/engine/heap/runtime/README.md index 8ef28fd9..56a44695 100644 --- a/src/engine/heap/runtime/README.md +++ b/src/engine/heap/runtime/README.md @@ -9,3 +9,4 @@ - [mod.rs](mod.rs):模块入口、共享接口与子模块声明。 - [tests/](tests/README.md):子模块职责与文件说明。 - [tests.rs](tests.rs):模块回归测试。 +- [builtin_batch_tests.rs](builtin_batch_tests.rs):内建方法批量发布、惰性属性和失败回滚测试。 diff --git a/src/engine/heap/runtime/builtin_batch_tests.rs b/src/engine/heap/runtime/builtin_batch_tests.rs new file mode 100644 index 00000000..86beba37 --- /dev/null +++ b/src/engine/heap/runtime/builtin_batch_tests.rs @@ -0,0 +1,310 @@ +use super::Runtime; +use crate::engine::builtins::native::{DateNativeKind, NativeFunctionId}; +use crate::engine::heap::{AutoInitProperty, PropertySlot, RawId, ShapeId}; +use crate::engine::object::ObjectRef; +use crate::engine::object::builtin_properties::NativeBuiltinProperty; +use crate::engine::object::shape::PropertyFlags; +use crate::engine::value::Value; + +fn method(name: &'static str) -> NativeBuiltinProperty { + NativeBuiltinProperty::new( + NativeFunctionId::Date(DateNativeKind::TimeValue), + name, + 2, + 3, + ) +} + +fn layout(runtime: &Runtime, object: &ObjectRef) -> (ShapeId, Vec) { + let state = runtime.0.state.borrow(); + let object = state.heap.object(object.object_id()).unwrap(); + (object.shape, object.slots.clone()) +} + +#[test] +fn builtin_batch_preserves_order_flags_metadata_and_lazy_identity() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + let object = runtime.new_object(None).unwrap(); + let mut second = method("batch_second"); + second.flags = PropertyFlags::data(false, true, false); + let methods = [method("batch_first"), second]; + runtime + .define_native_builtin_auto_init_batch(&object, context.realm, methods) + .unwrap(); + { + let state = runtime.0.state.borrow(); + let object = state.heap.object(object.object_id()).unwrap(); + let entries = state.heap.shape(object.shape).unwrap().entries(); + assert_eq!(entries.len(), 2); + for (index, method) in methods.iter().enumerate() { + assert_eq!( + state + .atoms + .to_js_string(entries[index].atom) + .unwrap() + .to_string(), + method.name + ); + assert_eq!(entries[index].flags, method.flags); + assert_eq!( + object.slots[index], + PropertySlot::AutoInit(AutoInitProperty::NativeBuiltin { + realm: context.realm, + target: method.target, + name: method.name, + length: method.length, + min_readable_args: method.min_readable_args, + }) + ); + } + } + let key = runtime.intern_property_key("batch_first").unwrap(); + let first = context.get_property(&object, &key).unwrap(); + assert!(matches!(first, Value::Object(_))); + assert_eq!(first, context.get_property(&object, &key).unwrap()); + assert!(matches!( + layout(&runtime, &object).1[1], + PropertySlot::AutoInit(_) + )); + let Value::Object(function) = first else { + unreachable!() + }; + let length = runtime.intern_property_key("length").unwrap(); + assert_eq!( + context.get_property(&function, &length).unwrap(), + Value::Int(2) + ); +} + +#[test] +fn builtin_batch_rejects_entire_invalid_table_without_leaking_keys() { + let runtime = Runtime::new(); + let context = runtime.new_context(); + let object = runtime.new_object(None).unwrap(); + runtime + .define_native_builtin_auto_init_batch(&object, context.realm, [method("batch_existing")]) + .unwrap(); + let original = layout(&runtime, &object); + let atoms = runtime.test_atom_count(); + let mut accessor = method("batch_accessor"); + accessor.flags = PropertyFlags::accessor(false, true); + for methods in [ + vec![method("batch_new"), method("batch_existing")], + vec![method("batch_new"), method("batch_new")], + vec![method("batch_new"), method("0")], + vec![method("batch_new"), accessor], + ] { + assert!( + runtime + .define_native_builtin_auto_init_batch(&object, context.realm, methods) + .is_err() + ); + assert_eq!(layout(&runtime, &object), original); + assert_eq!(runtime.test_atom_count(), atoms); + } + runtime + .define_native_builtin_auto_init_batch(&object, context.realm, []) + .unwrap(); + assert_eq!(layout(&runtime, &object), original); + runtime.prevent_extensions(&object).unwrap(); + assert!( + runtime + .define_native_builtin_auto_init_batch(&object, context.realm, [method("batch_new")]) + .is_err() + ); + assert_eq!(layout(&runtime, &object), original); +} + +#[test] +fn builtin_batch_validates_receiver_domain_and_realm_lifetime() { + let runtime = Runtime::new(); + let context = runtime.new_context(); + let other = Runtime::new(); + let foreign = other.new_object(None).unwrap(); + assert!( + runtime + .define_native_builtin_auto_init_batch( + &foreign, + context.realm, + [method("batch_foreign")] + ) + .is_err() + ); + let expired = runtime.new_context(); + let realm = expired.realm; + drop(expired); + runtime.run_gc().unwrap(); + let object = runtime.new_object(None).unwrap(); + let original = layout(&runtime, &object); + let atoms = runtime.test_atom_count(); + assert!( + runtime + .define_native_builtin_auto_init_batch(&object, realm, [method("batch_stale")]) + .is_err() + ); + assert_eq!(layout(&runtime, &object), original); + assert_eq!(runtime.test_atom_count(), atoms); +} + +#[test] +fn builtin_batch_rolls_back_shape_and_realm_edges_on_retain_overflow() { + let runtime = Runtime::new(); + let context = runtime.new_context(); + let object = runtime.new_object(None).unwrap(); + let original = layout(&runtime, &object); + let atoms = runtime.test_atom_count(); + let (strong, live, shapes) = { + let mut state = runtime.0.state.borrow_mut(); + let counts = state.heap.counts(); + let node = state + .heap + .live_node_mut(RawId::Context(context.realm)) + .unwrap(); + let strong = node.strong; + node.strong = u32::MAX; + (strong, counts.live, counts.shape_nodes) + }; + let result = runtime.define_native_builtin_auto_init_batch( + &object, + context.realm, + [method("batch_one"), method("batch_two")], + ); + { + let mut state = runtime.0.state.borrow_mut(); + let node = state + .heap + .live_node_mut(RawId::Context(context.realm)) + .unwrap(); + let after = node.strong; + node.strong = strong; + assert_eq!(after, u32::MAX); + assert_eq!(state.heap.counts().live, live); + assert_eq!(state.heap.counts().shape_nodes, shapes); + } + assert!(result.is_err()); + assert_eq!(layout(&runtime, &object), original); + assert_eq!(runtime.test_atom_count(), atoms); +} + +#[test] +fn builtin_batch_date_keeps_aliases_descriptors_and_realm_functions() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + assert_eq!( + context + .eval( + r#"(() => { + const p = Date.prototype; + const iso = p.toISOString; + const d = Object.getOwnPropertyDescriptor(p, 'setFullYear'); + return p.toGMTString === p.toUTCString && p.toGMTString.name === 'toUTCString' + && iso === p.toISOString && iso.name === 'toISOString' && iso.length === 0 + && d.writable && !d.enumerable && d.configurable && d.value.length === 3 + && new Date(0).toISOString() === '1970-01-01T00:00:00.000Z'; + })()"# + ) + .unwrap(), + Value::Bool(true) + ); +} + +#[test] +fn builtin_batch_keeps_context_functions_separate_on_a_shared_shape() { + let runtime = Runtime::new(); + let mut first = runtime.new_context(); + let mut second = runtime.new_context(); + let a = runtime.new_object(None).unwrap(); + let b = runtime.new_object(None).unwrap(); + runtime + .define_native_builtin_auto_init_batch(&a, first.realm, [method("batch_realm")]) + .unwrap(); + runtime + .define_native_builtin_auto_init_batch(&b, second.realm, [method("batch_realm")]) + .unwrap(); + assert_eq!(layout(&runtime, &a).0, layout(&runtime, &b).0); + let key = runtime.intern_property_key("batch_realm").unwrap(); + let a_function = first.get_property(&a, &key).unwrap(); + let b_function = second.get_property(&b, &key).unwrap(); + assert_ne!(a_function, b_function); + assert_eq!(a_function, second.get_property(&a, &key).unwrap()); + assert_eq!(b_function, first.get_property(&b, &key).unwrap()); + let Value::Object(a_function) = a_function else { + unreachable!() + }; + let Value::Object(b_function) = b_function else { + unreachable!() + }; + let Value::Object(first_prototype) = first.eval("Function.prototype").unwrap() else { + unreachable!() + }; + let Value::Object(second_prototype) = second.eval("Function.prototype").unwrap() else { + unreachable!() + }; + let state = runtime.0.state.borrow(); + for (function, prototype) in [ + (&a_function, &first_prototype), + (&b_function, &second_prototype), + ] { + let shape = state.heap.object(function.object_id()).unwrap().shape; + assert_eq!( + state.heap.shape(shape).unwrap().prototype(), + Some(prototype.object_id()) + ); + } +} + +#[test] +fn builtin_batch_rejects_exotic_receivers_without_observable_traps() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + for source in [ + "new Date(0)", + "new Uint8Array(2)", + "new Proxy({}, {defineProperty() { throw 42; }})", + ] { + let Value::Object(object) = context.eval(source).unwrap() else { + unreachable!() + }; + let original = layout(&runtime, &object); + let atoms = runtime.test_atom_count(); + assert!( + runtime + .define_native_builtin_auto_init_batch( + &object, + context.realm, + [method("batch_exotic")] + ) + .is_err() + ); + assert_eq!(layout(&runtime, &object), original); + assert_eq!(runtime.test_atom_count(), atoms); + } +} + +#[test] +fn builtin_batch_array_and_typed_array_keep_order_aliases_and_calls() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + assert_eq!( + context + .eval( + r#"(() => { + const p = Array.prototype; + const t = Object.getPrototypeOf(Uint8Array.prototype); + const keys = Object.getOwnPropertyNames(t); + const d = Object.getOwnPropertyDescriptor(t, 'set'); + return p.values === p[Symbol.iterator] && t.values === t[Symbol.iterator] + && t.toString === p.toString && Array.from.length === 1 + && Object.getPrototypeOf(Uint8Array).from.length === 1 + && keys.slice(0, 7).join(',') === 'length,at,with,buffer,byteLength,byteOffset,set' + && d.writable && !d.enumerable && d.configurable && d.value.length === 1 + && [3, 1, 2].toSorted().join() === '1,2,3' + && new Uint8Array([3, 1, 2]).toSorted().join() === '1,2,3' + && Uint8Array.fromHex('0aff').toHex() === '0aff'; + })()"# + ) + .unwrap(), + Value::Bool(true) + ); +} diff --git a/src/engine/heap/runtime/mod.rs b/src/engine/heap/runtime/mod.rs index 2fae94ba..fc563707 100644 --- a/src/engine/heap/runtime/mod.rs +++ b/src/engine/heap/runtime/mod.rs @@ -458,6 +458,9 @@ mod tests; #[cfg(test)] mod static_property_key_tests; +#[cfg(test)] +mod builtin_batch_tests; + use crate::engine::vm::frames::*; use crate::engine::object::operations::*; diff --git a/src/engine/object/README.md b/src/engine/object/README.md index f5dcf9cd..e4d38db7 100644 --- a/src/engine/object/README.md +++ b/src/engine/object/README.md @@ -11,6 +11,7 @@ - [arguments.rs](arguments.rs):QuickJS-compatible mapped and unmapped Arguments exotic objects.。 - [class.rs](class.rs):Class constructor/prototype publication.。 - [class_fields.rs](class_fields.rs):Public class-field property definition primitives.。 +- [builtin_properties.rs](builtin_properties.rs):在明确的初始化边界批量安装内建懒方法属性。 - [function_initialization.rs](function_initialization.rs):function_initialization 的类型和操作实现。 - [home_object.rs](home_object.rs):Bytecode-function HomeObject installation.。 - [internal_methods.rs](internal_methods.rs):Completion-aware ECMAScript internal-method dispatch.。 diff --git a/src/engine/object/builtin_properties.rs b/src/engine/object/builtin_properties.rs new file mode 100644 index 00000000..b13f09d9 --- /dev/null +++ b/src/engine/object/builtin_properties.rs @@ -0,0 +1,126 @@ +//! Batch installation of named lazy builtin methods at explicit bootstrap boundaries. + +use super::ObjectRef; +use super::shape::{PropertyFlags, PropertyStorageKind, ShapeEntry}; +use crate::engine::api::runtime::Runtime; +use crate::engine::api::runtime_error::RuntimeError; +use crate::engine::builtins::native::NativeFunctionId; +use crate::engine::heap::{AutoInitProperty, ContextId, HeapError, ObjectPayload, PropertySlot}; +use std::collections::HashSet; + +/// One named lazy method, keeping its descriptor and callable metadata together. +/// Symbol keys and alias materialization retain their explicit single-property paths. +#[derive(Clone, Copy)] +pub(crate) struct NativeBuiltinProperty { + pub(crate) target: NativeFunctionId, + pub(crate) name: &'static str, + pub(crate) length: u8, + pub(crate) min_readable_args: u8, + pub(crate) flags: PropertyFlags, +} + +impl NativeBuiltinProperty { + pub(crate) const fn new( + target: NativeFunctionId, + name: &'static str, + length: u8, + min_readable_args: u8, + ) -> Self { + Self { + target, + name, + length, + min_readable_args, + flags: PropertyFlags::data(true, false, true), + } + } +} + +impl Runtime { + /// Append an entire table without publishing intermediate layouts. + /// + /// This is a bootstrap primitive, not general [[DefineOwnProperty]]. It + /// accepts only extensible ordinary/native-function/Array receivers and + /// new non-index named data properties. No descriptor invokes user code. + /// Validation failures leave the receiver unchanged. Publication and any + /// subsequent invariant errors retain `replace_layout`'s existing contract. + pub(crate) fn define_native_builtin_auto_init_batch( + &self, + object: &ObjectRef, + realm: ContextId, + methods: impl IntoIterator, + ) -> Result<(), RuntimeError> { + let _operation = self.operation(); + // Consume descriptors and intern their keys before borrowing Runtime + // state. These owning keys outlive the state borrow on every exit. + let properties = methods + .into_iter() + .map(|method| { + self.intern_property_key(method.name) + .map(|key| (key, method)) + }) + .collect::, _>>()?; + if properties.is_empty() { + return Ok(()); + } + self.validate_object_and_key(object, &properties[0].0)?; + let mut state = self.0.state.borrow_mut(); + state.heap.context(realm)?; + let object_id = object.object_id(); + let (prototype, mut entries, mut slots) = { + let object = state.heap.object(object_id)?; + if !object.extensible + || !matches!( + object.payload, + ObjectPayload::Ordinary + | ObjectPayload::NativeFunction { .. } + | ObjectPayload::Array { .. } + ) + { + return Err(RuntimeError::Invariant("invalid builtin batch receiver")); + } + let shape = state.heap.shape(object.shape)?; + let mut seen = HashSet::with_capacity(properties.len()); + for (key, method) in &properties { + if method.flags.storage != PropertyStorageKind::Data + || state.atoms.array_index(key.atom())?.is_some() + || shape.find(key.atom()).is_some() + || !seen.insert(key.atom()) + { + return Err(RuntimeError::Invariant( + "invalid or duplicate builtin batch property", + )); + } + } + ( + shape.prototype(), + shape.entries().to_vec(), + object.slots.clone(), + ) + }; + entries + .try_reserve(properties.len()) + .map_err(|_| HeapError::Allocation { + operation: "preparing builtin batch shape entries", + })?; + slots + .try_reserve(properties.len()) + .map_err(|_| HeapError::Allocation { + operation: "preparing builtin batch property slots", + })?; + for (key, method) in &properties { + entries.push(ShapeEntry { + atom: key.atom(), + flags: method.flags, + }); + slots.push(PropertySlot::AutoInit(AutoInitProperty::NativeBuiltin { + realm, + target: method.target, + name: method.name, + length: method.length, + min_readable_args: method.min_readable_args, + })); + } + state.replace_layout(object_id, prototype, &entries, slots) + } +} diff --git a/src/engine/object/mod.rs b/src/engine/object/mod.rs index f22d5303..c051f78e 100644 --- a/src/engine/object/mod.rs +++ b/src/engine/object/mod.rs @@ -737,6 +737,7 @@ pub mod shape; pub(crate) mod allocation; +pub(crate) mod builtin_properties; pub(crate) mod function_initialization; pub(crate) mod access;