-
-
Notifications
You must be signed in to change notification settings - Fork 132
fix(object): property-descriptor engine — named-array merge, accessor delete/set, symbol & non-extensible throws #5146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -887,6 +887,35 @@ pub extern "C" fn js_object_set_field_by_name( | |
| None | ||
| }; | ||
|
|
||
| // Accessor short-circuit — must precede the frozen/sealed and | ||
| // writable checks below: a property defined with a setter is invoked | ||
| // via [[Set]] regardless of the object's frozen/sealed state (freezing | ||
| // an accessor only clears [[Configurable]]; the setter still runs). A | ||
| // getter-only accessor is read-only. Hoisted above the sidecar + the | ||
| // linear-scan blocks so BOTH key-lookup paths honor it — previously the | ||
| // frozen check at the top of each block threw before the accessor was | ||
| // consulted (test262 | ||
| // assign/target-is-frozen-accessor-property-set-succeeds). | ||
| if ACCESSORS_IN_USE.with(|c| c.get()) { | ||
| if let Some(ref k) = incoming_key_str { | ||
| if let Some(acc) = get_accessor_descriptor(obj as usize, k) { | ||
| if acc.set != 0 { | ||
| let closure = (acc.set & crate::value::POINTER_MASK) | ||
| as *const crate::closure::ClosureHeader; | ||
| if !closure.is_null() { | ||
| let receiver = crate::value::js_nanbox_pointer(obj as i64); | ||
| let previous_this = super::js_implicit_this_set(receiver); | ||
| crate::closure::js_closure_call1(closure, value); | ||
| super::js_implicit_this_set(previous_this); | ||
| } | ||
| } else { | ||
| crate::error::throw_immutable_write(0, k); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+890
to
+917
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prototype-chain setter lookup is still missing from the ordinary write paths. These updates fix own-accessor ordering, but both sites still treat accessors as receiver-local only. Ordinary objects whose setter lives on a prototype recorded via
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
|
|
||
| // Search through the keys array for a match | ||
| let key_count = crate::array::js_array_length(keys) as usize; | ||
| let alloc_limit = std::cmp::max((*obj).field_count, 8) as usize; | ||
|
|
@@ -1038,32 +1067,13 @@ pub extern "C" fn js_object_set_field_by_name( | |
| // Found it - update the field. Frozen objects must | ||
| // throw a TypeError on writes to existing keys | ||
| // (issue #615 — strict-mode behavior, default for TS). | ||
| // Accessors were already handled by the hoisted short-circuit | ||
| // above; a key found here is a data property, so a frozen object | ||
| // throws on the write (issue #615 — strict-mode default for TS). | ||
| if is_frozen { | ||
| let key_str = key_to_str_for_diag(key); | ||
| crate::error::throw_immutable_write(0, &key_str); | ||
| } | ||
| // Accessor short-circuit: if a setter is registered, invoke | ||
| // it instead of writing the slot. A getter-only accessor is | ||
| // read-only under Perry's strict-by-default TS semantics. | ||
| if ACCESSORS_IN_USE.with(|c| c.get()) { | ||
| if let Some(ref k) = incoming_key_str { | ||
| if let Some(acc) = get_accessor_descriptor(obj as usize, k) { | ||
| if acc.set != 0 { | ||
| let closure = (acc.set & crate::value::POINTER_MASK) | ||
| as *const crate::closure::ClosureHeader; | ||
| if !closure.is_null() { | ||
| let receiver = crate::value::js_nanbox_pointer(obj as i64); | ||
| let previous_this = super::js_implicit_this_set(receiver); | ||
| crate::closure::js_closure_call1(closure, value); | ||
| super::js_implicit_this_set(previous_this); | ||
| } | ||
| } else { | ||
| crate::error::throw_immutable_write(0, k); | ||
| } | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| // Per-property writable check (set by Object.defineProperty / freeze). | ||
| // Issue #615 — strict-mode throw on read-only assign. | ||
| if PROPERTY_ATTRS_IN_USE.with(|c| c.get()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1278,7 +1278,9 @@ pub extern "C" fn js_object_define_property( | |
| // `[[DefineOwnProperty]]` trap, and throw a TypeError if it reports | ||
| // failure. (Proxy crash cluster.) | ||
| if crate::proxy::js_proxy_is_proxy(obj_value) != 0 { | ||
| if !value_is_object_like(descriptor_value) { | ||
| if !value_is_object_like(descriptor_value) | ||
| || crate::symbol::js_is_symbol(descriptor_value) != 0 | ||
| { | ||
|
Comment on lines
+1281
to
+1283
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t reject valid descriptor objects that aren’t Both guards still hinge on Also applies to: 1336-1338 🤖 Prompt for AI Agents |
||
| let desc = describe_value_for_type_error(descriptor_value); | ||
| throw_object_type_error_with_suffix( | ||
| "Property description must be an object: ", | ||
|
|
@@ -1328,7 +1330,12 @@ pub extern "C" fn js_object_define_property( | |
| if !target_is_class_ref && !value_is_object_like(obj_value) { | ||
| throw_object_type_error(b"Object.defineProperty called on non-object"); | ||
| } | ||
| if !value_is_object_like(descriptor_value) { | ||
| // A descriptor must be an Object; a Symbol is pointer-tagged but not an | ||
| // object, so `ToPropertyDescriptor(Symbol())` throws (test262 | ||
| // property-description-must-be-an-object-not-symbol). | ||
| if !value_is_object_like(descriptor_value) | ||
| || crate::symbol::js_is_symbol(descriptor_value) != 0 | ||
| { | ||
| let desc = describe_value_for_type_error(descriptor_value); | ||
| throw_object_type_error_with_suffix("Property description must be an object: ", &desc); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.