Repository navigation
fix(intl): #5904 — %Intl%.[[FallbackSymbol]] legacy-constructed chain (NumberFormat/DateTimeFormat) #5956
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
fix(intl): #5904 — %Intl%.[[FallbackSymbol]] legacy-constructed chain (NumberFormat/DateTimeFormat) #5956
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 |
|---|---|---|
|
|
@@ -3,18 +3,19 @@ | |
| //! `intl.rs` to keep that file under the repository's 2,000-line gate. | ||
|
|
||
| use crate::closure::ClosureHeader; | ||
| use crate::object::{js_object_get_field_by_name_f64, ObjectHeader}; | ||
| use crate::object::{js_object_get_field_by_name_f64, ObjectHeader, PropertyAttrs}; | ||
| use crate::string::js_string_from_bytes; | ||
| use crate::value::JSValue; | ||
|
|
||
| const POINTER_TAG: u64 = 0x7FFD_0000_0000_0000; | ||
| const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; | ||
|
|
||
| /// `GetPrototypeFromConstructor(new.target, "%<Ctor>Prototype%")`: a | ||
| /// `Reflect.construct(Intl.X, args, CustomCtor)` call should install | ||
| /// `CustomCtor.prototype` on the result (test262 `ctor-custom-prototype.js`), | ||
| /// falling back to the invoked closure's own `"prototype"` when `new.target` | ||
| /// is absent (a bare `new Intl.X()`) or its `prototype` isn't an object. | ||
| pub(super) fn constructor_target_prototype(closure: *const ClosureHeader) -> f64 { | ||
| const POINTER_TAG: u64 = 0x7FFD_0000_0000_0000; | ||
| const POINTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; | ||
| let new_target = crate::object::js_new_target_get(); | ||
| let bits = new_target.to_bits(); | ||
| if (bits & !POINTER_MASK) == POINTER_TAG { | ||
|
|
@@ -38,3 +39,109 @@ pub(super) fn require_new_target(name: &str) { | |
| super::throw_type_error(&format!("Constructor Intl.{name} requires 'new'")); | ||
| } | ||
| } | ||
|
|
||
| /// The immediate `[[Prototype]]` pointer of an object at `obj_ptr`, resolved | ||
| /// across Perry's two prototype-recording mechanisms: | ||
| /// * the static-prototype side table (`object_set_static_prototype`, used by | ||
| /// Intl instances and `Object.setPrototypeOf`), and | ||
| /// * a synthetic `class_id` → `CLASS_PROTOTYPE_OBJECTS` entry (used by | ||
| /// `Object.create(proto)`). | ||
| /// Returns `None` when the object has its default `Object.prototype` (or a null | ||
| /// prototype), which is never a service constructor's own `.prototype`. | ||
| fn object_proto_ptr(obj_ptr: usize) -> Option<usize> { | ||
| if let Some(proto_bits) = crate::object::prototype_chain::object_static_prototype(obj_ptr) { | ||
| if (proto_bits & !POINTER_MASK) == POINTER_TAG { | ||
| let p = (proto_bits & POINTER_MASK) as usize; | ||
| return (p != 0).then_some(p); | ||
| } | ||
| return None; | ||
| } | ||
| let class_id = unsafe { | ||
| let obj = obj_ptr as *const ObjectHeader; | ||
| if !crate::object::is_valid_obj_ptr(obj as *const u8) { | ||
| return None; | ||
| } | ||
| (*obj).class_id | ||
| }; | ||
| if class_id == 0 { | ||
| return None; | ||
| } | ||
| let proto = crate::object::class_prototype_object(class_id); | ||
| (!proto.is_null()).then_some(proto as usize) | ||
| } | ||
|
|
||
| /// True when `receiver`'s prototype chain contains `Ctor.prototype` — the | ||
| /// `OrdinaryHasInstance(constructor, receiver)` test used by ChainNumberFormat / | ||
| /// ChainDateTimeFormat. Walks the resolved prototype chain (bounded to avoid a | ||
| /// cyclic-proto hang). | ||
| fn receiver_on_constructor_chain(receiver: f64, closure: *const ClosureHeader) -> bool { | ||
| let ctor_proto = crate::closure::closure_get_dynamic_prop(closure as usize, "prototype"); | ||
| let ctor_proto_bits = ctor_proto.to_bits(); | ||
| if (ctor_proto_bits & !POINTER_MASK) != POINTER_TAG { | ||
| return false; | ||
| } | ||
| let ctor_proto_ptr = (ctor_proto_bits & POINTER_MASK) as usize; | ||
| if ctor_proto_ptr == 0 { | ||
| return false; | ||
| } | ||
| let mut cur = (receiver.to_bits() & POINTER_MASK) as usize; | ||
| for _ in 0..64 { | ||
| match object_proto_ptr(cur) { | ||
| Some(proto_ptr) => { | ||
| if proto_ptr == ctor_proto_ptr { | ||
| return true; | ||
| } | ||
| cur = proto_ptr; | ||
| } | ||
| None => return false, | ||
| } | ||
| } | ||
| false | ||
| } | ||
|
|
||
| /// ChainNumberFormat / ChainDateTimeFormat (ECMA-402 normative-optional): when a | ||
| /// legacy Intl service constructor is invoked as a *plain function* (no `new`) | ||
| /// whose `this` is an object on the constructor's prototype chain, stash the | ||
| /// freshly-built internal `instance` under `%Intl%.[[FallbackSymbol]]` on `this` | ||
| /// (a non-writable / non-enumerable / non-configurable data property) and return | ||
| /// `this`. Returns `Some(this)` when the chain applied, else `None` so the caller | ||
| /// returns the plain instance. | ||
| /// | ||
| /// V8/Node implement this for `NumberFormat` and `DateTimeFormat` only — | ||
| /// `Intl.Collator` ignores its this-value and always returns a fresh object | ||
| /// (test262 `Collator/this-value-ignored.js`), so the caller must not invoke | ||
| /// this for the Collator kind. | ||
| pub(super) fn chain_legacy_constructed( | ||
| closure: *const ClosureHeader, | ||
| instance: f64, | ||
| ) -> Option<f64> { | ||
| // Only the legacy path: a `new`-invocation (new.target present) always | ||
| // returns the fresh instance. | ||
| if crate::object::js_new_target_get().to_bits() != crate::value::TAG_UNDEFINED { | ||
| return None; | ||
| } | ||
| let this_value = crate::object::js_implicit_this_get(); | ||
| let this_bits = this_value.to_bits(); | ||
| // `this` must be an Object (pointer-tagged, valid heap object). | ||
| if (this_bits & !POINTER_MASK) != POINTER_TAG { | ||
| return None; | ||
| } | ||
| let this_ptr = (this_bits & POINTER_MASK) as usize; | ||
| if this_ptr == 0 || !crate::object::is_valid_obj_ptr(this_ptr as *const u8) { | ||
| return None; | ||
| } | ||
| if !receiver_on_constructor_chain(this_value, closure) { | ||
| return None; | ||
| } | ||
| let fallback_sym = crate::symbol::intl_legacy_constructed_symbol(); | ||
| unsafe { | ||
| crate::symbol::js_object_set_symbol_property(this_value, fallback_sym, instance); | ||
| } | ||
| let sym_key = (fallback_sym.to_bits() & POINTER_MASK) as usize; | ||
| crate::symbol::set_symbol_property_attrs( | ||
| this_ptr, | ||
| sym_key, | ||
| PropertyAttrs::new(false, false, false), | ||
| ); | ||
|
Comment on lines
+136
to
+145
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. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find the descriptor-aware property definition APIs to reuse for symbol keys.
rg -n -C4 'DefineProperty|define_property|PropertyDescriptor|non_configurable|is_extensible|preventExtensions|set_symbol_property_attrs' crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the target context and the symbol property helpers.
sed -n '1,220p' crates/perry-runtime/src/intl/ctor_guard.rs
printf '\n--- symbol properties ---\n'
sed -n '1,220p' crates/perry-runtime/src/symbol/properties.rs
printf '\n--- ordinary define-property path ---\n'
sed -n '146,220p' crates/perry-runtime/src/object/reflect_support.rs
printf '\n--- object symbol helpers ---\n'
rg -n -C3 'js_object_set_symbol_property|set_symbol_property_attrs|reflect_define_property|js_object_define_property|PropertyAttrs::new' crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the concrete symbol write path and the ordinary define-property implementation.
sed -n '360,430p' crates/perry-runtime/src/symbol/properties.rs
printf '\n---\n'
rg -n -C4 'pub extern "C" fn js_object_define_property|pub(crate) fn js_object_define_property|define_property' crates/perry-runtime/src/object crates/perry-runtime/src/symbol
printf '\n---\n'
sed -n '1,260p' crates/perry-runtime/src/object/mod.rsRepository: PerryTS/perry Length of output: 48096 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the symbol write helper internals and the define-property symbol handling.
sed -n '240,370p' crates/perry-runtime/src/symbol/properties.rs
printf '\n---\n'
sed -n '110,220p' crates/perry-runtime/src/object/object_ops/define_property.rs
printf '\n---\n'
sed -n '720,920p' crates/perry-runtime/src/object/object_ops/define_property.rsRepository: PerryTS/perry Length of output: 22082 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find where the Intl fallback symbol is created and read back.
rg -n -C3 'intl_legacy_constructed_symbol|legacy_constructed_symbol|FallbackSymbol|legacy_constructed' crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 7345 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the symbol-key branch inside Object.defineProperty.
rg -n -C6 'js_is_symbol|symbol key|sym_key|set_symbol_property_attrs|js_object_set_symbol_property|throw_object_type_error' crates/perry-runtime/src/object/object_ops/define_property.rs crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs crates/perry-runtime/src/symbol/properties.rsRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Read the exact symbol-key branch in Object.defineProperty and the surrounding helper behavior.
sed -n '140,260p' crates/perry-runtime/src/object/object_ops/define_property.rs
printf '\n---\n'
sed -n '260,420p' crates/perry-runtime/src/object/object_ops/define_property.rsRepository: PerryTS/perry Length of output: 15182 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect symbol-key handling in the ordinary object define-property implementation.
rg -n -C4 'js_is_symbol\(key_value\)|js_object_set_symbol_property\(|set_symbol_property_attrs\(|PropertyAttrs::new\(false, false, false\)' crates/perry-runtime/src/object/object_ops/define_property.rs crates/perry-runtime/src/object/tests.rsRepository: PerryTS/perry Length of output: 12239 Install the fallback slot through the descriptor path
🤖 Prompt for AI Agents |
||
| Some(this_value) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.