mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 08:07:38 +00:00
Add borrowed AllowShared BufferSource conversion for algorithms that copy after dictionaries are converted, including native range writes and atomic shared-memory byte access. Reject resizable/growable storage and author proxies using native identity, including detached resizable buffers and views. Tests cover late snapshots, bounded writes, surrounding bytes, detachment and native resizable flags. Individual API migrations are separate.
174 lines
5.9 KiB
Rust
174 lines
5.9 KiB
Rust
//! Borrowed BufferSource conversion for algorithms that copy after dictionary conversion.
|
|
|
|
use crate::{Context, WebIdlConverter, WebIdlError};
|
|
use std::sync::atomic::{AtomicU8, Ordering};
|
|
|
|
/// `[AllowShared] BufferSource`, retaining the native buffer identity instead
|
|
/// of copying during conversion. Resizable and growable buffers require the
|
|
/// separate `[AllowResizable]` annotation and are rejected here.
|
|
pub enum AllowSharedBufferSource<'s> {
|
|
Buffer(v8::Local<'s, v8::ArrayBuffer>),
|
|
Shared(v8::Local<'s, v8::SharedArrayBuffer>),
|
|
View(v8::Local<'s, v8::ArrayBufferView>),
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for AllowSharedBufferSource<'s> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let source = if let Ok(buffer) = v8::Local::<v8::ArrayBuffer>::try_from(value) {
|
|
Self::Buffer(buffer)
|
|
} else if let Ok(buffer) = v8::Local::<v8::SharedArrayBuffer>::try_from(value) {
|
|
Self::Shared(buffer)
|
|
} else if let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(value) {
|
|
Self::View(view)
|
|
} else {
|
|
return Err(WebIdlError::cannot_convert(context, "BufferSource"));
|
|
};
|
|
let native_resizable = match &source {
|
|
Self::Buffer(buffer) => buffer.is_resizable_by_user_javascript(),
|
|
Self::View(view) => view.buffer(scope).is_some_and(|buffer| {
|
|
buffer.is_array_buffer() && buffer.is_resizable_by_user_javascript()
|
|
}),
|
|
Self::Shared(_) => false,
|
|
};
|
|
if native_resizable
|
|
|| source
|
|
.backing_store(scope)
|
|
.is_some_and(|backing| backing.is_resizable_by_user_javascript())
|
|
{
|
|
return Err(WebIdlError::cannot_convert(
|
|
context,
|
|
"non-resizable BufferSource",
|
|
));
|
|
}
|
|
Ok(source)
|
|
}
|
|
}
|
|
|
|
impl AllowSharedBufferSource<'_> {
|
|
pub fn byte_length(&self) -> usize {
|
|
match self {
|
|
Self::Buffer(buffer) => buffer.byte_length(),
|
|
Self::Shared(buffer) => buffer.byte_length(),
|
|
Self::View(view) => view.byte_length(),
|
|
}
|
|
}
|
|
|
|
fn byte_offset(&self) -> usize {
|
|
match self {
|
|
Self::View(view) => view.byte_offset(),
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
fn backing_store(
|
|
&self,
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
) -> Option<v8::SharedRef<v8::BackingStore>> {
|
|
match self {
|
|
Self::Buffer(buffer) => Some(buffer.get_backing_store()),
|
|
Self::Shared(buffer) => Some(buffer.get_backing_store()),
|
|
Self::View(view) => view.buffer(scope).map(|buffer| buffer.get_backing_store()),
|
|
}
|
|
}
|
|
|
|
pub fn to_vec(&self, scope: &mut v8::PinScope<'_, '_>) -> Vec<u8> {
|
|
let len = self.byte_length();
|
|
if len == 0 {
|
|
return Vec::new();
|
|
}
|
|
let backing = self
|
|
.backing_store(scope)
|
|
.expect("BufferSource has native backing");
|
|
let offset = self.byte_offset();
|
|
if backing.is_shared() {
|
|
// SAFETY: fixed shared storage is retained by `backing`, each byte
|
|
// is aligned for AtomicU8, and the view bounds were checked by V8.
|
|
let bytes = unsafe {
|
|
std::slice::from_raw_parts(
|
|
backing
|
|
.data()
|
|
.expect("nonempty backing")
|
|
.as_ptr()
|
|
.cast::<AtomicU8>()
|
|
.add(offset),
|
|
len,
|
|
)
|
|
};
|
|
bytes
|
|
.iter()
|
|
.map(|byte| byte.load(Ordering::Relaxed))
|
|
.collect()
|
|
} else {
|
|
backing[offset..offset + len]
|
|
.iter()
|
|
.map(|byte| byte.get())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
/// Copy into the native view's byte range, preserving surrounding bytes.
|
|
pub fn write_bytes(&self, scope: &mut v8::PinScope<'_, '_>, bytes: &[u8]) -> bool {
|
|
if bytes.len() > self.byte_length() {
|
|
return false;
|
|
}
|
|
if bytes.is_empty() {
|
|
return true;
|
|
}
|
|
let backing = self
|
|
.backing_store(scope)
|
|
.expect("BufferSource has native backing");
|
|
let offset = self.byte_offset();
|
|
if backing.is_shared() {
|
|
// SAFETY: fixed shared storage is retained and V8 validated the
|
|
// view range; byte stores are unordered as required for BufferSource.
|
|
let destination = unsafe {
|
|
std::slice::from_raw_parts(
|
|
backing
|
|
.data()
|
|
.expect("nonempty backing")
|
|
.as_ptr()
|
|
.cast::<AtomicU8>()
|
|
.add(offset),
|
|
bytes.len(),
|
|
)
|
|
};
|
|
for (cell, byte) in destination.iter().zip(bytes) {
|
|
cell.store(*byte, Ordering::Relaxed);
|
|
}
|
|
} else {
|
|
for (cell, byte) in backing[offset..offset + bytes.len()].iter().zip(bytes) {
|
|
cell.set(*byte);
|
|
}
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
impl<'s> WebIdlConverter<'s> for v8::Local<'s, v8::ArrayBuffer> {
|
|
type Options = ();
|
|
|
|
fn convert(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
context: Context,
|
|
_options: &Self::Options,
|
|
) -> Result<Self, WebIdlError> {
|
|
let buffer = Self::try_from(value)
|
|
.map_err(|_| WebIdlError::cannot_convert(context, "ArrayBuffer"))?;
|
|
if buffer.is_resizable_by_user_javascript() {
|
|
return Err(WebIdlError::cannot_convert(
|
|
context,
|
|
"non-resizable ArrayBuffer",
|
|
));
|
|
}
|
|
Ok(buffer)
|
|
}
|
|
}
|