cred: isolate scripts in WASI and move RSA signing to AWS-LC

This commit is contained in:
2026-11-06 22:22:49 +00:00
parent 5b3e1393f5
commit 86c5d8a4e4
217 changed files with 155392 additions and 391 deletions
+298
View File
@@ -0,0 +1,298 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
class::{Trace, Tracer},
Ctx, FromJs, IntoJs, JsLifetime, Result, Value,
};
macro_rules! define_any_of {
($name:ident, $($variant:ident),+) => {
#[derive(Debug, Clone)]
pub enum $name<$($variant),+> {
$(
$variant($variant),
)+
}
define_any_of_from_js!($name, $($variant),+);
impl<'js, $($variant: IntoJs<'js>),+> IntoJs<'js> for $name<$($variant),+> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self {
$(
Self::$variant(val) => val.into_js(ctx),
)+
}
}
}
unsafe impl<'js, $($variant: JsLifetime<'js>),+> JsLifetime<'js> for $name<$($variant),+> {
type Changed<'to> = $name<$($variant::Changed<'to>),+>;
}
impl<'js, $($variant: Trace<'js>),+> Trace<'js> for $name<$($variant),+> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
match self {
$(
Self::$variant(val) => val.trace(tracer),
)+
}
}
}
define_any_of_methods!($name, $($variant),+);
};
}
macro_rules! define_any_of_from_js {
($name:ident, $first:ident, $($rest:ident),+) => {
impl<'js, $first: FromJs<'js>, $($rest: FromJs<'js>),+> FromJs<'js> for $name<$first, $($rest),+> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
define_any_of_from_js_impl!($name, ctx, value, $first, $($rest),+)
}
}
};
}
macro_rules! define_any_of_from_js_impl {
($name:ident, $ctx:ident, $value:ident, $first:ident) => {
$first::from_js($ctx, $value).map($name::$first)
};
($name:ident, $ctx:ident, $value:ident, $first:ident, $($rest:ident),+) => {
$first::from_js($ctx, $value.clone()).map($name::$first).or_else(|error| {
if error.is_from_js() {
define_any_of_from_js_impl!($name, $ctx, $value, $($rest),+)
} else {
Err(error)
}
})
};
}
macro_rules! define_any_of_variant_methods {
($variant:ident, $is_fn:ident, $as_fn:ident, $as_mut_fn:ident, $into_fn:ident) => {
#[allow(dead_code)]
pub fn $is_fn(&self) -> bool {
matches!(self, Self::$variant(_))
}
#[allow(dead_code)]
pub fn $as_fn(&self) -> Option<&$variant> {
match self {
Self::$variant(val) => Some(val),
_ => None,
}
}
#[allow(dead_code)]
pub fn $as_mut_fn(&mut self) -> Option<&mut $variant> {
match self {
Self::$variant(val) => Some(val),
_ => None,
}
}
#[allow(dead_code)]
pub fn $into_fn(self) -> std::result::Result<$variant, Self> {
match self {
Self::$variant(val) => Ok(val),
other => Err(other),
}
}
};
(A) => {
define_any_of_variant_methods!(A, is_a, as_a, as_a_mut, into_a);
};
(B) => {
define_any_of_variant_methods!(B, is_b, as_b, as_b_mut, into_b);
};
(C) => {
define_any_of_variant_methods!(C, is_c, as_c, as_c_mut, into_c);
};
(D) => {
define_any_of_variant_methods!(D, is_d, as_d, as_d_mut, into_d);
};
(E) => {
define_any_of_variant_methods!(E, is_e, as_e, as_e_mut, into_e);
};
(F) => {
define_any_of_variant_methods!(F, is_f, as_f, as_f_mut, into_f);
};
(G) => {
define_any_of_variant_methods!(G, is_g, as_g, as_g_mut, into_g);
};
(H) => {
define_any_of_variant_methods!(H, is_h, as_h, as_h_mut, into_h);
};
}
macro_rules! define_any_of_methods {
($name:ident, $($variant:ident),+) => {
impl<$($variant),+> $name<$($variant),+> {
$(
define_any_of_variant_methods!($variant);
)+
}
};
}
define_any_of!(AnyOf2, A, B);
define_any_of!(AnyOf3, A, B, C);
define_any_of!(AnyOf4, A, B, C, D);
define_any_of!(AnyOf5, A, B, C, D, E);
define_any_of!(AnyOf6, A, B, C, D, E, F);
define_any_of!(AnyOf7, A, B, C, D, E, F, G);
define_any_of!(AnyOf8, A, B, C, D, E, F, G, H);
#[cfg(test)]
mod tests {
use super::*;
use rquickjs::{Context, Runtime};
#[test]
fn test_any_of_string_number() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test string conversion
let val: Value = ctx.eval("'hello'").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(any.is_a());
assert_eq!(any.as_a().unwrap(), "hello");
assert!(!any.is_b());
assert!(any.as_b().is_none());
// Test number conversion
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(!any.is_a());
assert!(any.is_b());
assert_eq!(*any.as_b().unwrap(), 42);
});
}
#[test]
fn test_any_of_fallback() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that it tries in order
let val: Value = ctx.eval("true").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_c());
assert!(*any.as_c().unwrap());
assert!(!any.is_a());
assert!(!any.is_b());
});
}
#[test]
fn test_any_of_into_js() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
let any: AnyOf2<String, i32> = AnyOf2::A("test".to_string());
let val: Value = any.into_js(&ctx).unwrap();
let result: String = val.get().unwrap();
assert_eq!(result, "test");
let any: AnyOf2<String, i32> = AnyOf2::B(99);
let val: Value = any.into_js(&ctx).unwrap();
let result: i32 = val.get().unwrap();
assert_eq!(result, 99);
});
}
#[test]
fn test_any_of_methods() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test all methods for variant A
let val: Value = ctx.eval("'test'").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_a());
assert_eq!(any.as_a().unwrap(), "test");
assert_eq!(any.into_a().unwrap(), "test");
// Test all methods for variant B
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b());
assert_eq!(*any.as_b().unwrap(), 42);
assert_eq!(any.into_b().unwrap(), 42);
// Test all methods for variant C
let val: Value = ctx.eval("true").unwrap();
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_c());
assert!(*any.as_c().unwrap());
assert!(any.into_c().unwrap());
});
}
#[test]
fn test_any_of_mutable_methods() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
let val: Value = ctx.eval("42").unwrap();
let mut any: AnyOf4<String, i32, bool, f64> = AnyOf4::from_js(&ctx, val).unwrap();
if let Some(n) = any.as_b_mut() {
*n = 100;
}
assert_eq!(any.into_b().unwrap(), 100);
});
}
#[test]
fn test_any_of_error_propagation() {
use rquickjs::{Array, Object};
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that conversion errors cause fallback to next type
let val: Value = ctx.eval("42").unwrap();
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
assert!(any.is_b());
// Test that all types fail results in an error
let val: Value = ctx.eval("null").unwrap();
let result: Result<AnyOf2<Object, Array>> = AnyOf2::from_js(&ctx, val);
assert!(result.is_err());
});
}
#[test]
fn test_any_of_conversion_order() {
let rt = Runtime::new().unwrap();
let ctx = Context::full(&rt).unwrap();
ctx.with(|ctx| {
// Test that conversion happens in order A, B, C, D, E
// Since 42 can be converted to f64, i32, etc., but String comes first and fails,
// it should try the next successful conversion
let val: Value = ctx.eval("42").unwrap();
// String should fail, so it tries i32 which succeeds
let any: AnyOf3<String, i32, f64> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b());
// If we flip the order, f64 would be tried first (but both work)
let val: Value = ctx.eval("3.14").unwrap();
let any: AnyOf3<String, f64, i32> = AnyOf3::from_js(&ctx, val).unwrap();
assert!(any.is_b()); // f64 should succeed first
});
}
}
@@ -0,0 +1,226 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{
cmp::min,
collections::VecDeque,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Mutex,
},
};
use tokio::sync::{Notify, Semaphore};
#[derive(Clone)]
pub struct BytearrayBuffer {
inner: Arc<Mutex<VecDeque<u8>>>,
max_capacity: Arc<AtomicUsize>,
len: Arc<AtomicUsize>,
notify: Arc<Notify>,
closed: Arc<AtomicBool>,
write_semaphore: Arc<Semaphore>,
}
impl BytearrayBuffer {
pub fn new(capacity: usize) -> Self {
let queue = VecDeque::with_capacity(capacity);
let capacity = queue.capacity();
Self {
inner: Arc::new(Mutex::new(queue)),
len: Arc::new(AtomicUsize::new(0)),
max_capacity: Arc::new(AtomicUsize::new(capacity)),
notify: Arc::new(Notify::new()),
closed: Arc::new(AtomicBool::new(false)),
write_semaphore: Arc::new(Semaphore::new(1)),
}
}
pub fn len(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
pub fn write_forced(&self, item: &[u8]) {
let mut inner = self.inner.lock().unwrap();
inner.extend(item);
let capacity = inner.capacity();
self.len.fetch_add(item.len(), Ordering::Relaxed);
self.max_capacity.store(capacity, Ordering::Relaxed);
}
pub async fn write(&self, item: &mut [u8]) -> usize {
let _ = self.write_semaphore.acquire().await.unwrap();
let mut slice_index = 0;
loop {
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
if self.closed.load(Ordering::Relaxed) {
return max_capacity;
}
let len = self.len.load(Ordering::Relaxed);
let available = max_capacity - len;
if available > 0 {
let end_index = min(item.len() - 1, slice_index + available - 1);
let sub_slice = &item[slice_index..=end_index];
let slice_length = sub_slice.len();
slice_index += slice_length;
self.inner.lock().unwrap().extend(sub_slice);
self.len.fetch_add(slice_length, Ordering::Relaxed);
if slice_index == item.len() {
return max_capacity;
}
}
self.notify.notified().await;
}
}
#[allow(dead_code)]
pub fn is_closed(&self) -> bool {
self.closed.load(Ordering::Relaxed)
}
pub async fn close(&self) {
self.closed.store(true, Ordering::Relaxed);
self.notify.notify_one();
//wait for write to finish
let _ = self.write_semaphore.acquire().await.unwrap();
}
pub async fn clear(&self) {
self.closed.store(false, Ordering::Relaxed);
self.notify.notify_one();
//wait for write to finish
let _ = self.write_semaphore.acquire().await.unwrap();
self.len.store(0, Ordering::Relaxed);
self.inner.lock().unwrap().clear();
self.closed.store(false, Ordering::Relaxed);
}
pub fn read(&self, desired_size: Option<usize>) -> Option<Vec<u8>> {
let mut inner = self.inner.lock().unwrap();
let done = self.closed.load(Ordering::Relaxed);
let items = if done {
Some(inner.drain(0..).collect())
} else if let Some(desired_len) = desired_size {
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
if desired_len > max_capacity {
let diff = desired_len - max_capacity;
inner.reserve(diff - 1);
let mut max_capacity = inner.capacity();
if desired_len > max_capacity {
inner.reserve(desired_len - max_capacity);
max_capacity = inner.capacity();
}
drop(inner);
self.max_capacity.store(max_capacity, Ordering::Relaxed);
self.notify.notify_one();
return None;
}
let len = self.len.load(Ordering::Relaxed);
if desired_len > len {
self.notify.notify_one();
return None;
}
Some(inner.drain(0..desired_len).collect())
} else {
Some(inner.drain(0..).collect())
};
self.len.store(inner.len(), Ordering::Relaxed);
drop(inner);
self.notify.notify_one();
items
}
}
#[cfg(test)]
mod tests {
use super::BytearrayBuffer;
#[tokio::test]
async fn clear_while_writing() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (0..=255).collect();
queue.write(&mut vec).await;
});
queue2.clear().await
}
#[tokio::test]
async fn write_one_at_a_time() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
let queue3 = queue.clone();
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (0..=127).collect();
queue.write(&mut vec).await;
});
tokio::task::spawn(async move {
let mut vec: Vec<u8> = (128..=255).collect();
queue2.write(&mut vec).await;
});
let mut data = Vec::<u8>::new();
loop {
tokio::task::yield_now().await;
if let Some(bytes) = queue3.read(Some(256)) {
data.extend(bytes);
break;
}
}
//assert that data in vec is increment from 0 to 255
for i in 0..=255 {
assert_eq!(data[i as usize], i);
}
}
#[tokio::test]
async fn queue() {
let queue = BytearrayBuffer::new(8);
let queue2 = queue.clone();
let write_task = tokio::task::spawn(async move {
for _ in 0..=255 {
let mut vec: Vec<u8> = (0..=255).collect();
queue.write(&mut vec).await;
}
queue.close().await;
});
let mut data = Vec::<u8>::new();
loop {
let done = queue2.is_closed();
tokio::task::yield_now().await;
if let Some(bytes) = queue2.read(Some(9)) {
data.extend(bytes);
}
if done {
break;
}
}
let _ = write_task.await;
assert_eq!(data.len(), 256 * 256)
}
}
+380
View File
@@ -0,0 +1,380 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::rc::Rc;
use rquickjs::{
atom::PredefinedAtom,
class::{Trace, Tracer},
function::Constructor,
ArrayBuffer, Coerced, Ctx, Exception, FromJs, IntoJs, JsLifetime, Object, Result, TypedArray,
Value,
};
use crate::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt};
#[derive(Clone, PartialEq)]
pub enum ObjectBytes<'js> {
U8Array(TypedArray<'js, u8>),
I8Array(TypedArray<'js, i8>),
U16Array(TypedArray<'js, u16>),
I16Array(TypedArray<'js, i16>),
U32Array(TypedArray<'js, u32>),
I32Array(TypedArray<'js, i32>),
U64Array(TypedArray<'js, u64>),
I64Array(TypedArray<'js, i64>),
F32Array(TypedArray<'js, f32>),
F64Array(TypedArray<'js, f64>),
DataView(ArrayBuffer<'js>),
Vec(Vec<u8>),
}
// Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64
unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> {
type Changed<'to> = ObjectBytes<'to>;
}
impl<'js> Trace<'js> for ObjectBytes<'js> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
match self {
ObjectBytes::U8Array(a) => a.trace(tracer),
ObjectBytes::I8Array(a) => a.trace(tracer),
ObjectBytes::U16Array(a) => a.trace(tracer),
ObjectBytes::I16Array(a) => a.trace(tracer),
ObjectBytes::U32Array(a) => a.trace(tracer),
ObjectBytes::I32Array(a) => a.trace(tracer),
ObjectBytes::U64Array(a) => a.trace(tracer),
ObjectBytes::I64Array(a) => a.trace(tracer),
ObjectBytes::F32Array(a) => a.trace(tracer),
ObjectBytes::F64Array(a) => a.trace(tracer),
ObjectBytes::DataView(d) => d.trace(tracer),
ObjectBytes::Vec(v) => v.trace(tracer),
}
}
}
impl<'js> IntoJs<'js> for ObjectBytes<'js> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self {
ObjectBytes::U8Array(a) => a.into_js(ctx),
ObjectBytes::I8Array(a) => a.into_js(ctx),
ObjectBytes::U16Array(a) => a.into_js(ctx),
ObjectBytes::I16Array(a) => a.into_js(ctx),
ObjectBytes::U32Array(a) => a.into_js(ctx),
ObjectBytes::I32Array(a) => a.into_js(ctx),
ObjectBytes::U64Array(a) => a.into_js(ctx),
ObjectBytes::I64Array(a) => a.into_js(ctx),
ObjectBytes::F32Array(a) => a.into_js(ctx),
ObjectBytes::F64Array(a) => a.into_js(ctx),
ObjectBytes::DataView(d) => {
let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?;
ctor.construct((d,))
},
ObjectBytes::Vec(v) => v.into_js(ctx),
}
}
}
impl<'js> TryFrom<ObjectBytes<'js>> for Vec<u8> {
type Error = Rc<str>;
fn try_from(value: ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
value.into_bytes_inner()
}
}
impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] {
type Error = Rc<str>;
fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
value.as_bytes_inner()
}
}
impl<'js> FromJs<'js> for ObjectBytes<'js> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
Self::from_offset(ctx, &value, 0, None)
}
}
impl<'js> ObjectBytes<'js> {
pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result<Self> {
Self::from_offset(ctx, value, 0, None)
}
pub fn from_offset(
ctx: &Ctx<'js>,
value: &Value<'js>,
offset: usize,
length: Option<usize>,
) -> Result<Self> {
if value.is_undefined() {
return Ok(ObjectBytes::Vec(vec![]));
}
if let Some(bytes) = get_string_bytes(value, offset, length)? {
return Ok(ObjectBytes::Vec(bytes));
}
if let Some(bytes) = get_array_bytes(value, offset, length)? {
return Ok(ObjectBytes::Vec(bytes));
}
if let Some(obj) = value.as_object() {
if let Some(bytes) = Self::from_array_buffer(obj)? {
return Ok(bytes);
}
}
if let Some(bytes) = get_coerced_string_bytes(value, offset, length) {
return Ok(ObjectBytes::Vec(bytes));
}
Err(Exception::throw_message(
ctx,
"value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
))
}
pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> {
self.as_bytes_inner().or_throw(ctx)
}
fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc<str>> {
match self {
ObjectBytes::U8Array(array) => array.as_bytes(),
ObjectBytes::I8Array(array) => array.as_bytes(),
ObjectBytes::U16Array(array) => array.as_bytes(),
ObjectBytes::I16Array(array) => array.as_bytes(),
ObjectBytes::U32Array(array) => array.as_bytes(),
ObjectBytes::I32Array(array) => array.as_bytes(),
ObjectBytes::U64Array(array) => array.as_bytes(),
ObjectBytes::I64Array(array) => array.as_bytes(),
ObjectBytes::F32Array(array) => array.as_bytes(),
ObjectBytes::F64Array(array) => array.as_bytes(),
ObjectBytes::DataView(array_buffer) => array_buffer.as_bytes(),
ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
}
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
}
pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result<Vec<u8>> {
self.into_bytes_inner().or_throw(ctx)
}
fn into_bytes_inner(self) -> std::result::Result<Vec<u8>, Rc<str>> {
if let ObjectBytes::Vec(bytes) = self {
return Ok(bytes);
}
Ok(self.as_bytes_inner()?.to_vec())
}
pub fn from_array_buffer(obj: &Object<'js>) -> Result<Option<ObjectBytes<'js>>> {
//most common
if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U8Array(typed_array)));
}
//second most common
if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::DataView(array_buffer)));
}
if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I8Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U16Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I16Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::U64Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::I64Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::F32Array(typed_array)));
}
if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
return Ok(Some(ObjectBytes::F64Array(typed_array)));
}
if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") {
return Ok(Some(ObjectBytes::DataView(array_buffer)));
}
Ok(None)
}
pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
let buffer = match self {
ObjectBytes::DataView(array_buffer) => (array_buffer.clone(), array_buffer.len(), 0),
ObjectBytes::U8Array(typed_array) => {
let byte_length = typed_array.len();
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I8Array(typed_array) => {
let byte_length = typed_array.len();
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U16Array(typed_array) => {
let byte_length = typed_array.len() * 2;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I16Array(typed_array) => {
let byte_length = typed_array.len() * 2;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::U64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::I64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::F32Array(typed_array) => {
let byte_length = typed_array.len() * 4;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
ObjectBytes::F64Array(typed_array) => {
let byte_length = typed_array.len() * 8;
(
typed_array.arraybuffer()?,
byte_length,
typed_array.get("byteOffset")?,
)
},
_ => return Ok(None),
};
Ok(Some(buffer))
}
}
pub fn get_start_end_indexes(
source_len: usize,
target_len: Option<usize>,
offset: usize,
) -> (usize, usize) {
if offset > source_len {
return (0, 0);
}
let target_len = target_len.unwrap_or(source_len - offset);
if offset + target_len > source_len {
return (offset, source_len);
}
(offset, target_len + offset)
}
pub fn get_array_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Result<Option<Vec<u8>>> {
if value.is_array() {
let array = value.as_array().unwrap();
let (start, end) = get_start_end_indexes(array.len(), length, offset);
let size = end - start;
let mut bytes: Vec<u8> = Vec::with_capacity(size);
for val in array.iter::<u8>().skip(start).take(size) {
let val: u8 = val?;
bytes.push(val);
}
return Ok(Some(bytes));
}
Ok(None)
}
pub fn get_coerced_string_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Option<Vec<u8>> {
if let Ok(val) = value.get::<Coerced<String>>() {
return Some(bytes_from_js_string(val.0, offset, length));
};
None
}
fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
let (start, end) = get_start_end_indexes(string.len(), length, offset);
string.as_bytes()[start..end].to_vec()
}
#[inline]
pub fn get_string_bytes(
value: &Value<'_>,
offset: usize,
length: Option<usize>,
) -> Result<Option<Vec<u8>>> {
if let Some(val) = value.as_string() {
let string = val.to_string()?;
return Ok(Some(bytes_from_js_string(string, offset, length)));
}
Ok(None)
}
pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
atom::PredefinedAtom, class::JsClass, object::Accessor, prelude::This, Array, Class, Ctx,
Function, Object, Result, Value,
};
use crate::primordials::{BasePrimordials, Primordial};
use super::{object::ObjectExt, result::OptionExt};
pub static CUSTOM_INSPECT_SYMBOL_DESCRIPTION: &str = "llrt.inspect.custom";
pub trait IteratorDef<'js>
where
Self: 'js + JsClass<'js> + Sized,
{
fn js_entries(&self, ctx: Ctx<'js>) -> Result<Array<'js>>;
fn js_iterator(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
let value = self.js_entries(ctx)?;
let obj = value.as_object();
let values_fn: Function = obj.get(PredefinedAtom::Values)?;
values_fn.call((This(value),))
}
}
pub fn get_class_name(value: &Value) -> Result<Option<String>> {
value
.get_optional::<_, Object>(PredefinedAtom::Constructor)?
.and_then_ok(|ctor| ctor.get_optional::<_, String>(PredefinedAtom::Name))
}
#[inline(always)]
pub fn get_class<'js, C>(provided: &Value<'js>) -> Result<Option<Class<'js, C>>>
where
C: JsClass<'js>,
{
if provided
.as_object()
.map(|p| p.instance_of::<C>())
.unwrap_or_default()
{
return Ok(Some(Class::<C>::from_value(provided)?));
}
Ok(None)
}
pub trait CustomInspectExtension<'js> {
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()>;
}
pub trait CustomInspect<'js>
where
Self: JsClass<'js>,
{
fn custom_inspect(&self, ctx: Ctx<'js>) -> Result<Object<'js>>;
}
impl<'js, C> CustomInspectExtension<'js> for Class<'js, C>
where
C: JsClass<'js> + CustomInspect<'js> + 'js,
{
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()> {
Self::define(globals)?;
let custom_inspect_symbol = BasePrimordials::get(globals.ctx())?
.symbol_custom_inspect
.clone();
if let Some(proto) = Class::<C>::prototype(globals.ctx())? {
proto.prop(
custom_inspect_symbol,
Accessor::from(|this: This<Class<'js, C>>, ctx| this.borrow().custom_inspect(ctx)),
)?;
}
Ok(())
}
}
+487
View File
@@ -0,0 +1,487 @@
use std::collections::HashSet;
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
atom::PredefinedAtom,
function::{Constructor, Opt, This},
Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, Null, Object, Result, Type, Value,
};
use super::{
hash,
object::ObjectExt,
primordials::{BasePrimordials, Primordial},
};
#[derive(Debug)]
enum StackItem<'js> {
Value(usize, Value<'js>, Option<String>, Option<usize>),
ObjectEnd,
}
#[derive(Debug)]
enum ObjectType {
Set,
Map,
}
#[derive(Debug)]
enum TapeValue<'js> {
Array(Array<'js>),
Object(Object<'js>),
Value(Value<'js>),
Collection(Option<Value<'js>>, ObjectType),
}
#[derive(Debug)]
struct TapeItem<'js> {
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
value: TapeValue<'js>,
}
pub fn structured_clone<'js>(
ctx: &Ctx<'js>,
value: Value<'js>,
options: Opt<Object<'js>>,
) -> Result<Value<'js>> {
let primordials = BasePrimordials::get(ctx)?;
let mut transfer_set = None;
if let Some(options) = options.0 {
if let Some(transfer_array) = options.get_optional::<_, Array>("transfer")? {
let mut set = HashSet::with_capacity(transfer_array.len());
for item in transfer_array.iter::<Value>() {
set.insert(item?);
}
transfer_set = Some(set);
}
}
let mut tape = Vec::<TapeItem>::with_capacity(10);
let mut stack = Vec::with_capacity(10);
let mut visited = Vec::<(usize, usize)>::with_capacity(10);
let mut index = 0usize;
stack.push(StackItem::Value(0, value, None, None));
while let Some(item) = stack.pop() {
match item {
StackItem::Value(parent, value, mut object_key, array_index) => {
if let Some(set) = &transfer_set {
if let Some(value) = set.get(&value) {
append_transfer_value(&mut tape, value, parent, object_key, array_index)?;
index += 1;
continue;
}
}
match value.type_of() {
Type::Proxy => {
return Err(Exception::throw_type(
ctx,
"A Proxy value could not be cloned",
));
},
Type::Object => {
if check_circular(
&mut tape,
&mut visited,
&value,
parent,
&mut object_key,
array_index,
index,
) {
index += 1;
continue;
}
//unsafe OK since we're guaranteed to be object by the match
let object = unsafe { value.as_object().unwrap_unchecked() };
if object.is_instance_of(&primordials.constructor_date) {
append_ctor_value(
&mut tape,
object,
&primordials.constructor_date,
parent,
object_key,
array_index,
)?;
index += 1;
continue;
}
if object.is_instance_of(&primordials.constructor_regexp) {
append_ctor_value(
&mut tape,
object,
&primordials.constructor_regexp,
parent,
object_key,
array_index,
)?;
index += 1;
continue;
}
let is_collection = if object.is_instance_of(&primordials.constructor_set) {
Some(ObjectType::Set)
} else if object.is_instance_of(&primordials.constructor_map) {
Some(ObjectType::Map)
} else {
None
};
if let Some(collection_type) = is_collection {
append_collection(
&mut tape,
&primordials.function_array_from,
object,
parent,
object_key,
array_index,
collection_type,
&mut stack,
index,
)?;
index += 1;
continue;
}
if primordials
.function_array_buffer_is_view
.call::<_, bool>((value.clone(),))?
{
append_buffer(&mut tape, object, parent, object_key, array_index)?;
index += 1;
continue;
}
let new: Object<'_> =
if object.is_instance_of(&primordials.constructor_error) {
primordials.constructor_error.construct(("",))
} else {
Object::new(ctx.clone())
}?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Object(new),
});
stack.push(StackItem::ObjectEnd);
for key in object.keys::<String>() {
let key = key?;
let value = object.get(&key)?;
stack.push(StackItem::Value(index, value, Some(key), None));
}
},
Type::Array => {
if check_circular(
&mut tape,
&mut visited,
&value,
parent,
&mut object_key,
array_index,
index,
) {
index += 1;
continue;
}
let new = Array::new(ctx.clone())?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Array(new),
});
stack.push(StackItem::ObjectEnd);
//unsafe OK since we're guaranteed to be object by the match
let array = unsafe { value.as_array().unwrap_unchecked() };
//reverse for loop of items in array
for array_index in (0usize..array.len()).rev() {
stack.push(StackItem::Value(
index,
array.get(array_index)?,
None,
Some(array_index),
));
}
},
_ => {
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
},
}
index += 1;
},
StackItem::ObjectEnd => {
visited.pop();
},
}
}
while let Some(item) = tape.pop() {
let value = match item.value {
TapeValue::Array(array) => array.into_value(),
TapeValue::Object(object) => object.into_value(),
TapeValue::Value(value) => value,
TapeValue::Collection(mut value, _) => value.take().unwrap(),
};
if tape.is_empty() {
return Ok(value);
}
let parent = &mut tape[item.parent];
let array_index = item.array_index;
let object_key = item.object_key;
match &mut parent.value {
TapeValue::Array(array) => {
array.set(array_index.unwrap(), value)?;
},
TapeValue::Object(object) => {
let string = object_key.unwrap();
object.set(string, value)?;
},
TapeValue::Collection(collection_value, collection_type) => {
match collection_type {
ObjectType::Set => {
collection_value.replace(primordials.constructor_set.construct((value,))?);
},
ObjectType::Map => {
collection_value.replace(primordials.constructor_map.construct((value,))?);
},
};
},
_ => {},
};
}
Null.into_js(ctx)
}
#[inline(always)]
#[cold]
fn append_buffer<'js>(
tape: &mut Vec<TapeItem<'js>>,
object: &Object<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let ctor: Constructor = object.get(PredefinedAtom::Constructor)?;
let slice: Function = object.get("slice")?;
let clone: Value = slice.call((This(object.clone()),))?;
let new = ctor.construct((clone,))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(new),
});
Ok(())
}
#[inline(always)]
#[cold]
#[allow(clippy::too_many_arguments)]
fn append_collection<'js>(
tape: &mut Vec<TapeItem<'js>>,
array_from: &Function<'js>,
object: &Object<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
collection_type: ObjectType,
stack: &mut Vec<StackItem<'js>>,
index: usize,
) -> Result<()> {
let array: Array = array_from.call((object.clone(),))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Collection(None, collection_type),
});
stack.push(StackItem::ObjectEnd);
stack.push(StackItem::Value(index, array.into(), None, None));
Ok(())
}
#[inline(always)]
fn check_circular(
tape: &mut Vec<TapeItem>,
visited: &mut Vec<(usize, usize)>,
value: &Value<'_>,
parent: usize,
object_key: &mut Option<String>,
array_index: Option<usize>,
index: usize,
) -> bool {
let hash = hash::default_hash(value);
if let Some(visited) = visited.iter().find(|v| v.0 == hash) {
append_circular(tape, visited, object_key, parent, array_index);
return true;
}
visited.push((hash, index));
false
}
#[inline(always)]
#[cold]
fn append_transfer_value<'js>(
tape: &mut Vec<TapeItem<'js>>,
value: &Value<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let value = if let Some(ab) = ArrayBuffer::from_value(value.clone()) {
ab.get::<_, Function>("transfer")?.call((This(ab),))?
} else {
value.clone()
};
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
Ok(())
}
#[inline(always)]
#[cold]
fn append_circular(
tape: &mut Vec<TapeItem<'_>>,
visited: &(usize, usize),
object_key: &mut Option<String>,
parent: usize,
array_index: Option<usize>,
) {
let value = match &tape[visited.1].value {
TapeValue::Array(array) => array.clone().into_value(),
TapeValue::Object(object) => object.clone().into_value(),
TapeValue::Value(value) => value.clone(),
TapeValue::Collection(value, _) => value.clone().unwrap(),
};
let object_key = object_key.take();
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(value),
});
}
#[inline(always)]
#[cold]
fn append_ctor_value<'js>(
tape: &mut Vec<TapeItem<'js>>,
object: &Object<'js>,
ctor: &Constructor<'js>,
parent: usize,
object_key: Option<String>,
array_index: Option<usize>,
) -> Result<()> {
let clone: Value = ctor.construct((object.clone(),))?;
tape.push(TapeItem {
parent,
object_key,
array_index,
value: TapeValue::Value(clone),
});
Ok(())
}
#[cfg(test)]
mod tests {
use llrt_test::test_sync_with;
use rquickjs::{function::Opt, Object, Value};
use crate::primordials::{BasePrimordials, Primordial};
use super::structured_clone;
#[tokio::test]
async fn clone() {
test_sync_with(|ctx| {
BasePrimordials::init(&ctx)?;
let value: Object = ctx.eval(
r#"
const a = {
"foo":{
"bar":"baz"
},
"foo1":{
"bar1":"baz1",
"bar11":"baz11"
}
};
a
"#,
)?;
let cloned = structured_clone(&ctx, value.clone().into_value(), Opt(None))?
.into_object()
.unwrap();
let json = ctx
.json_stringify(value.clone())?
.unwrap()
.to_string()?
.to_string();
let clone_json = ctx
.json_stringify(cloned.clone())?
.unwrap()
.to_string()?
.to_string();
assert_eq!(json, clone_json);
assert_ne!(
value.get::<_, Value>("foo")?,
cloned.get::<_, Value>("foo")?
);
Ok(())
})
.await
}
#[tokio::test]
async fn clone_circular() {
test_sync_with(|ctx| {
BasePrimordials::init(&ctx)?;
let _value: Object = ctx.eval(
r#"
const originalObject = { foo: { bar: "baz",arr: [1,2,3] } };
originalObject.foo.circularRef = originalObject;
originalObject.foo.circularRef2 = originalObject;
originalObject.foo.circularRef3 = originalObject.foo;
originalObject.ref2 = originalObject;
"#,
)?;
Ok(())
})
.await
}
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{Ctx, Result};
pub trait CtxExt {
fn get_script_or_module_name(&self) -> Result<String>;
}
impl CtxExt for Ctx<'_> {
fn get_script_or_module_name(&self) -> Result<String> {
if let Some(name) = self.script_or_module_name(0) {
name.to_string()
} else {
Ok(String::from("."))
}
}
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{CatchResultExt, CaughtError, Ctx, Error, IntoJs, Result, Value};
pub trait ErrorExtensions<'js> {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>>;
}
impl<'js> ErrorExtensions<'js> for Error {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
Err::<(), _>(self).catch(ctx).unwrap_err().into_value(ctx)
}
}
impl<'js> ErrorExtensions<'js> for CaughtError<'js> {
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
Ok(match self {
CaughtError::Error(err) => err.to_string().into_js(ctx)?,
CaughtError::Exception(ex) => ex.into_value(),
CaughtError::Value(val) => val,
})
}
}
@@ -0,0 +1,3 @@
pub const ERROR_MSG_NOT_ARRAY_BUFFER: &str = "Not an ArrayBuffer";
pub const ERROR_MSG_ARRAY_BUFFER_DETACHED: &str = "ArrayBuffer is detached";
pub const ERROR_MSG_BROADCAST_LAGGED: &str = "Lagged too much behind";
+103
View File
@@ -0,0 +1,103 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::{fs::Metadata, io, path::PathBuf};
use tokio::fs::{self};
pub struct DirectoryWalker<T>
where
T: Fn(&str) -> bool,
{
stack: Vec<(PathBuf, Option<Metadata>)>,
filter: T,
recursive: bool,
eat_root: bool,
}
impl<T> DirectoryWalker<T>
where
T: Fn(&str) -> bool,
{
pub fn new(root: PathBuf, filter: T) -> Self {
Self {
stack: vec![(root, None)],
filter,
recursive: false,
eat_root: true,
}
}
pub fn set_recursive(&mut self, recursive: bool) {
self.recursive = recursive;
}
pub async fn walk(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
if self.eat_root {
self.eat_root = false;
let (dir, _) = self.stack.pop().unwrap();
self.append_stack(&dir).await?;
}
if let Some((entry, metadata)) = self.stack.pop() {
let metadata = metadata.unwrap();
if self.recursive && metadata.is_dir() {
self.append_stack(&entry).await?;
}
Ok(Some((entry, metadata)))
} else {
Ok(None)
}
}
pub fn walk_sync(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
if self.eat_root {
self.eat_root = false;
let (dir, _) = self.stack.pop().unwrap();
self.append_stack_sync(&dir)?;
}
if let Some((entry, metadata)) = self.stack.pop() {
let metadata = metadata.unwrap();
if self.recursive && metadata.is_dir() {
self.append_stack_sync(&entry)?;
}
Ok(Some((entry, metadata)))
} else {
Ok(None)
}
}
async fn append_stack(&mut self, dir: &PathBuf) -> io::Result<()> {
let mut stream = fs::read_dir(dir).await?;
while let Some(entry) = stream.next_entry().await? {
let name = entry.file_name();
let name = name.to_string_lossy();
if !(self.filter)(name.as_ref()) {
continue;
}
let entry_path = entry.path();
let metadata = fs::symlink_metadata(&entry_path).await?;
self.stack.push((entry_path, Some(metadata)));
}
Ok(())
}
fn append_stack_sync(&mut self, dir: &PathBuf) -> io::Result<()> {
let dir = std::fs::read_dir(dir)?;
for entry in dir.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if !(self.filter)(name.as_ref()) {
continue;
}
let entry_path = entry.path();
let metadata = entry_path.symlink_metadata()?;
self.stack.push((entry_path, Some(metadata)))
}
Ok(())
}
}
+8
View File
@@ -0,0 +1,8 @@
use std::hash::{DefaultHasher, Hash, Hasher};
#[inline]
pub fn default_hash<T: Hash + ?Sized>(v: &T) -> usize {
let mut state = DefaultHasher::default();
v.hash(&mut state);
state.finish() as usize
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
macro_rules! define_extension {
($base:ident, $file:ident, $ext:expr) => {
#[allow(dead_code)]
pub const $base: &str = $ext;
#[allow(dead_code)]
pub const $file: &str = concat!(".", $ext);
};
}
define_extension!(BYTECODE_EXT, BYTECODE_FILE_EXT, "lrt");
macro_rules! define_supported_extensions {
// Accepts a list of supported extensions and a single additional constant extension
($constant_ext:ident, $($ext:literal),*) => {
// Define the array of extensions as a constant
pub const SUPPORTED_EXTENSIONS: &[&str] = &[$($ext),*, $constant_ext];
pub const JS_EXTENSIONS: &[&str] = &[$($ext),*];
// Define the function `is_supported_ext` using a match statement
pub fn is_supported_ext(ext: &str) -> bool {
matches!(ext, $($ext)|* | $constant_ext)
}
};
}
define_supported_extensions!(BYTECODE_FILE_EXT, ".js", ".mjs", ".cjs");
+30
View File
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::sync::Notify;
#[derive(Default)]
pub struct Latch {
count: AtomicUsize,
notify: Notify,
}
impl Latch {
pub fn increment(&self) {
self.count.fetch_add(1, Ordering::Relaxed);
}
pub fn decrement(&self) {
let previous = self.count.fetch_sub(1, Ordering::Relaxed);
if previous == 1 {
self.notify.notify_waiters();
}
}
pub async fn wait(&self) {
if self.count.load(Ordering::Relaxed) > 0 {
self.notify.notified().await;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub mod any_of;
#[cfg(feature = "bytearray-buffer")]
pub mod bytearray_buffer;
pub mod bytes;
pub mod class;
pub mod clone;
pub mod ctx;
pub mod error;
pub mod error_messages;
#[cfg(feature = "fs")]
pub mod fs;
pub mod hash;
pub mod io;
pub mod latch;
pub mod macros;
pub mod mc_oneshot;
pub mod module;
pub mod object;
pub mod option;
pub mod primordials;
pub mod provider;
pub mod result;
pub mod reuse_list;
pub mod string;
pub mod sysinfo;
pub mod time;
#[cfg(any(unix, windows))]
pub mod signals;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+55
View File
@@ -0,0 +1,55 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#[macro_export]
macro_rules! count_members {
() => (0);
($head:tt $(,$tail:tt)*) => (1 + count_members!($($tail),*));
}
#[macro_export]
macro_rules! iterable_enum {
($name:ident, $($variant:ident),*) => {
impl $name {
const VARIANTS: &'static [$name] = &[$($name::$variant,)*];
pub fn iter() -> std::slice::Iter<'static, $name> {
Self::VARIANTS.iter()
}
#[allow(dead_code)]
fn _ensure_all_variants(s: Self) {
match s {
$($name::$variant => {},)*
}
}
}
};
}
#[macro_export]
macro_rules! str_enum {
($name:ident, $($variant:ident => $str:expr),*) => {
impl $name {
pub fn as_str(&self) -> &'static str {
match self {
$($name::$variant => $str,)*
}
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl TryFrom<&str> for $name {
type Error = String;
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
match s {
$($str => Ok($name::$variant),)*
_ => Err(["'", s, "' not available"].concat())
}
}
}
};
}
@@ -0,0 +1,118 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
};
use rquickjs::{
class::{Trace, Tracer},
Value,
};
use std::ops::Deref;
use tokio::sync::Notify;
#[derive(Debug)]
pub struct Shared<T> {
is_sent: AtomicBool,
value: RwLock<Option<T>>,
notify: Notify,
}
#[derive(Clone, Debug)]
pub struct Sender<T: Clone>(Arc<Shared<T>>);
impl<T: Clone> Deref for Sender<T> {
type Target = Arc<Shared<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'js> Trace<'js> for Sender<Value<'js>> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
if let Ok(v) = self.value.read() {
if let Some(v) = v.as_ref() {
tracer.mark(v)
}
}
}
}
impl<T: Clone> Sender<T> {
pub fn send(&self, value: T) {
if !self.is_sent.load(Ordering::Relaxed) {
self.value.write().unwrap().replace(value);
self.is_sent.store(true, Ordering::Release);
self.notify.notify_waiters();
}
}
pub fn subscribe(&self) -> Receiver<T> {
Receiver(Arc::clone(&self.0))
}
}
#[derive(Clone, Debug)]
pub struct Receiver<T: Clone>(Arc<Shared<T>>);
impl<T: Clone> Deref for Receiver<T> {
type Target = Arc<Shared<T>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: Clone> Receiver<T> {
pub async fn recv(&self) -> T {
if !self.is_sent.load(Ordering::Acquire) {
self.notify.notified().await;
}
self.value.read().unwrap().clone().unwrap()
}
}
pub fn channel<T: Clone>() -> (Sender<T>, Receiver<T>) {
let shared = Arc::new(Shared {
is_sent: AtomicBool::new(false),
value: RwLock::new(None),
notify: Notify::new(),
});
(Sender(Arc::clone(&shared)), Receiver(shared))
}
#[cfg(test)]
mod tests {
use tokio::join;
#[tokio::test]
async fn test() {
let (tx, rx1) = super::channel::<bool>();
let rx2 = tx.subscribe();
let rx3 = tx.subscribe();
let a = tokio::spawn(async move {
let val = rx1.recv().await; //wait for value to become false
assert!(val)
});
let b = tokio::spawn(async move {
let val = rx2.recv().await; //wait for value to become false
assert!(val)
});
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
tx.send(true);
let val = rx3.recv().await;
assert!(val);
let (a, b) = join!(a, b);
a.unwrap();
b.unwrap();
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{
module::{Exports, ModuleDef},
Ctx, Object, Result, Value,
};
pub struct ModuleInfo<T: ModuleDef> {
pub name: &'static str,
pub module: T,
}
pub fn export_default<'js, F>(ctx: &Ctx<'js>, exports: &Exports<'js>, f: F) -> Result<()>
where
F: FnOnce(&Object<'js>) -> Result<()>,
{
let default = Object::new(ctx.clone())?;
f(&default)?;
for name in default.keys::<String>() {
let name = name?;
let value: Value = default.get(&name)?;
exports.export(name, value)?;
}
exports.export("default", default)?;
Ok(())
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::collections::BTreeMap;
use rquickjs::{
atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Coerced, Ctx, Error,
Exception, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value,
};
use crate::primordials::{BasePrimordials, Primordial};
pub trait ObjectExt<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V>;
fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str)
-> Result<Object<'js>>;
}
impl<'js> ObjectExt<'js> for Object<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
&self,
k: K,
) -> Result<Option<V>> {
self.get::<K, Option<V>>(k)
}
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V> {
let k = k.as_ref();
self.get::<&str, Option<V>>(k)?.ok_or_else(|| {
Exception::throw_type(
self.ctx(),
&[object_name, " '", k, "' property required"].concat(),
)
})
}
fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result<Object<'js>> {
Ok(self)
}
}
impl<'js> ObjectExt<'js> for Value<'js> {
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
if let Some(obj) = self.as_object() {
return obj.get_optional(k);
}
Ok(None)
}
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
&self,
k: K,
object_name: &'static str,
) -> Result<V> {
self.as_object()
.ok_or_else(|| not_a_object_error(self.ctx(), object_name))?
.get_required(k, object_name)
}
fn into_object_or_throw(
self,
ctx: &Ctx<'js>,
object_name: &'static str,
) -> Result<Object<'js>> {
self.into_object()
.ok_or_else(|| not_a_object_error(ctx, object_name))
}
}
pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error {
Exception::throw_type(ctx, &[object_name, " is not an object"].concat())
}
pub trait CreateSymbol<'js> {
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>>;
}
impl<'js> CreateSymbol<'js> for Symbol<'js> {
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>> {
BasePrimordials::get(ctx)?
.function_symbol_for
.call((description,))
}
}
pub struct Proxy<'js> {
target: Value<'js>,
options: Object<'js>,
}
impl<'js> IntoJs<'js> for Proxy<'js> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
BasePrimordials::get(ctx)?
.constructor_proxy
.construct::<_, Value>((self.target, self.options))
}
}
impl<'js> Proxy<'js> {
pub fn new(ctx: Ctx<'js>) -> Result<Self> {
let options = Object::new(ctx.clone())?;
Ok(Self {
target: Undefined.into_value(ctx),
options,
})
}
pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result<Self> {
let options = Object::new(ctx)?;
Ok(Self { target, options })
}
pub fn setter<T, P>(&self, setter: Func<T, P>) -> Result<()>
where
T: IntoJsFunc<'js, P> + 'js,
{
self.options.set(PredefinedAtom::Setter, setter)?;
Ok(())
}
pub fn getter<T, P>(&self, getter: Func<T, P>) -> Result<()>
where
T: IntoJsFunc<'js, P> + 'js,
{
self.options.set(PredefinedAtom::Getter, getter)?;
Ok(())
}
}
pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result<Array<'js>>
where
M: IntoIterator<Item = (K, V)>,
K: IntoJs<'js>,
V: IntoJs<'js>,
{
let array = Array::new(ctx.clone())?;
for (idx, (key, value)) in map.into_iter().enumerate() {
let entry = Array::new(ctx.clone())?;
entry.set(0, key)?;
entry.set(1, value)?;
array.set(idx, entry)?;
}
Ok(array)
}
pub fn array_to_btree_map<'js>(
ctx: &Ctx<'js>,
array: Array<'js>,
) -> Result<BTreeMap<String, Coerced<String>>> {
let value = object_from_entries(ctx, array)?;
let value = value.into_value();
BTreeMap::from_js(ctx, value)
}
pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
let obj = Object::new(ctx.clone())?;
for value in array.into_iter().flatten() {
if let Some(entry) = value.as_array() {
if let Ok(key) = entry.get::<Value>(0) {
if let Ok(value) = entry.get::<Value>(1) {
let _ = obj.set(key, value); //ignore result of failed
}
}
}
}
Ok(obj)
}
+101
View File
@@ -0,0 +1,101 @@
use rquickjs::{
class::{Trace, Tracer},
function::{FromParam, ParamRequirement, ParamsAccessor},
Ctx, FromJs, IntoJs, JsLifetime, Result, Type, Value,
};
/// Helper type for treating an undefined value as None, without treating null as None
#[derive(Clone)]
pub struct Undefined<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromJs<'js> for Undefined<T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
if value.type_of() == Type::Undefined {
Ok(Self(None))
} else {
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
}
}
}
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Undefined<T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self.0 {
None => Ok(Value::new_undefined(ctx.clone())),
Some(val) => val.into_js(ctx),
}
}
}
impl<T> Default for Undefined<T> {
fn default() -> Self {
Self(None)
}
}
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Undefined<T> {
type Changed<'to> = Undefined<T::Changed<'to>>;
}
impl<'js, T: Trace<'js>> Trace<'js> for Undefined<T> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.0.trace(tracer)
}
}
/// Helper type for converting an None into null instead of undefined.
/// Needed while rquickjs::function::Null has no IntoJs implementation
#[derive(Clone)]
pub struct Null<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromJs<'js> for Null<T> {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
if value.type_of() == Type::Null {
Ok(Self(None))
} else {
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
}
}
}
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Null<T> {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
match self.0 {
None => Ok(Value::new_null(ctx.clone())),
Some(val) => val.into_js(ctx),
}
}
}
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Null<T> {
type Changed<'to> = Null<T::Changed<'to>>;
}
impl<'js, T: Trace<'js>> Trace<'js> for Null<T> {
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.0.trace(tracer)
}
}
/// Helper type for accepting no value, or null, but considering undefined as a value
pub struct NullableOpt<T>(pub Option<T>);
impl<'js, T: FromJs<'js>> FromParam<'js> for NullableOpt<T> {
fn param_requirement() -> ParamRequirement {
ParamRequirement::optional()
}
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
if !params.is_empty() {
let arg = params.arg();
if arg.is_null() {
Ok(NullableOpt(None))
} else {
let ctx = params.ctx().clone();
Ok(NullableOpt(Some(T::from_js(&ctx, arg)?)))
}
} else {
Ok(NullableOpt(None))
}
}
}
@@ -0,0 +1,159 @@
use std::any::type_name;
use rquickjs::{
atom::PredefinedAtom, function::Constructor, runtime::UserDataGuard, Ctx, Function, JsLifetime,
Object, Result, Symbol,
};
use crate::{class::CUSTOM_INSPECT_SYMBOL_DESCRIPTION, result::ResultExt};
#[derive(JsLifetime)]
pub struct BasePrimordials<'js> {
// Constructors
pub constructor_map: Constructor<'js>,
pub constructor_set: Constructor<'js>,
pub constructor_date: Constructor<'js>,
pub constructor_error: Constructor<'js>,
pub constructor_type_error: Constructor<'js>,
pub constructor_range_error: Constructor<'js>,
pub constructor_regexp: Constructor<'js>,
pub constructor_uint8array: Constructor<'js>,
pub constructor_array_buffer: Constructor<'js>,
pub constructor_proxy: Constructor<'js>,
pub constructor_object: Constructor<'js>,
pub constructor_bool: Constructor<'js>,
pub constructor_number: Constructor<'js>,
pub constructor_string: Constructor<'js>,
// Prototypes
pub prototype_object: Object<'js>,
pub prototype_date: Object<'js>,
pub prototype_regexp: Object<'js>,
pub prototype_set: Object<'js>,
pub prototype_map: Object<'js>,
pub prototype_error: Object<'js>,
// Functions
pub function_array_from: Function<'js>,
pub function_array_buffer_is_view: Function<'js>,
pub function_get_own_property_descriptor: Function<'js>,
pub function_parse_int: Function<'js>,
pub function_parse_float: Function<'js>,
pub function_symbol_for: Function<'js>,
// Symbols
pub symbol_custom_inspect: Symbol<'js>,
}
pub trait Primordial<'js>
where
Self: Sized + JsLifetime<'js>,
{
fn get<'a>(ctx: &'a Ctx<'js>) -> Result<UserDataGuard<'a, Self>> {
let userdata = ctx.userdata::<Self>().or_throw_msg(
ctx,
&[
"Userdata of ",
type_name::<Self>(),
" not initialized. Call init(&ctx) on this type.",
]
.concat(),
)?;
Ok(userdata)
}
fn init<'a>(ctx: &'a Ctx<'js>) -> Result<()> {
if ctx.userdata::<Self>().is_none() {
let primoridals = Self::new(ctx)?;
let _ = ctx.store_userdata(primoridals);
}
Ok(())
}
fn new(ctx: &Ctx<'js>) -> Result<Self>;
}
impl<'js> Primordial<'js> for BasePrimordials<'js> {
fn new(ctx: &Ctx<'js>) -> Result<Self> {
let globals = ctx.globals();
let constructor_object: Constructor = globals.get(PredefinedAtom::Object)?;
let prototype_object: Object = constructor_object.get(PredefinedAtom::Prototype)?;
let constructor_proxy: Constructor = globals.get(PredefinedAtom::Proxy)?;
let function_get_own_property_descriptor: Function =
constructor_object.get(PredefinedAtom::GetOwnPropertyDescriptor)?;
let constructor_date: Constructor = globals.get(PredefinedAtom::Date)?;
let prototype_date: Object = constructor_date.get(PredefinedAtom::Prototype)?;
let constructor_map: Constructor = globals.get(PredefinedAtom::Map)?;
let prototype_map: Object = constructor_map.get(PredefinedAtom::Prototype)?;
let constructor_set: Constructor = globals.get(PredefinedAtom::Set)?;
let prototype_set: Object = constructor_set.get(PredefinedAtom::Prototype)?;
let constructor_regexp: Constructor = globals.get(PredefinedAtom::RegExp)?;
let prototype_regexp: Object = constructor_regexp.get(PredefinedAtom::Prototype)?;
let constructor_uint8array: Constructor = globals.get(PredefinedAtom::Uint8Array)?;
let constructor_arraybuffer: Constructor = globals.get(PredefinedAtom::ArrayBuffer)?;
let constructor_error: Constructor = globals.get(PredefinedAtom::Error)?;
let constructor_type_error: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
let constructor_range_error: Constructor = ctx.globals().get(PredefinedAtom::RangeError)?;
let prototype_error: Object = constructor_error.get(PredefinedAtom::Prototype)?;
let constructor_array: Object = globals.get(PredefinedAtom::Array)?;
let function_array_from: Function = constructor_array.get(PredefinedAtom::From)?;
let constructor_array_buffer: Object = globals.get(PredefinedAtom::ArrayBuffer)?;
let function_array_buffer_is_view: Function = constructor_array_buffer.get("isView")?;
let constructor_bool: Constructor = globals.get(PredefinedAtom::Boolean)?;
let constructor_number: Constructor = globals.get(PredefinedAtom::Number)?;
let function_parse_float: Function = constructor_number.get("parseFloat")?;
let function_parse_int: Function = constructor_number.get("parseInt")?;
let constructor_string: Constructor = globals.get(PredefinedAtom::String)?;
let constructor_symbol: Constructor = globals.get(PredefinedAtom::Symbol)?;
let function_symbol_for: Function = constructor_symbol.get(PredefinedAtom::For)?;
let symbol_custom_inspect: Symbol<'js> =
function_symbol_for.call((CUSTOM_INSPECT_SYMBOL_DESCRIPTION,))?;
Ok(Self {
constructor_map,
constructor_set,
constructor_date,
constructor_proxy,
constructor_error,
constructor_type_error,
constructor_range_error,
constructor_regexp,
constructor_uint8array,
constructor_array_buffer: constructor_arraybuffer,
constructor_object,
constructor_bool,
constructor_number,
constructor_string,
prototype_object,
prototype_date,
prototype_regexp,
prototype_set,
prototype_map,
prototype_error,
function_array_from,
function_array_buffer_is_view,
function_get_own_property_descriptor,
function_parse_float,
function_parse_int,
function_symbol_for,
symbol_custom_inspect,
})
}
}
@@ -0,0 +1,24 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#[derive(PartialEq)]
pub enum ProviderType {
None,
Resource(String), // Custom asynchronous resource
// Userland provider types
Immediate, // [Immediate] Processing by setImmediate()
Interval, // [Interval] Timer by setInterval()
MessagePort, // [MessagePort] Port for worker_threads
Microtask, // [Microtask] Processing by queueMicrotask()
TickObject, // [TickObject] Processing by process.nextTick()
Timeout, // [Timeout] Timer by setTimeout()
// Internal provider types
FsReqCallback, // [FSREQCALLBACK] Callback for file system operations
GetAddrInfoReqWrap, // [GETADDRINFOREQWRAP] When resolving DNS (dns.lookup(), etc.)
GetNameInfoReqWrap, // [GETNAMEINFOREQWRAP] DNS reverse lookup
PipeWrap, // [PIPEWRAP] Pipe connection
StatWatcher, // [STATWACHER] File monitoring such as fs.watch()
TcpWrap, // [TCPWRAP] TCP socket wrap (net.Socket, etc.)
TimerWrap, // [TIMERWRAP] Internal timer wrap (low level)
TlsWrap, // [TLSWRAP] TLS socket (HTTPS, etc.)
UdpWrap, // [UDPWRAP] UDP socket wrap (dgram module)
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#![allow(clippy::uninlined_format_args)]
use std::{fmt::Write, result::Result as StdResult};
use rquickjs::{Ctx, Exception, Result};
pub trait ResultExt<T> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T>;
fn or_throw(self, ctx: &Ctx) -> Result<T>;
}
pub trait OptionExt<T> {
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
where
F: FnOnce(T) -> StdResult<Option<U>, E>;
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
where
F: FnOnce() -> StdResult<T, E>;
}
impl<T, E: std::fmt::Display> ResultExt<T> for StdResult<T, E> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
message.push_str(msg);
message.push_str(". ");
write!(message, "{}", e).unwrap();
Exception::throw_message(ctx, &message)
})
}
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
if !message.is_empty() {
message.push_str(msg);
message.push_str(". ");
}
write!(message, "{}", e).unwrap();
Exception::throw_range(ctx, &message)
})
}
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.map_err(|e| {
let mut message = String::with_capacity(100);
if !msg.is_empty() {
message.push_str(msg);
message.push_str(". ");
}
write!(message, "{}", e).unwrap();
Exception::throw_type(ctx, &message)
})
}
fn or_throw(self, ctx: &Ctx) -> Result<T> {
self.map_err(|err| Exception::throw_message(ctx, &err.to_string()))
}
}
impl<T> ResultExt<T> for Option<T> {
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_message(ctx, msg))
}
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_range(ctx, msg))
}
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
self.ok_or_else(|| Exception::throw_type(ctx, msg))
}
fn or_throw(self, ctx: &Ctx) -> Result<T> {
self.ok_or_else(|| Exception::throw_message(ctx, "Value is not present"))
}
}
impl<T> OptionExt<T> for Option<T> {
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
where
F: FnOnce(T) -> StdResult<Option<U>, E>,
{
match self {
Some(v) => f(v),
None => Ok(None),
}
}
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
where
F: FnOnce() -> StdResult<T, E>,
{
match self {
Some(v) => Ok(v),
None => f(),
}
}
}
@@ -0,0 +1,337 @@
use std::fmt::Debug;
#[derive(Default, Clone)]
pub struct ReuseList<T> {
items: Vec<Option<T>>,
slots: Vec<usize>,
last_slot_idx: usize,
len: usize,
slot_size: usize,
}
impl<T: Debug> Debug for ReuseList<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReuseList")
.field("items", &self.items)
.field("slots", &self.slots)
.finish()
}
}
impl<T> ReuseList<T> {
pub fn new() -> Self {
Self::with_capacity(0)
}
//is empty
pub fn is_empty(&self) -> bool {
self.len == 0
}
//create a with capacity
pub fn with_capacity(capacity: usize) -> Self {
Self {
items: Vec::with_capacity(capacity),
slots: Vec::with_capacity(capacity >> 2),
last_slot_idx: 0,
len: 0,
slot_size: 0,
}
}
pub fn append(&mut self, item: T) -> usize {
if self.slot_size > 0 {
//reuse empty slot if valid
let slot = self.slots[self.last_slot_idx - 1];
if slot > 0 {
self.items[slot - 1] = Some(item);
self.slots[self.last_slot_idx - 1] = 0;
if self.last_slot_idx > 1 {
self.last_slot_idx -= 1;
}
self.len += 1;
return slot - 1;
}
}
//no valid empty slots, append to end
self.items.push(Some(item));
self.len += 1;
self.items.len() - 1
}
pub fn remove(&mut self, index: usize) -> Option<T> {
if index >= self.items.len() {
return None;
}
let item = self.items[index].take();
if item.is_some() {
if self.slot_size > 0 && self.slots[self.last_slot_idx - 1] == 0 {
self.slots[self.last_slot_idx - 1] = index + 1;
} else {
self.slots.push(index + 1);
self.last_slot_idx += 1;
self.slot_size += 1;
}
self.len -= 1;
}
item
}
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.items.len() {
None
} else {
self.items[index].as_ref()
}
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index >= self.items.len() {
None
} else {
self.items[index].as_mut()
}
}
pub fn capacity(&self) -> usize {
self.items.capacity()
}
pub fn len(&self) -> usize {
self.len
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.items.iter().filter_map(|x| x.as_ref())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.items.iter_mut().filter_map(|x| x.as_mut())
}
//implement clear
pub fn clear(&mut self) {
self.items.clear();
self.slots.clear();
self.last_slot_idx = 0;
self.len = 0;
self.slot_size = 0;
}
pub fn optimize(&mut self) {
let mut new_items = Vec::with_capacity(self.len);
for item in self.items.iter_mut() {
let a = item.take();
if a.is_some() {
new_items.push(a);
}
}
self.items = new_items;
self.slots.clear();
self.last_slot_idx = 0;
self.slot_size = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let list: ReuseList<i32> = ReuseList::new();
assert_eq!(list.len(), 0);
assert_eq!(list.capacity(), 0);
assert_eq!(list.items.len(), 0);
assert_eq!(list.slots.len(), 0);
}
#[test]
fn test_with_capacity() {
let list: ReuseList<i32> = ReuseList::with_capacity(10);
assert_eq!(list.len(), 0);
assert_eq!(list.capacity(), 10);
assert_eq!(list.items.len(), 0);
assert_eq!(list.slots.len(), 0);
}
#[test]
fn test_append() {
let mut list = ReuseList::new();
assert_eq!(list.append(1), 0);
assert_eq!(list.append(2), 1);
assert_eq!(list.append(3), 2);
assert_eq!(list.len(), 3);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 2, 3]);
assert_eq!(list.items, vec![Some(1), Some(2), Some(3)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_remove() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
assert_eq!(list.remove(1), Some(2));
assert_eq!(list.len(), 2);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
assert_eq!(list.remove(5), None);
}
#[test]
fn test_reuse_slots() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1); // Remove 2
assert_eq!(list.append(4), 1); // Should reuse index 1
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 4, 3]);
assert_eq!(list.items, vec![Some(1), Some(4), Some(3)]);
assert_eq!(list.slots, vec![0]);
}
#[test]
fn test_get() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
assert_eq!(list.get(0), Some(&1));
assert_eq!(list.get(1), Some(&2));
assert_eq!(list.get(2), None);
assert_eq!(list.items, vec![Some(1), Some(2)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_get_mut() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
if let Some(value) = list.get_mut(0) {
*value = 10;
}
assert_eq!(list.get(0), Some(&10));
assert_eq!(list.items, vec![Some(10), Some(2)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_iter() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
}
#[test]
fn test_iter_mut() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
for item in list.iter_mut() {
*item *= 2;
}
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![2, 4, 6]);
assert_eq!(list.items, vec![Some(2), Some(4), Some(6)]);
assert_eq!(list.slots, vec![]);
}
#[test]
fn test_multiple_removes() {
let mut list = ReuseList::new();
for i in 0..5 {
list.append(i);
}
list.remove(1);
list.remove(3);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![0, 2, 4]);
assert_eq!(list.items, vec![Some(0), None, Some(2), None, Some(4)]);
assert_eq!(list.slots, vec![2, 4]);
// Test reuse of both slots
list.append(10);
list.append(11);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![0, 11, 2, 10, 4]);
assert_eq!(
list.items,
vec![Some(0), Some(11), Some(2), Some(10), Some(4)]
);
assert_eq!(list.slots, vec![0, 0]);
list.remove(0);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![11, 2, 10, 4]);
assert_eq!(list.items, vec![None, Some(11), Some(2), Some(10), Some(4)]);
assert_eq!(list.slots, vec![1, 0]);
list.append(20);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![20, 11, 2, 10, 4]);
assert_eq!(
list.items,
vec![Some(20), Some(11), Some(2), Some(10), Some(4)]
);
assert_eq!(list.slots, vec![0, 0]);
//remove all items
list.clear();
}
#[test]
fn test_optimize() {
let mut list = ReuseList::new();
list.append(1);
list.append(2);
list.append(3);
list.remove(1);
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
assert_eq!(list.slots, vec![2]);
list.optimize();
assert_eq!(list.items, vec![Some(1), Some(3)]);
assert_eq!(list.slots, vec![]);
assert_eq!(list.last_slot_idx, 0);
assert_eq!(list.slot_size, 0);
let items: Vec<i32> = list.iter().cloned().collect();
assert_eq!(items, vec![1, 3]);
}
}
+149
View File
@@ -0,0 +1,149 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use rquickjs::{prelude::Opt, Ctx, Exception, Result, Value};
use crate::result::ResultExt;
use std::io;
#[cfg(unix)]
macro_rules! generate_signal_from_str_fn {
($($signal:path),*) => {
pub fn signal_from_str(signal: &str) -> Option<i32> {
let signal = ["libc::", signal].concat();
match signal.as_str() {
$(stringify!($signal) => Some($signal),)*
_ => None,
}
}
pub fn signal_str_from_i32(signal: i32) -> Option<&'static str> {
$(if signal == $signal {
return Some(&stringify!($signal)[6..]);
})*
None
}
};
}
#[cfg(unix)]
generate_signal_from_str_fn!(
libc::SIGHUP,
libc::SIGINT,
libc::SIGQUIT,
libc::SIGILL,
libc::SIGABRT,
libc::SIGFPE,
libc::SIGKILL,
libc::SIGSEGV,
libc::SIGPIPE,
libc::SIGALRM,
libc::SIGTERM
);
#[cfg(not(unix))]
static WINDOWS_SIGTERM: i32 = -1;
pub fn parse_signal(signal: Option<Value<'_>>) -> Result<i32> {
let Some(val) = signal else {
#[cfg(unix)]
return Ok(libc::SIGTERM);
#[cfg(not(unix))]
return Ok(WINDOWS_SIGTERM);
};
if let Some(num) = val.as_number() {
let sig = num as i32;
#[cfg(unix)]
return Ok(sig);
// On Windows: 0 checks existence, anything else kills
#[cfg(not(unix))]
return Ok(if sig == 0 { 0 } else { WINDOWS_SIGTERM });
}
if let Some(str_val) = val.as_string() {
let s = str_val.to_string()?;
#[cfg(unix)]
let mapped_sig = signal_from_str(&s);
#[cfg(not(unix))]
let mapped_sig = match s.as_str() {
"SIGINT" | "SIGTERM" | "SIGKILL" | "SIGQUIT" | "SIGHUP" | "SIGUSR1" => {
Some(WINDOWS_SIGTERM)
},
_ => None,
};
return match mapped_sig {
Some(sig) => Ok(sig),
None => Err(Exception::throw_type(
val.ctx(),
&format!("Unknown signal: {}", s),
)),
};
}
Err(Exception::throw_type(val.ctx(), "Invalid signal type"))
}
#[cfg(unix)]
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
// libc::kill returns 0 on success, -1 on error
// SAFETY: kill is a safe system call as long as the signal value is valid, which is ensured by parse_signal
if unsafe { libc::kill(pid as i32, signal) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
#[cfg(windows)]
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
// SAFETY: OpenProcess is safe to call with valid parameters, and PROCESS_TERMINATE is a valid access right
let handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) };
if handle == std::ptr::null_mut() {
return Err(io::Error::last_os_error());
}
let result = if signal == 0 {
Ok(())
} else {
// SAFETY: TerminateProcess is safe to call with a valid process handle obtained from OpenProcess
if unsafe { TerminateProcess(handle, 1) } != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
};
// SAFETY: CloseHandle is safe to call with a valid handle obtained from OpenProcess
unsafe { CloseHandle(handle) };
result
}
pub fn kill(ctx: &Ctx<'_>, pid: u32, signal: Opt<Value<'_>>) -> Result<bool> {
let signal = parse_signal(signal.0)?;
kill_process_raw(pid, signal)
.map(|_| true)
.or_else(|e| {
// Handle "Process Not Found" / "Existence Check" logic
// If signal is 0 (check existence) and we hit a specific error, return Ok(false).
#[cfg(unix)]
let is_not_found = e.raw_os_error() == Some(libc::ESRCH); // Error 3
#[cfg(windows)]
let is_not_found = true; // On Windows, any OpenProcess failure during check implies "not found" (or not accessible)
if signal == 0 && is_not_found {
Ok(false)
} else {
Err(e)
}
})
.or_throw(ctx)
}
+17
View File
@@ -0,0 +1,17 @@
use rquickjs::{Coerced, Result, Value};
#[inline]
pub fn get_string(value: &Value<'_>) -> Result<Option<String>> {
if let Some(val) = value.as_string() {
let string = val.to_string()?;
return Ok(Some(string));
}
Ok(None)
}
pub fn get_coerced_string(value: &Value<'_>) -> Option<String> {
if let Ok(val) = value.get::<Coerced<String>>() {
return Some(val.0);
};
None
}
+13
View File
@@ -0,0 +1,13 @@
#[cfg(target_os = "macos")]
pub const PLATFORM: &str = "darwin";
#[cfg(target_os = "windows")]
pub const PLATFORM: &str = "win32";
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
pub const PLATFORM: &str = std::env::consts::OS;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
pub const ARCH: &str = "x64";
#[cfg(target_arch = "aarch64")]
pub const ARCH: &str = "arm64";
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
pub const ARCH: &str = std::env::consts::ARCH;
+47
View File
@@ -0,0 +1,47 @@
use std::{
sync::atomic::{AtomicU64, Ordering},
time::SystemTime,
};
static TIME_ORIGIN: AtomicU64 = AtomicU64::new(0);
/// Get the current time in nanoseconds.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn now_nanos() -> u64 {
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
/// Get the current time in millis.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn now_millis() -> i64 {
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64
}
/// Get the origin time in nanoseconds.
///
/// # Safety
/// - Good until the year 2554
/// - Always use a checked substraction since this can return 0
pub fn origin_nanos() -> u64 {
TIME_ORIGIN.load(Ordering::Relaxed)
}
// For accuracy reasons, this function should be executed when the vm is initialized
pub fn init() {
if TIME_ORIGIN.load(Ordering::Relaxed) == 0 {
let time_origin = now_nanos();
TIME_ORIGIN.store(time_origin, Ordering::Relaxed)
}
}