use crate::Isolate;
use crate::Local;
use crate::String;
use crate::binding::v8__String__kMaxLength;
use crate::isolate::RealIsolate;
use crate::scope::PinScope;
use crate::support::Opaque;
use crate::support::char;
use crate::support::int;
use crate::support::size_t;
use std::borrow::Cow;
use std::convert::TryInto;
use std::default::Default;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::slice;
unsafe extern "C" {
fn v8__String__Empty(isolate: *mut RealIsolate) -> *const String;
fn v8__String__NewFromUtf8(
isolate: *mut RealIsolate,
data: *const char,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__NewFromOneByte(
isolate: *mut RealIsolate,
data: *const u8,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__NewFromTwoByte(
isolate: *mut RealIsolate,
data: *const u16,
new_type: NewStringType,
length: int,
) -> *const String;
fn v8__String__Length(this: *const String) -> int;
fn v8__String__Utf8Length(
this: *const String,
isolate: *mut RealIsolate,
) -> int;
fn v8__String__Write_v2(
this: *const String,
isolate: *mut RealIsolate,
offset: u32,
length: u32,
buffer: *mut u16,
flags: int,
);
fn v8__String__WriteOneByte_v2(
this: *const String,
isolate: *mut RealIsolate,
offset: u32,
length: u32,
buffer: *mut u8,
flags: int,
);
fn v8__String__WriteUtf8_v2(
this: *const String,
isolate: *mut RealIsolate,
buffer: *mut char,
capacity: size_t,
flags: int,
processed_characters_return: *mut size_t,
) -> int;
fn v8__String__GetExternalStringResource(
this: *const String,
) -> *mut ExternalStringResource;
fn v8__String__GetExternalStringResourceBase(
this: *const String,
encoding: *mut Encoding,
) -> *mut ExternalStringResourceBase;
fn v8__String__NewExternalOneByteConst(
isolate: *mut RealIsolate,
onebyte_const: *const OneByteConst,
) -> *const String;
fn v8__String__NewExternalOneByteStatic(
isolate: *mut RealIsolate,
buffer: *const char,
length: int,
) -> *const String;
fn v8__String__NewExternalOneByte(
isolate: *mut RealIsolate,
buffer: *mut char,
length: size_t,
free: unsafe extern "C" fn(*mut char, size_t),
) -> *const String;
fn v8__String__NewExternalTwoByteStatic(
isolate: *mut RealIsolate,
buffer: *const u16,
length: int,
) -> *const String;
#[allow(dead_code)]
fn v8__String__IsExternal(this: *const String) -> bool;
fn v8__String__IsExternalOneByte(this: *const String) -> bool;
fn v8__String__IsExternalTwoByte(this: *const String) -> bool;
#[allow(dead_code)]
fn v8__String__IsOneByte(this: *const String) -> bool;
fn v8__String__ContainsOnlyOneByte(this: *const String) -> bool;
fn v8__ExternalOneByteStringResource__data(
this: *const ExternalOneByteStringResource,
) -> *const char;
fn v8__ExternalOneByteStringResource__length(
this: *const ExternalOneByteStringResource,
) -> size_t;
fn v8__String__ValueView__CONSTRUCT(
buf: *mut ValueView,
isolate: *mut RealIsolate,
string: *const String,
);
fn v8__String__ValueView__DESTRUCT(this: *mut ValueView);
fn v8__String__ValueView__is_one_byte(this: *const ValueView) -> bool;
fn v8__String__ValueView__data(this: *const ValueView) -> *const c_void;
fn v8__String__ValueView__length(this: *const ValueView) -> int;
}
#[derive(PartialEq, Debug)]
#[repr(C)]
pub enum Encoding {
Unknown = 0x1,
TwoByte = 0x2,
OneByte = 0x8,
}
#[repr(C)]
pub struct ExternalStringResource(Opaque);
#[repr(C)]
pub struct ExternalStringResourceBase(Opaque);
#[repr(C)]
pub struct ExternalOneByteStringResource(Opaque);
impl ExternalOneByteStringResource {
#[inline]
pub fn data(&self) -> *const char {
unsafe { v8__ExternalOneByteStringResource__data(self) }
}
#[inline]
pub fn length(&self) -> usize {
unsafe { v8__ExternalOneByteStringResource__length(self) }
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let len = self.length();
if len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(self.data().cast(), len) }
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct OneByteConst {
vtable: *const OneByteConstNoOp,
cached_data: *const char,
length: usize,
}
impl OneByteConst {
#[inline(always)]
pub const fn as_str(&self) -> &str {
if self.length == 0 {
""
} else {
unsafe {
std::str::from_utf8_unchecked(std::slice::from_raw_parts(
self.cached_data as _,
self.length,
))
}
}
}
}
impl AsRef<str> for OneByteConst {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for OneByteConst {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl std::ops::Deref for OneByteConst {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
unsafe impl Sync for OneByteConst {}
unsafe extern "C" fn one_byte_const_no_op(_this: *const OneByteConst) {}
unsafe extern "C" fn one_byte_const_is_cacheable(
_this: *const OneByteConst,
) -> bool {
true
}
unsafe extern "C" fn one_byte_const_data(
this: *const OneByteConst,
) -> *const char {
unsafe { (*this).cached_data }
}
unsafe extern "C" fn one_byte_const_length(this: *const OneByteConst) -> usize {
unsafe { (*this).length }
}
unsafe extern "C" fn one_byte_const_unaccount(
_this: *const OneByteConst,
_isolate: *mut RealIsolate,
) {
}
unsafe extern "C" fn one_byte_const_estimate_memory_usage(
_this: *const OneByteConst,
) -> size_t {
usize::MAX }
unsafe extern "C" fn one_byte_const_estimate_shared_memory_usage(
_this: *const OneByteConst,
_recorder: *mut (),
) {
}
type OneByteConstNoOp = unsafe extern "C" fn(*const OneByteConst);
type OneByteConstIsCacheable =
unsafe extern "C" fn(*const OneByteConst) -> bool;
type OneByteConstData =
unsafe extern "C" fn(*const OneByteConst) -> *const char;
type OneByteConstLength = unsafe extern "C" fn(*const OneByteConst) -> usize;
type OneByteConstUnaccount =
unsafe extern "C" fn(*const OneByteConst, *mut RealIsolate);
type OneByteConstEstimateMemoryUsage =
unsafe extern "C" fn(*const OneByteConst) -> size_t;
type OneByteConstEstimateSharedMemoryUsage =
unsafe extern "C" fn(*const OneByteConst, *mut ());
#[repr(C)]
struct OneByteConstVtable {
#[cfg(target_family = "windows")]
_offset_to_top: usize,
_typeinfo: *const (),
delete1: OneByteConstNoOp,
#[cfg(not(target_family = "windows"))]
delete2: OneByteConstNoOp,
is_cacheable: OneByteConstIsCacheable,
unaccount: OneByteConstUnaccount,
estimate_memory_usage: OneByteConstEstimateMemoryUsage,
estimate_shared_memory_usage: OneByteConstEstimateSharedMemoryUsage,
dispose: OneByteConstNoOp,
lock: OneByteConstNoOp,
unlock: OneByteConstNoOp,
data: OneByteConstData,
length: OneByteConstLength,
}
const ONE_BYTE_CONST_VTABLE: OneByteConstVtable = OneByteConstVtable {
#[cfg(target_family = "windows")]
_offset_to_top: 0,
_typeinfo: std::ptr::null(),
delete1: one_byte_const_no_op,
#[cfg(not(target_family = "windows"))]
delete2: one_byte_const_no_op,
is_cacheable: one_byte_const_is_cacheable,
unaccount: one_byte_const_unaccount,
estimate_memory_usage: one_byte_const_estimate_memory_usage,
estimate_shared_memory_usage: one_byte_const_estimate_shared_memory_usage,
dispose: one_byte_const_no_op,
lock: one_byte_const_no_op,
unlock: one_byte_const_no_op,
data: one_byte_const_data,
length: one_byte_const_length,
};
#[repr(C)]
#[derive(Debug, Default)]
pub enum NewStringType {
#[default]
Normal,
Internalized,
}
bitflags! {
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct WriteOptions: int {
const NO_OPTIONS = 0;
const HINT_MANY_WRITES_EXPECTED = 1;
const NO_NULL_TERMINATION = 2;
const PRESERVE_ONE_BYTE_NULL = 4;
const REPLACE_INVALID_UTF8 = 8;
}
}
bitflags! {
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct WriteFlags: int {
const kNullTerminate = crate::binding::v8_String_WriteFlags_kNullTerminate as _;
const kReplaceInvalidUtf8 = crate::binding::v8_String_WriteFlags_kReplaceInvalidUtf8 as _;
}
}
impl String {
pub const MAX_LENGTH: usize = v8__String__kMaxLength as _;
#[inline(always)]
pub fn empty<'s>(scope: &PinScope<'s, '_, ()>) -> Local<'s, String> {
unsafe { scope.cast_local(|sd| v8__String__Empty(sd.get_isolate_ptr())) }
.unwrap()
}
#[inline(always)]
pub fn new_from_utf8<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: &[u8],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
if buffer.is_empty() {
return Some(Self::empty(scope));
}
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromUtf8(
sd.get_isolate_ptr(),
buffer.as_ptr() as *const char,
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_from_one_byte<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: &[u8],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromOneByte(
sd.get_isolate_ptr(),
buffer.as_ptr(),
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_from_two_byte<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: &[u16],
new_type: NewStringType,
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewFromTwoByte(
sd.get_isolate_ptr(),
buffer.as_ptr(),
new_type,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn length(&self) -> usize {
unsafe { v8__String__Length(self) as usize }
}
#[inline(always)]
pub fn utf8_length(&self, scope: &Isolate) -> usize {
unsafe { v8__String__Utf8Length(self, scope.as_real_ptr()) as usize }
}
#[inline(always)]
pub fn write_v2(
&self,
scope: &Isolate,
offset: u32,
buffer: &mut [u16],
flags: WriteFlags,
) {
unsafe {
v8__String__Write_v2(
self,
scope.as_real_ptr(),
offset,
self.length().min(buffer.len()) as _,
buffer.as_mut_ptr(),
flags.bits(),
)
}
}
#[inline(always)]
pub fn write_one_byte_v2(
&self,
scope: &Isolate,
offset: u32,
buffer: &mut [u8],
flags: WriteFlags,
) {
unsafe {
v8__String__WriteOneByte_v2(
self,
scope.as_real_ptr(),
offset,
self.length().min(buffer.len()) as _,
buffer.as_mut_ptr(),
flags.bits(),
)
}
}
#[inline(always)]
pub fn write_one_byte_uninit_v2(
&self,
scope: &Isolate,
offset: u32,
buffer: &mut [MaybeUninit<u8>],
flags: WriteFlags,
) {
unsafe {
v8__String__WriteOneByte_v2(
self,
scope.as_real_ptr(),
offset,
self.length().min(buffer.len()) as _,
buffer.as_mut_ptr() as _,
flags.bits(),
)
}
}
#[inline(always)]
pub fn write_utf8_v2(
&self,
scope: &Isolate,
buffer: &mut [u8],
flags: WriteFlags,
processed_characters_return: Option<&mut usize>,
) -> usize {
unsafe {
let buffer = {
let len = buffer.len();
let data = buffer.as_mut_ptr().cast();
slice::from_raw_parts_mut(data, len)
};
self.write_utf8_uninit_v2(
scope,
buffer,
flags,
processed_characters_return,
)
}
}
pub fn write_utf8_uninit_v2(
&self,
scope: &Isolate,
buffer: &mut [MaybeUninit<u8>],
flags: WriteFlags,
processed_characters_return: Option<&mut usize>,
) -> usize {
let bytes = unsafe {
v8__String__WriteUtf8_v2(
self,
scope.as_real_ptr(),
buffer.as_mut_ptr() as _,
buffer.len(),
flags.bits(),
processed_characters_return
.map(|p| p as *mut _)
.unwrap_or(std::ptr::null_mut()),
)
};
bytes as usize
}
#[inline(always)]
pub fn new<'s>(
scope: &PinScope<'s, '_, ()>,
value: &str,
) -> Option<Local<'s, String>> {
Self::new_from_utf8(scope, value.as_ref(), NewStringType::Normal)
}
#[inline(always)]
pub const fn create_external_onebyte_const(
buffer: &'static [u8],
) -> OneByteConst {
assert!(buffer.is_ascii() && buffer.len() <= ((1 << 29) - 24));
OneByteConst {
vtable: &ONE_BYTE_CONST_VTABLE.delete1,
cached_data: buffer.as_ptr() as *const char,
length: buffer.len(),
}
}
#[inline(always)]
pub const unsafe fn create_external_onebyte_const_unchecked(
buffer: &'static [u8],
) -> OneByteConst {
OneByteConst {
vtable: &ONE_BYTE_CONST_VTABLE.delete1,
cached_data: buffer.as_ptr() as *const char,
length: buffer.len(),
}
}
#[inline(always)]
pub fn new_from_onebyte_const<'s>(
scope: &PinScope<'s, '_, ()>,
onebyte_const: &'static OneByteConst,
) -> Option<Local<'s, String>> {
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByteConst(sd.get_isolate_ptr(), onebyte_const)
})
}
}
#[inline(always)]
pub fn new_external_onebyte_static<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: &'static [u8],
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByteStatic(
sd.get_isolate_ptr(),
buffer.as_ptr() as *const char,
buffer_len,
)
})
}
}
#[inline(always)]
pub fn new_external_onebyte<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: Box<[u8]>,
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len();
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByte(
sd.get_isolate_ptr(),
Box::into_raw(buffer).cast::<char>(),
buffer_len,
free_rust_external_onebyte,
)
})
}
}
#[inline(always)]
pub unsafe fn new_external_onebyte_raw<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: *mut char,
buffer_len: usize,
destructor: unsafe extern "C" fn(*mut char, usize),
) -> Option<Local<'s, String>> {
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalOneByte(
sd.get_isolate_ptr(),
buffer,
buffer_len,
destructor,
)
})
}
}
#[inline(always)]
pub fn new_external_twobyte_static<'s>(
scope: &PinScope<'s, '_, ()>,
buffer: &'static [u16],
) -> Option<Local<'s, String>> {
let buffer_len = buffer.len().try_into().ok()?;
unsafe {
scope.cast_local(|sd| {
v8__String__NewExternalTwoByteStatic(
sd.get_isolate_ptr(),
buffer.as_ptr(),
buffer_len,
)
})
}
}
#[inline]
pub fn get_external_string_resource(
&self,
) -> Option<NonNull<ExternalStringResource>> {
NonNull::new(unsafe { v8__String__GetExternalStringResource(self) })
}
#[inline]
pub fn get_external_onebyte_string_resource(
&self,
) -> Option<NonNull<ExternalOneByteStringResource>> {
let (base, encoding) = self.get_external_string_resource_base();
let base = base?;
if encoding != Encoding::OneByte {
return None;
}
Some(base.cast())
}
pub fn get_external_string_resource_base(
&self,
) -> (Option<NonNull<ExternalStringResourceBase>>, Encoding) {
let mut encoding = Encoding::Unknown;
(
NonNull::new(unsafe {
v8__String__GetExternalStringResourceBase(self, &mut encoding)
}),
encoding,
)
}
#[inline(always)]
pub fn is_external(&self) -> bool {
self.is_external_onebyte() || self.is_external_twobyte()
}
#[inline(always)]
pub fn is_external_onebyte(&self) -> bool {
unsafe { v8__String__IsExternalOneByte(self) }
}
#[inline(always)]
pub fn is_external_twobyte(&self) -> bool {
unsafe { v8__String__IsExternalTwoByte(self) }
}
#[inline(always)]
pub fn is_onebyte(&self) -> bool {
unsafe { v8__String__IsOneByte(self) }
}
#[inline(always)]
pub fn contains_only_onebyte(&self) -> bool {
unsafe { v8__String__ContainsOnlyOneByte(self) }
}
pub fn to_rust_string_lossy(&self, scope: &Isolate) -> std::string::String {
let len_utf16 = self.length();
if len_utf16 == 0 {
return std::string::String::new();
}
let len_utf8 = self.utf8_length(scope);
if self.is_onebyte() && len_utf8 == len_utf16 {
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf16, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf16);
self.write_one_byte_uninit_v2(
scope,
0,
&mut *buffer,
WriteFlags::kReplaceInvalidUtf8,
);
let buffer = data as *mut u8;
return std::string::String::from_raw_parts(
buffer, len_utf16, len_utf16,
);
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf8, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf8);
let length = self.write_utf8_uninit_v2(
scope,
&mut *buffer,
WriteFlags::kReplaceInvalidUtf8,
None,
);
debug_assert!(length == len_utf8);
let buffer = data as *mut u8;
std::string::String::from_raw_parts(buffer, length, len_utf8)
}
}
pub fn to_rust_cow_lossy<'a, const N: usize>(
&self,
scope: &mut Isolate,
buffer: &'a mut [MaybeUninit<u8>; N],
) -> Cow<'a, str> {
let len_utf16 = self.length();
if len_utf16 == 0 {
return "".into();
}
let len_utf8 = self.utf8_length(scope);
if self.is_onebyte() && len_utf8 == len_utf16 {
if len_utf16 <= N {
self.write_one_byte_uninit_v2(scope, 0, buffer, WriteFlags::empty());
unsafe {
let buffer = &mut buffer[..len_utf16];
let buffer = &mut *(buffer as *mut [_] as *mut [u8]);
return Cow::Borrowed(std::str::from_utf8_unchecked(buffer));
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf16, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf16);
self.write_one_byte_uninit_v2(
scope,
0,
&mut *buffer,
WriteFlags::kReplaceInvalidUtf8,
);
let buffer = data as *mut u8;
return Cow::Owned(std::string::String::from_raw_parts(
buffer, len_utf16, len_utf16,
));
}
}
if len_utf8 <= N {
let length = self.write_utf8_uninit_v2(
scope,
buffer,
WriteFlags::kReplaceInvalidUtf8,
None,
);
debug_assert!(length == len_utf8);
unsafe {
let buffer = &mut buffer[..length];
let buffer = &mut *(buffer as *mut [_] as *mut [u8]);
return Cow::Borrowed(std::str::from_utf8_unchecked(buffer));
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(len_utf8, 1).unwrap();
let data = std::alloc::alloc(layout) as *mut MaybeUninit<u8>;
let buffer = std::ptr::slice_from_raw_parts_mut(data, len_utf8);
let length = self.write_utf8_uninit_v2(
scope,
&mut *buffer,
WriteFlags::kReplaceInvalidUtf8,
None,
);
debug_assert!(length == len_utf8);
let buffer = data as *mut u8;
Cow::Owned(std::string::String::from_raw_parts(
buffer, length, len_utf8,
))
}
}
}
#[inline]
pub unsafe extern "C" fn free_rust_external_onebyte(s: *mut char, len: usize) {
unsafe {
let slice = std::slice::from_raw_parts_mut(s, len);
drop(Box::from_raw(slice));
}
}
#[derive(Debug, PartialEq)]
pub enum ValueViewData<'s> {
OneByte(&'s [u8]),
TwoByte(&'s [u16]),
}
#[repr(C)]
pub struct ValueView<'s>(
[u8; crate::binding::v8__String__ValueView_SIZE],
PhantomData<&'s ()>,
);
impl<'s> ValueView<'s> {
#[inline(always)]
pub fn new(isolate: &mut Isolate, string: Local<'s, String>) -> Self {
let mut v = std::mem::MaybeUninit::uninit();
unsafe {
v8__String__ValueView__CONSTRUCT(
v.as_mut_ptr(),
isolate.as_real_ptr(),
&*string,
);
v.assume_init()
}
}
#[inline(always)]
pub fn data(&self) -> ValueViewData<'_> {
unsafe {
let data = v8__String__ValueView__data(self);
let length = v8__String__ValueView__length(self) as usize;
if v8__String__ValueView__is_one_byte(self) {
ValueViewData::OneByte(std::slice::from_raw_parts(data as _, length))
} else {
ValueViewData::TwoByte(std::slice::from_raw_parts(data as _, length))
}
}
}
}
impl Drop for ValueView<'_> {
fn drop(&mut self) {
unsafe { v8__String__ValueView__DESTRUCT(self) }
}
}