diff --git a/core/engine/src/builtins/array_buffer/mod.rs b/core/engine/src/builtins/array_buffer/mod.rs index 49f2ea4ec9b..921fe1a0289 100644 --- a/core/engine/src/builtins/array_buffer/mod.rs +++ b/core/engine/src/builtins/array_buffer/mod.rs @@ -232,6 +232,11 @@ impl ArrayBuffer { self.data.as_mut() } + /// Sets the maximum byte length of the buffer, returning the previous value if present. + pub(crate) fn set_max_byte_length(&mut self, max_byte_len: u64) -> Option { + self.max_byte_len.replace(max_byte_len) + } + /// Gets the inner bytes of the buffer without accessing the current atomic length. #[track_caller] pub(crate) fn bytes_with_len(&self, len: usize) -> Option<&[u8]> { @@ -252,6 +257,32 @@ impl ArrayBuffer { } } + /// Resizes the buffer to the new size, clamped to the maximum byte length if present. + pub fn resize(&mut self, new_byte_length: u64) -> JsResult<()> { + let Some(max_byte_len) = self.max_byte_len else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer") + .into()); + }; + + let Some(buf) = self.vec_mut() else { + return Err(JsNativeError::typ() + .with_message("ArrayBuffer.resize: cannot resize a detached buffer") + .into()); + }; + + if new_byte_length > max_byte_len { + return Err(JsNativeError::range() + .with_message( + "ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length", + ) + .into()); + } + + buf.resize(new_byte_length as usize, 0); + Ok(()) + } + /// Detaches the inner data of this `ArrayBuffer`, returning the original buffer if still /// present. /// @@ -338,7 +369,7 @@ impl IntrinsicObject for ArrayBuffer { None, flag_attributes, ) - .method(Self::resize, js_string!("resize"), 1) + .method(Self::js_resize, js_string!("resize"), 1) .method(Self::slice, js_string!("slice"), 2) .property( JsSymbol::to_string_tag(), @@ -554,7 +585,7 @@ impl ArrayBuffer { /// [`ArrayBuffer.prototype.resize ( newLength )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.resize - pub(crate) fn resize( + pub(crate) fn js_resize( this: &JsValue, args: &[JsValue], context: &mut Context, @@ -570,31 +601,12 @@ impl ArrayBuffer { .with_message("ArrayBuffer.prototype.resize called with invalid `this`") })?; - let Some(max_byte_len) = buf.borrow().data.max_byte_len else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer") - .into()); - }; - // 4. Let newByteLength be ? ToIndex(newLength). let new_byte_length = args.get_or_undefined(0).to_index(context)?; - let mut buf = buf.borrow_mut(); + // These steps are performed in the `Self::resize` method. // 5. If IsDetachedBuffer(O) is true, throw a TypeError exception. - let Some(buf) = buf.data.vec_mut() else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.resize: cannot resize a detached buffer") - .into()); - }; - // 6. If newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception. - if new_byte_length > max_byte_len { - return Err(JsNativeError::range() - .with_message( - "ArrayBuffer.resize: new byte length exceeds buffer's maximum byte length", - ) - .into()); - } // TODO: 7. Let hostHandled be ? HostResizeArrayBuffer(O, newByteLength). // 8. If hostHandled is handled, return undefined. @@ -609,7 +621,7 @@ impl ArrayBuffer { // Implementations may implement this method as in-place growth or shrinkage. // 14. Set O.[[ArrayBufferData]] to newBlock. // 15. Set O.[[ArrayBufferByteLength]] to newByteLength. - buf.resize(new_byte_length as usize, 0); + buf.borrow_mut().data_mut().resize(new_byte_length)?; // 16. Return undefined. Ok(JsValue::undefined()) diff --git a/core/engine/src/builtins/array_buffer/tests.rs b/core/engine/src/builtins/array_buffer/tests.rs index f1af0936fc8..bdf961fcf88 100644 --- a/core/engine/src/builtins/array_buffer/tests.rs +++ b/core/engine/src/builtins/array_buffer/tests.rs @@ -1,3 +1,4 @@ +use crate::object::JsArrayBuffer; use crate::{run_test_actions, Context, TestAction}; #[test] @@ -20,6 +21,22 @@ fn create_shared_byte_data_block() { assert!(super::shared::create_shared_byte_data_block(u64::MAX, context).is_err()); } +#[test] +fn resize() { + let context = &mut Context::default(); + let data_block = super::create_byte_data_block(100, None, context).unwrap(); + let js_arr = JsArrayBuffer::from_byte_block(data_block, context) + .unwrap() + .with_max_byte_length(100); + let mut arr = js_arr.borrow_mut(); + + // Sunny day + assert_eq!(arr.data_mut().resize(50), Ok(())); + + // Rainy day + assert!(arr.data_mut().resize(u64::MAX).is_err()); +} + #[test] fn get_values() { run_test_actions([ @@ -68,6 +85,7 @@ fn sort() { run_test_actions([ TestAction::run( r#" + // This cmp function is needed as the harness does not support TypedArray comparison. function cmp(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } @@ -123,6 +141,7 @@ fn sort_negative_zero() { run_test_actions([ TestAction::run( r#" + // This cmp function is needed as the harness does not support TypedArray comparison. function cmp(a, b) { return a.length === b.length && a.every((v, i) => v === b[i]); } diff --git a/core/engine/src/object/builtins/jsarraybuffer.rs b/core/engine/src/object/builtins/jsarraybuffer.rs index bc07d86c830..11f70bc39ea 100644 --- a/core/engine/src/object/builtins/jsarraybuffer.rs +++ b/core/engine/src/object/builtins/jsarraybuffer.rs @@ -126,6 +126,17 @@ impl JsArrayBuffer { Ok(Self { inner: obj }) } + /// Set a maximum length for the underlying array buffer. + #[inline] + #[must_use] + pub fn with_max_byte_length(self, max_byte_len: u64) -> Self { + self.inner + .borrow_mut() + .data + .set_max_byte_length(max_byte_len); + self + } + /// Create a [`JsArrayBuffer`] from a [`JsObject`], if the object is not an array buffer throw a `TypeError`. /// /// This does not clone the fields of the array buffer, it only does a shallow clone of the object.