From 4df258505e9a9f2003551c8d00cb73d85eb9d4c6 Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 13:05:49 +0000 Subject: [PATCH 1/6] fix(built-ins): permit preventExtensions on fixed typed arrays backed by GSABs As required by ecma262, `Object.preventExtensions` may be applied to typed arrays without error if the `IsTypedArrayFixedLength` is not false. Prior to this commit, this check was incorrectly implemented via `ArrayBuffer::is_fixed_len` which does not/can not include an AUTO length check. This discrepancy lies in that the requirements of a fixed-length `TypedArray` slightly differ from a fixed-length arraybuffer. --- core/engine/src/builtins/typed_array/object.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 500071e8be2..83f2eea4c5f 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -4,7 +4,7 @@ use std::sync::atomic::Ordering; use crate::{ Context, JsExpect, JsNativeError, JsResult, JsString, JsValue, - builtins::array_buffer::BufferObject, + builtins::array_buffer::{BufferObject, BufferRef}, object::{ JsData, JsObject, internal_methods::{ @@ -295,7 +295,14 @@ pub(crate) fn typed_array_exotic_prevent_extensions( .downcast_ref::() .js_expect("must be a TypedArray")?; - ta.viewed_array_buffer().as_buffer().is_fixed_len() + if ta.is_auto_length() { + return Ok(false); + } + + match ta.viewed_array_buffer().as_buffer() { + BufferRef::Buffer(buf) => !buf.is_fixed_len(), + _ => true + } }; // 1. If IsTypedArrayFixedLength(O) is false, return false. From 8b65dfc4f2a5823cc08969d7859a27e6bcabc21f Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 14:12:44 +0000 Subject: [PATCH 2/6] test(builtins): ensure `Object.preventExtensions` works on fixed-length typed arrays backed by GSABs --- core/engine/src/builtins/typed_array/object.rs | 2 +- core/engine/src/builtins/typed_array/tests.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 83f2eea4c5f..2c184e3fd6e 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -301,7 +301,7 @@ pub(crate) fn typed_array_exotic_prevent_extensions( match ta.viewed_array_buffer().as_buffer() { BufferRef::Buffer(buf) => !buf.is_fixed_len(), - _ => true + BufferRef::SharedBuffer(_) => true, } }; diff --git a/core/engine/src/builtins/typed_array/tests.rs b/core/engine/src/builtins/typed_array/tests.rs index a9c978c2750..674b9c39f57 100644 --- a/core/engine/src/builtins/typed_array/tests.rs +++ b/core/engine/src/builtins/typed_array/tests.rs @@ -161,3 +161,17 @@ fn typedarray_conversion_mismatch_throws() { ), ]); } + +#[test] +fn typedarray_exotic_prevent_extensions() { + // ref: https://github.com/tc39/test262/blob/main/test/staging/built-ins/Object/preventExtensions/preventExtensions-variable-length-typed-arrays.js + run_test_actions([ + TestAction::run("const gsab = new SharedArrayBuffer(4, { maxByteLength: 8 });"), + TestAction::run("const fixedLength = new Uint8Array(gsab, 0, 4);"), + TestAction::run("const fixedLengthWithOffset = new Uint8Array(gsab, 2, 2);"), + TestAction::run("Object.preventExtensions(fixedLength);"), + TestAction::run("Object.preventExtensions(fixedLengthWithOffset);"), + TestAction::assert("!Object.isExtensible(fixedLength)"), + TestAction::assert("!Object.isExtensible(fixedLengthWithOffset)"), + ]); +} From e471f9dc8c2cc58bd7a333635c9ce78b8696a989 Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 15:24:05 +0000 Subject: [PATCH 3/6] fix(built-ins): incorrect use of `!` in `IsTypedArrayFixedLength` impl. Tests addec to guard against this. --- core/engine/src/builtins/typed_array/object.rs | 2 +- core/engine/src/builtins/typed_array/tests.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 2c184e3fd6e..9e660a99a7b 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -300,7 +300,7 @@ pub(crate) fn typed_array_exotic_prevent_extensions( } match ta.viewed_array_buffer().as_buffer() { - BufferRef::Buffer(buf) => !buf.is_fixed_len(), + BufferRef::Buffer(buf) => buf.is_fixed_len(), BufferRef::SharedBuffer(_) => true, } }; diff --git a/core/engine/src/builtins/typed_array/tests.rs b/core/engine/src/builtins/typed_array/tests.rs index 674b9c39f57..547dc4dff94 100644 --- a/core/engine/src/builtins/typed_array/tests.rs +++ b/core/engine/src/builtins/typed_array/tests.rs @@ -173,5 +173,12 @@ fn typedarray_exotic_prevent_extensions() { TestAction::run("Object.preventExtensions(fixedLengthWithOffset);"), TestAction::assert("!Object.isExtensible(fixedLength)"), TestAction::assert("!Object.isExtensible(fixedLengthWithOffset)"), + TestAction::run("const rab = new ArrayBuffer(4);"), + TestAction::run("const fixedLength1 = new Uint8Array(rab, 0, 4);"), + TestAction::run("const fixedLengthWithOffset1 = new Uint8Array(rab, 2, 2);"), + TestAction::run("Object.preventExtensions(fixedLength1);"), + TestAction::run("Object.preventExtensions(fixedLengthWithOffset1);"), + TestAction::assert("!Object.isExtensible(fixedLength1)"), + TestAction::assert("!Object.isExtensible(fixedLengthWithOffset1)"), ]); } From 28bf51bd7811a673eab6e538d17933e0eb9ad26c Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 21:12:14 +0000 Subject: [PATCH 4/6] refactor(built-ins): implement `IsTypedArrayFixedLength` as a separate function for use in `typed_array_exotic_prevent_extensions`. --- .../engine/src/builtins/typed_array/object.rs | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 9e660a99a7b..03a033eca24 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -290,23 +290,8 @@ pub(crate) fn typed_array_exotic_prevent_extensions( obj: &JsObject, context: &mut Context, ) -> JsResult { - let is_fixed_length = { - let ta = obj - .downcast_ref::() - .js_expect("must be a TypedArray")?; - - if ta.is_auto_length() { - return Ok(false); - } - - match ta.viewed_array_buffer().as_buffer() { - BufferRef::Buffer(buf) => buf.is_fixed_len(), - BufferRef::SharedBuffer(_) => true, - } - }; - // 1. If IsTypedArrayFixedLength(O) is false, return false. - if !is_fixed_length { + if !is_typed_array_fixed_length(obj)? { return Ok(false); } @@ -314,6 +299,32 @@ pub(crate) fn typed_array_exotic_prevent_extensions( ordinary_prevent_extensions(obj, context) } +/// Abstract operation `IsTypedArrayFixedLength ( O )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-istypedarrayfixedlength +fn is_typed_array_fixed_length(obj: &JsObject) -> JsResult { + let ta = obj + .downcast_ref::() + .js_expect("must be a TypedArray")?; + + // 1. If O.[[ArrayLength]] is auto, return false. + if ta.is_auto_length() { + return Ok(false); + } + + // 2. Let buffer be O.[[ViewedArrayBuffer]]. + let is_fixed_length = match ta.viewed_array_buffer().as_buffer() { + // 3. If IsFixedLengthArrayBuffer(buffer) is false and IsSharedArrayBuffer(buffer) is false, return false. + BufferRef::Buffer(buf) => buf.is_fixed_len(), + BufferRef::SharedBuffer(_) => true, + }; + + Ok(is_fixed_length) +} + /// `CanonicalNumericIndexString ( argument )` /// /// More information: From 6da430f5553d1a4babd05b38331b434b007dc3eb Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 22:02:54 +0000 Subject: [PATCH 5/6] refactor(built-ins): re-implement `IsTypedArrayFixedLength` as a `TypedArray` method --- .../engine/src/builtins/typed_array/object.rs | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 03a033eca24..6dbf565fc28 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -279,6 +279,26 @@ impl TypedArray { // 4. Return true. Some(index) } + + /// Abstract operation `IsTypedArrayFixedLength ( O )`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-istypedarrayfixedlength + fn is_fixed_length(&self) -> bool { + // 1. If O.[[ArrayLength]] is auto, return false. + if self.is_auto_length() { + return false; + } + + // 2. Let buffer be O.[[ViewedArrayBuffer]]. + match self.viewed_array_buffer().as_buffer() { + // 3. If IsFixedLengthArrayBuffer(buffer) is false and IsSharedArrayBuffer(buffer) is false, return false. + BufferRef::Buffer(buf) => buf.is_fixed_len(), + BufferRef::SharedBuffer(_) => true, + } + } } // Integer-Indexed Exotic Objects [[PreventExtensions]] ( O ) @@ -290,39 +310,22 @@ pub(crate) fn typed_array_exotic_prevent_extensions( obj: &JsObject, context: &mut Context, ) -> JsResult { - // 1. If IsTypedArrayFixedLength(O) is false, return false. - if !is_typed_array_fixed_length(obj)? { - return Ok(false); - } - - // 2. Return OrdinaryPreventExtensions(O). - ordinary_prevent_extensions(obj, context) -} - -/// Abstract operation `IsTypedArrayFixedLength ( O )`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-istypedarrayfixedlength -fn is_typed_array_fixed_length(obj: &JsObject) -> JsResult { - let ta = obj + let is_fixed_length = { + let ta = obj .downcast_ref::() .js_expect("must be a TypedArray")?; - // 1. If O.[[ArrayLength]] is auto, return false. - if ta.is_auto_length() { + ta.is_fixed_length() + }; // Note: this block ensures that the borrow of obj is dropped + // so it may be re-borrowed in step 3 + + // 1. If IsTypedArrayFixedLength(O) is false, return false. + if !is_fixed_length { return Ok(false); } - // 2. Let buffer be O.[[ViewedArrayBuffer]]. - let is_fixed_length = match ta.viewed_array_buffer().as_buffer() { - // 3. If IsFixedLengthArrayBuffer(buffer) is false and IsSharedArrayBuffer(buffer) is false, return false. - BufferRef::Buffer(buf) => buf.is_fixed_len(), - BufferRef::SharedBuffer(_) => true, - }; - - Ok(is_fixed_length) + // 2. Return OrdinaryPreventExtensions(O). + ordinary_prevent_extensions(obj, context) } /// `CanonicalNumericIndexString ( argument )` From 1e7009731e7866e0a25bbc863709290c49ea67c4 Mon Sep 17 00:00:00 2001 From: AlvinThorn008 Date: Thu, 26 Mar 2026 22:06:06 +0000 Subject: [PATCH 6/6] fmt: satisfy rustfmt --- core/engine/src/builtins/typed_array/object.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/engine/src/builtins/typed_array/object.rs b/core/engine/src/builtins/typed_array/object.rs index 6dbf565fc28..57087cc1350 100644 --- a/core/engine/src/builtins/typed_array/object.rs +++ b/core/engine/src/builtins/typed_array/object.rs @@ -312,8 +312,8 @@ pub(crate) fn typed_array_exotic_prevent_extensions( ) -> JsResult { let is_fixed_length = { let ta = obj - .downcast_ref::() - .js_expect("must be a TypedArray")?; + .downcast_ref::() + .js_expect("must be a TypedArray")?; ta.is_fixed_length() }; // Note: this block ensures that the borrow of obj is dropped