From abf9a6b352d5ea17d3ffe6eb1ffab6176ef1b58c Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 15:58:51 +0200 Subject: [PATCH 01/20] feat: init serde crate --- Cargo.toml | 1 + serde/Cargo.toml | 9 +++++++++ serde/src/lib.rs | 1 + serde/src/ser.rs | 0 4 files changed, 11 insertions(+) create mode 100644 serde/Cargo.toml create mode 100644 serde/src/lib.rs create mode 100644 serde/src/ser.rs diff --git a/Cargo.toml b/Cargo.toml index 5ba2515..cefced3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,6 @@ once_cell = "1.2.0" [workspace] members = [ "libquickjs-sys", + "serde" ] diff --git a/serde/Cargo.toml b/serde/Cargo.toml new file mode 100644 index 0000000..1ef196c --- /dev/null +++ b/serde/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "quick-js-serde" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libquickjs-sys = { path = "../libquickjs-sys" } diff --git a/serde/src/lib.rs b/serde/src/lib.rs new file mode 100644 index 0000000..bcb25b6 --- /dev/null +++ b/serde/src/lib.rs @@ -0,0 +1 @@ +mod ser; diff --git a/serde/src/ser.rs b/serde/src/ser.rs new file mode 100644 index 0000000..e69de29 From d46127de4aa3652c49540bbb3f0734334b90ae88 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 16:00:41 +0200 Subject: [PATCH 02/20] feat: all init --- serde/Cargo.toml | 2 + serde/src/ser.rs | 192 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/serde/Cargo.toml b/serde/Cargo.toml index 1ef196c..c3d2670 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] libquickjs-sys = { path = "../libquickjs-sys" } + +serde = "1" diff --git a/serde/src/ser.rs b/serde/src/ser.rs index e69de29..7aa8f1d 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser.rs @@ -0,0 +1,192 @@ +use libquickjs_sys::JSContext; +use serde::Serialize; + +pub struct Serializer<'a> { + context: &'a JSContext, +} + +impl<'a> Serializer<'a> { + pub fn new(context: &'a JSContext) -> Self { + Self { context } + } +} + +impl<'a> serde::Serializer for Serializer<'a> { + type Error = (); + type Ok = (); + type SerializeMap = (); + type SerializeSeq = (); + type SerializeStruct = (); + type SerializeStructVariant = (); + type SerializeTuple = (); + type SerializeTupleStruct = (); + type SerializeTupleVariant = (); + + fn serialize_bool(self, v: bool) -> Result { + todo!() + } + + fn serialize_i8(self, v: i8) -> Result { + todo!() + } + + fn serialize_i16(self, v: i16) -> Result { + todo!() + } + + fn serialize_i32(self, v: i32) -> Result { + todo!() + } + + fn serialize_i64(self, v: i64) -> Result { + todo!() + } + + fn serialize_i128(self, v: i128) -> Result { + todo!() + } + + fn serialize_u8(self, v: u8) -> Result { + todo!() + } + + fn serialize_u16(self, v: u16) -> Result { + todo!() + } + + fn serialize_u32(self, v: u32) -> Result { + todo!() + } + + fn serialize_u64(self, v: u64) -> Result { + todo!() + } + + fn serialize_u128(self, v: u128) -> Result { + todo!() + } + + fn serialize_f32(self, v: f32) -> Result { + todo!() + } + + fn serialize_f64(self, v: f64) -> Result { + todo!() + } + + fn serialize_char(self, v: char) -> Result { + todo!() + } + + fn serialize_str(self, v: &str) -> Result { + todo!() + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + todo!() + } + + fn serialize_none(self) -> Result { + todo!() + } + + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + todo!() + } + + fn serialize_unit(self) -> Result { + todo!() + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + todo!() + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + todo!() + } + + fn serialize_newtype_struct( + self, + name: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + todo!() + } + + fn serialize_newtype_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + todo!() + } + + fn serialize_seq(self, len: Option) -> Result { + todo!() + } + + fn serialize_tuple(self, len: usize) -> Result { + todo!() + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + todo!() + } + + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + todo!() + } + + fn serialize_map(self, len: Option) -> Result { + todo!() + } + + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + todo!() + } + + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + todo!() + } + + fn is_human_readable(&self) -> bool { + true + } +} From cee7cd5c9c17a72f3a71b83f07857045b372f0ef Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 16:45:56 +0200 Subject: [PATCH 03/20] feat: start for serializer --- libquickjs-sys/src/static-functions.rs | 54 ++++++++++++--- serde/Cargo.toml | 1 + serde/src/errors.rs | 92 +++++++++++++++++++++++++ serde/src/lib.rs | 1 + serde/src/ser.rs | 95 +++++++++++++++++--------- 5 files changed, 199 insertions(+), 44 deletions(-) create mode 100644 serde/src/errors.rs diff --git a/libquickjs-sys/src/static-functions.rs b/libquickjs-sys/src/static-functions.rs index 751f4be..66c5f85 100644 --- a/libquickjs-sys/src/static-functions.rs +++ b/libquickjs-sys/src/static-functions.rs @@ -1,4 +1,3 @@ - extern "C" { fn JS_ValueGetTag_real(v: JSValue) -> i32; fn JS_DupValue_real(ctx: *mut JSContext, v: JSValue); @@ -24,9 +23,26 @@ extern "C" { fn JS_IsSymbol_real(v: JSValue) -> bool; fn JS_IsObject_real(v: JSValue) -> bool; fn JS_ToUint32_real(ctx: *mut JSContext, pres: u32, val: JSValue) -> u32; - fn JS_SetProperty_real(ctx: *mut JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue) -> ::std::os::raw::c_int; - fn JS_NewCFunction_real(ctx: *mut JSContext, func: *mut JSCFunction, name: *const ::std::os::raw::c_char,length: ::std::os::raw::c_int) -> JSValue; - fn JS_NewCFunctionMagic_real(ctx: *mut JSContext, func: *mut JSCFunctionMagic, name: *const ::std::os::raw::c_char, length: ::std::os::raw::c_int, cproto: JSCFunctionEnum, magic: ::std::os::raw::c_int) -> JSValue; + fn JS_SetProperty_real( + ctx: *mut JSContext, + this_obj: JSValue, + prop: JSAtom, + val: JSValue, + ) -> ::std::os::raw::c_int; + fn JS_NewCFunction_real( + ctx: *mut JSContext, + func: *mut JSCFunction, + name: *const ::std::os::raw::c_char, + length: ::std::os::raw::c_int, + ) -> JSValue; + fn JS_NewCFunctionMagic_real( + ctx: *mut JSContext, + func: *mut JSCFunctionMagic, + name: *const ::std::os::raw::c_char, + length: ::std::os::raw::c_int, + cproto: JSCFunctionEnum, + magic: ::std::os::raw::c_int, + ) -> JSValue; } pub unsafe fn JS_ValueGetTag(v: JSValue) -> i32 { @@ -63,7 +79,8 @@ pub unsafe fn JS_NewInt32(ctx: *mut JSContext, v: i32) -> JSValue { JS_NewInt32_real(ctx, v) } -/// create a new f64 value, please note that if the passed f64 fits in a i32 this will return a value with flag 0 (i32) +/// create a new f64 value, please note that if the passed f64 fits in a i32 +/// this will return a value with flag 0 (i32) pub unsafe fn JS_NewFloat64(ctx: *mut JSContext, v: f64) -> JSValue { JS_NewFloat64_real(ctx, v) } @@ -90,7 +107,7 @@ pub unsafe fn JS_IsNumber(v: JSValue) -> bool { /// check if a JSValue is a BigInt pub unsafe fn JS_IsBigInt(ctx: *mut JSContext, v: JSValue) -> bool { - JS_IsBigInt_real(ctx,v) + JS_IsBigInt_real(ctx, v) } /// check if a JSValue is a BigFloat @@ -119,7 +136,7 @@ pub unsafe fn JS_IsUndefined(v: JSValue) -> bool { } /// check if a JSValue is an Exception -pub unsafe fn JS_IsException(v: JSValue) -> bool{ +pub unsafe fn JS_IsException(v: JSValue) -> bool { JS_IsException_real(v) } @@ -149,16 +166,33 @@ pub unsafe fn JS_ToUint32(ctx: *mut JSContext, pres: u32, val: JSValue) -> u32 { } /// set a property of an object identified by a JSAtom -pub unsafe fn JS_SetProperty(ctx: *mut JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue) -> ::std::os::raw::c_int { +pub unsafe fn JS_SetProperty( + ctx: *mut JSContext, + this_obj: JSValue, + prop: JSAtom, + val: JSValue, +) -> ::std::os::raw::c_int { JS_SetProperty_real(ctx, this_obj, prop, val) } /// create a new Function based on a JSCFunction -pub unsafe fn JS_NewCFunction(ctx: *mut JSContext, func: *mut JSCFunction, name: *const ::std::os::raw::c_char,length: ::std::os::raw::c_int) -> JSValue { +pub unsafe fn JS_NewCFunction( + ctx: *mut JSContext, + func: *mut JSCFunction, + name: *const ::std::os::raw::c_char, + length: ::std::os::raw::c_int, +) -> JSValue { JS_NewCFunction_real(ctx, func, name, length) } /// create a new Function based on a JSCFunction -pub unsafe fn JS_NewCFunctionMagic(ctx: *mut JSContext, func: *mut JSCFunctionMagic, name: *const ::std::os::raw::c_char, length: ::std::os::raw::c_int, cproto: JSCFunctionEnum, magic: ::std::os::raw::c_int) -> JSValue { +pub unsafe fn JS_NewCFunctionMagic( + ctx: *mut JSContext, + func: *mut JSCFunctionMagic, + name: *const ::std::os::raw::c_char, + length: ::std::os::raw::c_int, + cproto: JSCFunctionEnum, + magic: ::std::os::raw::c_int, +) -> JSValue { JS_NewCFunctionMagic_real(ctx, func, name, length, cproto, magic) } diff --git a/serde/Cargo.toml b/serde/Cargo.toml index c3d2670..08d2fd1 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" libquickjs-sys = { path = "../libquickjs-sys" } serde = "1" +thiserror = "1" diff --git a/serde/src/errors.rs b/serde/src/errors.rs new file mode 100644 index 0000000..ddc4ef9 --- /dev/null +++ b/serde/src/errors.rs @@ -0,0 +1,92 @@ +use std::ffi::CStr; +use std::str::Utf8Error; + +use libquickjs_sys::{ + JSContext, JSValue, JS_FreeCString, JS_FreeValue, JS_GetException, JS_IsException, JS_IsNull, + JS_IsString, JS_ToCStringLen2, JS_ToString, +}; +use thiserror::Error; + +#[derive(Debug, Clone, Copy, Error)] +pub enum Internal { + #[error("Unexpected null pointer")] + UnexpectedNullPointer, + #[error("Unexpected null value")] + UnexpectedNullValue, + #[error("Expected string")] + ExpectedString, + #[error("Invalid UTF-8")] + InvalidUtf8(#[from] Utf8Error), +} + +unsafe fn get_string(context: &mut JSContext, value: JSValue) -> Result { + if !JS_IsString(value) { + return Err(Internal::ExpectedString); + } + + // convert to a rust string + let ptr = JS_ToCStringLen2(context, std::ptr::null_mut(), value, 0); + + if ptr.is_null() { + return Err(Internal::UnexpectedNullPointer); + } + + let c_str = CStr::from_ptr(ptr); + + let string = c_str.to_str()?.to_string(); + + // Free the C string + JS_FreeCString(context, ptr); + + Ok(string) +} + +unsafe fn exception_to_string( + context: &mut JSContext, + exception: JSValue, +) -> Result { + if JS_IsNull(exception) { + return Err(Internal::UnexpectedNullValue); + } + + let exception = if JS_IsString(exception) { + exception + } else { + JS_ToString(context, exception) + }; + + get_string(context, exception) +} + +#[derive(Debug, Clone, Error)] +pub enum SerializationError { + #[error("Out of memory")] + OutOfMemory, + #[error("Internal error: {0}")] + Internal(#[from] Internal), + #[error("Unknown error: {0}")] + Unknown(String), +} + +impl SerializationError { + pub fn from_value(context: &mut JSContext, value: JSValue) -> Result { + if unsafe { JS_IsException(value) } { + // we're for sure an error, we just don't know which one + // TODO: do we need to free here? + unsafe { JS_FreeValue(context, value) } + + // https://bellard.org/quickjs/quickjs.html#Exceptions 3.4.4 + let exception = unsafe { JS_GetException(context) }; + + let value = unsafe { exception_to_string(context, exception) }?; + + if value.contains("out of memory") { + return Err(Self::OutOfMemory); + } + + Err(Self::Unknown(value)) + } else { + Ok(value) + } + } +} diff --git a/serde/src/lib.rs b/serde/src/lib.rs index bcb25b6..1cac64c 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -1 +1,2 @@ +mod errors; mod ser; diff --git a/serde/src/ser.rs b/serde/src/ser.rs index 7aa8f1d..dd07343 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser.rs @@ -1,19 +1,22 @@ -use libquickjs_sys::JSContext; +use libquickjs_sys::{ + JSContext, JSValue, JS_IsException, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, + JS_NewInt32, +}; use serde::Serialize; pub struct Serializer<'a> { - context: &'a JSContext, + context: &'a mut JSContext, } impl<'a> Serializer<'a> { - pub fn new(context: &'a JSContext) -> Self { + pub fn new(context: &'a mut JSContext) -> Self { Self { context } } } impl<'a> serde::Serializer for Serializer<'a> { type Error = (); - type Ok = (); + type Ok = JSValue; type SerializeMap = (); type SerializeSeq = (); type SerializeStruct = (); @@ -22,67 +25,91 @@ impl<'a> serde::Serializer for Serializer<'a> { type SerializeTupleStruct = (); type SerializeTupleVariant = (); - fn serialize_bool(self, v: bool) -> Result { - todo!() - } + fn serialize_bool(self, value: bool) -> Result { + let value = unsafe { JS_NewBool(self.context, value) }; - fn serialize_i8(self, v: i8) -> Result { - todo!() + Ok(value) } - fn serialize_i16(self, v: i16) -> Result { - todo!() + fn serialize_i8(self, value: i8) -> Result { + self.serialize_i32(i32::from(value)) } - fn serialize_i32(self, v: i32) -> Result { - todo!() + fn serialize_i16(self, value: i16) -> Result { + self.serialize_i32(i32::from(value)) } - fn serialize_i64(self, v: i64) -> Result { - todo!() - } + fn serialize_i32(self, value: i32) -> Result { + let value = unsafe { JS_NewInt32(self.context, value) }; - fn serialize_i128(self, v: i128) -> Result { - todo!() + Ok(value) } - fn serialize_u8(self, v: u8) -> Result { - todo!() + fn serialize_i64(self, value: i64) -> Result { + // try to fit the value into a 32-bit integer, otherwise return a BigInt + if let Ok(value) = i32::try_from(value) { + return self.serialize_i32(value); + } + + let value = unsafe { JS_NewBigInt64(self.context, value) }; + + Ok(value) } - fn serialize_u16(self, v: u16) -> Result { - todo!() + // For now we don't support i128 and u128, as there are no methods to create + // BigInts for them. + // In theory we could create our own function to do so, but for now that's + // overkill. + + fn serialize_u8(self, value: u8) -> Result { + self.serialize_i32(i32::from(value)) } - fn serialize_u32(self, v: u32) -> Result { - todo!() + fn serialize_u16(self, value: u16) -> Result { + self.serialize_i32(i32::from(value)) } - fn serialize_u64(self, v: u64) -> Result { - todo!() + fn serialize_u32(self, value: u32) -> Result { + // we cannot use `JS_NewInt32` here, as there are values in u32 that cannot be + // represented in i32 (and would wrap around) + self.serialize_u64(u64::from(value)) } - fn serialize_u128(self, v: u128) -> Result { - todo!() + fn serialize_u64(self, value: u64) -> Result { + // try to fit the value into a 32-bit integer, otherwise return a BigInt + // we could also call `serialize_u64` instead, but that is largely redundant. + if let Ok(value) = i32::try_from(value) { + return self.serialize_i32(value); + } + + let value = unsafe { JS_NewBigUint64(self.context, value) }; + + Ok(value) } - fn serialize_f32(self, v: f32) -> Result { - todo!() + fn serialize_f32(self, value: f32) -> Result { + let value = unsafe { JS_NewFloat64(self.context, f64::from(value)) }; + + if JS_IsException(value) { + return Err(()); + } + + Ok(value) } - fn serialize_f64(self, v: f64) -> Result { + fn serialize_f64(self, value: f64) -> Result { todo!() } - fn serialize_char(self, v: char) -> Result { + fn serialize_char(self, value: char) -> Result { todo!() } - fn serialize_str(self, v: &str) -> Result { + fn serialize_str(self, value: &str) -> Result { todo!() } - fn serialize_bytes(self, v: &[u8]) -> Result { + fn serialize_bytes(self, value: &[u8]) -> Result { todo!() } From 3fd7f67eed45c06b3f6ac337f1eb604d264299d2 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 16:47:43 +0200 Subject: [PATCH 04/20] feat: add errors to serialization logic --- serde/src/errors.rs | 2 +- serde/src/ser.rs | 27 ++++++++++----------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index ddc4ef9..1073965 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -69,7 +69,7 @@ pub enum SerializationError { } impl SerializationError { - pub fn from_value(context: &mut JSContext, value: JSValue) -> Result { + pub fn try_from_value(context: &mut JSContext, value: JSValue) -> Result { if unsafe { JS_IsException(value) } { // we're for sure an error, we just don't know which one // TODO: do we need to free here? diff --git a/serde/src/ser.rs b/serde/src/ser.rs index dd07343..3c56391 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser.rs @@ -4,6 +4,8 @@ use libquickjs_sys::{ }; use serde::Serialize; +use crate::errors::SerializationError; + pub struct Serializer<'a> { context: &'a mut JSContext, } @@ -15,7 +17,7 @@ impl<'a> Serializer<'a> { } impl<'a> serde::Serializer for Serializer<'a> { - type Error = (); + type Error = SerializationError; type Ok = JSValue; type SerializeMap = (); type SerializeSeq = (); @@ -27,8 +29,7 @@ impl<'a> serde::Serializer for Serializer<'a> { fn serialize_bool(self, value: bool) -> Result { let value = unsafe { JS_NewBool(self.context, value) }; - - Ok(value) + SerializationError::try_from_value(self.context, value) } fn serialize_i8(self, value: i8) -> Result { @@ -41,8 +42,7 @@ impl<'a> serde::Serializer for Serializer<'a> { fn serialize_i32(self, value: i32) -> Result { let value = unsafe { JS_NewInt32(self.context, value) }; - - Ok(value) + SerializationError::try_from_value(self.context, value) } fn serialize_i64(self, value: i64) -> Result { @@ -52,8 +52,7 @@ impl<'a> serde::Serializer for Serializer<'a> { } let value = unsafe { JS_NewBigInt64(self.context, value) }; - - Ok(value) + SerializationError::try_from_value(self.context, value) } // For now we don't support i128 and u128, as there are no methods to create @@ -83,22 +82,16 @@ impl<'a> serde::Serializer for Serializer<'a> { } let value = unsafe { JS_NewBigUint64(self.context, value) }; - - Ok(value) + SerializationError::try_from_value(self.context, value) } fn serialize_f32(self, value: f32) -> Result { - let value = unsafe { JS_NewFloat64(self.context, f64::from(value)) }; - - if JS_IsException(value) { - return Err(()); - } - - Ok(value) + self.serialize_f64(f64::from(value)) } fn serialize_f64(self, value: f64) -> Result { - todo!() + let value = unsafe { JS_NewFloat64(self.context, value) }; + SerializationError::try_from_value(self.context, value) } fn serialize_char(self, value: char) -> Result { From 132b6d81a9bf0bfa295ca4237e1da065f3fcea44 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 16:56:21 +0200 Subject: [PATCH 05/20] feat: serialize_char & serialize_str --- serde/src/errors.rs | 4 +++- serde/src/ser.rs | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index 1073965..3b3eec4 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -7,7 +7,7 @@ use libquickjs_sys::{ }; use thiserror::Error; -#[derive(Debug, Clone, Copy, Error)] +#[derive(Debug, Clone, Error)] pub enum Internal { #[error("Unexpected null pointer")] UnexpectedNullPointer, @@ -17,6 +17,8 @@ pub enum Internal { ExpectedString, #[error("Invalid UTF-8")] InvalidUtf8(#[from] Utf8Error), + #[error("Nul byte found in string")] + NulError(#[from] std::ffi::NulError), } unsafe fn get_string(context: &mut JSContext, value: JSValue) -> Result { diff --git a/serde/src/ser.rs b/serde/src/ser.rs index 3c56391..bedfc65 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser.rs @@ -1,10 +1,12 @@ +use std::ffi::{CStr, CString}; + use libquickjs_sys::{ - JSContext, JSValue, JS_IsException, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, - JS_NewInt32, + size_t, JSContext, JSValue, JS_IsException, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, + JS_NewFloat64, JS_NewInt32, JS_NewStringLen, }; use serde::Serialize; -use crate::errors::SerializationError; +use crate::errors::{Internal, SerializationError}; pub struct Serializer<'a> { context: &'a mut JSContext, @@ -95,11 +97,17 @@ impl<'a> serde::Serializer for Serializer<'a> { } fn serialize_char(self, value: char) -> Result { - todo!() + let mut buffer = [0; 4]; + let string = value.encode_utf8(&mut buffer); + + self.serialize_str(string) } fn serialize_str(self, value: &str) -> Result { - todo!() + let c_str = CString::new(value).map_err(Internal::from)?; + + let value = unsafe { JS_NewStringLen(self.context, c_str.as_ptr(), value.len() as size_t) }; + SerializationError::try_from_value(self.context, value) } fn serialize_bytes(self, value: &[u8]) -> Result { From d603b3ddc7d14a2e07308522d341268bdfde7300 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:04:11 +0200 Subject: [PATCH 06/20] feat: more implementations --- serde/src/ser.rs | 49 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/serde/src/ser.rs b/serde/src/ser.rs index bedfc65..a80abc5 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser.rs @@ -1,8 +1,9 @@ use std::ffi::{CStr, CString}; use libquickjs_sys::{ - size_t, JSContext, JSValue, JS_IsException, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, - JS_NewFloat64, JS_NewInt32, JS_NewStringLen, + size_t, JSContext, JSValue, JS_AtomToValue, JS_DupAtom, JS_DupValue, JS_IsException, + JS_NewArrayBufferCopy, JS_NewAtom, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, + JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, }; use serde::Serialize; @@ -111,11 +112,17 @@ impl<'a> serde::Serializer for Serializer<'a> { } fn serialize_bytes(self, value: &[u8]) -> Result { - todo!() + // TODO: in theory we could also use `JS_NewArrayBuffer` here, but that would be + // _a lot_ more complicated. + let length = value.len(); + + let value = + unsafe { JS_NewArrayBufferCopy(self.context, value.as_ptr(), length as size_t) }; + SerializationError::try_from_value(self.context, value) } fn serialize_none(self) -> Result { - todo!() + self.serialize_unit() } fn serialize_some(self, value: &T) -> Result @@ -126,44 +133,56 @@ impl<'a> serde::Serializer for Serializer<'a> { } fn serialize_unit(self) -> Result { - todo!() + // Unit corresponds to `null` in JS + + // TODO: I have no idea if this is correct (AtomToValue) + let value = unsafe { JS_AtomToValue(self.context, JS_ATOM_NULL) }; + SerializationError::try_from_value(self.context, value) } - fn serialize_unit_struct(self, name: &'static str) -> Result { - todo!() + fn serialize_unit_struct(self, _: &'static str) -> Result { + self.serialize_unit() } fn serialize_unit_variant( self, - name: &'static str, - variant_index: u32, + _: &'static str, + _: u32, variant: &'static str, ) -> Result { - todo!() + // We follow the same approach as serde_json here, and serialize the variant as + // a string. + self.serialize_str(variant) } fn serialize_newtype_struct( self, - name: &'static str, + _: &'static str, value: &T, ) -> Result where T: Serialize, { - todo!() + T::serialize(value, self) } fn serialize_newtype_variant( self, - name: &'static str, - variant_index: u32, + _: &'static str, + _: u32, variant: &'static str, value: &T, ) -> Result where T: Serialize, { - todo!() + // We follow the same approach as serde_json here, and serialize the variant as, + // we serialize the value as an object with a single field. + // { `variant`: `value` } + + let mut serializer = self.serialize_map(Some(1))?; + serializer.serialize_entry(variant, value)?; + serializer.end() } fn serialize_seq(self, len: Option) -> Result { From 69eb7f508ac63980c5668f456ca0676ccabaa74a Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:07:13 +0200 Subject: [PATCH 07/20] feat: artificial context --- serde/src/errors.rs | 4 ++-- serde/src/ser/map.rs | 5 +++++ serde/src/{ser.rs => ser/mod.rs} | 20 ++++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 serde/src/ser/map.rs rename serde/src/{ser.rs => ser/mod.rs} (95%) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index 3b3eec4..2241864 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -44,7 +44,7 @@ unsafe fn get_string(context: &mut JSContext, value: JSValue) -> Result Result { if JS_IsNull(exception) { @@ -71,7 +71,7 @@ pub enum SerializationError { } impl SerializationError { - pub fn try_from_value(context: &mut JSContext, value: JSValue) -> Result { + pub fn try_from_value(context: *mut JSContext, value: JSValue) -> Result { if unsafe { JS_IsException(value) } { // we're for sure an error, we just don't know which one // TODO: do we need to free here? diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs new file mode 100644 index 0000000..e251baf --- /dev/null +++ b/serde/src/ser/map.rs @@ -0,0 +1,5 @@ +use libquickjs_sys::JSContext; + +pub struct SerializeMap<'a> { + context: &'a mut JSContext, +} diff --git a/serde/src/ser.rs b/serde/src/ser/mod.rs similarity index 95% rename from serde/src/ser.rs rename to serde/src/ser/mod.rs index a80abc5..ac727e3 100644 --- a/serde/src/ser.rs +++ b/serde/src/ser/mod.rs @@ -1,7 +1,9 @@ +mod map; + use std::ffi::{CStr, CString}; use libquickjs_sys::{ - size_t, JSContext, JSValue, JS_AtomToValue, JS_DupAtom, JS_DupValue, JS_IsException, + size_t, JSContext, JSValue, JS_AtomToValue, JS_DupValue, JS_FreeContext, JS_IsException, JS_NewArrayBufferCopy, JS_NewAtom, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, }; @@ -9,17 +11,23 @@ use serde::Serialize; use crate::errors::{Internal, SerializationError}; -pub struct Serializer<'a> { - context: &'a mut JSContext, +pub struct Serializer { + context: *mut JSContext, } -impl<'a> Serializer<'a> { - pub fn new(context: &'a mut JSContext) -> Self { +impl Serializer { + pub fn new(context: *mut JSContext) -> Self { Self { context } } } -impl<'a> serde::Serializer for Serializer<'a> { +impl Drop for Serializer { + fn drop(&mut self) { + unsafe { JS_FreeContext(self.context) }; + } +} + +impl serde::Serializer for Serializer { type Error = SerializationError; type Ok = JSValue; type SerializeMap = (); From 3b3a2d2389e583830b360946994a3d21de00d01c Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:11:07 +0200 Subject: [PATCH 08/20] feat: abstraction over contexzt --- serde/src/context.rs | 25 ++++++++++++++++ serde/src/lib.rs | 1 + serde/src/ser/mod.rs | 70 +++++++++++++++++++++++--------------------- 3 files changed, 62 insertions(+), 34 deletions(-) create mode 100644 serde/src/context.rs diff --git a/serde/src/context.rs b/serde/src/context.rs new file mode 100644 index 0000000..25f2c82 --- /dev/null +++ b/serde/src/context.rs @@ -0,0 +1,25 @@ +use libquickjs_sys::JSContext; + +pub struct Context { + context: *const JSContext, +} + +impl Context { + pub fn new(context: *mut JSContext) -> Self { + Self { context } + } + + pub(crate) fn as_ptr(&self) -> *const JSContext { + self.context + } + + pub(crate) fn as_mut_ptr(&mut self) -> *mut JSContext { + self.context as *mut _ + } +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { libquickjs_sys::JS_FreeContext(self.context as *mut _) }; + } +} diff --git a/serde/src/lib.rs b/serde/src/lib.rs index 1cac64c..fac37dc 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -1,2 +1,3 @@ +mod context; mod errors; mod ser; diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index ac727e3..75b237f 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -3,31 +3,26 @@ mod map; use std::ffi::{CStr, CString}; use libquickjs_sys::{ - size_t, JSContext, JSValue, JS_AtomToValue, JS_DupValue, JS_FreeContext, JS_IsException, + size_t, JSValue, JS_AtomToValue, JS_DupValue, JS_FreeContext, JS_IsException, JS_NewArrayBufferCopy, JS_NewAtom, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, }; use serde::Serialize; +use crate::context::Context; use crate::errors::{Internal, SerializationError}; -pub struct Serializer { - context: *mut JSContext, +pub struct Serializer<'a> { + context: &'a mut Context, } -impl Serializer { - pub fn new(context: *mut JSContext) -> Self { +impl<'a> Serializer<'a> { + pub fn new(context: &'a mut Context) -> Self { Self { context } } } -impl Drop for Serializer { - fn drop(&mut self) { - unsafe { JS_FreeContext(self.context) }; - } -} - -impl serde::Serializer for Serializer { +impl<'a> serde::Serializer for Serializer<'a> { type Error = SerializationError; type Ok = JSValue; type SerializeMap = (); @@ -38,9 +33,9 @@ impl serde::Serializer for Serializer { type SerializeTupleStruct = (); type SerializeTupleVariant = (); - fn serialize_bool(self, value: bool) -> Result { - let value = unsafe { JS_NewBool(self.context, value) }; - SerializationError::try_from_value(self.context, value) + fn serialize_bool(mut self, value: bool) -> Result { + let value = unsafe { JS_NewBool(self.context.as_mut_ptr(), value) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_i8(self, value: i8) -> Result { @@ -51,19 +46,19 @@ impl serde::Serializer for Serializer { self.serialize_i32(i32::from(value)) } - fn serialize_i32(self, value: i32) -> Result { - let value = unsafe { JS_NewInt32(self.context, value) }; - SerializationError::try_from_value(self.context, value) + fn serialize_i32(mut self, value: i32) -> Result { + let value = unsafe { JS_NewInt32(self.context.as_mut_ptr(), value) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } - fn serialize_i64(self, value: i64) -> Result { + fn serialize_i64(mut self, value: i64) -> Result { // try to fit the value into a 32-bit integer, otherwise return a BigInt if let Ok(value) = i32::try_from(value) { return self.serialize_i32(value); } - let value = unsafe { JS_NewBigInt64(self.context, value) }; - SerializationError::try_from_value(self.context, value) + let value = unsafe { JS_NewBigInt64(self.context.as_mut_ptr(), value) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } // For now we don't support i128 and u128, as there are no methods to create @@ -85,24 +80,24 @@ impl serde::Serializer for Serializer { self.serialize_u64(u64::from(value)) } - fn serialize_u64(self, value: u64) -> Result { + fn serialize_u64(mut self, value: u64) -> Result { // try to fit the value into a 32-bit integer, otherwise return a BigInt // we could also call `serialize_u64` instead, but that is largely redundant. if let Ok(value) = i32::try_from(value) { return self.serialize_i32(value); } - let value = unsafe { JS_NewBigUint64(self.context, value) }; - SerializationError::try_from_value(self.context, value) + let value = unsafe { JS_NewBigUint64(self.context.as_mut_ptr(), value) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_f32(self, value: f32) -> Result { self.serialize_f64(f64::from(value)) } - fn serialize_f64(self, value: f64) -> Result { - let value = unsafe { JS_NewFloat64(self.context, value) }; - SerializationError::try_from_value(self.context, value) + fn serialize_f64(mut self, value: f64) -> Result { + let value = unsafe { JS_NewFloat64(self.context.as_mut_ptr(), value) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_char(self, value: char) -> Result { @@ -115,8 +110,14 @@ impl serde::Serializer for Serializer { fn serialize_str(self, value: &str) -> Result { let c_str = CString::new(value).map_err(Internal::from)?; - let value = unsafe { JS_NewStringLen(self.context, c_str.as_ptr(), value.len() as size_t) }; - SerializationError::try_from_value(self.context, value) + let value = unsafe { + JS_NewStringLen( + self.context.as_mut_ptr(), + c_str.as_ptr(), + value.len() as size_t, + ) + }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_bytes(self, value: &[u8]) -> Result { @@ -124,9 +125,10 @@ impl serde::Serializer for Serializer { // _a lot_ more complicated. let length = value.len(); - let value = - unsafe { JS_NewArrayBufferCopy(self.context, value.as_ptr(), length as size_t) }; - SerializationError::try_from_value(self.context, value) + let value = unsafe { + JS_NewArrayBufferCopy(self.context.as_mut_ptr(), value.as_ptr(), length as size_t) + }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_none(self) -> Result { @@ -144,8 +146,8 @@ impl serde::Serializer for Serializer { // Unit corresponds to `null` in JS // TODO: I have no idea if this is correct (AtomToValue) - let value = unsafe { JS_AtomToValue(self.context, JS_ATOM_NULL) }; - SerializationError::try_from_value(self.context, value) + let value = unsafe { JS_AtomToValue(self.context.as_mut_ptr(), JS_ATOM_NULL) }; + SerializationError::try_from_value(self.context.as_mut_ptr(), value) } fn serialize_unit_struct(self, _: &'static str) -> Result { From 6c721938ffa69aa495a38d1098191013cacc481d Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:27:42 +0200 Subject: [PATCH 09/20] feat: map shennaningangs --- serde/src/errors.rs | 45 +++++++++++---- serde/src/ser/map.rs | 135 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 14 deletions(-) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index 2241864..6e3ded4 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -1,4 +1,6 @@ +use std::error::Error; use std::ffi::CStr; +use std::fmt::{Display, Formatter}; use std::str::Utf8Error; use libquickjs_sys::{ @@ -21,7 +23,7 @@ pub enum Internal { NulError(#[from] std::ffi::NulError), } -unsafe fn get_string(context: &mut JSContext, value: JSValue) -> Result { +unsafe fn get_string(context: *mut JSContext, value: JSValue) -> Result { if !JS_IsString(value) { return Err(Internal::ExpectedString); } @@ -43,7 +45,7 @@ unsafe fn get_string(context: &mut JSContext, value: JSValue) -> Result Result { @@ -68,27 +70,46 @@ pub enum SerializationError { Internal(#[from] Internal), #[error("Unknown error: {0}")] Unknown(String), + #[error("Expected call to `serialize_key` before `serialize_value`")] + MissingKey, + #[error("Expected either a string or a number as a key")] + InvalidKey, } impl SerializationError { + pub fn from_exception(context: *mut JSContext) -> Self { + // https://bellard.org/quickjs/quickjs.html#Exceptions 3.4.4 + let exception = unsafe { JS_GetException(context) }; + + let value = unsafe { exception_to_string(context, exception) }; + + match value { + Ok(value) => { + if value.contains("out of memory") { + Self::OutOfMemory + } else { + Self::Unknown(value) + } + } + Err(err) => err.into(), + } + } + pub fn try_from_value(context: *mut JSContext, value: JSValue) -> Result { if unsafe { JS_IsException(value) } { // we're for sure an error, we just don't know which one // TODO: do we need to free here? unsafe { JS_FreeValue(context, value) } - // https://bellard.org/quickjs/quickjs.html#Exceptions 3.4.4 - let exception = unsafe { JS_GetException(context) }; - - let value = unsafe { exception_to_string(context, exception) }?; - - if value.contains("out of memory") { - return Err(Self::OutOfMemory); - } - - Err(Self::Unknown(value)) + Err(Self::from_exception(context)) } else { Ok(value) } } } + +impl serde::ser::Error for SerializationError { + fn custom(msg: T) -> Self { + Self::Unknown(msg.to_string()) + } +} diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index e251baf..ac8bbb4 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -1,5 +1,136 @@ -use libquickjs_sys::JSContext; +use libquickjs_sys::{ + JSAtom, JSContext, JSValue, JS_FreeAtom, JS_FreeValue, JS_GetException, JS_IsNull, + JS_NewObject, JS_SetProperty, JS_ValueToAtom, JS_ATOM_NULL, +}; +use serde::Serialize; + +use crate::context::Context; +use crate::errors::{exception_to_string, SerializationError}; +use crate::ser::Serializer; pub struct SerializeMap<'a> { - context: &'a mut JSContext, + context: &'a mut Context, + object: JSValue, + + pending_key: Option, + buffer: Vec<(JSValue, JSValue)>, +} + +impl<'a> SerializeMap<'a> { + pub(crate) fn new(context: &'a mut Context) -> Self { + let object = unsafe { JS_NewObject(context.as_mut_ptr()) }; + + Self { context, object } + } + + fn key_to_atom(&mut self, key: JSValue) -> Result { + let atom = unsafe { JS_ValueToAtom(self.context.as_mut_ptr(), key) }; + + // free the key value + unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; + + if atom == JS_ATOM_NULL { + return Err(SerializationError::InvalidKey); + } + + Ok(atom) + } + + fn insert(&mut self, key: JSValue, value: JSValue) -> Result<(), SerializationError> { + let key = self.key_to_atom(key)?; + + let error = unsafe { JS_SetProperty(self.context.as_mut_ptr(), self.object, key, value) }; + + if error == -1 { + // exception occurred, time to roll back + let error = SerializationError::from_exception(self.context.as_mut_ptr()); + + // free the value and key + unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; + unsafe { JS_FreeAtom(self.context.as_mut_ptr(), key) }; + + return Err(error); + } + + // The value is freed by JS_SetProperty, the key is not freed + unsafe { JS_FreeAtom(self.context.as_mut_ptr(), key) }; + + Ok(()) + } +} + +impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let serializer = Serializer::new(self.context); + let value = key.serialize(serializer)?; + + self.pending_key = Some(value); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = self + .pending_key + .take() + .ok_or(SerializationError::MissingKey)?; + + let serializer = Serializer::new(self.context); + let value = value.serialize(serializer)?; + + self.buffer.push((key, value)); + Ok(()) + } + + fn serialize_entry( + &mut self, + key: &K, + value: &V, + ) -> Result<(), Self::Error> + where + K: Serialize, + V: Serialize, + { + // we don't need to buffer the key, we can serialize it directly + + let key = { + let serializer = Serializer::new(self.context); + key.serialize(serializer)? + }; + + let value = { + let serializer = Serializer::new(self.context); + value.serialize(serializer)? + }; + } + + fn end(self) -> Result { + todo!() + } +} + +impl Drop for SerializeMap<'_> { + fn drop(&mut self) { + // free the object + unsafe { JS_FreeValue(self.context.as_mut_ptr(), self.object) }; + + // free the pending key + if let Some(key) = self.pending_key.take() { + unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; + } + + // free the buffer + for (key, value) in self.buffer.drain(..) { + unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; + unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; + } + } } From 548cc3ac85e14043508073548a3698551222f025 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:29:25 +0200 Subject: [PATCH 10/20] feat: map end --- serde/src/errors.rs | 4 +++- serde/src/ser/map.rs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index 6e3ded4..d3d97b7 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -45,7 +45,7 @@ unsafe fn get_string(context: *mut JSContext, value: JSValue) -> Result Result { @@ -72,6 +72,8 @@ pub enum SerializationError { Unknown(String), #[error("Expected call to `serialize_key` before `serialize_value`")] MissingKey, + #[error("Expected call times of calls to `serialize_key` and `serialize_value` to be equal")] + MissingValue, #[error("Expected either a string or a number as a key")] InvalidKey, } diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index ac8bbb4..f9cad08 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -5,7 +5,7 @@ use libquickjs_sys::{ use serde::Serialize; use crate::context::Context; -use crate::errors::{exception_to_string, SerializationError}; +use crate::errors::SerializationError; use crate::ser::Serializer; pub struct SerializeMap<'a> { @@ -110,10 +110,21 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { let serializer = Serializer::new(self.context); value.serialize(serializer)? }; + + self.insert(key, value) } - fn end(self) -> Result { - todo!() + fn end(mut self) -> Result { + if self.pending_key.is_some() { + return Err(SerializationError::MissingValue); + } + + // insert the buffered values + for (key, value) in self.buffer { + self.insert(key, value)?; + } + + Ok(self.object) } } From 1dbb7182e24cb61e9288d303a8148c7d9399da7d Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:46:40 +0200 Subject: [PATCH 11/20] feat: serialize sequence --- serde/src/ser/map.rs | 41 ++++++++++++++++++++++---- serde/src/ser/mod.rs | 28 ++++++++++-------- serde/src/ser/seq.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 serde/src/ser/seq.rs diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index f9cad08..38ff59c 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -10,7 +10,7 @@ use crate::ser::Serializer; pub struct SerializeMap<'a> { context: &'a mut Context, - object: JSValue, + object: Option, pending_key: Option, buffer: Vec<(JSValue, JSValue)>, @@ -20,7 +20,10 @@ impl<'a> SerializeMap<'a> { pub(crate) fn new(context: &'a mut Context) -> Self { let object = unsafe { JS_NewObject(context.as_mut_ptr()) }; - Self { context, object } + Self { + context, + object: Some(object), + } } fn key_to_atom(&mut self, key: JSValue) -> Result { @@ -37,9 +40,11 @@ impl<'a> SerializeMap<'a> { } fn insert(&mut self, key: JSValue, value: JSValue) -> Result<(), SerializationError> { + let object = self.object.expect("object is not initialized"); + let key = self.key_to_atom(key)?; - let error = unsafe { JS_SetProperty(self.context.as_mut_ptr(), self.object, key, value) }; + let error = unsafe { JS_SetProperty(self.context.as_mut_ptr(), object, key, value) }; if error == -1 { // exception occurred, time to roll back @@ -114,24 +119,48 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { self.insert(key, value) } + // TODO: when does Drop get called? (I hope after this function) fn end(mut self) -> Result { if self.pending_key.is_some() { return Err(SerializationError::MissingValue); } // insert the buffered values - for (key, value) in self.buffer { + for (key, value) in self.buffer.drain(..) { self.insert(key, value)?; } - Ok(self.object) + let object = self.object.take().expect("object is not initialized"); + Ok(object) + } +} + +impl<'a> serde::ser::SerializeStruct for SerializeMap<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + ::serialize_entry(self, key, value) + } + + fn end(self) -> Result { + ::end(self) } } impl Drop for SerializeMap<'_> { fn drop(&mut self) { // free the object - unsafe { JS_FreeValue(self.context.as_mut_ptr(), self.object) }; + if let Some(object) = self.object.take() { + unsafe { JS_FreeValue(self.context.as_mut_ptr(), object) }; + } // free the pending key if let Some(key) = self.pending_key.take() { diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index 75b237f..aa248dc 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -1,16 +1,20 @@ mod map; +mod seq; -use std::ffi::{CStr, CString}; +use std::ffi::CString; use libquickjs_sys::{ - size_t, JSValue, JS_AtomToValue, JS_DupValue, JS_FreeContext, JS_IsException, - JS_NewArrayBufferCopy, JS_NewAtom, JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, - JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, + size_t, JSValue, JS_AtomToValue, JS_IsException, JS_NewArrayBufferCopy, JS_NewAtom, + JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, + JS_ATOM_NULL, }; +use serde::ser::SerializeMap as _; use serde::Serialize; use crate::context::Context; use crate::errors::{Internal, SerializationError}; +use crate::ser::map::SerializeMap; +use crate::ser::seq::SerializeSeq; pub struct Serializer<'a> { context: &'a mut Context, @@ -25,9 +29,9 @@ impl<'a> Serializer<'a> { impl<'a> serde::Serializer for Serializer<'a> { type Error = SerializationError; type Ok = JSValue; - type SerializeMap = (); - type SerializeSeq = (); - type SerializeStruct = (); + type SerializeMap = SerializeMap<'a>; + type SerializeSeq = SerializeSeq<'a>; + type SerializeStruct = SerializeMap<'a>; type SerializeStructVariant = (); type SerializeTuple = (); type SerializeTupleStruct = (); @@ -195,8 +199,8 @@ impl<'a> serde::Serializer for Serializer<'a> { serializer.end() } - fn serialize_seq(self, len: Option) -> Result { - todo!() + fn serialize_seq(self, _: Option) -> Result { + Ok(SerializeSeq::new(self.context)) } fn serialize_tuple(self, len: usize) -> Result { @@ -221,8 +225,8 @@ impl<'a> serde::Serializer for Serializer<'a> { todo!() } - fn serialize_map(self, len: Option) -> Result { - todo!() + fn serialize_map(self, _: Option) -> Result { + Ok(SerializeMap::new(self.context)) } fn serialize_struct( @@ -230,7 +234,7 @@ impl<'a> serde::Serializer for Serializer<'a> { name: &'static str, len: usize, ) -> Result { - todo!() + Ok(SerializeMap::new(self.context)) } fn serialize_struct_variant( diff --git a/serde/src/ser/seq.rs b/serde/src/ser/seq.rs new file mode 100644 index 0000000..7fec208 --- /dev/null +++ b/serde/src/ser/seq.rs @@ -0,0 +1,70 @@ +use libquickjs_sys::{JSValue, JS_FreeValue, JS_NewArray, JS_SetPropertyUint32}; +use serde::Serialize; + +use crate::context::Context; +use crate::errors::SerializationError; +use crate::ser::Serializer; + +pub struct SerializeSeq<'a> { + context: &'a mut Context, + + count: u32, + array: Option, +} + +impl<'a> SerializeSeq<'a> { + pub fn new(context: &'a mut Context) -> Self { + let array = unsafe { JS_NewArray(context.as_mut_ptr()) }; + + Self { + context, + count: 0, + array: Some(array), + } + } +} + +impl<'a> serde::ser::SerializeSeq for SerializeSeq<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = Serializer::new(self.context); + value.serialize(serializer)? + }; + + let array = self.array.expect("array is not initialized"); + let error = + unsafe { JS_SetPropertyUint32(self.context.as_mut_ptr(), array, self.count, value) }; + + if error == -1 { + // exception occurred, time to roll back + let error = SerializationError::from_exception(self.context.as_mut_ptr()); + + // free the value + unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; + + return Err(error); + } + + self.count += 1; + Ok(()) + } + + // TODO: investigate Drop + fn end(mut self) -> Result { + Ok(self.array.take().expect("array is not initialized")) + } +} + +impl Drop for SerializeSeq<'_> { + fn drop(&mut self) { + if let Some(array) = self.array.take() { + unsafe { JS_FreeValue(self.context.as_mut_ptr(), array) }; + } + } +} From eb0b9b5bd6ef6f257e28ac5ce0fd7ca33a1f139e Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 17:50:30 +0200 Subject: [PATCH 12/20] feat: serialize tuple struct --- serde/src/ser/mod.rs | 16 ++++++++-------- serde/src/ser/seq.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index aa248dc..112cf69 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -8,7 +8,7 @@ use libquickjs_sys::{ JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, }; -use serde::ser::SerializeMap as _; +use serde::ser::{SerializeMap as _, SerializeTuple as _}; use serde::Serialize; use crate::context::Context; @@ -33,8 +33,8 @@ impl<'a> serde::Serializer for Serializer<'a> { type SerializeSeq = SerializeSeq<'a>; type SerializeStruct = SerializeMap<'a>; type SerializeStructVariant = (); - type SerializeTuple = (); - type SerializeTupleStruct = (); + type SerializeTuple = SerializeSeq<'a>; + type SerializeTupleStruct = SerializeSeq<'a>; type SerializeTupleVariant = (); fn serialize_bool(mut self, value: bool) -> Result { @@ -203,16 +203,16 @@ impl<'a> serde::Serializer for Serializer<'a> { Ok(SerializeSeq::new(self.context)) } - fn serialize_tuple(self, len: usize) -> Result { - todo!() + fn serialize_tuple(self, _: usize) -> Result { + Ok(SerializeSeq::new(self.context)) } fn serialize_tuple_struct( self, - name: &'static str, - len: usize, + _: &'static str, + _: usize, ) -> Result { - todo!() + Ok(SerializeSeq::new(self.context)) } fn serialize_tuple_variant( diff --git a/serde/src/ser/seq.rs b/serde/src/ser/seq.rs index 7fec208..4247c21 100644 --- a/serde/src/ser/seq.rs +++ b/serde/src/ser/seq.rs @@ -61,6 +61,38 @@ impl<'a> serde::ser::SerializeSeq for SerializeSeq<'a> { } } +impl<'a> serde::ser::SerializeTuple for SerializeSeq<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + ::serialize_element(self, value) + } + + fn end(self) -> Result { + ::end(self) + } +} + +impl<'a> serde::ser::SerializeTupleStruct for SerializeSeq<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + ::serialize_element(self, value) + } + + fn end(self) -> Result { + ::end(self) + } +} + impl Drop for SerializeSeq<'_> { fn drop(&mut self) { if let Some(array) = self.array.take() { From 66070f98b9eb9c492e332dc92d4bb2ed9c966260 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 18:00:48 +0200 Subject: [PATCH 13/20] feat: struct variant --- serde/src/ser/map.rs | 28 ++++++++------- serde/src/ser/mod.rs | 1 + serde/src/ser/variant.rs | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 serde/src/ser/variant.rs diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index 38ff59c..6cec3f2 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -9,7 +9,7 @@ use crate::errors::SerializationError; use crate::ser::Serializer; pub struct SerializeMap<'a> { - context: &'a mut Context, + pub(crate) context: &'a mut Context, object: Option, pending_key: Option, @@ -62,6 +62,20 @@ impl<'a> SerializeMap<'a> { Ok(()) } + + pub(crate) fn finish_object(&mut self) -> Result { + if self.pending_key.is_some() { + return Err(SerializationError::MissingValue); + } + + // insert the buffered values + for (key, value) in self.buffer.drain(..) { + self.insert(key, value)?; + } + + let object = self.object.take().expect("object is not initialized"); + Ok(object) + } } impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { @@ -121,17 +135,7 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { // TODO: when does Drop get called? (I hope after this function) fn end(mut self) -> Result { - if self.pending_key.is_some() { - return Err(SerializationError::MissingValue); - } - - // insert the buffered values - for (key, value) in self.buffer.drain(..) { - self.insert(key, value)?; - } - - let object = self.object.take().expect("object is not initialized"); - Ok(object) + self.finish_object() } } diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index 112cf69..3b8d476 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -1,5 +1,6 @@ mod map; mod seq; +mod variant; use std::ffi::CString; diff --git a/serde/src/ser/variant.rs b/serde/src/ser/variant.rs new file mode 100644 index 0000000..f0ce3df --- /dev/null +++ b/serde/src/ser/variant.rs @@ -0,0 +1,73 @@ +use std::ffi::CString; + +use libquickjs_sys::{JSValue, JS_FreeValue, JS_NewObject, JS_SetPropertyStr}; +use serde::Serialize; + +use crate::context::Context; +use crate::errors::{Internal, SerializationError}; +use crate::ser::map::SerializeMap; + +pub struct SerializeStructVariant<'a> { + variant: &'static str, + + inner: SerializeMap<'a>, +} + +impl<'a> SerializeStructVariant<'a> { + pub fn new(variant: &'static str, context: &'a mut Context) -> Self { + let inner = SerializeMap::new(context); + + Self { variant, inner } + } +} + +impl<'a> serde::ser::SerializeStructVariant for SerializeStructVariant<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + as serde::ser::SerializeMap>::serialize_entry(&mut self.inner, key, value) + } + + fn end(mut self) -> Result { + // we now need to create an object with the variant name with the value + // as the inner object + + // IMPORTANT: we do this conversion before we call finish_object, that way if it + // fails we don't have to worry about freeing the object + let variant = CString::new(self.variant).map_err(Internal::from)?; + + let inner = self.inner.finish_object()?; + + let object = unsafe { JS_NewObject(self.inner.context.as_mut_ptr()) }; + // TODO: check in other places as well + let object = SerializationError::try_from_value(self.inner.context.as_mut_ptr(), object)?; + + let result = unsafe { + JS_SetPropertyStr( + self.inner.context.as_mut_ptr(), + object, + variant.as_ptr(), + inner, + ) + }; + + if result < 0 { + unsafe { JS_FreeValue(self.inner.context.as_mut_ptr(), object) }; + unsafe { JS_FreeValue(self.inner.context.as_mut_ptr(), inner) }; + + return Err(SerializationError::from_exception( + self.inner.context.as_mut_ptr(), + )); + } + + Ok(object) + } +} From 2f2aa6e5d3976d47ce16cac7db74213b3e325dbc Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 18:04:24 +0200 Subject: [PATCH 14/20] feat: check for exception on creation --- serde/src/errors.rs | 2 ++ serde/src/ser/map.rs | 15 ++++++++------- serde/src/ser/seq.rs | 20 ++++++++++++++------ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/serde/src/errors.rs b/serde/src/errors.rs index d3d97b7..8bb495d 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -76,6 +76,8 @@ pub enum SerializationError { MissingValue, #[error("Expected either a string or a number as a key")] InvalidKey, + #[error("The serializer is in an invalid state")] + InvalidState, } impl SerializationError { diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index 6cec3f2..4d80370 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -17,13 +17,14 @@ pub struct SerializeMap<'a> { } impl<'a> SerializeMap<'a> { - pub(crate) fn new(context: &'a mut Context) -> Self { + pub(crate) fn new(context: &'a mut Context) -> Result { let object = unsafe { JS_NewObject(context.as_mut_ptr()) }; + let object = SerializationError::try_from_value(context.as_mut_ptr(), object)?; - Self { + Ok(Self { context, object: Some(object), - } + }) } fn key_to_atom(&mut self, key: JSValue) -> Result { @@ -40,7 +41,9 @@ impl<'a> SerializeMap<'a> { } fn insert(&mut self, key: JSValue, value: JSValue) -> Result<(), SerializationError> { - let object = self.object.expect("object is not initialized"); + // IMPORTANT: This is on top, so that we don't need to free the value in case of + // an error. + let object = self.object.ok_or(SerializationError::InvalidState)?; let key = self.key_to_atom(key)?; @@ -73,8 +76,7 @@ impl<'a> SerializeMap<'a> { self.insert(key, value)?; } - let object = self.object.take().expect("object is not initialized"); - Ok(object) + self.object.take().ok_or(SerializationError::InvalidState) } } @@ -133,7 +135,6 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { self.insert(key, value) } - // TODO: when does Drop get called? (I hope after this function) fn end(mut self) -> Result { self.finish_object() } diff --git a/serde/src/ser/seq.rs b/serde/src/ser/seq.rs index 4247c21..de4b20a 100644 --- a/serde/src/ser/seq.rs +++ b/serde/src/ser/seq.rs @@ -13,14 +13,20 @@ pub struct SerializeSeq<'a> { } impl<'a> SerializeSeq<'a> { - pub fn new(context: &'a mut Context) -> Self { + pub fn new(context: &'a mut Context) -> Result { let array = unsafe { JS_NewArray(context.as_mut_ptr()) }; + let array = SerializationError::try_from_value(context.as_mut_ptr(), array) + .expect("failed to create array"); - Self { + Ok(Self { context, count: 0, array: Some(array), - } + }) + } + + pub(crate) fn finish_array(&mut self) -> Result { + self.array.take().ok_or(SerializationError::InvalidState) } } @@ -32,12 +38,15 @@ impl<'a> serde::ser::SerializeSeq for SerializeSeq<'a> { where T: Serialize, { + // IMPORTANT: This is on top, so that we don't need to free the value in case of + // an error. + let array = self.array.ok_or(SerializationError::InvalidState)?; + let value = { let serializer = Serializer::new(self.context); value.serialize(serializer)? }; - let array = self.array.expect("array is not initialized"); let error = unsafe { JS_SetPropertyUint32(self.context.as_mut_ptr(), array, self.count, value) }; @@ -55,9 +64,8 @@ impl<'a> serde::ser::SerializeSeq for SerializeSeq<'a> { Ok(()) } - // TODO: investigate Drop fn end(mut self) -> Result { - Ok(self.array.take().expect("array is not initialized")) + self.finish_array() } } From 243010bbb65ac118c801d1ff4822578e8d935e1d Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 18:04:29 +0200 Subject: [PATCH 15/20] fix: return type --- serde/src/ser/variant.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/serde/src/ser/variant.rs b/serde/src/ser/variant.rs index f0ce3df..254a527 100644 --- a/serde/src/ser/variant.rs +++ b/serde/src/ser/variant.rs @@ -14,10 +14,13 @@ pub struct SerializeStructVariant<'a> { } impl<'a> SerializeStructVariant<'a> { - pub fn new(variant: &'static str, context: &'a mut Context) -> Self { - let inner = SerializeMap::new(context); + pub fn new( + variant: &'static str, + context: &'a mut Context, + ) -> Result { + let inner = SerializeMap::new(context)?; - Self { variant, inner } + Ok(Self { variant, inner }) } } From 937955d19b042a4c9b8f0bbfef124d852c1bd1b7 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 30 Jul 2023 18:17:19 +0200 Subject: [PATCH 16/20] feat: compile --- serde/src/context.rs | 4 -- serde/src/errors.rs | 2 - serde/src/lib.rs | 16 ++++++ serde/src/ser/map.rs | 25 ++++++++-- serde/src/ser/mod.rs | 54 ++++++++++---------- serde/src/ser/seq.rs | 2 +- serde/src/ser/variant.rs | 103 ++++++++++++++++++++++++++++----------- 7 files changed, 138 insertions(+), 68 deletions(-) diff --git a/serde/src/context.rs b/serde/src/context.rs index 25f2c82..7cef313 100644 --- a/serde/src/context.rs +++ b/serde/src/context.rs @@ -9,10 +9,6 @@ impl Context { Self { context } } - pub(crate) fn as_ptr(&self) -> *const JSContext { - self.context - } - pub(crate) fn as_mut_ptr(&mut self) -> *mut JSContext { self.context as *mut _ } diff --git a/serde/src/errors.rs b/serde/src/errors.rs index 8bb495d..f3e4c88 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -1,6 +1,4 @@ -use std::error::Error; use std::ffi::CStr; -use std::fmt::{Display, Formatter}; use std::str::Utf8Error; use libquickjs_sys::{ diff --git a/serde/src/lib.rs b/serde/src/lib.rs index fac37dc..d73b503 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -1,3 +1,19 @@ mod context; mod errors; mod ser; + +pub use context::Context; +use libquickjs_sys::JSValue; +use serde::Serialize; + +pub fn serialize( + value: &T, + context: &mut Context, +) -> Result +where + T: Serialize, +{ + let serializer = ser::Serializer::new(context); + + value.serialize(serializer) +} diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index 4d80370..97e4ed5 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -1,6 +1,8 @@ +use std::mem; + use libquickjs_sys::{ - JSAtom, JSContext, JSValue, JS_FreeAtom, JS_FreeValue, JS_GetException, JS_IsNull, - JS_NewObject, JS_SetProperty, JS_ValueToAtom, JS_ATOM_NULL, + JSAtom, JSValue, JS_FreeAtom, JS_FreeValue, JS_NewObject, JS_SetProperty, JS_ValueToAtom, + JS_ATOM_NULL, }; use serde::Serialize; @@ -24,6 +26,9 @@ impl<'a> SerializeMap<'a> { Ok(Self { context, object: Some(object), + + pending_key: None, + buffer: Vec::new(), }) } @@ -71,9 +76,19 @@ impl<'a> SerializeMap<'a> { return Err(SerializationError::MissingValue); } - // insert the buffered values - for (key, value) in self.buffer.drain(..) { - self.insert(key, value)?; + let mut buffer = mem::take(&mut self.buffer); + buffer.reverse(); + + while let Some((key, value)) = buffer.pop() { + if let Err(error) = self.insert(key, value) { + // free the remaining values + for (key, value) in buffer { + unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; + unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; + } + + return Err(error); + } } self.object.take().ok_or(SerializationError::InvalidState) diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index 3b8d476..c9d0652 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -5,17 +5,17 @@ mod variant; use std::ffi::CString; use libquickjs_sys::{ - size_t, JSValue, JS_AtomToValue, JS_IsException, JS_NewArrayBufferCopy, JS_NewAtom, - JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, - JS_ATOM_NULL, + size_t, JSValue, JS_AtomToValue, JS_NewArrayBufferCopy, JS_NewBigInt64, JS_NewBigUint64, + JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, }; -use serde::ser::{SerializeMap as _, SerializeTuple as _}; +use serde::ser::SerializeMap as _; use serde::Serialize; use crate::context::Context; use crate::errors::{Internal, SerializationError}; use crate::ser::map::SerializeMap; use crate::ser::seq::SerializeSeq; +use crate::ser::variant::{SerializeStructVariant, SerializeTupleVariant}; pub struct Serializer<'a> { context: &'a mut Context, @@ -33,12 +33,12 @@ impl<'a> serde::Serializer for Serializer<'a> { type SerializeMap = SerializeMap<'a>; type SerializeSeq = SerializeSeq<'a>; type SerializeStruct = SerializeMap<'a>; - type SerializeStructVariant = (); + type SerializeStructVariant = SerializeStructVariant<'a>; type SerializeTuple = SerializeSeq<'a>; type SerializeTupleStruct = SerializeSeq<'a>; - type SerializeTupleVariant = (); + type SerializeTupleVariant = SerializeTupleVariant<'a>; - fn serialize_bool(mut self, value: bool) -> Result { + fn serialize_bool(self, value: bool) -> Result { let value = unsafe { JS_NewBool(self.context.as_mut_ptr(), value) }; SerializationError::try_from_value(self.context.as_mut_ptr(), value) } @@ -51,12 +51,12 @@ impl<'a> serde::Serializer for Serializer<'a> { self.serialize_i32(i32::from(value)) } - fn serialize_i32(mut self, value: i32) -> Result { + fn serialize_i32(self, value: i32) -> Result { let value = unsafe { JS_NewInt32(self.context.as_mut_ptr(), value) }; SerializationError::try_from_value(self.context.as_mut_ptr(), value) } - fn serialize_i64(mut self, value: i64) -> Result { + fn serialize_i64(self, value: i64) -> Result { // try to fit the value into a 32-bit integer, otherwise return a BigInt if let Ok(value) = i32::try_from(value) { return self.serialize_i32(value); @@ -85,7 +85,7 @@ impl<'a> serde::Serializer for Serializer<'a> { self.serialize_u64(u64::from(value)) } - fn serialize_u64(mut self, value: u64) -> Result { + fn serialize_u64(self, value: u64) -> Result { // try to fit the value into a 32-bit integer, otherwise return a BigInt // we could also call `serialize_u64` instead, but that is largely redundant. if let Ok(value) = i32::try_from(value) { @@ -100,7 +100,7 @@ impl<'a> serde::Serializer for Serializer<'a> { self.serialize_f64(f64::from(value)) } - fn serialize_f64(mut self, value: f64) -> Result { + fn serialize_f64(self, value: f64) -> Result { let value = unsafe { JS_NewFloat64(self.context.as_mut_ptr(), value) }; SerializationError::try_from_value(self.context.as_mut_ptr(), value) } @@ -144,7 +144,7 @@ impl<'a> serde::Serializer for Serializer<'a> { where T: Serialize, { - todo!() + value.serialize(self) } fn serialize_unit(self) -> Result { @@ -201,11 +201,11 @@ impl<'a> serde::Serializer for Serializer<'a> { } fn serialize_seq(self, _: Option) -> Result { - Ok(SerializeSeq::new(self.context)) + SerializeSeq::new(self.context) } fn serialize_tuple(self, _: usize) -> Result { - Ok(SerializeSeq::new(self.context)) + SerializeSeq::new(self.context) } fn serialize_tuple_struct( @@ -213,39 +213,39 @@ impl<'a> serde::Serializer for Serializer<'a> { _: &'static str, _: usize, ) -> Result { - Ok(SerializeSeq::new(self.context)) + SerializeSeq::new(self.context) } fn serialize_tuple_variant( self, - name: &'static str, - variant_index: u32, + _: &'static str, + _: u32, variant: &'static str, - len: usize, + _: usize, ) -> Result { - todo!() + SerializeTupleVariant::new(variant, self.context) } fn serialize_map(self, _: Option) -> Result { - Ok(SerializeMap::new(self.context)) + SerializeMap::new(self.context) } fn serialize_struct( self, - name: &'static str, - len: usize, + _: &'static str, + _: usize, ) -> Result { - Ok(SerializeMap::new(self.context)) + SerializeMap::new(self.context) } fn serialize_struct_variant( self, - name: &'static str, - variant_index: u32, + _: &'static str, + _: u32, variant: &'static str, - len: usize, + _: usize, ) -> Result { - todo!() + SerializeStructVariant::new(variant, self.context) } fn is_human_readable(&self) -> bool { diff --git a/serde/src/ser/seq.rs b/serde/src/ser/seq.rs index de4b20a..9c4b3bc 100644 --- a/serde/src/ser/seq.rs +++ b/serde/src/ser/seq.rs @@ -6,7 +6,7 @@ use crate::errors::SerializationError; use crate::ser::Serializer; pub struct SerializeSeq<'a> { - context: &'a mut Context, + pub(crate) context: &'a mut Context, count: u32, array: Option, diff --git a/serde/src/ser/variant.rs b/serde/src/ser/variant.rs index 254a527..5a552f8 100644 --- a/serde/src/ser/variant.rs +++ b/serde/src/ser/variant.rs @@ -1,11 +1,50 @@ use std::ffi::CString; -use libquickjs_sys::{JSValue, JS_FreeValue, JS_NewObject, JS_SetPropertyStr}; +use libquickjs_sys::{JSContext, JSValue, JS_FreeValue, JS_NewObject, JS_SetPropertyStr}; use serde::Serialize; use crate::context::Context; use crate::errors::{Internal, SerializationError}; use crate::ser::map::SerializeMap; +use crate::ser::seq::SerializeSeq; + +/// Serialize an enum variant. +/// +/// Serializes an enum variant as `{variant: value}`. +fn finish( + context: *mut JSContext, + variant: &'static str, + value: JSValue, +) -> Result { + // IMPORTANT: we do this conversion before we call finish_object, that way if it + // fails we don't have to worry about freeing the object + // The only one we need to worry about is the given value. + let variant = CString::new(variant).map_err(Internal::from); + + let variant = match variant { + Ok(variant) => variant, + Err(error) => { + // ensure that we don't memory leak + unsafe { JS_FreeValue(context, value) }; + return Err(SerializationError::Internal(error)); + } + }; + + let object = unsafe { JS_NewObject(context) }; + // TODO: check in other places as well + let object = SerializationError::try_from_value(context, object)?; + + let result = unsafe { JS_SetPropertyStr(context, object, variant.as_ptr(), value) }; + + if result < 0 { + unsafe { JS_FreeValue(context, object) }; + unsafe { JS_FreeValue(context, value) }; + + return Err(SerializationError::from_exception(context)); + } + + Ok(object) +} pub struct SerializeStructVariant<'a> { variant: &'static str, @@ -40,37 +79,43 @@ impl<'a> serde::ser::SerializeStructVariant for SerializeStructVariant<'a> { } fn end(mut self) -> Result { - // we now need to create an object with the variant name with the value - // as the inner object + let inner = self.inner.finish_object()?; - // IMPORTANT: we do this conversion before we call finish_object, that way if it - // fails we don't have to worry about freeing the object - let variant = CString::new(self.variant).map_err(Internal::from)?; + finish(self.inner.context.as_mut_ptr(), self.variant, inner) + } +} - let inner = self.inner.finish_object()?; +pub struct SerializeTupleVariant<'a> { + variant: &'static str, - let object = unsafe { JS_NewObject(self.inner.context.as_mut_ptr()) }; - // TODO: check in other places as well - let object = SerializationError::try_from_value(self.inner.context.as_mut_ptr(), object)?; - - let result = unsafe { - JS_SetPropertyStr( - self.inner.context.as_mut_ptr(), - object, - variant.as_ptr(), - inner, - ) - }; - - if result < 0 { - unsafe { JS_FreeValue(self.inner.context.as_mut_ptr(), object) }; - unsafe { JS_FreeValue(self.inner.context.as_mut_ptr(), inner) }; - - return Err(SerializationError::from_exception( - self.inner.context.as_mut_ptr(), - )); - } + inner: SerializeSeq<'a>, +} + +impl<'a> SerializeTupleVariant<'a> { + pub fn new( + variant: &'static str, + context: &'a mut Context, + ) -> Result { + let inner = SerializeSeq::new(context)?; + + Ok(Self { variant, inner }) + } +} + +impl<'a> serde::ser::SerializeTupleVariant for SerializeTupleVariant<'a> { + type Error = SerializationError; + type Ok = JSValue; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + as serde::ser::SerializeSeq>::serialize_element(&mut self.inner, value) + } + + fn end(mut self) -> Result { + let inner = self.inner.finish_array()?; - Ok(object) + finish(self.inner.context.as_mut_ptr(), self.variant, inner) } } From 8d6e9fec17f997d1f4ca774e20d629cc301ccfab Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 31 Jul 2023 00:38:40 +0200 Subject: [PATCH 17/20] fix: potential memory leak --- Cargo.toml | 8 +++++++- examples/serde.rs | 41 +++++++++++++++++++++++++++++++++++++++++ serde/src/context.rs | 17 +++++++++++++++-- serde/src/ser/map.rs | 37 ++++++++++++------------------------- src/lib.rs | 40 +++++++++++++++++++++++++++++++--------- 5 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 examples/serde.rs diff --git a/Cargo.toml b/Cargo.toml index cefced3..2a5b457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,21 +11,27 @@ authors = ["Christoph Herzog "] keywords = ["quickjs", "javascript", "js", "engine", "interpreter"] [package.metadata.docs.rs] -features = [ "chrono", "bigint", "log" ] +features = ["chrono", "bigint", "log"] [features] default = ["chrono"] patched = ["libquickjs-sys/patched"] bigint = ["num-bigint", "num-traits", "libquickjs-sys/patched"] +serde = ["dep:quick-js-serde", "dep:serde"] [dependencies] libquickjs-sys = { version = ">= 0.9.0, < 0.10.0", path = "./libquickjs-sys" } +quick-js-serde = { path = "./serde", optional = true } +serde = { version = "1", optional = true } chrono = { version = "0.4.7", optional = true } num-bigint = { version = "0.2.2", optional = true } num-traits = { version = "0.2.0", optional = true } log = { version = "0.4.8", optional = true } once_cell = "1.2.0" +[dev-dependencies] +serde = { version = "1.0.176", features = ['derive'] } + [workspace] members = [ "libquickjs-sys", diff --git a/examples/serde.rs b/examples/serde.rs new file mode 100644 index 0000000..4483836 --- /dev/null +++ b/examples/serde.rs @@ -0,0 +1,41 @@ +use quick_js::Context; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct Inner { + b: u8, +} + +#[derive(Debug, Serialize)] +pub struct Example { + a: Vec, +} + +fn main() { + let context = Context::new().unwrap(); + + let value = context.eval("1 + 2").unwrap(); + println!("js: 1 + 2 = {:?}", value); + + context + .add_callback("myCallback", |a: i32, b: i32| a + b * b) + .unwrap(); + + context + .set_global_serde( + "example", + &Example { + a: vec![Inner { b: 5 }, Inner { b: 6 }], + }, + ) + .unwrap(); + + let value = context + .eval( + r#" + JSON.stringify(example) +"#, + ) + .unwrap(); + println!("js: JSON.stringify(example) = {:?}", value); +} diff --git a/serde/src/context.rs b/serde/src/context.rs index 7cef313..3648541 100644 --- a/serde/src/context.rs +++ b/serde/src/context.rs @@ -2,11 +2,22 @@ use libquickjs_sys::JSContext; pub struct Context { context: *const JSContext, + should_drop: bool, } impl Context { pub fn new(context: *mut JSContext) -> Self { - Self { context } + Self { + context, + should_drop: true, + } + } + + pub fn new_without_drop(context: *mut JSContext) -> Self { + Self { + context, + should_drop: false, + } } pub(crate) fn as_mut_ptr(&mut self) -> *mut JSContext { @@ -16,6 +27,8 @@ impl Context { impl Drop for Context { fn drop(&mut self) { - unsafe { libquickjs_sys::JS_FreeContext(self.context as *mut _) }; + if self.should_drop { + unsafe { libquickjs_sys::JS_FreeContext(self.context as *mut _) }; + } } } diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index 97e4ed5..ef13b26 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -15,7 +15,6 @@ pub struct SerializeMap<'a> { object: Option, pending_key: Option, - buffer: Vec<(JSValue, JSValue)>, } impl<'a> SerializeMap<'a> { @@ -28,7 +27,6 @@ impl<'a> SerializeMap<'a> { object: Some(object), pending_key: None, - buffer: Vec::new(), }) } @@ -76,21 +74,6 @@ impl<'a> SerializeMap<'a> { return Err(SerializationError::MissingValue); } - let mut buffer = mem::take(&mut self.buffer); - buffer.reverse(); - - while let Some((key, value)) = buffer.pop() { - if let Err(error) = self.insert(key, value) { - // free the remaining values - for (key, value) in buffer { - unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; - unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; - } - - return Err(error); - } - } - self.object.take().ok_or(SerializationError::InvalidState) } } @@ -122,7 +105,7 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { let serializer = Serializer::new(self.context); let value = value.serialize(serializer)?; - self.buffer.push((key, value)); + self.insert(key, value)?; Ok(()) } @@ -144,7 +127,17 @@ impl<'a> serde::ser::SerializeMap for SerializeMap<'a> { let value = { let serializer = Serializer::new(self.context); - value.serialize(serializer)? + value.serialize(serializer) + }; + + let value = match value { + Ok(value) => value, + Err(error) => { + // free the key + unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; + + return Err(error); + } }; self.insert(key, value) @@ -186,11 +179,5 @@ impl Drop for SerializeMap<'_> { if let Some(key) = self.pending_key.take() { unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; } - - // free the buffer - for (key, value) in self.buffer.drain(..) { - unsafe { JS_FreeValue(self.context.as_mut_ptr(), key) }; - unsafe { JS_FreeValue(self.context.as_mut_ptr(), value) }; - } } } diff --git a/src/lib.rs b/src/lib.rs index aeaeab5..4a513d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ //! quick-js is a a Rust wrapper for [QuickJS](https://bellard.org/quickjs/), a new Javascript //! engine by Fabrice Bellard. //! -//! It enables easy and straight-forward execution of modern Javascript from Rust. +//! It enables easy and straight-forward execution of modern Javascript from +//! Rust. //! //! ## Limitations //! @@ -42,12 +43,12 @@ mod value; #[cfg(test)] mod tests; -use std::{convert::TryFrom, error, fmt}; +use std::convert::TryFrom; +use std::{error, fmt}; -pub use self::{ - callback::{Arguments, Callback}, - value::*, -}; +pub use self::callback::{Arguments, Callback}; +pub use self::value::*; +use crate::bindings::OwnedJsValue; /// Error on Javascript execution. #[derive(PartialEq, Debug)] @@ -62,6 +63,9 @@ pub enum ExecutionError { Exception(JsValue), /// JS Runtime exceeded the memory limit. OutOfMemory, + // TODO: temp + /// Serialization Error + Serialize, #[doc(hidden)] __NonExhaustive, } @@ -74,6 +78,7 @@ impl fmt::Display for ExecutionError { Conversion(e) => e.fmt(f), Internal(e) => write!(f, "Internal error: {}", e), Exception(e) => write!(f, "{:?}", e), + Serialize => write!(f, "Serialization error"), OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"), __NonExhaustive => unreachable!(), } @@ -182,7 +187,8 @@ impl Context { Self { wrapper } } - /// Create a `ContextBuilder` that allows customization of JS Runtime settings. + /// Create a `ContextBuilder` that allows customization of JS Runtime + /// settings. /// /// For details, see the methods on `ContextBuilder`. /// @@ -304,6 +310,22 @@ impl Context { Ok(()) } + /// TODO + #[cfg(feature = "serde")] + pub fn set_global_serde(&self, name: &str, value: &V) -> Result<(), ExecutionError> + where + V: serde::Serialize, + { + let global = self.wrapper.global()?; + + let mut context = quick_js_serde::Context::new_without_drop(self.wrapper.context); + let value = quick_js_serde::serialize(value, &mut context) + .map_err(|_| ExecutionError::Serialize)?; + let value = OwnedJsValue::new(&self.wrapper, value); + + global.set_property(name, value) + } + /// Call a global function in the Javascript namespace. /// /// **Promises**: @@ -348,8 +370,8 @@ impl Context { /// * must return a value /// * the return value must either: /// - be convertible to JsValue - /// - be a Result where T is convertible to JsValue - /// if Err(e) is returned, a Javascript exception will be raised + /// - be a Result where T is convertible to JsValue if Err(e) is + /// returned, a Javascript exception will be raised /// /// ```rust /// use quick_js::{Context, JsValue}; From 8d462ecef1aa4b8f0e85008275e9df86d35a067f Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 31 Jul 2023 19:10:23 +0200 Subject: [PATCH 18/20] feat: positive tests --- serde/Cargo.toml | 4 + serde/src/errors.rs | 2 + serde/src/ser/mod.rs | 31 +++++- serde/tests/ser.rs | 245 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 14 ++- 5 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 serde/tests/ser.rs diff --git a/serde/Cargo.toml b/serde/Cargo.toml index 08d2fd1..90da906 100644 --- a/serde/Cargo.toml +++ b/serde/Cargo.toml @@ -10,3 +10,7 @@ libquickjs-sys = { path = "../libquickjs-sys" } serde = "1" thiserror = "1" + +[dev-dependencies] +quick-js = { path = "..", features = ["serde"] } +serde = { version = "1", features = ["derive"] } diff --git a/serde/src/errors.rs b/serde/src/errors.rs index f3e4c88..138ecd3 100644 --- a/serde/src/errors.rs +++ b/serde/src/errors.rs @@ -76,6 +76,8 @@ pub enum SerializationError { InvalidKey, #[error("The serializer is in an invalid state")] InvalidState, + #[error("The number is too large to be represented")] + IntTooLarge, } impl SerializationError { diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index c9d0652..0fba9bb 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -5,8 +5,9 @@ mod variant; use std::ffi::CString; use libquickjs_sys::{ - size_t, JSValue, JS_AtomToValue, JS_NewArrayBufferCopy, JS_NewBigInt64, JS_NewBigUint64, - JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_ATOM_NULL, + size_t, JSValue, JSValueUnion, JS_AtomToValue, JS_DupAtom, JS_NewArrayBufferCopy, + JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, + JS_ATOM_NULL, JS_TAG_NULL, }; use serde::ser::SerializeMap as _; use serde::Serialize; @@ -66,6 +67,14 @@ impl<'a> serde::Serializer for Serializer<'a> { SerializationError::try_from_value(self.context.as_mut_ptr(), value) } + fn serialize_i128(self, value: i128) -> Result { + if let Ok(value) = i64::try_from(value) { + return self.serialize_i64(value); + } + + return Err(SerializationError::IntTooLarge); + } + // For now we don't support i128 and u128, as there are no methods to create // BigInts for them. // In theory we could create our own function to do so, but for now that's @@ -96,6 +105,14 @@ impl<'a> serde::Serializer for Serializer<'a> { SerializationError::try_from_value(self.context.as_mut_ptr(), value) } + fn serialize_u128(self, value: u128) -> Result { + if let Ok(value) = u64::try_from(value) { + return self.serialize_u64(value); + } + + Err(SerializationError::IntTooLarge) + } + fn serialize_f32(self, value: f32) -> Result { self.serialize_f64(f64::from(value)) } @@ -150,9 +167,13 @@ impl<'a> serde::Serializer for Serializer<'a> { fn serialize_unit(self) -> Result { // Unit corresponds to `null` in JS - // TODO: I have no idea if this is correct (AtomToValue) - let value = unsafe { JS_AtomToValue(self.context.as_mut_ptr(), JS_ATOM_NULL) }; - SerializationError::try_from_value(self.context.as_mut_ptr(), value) + // Taken from: https://docs.rs/quickjs_runtime/latest/src/quickjs_runtime/quickjs_utils/mod.rs.html#46-51 + let null = JSValue { + u: JSValueUnion { int32: 0 }, + tag: i64::from(JS_TAG_NULL), + }; + + Ok(null) } fn serialize_unit_struct(self, _: &'static str) -> Result { diff --git a/serde/tests/ser.rs b/serde/tests/ser.rs new file mode 100644 index 0000000..abb953b --- /dev/null +++ b/serde/tests/ser.rs @@ -0,0 +1,245 @@ +use quick_js::Context; +use serde::Serialize; + +fn run(value: &T) -> String +where + T: Serialize, +{ + let context = Context::new().unwrap(); + + context.set_global_serde("example", value).unwrap(); + + context + .eval_as::("JSON.stringify(example)") + .unwrap() +} + +#[test] +fn u8() { + assert_eq!(run(&5u8), "5"); +} + +#[test] +fn u16() { + assert_eq!(run(&5u16), "5"); +} + +#[test] +fn u32() { + assert_eq!(run(&5u32), "5"); +} + +#[test] +fn u64() { + assert_eq!(run(&5u64), "5"); +} + +#[test] +fn u128() { + assert_eq!(run(&5u128), "5"); +} + +#[test] +fn i8() { + assert_eq!(run(&-5i8), "-5"); +} + +#[test] +fn i16() { + assert_eq!(run(&-5i16), "-5"); +} + +#[test] +fn i32() { + assert_eq!(run(&-5i32), "-5"); +} + +#[test] +fn i64() { + assert_eq!(run(&-5i64), "-5"); +} + +#[test] +fn i128() { + assert_eq!(run(&-5i128), "-5"); +} + +#[test] +fn bool() { + assert_eq!(run(&true), "true"); + assert_eq!(run(&false), "false"); +} + +#[test] +fn char() { + assert_eq!(run(&'a'), r#""a""#); +} + +#[test] +fn str() { + assert_eq!(run(&"abc"), r#""abc""#); +} + +#[test] +fn string() { + assert_eq!(run(&String::from("abc")), r#""abc""#); +} + +#[test] +fn unit() { + assert_eq!(run(&()), "null"); +} + +#[test] +fn option() { + assert_eq!(run(&Some(5u8)), "5"); + assert_eq!(run(&None::), "null"); +} + +#[test] +fn vec() { + assert_eq!(run(&vec![5u8, 6u8]), "[5,6]"); +} + +#[test] +fn tuple() { + assert_eq!(run(&(5u8, 6u8)), "[5,6]"); +} + +#[test] +fn tuple_struct() { + #[derive(Serialize)] + struct TupleStruct(u8, u8); + + assert_eq!(run(&TupleStruct(5u8, 6u8)), "[5,6]"); +} + +#[test] +fn map() { + use std::collections::BTreeMap; + + let mut map = BTreeMap::new(); + map.insert("a", 5u8); + map.insert("b", 6u8); + + assert_eq!(run(&map), r#"{"a":5,"b":6}"#); +} + +#[test] +fn struct_() { + #[derive(Serialize)] + struct Struct { + a: u8, + b: u8, + } + + assert_eq!(run(&Struct { a: 5u8, b: 6u8 }), r#"{"a":5,"b":6}"#); +} + +#[test] +fn struct_with_lifetime() { + #[derive(Serialize)] + struct Struct<'a> { + a: &'a str, + b: &'a str, + } + + assert_eq!( + run(&Struct { a: "abc", b: "def" }), + r#"{"a":"abc","b":"def"}"# + ); +} + +#[test] +fn struct_with_lifetime_and_lifetime_in_type() { + #[derive(Serialize)] + struct Struct<'a> { + a: &'a str, + b: &'a str, + c: std::marker::PhantomData<&'a ()>, + } + + assert_eq!( + run(&Struct { + a: "abc", + b: "def", + c: std::marker::PhantomData, + }), + r#"{"a":"abc","b":"def","c":null}"# + ); +} + +#[test] +fn zero_sized_struct() { + #[derive(Serialize)] + struct Struct; + + assert_eq!(run(&Struct), r#"null"#); +} + +#[test] +fn enum_() { + #[derive(Serialize)] + enum Enum { + A, + B, + } + + assert_eq!(run(&Enum::A), r#""A""#); + assert_eq!(run(&Enum::B), r#""B""#); +} + +#[test] +fn enum_tuple() { + #[derive(Serialize)] + enum Enum { + A(u8), + B(u8), + } + + assert_eq!(run(&Enum::A(5u8)), r#"{"A":5}"#); + assert_eq!(run(&Enum::B(6u8)), r#"{"B":6}"#); +} + +#[test] +fn enum_struct() { + #[derive(Serialize)] + enum Enum { + A { a: u8 }, + B { b: u8 }, + } + + assert_eq!(run(&Enum::A { a: 5u8 }), r#"{"A":{"a":5}}"#); + assert_eq!(run(&Enum::B { b: 6u8 }), r#"{"B":{"b":6}}"#); +} + +#[test] +fn enum_with_lifetime() { + #[derive(Serialize)] + enum Enum<'a> { + A { a: &'a str }, + B { b: &'a str }, + } + + assert_eq!(run(&Enum::A { a: "abc" }), r#"{"A":{"a":"abc"}}"#); + assert_eq!(run(&Enum::B { b: "def" }), r#"{"B":{"b":"def"}}"#); +} + +#[test] +fn enum_with_lifetime_and_lifetime_in_type() { + #[derive(Serialize)] + enum Enum<'a> { + A { a: &'a str }, + B { b: &'a str }, + C(std::marker::PhantomData<&'a ()>), + } + + assert_eq!(run(&Enum::A { a: "abc" }), r#"{"A":{"a":"abc"}}"#); + assert_eq!(run(&Enum::B { b: "def" }), r#"{"B":{"b":"def"}}"#); +} + +// TODO: sequence failure of element +// TODO: map failure of key +// TODO: map failure of value +// TODO: map do not supply value +// TODO: map supply extra key diff --git a/src/lib.rs b/src/lib.rs index 4a513d8..f5255b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -310,7 +310,19 @@ impl Context { Ok(()) } - /// TODO + /// Set a global variable using serde serialization. + /// + /// ```rust + /// use quick_js::{Context, JsValue}; + /// let context = Context::new().unwrap(); + /// + /// context.set_global_serde("someGlobalVariable", &42).unwrap(); + /// let value = context.eval_as::("someGlobalVariable").unwrap(); + /// assert_eq!( + /// value, + /// 42, + /// ); + /// ``` #[cfg(feature = "serde")] pub fn set_global_serde(&self, name: &str, value: &V) -> Result<(), ExecutionError> where From 34597e6f44dfda276783ac8f207fbc3bc58a145d Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 31 Jul 2023 19:20:50 +0200 Subject: [PATCH 19/20] feat: negative tests (needed for miri) --- serde/tests/ser.rs | 249 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 241 insertions(+), 8 deletions(-) diff --git a/serde/tests/ser.rs b/serde/tests/ser.rs index abb953b..983a5f2 100644 --- a/serde/tests/ser.rs +++ b/serde/tests/ser.rs @@ -1,5 +1,21 @@ -use quick_js::Context; -use serde::Serialize; +use std::collections::BTreeMap; +use std::marker::PhantomData; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use quick_js::{Context, ExecutionError}; +use serde::ser::{Error, SerializeMap}; +use serde::{Serialize, Serializer}; + +fn run_serialize_error(value: &T) -> ExecutionError +where + T: Serialize, +{ + let context = Context::new().unwrap(); + + let result = context.set_global_serde("example", value); + + result.expect_err("serialization should fail") +} fn run(value: &T) -> String where @@ -231,15 +247,232 @@ fn enum_with_lifetime_and_lifetime_in_type() { enum Enum<'a> { A { a: &'a str }, B { b: &'a str }, - C(std::marker::PhantomData<&'a ()>), + C(PhantomData<&'a ()>), } assert_eq!(run(&Enum::A { a: "abc" }), r#"{"A":{"a":"abc"}}"#); assert_eq!(run(&Enum::B { b: "def" }), r#"{"B":{"b":"def"}}"#); } -// TODO: sequence failure of element -// TODO: map failure of key -// TODO: map failure of value -// TODO: map do not supply value -// TODO: map supply extra key +#[test] +fn vec_element_error() { + struct Element; + + static COUNT: AtomicUsize = AtomicUsize::new(0); + + impl Serialize for Element { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let next = COUNT.fetch_add(1, Ordering::SeqCst); + + if next == 1 { + Err(Error::custom("failure")) + } else { + next.serialize(serializer) + } + } + } + + assert_eq!( + run_serialize_error(&vec![Element, Element, Element]), + ExecutionError::Serialize + ); +} + +#[test] +fn map_key_error() { + struct Key; + + impl Serialize for Key { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_key(&Key)?; + map.serialize_value(&5u8)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_value_error() { + struct Value; + + impl Serialize for Value { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_key(&5u8)?; + map.serialize_value(&Value)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_entry_key_error() { + struct Key; + + impl Serialize for Key { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry(&Key, &5u8)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_entry_value_error() { + struct Value; + + impl Serialize for Value { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry(&5u8, &Value)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_entry_error() { + struct Key; + + impl Serialize for Key { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Value; + + impl Serialize for Value { + fn serialize(&self, _: S) -> Result + where + S: Serializer, + { + Err(Error::custom("failure")) + } + } + + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_entry(&Key, &Value)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_no_corresponding_value_error() { + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_key(&5u8)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} + +#[test] +fn map_extra_value_error() { + struct Map; + + impl Serialize for Map { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + map.serialize_key(&5u8)?; + map.serialize_value(&5u8)?; + map.serialize_value(&5u8)?; + map.end() + } + } + + assert_eq!(run_serialize_error(&Map), ExecutionError::Serialize); +} From 287e496093b97944634ec496814dea4aad5a489d Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Mon, 31 Jul 2023 19:25:26 +0200 Subject: [PATCH 20/20] chore: clippy --- serde/src/ser/map.rs | 2 -- serde/src/ser/mod.rs | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/serde/src/ser/map.rs b/serde/src/ser/map.rs index ef13b26..17275d5 100644 --- a/serde/src/ser/map.rs +++ b/serde/src/ser/map.rs @@ -1,5 +1,3 @@ -use std::mem; - use libquickjs_sys::{ JSAtom, JSValue, JS_FreeAtom, JS_FreeValue, JS_NewObject, JS_SetProperty, JS_ValueToAtom, JS_ATOM_NULL, diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index 0fba9bb..f0697bc 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -5,9 +5,8 @@ mod variant; use std::ffi::CString; use libquickjs_sys::{ - size_t, JSValue, JSValueUnion, JS_AtomToValue, JS_DupAtom, JS_NewArrayBufferCopy, - JS_NewBigInt64, JS_NewBigUint64, JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, - JS_ATOM_NULL, JS_TAG_NULL, + size_t, JSValue, JSValueUnion, JS_NewArrayBufferCopy, JS_NewBigInt64, JS_NewBigUint64, + JS_NewBool, JS_NewFloat64, JS_NewInt32, JS_NewStringLen, JS_TAG_NULL, }; use serde::ser::SerializeMap as _; use serde::Serialize;