From 6b7f57d8e364948ab3a762f3d6f147c885da66ac Mon Sep 17 00:00:00 2001 From: Redfire Date: Tue, 28 Nov 2023 01:52:23 +0800 Subject: [PATCH] Added Support for ArrayBuffers Renamed String Functions Simplified External Strings --- cli/src/commands/cache.rs | 1 + ion/src/conversions/key.rs | 4 +- ion/src/conversions/value/to.rs | 2 +- ion/src/module.rs | 2 +- ion/src/objects/key.rs | 2 +- ion/src/objects/typedarray/buffer.rs | 181 ++++++++++++++++++ .../{typedarray.rs => typedarray/mod.rs} | 2 + ion/src/string/external.rs | 90 --------- ion/src/string/mod.rs | 60 ++++-- ion/src/utils.rs | 21 ++ 10 files changed, 258 insertions(+), 107 deletions(-) create mode 100644 ion/src/objects/typedarray/buffer.rs rename ion/src/objects/{typedarray.rs => typedarray/mod.rs} (99%) delete mode 100644 ion/src/string/external.rs diff --git a/cli/src/commands/cache.rs b/cli/src/commands/cache.rs index d3811477..bc5dc8fd 100644 --- a/cli/src/commands/cache.rs +++ b/cli/src/commands/cache.rs @@ -9,6 +9,7 @@ use std::io; use std::path::Path; use humansize::{BINARY, SizeFormatter}; + use runtime::cache::Cache; pub(crate) fn cache_statistics() { diff --git a/ion/src/conversions/key.rs b/ion/src/conversions/key.rs index 81abfcba..3213ba79 100644 --- a/ion/src/conversions/key.rs +++ b/ion/src/conversions/key.rs @@ -56,13 +56,13 @@ impl<'cx> ToPropertyKey<'cx> for String<'cx> { impl<'cx> ToPropertyKey<'cx> for RustString { fn to_key(&self, cx: &'cx Context) -> Option> { - String::new(cx, self)?.to_key(cx) + String::copy_from_str(cx, self)?.to_key(cx) } } impl<'cx> ToPropertyKey<'cx> for &str { fn to_key(&self, cx: &'cx Context) -> Option> { - String::new(cx, self)?.to_key(cx) + String::copy_from_str(cx, self)?.to_key(cx) } } diff --git a/ion/src/conversions/value/to.rs b/ion/src/conversions/value/to.rs index 933919a0..961db0c0 100644 --- a/ion/src/conversions/value/to.rs +++ b/ion/src/conversions/value/to.rs @@ -103,7 +103,7 @@ impl<'cx> ToValue<'cx> for crate::String<'cx> { impl ToValue<'_> for str { fn to_value(&self, cx: &Context, value: &mut Value) { - let string = crate::String::new(cx, self); + let string = crate::String::copy_from_str(cx, self); if let Some(string) = string { string.to_value(cx, value); } else { diff --git a/ion/src/module.rs b/ion/src/module.rs index aa8f2a85..2cc8dd60 100644 --- a/ion/src/module.rs +++ b/ion/src/module.rs @@ -48,7 +48,7 @@ pub struct ModuleRequest<'r>(Object<'r>); impl<'r> ModuleRequest<'r> { /// Creates a new [ModuleRequest] with a given specifier. pub fn new>(cx: &'r Context, specifier: S) -> ModuleRequest<'r> { - let specifier = crate::String::new(cx, specifier.as_ref()).unwrap(); + let specifier = crate::String::copy_from_str(cx, specifier.as_ref()).unwrap(); ModuleRequest( cx.root_object(unsafe { CreateModuleRequest(cx.as_ptr(), specifier.handle().into()) }) .into(), diff --git a/ion/src/objects/key.rs b/ion/src/objects/key.rs index 0e41a67b..5a30727d 100644 --- a/ion/src/objects/key.rs +++ b/ion/src/objects/key.rs @@ -27,7 +27,7 @@ impl<'k> PropertyKey<'k> { /// Creates a [PropertyKey] from a string. pub fn with_string(cx: &'k Context, string: &str) -> Option> { - let string = String::new(cx, string)?; + let string = String::copy_from_str(cx, string)?; string.to_key(cx) } diff --git a/ion/src/objects/typedarray/buffer.rs b/ion/src/objects/typedarray/buffer.rs new file mode 100644 index 00000000..72f05365 --- /dev/null +++ b/ion/src/objects/typedarray/buffer.rs @@ -0,0 +1,181 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +use std::{ptr, slice}; +use std::ffi::c_void; +use std::ops::{Deref, DerefMut}; + +use mozjs::jsapi::{ + ArrayBufferClone, ArrayBufferCopyData, DetachArrayBuffer, GetArrayBufferLengthAndData, IsArrayBufferObject, + IsDetachedArrayBufferObject, JS_GetTypedArraySharedness, JSObject, NewExternalArrayBuffer, +}; +use mozjs::typedarray::CreateWith; +use mozjs_sys::jsapi::JS::{NewArrayBufferWithContents, StealArrayBufferContents}; + +use crate::{Context, Error, ErrorKind, Local, Object, Result}; +use crate::utils::BoxExt; + +pub struct ArrayBuffer<'ab> { + buffer: Local<'ab, *mut JSObject>, +} + +impl<'ab> ArrayBuffer<'ab> { + fn create_with(cx: &'ab Context, with: CreateWith) -> Option> { + let mut buffer = Object::null(cx); + unsafe { mozjs::typedarray::ArrayBuffer::create(cx.as_ptr(), with, buffer.handle_mut()).ok()? }; + Some(ArrayBuffer { buffer: buffer.into_local() }) + } + + /// Creates a new [ArrayBuffer] with the given length. + pub fn new(cx: &Context, len: usize) -> Option { + ArrayBuffer::create_with(cx, CreateWith::Length(len)) + } + + /// Creates a new [ArrayBuffer] by copying the contents of the given slice. + pub fn copy_from_bytes(cx: &'ab Context, bytes: &[u8]) -> Option> { + ArrayBuffer::create_with(cx, CreateWith::Slice(bytes)) + } + + /// Creates a new [ArrayBuffer] by transferring ownership of the bytes to the JS runtime. + pub fn from_vec(cx: &Context, bytes: Vec) -> Option { + ArrayBuffer::from_boxed_slice(cx, bytes.into_boxed_slice()) + } + + /// Creates a new [ArrayBuffer] by transferring ownership of the bytes to the JS runtime. + pub fn from_boxed_slice(cx: &Context, bytes: Box<[u8]>) -> Option { + unsafe extern "C" fn free_external_array_buffer(contents: *mut c_void, data: *mut c_void) { + let _ = unsafe { Box::from_raw_parts(contents.cast::(), data as usize) }; + } + + let (ptr, len) = unsafe { Box::into_raw_parts(bytes) }; + let buffer = unsafe { + NewExternalArrayBuffer( + cx.as_ptr(), + len, + ptr.cast(), + Some(free_external_array_buffer), + len as *mut c_void, + ) + }; + + if buffer.is_null() { + None + } else { + Some(ArrayBuffer { buffer: cx.root_object(buffer) }) + } + } + + pub fn from(object: Local<'ab, *mut JSObject>) -> Option> { + if ArrayBuffer::is_array_buffer(object.get()) { + Some(ArrayBuffer { buffer: object }) + } else { + None + } + } + + pub unsafe fn from_unchecked(object: Local<'ab, *mut JSObject>) -> ArrayBuffer<'ab> { + ArrayBuffer { buffer: object } + } + + /// Returns a pointer and length to the contents of the [ArrayBuffer]. + /// + /// The pointer may be invalidated if the [ArrayBuffer] is detached. + pub fn data(&self) -> (*mut u8, usize) { + let mut len = 0; + let mut shared = false; + let mut data = ptr::null_mut(); + unsafe { GetArrayBufferLengthAndData(self.get(), &mut len, &mut shared, &mut data) }; + (data, len) + } + + /// Returns a slice to the contents of the [ArrayBuffer]. + /// + /// The slice may be invalidated if the [ArrayBuffer] is detached. + pub unsafe fn as_slice(&self) -> &[u8] { + let (ptr, len) = self.data(); + unsafe { slice::from_raw_parts(ptr, len) } + } + + /// Returns a mutable slice to the contents of the [ArrayBuffer]. + /// + /// The slice may be invalidated if the [ArrayBuffer] is detached. + pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { + let (ptr, len) = self.data(); + unsafe { slice::from_raw_parts_mut(ptr, len) } + } + + /// Clones an [ArrayBuffer]. + pub fn clone<'cx>(&self, cx: &'cx Context, offset: usize, len: usize) -> Option> { + let buffer = unsafe { ArrayBufferClone(cx.as_ptr(), self.handle().into(), offset, len) }; + if buffer.is_null() { + None + } else { + Some(ArrayBuffer { buffer: cx.root_object(buffer) }) + } + } + + /// Copies data from one [ArrayBuffer] to another. + /// Returns `false` if the sizes do not match. + pub fn copy_data_to( + &self, cx: &Context, to: &mut ArrayBuffer, from_index: usize, to_index: usize, count: usize, + ) -> bool { + unsafe { + ArrayBufferCopyData( + cx.as_ptr(), + to.handle().into(), + to_index, + self.handle().into(), + from_index, + count, + ) + } + } + + pub fn detach(&mut self, cx: &Context) -> bool { + unsafe { DetachArrayBuffer(cx.as_ptr(), self.handle().into()) } + } + + pub fn transfer<'cx>(&mut self, cx: &'cx Context) -> Result> { + let len = self.data().1; + let data = unsafe { StealArrayBufferContents(cx.as_ptr(), self.handle().into()) }; + if data.is_null() { + return Err(Error::new("ArrayBuffer transfer failed", ErrorKind::Normal)); + } + let buffer = cx.root_object(unsafe { NewArrayBufferWithContents(cx.as_ptr(), len, data) }); + if buffer.handle().is_null() { + return Err(Error::new("ArrayBuffer transfer failed", ErrorKind::Normal)); + } + Ok(ArrayBuffer { buffer }) + } + + pub fn is_detached(&self) -> bool { + unsafe { IsDetachedArrayBufferObject(self.get()) } + } + + pub fn is_shared(&self) -> bool { + unsafe { JS_GetTypedArraySharedness(self.get()) } + } + + /// Checks if an object is an array buffer. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn is_array_buffer(object: *mut JSObject) -> bool { + unsafe { IsArrayBufferObject(object) } + } +} + +impl<'ab> Deref for ArrayBuffer<'ab> { + type Target = Local<'ab, *mut JSObject>; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl<'ab> DerefMut for ArrayBuffer<'ab> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} diff --git a/ion/src/objects/typedarray.rs b/ion/src/objects/typedarray/mod.rs similarity index 99% rename from ion/src/objects/typedarray.rs rename to ion/src/objects/typedarray/mod.rs index 1ad3e039..8374eeac 100644 --- a/ion/src/objects/typedarray.rs +++ b/ion/src/objects/typedarray/mod.rs @@ -12,6 +12,8 @@ use crate::{Context, Error, Object, Result, Value}; use crate::conversions::ToValue; use crate::exception::ThrowException; +pub mod buffer; + macro_rules! impl_typedarray_wrapper { ($typedarray:ident, $ty:ty) => { pub struct $typedarray { diff --git a/ion/src/string/external.rs b/ion/src/string/external.rs deleted file mode 100644 index 530aee92..00000000 --- a/ion/src/string/external.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -use std::ffi::c_void; -use std::slice; - -use byteorder::NativeEndian; -use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps}; -use mozjs::jsapi::JS_NewExternalString; -use mozjs::jsapi::mozilla::MallocSizeOf; -use utf16string::WString; - -use crate::{Context, String}; - -pub(crate) fn new_external_string(cx: &Context, str: WString) -> Result> { - let vec = str.into_bytes(); - let boxed = vec.into_boxed_slice(); - - let (chars, len) = box_into_raw(boxed); - - unsafe { - let callbacks = CreateJSExternalStringCallbacks(&EXTERNAL_STRING_CALLBACKS_TRAPS, len as *mut c_void); - let jsstr = JS_NewExternalString(cx.as_ptr(), chars, len, callbacks); - - if !jsstr.is_null() { - Ok(String::from(cx.root_string(jsstr))) - } else { - let slice = slice::from_raw_parts_mut(chars as *mut u8, len * 2); - let boxed = Box::from_raw(slice); - let vec = Vec::from(boxed); - Err(WString::from_utf16_unchecked(vec)) - } - } -} - -extern "C" fn finalise_external_string(private_data: *const c_void, chars: *mut u16) { - let _ = box_from_raw(chars, private_data); -} - -extern "C" fn size_of_buffer(private_data: *const c_void, _: *const u16, _: MallocSizeOf) -> usize { - private_data as usize -} - -static EXTERNAL_STRING_CALLBACKS_TRAPS: JSExternalStringCallbacksTraps = JSExternalStringCallbacksTraps { - finalize: Some(finalise_external_string), - sizeOfBuffer: Some(size_of_buffer), -}; - -fn box_into_raw(boxed: Box<[u8]>) -> (*const u16, usize) { - assert_eq!(boxed.len() % 2, 0); - let len = boxed.len() / 2; - let chars = Box::into_raw(boxed) as *const u16; - (chars, len) -} - -fn box_from_raw(chars: *mut u16, private_data: *const c_void) -> Box<[u8]> { - let len = private_data as usize; - unsafe { - let slice = slice::from_raw_parts_mut(chars as *mut u8, len * 2); - Box::from_raw(slice) - } -} - -#[cfg(test)] -mod tests { - use std::ffi::c_void; - - use byteorder::NativeEndian; - use utf16string::WString; - - use crate::string::external::{box_from_raw, box_into_raw}; - - type NativeWString = WString; - - #[test] - fn wstring_into_box() { - let string = "S\u{500}t\u{1000}r\u{5000}i\u{10000}n\u{50000}g"; - - let base = NativeWString::from(string); - let boxed = NativeWString::from(string).into_bytes().into_boxed_slice(); - let (chars, len) = box_into_raw(boxed); - - let boxed = box_from_raw(chars as *mut u16, len as *mut c_void); - let wstring = NativeWString::from_utf16(Vec::from(boxed)).unwrap(); - assert_eq!(base, wstring); - } -} diff --git a/ion/src/string/mod.rs b/ion/src/string/mod.rs index f562e42f..6b53c56b 100644 --- a/ion/src/string/mod.rs +++ b/ion/src/string/mod.rs @@ -5,25 +5,26 @@ */ use std::{ptr, slice}; +use std::ffi::c_void; use std::ops::{Deref, DerefMut, Range}; -use std::ptr::NonNull; use std::string::String as RustString; use bytemuck::cast_slice; use byteorder::NativeEndian; +use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps}; use mozjs::jsapi::{ JS_CompareStrings, JS_ConcatStrings, JS_DeprecatedStringHasLatin1Chars, JS_GetEmptyString, JS_GetLatin1StringCharsAndLength, JS_GetStringCharAt, JS_GetTwoByteStringCharsAndLength, JS_NewDependentString, - JS_NewUCStringCopyN, JS_StringIsLinear, JSString, + JS_NewExternalString, JS_NewUCStringCopyN, JS_StringIsLinear, JSString, }; +use mozjs::jsapi::mozilla::MallocSizeOf; use utf16string::{WStr, WString}; use crate::{Context, Local}; use crate::string::byte::{ByteStr, Latin1}; -use crate::string::external::new_external_string; +use crate::utils::BoxExt; pub mod byte; -mod external; #[derive(Copy, Clone, Debug)] pub enum StringRef<'s> { @@ -65,26 +66,61 @@ pub struct String<'s> { impl<'s> String<'s> { /// Creates an empty [String]. - pub fn empty(cx: &Context) -> String { + pub fn new(cx: &Context) -> String { String::from(cx.root_string(unsafe { JS_GetEmptyString(cx.as_ptr()) })) } /// Creates a new [String] with a given string, by copying it to the JS Runtime. - pub fn new<'cx>(cx: &'cx Context, string: &str) -> Option> { + pub fn copy_from_str<'cx>(cx: &'cx Context, string: &str) -> Option> { let utf16: Vec = string.encode_utf16().collect(); let jsstr = unsafe { JS_NewUCStringCopyN(cx.as_ptr(), utf16.as_ptr(), utf16.len()) }; - NonNull::new(jsstr).map(|str| String::from(cx.root_string(str.as_ptr()))) + if jsstr.is_null() { + None + } else { + Some(String::from(cx.root_string(jsstr))) + } } - /// Creates a new external string by moving ownership of the UTF-16 string to the JS Runtime. - pub fn new_external(cx: &Context, string: WString) -> Result> { - new_external_string(cx, string) + /// Creates a new string by moving ownership of the UTF-16 string to the JS Runtime temporarily. + /// Returns the string if the creation of the string in the runtime fails. + pub fn from_wstring(cx: &Context, string: WString) -> Result> { + unsafe extern "C" fn finalise_external_string(data: *const c_void, chars: *mut u16) { + let _ = unsafe { Box::from_raw_parts(chars.cast::(), data as usize * 2) }; + } + + extern "C" fn size_of_external_string(data: *const c_void, _: *const u16, _: MallocSizeOf) -> usize { + data as usize + } + + static EXTERNAL_STRING_CALLBACKS_TRAPS: JSExternalStringCallbacksTraps = JSExternalStringCallbacksTraps { + finalize: Some(finalise_external_string), + sizeOfBuffer: Some(size_of_external_string), + }; + + let vec = string.into_bytes(); + let boxed = vec.into_boxed_slice(); + + let (chars, len) = unsafe { Box::into_raw_parts(boxed) }; + + unsafe { + let callbacks = CreateJSExternalStringCallbacks(&EXTERNAL_STRING_CALLBACKS_TRAPS, len as *mut c_void); + let jsstr = JS_NewExternalString(cx.as_ptr(), chars.cast::(), len / 2, callbacks); + + if jsstr.is_null() { + let slice = slice::from_raw_parts_mut(chars, len); + let boxed = Box::from_raw(slice); + let vec = Vec::from(boxed); + Err(WString::from_utf16_unchecked(vec)) + } else { + Ok(String::from(cx.root_string(jsstr))) + } + } } /// Returns a slice of a [String] as a new [String]. - pub fn slice<'cx>(&self, cx: &'cx Context, range: &Range) -> String<'cx> { + pub fn slice<'cx>(&self, cx: &'cx Context, range: Range) -> String<'cx> { let Range { start, end } = range; - String::from(cx.root_string(unsafe { JS_NewDependentString(cx.as_ptr(), self.handle().into(), *start, *end) })) + String::from(cx.root_string(unsafe { JS_NewDependentString(cx.as_ptr(), self.handle().into(), start, end) })) } /// Concatenates two [String]s into a new [String]. diff --git a/ion/src/utils.rs b/ion/src/utils.rs index 30120a4f..f2505184 100644 --- a/ion/src/utils.rs +++ b/ion/src/utils.rs @@ -5,6 +5,7 @@ */ use std::path::{Component, Path, PathBuf}; +use std::slice; /// Normalises a [Path] by removing all `./` and resolving all `../` simplistically. /// This function does not follow symlinks and may result in unexpected behaviour. @@ -28,3 +29,23 @@ pub fn normalise_path>(path: P) -> PathBuf { } buf } + +pub trait BoxExt { + unsafe fn into_raw_parts(self) -> (*mut T, usize); + + unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self; +} + +impl BoxExt for Box<[T]> { + unsafe fn into_raw_parts(self) -> (*mut T, usize) { + let len = self.len(); + (Box::into_raw(self).cast(), len) + } + + unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self { + unsafe { + let slice = slice::from_raw_parts_mut(ptr, len); + Box::from_raw(slice) + } + } +}