Skip to content

Commit f88246b

Browse files
authored
feat: Implement JSValue::new_typed_array_with_bytes (#18)
* feat: Implement `JSValue::new_typed_array_with_bytes`. This patch implements `JSValue::new_typed_array_with_bytes`. This method is marked as unsafe, because bytes aren't copied: they are borrowed mutably by JavaScript. The borrow checker cannot ensure safety. JavaScript or Rust can concurrently access to the bytes. It's unsafe, but it's fast.
1 parent c03aaa3 commit f88246b

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

Diff for: src/value.rs

+113
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,68 @@ impl JSValue {
188188
Ok(JSValue::new_inner(ctx.raw, result))
189189
}
190190

191+
/// Creates a JavaScript value of the `TypedArray` type.
192+
///
193+
/// * `ctx`: The execution context to use.
194+
/// * `bytes`: The typed array bytes. The constructed `TypedArray` doesn't copy the bytes,
195+
/// thus this method takes a `&mut` reference as it is possible to mutate the bytes via
196+
/// `TypedArray` or via Rust.
197+
///
198+
/// Returns a `JSValue` of the `TypedArray` type, otherwise an exception.
199+
///
200+
/// # Safety
201+
///
202+
/// `bytes` can be mutated both by Rust or JavaScript. There is no lock, no mutex, no
203+
/// guard. Be extremely careful when using this API. `bytes` aren't copied, they are
204+
/// borrowed mutably by JavaScript. Dropping the value in Rust will clear them in
205+
/// JavaScript, and vice versa. Hence, this method is marked as `unsafe`.
206+
///
207+
/// # Example
208+
///
209+
/// ```rust
210+
/// # use javascriptcore::{JSContext, JSValue};
211+
/// let ctx = JSContext::default();
212+
/// let mut bytes = vec![1u8, 2, 3, 4, 5];
213+
/// let value = unsafe {
214+
/// JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice())
215+
/// .unwrap()
216+
/// };
217+
/// ```
218+
pub unsafe fn new_typed_array_with_bytes(
219+
ctx: &JSContext,
220+
// `&mut` instead of &` because the typed array borrows mutably the bytes.
221+
//
222+
// The argument is named `_bytes` instead of `bytes` to avoid a
223+
// `clippy::needless_pass_by_ref_mut` warning (only on Rust nightly).
224+
_bytes: &mut [u8],
225+
) -> Result<Self, JSException> {
226+
let bytes = _bytes;
227+
let deallocator_ctx = ptr::null_mut();
228+
let mut exception: sys::JSValueRef = ptr::null_mut();
229+
230+
let result = unsafe {
231+
sys::JSObjectMakeTypedArrayWithBytesNoCopy(
232+
ctx.raw,
233+
sys::JSTypedArrayType::Uint8Array,
234+
bytes.as_ptr() as _,
235+
bytes.len(),
236+
None,
237+
deallocator_ctx,
238+
&mut exception,
239+
)
240+
};
241+
242+
if !exception.is_null() {
243+
return Err(JSValue::new_inner(ctx.raw, exception).into());
244+
}
245+
246+
if result.is_null() {
247+
return Err(JSValue::new_string(ctx, "Failed to make a new typed array").into());
248+
}
249+
250+
Ok(JSValue::new_inner(ctx.raw, result))
251+
}
252+
191253
/// Creates a JavaScript value from a JSON formatted string.
192254
///
193255
/// * `ctx`: The execution context to use.
@@ -650,6 +712,57 @@ mod tests {
650712
assert!(!vo.get_property_at_index(1).as_boolean());
651713
}
652714

715+
#[test]
716+
fn typed_array() {
717+
let ctx = JSContext::default();
718+
let mut bytes = vec![1u8, 2, 3, 4, 5];
719+
let array = unsafe { JSValue::new_typed_array_with_bytes(&ctx, bytes.as_mut_slice()) }
720+
.unwrap()
721+
.as_object()
722+
.unwrap();
723+
724+
assert_eq!(
725+
unsafe {
726+
array
727+
.get_property("byteLength")
728+
.as_number()
729+
.unwrap()
730+
.to_int_unchecked::<usize>()
731+
},
732+
bytes.len()
733+
);
734+
assert_eq!(
735+
unsafe {
736+
array
737+
.get_property("BYTES_PER_ELEMENT")
738+
.as_number()
739+
.unwrap()
740+
.to_int_unchecked::<usize>()
741+
},
742+
1
743+
);
744+
745+
// Let's test the mutability of the bytes, i.e. they aren't copied but borrowed.
746+
array
747+
.set_property_at_index(2, JSValue::new_number(&ctx, 10.))
748+
.unwrap();
749+
750+
assert_eq!(bytes, &[1u8, 2, 10, 4, 5]);
751+
752+
bytes[3] = 11;
753+
754+
assert_eq!(
755+
unsafe {
756+
array
757+
.get_property_at_index(3)
758+
.as_number()
759+
.unwrap()
760+
.to_int_unchecked::<u8>()
761+
},
762+
11
763+
)
764+
}
765+
653766
#[test]
654767
fn json_boolean_true() {
655768
let ctx = JSContext::default();

0 commit comments

Comments
 (0)