Skip to content

Commit

Permalink
Allow resizing of underlying ArrayBuffer from Rust (boa-dev#4082)
Browse files Browse the repository at this point in the history
* Allow resizing of underlying ArrayBuffer from Rust

* Reuse ArrayBuffer::resize in the javascript API
  • Loading branch information
hansl authored Dec 19, 2024
1 parent d870057 commit 3777df0
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 23 deletions.
58 changes: 35 additions & 23 deletions core/engine/src/builtins/array_buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64> {
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]> {
Expand All @@ -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.
///
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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())
Expand Down
19 changes: 19 additions & 0 deletions core/engine/src/builtins/array_buffer/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::object::JsArrayBuffer;
use crate::{run_test_actions, Context, TestAction};

#[test]
Expand All @@ -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([
Expand Down Expand Up @@ -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]);
}
Expand Down Expand Up @@ -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]);
}
Expand Down
11 changes: 11 additions & 0 deletions core/engine/src/object/builtins/jsarraybuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 3777df0

Please sign in to comment.