From 816efcbad8d381a2bc794625c1df072aa02bc8f7 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Mon, 29 Apr 2024 23:58:21 +0200 Subject: [PATCH 01/15] feat: recursion Support recursion in the serialization schema graph --- README.md | 12 + savefile-abi/src/lib.rs | 26 +- savefile-derive/src/lib.rs | 12 +- savefile-derive/src/savefile_abi.rs | 8 +- savefile-min-build/src/lib.rs | 17 +- savefile-test/src/cycles.rs | 87 ++++ savefile-test/src/lib.rs | 2 + .../advanced_datatypes_test.rs | 14 + .../argument_backward_compatibility.rs | 4 +- .../src/savefile_abi_test/basic_abi_tests.rs | 9 + savefile/src/lib.rs | 411 +++++++++++------- savefile/src/prelude.rs | 2 +- 12 files changed, 421 insertions(+), 183 deletions(-) create mode 100644 savefile-test/src/cycles.rs diff --git a/README.md b/README.md index e211785..c82ed4e 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,18 @@ what the rust project calls it) encode the discriminant. Set to 1 for enums which will never have more than 256 fields. Set to 2 for bigger enums. If you ever need an enum to have more than 65536 fields, set it to 4. +1.5: The WithSchema::schema function now takes a context object. You can just pass this through for +most data types. Only smart pointers, containers, Box etc need ot use this, to guard against +recursion. See documentation of WithSchemaContext. + +1.6: Several savefile trait implementations have now gained 'static-bounds. For example, +Box, Vec and many more now require T:'static. There was no such bound before, but +since references cannot be deserialized, it was still typically not possible to deserialize +anything containing a reference. In principle, someone could have implemented Deserialize +for some type, leaking memory and returning an instance with a non-static lifetime. However, +this is a very niche use, and it seems much more likely that deserializing types with arbitrary +lifetimes is an error. Please file an issue if you have an use-case for deserializing types +with lifetimes. ## 0.17.0-beta.13 diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index 7d6a7cb..abd939b 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -1174,7 +1174,7 @@ impl AbiConnection { }); } let mut mask = 0; - let mut check_diff = |effective1, effective2, native1, native2, index: Option| { + let mut verify_compatibility = |effective1, effective2, native1, native2, index: Option| { let effective_schema_diff = diff_schema(effective1, effective2, "".to_string()); if let Some(diff) = effective_schema_diff { return Err(SavefileError::IncompatibleSchema { @@ -1192,9 +1192,6 @@ impl AbiConnection { }); } - //let caller_isref = caller_native_method.info.arguments[index].can_be_sent_as_ref; - //let callee_isref = callee_native_method.info.arguments[index].can_be_sent_as_ref; - let comp = arg_layout_compatible(native1, native2, effective1, effective2, effective_version); if comp { @@ -1210,10 +1207,10 @@ impl AbiConnection { let effective2 = &callee_effective_method.info.arguments[index].schema; let native1 = &caller_native_method.info.arguments[index].schema; let native2 = &callee_native_method.info.arguments[index].schema; - check_diff(effective1, effective2, native1, native2, Some(index))?; + verify_compatibility(effective1, effective2, native1, native2, Some(index))?; } - check_diff( + verify_compatibility( &caller_effective_method.info.return_value, &callee_effective_method.info.return_value, &caller_native_method.info.return_value, @@ -1322,6 +1319,23 @@ impl AbiConnection { Self::from_raw(packed.entry, packed.trait_object, owning) } + /// Check if the given argument 'arg' in method 'method' is memory compatible such that + /// it will be sent as a reference, not copied. This will depend on the memory layout + /// of the code being called into. It will not change during the lifetime of an + /// AbiConnector, but it may change if the target library is recompiled. + pub fn get_arg_passable_by_ref(&self, method: &str, arg: usize) -> bool { + if let Some(found) = self.template.methods.iter().find(|var|var.method_name == method) { + let abi_method: &AbiConnectionMethod = found; + if arg >= abi_method.caller_info.arguments.len() { + panic!("Method '{}' has only {} arguments, so there is no argument #{}", method, abi_method.caller_info.arguments.len(), arg); + } + (abi_method.compatibility_mask & (1 << (arg as u64))) != 0 + } else { + let arg_names : Vec<_> = self.template.methods.iter().map(|x|x.method_name.as_str()).collect(); + panic!("Trait has no method with name '{}'. Available methods: {}", method, arg_names.join(", ")); + } + } + /// This routine is mostly for tests. /// It allows using a raw external API entry point directly. /// This is mostly useful for internal testing of the savefile-abi-library. diff --git a/savefile-derive/src/lib.rs b/savefile-derive/src/lib.rs index d52dcab..92a5a25 100644 --- a/savefile-derive/src/lib.rs +++ b/savefile-derive/src/lib.rs @@ -253,7 +253,7 @@ pub fn savefile_abi_exportable( let uses = quote_spanned! { defspan => extern crate savefile; extern crate savefile_abi; - use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; + use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, WithSchemaContext, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; use savefile_abi::{parse_return_value_impl,abi_result_receiver,abi_boxed_trait_receiver, FlexBuffer, AbiExportable, TraitObject, PackagedTraitObject, Owning, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, AbiProtocol, abi_entry_light}; use std::collections::HashMap; use std::mem::MaybeUninit; @@ -1574,7 +1574,7 @@ fn implement_withschema( "The Removed type can only be used for removed fields. Use the savefile_version attribute." ); } - fields.push(quote_spanned!( span => #fields1.push(unsafe{#Field::unsafe_new(#name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version)), #offset)} ))); + fields.push(quote_spanned!( span => #fields1.push(unsafe{#Field::unsafe_new(#name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version, context)), #offset)} ))); } else { let mut version_mappings = Vec::new(); let offset = if field_to_version != u32::MAX { @@ -1589,7 +1589,7 @@ fn implement_withschema( // We don't supply offset in this case, deserialized type doesn't match field type version_mappings.push(quote!{ if #local_version >= #dt_from && local_version <= #dt_to { - #fields1.push(#Field ::new( #name_str.to_string(), std::boxed::Box::new(<#dt_field_type as #WithSchema>::schema(#local_version))) ); + #fields1.push(#Field ::new( #name_str.to_string(), std::boxed::Box::new(<#dt_field_type as #WithSchema>::schema(#local_version, context))) ); } }); } @@ -1598,7 +1598,7 @@ fn implement_withschema( #(#version_mappings)* if #local_version >= #field_from_version && #local_version <= #field_to_version { - #fields1.push(unsafe{#Field ::unsafe_new( #name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version)), #offset )} ); + #fields1.push(unsafe{#Field ::unsafe_new( #name_str.to_string(), std::boxed::Box::new(<#field_type as #WithSchema>::schema(#local_version, context)), #offset )} ); } )); } @@ -1831,7 +1831,7 @@ fn savefile_derive_crate_withschema(input: DeriveInput) -> TokenStream { #[allow(unused_mut)] #[allow(unused_comparisons, unused_variables)] - fn schema(version:u32) -> #Schema { + fn schema(version:u32, context: &mut _savefile::prelude::WithSchemaContext) -> #Schema { let local_version = version; #Schema::Enum ( @@ -1912,7 +1912,7 @@ fn savefile_derive_crate_withschema(input: DeriveInput) -> TokenStream { impl #impl_generics #withschema for #name #ty_generics #where_clause #extra_where { #[allow(unused_comparisons)] #[allow(unused_mut, unused_variables)] - fn schema(version:u32) -> #Schema { + fn schema(version:u32, context: &mut _savefile::prelude::WithSchemaContext) -> #Schema { let local_version = version; let mut fields1 = Vec::new(); #(#fields;)* ; diff --git a/savefile-derive/src/savefile_abi.rs b/savefile-derive/src/savefile_abi.rs index 544439d..13cfcb0 100644 --- a/savefile-derive/src/savefile_abi.rs +++ b/savefile-derive/src/savefile_abi.rs @@ -685,7 +685,7 @@ impl ArgType { caller_arg_serializer1: quote! { #arg_name.serialize(&mut serializer) }, - schema: quote!( <#arg_type as WithSchema>::schema(version) ), + schema: quote!( <#arg_type as WithSchema>::schema(version, context) ), known_size_align1: if compile_time_check_reprc(arg_type) { compile_time_size(arg_type) } else { @@ -938,7 +938,7 @@ pub(super) fn generate_method_definitions( //let can_be_sent_as_ref = instruction.can_be_sent_as_ref; metadata_arguments.push(quote! { AbiMethodArgument { - schema: #schema, + schema: { let mut context = WithSchemaContext::new(); let context = &mut context; #schema }, } }); if let Some(total_size) = &mut compile_time_known_size { @@ -975,7 +975,7 @@ pub(super) fn generate_method_definitions( let result_default; let return_ser_temp; if no_return { - return_value_schema = quote!(<() as WithSchema>::schema(0)); + return_value_schema = quote!(<() as WithSchema>::schema(0, &mut WithSchemaContext::new())); ret_deserializer = quote!(()); //Zero-sized, no deserialize actually needed ret_serialize = quote!(()); caller_return_type = quote!(()); @@ -1117,7 +1117,7 @@ pub(super) fn generate_method_definitions( AbiMethod { name: #method_name_str.to_string(), info: AbiMethodInfo { - return_value: #return_value_schema, + return_value: { let mut context = WithSchemaContext::new(); let context = &mut context; #return_value_schema}, arguments: vec![ #(#metadata_arguments,)* ], } } diff --git a/savefile-min-build/src/lib.rs b/savefile-min-build/src/lib.rs index 18c49d0..91af8c8 100644 --- a/savefile-min-build/src/lib.rs +++ b/savefile-min-build/src/lib.rs @@ -1,13 +1,26 @@ extern crate savefile_abi; extern crate savefile_derive; +use savefile_abi::AbiConnection; use savefile_derive::savefile_abi_exportable; #[savefile_abi_exportable(version = 0)] pub trait ExampleTrait { - fn get(&mut self) -> &'static str; + fn test_slices(&mut self, slice: &[u32]) -> u32 { + slice.iter().copied().sum() + } +} + +impl ExampleTrait for () { + } #[test] -fn dummy_test() {} +fn dummy_test() { + let boxed: Box = Box::new(()); + let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); + + assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); + //conn.test_slices(&[1,2,3,4]); +} diff --git a/savefile-test/src/cycles.rs b/savefile-test/src/cycles.rs new file mode 100644 index 0000000..e776978 --- /dev/null +++ b/savefile-test/src/cycles.rs @@ -0,0 +1,87 @@ +use assert_roundtrip; +use savefile::Removed; + +#[derive(Savefile, Debug, PartialEq)] +enum Tree { + Leaf, + Node(Box,Box) +} + +#[test] +pub fn test_cyclic() { + let example = Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf)); + assert_roundtrip(example); + + let example = Tree::Node(Box::new(Tree::Node(Box::new(Tree::Leaf),Box::new(Tree::Leaf))), Box::new(Tree::Leaf)); + assert_roundtrip(example); +} + + +#[derive(Savefile, Debug, PartialEq)] +struct TreeNode { + tree: Box +} + +#[derive(Savefile, Debug, PartialEq)] +enum Tree2 { + Leaf(String), + Node(TreeNode) +} +#[test] +pub fn test_cyclic2() { + let example = Tree2::Node(TreeNode{tree: Box::new(Tree2::Leaf("hej".into()))}); + assert_roundtrip(example); +} +#[derive(Savefile, Debug, PartialEq)] +enum Version1LevelD { + Leaf, + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version1LevelC { + Leaf, + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version1LevelB { + Leaf(Box), + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version1LevelA { + Leaf, + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version2LevelC { + Leaf, + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version2LevelB { + Leaf(Box), + Node(Box) +} + +#[derive(Savefile, Debug, PartialEq)] +enum Version2LevelA { + Leaf, + Node(Box) +} + +#[test] +#[should_panic(expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.Version1LevelA/Node/0Version1LevelB/Leaf/0Version1LevelC/Node/0Version1LevelD/Node/0]: In memory schema: , file schema: enum")] +fn cycles_vertest1() { + use assert_roundtrip_to_new_version; + assert_roundtrip_to_new_version( + Version1LevelA::Leaf, + 0, + Version2LevelA::Leaf, + 1, + ); +} \ No newline at end of file diff --git a/savefile-test/src/lib.rs b/savefile-test/src/lib.rs index adeec2f..25f9df9 100644 --- a/savefile-test/src/lib.rs +++ b/savefile-test/src/lib.rs @@ -460,6 +460,8 @@ pub fn assert_roundtrip_to_new_version< mod enum_variant_versioning; +mod cycles; + #[test] pub fn test_array_string() { use arrayvec::ArrayString; diff --git a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs index 52f7102..7d5fc85 100644 --- a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs +++ b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs @@ -12,6 +12,7 @@ pub trait AdvancedTestInterface { fn clone_hashmap(&self, x: &HashMap) -> HashMap; fn return_trait_object(&self) -> Box; + fn test_slices(&mut self, slice: &[u32]) -> u32; fn return_boxed_closure(&self) -> Box u32>; fn return_boxed_closure2(&self) -> Box; @@ -52,11 +53,24 @@ impl AdvancedTestInterface for AdvancedTestInterfaceImpl { Box::new(|| {}) } + fn test_slices(&mut self, slice: &[u32]) -> u32 { + slice.iter().copied().sum() + } + fn many_callbacks(&mut self, x: &mut dyn FnMut(&dyn Fn(&dyn Fn() -> u32) -> u32) -> u32) -> u32 { x(&|y| y()) } } +#[test] +fn abi_test_slice() { + let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); + let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); + + assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); + assert_eq!(conn.test_slices(&[1,2,3,4]), 10); +} + #[test] fn test_trait_object_in_return_position() { let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); diff --git a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs index 7bfc3b2..5927c96 100644 --- a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs +++ b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs @@ -1,5 +1,5 @@ use savefile::prelude::AbiRemoved; -use savefile::SavefileError; +use savefile::{SavefileError, WithSchemaContext}; use savefile_abi::RawAbiCallResult::AbiError; use savefile_abi::{verify_compatiblity, AbiConnection, AbiExportable}; use savefile_abi_test::argument_backward_compatibility::v1::{ArgInterfaceV1, EnumArgument, Implementation1}; @@ -98,7 +98,7 @@ pub fn test_backward_compatibility() -> Result<(), SavefileError> { #[test] pub fn test_arg_argument_metadata() { use savefile::WithSchema; - let schema = v2::ArgArgument::schema(0); + let schema = v2::ArgArgument::schema(0, &mut WithSchemaContext::new()); println!("Schema: {:#?}", schema); assert!(!schema.layout_compatible(&schema)); //Versions containing removed items should never be considered layout compatible (since their schema type is not identical to the memory type) } diff --git a/savefile-test/src/savefile_abi_test/basic_abi_tests.rs b/savefile-test/src/savefile_abi_test/basic_abi_tests.rs index 85b97d6..c34dbbe 100644 --- a/savefile-test/src/savefile_abi_test/basic_abi_tests.rs +++ b/savefile-test/src/savefile_abi_test/basic_abi_tests.rs @@ -24,6 +24,7 @@ pub trait TestInterface { fn do_mut_nothing(&mut self); + fn deref_u32(&self, x: &u32) -> u32; fn count_chars(&self, x: &String) -> usize; fn count_chars_str(&self, x: &str) -> usize; @@ -118,6 +119,10 @@ impl TestInterface for TestInterfaceImpl { fn get_static_str(&self) -> &'static str { "hello world" } + + fn deref_u32(&self, x: &u32) -> u32 { + *x + } } savefile_abi_export!(TestInterfaceImpl, TestInterface); @@ -139,7 +144,11 @@ fn test_basic_call_abi() { assert_eq!(conn.count_chars(&"hejsan".to_string()), 6); assert_eq!(conn.count_chars_str("hejsan"), 6); + assert!(conn.get_arg_passable_by_ref("count_chars", 0)); assert_eq!(conn.get_static_str(), "hello world"); + + assert_eq!(conn.deref_u32(&42), 42); + assert!(conn.get_arg_passable_by_ref("deref_u32", 0)); } #[test] diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index c676036..b93768e 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -663,7 +663,7 @@ This can be fixed with manual padding: } use savefile::prelude::*; impl WithSchema for MyPathBuf { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string((Default::default()))) } } @@ -1255,7 +1255,7 @@ impl From> for SavefileError { } impl WithSchema for PathBuf { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(VecOrStringLayout::Unknown)) } } @@ -1282,8 +1282,8 @@ impl Introspect for PathBuf { } impl<'a, T: 'a + WithSchema + ToOwned + ?Sized> WithSchema for Cow<'a, T> { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl<'a, T: 'a + ToOwned + ?Sized> ReprC for Cow<'a, T> {} @@ -1785,7 +1785,7 @@ impl<'a, W: Write + 'a> Serializer<'a, W> { writer, version, data, - Some(T::schema(version)), + Some(T::schema(version, &mut WithSchemaContext::new())), with_compression, )?) } @@ -2014,7 +2014,7 @@ impl<'a, TR: Read> Deserializer<'a, TR> { /// Don't use this method directly, use the [crate::load] function /// instead. pub fn load(reader: &mut TR, version: u32) -> Result { - Deserializer::<_>::load_impl::(reader, version, Some(|version| T::schema(version))) + Deserializer::<_>::load_impl::(reader, version, Some(|version| T::schema(version, &mut WithSchemaContext::new()))) } /// Deserialize an object of type T from the given reader. @@ -2232,6 +2232,62 @@ pub fn save_file_noschema>( Serializer::save_noschema::(&mut f, version, data) } + +/// Context object used to keep track of recursion. +/// Datastructures which cannot contain recursion do not need to concern themselves with +/// this. Recursive data structures in rust require the use of Box, Vec, Arc or similar. +/// The most common of these datatypes from std are supported by savefile, and will guard +/// against recursion in a well-defined way. +/// As a user of Savefile, you only need to use this if you are implementing Savefile for +/// container or smart-pointer type. +#[derive(Default)] +pub struct WithSchemaContext { + seen_types: HashMap, +} + +impl WithSchemaContext { + /// Create a new empty WithSchemaContext. + /// This is useful for calling ::schema at the top-level. + pub fn new() -> WithSchemaContext { + Default::default() + } +} + +impl WithSchemaContext { + /// Use this when returning the schema of a type that can be part of a recursion. + /// For example, given a hypothetical user-implemented type MyBox, do + /// + /// ```rust + /// use savefile::{Schema, WithSchema, WithSchemaContext}; + /// #[transparent] + /// struct MyBox { + /// content: *const T + /// } + /// impl WithSchema for MyBox { + /// fn schema(version: u32, context: &mut WithSchemaContext, context: &mut WithSchemaContext) -> Schema { + /// context.possible_recursion::>(|context| Schema::Boxed(T::schema(version, context))) + /// } + /// + /// } + /// ``` + pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { + let typeid = TypeId::of::(); + let prevlen = self.seen_types.len(); + match self.seen_types.entry(typeid) { + Entry::Occupied(occ) => { + let present_value_depth = *occ.get(); + return Schema::Recursion( prevlen - present_value_depth ); + } + Entry::Vacant(vac) => { + vac.insert(prevlen); + } + } + let ret = (cb)(self); + self.seen_types.remove(&typeid); + ret + } +} + /// This trait must be implemented by all data structures you wish to be able to save. /// It must encode the schema for the datastructure when saved using the given version number. /// When files are saved, the schema is encoded into the file. @@ -2241,7 +2297,7 @@ pub fn save_file_noschema>( /// can be disabled). pub trait WithSchema { /// Returns a representation of the schema used by this Serialize implementation for the given version. - fn schema(version: u32) -> Schema; + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema; } /// This trait must be implemented for all data structures you wish to be @@ -2640,6 +2696,7 @@ impl SchemaEnum { } } fn layout_compatible(&self, other: &SchemaEnum) -> bool { + if self.has_explicit_repr == false || other.has_explicit_repr == false { return false; } @@ -2825,7 +2882,7 @@ impl Deserialize for AbiMethodArgument { } impl WithSchema for AbiMethodArgument { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -2848,7 +2905,7 @@ pub struct AbiMethodInfo { } impl ReprC for AbiMethodInfo {} impl WithSchema for AbiMethodInfo { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -2881,7 +2938,7 @@ pub struct AbiMethod { } impl ReprC for AbiMethod {} impl WithSchema for AbiMethod { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -2912,7 +2969,7 @@ pub struct AbiTraitDefinition { } impl ReprC for AbiTraitDefinition {} impl WithSchema for AbiTraitDefinition { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3065,6 +3122,13 @@ pub enum Schema { /// Their memory layout is considered to depend on all method signatures, /// and the layouts of all argument types and all return types. FnClosure(bool /*mut self*/, AbiTraitDefinition), + /// The datastructure is recursive, and the datatype now continues from + /// the element that is 'depth' layers higher in the schema tree. + /// Note, the 'depth' only counts possible recursion points, i.e, objects + /// such as 'Box', 'Vec' etc. This works, since the schema will only ever match + /// if it is identical in memory and file, and because of this, counting + /// only the recursion points is non-ambiguous. + Recursion(usize/*depth*/) } /// Introspect is not implemented for Schema, though it could be impl Introspect for Schema { @@ -3080,23 +3144,24 @@ impl Introspect for Schema { impl Schema { /// Get a short description of the major type of this schema. /// 'struct', 'enum' etc. - pub fn top_level_description(&self) -> &'static str { + pub fn top_level_description(&self) -> String { match self { - Schema::Struct(_) => "struct", - Schema::Enum(_) => "enum", - Schema::Primitive(_) => "primitive", - Schema::Vector(_, _) => "vector", - Schema::Array(_) => "array", - Schema::SchemaOption(_) => "option", - Schema::Undefined => "undefined", - Schema::ZeroSize => "zerosize", - Schema::Custom(_) => "custom", - Schema::Boxed(_) => "box", - Schema::FnClosure(_, _) => "fntrait", - Schema::Slice(_) => "slice", - Schema::Str => "str", - Schema::Reference(_) => "reference", - Schema::Trait(_, _) => "trait", + Schema::Struct(_) => "struct".into(), + Schema::Enum(_) => "enum".into(), + Schema::Primitive(_) => "primitive".into(), + Schema::Vector(_, _) => "vector".into(), + Schema::Array(_) => "array".into(), + Schema::SchemaOption(_) => "option".into(), + Schema::Undefined => "undefined".into(), + Schema::ZeroSize => "zerosize".into(), + Schema::Custom(_) => "custom".into(), + Schema::Boxed(_) => "box".into(), + Schema::FnClosure(_, _) => "fntrait".into(), + Schema::Slice(_) => "slice".into(), + Schema::Str => "str".into(), + Schema::Reference(_) => "reference".into(), + Schema::Trait(_, _) => "trait".into(), + Schema::Recursion(depth) => {format!("",depth)} } } /// Determine if the two fields are laid out identically in memory, in their parent objects. @@ -3124,18 +3189,21 @@ impl Schema { // Closures are not supported in any other position false } - (Schema::Boxed(_a), Schema::Boxed(_b)) => { - // Boxed traits can never "just be serialized". We always have to serialize - // if boxed traits are contained in a data structure - false + (Schema::Boxed(a), Schema::Boxed(b)) => { + // The memory layout of boxes is guaranteed in practice (just a pointer) + // Trait pointers (which are fat) could conceivably differ, but we don't + // actually rely on memory layout compatibility for them, and this expression + // will also return false (since Schema::Trait 'layout_compatible' always returns false). + a.layout_compatible(&*b) } (Schema::Reference(a), Schema::Reference(b)) => a.layout_compatible(&*b), + (Schema::Slice(a), Schema::Slice(b)) => a.layout_compatible(&*b), _ => false, } } /// Create a 1-element tuple - pub fn new_tuple1(version: u32) -> Schema { - let schema = Box::new(T1::schema(version)); + pub fn new_tuple1(version: u32, context: &mut WithSchemaContext) -> Schema { + let schema = Box::new(T1::schema(version, context)); Schema::Struct(SchemaStruct { dbg_name: "1-Tuple".to_string(), size: Some(std::mem::size_of::<(T1,)>()), @@ -3149,7 +3217,7 @@ impl Schema { } /// Create a 2-element tuple - pub fn new_tuple2(version: u32) -> Schema { + pub fn new_tuple2(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "2-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2)>()), @@ -3157,19 +3225,19 @@ impl Schema { fields: vec![ Field { name: "0".to_string(), - value: Box::new(T1::schema(version)), + value: Box::new(T1::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2), 0)), }, Field { name: "1".to_string(), - value: Box::new(T2::schema(version)), + value: Box::new(T2::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2), 1)), }, ], }) } /// Create a 3-element tuple - pub fn new_tuple3(version: u32) -> Schema { + pub fn new_tuple3(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "3-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3)>()), @@ -3177,24 +3245,24 @@ impl Schema { fields: vec![ Field { name: "0".to_string(), - value: Box::new(T1::schema(version)), + value: Box::new(T1::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3), 0)), }, Field { name: "1".to_string(), - value: Box::new(T2::schema(version)), + value: Box::new(T2::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3), 1)), }, Field { name: "2".to_string(), - value: Box::new(T3::schema(version)), + value: Box::new(T3::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3), 2)), }, ], }) } /// Create a 4-element tuple - pub fn new_tuple4(version: u32) -> Schema { + pub fn new_tuple4(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "4-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3, T4)>()), @@ -3202,22 +3270,22 @@ impl Schema { fields: vec![ Field { name: "0".to_string(), - value: Box::new(T1::schema(version)), + value: Box::new(T1::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3, T4), 0)), }, Field { name: "1".to_string(), - value: Box::new(T2::schema(version)), + value: Box::new(T2::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3, T4), 1)), }, Field { name: "2".to_string(), - value: Box::new(T3::schema(version)), + value: Box::new(T3::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3, T4), 2)), }, Field { name: "3".to_string(), - value: Box::new(T4::schema(version)), + value: Box::new(T4::schema(version, context)), offset: Some(offset_of_tuple!((T1, T2, T3, T4), 3)), }, ], @@ -3241,6 +3309,7 @@ impl Schema { Schema::Str => None, Schema::Reference(_) => None, Schema::Trait(_, _) => None, + Schema::Recursion(_) => None, } } } @@ -3409,6 +3478,16 @@ pub fn diff_schema(a: &Schema, b: &Schema, path: String) -> Option { } return diff_abi_def(a, b, path); } + (Schema::Recursion(adepth), Schema::Recursion(bdepth)) => { + if adepth == bdepth { + return None; //Ok + } else { + return Some(format!( + "At location [{}]: Application protocol uses recursion up {} levels, but foreign format uses {}.", + path, adepth, bdepth + )); + } + } (a, b) => (a.top_level_description(), b.top_level_description()), }; @@ -3445,7 +3524,7 @@ fn diff_abi_def(a: &AbiTraitDefinition, b: &AbiTraitDefinition, path: String) -> } impl WithSchema for Field { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3473,7 +3552,7 @@ impl Deserialize for Field { } } impl WithSchema for Variant { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3530,13 +3609,13 @@ impl Deserialize for SchemaArray { } } impl WithSchema for SchemaArray { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } impl WithSchema for SchemaStruct { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3573,7 +3652,7 @@ impl Deserialize for SchemaStruct { } impl WithSchema for SchemaPrimitive { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3607,7 +3686,7 @@ impl Serialize for SchemaPrimitive { } } impl WithSchema for VecOrStringLayout { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3666,7 +3745,7 @@ impl Deserialize for SchemaPrimitive { } impl WithSchema for SchemaEnum { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3849,7 +3928,7 @@ impl Arbitrary for Schema { } impl WithSchema for Schema { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Undefined } } @@ -3921,6 +4000,11 @@ impl Serialize for Schema { b.serialize(serializer)?; Ok(()) } + Schema::Recursion(depth) => { + serializer.write_u8(16)?; + serializer.write_usize(*depth)?; + Ok(()) + } } } } @@ -3957,6 +4041,7 @@ impl Deserialize for Schema { <_ as Deserialize>::deserialize(deserializer)?, <_ as Deserialize>::deserialize(deserializer)?, ), + 16 => Schema::Recursion(<_ as Deserialize>::deserialize(deserializer)?), c => { return Err(SavefileError::GeneralError { msg: format!("Corrupt schema, schema variant {} encountered", c), @@ -3969,7 +4054,7 @@ impl Deserialize for Schema { } impl WithSchema for String { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(calculate_string_memory_layout())) } } @@ -4064,8 +4149,8 @@ impl Introspect for std::sync::Mutex { } impl WithSchema for std::sync::Mutex { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl ReprC for std::sync::Mutex {} @@ -4084,8 +4169,8 @@ impl Deserialize for std::sync::Mutex { #[cfg(feature = "parking_lot")] impl WithSchema for Mutex { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } @@ -4215,8 +4300,8 @@ impl Introspect for RwLock { #[cfg(feature = "parking_lot")] impl WithSchema for RwLock { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } @@ -4374,7 +4459,7 @@ impl Introspect for BTreeMap { } } impl WithSchema for BTreeMap { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { dbg_name: "KeyValuePair".to_string(), @@ -4383,12 +4468,12 @@ impl WithSchema for BTreeMap { fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version)), + value: Box::new(K::schema(version, context)), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version)), + value: Box::new(V::schema(version, context)), offset: None, }, ], @@ -4424,8 +4509,8 @@ impl Deserialize for BTreeMap { impl ReprC for HashSet {} impl WithSchema for HashSet { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(K::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(K::schema(version, context)), VecOrStringLayout::Unknown) } } impl Serialize for HashSet { @@ -4449,7 +4534,7 @@ impl Deserial } impl WithSchema for HashMap { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { dbg_name: "KeyValuePair".to_string(), @@ -4458,12 +4543,12 @@ impl With fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version)), + value: Box::new(K::schema(version, context)), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version)), + value: Box::new(V::schema(version, context)), offset: None, }, ], @@ -4499,7 +4584,7 @@ impl WithSchema for IndexMap { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { dbg_name: "KeyValuePair".to_string(), @@ -4508,12 +4593,12 @@ impl With fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version)), + value: Box::new(K::schema(version, context)), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version)), + value: Box::new(V::schema(version, context)), offset: None, }, ], @@ -4657,7 +4742,7 @@ impl ReprC for IndexSet {} #[cfg(feature = "indexmap")] impl WithSchema for IndexSet { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { dbg_name: "Key".to_string(), @@ -4665,7 +4750,7 @@ impl WithSchema for Inde alignment: None, fields: vec![Field { name: "key".to_string(), - value: Box::new(K::schema(version)), + value: Box::new(K::schema(version, context)), offset: None, }], })), @@ -4748,8 +4833,8 @@ impl Removed { } } impl WithSchema for Removed { - fn schema(version: u32) -> Schema { - ::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + ::schema(version, context) } } @@ -4810,8 +4895,8 @@ impl> AbiRemoved { } impl> WithSchema for AbiRemoved { - fn schema(version: u32) -> Schema { - ::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + ::schema(version, context) } } @@ -4855,7 +4940,7 @@ impl Introspect for PhantomData { } } impl WithSchema for std::marker::PhantomData { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::ZeroSize } } @@ -4912,8 +4997,8 @@ impl Introspect for Option { } impl WithSchema for Option { - fn schema(version: u32) -> Schema { - Schema::SchemaOption(Box::new(T::schema(version))) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::SchemaOption(Box::new(T::schema(version, context))) } } impl ReprC for Option {} //Sadly, Option does not allow the #"reprC"-optimization @@ -4962,7 +5047,7 @@ impl Introspect for Result { } impl WithSchema for Result { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Enum(SchemaEnum { dbg_name: "Result".to_string(), size: None, @@ -4973,7 +5058,7 @@ impl WithSchema for Result { discriminant: 0, fields: vec![Field { name: "ok".to_string(), - value: Box::new(T::schema(version)), + value: Box::new(T::schema(version, context)), offset: None, }], }, @@ -4982,7 +5067,7 @@ impl WithSchema for Result { discriminant: 0, fields: vec![Field { name: "err".to_string(), - value: Box::new(R::schema(version)), + value: Box::new(R::schema(version, context)), offset: None, }], }, @@ -5024,7 +5109,7 @@ compile_error!("savefile bit-vec feature does not support big-endian machines"); #[cfg(feature = "bit-vec")] impl WithSchema for bit_vec::BitVec { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "BitVec".to_string(), size: None, @@ -5032,18 +5117,18 @@ impl WithSchema for bit_vec::BitVec { fields: vec![ Field { name: "num_bits".to_string(), - value: Box::new(usize::schema(version)), + value: Box::new(usize::schema(version, context)), offset: None, }, Field { name: "num_bytes".to_string(), - value: Box::new(usize::schema(version)), + value: Box::new(usize::schema(version, context)), offset: None, }, Field { name: "buffer".to_string(), value: Box::new(Schema::Vector( - Box::new(u8::schema(version)), + Box::new(u8::schema(version, context)), VecOrStringLayout::Unknown, )), offset: None, @@ -5119,7 +5204,7 @@ impl Deserialize for bit_vec::BitVec { #[cfg(feature = "bit-set")] impl WithSchema for bit_set::BitSet { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "BitSet".to_string(), size: None, @@ -5127,18 +5212,18 @@ impl WithSchema for bit_set::BitSet { fields: vec![ Field { name: "num_bits".to_string(), - value: Box::new(usize::schema(version)), + value: Box::new(usize::schema(version, context)), offset: None, }, Field { name: "num_bytes".to_string(), - value: Box::new(usize::schema(version)), + value: Box::new(usize::schema(version, context)), offset: None, }, Field { name: "buffer".to_string(), value: Box::new(Schema::Vector( - Box::new(u8::schema(version)), + Box::new(u8::schema(version, context)), VecOrStringLayout::Unknown, )), offset: None, @@ -5207,8 +5292,8 @@ impl Introspect for BinaryHeap { impl ReprC for BinaryHeap {} impl WithSchema for BinaryHeap { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) } } impl Serialize for BinaryHeap { @@ -5259,8 +5344,8 @@ impl WithSchema for smallvec::SmallVec where T::Item: WithSchema, { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::Item::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::Item::schema(version, context)), VecOrStringLayout::Unknown) } } #[cfg(feature = "smallvec")] @@ -5327,13 +5412,13 @@ fn regular_serialize_vec( } impl WithSchema for Box<[T]> { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) } } impl WithSchema for Arc<[T]> { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) } } impl Introspect for Box<[T]> { @@ -5369,7 +5454,7 @@ impl Introspect for Arc<[T]> { } impl WithSchema for Arc { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(VecOrStringLayout::Unknown)) } } @@ -5455,7 +5540,7 @@ impl Deserialize for Box<[T]> { } } impl<'a> WithSchema for &'a str { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(calculate_string_memory_layout())) //TODO: This is _not_ the same memory layout as vec. Make a new Box type for slices? } @@ -5469,8 +5554,8 @@ impl<'a> Serialize for &'a str { } impl<'a, T: WithSchema> WithSchema for &'a [T] { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), calculate_slice_memory_layout::()) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), calculate_slice_memory_layout::()) //TODO: This is _not_ the same memory layout as vec. Make a new Box type for slices? } } @@ -5588,8 +5673,8 @@ fn calculate_string_memory_layout() -> VecOrStringLayout { return unsafe { std::mem::transmute(is_std) }; } impl WithSchema for Vec { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), calculate_vec_memory_layout::()) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), calculate_vec_memory_layout::()) } } @@ -5715,8 +5800,8 @@ impl Introspect for VecDeque { } impl WithSchema for VecDeque { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(T::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) } } @@ -5843,9 +5928,9 @@ impl ReprC for () { } impl WithSchema for [T; N] { - fn schema(version: u32) -> Schema { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Array(SchemaArray { - item_type: Box::new(T::schema(version)), + item_type: Box::new(T::schema(version, context)), count: N, }) } @@ -5921,8 +6006,8 @@ impl Deserialize for [T; N] { impl ReprC for Range {} impl WithSchema for Range { - fn schema(version: u32) -> Schema { - Schema::new_tuple2::(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::new_tuple2::(version, context) } } impl Serialize for Range { @@ -6010,8 +6095,8 @@ impl ReprC for (T1, T2, T3, T4) { } impl WithSchema for (T1, T2, T3) { - fn schema(version: u32) -> Schema { - Schema::new_tuple3::(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::new_tuple3::(version, context) } } impl Serialize for (T1, T2, T3) { @@ -6032,8 +6117,8 @@ impl Deserialize for (T1, T2, } impl WithSchema for (T1, T2) { - fn schema(version: u32) -> Schema { - Schema::new_tuple2::(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::new_tuple2::(version, context) } } impl Serialize for (T1, T2) { @@ -6049,8 +6134,8 @@ impl Deserialize for (T1, T2) { } impl WithSchema for (T1,) { - fn schema(version: u32) -> Schema { - Schema::new_tuple1::(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::new_tuple1::(version, context) } } impl Serialize for (T1,) { @@ -6069,7 +6154,7 @@ impl ReprC for arrayvec::ArrayString {} #[cfg(feature = "arrayvec")] impl WithSchema for arrayvec::ArrayString { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(VecOrStringLayout::Unknown)) } } @@ -6112,8 +6197,8 @@ impl Introspect for arrayvec::ArrayString { #[cfg(feature = "arrayvec")] impl WithSchema for arrayvec::ArrayVec { - fn schema(version: u32) -> Schema { - Schema::Vector(Box::new(V::schema(version)), VecOrStringLayout::Unknown) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + Schema::Vector(Box::new(V::schema(version, context)), VecOrStringLayout::Unknown) } } @@ -6188,18 +6273,19 @@ impl Deserialize for arrayvec::ArrayVec< } use std::ops::{Deref, Range}; -impl WithSchema for Box { - fn schema(version: u32) -> Schema { - T::schema(version) +impl WithSchema for Box { + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + context.possible_recursion::(|context|T::schema(version, context)) + } } impl ReprC for Box {} -impl Serialize for Box { +impl Serialize for Box { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Box { +impl Deserialize for Box { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Box::new(T::deserialize(deserializer)?)) } @@ -6209,8 +6295,8 @@ use std::rc::Rc; impl ReprC for Rc {} impl WithSchema for Rc { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl Serialize for Rc { @@ -6226,8 +6312,8 @@ impl Deserialize for Rc { impl ReprC for Arc {} impl WithSchema for Arc { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl Serialize for Arc { @@ -6245,6 +6331,7 @@ use bzip2::Compression; use std::any::{Any, TypeId}; use std::cell::Cell; use std::cell::RefCell; +use std::collections::hash_map::Entry; use std::convert::{TryFrom, TryInto}; use std::fmt::{Debug, Display, Formatter}; use std::marker::PhantomData; @@ -6258,8 +6345,8 @@ use memoffset::offset_of_tuple; impl ReprC for RefCell {} impl WithSchema for RefCell { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl Serialize for RefCell { @@ -6279,8 +6366,8 @@ impl ReprC for Cell { } } impl WithSchema for Cell { - fn schema(version: u32) -> Schema { - T::schema(version) + fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + T::schema(version, context) } } impl Serialize for Cell { @@ -6296,7 +6383,7 @@ impl Deserialize for Cell { } impl WithSchema for () { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::ZeroSize } } @@ -6478,52 +6565,52 @@ impl Introspect for AtomicIsize { } impl WithSchema for AtomicBool { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_bool) } } impl WithSchema for AtomicU8 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u8) } } impl WithSchema for AtomicI8 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i8) } } impl WithSchema for AtomicU16 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u16) } } impl WithSchema for AtomicI16 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i16) } } impl WithSchema for AtomicU32 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u32) } } impl WithSchema for AtomicI32 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i32) } } impl WithSchema for AtomicU64 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u64) } } impl WithSchema for AtomicI64 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i64) } } impl WithSchema for AtomicUsize { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { match std::mem::size_of::() { 4 => Schema::Primitive(SchemaPrimitive::schema_u32), 8 => Schema::Primitive(SchemaPrimitive::schema_u64), @@ -6532,7 +6619,7 @@ impl WithSchema for AtomicUsize { } } impl WithSchema for AtomicIsize { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { match std::mem::size_of::() { 4 => Schema::Primitive(SchemaPrimitive::schema_i32), 8 => Schema::Primitive(SchemaPrimitive::schema_i64), @@ -6542,67 +6629,67 @@ impl WithSchema for AtomicIsize { } impl WithSchema for bool { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_bool) } } impl WithSchema for u8 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u8) } } impl WithSchema for i8 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i8) } } impl WithSchema for u16 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u16) } } impl WithSchema for i16 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i16) } } impl WithSchema for u32 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u32) } } impl WithSchema for i32 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i32) } } impl WithSchema for u64 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u64) } } impl WithSchema for u128 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_u128) } } impl WithSchema for i128 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i128) } } impl WithSchema for i64 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_i64) } } impl WithSchema for char { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_char) } } impl WithSchema for usize { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { match std::mem::size_of::() { 4 => Schema::Primitive(SchemaPrimitive::schema_u32), 8 => Schema::Primitive(SchemaPrimitive::schema_u64), @@ -6611,7 +6698,7 @@ impl WithSchema for usize { } } impl WithSchema for isize { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { match std::mem::size_of::() { 4 => Schema::Primitive(SchemaPrimitive::schema_i32), 8 => Schema::Primitive(SchemaPrimitive::schema_i64), @@ -6620,12 +6707,12 @@ impl WithSchema for isize { } } impl WithSchema for f32 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_f32) } } impl WithSchema for f64 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_f64) } } @@ -7100,7 +7187,7 @@ impl Serialize for Canary1 { } impl ReprC for Canary1 {} impl WithSchema for Canary1 { - fn schema(_version: u32) -> Schema { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_canary1) } } diff --git a/savefile/src/prelude.rs b/savefile/src/prelude.rs index 281e229..bfb560a 100644 --- a/savefile/src/prelude.rs +++ b/savefile/src/prelude.rs @@ -5,7 +5,7 @@ pub use { super::Deserializer, super::Field, super::Introspect, super::IntrospectItem, super::IntrospectedElementKey, super::IntrospectionResult, super::Introspector, super::IntrospectorNavCommand, super::IsReprC, super::Removed, super::ReprC, super::SavefileError, super::Schema, super::SchemaEnum, super::SchemaPrimitive, super::SchemaStruct, - super::Serialize, super::Serializer, super::Variant, super::WithSchema, + super::Serialize, super::Serializer, super::Variant, super::WithSchema, super::WithSchemaContext, }; pub use byteorder::{LittleEndian, ReadBytesExt}; From 673864371ead4b500bc7fb59dd82fc160004334f Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Mon, 29 Apr 2024 23:59:19 +0200 Subject: [PATCH 02/15] fix: Remove compile_tests from release-plz config --- release-plz.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/release-plz.toml b/release-plz.toml index 997d7a9..90a3956 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -18,11 +18,6 @@ name= "savefile-abi" publish = true release = true -[[package]] -name= "compile_tests" -publish = false -release = false - [[package]] name= "savefile-test" publish = false From 367fad6cb3bf790b7015db11fd0acca3cce7d5f7 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 00:04:04 +0200 Subject: [PATCH 03/15] Enable github-releases again --- release-plz.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-plz.toml b/release-plz.toml index 90a3956..2ec78fa 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -1,5 +1,5 @@ [workspace] -git_release_enable = false +#git_release_enable = false [[package]] name= "savefile" From f9a065c04dcb0f08f3f240123ebf3321e66bcf8f Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 00:18:44 +0200 Subject: [PATCH 04/15] wip --- savefile/src/lib.rs | 134 ++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index b93768e..c8d0f1e 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -2259,13 +2259,13 @@ impl WithSchemaContext { /// /// ```rust /// use savefile::{Schema, WithSchema, WithSchemaContext}; - /// #[transparent] + /// #[repr(transparent)] /// struct MyBox { /// content: *const T /// } - /// impl WithSchema for MyBox { - /// fn schema(version: u32, context: &mut WithSchemaContext, context: &mut WithSchemaContext) -> Schema { - /// context.possible_recursion::>(|context| Schema::Boxed(T::schema(version, context))) + /// impl WithSchema for MyBox { + /// fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { + /// context.possible_recursion::(|context| Schema::Boxed(Box::new(T::schema(version, context)))) /// } /// /// } @@ -4458,7 +4458,7 @@ impl Introspect for BTreeMap { self.len() } } -impl WithSchema for BTreeMap { +impl WithSchema for BTreeMap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4468,12 +4468,12 @@ impl WithSchema for BTreeMap { fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), offset: None, }, ], @@ -4483,7 +4483,7 @@ impl WithSchema for BTreeMap { } } impl ReprC for BTreeMap {} -impl Serialize for BTreeMap { +impl Serialize for BTreeMap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.len().serialize(serializer)?; for (k, v) in self { @@ -4493,7 +4493,7 @@ impl Serialize for BTreeMap { Ok(()) } } -impl Deserialize for BTreeMap { +impl Deserialize for BTreeMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let mut ret = BTreeMap::new(); let count = ::deserialize(deserializer)?; @@ -4508,12 +4508,12 @@ impl Deserialize for BTreeMap { } impl ReprC for HashSet {} -impl WithSchema for HashSet { +impl WithSchema for HashSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(K::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|K::schema(version, context))), VecOrStringLayout::Unknown) } } -impl Serialize for HashSet { +impl Serialize for HashSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for item in self { @@ -4522,7 +4522,7 @@ impl Serialize for HashSet { Ok(()) } } -impl Deserialize for HashSet { +impl Deserialize for HashSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let cnt = deserializer.read_usize()?; let mut ret = HashSet::with_capacity_and_hasher(cnt, S::default()); @@ -4533,7 +4533,7 @@ impl Deserial } } -impl WithSchema for HashMap { +impl WithSchema for HashMap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4543,12 +4543,12 @@ impl With fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), offset: None, }, ], @@ -4558,7 +4558,7 @@ impl With } } impl ReprC for HashMap {} -impl Serialize for HashMap { +impl Serialize for HashMap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4569,7 +4569,7 @@ impl Serial } } -impl Deserialize +impl Deserialize for HashMap { fn deserialize(deserializer: &mut Deserializer) -> Result { @@ -4583,7 +4583,7 @@ impl WithSchema for IndexMap { +impl WithSchema for IndexMap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4593,12 +4593,12 @@ impl With fields: vec![ Field { name: "key".to_string(), - value: Box::new(K::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(V::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), offset: None, }, ], @@ -4695,7 +4695,7 @@ where impl ReprC for IndexMap {} #[cfg(feature = "indexmap")] -impl Serialize for IndexMap { +impl Serialize for IndexMap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4707,7 +4707,7 @@ impl Serial } #[cfg(feature = "indexmap")] -impl Deserialize for IndexMap { +impl Deserialize for IndexMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexMap::with_capacity(l); @@ -4741,7 +4741,7 @@ impl Introspect for Inde impl ReprC for IndexSet {} #[cfg(feature = "indexmap")] -impl WithSchema for IndexSet { +impl WithSchema for IndexSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4750,7 +4750,7 @@ impl WithSchema for Inde alignment: None, fields: vec![Field { name: "key".to_string(), - value: Box::new(K::schema(version, context)), + value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), offset: None, }], })), @@ -4760,7 +4760,7 @@ impl WithSchema for Inde } #[cfg(feature = "indexmap")] -impl Serialize for IndexSet { +impl Serialize for IndexSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for k in self.iter() { @@ -4771,7 +4771,7 @@ impl Serialize for IndexS } #[cfg(feature = "indexmap")] -impl Deserialize for IndexSet { +impl Deserialize for IndexSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexSet::with_capacity(l); @@ -5291,12 +5291,12 @@ impl Introspect for BinaryHeap { } impl ReprC for BinaryHeap {} -impl WithSchema for BinaryHeap { +impl WithSchema for BinaryHeap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) } } -impl Serialize for BinaryHeap { +impl Serialize for BinaryHeap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { let l = self.len(); serializer.write_usize(l)?; @@ -5306,7 +5306,7 @@ impl Serialize for BinaryHeap { Ok(()) } } -impl Deserialize for BinaryHeap { +impl Deserialize for BinaryHeap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = BinaryHeap::with_capacity(l); @@ -5340,19 +5340,19 @@ where } #[cfg(feature = "smallvec")] -impl WithSchema for smallvec::SmallVec +impl WithSchema for smallvec::SmallVec where T::Item: WithSchema, { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::Item::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::Item::schema(version, context))), VecOrStringLayout::Unknown) } } #[cfg(feature = "smallvec")] impl ReprC for smallvec::SmallVec {} #[cfg(feature = "smallvec")] -impl Serialize for smallvec::SmallVec +impl Serialize for smallvec::SmallVec where T::Item: Serialize, { @@ -5366,7 +5366,7 @@ where } } #[cfg(feature = "smallvec")] -impl Deserialize for smallvec::SmallVec +impl Deserialize for smallvec::SmallVec where T::Item: Deserialize, { @@ -5411,14 +5411,14 @@ fn regular_serialize_vec( } } -impl WithSchema for Box<[T]> { +impl WithSchema for Box<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) } } -impl WithSchema for Arc<[T]> { +impl WithSchema for Arc<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) } } impl Introspect for Box<[T]> { @@ -5493,7 +5493,7 @@ impl Deserialize for Arc { } } -impl Serialize for Box<[T]> { +impl Serialize for Box<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5511,7 +5511,7 @@ impl Serialize for Box<[T]> { } impl ReprC for Box<[T]> {} -impl Serialize for Arc<[T]> { +impl Serialize for Arc<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5529,12 +5529,12 @@ impl Serialize for Arc<[T]> { } impl ReprC for Arc<[T]> {} -impl Deserialize for Arc<[T]> { +impl Deserialize for Arc<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into()) } } -impl Deserialize for Box<[T]> { +impl Deserialize for Box<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into_boxed_slice()) } @@ -5553,13 +5553,13 @@ impl<'a> Serialize for &'a str { } } -impl<'a, T: WithSchema> WithSchema for &'a [T] { +impl<'a, T: WithSchema+'static> WithSchema for &'a [T] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), calculate_slice_memory_layout::()) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_slice_memory_layout::()) //TODO: This is _not_ the same memory layout as vec. Make a new Box type for slices? } } -impl<'a, T: Serialize + ReprC> Serialize for &'a [T] { +impl<'a, T: Serialize + ReprC+'static> Serialize for &'a [T] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5579,7 +5579,7 @@ impl<'a, T: Serialize + ReprC> Serialize for &'a [T] { /// Deserialize a slice into a Vec /// Unsized slices cannot be deserialized into unsized slices. -pub fn deserialize_slice_as_vec( +pub fn deserialize_slice_as_vec( deserializer: &mut Deserializer, ) -> Result, SavefileError> { Vec::deserialize(deserializer) @@ -5672,9 +5672,9 @@ fn calculate_string_memory_layout() -> VecOrStringLayout { STRING_IS_STANDARD_LAYOUT.store(is_std, Ordering::Relaxed); return unsafe { std::mem::transmute(is_std) }; } -impl WithSchema for Vec { +impl WithSchema for Vec { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), calculate_vec_memory_layout::()) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_vec_memory_layout::()) } } @@ -5694,7 +5694,7 @@ impl Introspect for Vec { } } -impl Serialize for Vec { +impl Serialize for Vec { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5731,7 +5731,7 @@ fn regular_deserialize_vec( Ok(ret) } -impl Deserialize for Vec { +impl Deserialize for Vec { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { Ok(regular_deserialize_vec(deserializer)?) @@ -5799,20 +5799,20 @@ impl Introspect for VecDeque { } } -impl WithSchema for VecDeque { +impl WithSchema for VecDeque { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(T::schema(version, context)), VecOrStringLayout::Unknown) + Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) } } impl ReprC for VecDeque {} -impl Serialize for VecDeque { +impl Serialize for VecDeque { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { regular_serialize_vecdeque(self, serializer) } } -impl Deserialize for VecDeque { +impl Deserialize for VecDeque { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(regular_deserialize_vecdeque(deserializer)?) } @@ -5927,10 +5927,10 @@ impl ReprC for () { } } -impl WithSchema for [T; N] { +impl WithSchema for [T; N] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Array(SchemaArray { - item_type: Box::new(T::schema(version, context)), + item_type: Box::new(context.possible_recursion::(|context|T::schema(version, context))), count: N, }) } @@ -5955,7 +5955,7 @@ impl ReprC for [T; N] { T::repr_c_optimization_safe(version) } } -impl Serialize for [T; N] { +impl Serialize for [T; N] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5973,7 +5973,7 @@ impl Serialize for [T; N] { } } -impl Deserialize for [T; N] { +impl Deserialize for [T; N] { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { let mut data: [MaybeUninit; N] = unsafe { @@ -6294,34 +6294,34 @@ impl Deserialize for Box { use std::rc::Rc; impl ReprC for Rc {} -impl WithSchema for Rc { +impl WithSchema for Rc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - T::schema(version, context) + context.possible_recursion::(|context|T::schema(version, context)) } } -impl Serialize for Rc { +impl Serialize for Rc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Rc { +impl Deserialize for Rc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Rc::new(T::deserialize(deserializer)?)) } } impl ReprC for Arc {} -impl WithSchema for Arc { +impl WithSchema for Arc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - T::schema(version, context) + context.possible_recursion::(|context|T::schema(version, context)) } } -impl Serialize for Arc { +impl Serialize for Arc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Arc { +impl Deserialize for Arc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Arc::new(T::deserialize(deserializer)?)) } From 4843f8aca538f40dd3e62f8b5bb5e33532e71ddf Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:22:37 +0200 Subject: [PATCH 05/15] fix: Bad cycles detection --- savefile-derive/src/lib.rs | 2 +- savefile-derive/src/savefile_abi.rs | 8 ++-- savefile-min-build/src/lib.rs | 1 + savefile-test/src/cycles.rs | 48 +++++++------------ savefile-test/src/lib.rs | 1 - .../argument_backward_compatibility.rs | 4 +- savefile/src/lib.rs | 22 ++++++--- savefile/src/prelude.rs | 2 +- 8 files changed, 41 insertions(+), 47 deletions(-) diff --git a/savefile-derive/src/lib.rs b/savefile-derive/src/lib.rs index 92a5a25..9881dd6 100644 --- a/savefile-derive/src/lib.rs +++ b/savefile-derive/src/lib.rs @@ -253,7 +253,7 @@ pub fn savefile_abi_exportable( let uses = quote_spanned! { defspan => extern crate savefile; extern crate savefile_abi; - use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, WithSchemaContext, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; + use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, get_schema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; use savefile_abi::{parse_return_value_impl,abi_result_receiver,abi_boxed_trait_receiver, FlexBuffer, AbiExportable, TraitObject, PackagedTraitObject, Owning, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, AbiProtocol, abi_entry_light}; use std::collections::HashMap; use std::mem::MaybeUninit; diff --git a/savefile-derive/src/savefile_abi.rs b/savefile-derive/src/savefile_abi.rs index 13cfcb0..eb9b9d0 100644 --- a/savefile-derive/src/savefile_abi.rs +++ b/savefile-derive/src/savefile_abi.rs @@ -685,7 +685,7 @@ impl ArgType { caller_arg_serializer1: quote! { #arg_name.serialize(&mut serializer) }, - schema: quote!( <#arg_type as WithSchema>::schema(version, context) ), + schema: quote!( get_schema::<#arg_type>(version) ), known_size_align1: if compile_time_check_reprc(arg_type) { compile_time_size(arg_type) } else { @@ -938,7 +938,7 @@ pub(super) fn generate_method_definitions( //let can_be_sent_as_ref = instruction.can_be_sent_as_ref; metadata_arguments.push(quote! { AbiMethodArgument { - schema: { let mut context = WithSchemaContext::new(); let context = &mut context; #schema }, + schema: #schema, } }); if let Some(total_size) = &mut compile_time_known_size { @@ -975,7 +975,7 @@ pub(super) fn generate_method_definitions( let result_default; let return_ser_temp; if no_return { - return_value_schema = quote!(<() as WithSchema>::schema(0, &mut WithSchemaContext::new())); + return_value_schema = quote!(get_schema::<()>(0)); ret_deserializer = quote!(()); //Zero-sized, no deserialize actually needed ret_serialize = quote!(()); caller_return_type = quote!(()); @@ -1117,7 +1117,7 @@ pub(super) fn generate_method_definitions( AbiMethod { name: #method_name_str.to_string(), info: AbiMethodInfo { - return_value: { let mut context = WithSchemaContext::new(); let context = &mut context; #return_value_schema}, + return_value: #return_value_schema, arguments: vec![ #(#metadata_arguments,)* ], } } diff --git a/savefile-min-build/src/lib.rs b/savefile-min-build/src/lib.rs index 91af8c8..bbf7653 100644 --- a/savefile-min-build/src/lib.rs +++ b/savefile-min-build/src/lib.rs @@ -1,6 +1,7 @@ extern crate savefile_abi; extern crate savefile_derive; +#[cfg(test)] use savefile_abi::AbiConnection; use savefile_derive::savefile_abi_exportable; diff --git a/savefile-test/src/cycles.rs b/savefile-test/src/cycles.rs index e776978..6a9c6d2 100644 --- a/savefile-test/src/cycles.rs +++ b/savefile-test/src/cycles.rs @@ -1,5 +1,5 @@ use assert_roundtrip; -use savefile::Removed; +use savefile::{get_schema, Removed, WithSchema, WithSchemaContext}; #[derive(Savefile, Debug, PartialEq)] enum Tree { @@ -32,56 +32,40 @@ pub fn test_cyclic2() { let example = Tree2::Node(TreeNode{tree: Box::new(Tree2::Leaf("hej".into()))}); assert_roundtrip(example); } + #[derive(Savefile, Debug, PartialEq)] -enum Version1LevelD { - Leaf, - Node(Box) -} +struct Version1LevelC(Box); #[derive(Savefile, Debug, PartialEq)] -enum Version1LevelC { - Leaf, - Node(Box) -} +struct Version1LevelB(Box); #[derive(Savefile, Debug, PartialEq)] -enum Version1LevelB { - Leaf(Box), - Node(Box) -} +struct Version1LevelA(Option>); #[derive(Savefile, Debug, PartialEq)] -enum Version1LevelA { - Leaf, - Node(Box) -} +struct Version1Base(Option>); + #[derive(Savefile, Debug, PartialEq)] -enum Version2LevelC { - Leaf, - Node(Box) -} +struct Version2LevelC(Box); #[derive(Savefile, Debug, PartialEq)] -enum Version2LevelB { - Leaf(Box), - Node(Box) -} +struct Version2LevelB(Box); #[derive(Savefile, Debug, PartialEq)] -enum Version2LevelA { - Leaf, - Node(Box) -} +struct Version2LevelA(Option>); +#[derive(Savefile, Debug, PartialEq)] +struct Version2Base(Option>); + #[test] -#[should_panic(expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.Version1LevelA/Node/0Version1LevelB/Leaf/0Version1LevelC/Node/0Version1LevelD/Node/0]: In memory schema: , file schema: enum")] +#[should_panic(expected = "Saved schema differs from in-memory schema for version 0. Error: At location [./Version1Base/0/?/Version1LevelA/0/?/Version1LevelB/0/Version1LevelC/0]: Application protocol uses recursion up 3 levels, but foreign format uses 2")] fn cycles_vertest1() { use assert_roundtrip_to_new_version; assert_roundtrip_to_new_version( - Version1LevelA::Leaf, + Version1Base(None), 0, - Version2LevelA::Leaf, + Version2Base(None), 1, ); } \ No newline at end of file diff --git a/savefile-test/src/lib.rs b/savefile-test/src/lib.rs index 25f9df9..f14135d 100644 --- a/savefile-test/src/lib.rs +++ b/savefile-test/src/lib.rs @@ -60,7 +60,6 @@ use std::io::Cursor; pub fn assert_roundtrip(sample: E) { assert_roundtrip_version(sample, 0, true) } - pub fn assert_roundtrip_version(sample: E, version: u32, schema: bool) { let mut f = Cursor::new(Vec::new()); { diff --git a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs index 5927c96..fa72f81 100644 --- a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs +++ b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs @@ -1,5 +1,5 @@ use savefile::prelude::AbiRemoved; -use savefile::{SavefileError, WithSchemaContext}; +use savefile::{get_schema, SavefileError, WithSchemaContext}; use savefile_abi::RawAbiCallResult::AbiError; use savefile_abi::{verify_compatiblity, AbiConnection, AbiExportable}; use savefile_abi_test::argument_backward_compatibility::v1::{ArgInterfaceV1, EnumArgument, Implementation1}; @@ -98,7 +98,7 @@ pub fn test_backward_compatibility() -> Result<(), SavefileError> { #[test] pub fn test_arg_argument_metadata() { use savefile::WithSchema; - let schema = v2::ArgArgument::schema(0, &mut WithSchemaContext::new()); + let schema = get_schema::(0); println!("Schema: {:#?}", schema); assert!(!schema.layout_compatible(&schema)); //Versions containing removed items should never be considered layout compatible (since their schema type is not identical to the memory type) } diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index c8d0f1e..25fd45b 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -861,6 +861,7 @@ extern crate serde; extern crate serde_derive; use core::str::Utf8Error; +use std::any::TypeId; #[cfg(feature = "serde_derive")] use serde_derive::{Deserialize, Serialize}; @@ -2240,7 +2241,6 @@ pub fn save_file_noschema>( /// against recursion in a well-defined way. /// As a user of Savefile, you only need to use this if you are implementing Savefile for /// container or smart-pointer type. -#[derive(Default)] pub struct WithSchemaContext { seen_types: HashMap, } @@ -2249,7 +2249,10 @@ impl WithSchemaContext { /// Create a new empty WithSchemaContext. /// This is useful for calling ::schema at the top-level. pub fn new() -> WithSchemaContext { - Default::default() + let seen_types = HashMap::new(); + WithSchemaContext { + seen_types + } } } @@ -2265,7 +2268,7 @@ impl WithSchemaContext { /// } /// impl WithSchema for MyBox { /// fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - /// context.possible_recursion::(|context| Schema::Boxed(Box::new(T::schema(version, context)))) + /// context.possible_recursion::>(|context| Schema::Boxed(Box::new(T::schema(version, context)))) /// } /// /// } @@ -2300,6 +2303,13 @@ pub trait WithSchema { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema; } +/// Create a new WithSchemaContext, and then call 'schema' on type T. +/// This is a useful convenience method. +pub fn get_schema(version: u32) -> Schema { + T::schema(version, &mut WithSchemaContext::new()) +} + + /// This trait must be implemented for all data structures you wish to be /// able to serialize. To actually serialize data: create a [Serializer], /// then call serialize on your data to save, giving the Serializer @@ -2643,14 +2653,14 @@ impl SchemaEnum { /// Arguments: /// /// * dbg_name - Name of the enum type. - /// * variants - The variants of the enum /// * discriminant_size: /// If this is a repr(uX)-enum, then the size of the discriminant, in bytes. /// Valid values are 1, 2 or 4. /// Otherwise, this is the number of bytes needed to represent the discriminant. /// In either case, this is the size of the enum in the disk-format. + /// * variants - The variants of the enum /// - pub fn new(dbg_name: String, variants: Vec, discriminant_size: u8) -> SchemaEnum { + pub fn new(dbg_name: String, discriminant_size: u8, variants: Vec) -> SchemaEnum { SchemaEnum { dbg_name, variants, @@ -6328,7 +6338,7 @@ impl Deserialize for Arc { } #[cfg(feature = "bzip2")] use bzip2::Compression; -use std::any::{Any, TypeId}; +use std::any::{Any}; use std::cell::Cell; use std::cell::RefCell; use std::collections::hash_map::Entry; diff --git a/savefile/src/prelude.rs b/savefile/src/prelude.rs index bfb560a..1b52535 100644 --- a/savefile/src/prelude.rs +++ b/savefile/src/prelude.rs @@ -5,7 +5,7 @@ pub use { super::Deserializer, super::Field, super::Introspect, super::IntrospectItem, super::IntrospectedElementKey, super::IntrospectionResult, super::Introspector, super::IntrospectorNavCommand, super::IsReprC, super::Removed, super::ReprC, super::SavefileError, super::Schema, super::SchemaEnum, super::SchemaPrimitive, super::SchemaStruct, - super::Serialize, super::Serializer, super::Variant, super::WithSchema, super::WithSchemaContext, + super::Serialize, super::Serializer, super::Variant, super::WithSchema, super::WithSchemaContext, super::get_schema }; pub use byteorder::{LittleEndian, ReadBytesExt}; From f4b920c55624401fd494a8d63d4eeefeb3303968 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:25:33 +0200 Subject: [PATCH 06/15] chore: release (#45) * chore: release * Update CHANGELOG.md * Update Cargo.toml * Update CHANGELOG.md --- Cargo.lock | 6 +++--- savefile-abi/CHANGELOG.md | 7 ++++++- savefile-abi/Cargo.toml | 6 +++--- savefile-derive/CHANGELOG.md | 5 +++++ savefile-derive/Cargo.toml | 2 +- savefile-test/Cargo.toml | 2 +- savefile/CHANGELOG.md | 5 +++++ savefile/Cargo.toml | 6 +++--- 8 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45e1f31..4bf4922 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "savefile" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" dependencies = [ "arrayvec", "bit-set", @@ -471,7 +471,7 @@ dependencies = [ [[package]] name = "savefile-abi" -version = "0.17.0-beta.13" +version = "0.17.1" dependencies = [ "byteorder", "libloading", @@ -511,7 +511,7 @@ dependencies = [ [[package]] name = "savefile-derive" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/savefile-abi/CHANGELOG.md b/savefile-abi/CHANGELOG.md index 34c17e4..c9018c7 100644 --- a/savefile-abi/CHANGELOG.md +++ b/savefile-abi/CHANGELOG.md @@ -6,7 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.17.-beta.13](https://github.com/avl/savefile/compare/savefile-abi-v0.17.0-beta.12...savefile-abi-v0.17.0-beta.13) - 2024-04-27 +## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-abi-v0.17.0-beta.13...savefile-abi-v0.17.0-beta.14) - 2024-04-30 + +### Other +- updated the following local packages: savefile, savefile-derive + +## [0.17.0-beta.13](https://github.com/avl/savefile/compare/savefile-abi-v0.17.0-beta.12...savefile-abi-v0.17.0-beta.13) - 2024-04-27 ### Other - updated the following local packages: savefile diff --git a/savefile-abi/Cargo.toml b/savefile-abi/Cargo.toml index 21156da..c7100c9 100644 --- a/savefile-abi/Cargo.toml +++ b/savefile-abi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile-abi" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" edition = "2021" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile-abi/" @@ -16,7 +16,7 @@ keywords = ["dylib", "dlopen", "ffi"] license = "MIT/Apache-2.0" [dependencies] -savefile = { path="../savefile", version = "=0.17.0-beta.13" } -savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.13" } +savefile = { path="../savefile", version = "=0.17.0-beta.14" } +savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.14" } byteorder = "1.4" libloading = "0.8" diff --git a/savefile-derive/CHANGELOG.md b/savefile-derive/CHANGELOG.md index 92047a1..118a115 100644 --- a/savefile-derive/CHANGELOG.md +++ b/savefile-derive/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-derive-v0.17.0-beta.13...savefile-derive-v0.17.0-beta.14) - 2024-04-30 + +### Fixed +- Bad cycles detection + ## [0.17.0-beta.12](https://github.com/avl/savefile/compare/savefile-derive-v0.17.0-beta.11...savefile-derive-v0.17.0-beta.12) - 2024-04-27 ### Other diff --git a/savefile-derive/Cargo.toml b/savefile-derive/Cargo.toml index e06843c..0100607 100644 --- a/savefile-derive/Cargo.toml +++ b/savefile-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile-derive" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" authors = ["Anders Musikka "] description = "Custom derive macros for savefile crate - simple, convenient, fast, versioned, binary serialization/deserialization library." diff --git a/savefile-test/Cargo.toml b/savefile-test/Cargo.toml index 3c67e67..f2d9279 100644 --- a/savefile-test/Cargo.toml +++ b/savefile-test/Cargo.toml @@ -12,7 +12,7 @@ nightly=["savefile/nightly"] [dependencies] savefile = { path = "../savefile", features = ["size_sanity_checks", "encryption", "compression","bit-set","bit-vec","rustc-hash","serde_derive", "quickcheck"]} -savefile-derive = { path = "../savefile-derive", version = "=0.17.0-beta.13" } +savefile-derive = { path = "../savefile-derive", version = "=0.17.0-beta.14" } savefile-abi = { path = "../savefile-abi" } bit-vec = "0.6" arrayvec="0.7" diff --git a/savefile/CHANGELOG.md b/savefile/CHANGELOG.md index 3fe8e66..a96abde 100644 --- a/savefile/CHANGELOG.md +++ b/savefile/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-v0.17.0-beta.13...savefile-v0.17.0-beta.14) - 2024-04-30 + +### Fixed +- Bad cycles detection + ## [0.17.0-beta.13](https://github.com/avl/savefile/compare/savefile-v0.17.0-beta.12...savefile-v0.17.0-beta.13) - 2024-04-27 ### Added diff --git a/savefile/Cargo.toml b/savefile/Cargo.toml index e83d164..fc2b2fe 100644 --- a/savefile/Cargo.toml +++ b/savefile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile/" homepage = "https://github.com/avl/savefile/" @@ -54,13 +54,13 @@ bit-set = {version = "0.5", optional = true} rustc-hash = {version = "1.1", optional = true} memoffset = "0.9" byteorder = "1.4" -savefile-derive = {path="../savefile-derive", version = "=0.17.0-beta.13", optional = true } +savefile-derive = {path="../savefile-derive", version = "=0.17.0-beta.14", optional = true } serde_derive = {version= "1.0", optional = true} serde = {version= "1.0", optional = true} quickcheck = {version= "1.0", optional = true} [dev-dependencies] -savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.13" } +savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.14" } [build-dependencies] rustc_version="0.2" From ce9641b2dfe7a443e7f8729be570c970afd812bd Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:26:31 +0200 Subject: [PATCH 07/15] Format --- savefile-abi/src/lib.rs | 33 +-- savefile-derive/src/lib.rs | 62 ++++-- savefile-derive/src/savefile_abi.rs | 69 +++--- savefile-min-build/src/lib.rs | 7 +- savefile-test/src/cycles.rs | 31 ++- .../advanced_datatypes_test.rs | 4 +- savefile/src/lib.rs | 204 +++++++++++------- savefile/src/prelude.rs | 15 +- 8 files changed, 258 insertions(+), 167 deletions(-) diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index abd939b..35a15eb 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -468,12 +468,8 @@ pub struct TraitObject { vtable: *const (), } -unsafe impl Sync for TraitObject { - -} -unsafe impl Send for TraitObject { - -} +unsafe impl Sync for TraitObject {} +unsafe impl Send for TraitObject {} impl TraitObject { /// Returns a TraitObject with two null ptrs. This value must never be used, @@ -617,12 +613,8 @@ pub struct AbiConnection { #[doc(hidden)] pub phantom: PhantomData<*const T>, } -unsafe impl Sync for AbiConnection{ - -} -unsafe impl Send for AbiConnection{ - -} +unsafe impl Sync for AbiConnection {} +unsafe impl Send for AbiConnection {} /// A trait object together with its entry point #[repr(C)] @@ -1324,15 +1316,24 @@ impl AbiConnection { /// of the code being called into. It will not change during the lifetime of an /// AbiConnector, but it may change if the target library is recompiled. pub fn get_arg_passable_by_ref(&self, method: &str, arg: usize) -> bool { - if let Some(found) = self.template.methods.iter().find(|var|var.method_name == method) { + if let Some(found) = self.template.methods.iter().find(|var| var.method_name == method) { let abi_method: &AbiConnectionMethod = found; if arg >= abi_method.caller_info.arguments.len() { - panic!("Method '{}' has only {} arguments, so there is no argument #{}", method, abi_method.caller_info.arguments.len(), arg); + panic!( + "Method '{}' has only {} arguments, so there is no argument #{}", + method, + abi_method.caller_info.arguments.len(), + arg + ); } (abi_method.compatibility_mask & (1 << (arg as u64))) != 0 } else { - let arg_names : Vec<_> = self.template.methods.iter().map(|x|x.method_name.as_str()).collect(); - panic!("Trait has no method with name '{}'. Available methods: {}", method, arg_names.join(", ")); + let arg_names: Vec<_> = self.template.methods.iter().map(|x| x.method_name.as_str()).collect(); + panic!( + "Trait has no method with name '{}'. Available methods: {}", + method, + arg_names.join(", ") + ); } } diff --git a/savefile-derive/src/lib.rs b/savefile-derive/src/lib.rs index 9881dd6..d059324 100644 --- a/savefile-derive/src/lib.rs +++ b/savefile-derive/src/lib.rs @@ -36,7 +36,10 @@ use syn::__private::bool; use syn::spanned::Spanned; use syn::token::Paren; use syn::Type::Tuple; -use syn::{DeriveInput, FnArg, GenericParam, Generics, Ident, ImplGenerics, Index, ItemTrait, Pat, ReturnType, TraitItem, Type, TypeGenerics, TypeParamBound, TypeTuple}; +use syn::{ + DeriveInput, FnArg, GenericParam, Generics, Ident, ImplGenerics, Index, ItemTrait, Pat, ReturnType, TraitItem, + Type, TypeGenerics, TypeParamBound, TypeTuple, +}; fn implement_fields_serialize( field_infos: Vec, implicit_self: bool, @@ -266,7 +269,10 @@ pub fn savefile_abi_exportable( let mut extra_definitions = HashMap::new(); if parsed.generics.params.is_empty() == false { - abort!(parsed.generics.params.span(), "Savefile does not support generic traits."); + abort!( + parsed.generics.params.span(), + "Savefile does not support generic traits." + ); } for supertrait in parsed.supertraits.iter() { match supertrait { @@ -293,7 +299,10 @@ pub fn savefile_abi_exportable( } if parsed.generics.where_clause.is_some() { - abort!(parsed.generics.where_clause.span(), "Savefile does not support where-clauses for traits"); + abort!( + parsed.generics.where_clause.span(), + "Savefile does not support where-clauses for traits" + ); } for (method_number, item) in parsed.items.iter().enumerate() { @@ -312,7 +321,10 @@ pub fn savefile_abi_exportable( } TraitItem::Method(method) => { if method.sig.generics.where_clause.is_some() { - abort!(method.sig.generics.where_clause.span(), "Savefile does not support where-clauses for methods"); + abort!( + method.sig.generics.where_clause.span(), + "Savefile does not support where-clauses for methods" + ); } let method_name = method.sig.ident.clone(); //let method_name_str = method.sig.ident.to_string(); @@ -392,13 +404,22 @@ pub fn savefile_abi_exportable( } } if method.sig.asyncness.is_some() { - abort!(method.sig.asyncness.span(), "savefile-abi does not support async methods.") + abort!( + method.sig.asyncness.span(), + "savefile-abi does not support async methods." + ) } if method.sig.variadic.is_some() { - abort!(method.sig.variadic.span(), "savefile-abi does not support variadic methods.") + abort!( + method.sig.variadic.span(), + "savefile-abi does not support variadic methods." + ) } if method.sig.unsafety.is_some() { - abort!(method.sig.unsafety.span(), "savefile-abi does not presently support unsafe methods.") + abort!( + method.sig.unsafety.span(), + "savefile-abi does not presently support unsafe methods." + ) } if method.sig.abi.is_some() { abort!(method.sig.abi.span(), "savefile-abi does not need (or support) 'extern \"C\"' or similar ABI-constructs. Just remove this keyword.") @@ -406,12 +427,19 @@ pub fn savefile_abi_exportable( if method.sig.generics.params.is_empty() == false { for item in method.sig.generics.params.iter() { match item { - GenericParam::Type(typ) => abort!(typ.span(), "savefile-abi does not support generic methods."), - GenericParam::Const(typ) => abort!(typ.span(), "savefile-abi does not support const-generic methods."), + GenericParam::Type(typ) => { + abort!(typ.span(), "savefile-abi does not support generic methods.") + } + GenericParam::Const(typ) => { + abort!(typ.span(), "savefile-abi does not support const-generic methods.") + } _ => {} } } - abort!(method.sig.generics.params.span(), "savefile-abi does not support methods with lifetimes."); + abort!( + method.sig.generics.params.span(), + "savefile-abi does not support methods with lifetimes." + ); } let method_defs = crate::savefile_abi::generate_method_definitions( @@ -446,9 +474,17 @@ pub fn savefile_abi_exportable( ); } TraitItem::Verbatim(v) => { - abort!(v.span(), "Unsupported item in trait definition: {}", v.to_token_stream()); + abort!( + v.span(), + "Unsupported item in trait definition: {}", + v.to_token_stream() + ); } - x => abort!(x.span(), "Unsupported item in trait definition: {}", x.to_token_stream()), + x => abort!( + x.span(), + "Unsupported item in trait definition: {}", + x.to_token_stream() + ), } } @@ -544,8 +580,6 @@ pub fn savefile_abi_export(item: proc_macro::TokenStream) -> proc_macro::TokenSt let trait_type = Ident::new(symbols[1], Span::call_site()); let abi_entry = Ident::new(("abi_entry_".to_string() + symbols[1]).as_str(), Span::call_site()); - - let expanded = quote! { #[allow(clippy::double_comparisons)] const _:() = { diff --git a/savefile-derive/src/savefile_abi.rs b/savefile-derive/src/savefile_abi.rs index eb9b9d0..1dbb728 100644 --- a/savefile-derive/src/savefile_abi.rs +++ b/savefile-derive/src/savefile_abi.rs @@ -137,7 +137,7 @@ fn emit_closure_helpers( pub(crate) enum ArgType { PlainData(Type), Reference(Box, bool /*ismut (only traits objects can be mut here)*/), - Str(bool/*static*/), + Str(bool /*static*/), Boxed(Box), Slice(Box), Trait(Ident, bool /*ismut self*/), @@ -167,7 +167,6 @@ pub(crate) fn parse_box_type( is_reference: bool, is_mut_ref: bool, ) -> ArgType { - let location; if is_return_value { location = format!("In return value of method '{}'", method_name); @@ -175,12 +174,16 @@ pub(crate) fn parse_box_type( location = format!("Method '{}', argument {}", method_name, arg_name); } - if path.segments.len() != 1 { abort!(path.span(), "Savefile does not support types named 'Box', unless they are the standard type Box, and it must be specified as 'Box', without any namespace"); } if is_reference { - abort!(path.span(), "{}. Savefile does not support references to Boxes. Just supply a reference to the inner type: {}", location, typ.to_token_stream()); + abort!( + path.span(), + "{}. Savefile does not support references to Boxes. Just supply a reference to the inner type: {}", + location, + typ.to_token_stream() + ); } let last_seg = path.segments.iter().last().unwrap(); @@ -203,29 +206,33 @@ pub(crate) fn parse_box_type( extra_definitions, true, is_mut_ref, - ){ + ) { ArgType::Boxed(_) => { - abort!(first_gen_arg.span(), "{}. Savefile does not support a Box containing another Box: {}", location, typ.to_token_stream()) + abort!( + first_gen_arg.span(), + "{}. Savefile does not support a Box containing another Box: {}", + location, + typ.to_token_stream() + ) } ArgType::PlainData(_) | ArgType::Str(_) => { return ArgType::PlainData(typ.clone()); //Box is itself a plaintype. So handle it as such. It can matter, if Box implements Serializable, when T does not. (example: str) } - ArgType::Slice(slicetype) => { - match &*slicetype { - ArgType::PlainData(_) => { - return ArgType::Slice(slicetype); - } - _x => - abort!(angargs.span(), "{}. Savefile does not support a Box containing a slice of anything complex, like: {}", location, typ.to_token_stream()) + ArgType::Slice(slicetype) => match &*slicetype { + ArgType::PlainData(_) => { + return ArgType::Slice(slicetype); } - } + _x => abort!( + angargs.span(), + "{}. Savefile does not support a Box containing a slice of anything complex, like: {}", + location, + typ.to_token_stream() + ), + }, ArgType::Reference(_, _) => { abort!(first_gen_arg.span(), "{}. Savefile does not support a Box containing a reference, like: {} (boxing a reference is generally a useless thing to do))", location, typ.to_token_stream()); } - x@ArgType::Trait(_, _) | - x@ArgType::Fn(_, _, _, _) => { - ArgType::Boxed(Box::new(x)) - } + x @ ArgType::Trait(_, _) | x @ ArgType::Fn(_, _, _, _) => ArgType::Boxed(Box::new(x)), } } _ => { @@ -285,7 +292,7 @@ fn parse_type( typref.lifetime.span(), "{}: Specifying lifetimes is not supported by Savefile-Abi.", location, - ) + ), } } else { is_static_lifetime = false; @@ -326,7 +333,12 @@ fn parse_type( ); } if is_mut_ref { - abort!(typ.span(), "{}: Mutable refernces are not supported by savefile-abi, except for FnMut-trait objects. {}", location, typ.to_token_stream()); + abort!( + typ.span(), + "{}: Mutable refernces are not supported by savefile-abi, except for FnMut-trait objects. {}", + location, + typ.to_token_stream() + ); } let argtype = parse_type( version, @@ -343,7 +355,12 @@ fn parse_type( } Type::TraitObject(trait_obj) => { if !is_reference { - abort!(trait_obj.span(), "{}: Trait objects must always be behind references. Try adding a '&' to the type: {}", location, typ.to_token_stream()); + abort!( + trait_obj.span(), + "{}: Trait objects must always be behind references. Try adding a '&' to the type: {}", + location, + typ.to_token_stream() + ); } if trait_obj.dyn_token.is_some() { let type_bounds: Vec<_> = trait_obj @@ -369,7 +386,12 @@ fn parse_type( abort!(trait_obj.bounds.span(), "{}, unsupported trait object reference. Only &dyn Trait is supported. Encountered zero traits.", location); } if type_bounds.len() > 1 { - abort!(trait_obj.bounds.span(), "{}, unsupported Box-type. Only &dyn Trait> is supported. Encountered multiple traits: {:?}", location, trait_obj); + abort!( + trait_obj.bounds.span(), + "{}, unsupported Box-type. Only &dyn Trait> is supported. Encountered multiple traits: {:?}", + location, + trait_obj + ); } let bound = type_bounds.into_iter().next().expect("Internal error, missing bounds"); @@ -896,7 +918,6 @@ pub(super) fn generate_method_definitions( let mut compile_time_known_size = Some(0); for (arg_index, (arg_name, typ)) in args.iter().enumerate() { - let argtype = parse_type( version, &arg_name.to_string(), @@ -906,7 +927,7 @@ pub(super) fn generate_method_definitions( &mut *name_generator, extra_definitions, false, - false + false, ); callee_trampoline_variable_declaration.push(quote! {let #arg_name;}); diff --git a/savefile-min-build/src/lib.rs b/savefile-min-build/src/lib.rs index bbf7653..e988b01 100644 --- a/savefile-min-build/src/lib.rs +++ b/savefile-min-build/src/lib.rs @@ -5,7 +5,6 @@ extern crate savefile_derive; use savefile_abi::AbiConnection; use savefile_derive::savefile_abi_exportable; - #[savefile_abi_exportable(version = 0)] pub trait ExampleTrait { fn test_slices(&mut self, slice: &[u32]) -> u32 { @@ -13,15 +12,13 @@ pub trait ExampleTrait { } } -impl ExampleTrait for () { - -} +impl ExampleTrait for () {} #[test] fn dummy_test() { let boxed: Box = Box::new(()); let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); - assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); + assert!(conn.get_arg_passable_by_ref("test_slices", 0)); //conn.test_slices(&[1,2,3,4]); } diff --git a/savefile-test/src/cycles.rs b/savefile-test/src/cycles.rs index 6a9c6d2..6785182 100644 --- a/savefile-test/src/cycles.rs +++ b/savefile-test/src/cycles.rs @@ -4,7 +4,7 @@ use savefile::{get_schema, Removed, WithSchema, WithSchemaContext}; #[derive(Savefile, Debug, PartialEq)] enum Tree { Leaf, - Node(Box,Box) + Node(Box, Box), } #[test] @@ -12,24 +12,28 @@ pub fn test_cyclic() { let example = Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf)); assert_roundtrip(example); - let example = Tree::Node(Box::new(Tree::Node(Box::new(Tree::Leaf),Box::new(Tree::Leaf))), Box::new(Tree::Leaf)); + let example = Tree::Node( + Box::new(Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf))), + Box::new(Tree::Leaf), + ); assert_roundtrip(example); } - #[derive(Savefile, Debug, PartialEq)] struct TreeNode { - tree: Box + tree: Box, } #[derive(Savefile, Debug, PartialEq)] enum Tree2 { Leaf(String), - Node(TreeNode) + Node(TreeNode), } #[test] pub fn test_cyclic2() { - let example = Tree2::Node(TreeNode{tree: Box::new(Tree2::Leaf("hej".into()))}); + let example = Tree2::Node(TreeNode { + tree: Box::new(Tree2::Leaf("hej".into())), + }); assert_roundtrip(example); } @@ -45,7 +49,6 @@ struct Version1LevelA(Option>); #[derive(Savefile, Debug, PartialEq)] struct Version1Base(Option>); - #[derive(Savefile, Debug, PartialEq)] struct Version2LevelC(Box); @@ -57,15 +60,11 @@ struct Version2LevelA(Option>); #[derive(Savefile, Debug, PartialEq)] struct Version2Base(Option>); - #[test] -#[should_panic(expected = "Saved schema differs from in-memory schema for version 0. Error: At location [./Version1Base/0/?/Version1LevelA/0/?/Version1LevelB/0/Version1LevelC/0]: Application protocol uses recursion up 3 levels, but foreign format uses 2")] +#[should_panic( + expected = "Saved schema differs from in-memory schema for version 0. Error: At location [./Version1Base/0/?/Version1LevelA/0/?/Version1LevelB/0/Version1LevelC/0]: Application protocol uses recursion up 3 levels, but foreign format uses 2" +)] fn cycles_vertest1() { use assert_roundtrip_to_new_version; - assert_roundtrip_to_new_version( - Version1Base(None), - 0, - Version2Base(None), - 1, - ); -} \ No newline at end of file + assert_roundtrip_to_new_version(Version1Base(None), 0, Version2Base(None), 1); +} diff --git a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs index 7d5fc85..44b1e8d 100644 --- a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs +++ b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs @@ -67,8 +67,8 @@ fn abi_test_slice() { let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); - assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); - assert_eq!(conn.test_slices(&[1,2,3,4]), 10); + assert!(conn.get_arg_passable_by_ref("test_slices", 0)); + assert_eq!(conn.test_slices(&[1, 2, 3, 4]), 10); } #[test] diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index 25fd45b..fa27241 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -861,9 +861,9 @@ extern crate serde; extern crate serde_derive; use core::str::Utf8Error; -use std::any::TypeId; #[cfg(feature = "serde_derive")] use serde_derive::{Deserialize, Serialize}; +use std::any::TypeId; #[cfg(feature = "quickcheck")] extern crate quickcheck; @@ -2015,7 +2015,11 @@ impl<'a, TR: Read> Deserializer<'a, TR> { /// Don't use this method directly, use the [crate::load] function /// instead. pub fn load(reader: &mut TR, version: u32) -> Result { - Deserializer::<_>::load_impl::(reader, version, Some(|version| T::schema(version, &mut WithSchemaContext::new()))) + Deserializer::<_>::load_impl::( + reader, + version, + Some(|version| T::schema(version, &mut WithSchemaContext::new())), + ) } /// Deserialize an object of type T from the given reader. @@ -2233,7 +2237,6 @@ pub fn save_file_noschema>( Serializer::save_noschema::(&mut f, version, data) } - /// Context object used to keep track of recursion. /// Datastructures which cannot contain recursion do not need to concern themselves with /// this. Recursive data structures in rust require the use of Box, Vec, Arc or similar. @@ -2242,7 +2245,7 @@ pub fn save_file_noschema>( /// As a user of Savefile, you only need to use this if you are implementing Savefile for /// container or smart-pointer type. pub struct WithSchemaContext { - seen_types: HashMap, + seen_types: HashMap, } impl WithSchemaContext { @@ -2250,9 +2253,7 @@ impl WithSchemaContext { /// This is useful for calling ::schema at the top-level. pub fn new() -> WithSchemaContext { let seen_types = HashMap::new(); - WithSchemaContext { - seen_types - } + WithSchemaContext { seen_types } } } @@ -2273,13 +2274,13 @@ impl WithSchemaContext { /// /// } /// ``` - pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { + pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { let typeid = TypeId::of::(); let prevlen = self.seen_types.len(); match self.seen_types.entry(typeid) { Entry::Occupied(occ) => { let present_value_depth = *occ.get(); - return Schema::Recursion( prevlen - present_value_depth ); + return Schema::Recursion(prevlen - present_value_depth); } Entry::Vacant(vac) => { vac.insert(prevlen); @@ -2305,11 +2306,10 @@ pub trait WithSchema { /// Create a new WithSchemaContext, and then call 'schema' on type T. /// This is a useful convenience method. -pub fn get_schema(version: u32) -> Schema { +pub fn get_schema(version: u32) -> Schema { T::schema(version, &mut WithSchemaContext::new()) } - /// This trait must be implemented for all data structures you wish to be /// able to serialize. To actually serialize data: create a [Serializer], /// then call serialize on your data to save, giving the Serializer @@ -2706,7 +2706,6 @@ impl SchemaEnum { } } fn layout_compatible(&self, other: &SchemaEnum) -> bool { - if self.has_explicit_repr == false || other.has_explicit_repr == false { return false; } @@ -3138,7 +3137,7 @@ pub enum Schema { /// such as 'Box', 'Vec' etc. This works, since the schema will only ever match /// if it is identical in memory and file, and because of this, counting /// only the recursion points is non-ambiguous. - Recursion(usize/*depth*/) + Recursion(usize /*depth*/), } /// Introspect is not implemented for Schema, though it could be impl Introspect for Schema { @@ -3171,7 +3170,9 @@ impl Schema { Schema::Str => "str".into(), Schema::Reference(_) => "reference".into(), Schema::Trait(_, _) => "trait".into(), - Schema::Recursion(depth) => {format!("",depth)} + Schema::Recursion(depth) => { + format!("", depth) + } } } /// Determine if the two fields are laid out identically in memory, in their parent objects. @@ -3247,7 +3248,10 @@ impl Schema { }) } /// Create a 3-element tuple - pub fn new_tuple3(version: u32, context: &mut WithSchemaContext) -> Schema { + pub fn new_tuple3( + version: u32, + context: &mut WithSchemaContext, + ) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "3-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3)>()), @@ -3272,7 +3276,10 @@ impl Schema { }) } /// Create a 4-element tuple - pub fn new_tuple4(version: u32, context: &mut WithSchemaContext) -> Schema { + pub fn new_tuple4( + version: u32, + context: &mut WithSchemaContext, + ) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "4-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3, T4)>()), @@ -4468,7 +4475,7 @@ impl Introspect for BTreeMap { self.len() } } -impl WithSchema for BTreeMap { +impl WithSchema for BTreeMap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4478,12 +4485,12 @@ impl WithSchema for BTreeMap fields: vec![ Field { name: "key".to_string(), - value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4493,7 +4500,7 @@ impl WithSchema for BTreeMap } } impl ReprC for BTreeMap {} -impl Serialize for BTreeMap { +impl Serialize for BTreeMap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.len().serialize(serializer)?; for (k, v) in self { @@ -4503,7 +4510,7 @@ impl Serialize for BTreeMap { Ok(()) } } -impl Deserialize for BTreeMap { +impl Deserialize for BTreeMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let mut ret = BTreeMap::new(); let count = ::deserialize(deserializer)?; @@ -4518,12 +4525,15 @@ impl Deserialize for BTree } impl ReprC for HashSet {} -impl WithSchema for HashSet { +impl WithSchema for HashSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|K::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| K::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl Serialize for HashSet { +impl Serialize for HashSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for item in self { @@ -4532,7 +4542,7 @@ impl Serialize for HashSet Deserialize for HashSet { +impl Deserialize for HashSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let cnt = deserializer.read_usize()?; let mut ret = HashSet::with_capacity_and_hasher(cnt, S::default()); @@ -4543,7 +4553,9 @@ impl } } -impl WithSchema for HashMap { +impl WithSchema + for HashMap +{ fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4553,12 +4565,12 @@ impl(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4568,7 +4580,9 @@ impl ReprC for HashMap {} -impl Serialize for HashMap { +impl Serialize + for HashMap +{ fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4579,7 +4593,7 @@ impl Deserialize +impl Deserialize for HashMap { fn deserialize(deserializer: &mut Deserializer) -> Result { @@ -4593,7 +4607,9 @@ impl WithSchema for IndexMap { +impl WithSchema + for IndexMap +{ fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4603,12 +4619,12 @@ impl(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4705,7 +4721,9 @@ where impl ReprC for IndexMap {} #[cfg(feature = "indexmap")] -impl Serialize for IndexMap { +impl Serialize + for IndexMap +{ fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4717,7 +4735,7 @@ impl Deserialize for IndexMap { +impl Deserialize for IndexMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexMap::with_capacity(l); @@ -4751,7 +4769,7 @@ impl Introspect for Inde impl ReprC for IndexSet {} #[cfg(feature = "indexmap")] -impl WithSchema for IndexSet { +impl WithSchema for IndexSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4760,7 +4778,7 @@ impl WithSchema alignment: None, fields: vec![Field { name: "key".to_string(), - value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }], })), @@ -4770,7 +4788,7 @@ impl WithSchema } #[cfg(feature = "indexmap")] -impl Serialize for IndexSet { +impl Serialize for IndexSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for k in self.iter() { @@ -4781,7 +4799,7 @@ impl Serialize fo } #[cfg(feature = "indexmap")] -impl Deserialize for IndexSet { +impl Deserialize for IndexSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexSet::with_capacity(l); @@ -5301,12 +5319,15 @@ impl Introspect for BinaryHeap { } impl ReprC for BinaryHeap {} -impl WithSchema for BinaryHeap { +impl WithSchema for BinaryHeap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl Serialize for BinaryHeap { +impl Serialize for BinaryHeap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { let l = self.len(); serializer.write_usize(l)?; @@ -5316,7 +5337,7 @@ impl Serialize for BinaryHeap { Ok(()) } } -impl Deserialize for BinaryHeap { +impl Deserialize for BinaryHeap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = BinaryHeap::with_capacity(l); @@ -5350,19 +5371,22 @@ where } #[cfg(feature = "smallvec")] -impl WithSchema for smallvec::SmallVec +impl WithSchema for smallvec::SmallVec where T::Item: WithSchema, { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::Item::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::Item::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } #[cfg(feature = "smallvec")] impl ReprC for smallvec::SmallVec {} #[cfg(feature = "smallvec")] -impl Serialize for smallvec::SmallVec +impl Serialize for smallvec::SmallVec where T::Item: Serialize, { @@ -5376,7 +5400,7 @@ where } } #[cfg(feature = "smallvec")] -impl Deserialize for smallvec::SmallVec +impl Deserialize for smallvec::SmallVec where T::Item: Deserialize, { @@ -5421,14 +5445,20 @@ fn regular_serialize_vec( } } -impl WithSchema for Box<[T]> { +impl WithSchema for Box<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl WithSchema for Arc<[T]> { +impl WithSchema for Arc<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } impl Introspect for Box<[T]> { @@ -5503,7 +5533,7 @@ impl Deserialize for Arc { } } -impl Serialize for Box<[T]> { +impl Serialize for Box<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5521,7 +5551,7 @@ impl Serialize for Box<[T]> { } impl ReprC for Box<[T]> {} -impl Serialize for Arc<[T]> { +impl Serialize for Arc<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5539,12 +5569,12 @@ impl Serialize for Arc<[T]> { } impl ReprC for Arc<[T]> {} -impl Deserialize for Arc<[T]> { +impl Deserialize for Arc<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into()) } } -impl Deserialize for Box<[T]> { +impl Deserialize for Box<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into_boxed_slice()) } @@ -5563,13 +5593,16 @@ impl<'a> Serialize for &'a str { } } -impl<'a, T: WithSchema+'static> WithSchema for &'a [T] { +impl<'a, T: WithSchema + 'static> WithSchema for &'a [T] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_slice_memory_layout::()) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + calculate_slice_memory_layout::(), + ) //TODO: This is _not_ the same memory layout as vec. Make a new Box type for slices? } } -impl<'a, T: Serialize + ReprC+'static> Serialize for &'a [T] { +impl<'a, T: Serialize + ReprC + 'static> Serialize for &'a [T] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5589,7 +5622,7 @@ impl<'a, T: Serialize + ReprC+'static> Serialize for &'a [T] { /// Deserialize a slice into a Vec /// Unsized slices cannot be deserialized into unsized slices. -pub fn deserialize_slice_as_vec( +pub fn deserialize_slice_as_vec( deserializer: &mut Deserializer, ) -> Result, SavefileError> { Vec::deserialize(deserializer) @@ -5682,9 +5715,12 @@ fn calculate_string_memory_layout() -> VecOrStringLayout { STRING_IS_STANDARD_LAYOUT.store(is_std, Ordering::Relaxed); return unsafe { std::mem::transmute(is_std) }; } -impl WithSchema for Vec { +impl WithSchema for Vec { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_vec_memory_layout::()) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + calculate_vec_memory_layout::(), + ) } } @@ -5704,7 +5740,7 @@ impl Introspect for Vec { } } -impl Serialize for Vec { +impl Serialize for Vec { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5741,7 +5777,7 @@ fn regular_deserialize_vec( Ok(ret) } -impl Deserialize for Vec { +impl Deserialize for Vec { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { Ok(regular_deserialize_vec(deserializer)?) @@ -5809,20 +5845,23 @@ impl Introspect for VecDeque { } } -impl WithSchema for VecDeque { +impl WithSchema for VecDeque { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } impl ReprC for VecDeque {} -impl Serialize for VecDeque { +impl Serialize for VecDeque { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { regular_serialize_vecdeque(self, serializer) } } -impl Deserialize for VecDeque { +impl Deserialize for VecDeque { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(regular_deserialize_vecdeque(deserializer)?) } @@ -5937,10 +5976,10 @@ impl ReprC for () { } } -impl WithSchema for [T; N] { +impl WithSchema for [T; N] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Array(SchemaArray { - item_type: Box::new(context.possible_recursion::(|context|T::schema(version, context))), + item_type: Box::new(context.possible_recursion::(|context| T::schema(version, context))), count: N, }) } @@ -5965,7 +6004,7 @@ impl ReprC for [T; N] { T::repr_c_optimization_safe(version) } } -impl Serialize for [T; N] { +impl Serialize for [T; N] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5983,7 +6022,7 @@ impl Serialize for [T; N] { } } -impl Deserialize for [T; N] { +impl Deserialize for [T; N] { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { let mut data: [MaybeUninit; N] = unsafe { @@ -6285,12 +6324,11 @@ impl Deserialize for arrayvec::ArrayVec< use std::ops::{Deref, Range}; impl WithSchema for Box { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) - + context.possible_recursion::(|context| T::schema(version, context)) } } impl ReprC for Box {} -impl Serialize for Box { +impl Serialize for Box { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } @@ -6304,41 +6342,41 @@ impl Deserialize for Box { use std::rc::Rc; impl ReprC for Rc {} -impl WithSchema for Rc { +impl WithSchema for Rc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) + context.possible_recursion::(|context| T::schema(version, context)) } } -impl Serialize for Rc { +impl Serialize for Rc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Rc { +impl Deserialize for Rc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Rc::new(T::deserialize(deserializer)?)) } } impl ReprC for Arc {} -impl WithSchema for Arc { +impl WithSchema for Arc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) + context.possible_recursion::(|context| T::schema(version, context)) } } -impl Serialize for Arc { +impl Serialize for Arc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Arc { +impl Deserialize for Arc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Arc::new(T::deserialize(deserializer)?)) } } #[cfg(feature = "bzip2")] use bzip2::Compression; -use std::any::{Any}; +use std::any::Any; use std::cell::Cell; use std::cell::RefCell; use std::collections::hash_map::Entry; diff --git a/savefile/src/prelude.rs b/savefile/src/prelude.rs index 1b52535..b30ab19 100644 --- a/savefile/src/prelude.rs +++ b/savefile/src/prelude.rs @@ -1,11 +1,12 @@ pub use { - super::deserialize_slice_as_vec, super::introspect_item, super::load, super::load_file, super::load_file_noschema, - super::load_from_mem, super::load_noschema, super::save, super::save_file, super::save_file_noschema, - super::save_noschema, super::save_to_mem, super::AbiRemoved, super::Canary1, super::Deserialize, - super::Deserializer, super::Field, super::Introspect, super::IntrospectItem, super::IntrospectedElementKey, - super::IntrospectionResult, super::Introspector, super::IntrospectorNavCommand, super::IsReprC, super::Removed, - super::ReprC, super::SavefileError, super::Schema, super::SchemaEnum, super::SchemaPrimitive, super::SchemaStruct, - super::Serialize, super::Serializer, super::Variant, super::WithSchema, super::WithSchemaContext, super::get_schema + super::deserialize_slice_as_vec, super::get_schema, super::introspect_item, super::load, super::load_file, + super::load_file_noschema, super::load_from_mem, super::load_noschema, super::save, super::save_file, + super::save_file_noschema, super::save_noschema, super::save_to_mem, super::AbiRemoved, super::Canary1, + super::Deserialize, super::Deserializer, super::Field, super::Introspect, super::IntrospectItem, + super::IntrospectedElementKey, super::IntrospectionResult, super::Introspector, super::IntrospectorNavCommand, + super::IsReprC, super::Removed, super::ReprC, super::SavefileError, super::Schema, super::SchemaEnum, + super::SchemaPrimitive, super::SchemaStruct, super::Serialize, super::Serializer, super::Variant, + super::WithSchema, super::WithSchemaContext, }; pub use byteorder::{LittleEndian, ReadBytesExt}; From b12583d82af7dabb82915a180c3c0091f95a6ddc Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:28:13 +0200 Subject: [PATCH 08/15] doc --- savefile/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index fa27241..74cbf02 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -2274,6 +2274,10 @@ impl WithSchemaContext { /// /// } /// ``` + /// + /// If recursion is detected (traversing to exactly MyBox twice, in the above example), the method + /// 'possible_recursion' will return Schema::Recursion, stopping the Schema instance from becoming infinitely big. + /// pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { let typeid = TypeId::of::(); let prevlen = self.seen_types.len(); @@ -2301,6 +2305,8 @@ impl WithSchemaContext { /// can be disabled). pub trait WithSchema { /// Returns a representation of the schema used by this Serialize implementation for the given version. + /// The WithSchemaContext can be used to guard against recursive data structures. + /// See documentation of WithSchemaContext. fn schema(version: u32, context: &mut WithSchemaContext) -> Schema; } From 1a0c1d3483f437d508cb3db0cf30bcaeeb3cecf9 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:32:24 +0200 Subject: [PATCH 09/15] format --- savefile-abi/src/lib.rs | 33 +-- savefile-derive/src/lib.rs | 62 ++++-- savefile-derive/src/savefile_abi.rs | 69 ++++--- savefile-min-build/src/lib.rs | 7 +- savefile-test/src/cycles.rs | 43 ++-- .../advanced_datatypes_test.rs | 4 +- savefile/src/lib.rs | 193 +++++++++++------- 7 files changed, 253 insertions(+), 158 deletions(-) diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index abd939b..35a15eb 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -468,12 +468,8 @@ pub struct TraitObject { vtable: *const (), } -unsafe impl Sync for TraitObject { - -} -unsafe impl Send for TraitObject { - -} +unsafe impl Sync for TraitObject {} +unsafe impl Send for TraitObject {} impl TraitObject { /// Returns a TraitObject with two null ptrs. This value must never be used, @@ -617,12 +613,8 @@ pub struct AbiConnection { #[doc(hidden)] pub phantom: PhantomData<*const T>, } -unsafe impl Sync for AbiConnection{ - -} -unsafe impl Send for AbiConnection{ - -} +unsafe impl Sync for AbiConnection {} +unsafe impl Send for AbiConnection {} /// A trait object together with its entry point #[repr(C)] @@ -1324,15 +1316,24 @@ impl AbiConnection { /// of the code being called into. It will not change during the lifetime of an /// AbiConnector, but it may change if the target library is recompiled. pub fn get_arg_passable_by_ref(&self, method: &str, arg: usize) -> bool { - if let Some(found) = self.template.methods.iter().find(|var|var.method_name == method) { + if let Some(found) = self.template.methods.iter().find(|var| var.method_name == method) { let abi_method: &AbiConnectionMethod = found; if arg >= abi_method.caller_info.arguments.len() { - panic!("Method '{}' has only {} arguments, so there is no argument #{}", method, abi_method.caller_info.arguments.len(), arg); + panic!( + "Method '{}' has only {} arguments, so there is no argument #{}", + method, + abi_method.caller_info.arguments.len(), + arg + ); } (abi_method.compatibility_mask & (1 << (arg as u64))) != 0 } else { - let arg_names : Vec<_> = self.template.methods.iter().map(|x|x.method_name.as_str()).collect(); - panic!("Trait has no method with name '{}'. Available methods: {}", method, arg_names.join(", ")); + let arg_names: Vec<_> = self.template.methods.iter().map(|x| x.method_name.as_str()).collect(); + panic!( + "Trait has no method with name '{}'. Available methods: {}", + method, + arg_names.join(", ") + ); } } diff --git a/savefile-derive/src/lib.rs b/savefile-derive/src/lib.rs index 92a5a25..62ef1d4 100644 --- a/savefile-derive/src/lib.rs +++ b/savefile-derive/src/lib.rs @@ -36,7 +36,10 @@ use syn::__private::bool; use syn::spanned::Spanned; use syn::token::Paren; use syn::Type::Tuple; -use syn::{DeriveInput, FnArg, GenericParam, Generics, Ident, ImplGenerics, Index, ItemTrait, Pat, ReturnType, TraitItem, Type, TypeGenerics, TypeParamBound, TypeTuple}; +use syn::{ + DeriveInput, FnArg, GenericParam, Generics, Ident, ImplGenerics, Index, ItemTrait, Pat, ReturnType, TraitItem, + Type, TypeGenerics, TypeParamBound, TypeTuple, +}; fn implement_fields_serialize( field_infos: Vec, implicit_self: bool, @@ -266,7 +269,10 @@ pub fn savefile_abi_exportable( let mut extra_definitions = HashMap::new(); if parsed.generics.params.is_empty() == false { - abort!(parsed.generics.params.span(), "Savefile does not support generic traits."); + abort!( + parsed.generics.params.span(), + "Savefile does not support generic traits." + ); } for supertrait in parsed.supertraits.iter() { match supertrait { @@ -293,7 +299,10 @@ pub fn savefile_abi_exportable( } if parsed.generics.where_clause.is_some() { - abort!(parsed.generics.where_clause.span(), "Savefile does not support where-clauses for traits"); + abort!( + parsed.generics.where_clause.span(), + "Savefile does not support where-clauses for traits" + ); } for (method_number, item) in parsed.items.iter().enumerate() { @@ -312,7 +321,10 @@ pub fn savefile_abi_exportable( } TraitItem::Method(method) => { if method.sig.generics.where_clause.is_some() { - abort!(method.sig.generics.where_clause.span(), "Savefile does not support where-clauses for methods"); + abort!( + method.sig.generics.where_clause.span(), + "Savefile does not support where-clauses for methods" + ); } let method_name = method.sig.ident.clone(); //let method_name_str = method.sig.ident.to_string(); @@ -392,13 +404,22 @@ pub fn savefile_abi_exportable( } } if method.sig.asyncness.is_some() { - abort!(method.sig.asyncness.span(), "savefile-abi does not support async methods.") + abort!( + method.sig.asyncness.span(), + "savefile-abi does not support async methods." + ) } if method.sig.variadic.is_some() { - abort!(method.sig.variadic.span(), "savefile-abi does not support variadic methods.") + abort!( + method.sig.variadic.span(), + "savefile-abi does not support variadic methods." + ) } if method.sig.unsafety.is_some() { - abort!(method.sig.unsafety.span(), "savefile-abi does not presently support unsafe methods.") + abort!( + method.sig.unsafety.span(), + "savefile-abi does not presently support unsafe methods." + ) } if method.sig.abi.is_some() { abort!(method.sig.abi.span(), "savefile-abi does not need (or support) 'extern \"C\"' or similar ABI-constructs. Just remove this keyword.") @@ -406,12 +427,19 @@ pub fn savefile_abi_exportable( if method.sig.generics.params.is_empty() == false { for item in method.sig.generics.params.iter() { match item { - GenericParam::Type(typ) => abort!(typ.span(), "savefile-abi does not support generic methods."), - GenericParam::Const(typ) => abort!(typ.span(), "savefile-abi does not support const-generic methods."), + GenericParam::Type(typ) => { + abort!(typ.span(), "savefile-abi does not support generic methods.") + } + GenericParam::Const(typ) => { + abort!(typ.span(), "savefile-abi does not support const-generic methods.") + } _ => {} } } - abort!(method.sig.generics.params.span(), "savefile-abi does not support methods with lifetimes."); + abort!( + method.sig.generics.params.span(), + "savefile-abi does not support methods with lifetimes." + ); } let method_defs = crate::savefile_abi::generate_method_definitions( @@ -446,9 +474,17 @@ pub fn savefile_abi_exportable( ); } TraitItem::Verbatim(v) => { - abort!(v.span(), "Unsupported item in trait definition: {}", v.to_token_stream()); + abort!( + v.span(), + "Unsupported item in trait definition: {}", + v.to_token_stream() + ); } - x => abort!(x.span(), "Unsupported item in trait definition: {}", x.to_token_stream()), + x => abort!( + x.span(), + "Unsupported item in trait definition: {}", + x.to_token_stream() + ), } } @@ -544,8 +580,6 @@ pub fn savefile_abi_export(item: proc_macro::TokenStream) -> proc_macro::TokenSt let trait_type = Ident::new(symbols[1], Span::call_site()); let abi_entry = Ident::new(("abi_entry_".to_string() + symbols[1]).as_str(), Span::call_site()); - - let expanded = quote! { #[allow(clippy::double_comparisons)] const _:() = { diff --git a/savefile-derive/src/savefile_abi.rs b/savefile-derive/src/savefile_abi.rs index 13cfcb0..f36fc31 100644 --- a/savefile-derive/src/savefile_abi.rs +++ b/savefile-derive/src/savefile_abi.rs @@ -137,7 +137,7 @@ fn emit_closure_helpers( pub(crate) enum ArgType { PlainData(Type), Reference(Box, bool /*ismut (only traits objects can be mut here)*/), - Str(bool/*static*/), + Str(bool /*static*/), Boxed(Box), Slice(Box), Trait(Ident, bool /*ismut self*/), @@ -167,7 +167,6 @@ pub(crate) fn parse_box_type( is_reference: bool, is_mut_ref: bool, ) -> ArgType { - let location; if is_return_value { location = format!("In return value of method '{}'", method_name); @@ -175,12 +174,16 @@ pub(crate) fn parse_box_type( location = format!("Method '{}', argument {}", method_name, arg_name); } - if path.segments.len() != 1 { abort!(path.span(), "Savefile does not support types named 'Box', unless they are the standard type Box, and it must be specified as 'Box', without any namespace"); } if is_reference { - abort!(path.span(), "{}. Savefile does not support references to Boxes. Just supply a reference to the inner type: {}", location, typ.to_token_stream()); + abort!( + path.span(), + "{}. Savefile does not support references to Boxes. Just supply a reference to the inner type: {}", + location, + typ.to_token_stream() + ); } let last_seg = path.segments.iter().last().unwrap(); @@ -203,29 +206,33 @@ pub(crate) fn parse_box_type( extra_definitions, true, is_mut_ref, - ){ + ) { ArgType::Boxed(_) => { - abort!(first_gen_arg.span(), "{}. Savefile does not support a Box containing another Box: {}", location, typ.to_token_stream()) + abort!( + first_gen_arg.span(), + "{}. Savefile does not support a Box containing another Box: {}", + location, + typ.to_token_stream() + ) } ArgType::PlainData(_) | ArgType::Str(_) => { return ArgType::PlainData(typ.clone()); //Box is itself a plaintype. So handle it as such. It can matter, if Box implements Serializable, when T does not. (example: str) } - ArgType::Slice(slicetype) => { - match &*slicetype { - ArgType::PlainData(_) => { - return ArgType::Slice(slicetype); - } - _x => - abort!(angargs.span(), "{}. Savefile does not support a Box containing a slice of anything complex, like: {}", location, typ.to_token_stream()) + ArgType::Slice(slicetype) => match &*slicetype { + ArgType::PlainData(_) => { + return ArgType::Slice(slicetype); } - } + _x => abort!( + angargs.span(), + "{}. Savefile does not support a Box containing a slice of anything complex, like: {}", + location, + typ.to_token_stream() + ), + }, ArgType::Reference(_, _) => { abort!(first_gen_arg.span(), "{}. Savefile does not support a Box containing a reference, like: {} (boxing a reference is generally a useless thing to do))", location, typ.to_token_stream()); } - x@ArgType::Trait(_, _) | - x@ArgType::Fn(_, _, _, _) => { - ArgType::Boxed(Box::new(x)) - } + x @ ArgType::Trait(_, _) | x @ ArgType::Fn(_, _, _, _) => ArgType::Boxed(Box::new(x)), } } _ => { @@ -285,7 +292,7 @@ fn parse_type( typref.lifetime.span(), "{}: Specifying lifetimes is not supported by Savefile-Abi.", location, - ) + ), } } else { is_static_lifetime = false; @@ -326,7 +333,12 @@ fn parse_type( ); } if is_mut_ref { - abort!(typ.span(), "{}: Mutable refernces are not supported by savefile-abi, except for FnMut-trait objects. {}", location, typ.to_token_stream()); + abort!( + typ.span(), + "{}: Mutable refernces are not supported by savefile-abi, except for FnMut-trait objects. {}", + location, + typ.to_token_stream() + ); } let argtype = parse_type( version, @@ -343,7 +355,12 @@ fn parse_type( } Type::TraitObject(trait_obj) => { if !is_reference { - abort!(trait_obj.span(), "{}: Trait objects must always be behind references. Try adding a '&' to the type: {}", location, typ.to_token_stream()); + abort!( + trait_obj.span(), + "{}: Trait objects must always be behind references. Try adding a '&' to the type: {}", + location, + typ.to_token_stream() + ); } if trait_obj.dyn_token.is_some() { let type_bounds: Vec<_> = trait_obj @@ -369,7 +386,12 @@ fn parse_type( abort!(trait_obj.bounds.span(), "{}, unsupported trait object reference. Only &dyn Trait is supported. Encountered zero traits.", location); } if type_bounds.len() > 1 { - abort!(trait_obj.bounds.span(), "{}, unsupported Box-type. Only &dyn Trait> is supported. Encountered multiple traits: {:?}", location, trait_obj); + abort!( + trait_obj.bounds.span(), + "{}, unsupported Box-type. Only &dyn Trait> is supported. Encountered multiple traits: {:?}", + location, + trait_obj + ); } let bound = type_bounds.into_iter().next().expect("Internal error, missing bounds"); @@ -896,7 +918,6 @@ pub(super) fn generate_method_definitions( let mut compile_time_known_size = Some(0); for (arg_index, (arg_name, typ)) in args.iter().enumerate() { - let argtype = parse_type( version, &arg_name.to_string(), @@ -906,7 +927,7 @@ pub(super) fn generate_method_definitions( &mut *name_generator, extra_definitions, false, - false + false, ); callee_trampoline_variable_declaration.push(quote! {let #arg_name;}); diff --git a/savefile-min-build/src/lib.rs b/savefile-min-build/src/lib.rs index 91af8c8..636154d 100644 --- a/savefile-min-build/src/lib.rs +++ b/savefile-min-build/src/lib.rs @@ -4,7 +4,6 @@ extern crate savefile_derive; use savefile_abi::AbiConnection; use savefile_derive::savefile_abi_exportable; - #[savefile_abi_exportable(version = 0)] pub trait ExampleTrait { fn test_slices(&mut self, slice: &[u32]) -> u32 { @@ -12,15 +11,13 @@ pub trait ExampleTrait { } } -impl ExampleTrait for () { - -} +impl ExampleTrait for () {} #[test] fn dummy_test() { let boxed: Box = Box::new(()); let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); - assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); + assert!(conn.get_arg_passable_by_ref("test_slices", 0)); //conn.test_slices(&[1,2,3,4]); } diff --git a/savefile-test/src/cycles.rs b/savefile-test/src/cycles.rs index e776978..0d01ece 100644 --- a/savefile-test/src/cycles.rs +++ b/savefile-test/src/cycles.rs @@ -4,7 +4,7 @@ use savefile::Removed; #[derive(Savefile, Debug, PartialEq)] enum Tree { Leaf, - Node(Box,Box) + Node(Box, Box), } #[test] @@ -12,76 +12,77 @@ pub fn test_cyclic() { let example = Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf)); assert_roundtrip(example); - let example = Tree::Node(Box::new(Tree::Node(Box::new(Tree::Leaf),Box::new(Tree::Leaf))), Box::new(Tree::Leaf)); + let example = Tree::Node( + Box::new(Tree::Node(Box::new(Tree::Leaf), Box::new(Tree::Leaf))), + Box::new(Tree::Leaf), + ); assert_roundtrip(example); } - #[derive(Savefile, Debug, PartialEq)] struct TreeNode { - tree: Box + tree: Box, } #[derive(Savefile, Debug, PartialEq)] enum Tree2 { Leaf(String), - Node(TreeNode) + Node(TreeNode), } #[test] pub fn test_cyclic2() { - let example = Tree2::Node(TreeNode{tree: Box::new(Tree2::Leaf("hej".into()))}); + let example = Tree2::Node(TreeNode { + tree: Box::new(Tree2::Leaf("hej".into())), + }); assert_roundtrip(example); } #[derive(Savefile, Debug, PartialEq)] enum Version1LevelD { Leaf, - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version1LevelC { Leaf, - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version1LevelB { Leaf(Box), - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version1LevelA { Leaf, - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version2LevelC { Leaf, - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version2LevelB { Leaf(Box), - Node(Box) + Node(Box), } #[derive(Savefile, Debug, PartialEq)] enum Version2LevelA { Leaf, - Node(Box) + Node(Box), } #[test] -#[should_panic(expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.Version1LevelA/Node/0Version1LevelB/Leaf/0Version1LevelC/Node/0Version1LevelD/Node/0]: In memory schema: , file schema: enum")] +#[should_panic( + expected = "Saved schema differs from in-memory schema for version 0. Error: At location [.Version1LevelA/Node/0Version1LevelB/Leaf/0Version1LevelC/Node/0Version1LevelD/Node/0]: In memory schema: , file schema: enum" +)] fn cycles_vertest1() { use assert_roundtrip_to_new_version; - assert_roundtrip_to_new_version( - Version1LevelA::Leaf, - 0, - Version2LevelA::Leaf, - 1, - ); -} \ No newline at end of file + assert_roundtrip_to_new_version(Version1LevelA::Leaf, 0, Version2LevelA::Leaf, 1); +} diff --git a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs index 7d5fc85..44b1e8d 100644 --- a/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs +++ b/savefile-test/src/savefile_abi_test/advanced_datatypes_test.rs @@ -67,8 +67,8 @@ fn abi_test_slice() { let boxed: Box = Box::new(AdvancedTestInterfaceImpl {}); let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); - assert!( conn.get_arg_passable_by_ref("test_slices", 0) ); - assert_eq!(conn.test_slices(&[1,2,3,4]), 10); + assert!(conn.get_arg_passable_by_ref("test_slices", 0)); + assert_eq!(conn.test_slices(&[1, 2, 3, 4]), 10); } #[test] diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index c8d0f1e..b075996 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -2014,7 +2014,11 @@ impl<'a, TR: Read> Deserializer<'a, TR> { /// Don't use this method directly, use the [crate::load] function /// instead. pub fn load(reader: &mut TR, version: u32) -> Result { - Deserializer::<_>::load_impl::(reader, version, Some(|version| T::schema(version, &mut WithSchemaContext::new()))) + Deserializer::<_>::load_impl::( + reader, + version, + Some(|version| T::schema(version, &mut WithSchemaContext::new())), + ) } /// Deserialize an object of type T from the given reader. @@ -2232,7 +2236,6 @@ pub fn save_file_noschema>( Serializer::save_noschema::(&mut f, version, data) } - /// Context object used to keep track of recursion. /// Datastructures which cannot contain recursion do not need to concern themselves with /// this. Recursive data structures in rust require the use of Box, Vec, Arc or similar. @@ -2242,7 +2245,7 @@ pub fn save_file_noschema>( /// container or smart-pointer type. #[derive(Default)] pub struct WithSchemaContext { - seen_types: HashMap, + seen_types: HashMap, } impl WithSchemaContext { @@ -2270,13 +2273,13 @@ impl WithSchemaContext { /// /// } /// ``` - pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { + pub fn possible_recursion(&mut self, cb: impl FnOnce(&mut WithSchemaContext) -> Schema) -> Schema { let typeid = TypeId::of::(); let prevlen = self.seen_types.len(); match self.seen_types.entry(typeid) { Entry::Occupied(occ) => { let present_value_depth = *occ.get(); - return Schema::Recursion( prevlen - present_value_depth ); + return Schema::Recursion(prevlen - present_value_depth); } Entry::Vacant(vac) => { vac.insert(prevlen); @@ -2696,7 +2699,6 @@ impl SchemaEnum { } } fn layout_compatible(&self, other: &SchemaEnum) -> bool { - if self.has_explicit_repr == false || other.has_explicit_repr == false { return false; } @@ -3128,7 +3130,7 @@ pub enum Schema { /// such as 'Box', 'Vec' etc. This works, since the schema will only ever match /// if it is identical in memory and file, and because of this, counting /// only the recursion points is non-ambiguous. - Recursion(usize/*depth*/) + Recursion(usize /*depth*/), } /// Introspect is not implemented for Schema, though it could be impl Introspect for Schema { @@ -3161,7 +3163,9 @@ impl Schema { Schema::Str => "str".into(), Schema::Reference(_) => "reference".into(), Schema::Trait(_, _) => "trait".into(), - Schema::Recursion(depth) => {format!("",depth)} + Schema::Recursion(depth) => { + format!("", depth) + } } } /// Determine if the two fields are laid out identically in memory, in their parent objects. @@ -3237,7 +3241,10 @@ impl Schema { }) } /// Create a 3-element tuple - pub fn new_tuple3(version: u32, context: &mut WithSchemaContext) -> Schema { + pub fn new_tuple3( + version: u32, + context: &mut WithSchemaContext, + ) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "3-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3)>()), @@ -3262,7 +3269,10 @@ impl Schema { }) } /// Create a 4-element tuple - pub fn new_tuple4(version: u32, context: &mut WithSchemaContext) -> Schema { + pub fn new_tuple4( + version: u32, + context: &mut WithSchemaContext, + ) -> Schema { Schema::Struct(SchemaStruct { dbg_name: "4-Tuple".to_string(), size: Some(std::mem::size_of::<(T1, T2, T3, T4)>()), @@ -4458,7 +4468,7 @@ impl Introspect for BTreeMap { self.len() } } -impl WithSchema for BTreeMap { +impl WithSchema for BTreeMap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4468,12 +4478,12 @@ impl WithSchema for BTreeMap fields: vec![ Field { name: "key".to_string(), - value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4483,7 +4493,7 @@ impl WithSchema for BTreeMap } } impl ReprC for BTreeMap {} -impl Serialize for BTreeMap { +impl Serialize for BTreeMap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.len().serialize(serializer)?; for (k, v) in self { @@ -4493,7 +4503,7 @@ impl Serialize for BTreeMap { Ok(()) } } -impl Deserialize for BTreeMap { +impl Deserialize for BTreeMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let mut ret = BTreeMap::new(); let count = ::deserialize(deserializer)?; @@ -4508,12 +4518,15 @@ impl Deserialize for BTree } impl ReprC for HashSet {} -impl WithSchema for HashSet { +impl WithSchema for HashSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|K::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| K::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl Serialize for HashSet { +impl Serialize for HashSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for item in self { @@ -4522,7 +4535,7 @@ impl Serialize for HashSet Deserialize for HashSet { +impl Deserialize for HashSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let cnt = deserializer.read_usize()?; let mut ret = HashSet::with_capacity_and_hasher(cnt, S::default()); @@ -4533,7 +4546,9 @@ impl } } -impl WithSchema for HashMap { +impl WithSchema + for HashMap +{ fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4543,12 +4558,12 @@ impl(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4558,7 +4573,9 @@ impl ReprC for HashMap {} -impl Serialize for HashMap { +impl Serialize + for HashMap +{ fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4569,7 +4586,7 @@ impl Deserialize +impl Deserialize for HashMap { fn deserialize(deserializer: &mut Deserializer) -> Result { @@ -4583,7 +4600,9 @@ impl WithSchema for IndexMap { +impl WithSchema + for IndexMap +{ fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4593,12 +4612,12 @@ impl(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }, Field { name: "value".to_string(), - value: Box::new(context.possible_recursion::(|context|V::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| V::schema(version, context))), offset: None, }, ], @@ -4695,7 +4714,9 @@ where impl ReprC for IndexMap {} #[cfg(feature = "indexmap")] -impl Serialize for IndexMap { +impl Serialize + for IndexMap +{ fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for (k, v) in self.iter() { @@ -4707,7 +4728,7 @@ impl Deserialize for IndexMap { +impl Deserialize for IndexMap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexMap::with_capacity(l); @@ -4741,7 +4762,7 @@ impl Introspect for Inde impl ReprC for IndexSet {} #[cfg(feature = "indexmap")] -impl WithSchema for IndexSet { +impl WithSchema for IndexSet { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Vector( Box::new(Schema::Struct(SchemaStruct { @@ -4750,7 +4771,7 @@ impl WithSchema alignment: None, fields: vec![Field { name: "key".to_string(), - value: Box::new(context.possible_recursion::(|context|K::schema(version, context))), + value: Box::new(context.possible_recursion::(|context| K::schema(version, context))), offset: None, }], })), @@ -4760,7 +4781,7 @@ impl WithSchema } #[cfg(feature = "indexmap")] -impl Serialize for IndexSet { +impl Serialize for IndexSet { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { serializer.write_usize(self.len())?; for k in self.iter() { @@ -4771,7 +4792,7 @@ impl Serialize fo } #[cfg(feature = "indexmap")] -impl Deserialize for IndexSet { +impl Deserialize for IndexSet { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = IndexSet::with_capacity(l); @@ -5291,12 +5312,15 @@ impl Introspect for BinaryHeap { } impl ReprC for BinaryHeap {} -impl WithSchema for BinaryHeap { +impl WithSchema for BinaryHeap { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl Serialize for BinaryHeap { +impl Serialize for BinaryHeap { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { let l = self.len(); serializer.write_usize(l)?; @@ -5306,7 +5330,7 @@ impl Serialize for BinaryHeap { Ok(()) } } -impl Deserialize for BinaryHeap { +impl Deserialize for BinaryHeap { fn deserialize(deserializer: &mut Deserializer) -> Result { let l = deserializer.read_usize()?; let mut ret = BinaryHeap::with_capacity(l); @@ -5340,19 +5364,22 @@ where } #[cfg(feature = "smallvec")] -impl WithSchema for smallvec::SmallVec +impl WithSchema for smallvec::SmallVec where T::Item: WithSchema, { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::Item::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::Item::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } #[cfg(feature = "smallvec")] impl ReprC for smallvec::SmallVec {} #[cfg(feature = "smallvec")] -impl Serialize for smallvec::SmallVec +impl Serialize for smallvec::SmallVec where T::Item: Serialize, { @@ -5366,7 +5393,7 @@ where } } #[cfg(feature = "smallvec")] -impl Deserialize for smallvec::SmallVec +impl Deserialize for smallvec::SmallVec where T::Item: Deserialize, { @@ -5411,14 +5438,20 @@ fn regular_serialize_vec( } } -impl WithSchema for Box<[T]> { +impl WithSchema for Box<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } -impl WithSchema for Arc<[T]> { +impl WithSchema for Arc<[T]> { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } impl Introspect for Box<[T]> { @@ -5493,7 +5526,7 @@ impl Deserialize for Arc { } } -impl Serialize for Box<[T]> { +impl Serialize for Box<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5511,7 +5544,7 @@ impl Serialize for Box<[T]> { } impl ReprC for Box<[T]> {} -impl Serialize for Arc<[T]> { +impl Serialize for Arc<[T]> { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5529,12 +5562,12 @@ impl Serialize for Arc<[T]> { } impl ReprC for Arc<[T]> {} -impl Deserialize for Arc<[T]> { +impl Deserialize for Arc<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into()) } } -impl Deserialize for Box<[T]> { +impl Deserialize for Box<[T]> { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Vec::::deserialize(deserializer)?.into_boxed_slice()) } @@ -5553,13 +5586,16 @@ impl<'a> Serialize for &'a str { } } -impl<'a, T: WithSchema+'static> WithSchema for &'a [T] { +impl<'a, T: WithSchema + 'static> WithSchema for &'a [T] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_slice_memory_layout::()) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + calculate_slice_memory_layout::(), + ) //TODO: This is _not_ the same memory layout as vec. Make a new Box type for slices? } } -impl<'a, T: Serialize + ReprC+'static> Serialize for &'a [T] { +impl<'a, T: Serialize + ReprC + 'static> Serialize for &'a [T] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5579,7 +5615,7 @@ impl<'a, T: Serialize + ReprC+'static> Serialize for &'a [T] { /// Deserialize a slice into a Vec /// Unsized slices cannot be deserialized into unsized slices. -pub fn deserialize_slice_as_vec( +pub fn deserialize_slice_as_vec( deserializer: &mut Deserializer, ) -> Result, SavefileError> { Vec::deserialize(deserializer) @@ -5672,9 +5708,12 @@ fn calculate_string_memory_layout() -> VecOrStringLayout { STRING_IS_STANDARD_LAYOUT.store(is_std, Ordering::Relaxed); return unsafe { std::mem::transmute(is_std) }; } -impl WithSchema for Vec { +impl WithSchema for Vec { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), calculate_vec_memory_layout::()) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + calculate_vec_memory_layout::(), + ) } } @@ -5694,7 +5733,7 @@ impl Introspect for Vec { } } -impl Serialize for Vec { +impl Serialize for Vec { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5731,7 +5770,7 @@ fn regular_deserialize_vec( Ok(ret) } -impl Deserialize for Vec { +impl Deserialize for Vec { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { Ok(regular_deserialize_vec(deserializer)?) @@ -5799,20 +5838,23 @@ impl Introspect for VecDeque { } } -impl WithSchema for VecDeque { +impl WithSchema for VecDeque { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - Schema::Vector(Box::new(context.possible_recursion::(|context|T::schema(version, context))), VecOrStringLayout::Unknown) + Schema::Vector( + Box::new(context.possible_recursion::(|context| T::schema(version, context))), + VecOrStringLayout::Unknown, + ) } } impl ReprC for VecDeque {} -impl Serialize for VecDeque { +impl Serialize for VecDeque { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { regular_serialize_vecdeque(self, serializer) } } -impl Deserialize for VecDeque { +impl Deserialize for VecDeque { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(regular_deserialize_vecdeque(deserializer)?) } @@ -5927,10 +5969,10 @@ impl ReprC for () { } } -impl WithSchema for [T; N] { +impl WithSchema for [T; N] { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { Schema::Array(SchemaArray { - item_type: Box::new(context.possible_recursion::(|context|T::schema(version, context))), + item_type: Box::new(context.possible_recursion::(|context| T::schema(version, context))), count: N, }) } @@ -5955,7 +5997,7 @@ impl ReprC for [T; N] { T::repr_c_optimization_safe(version) } } -impl Serialize for [T; N] { +impl Serialize for [T; N] { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { unsafe { if T::repr_c_optimization_safe(serializer.file_version).is_false() { @@ -5973,7 +6015,7 @@ impl Serialize for [T; N] { } } -impl Deserialize for [T; N] { +impl Deserialize for [T; N] { fn deserialize(deserializer: &mut Deserializer) -> Result { if unsafe { T::repr_c_optimization_safe(deserializer.file_version) }.is_false() { let mut data: [MaybeUninit; N] = unsafe { @@ -6275,12 +6317,11 @@ impl Deserialize for arrayvec::ArrayVec< use std::ops::{Deref, Range}; impl WithSchema for Box { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) - + context.possible_recursion::(|context| T::schema(version, context)) } } impl ReprC for Box {} -impl Serialize for Box { +impl Serialize for Box { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } @@ -6294,34 +6335,34 @@ impl Deserialize for Box { use std::rc::Rc; impl ReprC for Rc {} -impl WithSchema for Rc { +impl WithSchema for Rc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) + context.possible_recursion::(|context| T::schema(version, context)) } } -impl Serialize for Rc { +impl Serialize for Rc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Rc { +impl Deserialize for Rc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Rc::new(T::deserialize(deserializer)?)) } } impl ReprC for Arc {} -impl WithSchema for Arc { +impl WithSchema for Arc { fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { - context.possible_recursion::(|context|T::schema(version, context)) + context.possible_recursion::(|context| T::schema(version, context)) } } -impl Serialize for Arc { +impl Serialize for Arc { fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { self.deref().serialize(serializer) } } -impl Deserialize for Arc { +impl Deserialize for Arc { fn deserialize(deserializer: &mut Deserializer) -> Result { Ok(Arc::new(T::deserialize(deserializer)?)) } From 1160175d655ead79b7c48e7bf4e5187e5f03c45e Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 17:38:49 +0200 Subject: [PATCH 10/15] Fix merge conflict --- savefile-derive/src/lib.rs | 2 +- savefile-derive/src/savefile_abi.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/savefile-derive/src/lib.rs b/savefile-derive/src/lib.rs index d059324..b36d502 100644 --- a/savefile-derive/src/lib.rs +++ b/savefile-derive/src/lib.rs @@ -256,7 +256,7 @@ pub fn savefile_abi_exportable( let uses = quote_spanned! { defspan => extern crate savefile; extern crate savefile_abi; - use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, get_schema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; + use savefile::prelude::{ReprC, Schema, SchemaPrimitive, WithSchema, WithSchemaContext, get_schema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition}; use savefile_abi::{parse_return_value_impl,abi_result_receiver,abi_boxed_trait_receiver, FlexBuffer, AbiExportable, TraitObject, PackagedTraitObject, Owning, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, AbiProtocol, abi_entry_light}; use std::collections::HashMap; use std::mem::MaybeUninit; diff --git a/savefile-derive/src/savefile_abi.rs b/savefile-derive/src/savefile_abi.rs index f36fc31..95cc4b1 100644 --- a/savefile-derive/src/savefile_abi.rs +++ b/savefile-derive/src/savefile_abi.rs @@ -707,7 +707,7 @@ impl ArgType { caller_arg_serializer1: quote! { #arg_name.serialize(&mut serializer) }, - schema: quote!( <#arg_type as WithSchema>::schema(version, context) ), + schema: quote!( get_schema::<#arg_type>(version) ), known_size_align1: if compile_time_check_reprc(arg_type) { compile_time_size(arg_type) } else { @@ -996,7 +996,7 @@ pub(super) fn generate_method_definitions( let result_default; let return_ser_temp; if no_return { - return_value_schema = quote!(<() as WithSchema>::schema(0, &mut WithSchemaContext::new())); + return_value_schema = quote!(get_schema::<()>(0)); ret_deserializer = quote!(()); //Zero-sized, no deserialize actually needed ret_serialize = quote!(()); caller_return_type = quote!(()); From 895bf313d13f2d4b418bc7aed1aa3a1cac464233 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 22:38:22 +0200 Subject: [PATCH 11/15] feat: Support for serializing 'str' --- README.md | 16 ++++++--- compile_tests/Cargo.lock | 6 ++-- .../tests/compile-fail/cow_smuggler.rs | 26 ++++++++++++++ savefile-test/src/lib.rs | 34 ++++++++++++++++++ .../src/savefile_abi_test/basic_abi_tests.rs | 34 ++++++++++++++++++ savefile/src/lib.rs | 36 +++++++++++++++++-- 6 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 compile_tests/tests/compile-fail/cow_smuggler.rs diff --git a/README.md b/README.md index c82ed4e..3f6e1fa 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,17 @@ recursion. See documentation of WithSchemaContext. 1.6: Several savefile trait implementations have now gained 'static-bounds. For example, Box, Vec and many more now require T:'static. There was no such bound before, but since references cannot be deserialized, it was still typically not possible to deserialize -anything containing a reference. In principle, someone could have implemented Deserialize -for some type, leaking memory and returning an instance with a non-static lifetime. However, -this is a very niche use, and it seems much more likely that deserializing types with arbitrary -lifetimes is an error. Please file an issue if you have an use-case for deserializing types -with lifetimes. +anything containing a reference. + +It turns out there is a usecase for serializing objects with lifetimes: Things like +Cow can be useful. Everything the deserializer produces must still have 'static lifetime in +practice, because of how the Deserialize trait is defined (there's no other lifetime the +return value can have). + +Serializing things with lifetimes is still possible, the only place where 'static is required +is the contents of containers such as Box, Vec etc. The reason is that the new recursion +support needs to be able to create TypeIds, and this is only possible for objects with +'static lifetime. ## 0.17.0-beta.13 diff --git a/compile_tests/Cargo.lock b/compile_tests/Cargo.lock index 3974e67..43b62f2 100644 --- a/compile_tests/Cargo.lock +++ b/compile_tests/Cargo.lock @@ -463,7 +463,7 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "savefile" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" dependencies = [ "arrayvec", "bit-set", @@ -479,7 +479,7 @@ dependencies = [ [[package]] name = "savefile-abi" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" dependencies = [ "byteorder", "libloading", @@ -489,7 +489,7 @@ dependencies = [ [[package]] name = "savefile-derive" -version = "0.17.0-beta.13" +version = "0.17.0-beta.14" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/compile_tests/tests/compile-fail/cow_smuggler.rs b/compile_tests/tests/compile-fail/cow_smuggler.rs new file mode 100644 index 0000000..475b540 --- /dev/null +++ b/compile_tests/tests/compile-fail/cow_smuggler.rs @@ -0,0 +1,26 @@ +extern crate savefile; +extern crate savefile_abi; +extern crate savefile_derive; +use std::collections::HashMap; +use savefile::prelude::*; +use savefile::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Debug; +use std::io::{BufWriter, Cursor, Write}; +use savefile_abi::AbiConnection; +use savefile_derive::savefile_abi_exportable; +use std::borrow::Cow; +#[savefile_abi_exportable(version = 0)] +pub trait CowSmuggler { + fn smuggle(&mut self, x: Cow) -> Cow; +} +impl CowSmuggler for () { + fn smuggle(&mut self, x: Cow) -> Cow { + x +//~^ 18:9: 18:10: lifetime may not live long enough + } + // If someone calls smuggle(..) with a reference to a long-lived, but not static item, + // it is important to understand that the returned Cow cannot have the same lifetime. + // it may have to be deserialized, and will then be an owned value. It will not be a reference + // with the same lifetime as the argument. +} +fn main() {} \ No newline at end of file diff --git a/savefile-test/src/lib.rs b/savefile-test/src/lib.rs index f14135d..bec40a0 100644 --- a/savefile-test/src/lib.rs +++ b/savefile-test/src/lib.rs @@ -1330,6 +1330,40 @@ pub fn test_cow_borrowed() { let x: Cow = Cow::Borrowed(&borrow); assert_roundtrip(x); } +#[test] +pub fn test_cow_str_ref() { + let borrow = "world".to_string(); + let x: Cow = Cow::Borrowed(&borrow); + assert_roundtrip(x); +} +#[test] +pub fn test_cow_str_owned() { + let st = "world".to_string(); + let x: Cow = Cow::Owned(st); + assert_roundtrip(x); +} + +#[test] +pub fn test_verify_cow_deserialize_not_borrowed() { + let mut f = Cursor::new(Vec::new()); + { + let borrow = "Daisy".to_string(); + let x: Cow = Cow::Borrowed(&borrow); + let mut bufw = BufWriter::new(&mut f); + { + Serializer::save(&mut bufw, 0, &x, false).unwrap(); + } + bufw.flush().unwrap(); + } + f.set_position(0); + { + let roundtripped:Cow = Deserializer::load(&mut f, 0).unwrap(); + match roundtripped { + Cow::Borrowed(_) => {panic!("Roundtripped Cow should not be borrowed!")} + Cow::Owned(_) => {} + } + } +} #[derive(Savefile, Debug, PartialEq)] struct SomethingWithPathbufIn { diff --git a/savefile-test/src/savefile_abi_test/basic_abi_tests.rs b/savefile-test/src/savefile_abi_test/basic_abi_tests.rs index c34dbbe..5b30573 100644 --- a/savefile-test/src/savefile_abi_test/basic_abi_tests.rs +++ b/savefile-test/src/savefile_abi_test/basic_abi_tests.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cell::{Cell, UnsafeCell}; use std::io::Cursor; @@ -262,3 +263,36 @@ fn bench_count_chars_str(b: &mut Bencher) { } b.iter(move || conn.count_chars_str(std::hint::black_box(&s))) } + +#[savefile_abi_exportable(version = 0)] +pub trait CowSmuggler { + // Specifying &'static is supported. Otherwise, the lifetime + // becomes artificially short in this case (it becomes that of &self). + fn smuggle2(&mut self, x: Cow) -> Cow<'static, str>; + // In this case, the lifetime of Cow is that of &mut self. + // (Rust lifetime elision rules). + fn smuggle(&mut self, x: Cow) -> Cow; +} +impl CowSmuggler for () { + fn smuggle(&mut self, x: Cow) -> Cow { + (*x).to_owned().into() + } + fn smuggle2(&mut self, x: Cow) -> Cow<'static, str> { + (*x).to_owned().into() + } +} + +#[test] +fn test_cow_smuggler() { + let boxed: Box = Box::new(()); + let mut conn = AbiConnection::from_boxed_trait(boxed).unwrap(); + assert_eq!(conn.smuggle("hej".into()), "hej"); + assert_eq!(conn.smuggle("hej".to_string().into()), "hej"); + + assert_eq!(conn.smuggle2("hej".into()), "hej"); + assert_eq!(conn.smuggle2("hej".to_string().into()), "hej"); + + let static_ret : Cow<'static, str> = conn.smuggle2("hej".into()); + assert_eq!(static_ret, "hej"); + +} \ No newline at end of file diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index 74cbf02..a19f396 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -1298,7 +1298,7 @@ impl<'a, T: 'a + WithSchema + ToOwned + ?Sized> Deserialize for Cow<'a, T> where T::Owned: Deserialize, { - fn deserialize(deserializer: &mut Deserializer) -> Result { + fn deserialize(deserializer: &mut Deserializer) -> Result, SavefileError> { Ok(Cow::Owned(::Owned::deserialize(deserializer)?)) } } @@ -2271,7 +2271,6 @@ impl WithSchemaContext { /// fn schema(version: u32, context: &mut WithSchemaContext) -> Schema { /// context.possible_recursion::>(|context| Schema::Boxed(Box::new(T::schema(version, context)))) /// } - /// /// } /// ``` /// @@ -2362,6 +2361,18 @@ impl Introspect for NullIntrospectable { 0 } } + +impl<'a> IntrospectItem<'a> for str { + fn key(&self) -> &str { + self + } + + fn val(&self) -> &dyn Introspect { + &THE_NULL_INTROSPECTABLE + } +} + + impl<'a> IntrospectItem<'a> for String { fn key(&self) -> &str { self @@ -4075,13 +4086,26 @@ impl Deserialize for Schema { Ok(schema) } } +impl WithSchema for str { + fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { + Schema::Primitive(SchemaPrimitive::schema_string(VecOrStringLayout::Unknown)) + } +} impl WithSchema for String { fn schema(_version: u32, _context: &mut WithSchemaContext) -> Schema { Schema::Primitive(SchemaPrimitive::schema_string(calculate_string_memory_layout())) } } +impl Introspect for str { + fn introspect_value(&self) -> String { + self.to_string() + } + fn introspect_child(&self, _index: usize) -> Option> { + None + } +} impl Introspect for String { fn introspect_value(&self) -> String { self.to_string() @@ -4096,9 +4120,17 @@ impl Serialize for String { serializer.write_string(self) } } +impl Serialize for str { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), SavefileError> { + serializer.write_string(self) + } +} impl ReprC for String {} +impl ReprC for str {} + + impl Deserialize for String { fn deserialize(deserializer: &mut Deserializer) -> Result { deserializer.read_string() From 54b0335eae19e34b165d1ecf3236460b31811565 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 23:13:12 +0200 Subject: [PATCH 12/15] fix: Fix bug in deserializing old schemas --- savefile/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index a19f396..cbaeafd 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -3672,8 +3672,8 @@ impl Deserialize for SchemaStruct { let l = deserializer.read_usize()?; Ok(SchemaStruct { dbg_name, - size: <_ as Deserialize>::deserialize(deserializer)?, - alignment: <_ as Deserialize>::deserialize(deserializer)?, + size: if deserializer.file_version > 0 {<_ as Deserialize>::deserialize(deserializer)?} else {None}, + alignment: if deserializer.file_version > 0 {<_ as Deserialize>::deserialize(deserializer)?} else {None}, fields: { let mut ret = Vec::new(); for _ in 0..l { @@ -4046,7 +4046,8 @@ impl Serialize for Schema { impl ReprC for Schema {} impl Deserialize for Schema { fn deserialize(deserializer: &mut Deserializer) -> Result { - let schema = match deserializer.read_u8()? { + let x = deserializer.read_u8()?; + let schema = match x { 1 => Schema::Struct(SchemaStruct::deserialize(deserializer)?), 2 => Schema::Enum(SchemaEnum::deserialize(deserializer)?), 3 => Schema::Primitive(SchemaPrimitive::deserialize(deserializer)?), From af125056ebe9ccf0e947af8e3ca2024816c775e3 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Tue, 30 Apr 2024 23:32:24 +0200 Subject: [PATCH 13/15] fix: Improve documentation --- .../tests/compile-fail/bad_export.rs | 29 +++++++++++ .../tests/compile-fail/bad_export2.rs | 26 ++++++++++ .../tests/compile-fail/bad_export3.rs | 31 ++++++++++++ savefile-abi/src/lib.rs | 12 +++-- .../argument_backward_compatibility.rs | 48 +++++++++++++++++++ ...rd_compatibility__abi_schemas_get_def.snap | 4 ++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 compile_tests/tests/compile-fail/bad_export.rs create mode 100644 compile_tests/tests/compile-fail/bad_export2.rs create mode 100644 compile_tests/tests/compile-fail/bad_export3.rs diff --git a/compile_tests/tests/compile-fail/bad_export.rs b/compile_tests/tests/compile-fail/bad_export.rs new file mode 100644 index 0000000..0794278 --- /dev/null +++ b/compile_tests/tests/compile-fail/bad_export.rs @@ -0,0 +1,29 @@ +extern crate savefile; +extern crate savefile_abi; +extern crate savefile_derive; +use std::collections::HashMap; +use savefile::prelude::*; +use savefile::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Debug; +use std::io::{BufWriter, Cursor, Write}; +use savefile_abi::AbiConnection; +use savefile_derive::savefile_abi_exportable; +use savefile_derive::savefile_abi_export; + +#[savefile_abi_exportable(version = 0)] +pub trait ExampleTrait { + fn get(&mut self, x: u32) -> u32; +} +struct ExampleImpl { + +} +impl ExampleTrait for ExampleImpl { + fn get(&mut self, x: u32) -> u32 { + x + } +} +// Test what happens when you mix up the ordering of trait and impl: +savefile_abi_export!(ExampleTrait, ExampleImpl); +//~^ 26:1: 26:48: expected trait, found struct `ExampleImpl` [E0404] + +fn main() {} \ No newline at end of file diff --git a/compile_tests/tests/compile-fail/bad_export2.rs b/compile_tests/tests/compile-fail/bad_export2.rs new file mode 100644 index 0000000..7be080f --- /dev/null +++ b/compile_tests/tests/compile-fail/bad_export2.rs @@ -0,0 +1,26 @@ +extern crate savefile; +extern crate savefile_abi; +extern crate savefile_derive; +use std::collections::HashMap; +use savefile::prelude::*; +use savefile::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Debug; +use std::io::{BufWriter, Cursor, Write}; +use savefile_abi::AbiConnection; +use savefile_derive::savefile_abi_exportable; +use savefile_derive::savefile_abi_export; + +#[savefile_abi_exportable(version = 0)] +pub trait ExampleTrait { + fn get(&mut self, x: u32) -> u32; +} +#[derive(Default)] +struct ExampleImpl { + +} + +// Forgot to implement trait +savefile_abi_export!(ExampleImpl, ExampleTrait); +//~^ 23:1: 23:48: the trait bound `ExampleImpl: ExampleTrait` is not satisfied [E0277] + +fn main() {} \ No newline at end of file diff --git a/compile_tests/tests/compile-fail/bad_export3.rs b/compile_tests/tests/compile-fail/bad_export3.rs new file mode 100644 index 0000000..9af21e0 --- /dev/null +++ b/compile_tests/tests/compile-fail/bad_export3.rs @@ -0,0 +1,31 @@ +extern crate savefile; +extern crate savefile_abi; +extern crate savefile_derive; +use std::collections::HashMap; +use savefile::prelude::*; +use savefile::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Debug; +use std::io::{BufWriter, Cursor, Write}; +use savefile_abi::AbiConnection; +use savefile_derive::savefile_abi_exportable; +use savefile_derive::savefile_abi_export; + +#[savefile_abi_exportable(version = 0)] +pub trait ExampleTrait { + fn get(&mut self, x: u32) -> u32; +} +struct ExampleImpl { + +} + +impl ExampleTrait for ExampleImpl { + fn get(&mut self, x: u32) -> u32 { + x + } +} + +// Forgot to implement Default +savefile_abi_export!(ExampleImpl, ExampleTrait); +//~^ 28:1: 28:48: the trait bound `ExampleImpl: Default` is not satisfied [E0277] + +fn main() {} \ No newline at end of file diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index 35a15eb..6b56581 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -233,10 +233,11 @@ This has a performance penalty, and may require heap allocation. * It supports trait objects as arguments, including FnMut() and Fn(). - * Boxed trait objects can be transferred across FFI-boundaries, passing ownership, while - still not invoking UB if the object is dropped on the other side of the FFI-boundary. + * Boxed trait objects, including Fn-traits, can be transferred across FFI-boundaries, passing + ownership, while still not invoking UB if the object is dropped on the other side of the + FFI-boundary. - * It requires enums to be `#[repr(C,u8)]` in order to pass them by reference. Other enums + * It requires enums to be `#[repr(uX)]` in order to pass them by reference. Other enums will still work correctly, but will be serialized under the hood at a performance penalty. * It places severe restrictions on types of arguments, since they must be serializable @@ -274,8 +275,9 @@ One thing to be aware of is that, at present, the AbiConnection::load_shared_lib is not marked as unsafe. However, if the .so-file given as argument is corrupt, using this method can cause any amount of UB. Thus, it could be argued that it should be marked unsafe. -However, the same is true for _any_ rust shared library. We are simply reliant on the -compiler and all dependencies we use being implemented correctly. Thus, it has been +However, the same is true for _any_ shared library used by a rust program, including the +system C-library. It is also true that rust programs rely on the rust +compiler being implemented correctly. Thus, it has been judged that the issue of corrupt binary files is beyond the scope of safety for Savefile-Abi. As long as the shared library is a real Savefile-Abi shared library, it should be sound to use, diff --git a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs index fa72f81..02c741c 100644 --- a/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs +++ b/savefile-test/src/savefile_abi_test/argument_backward_compatibility.rs @@ -4,6 +4,7 @@ use savefile_abi::RawAbiCallResult::AbiError; use savefile_abi::{verify_compatiblity, AbiConnection, AbiExportable}; use savefile_abi_test::argument_backward_compatibility::v1::{ArgInterfaceV1, EnumArgument, Implementation1}; use savefile_abi_test::argument_backward_compatibility::v2::{ArgInterfaceV2, Implementation2}; +use savefile_abi_test::basic_abi_tests::CowSmuggler; use savefile_derive::Savefile; mod v1 { @@ -23,6 +24,7 @@ mod v1 { pub trait ArgInterfaceV1 { fn sums(&self, a: Argument, b: Argument) -> u32; fn enum_arg(&self, a: EnumArgument) -> String; + fn function_existing_in_v1(&self); } #[derive(Default)] pub struct Implementation1 {} @@ -37,6 +39,8 @@ mod v1 { EnumArgument::Variant2 => "Variant2".into(), } } + fn function_existing_in_v1(&self) { + } } } @@ -71,6 +75,7 @@ mod v2 { EnumArgument::Variant3 => "Variant3".into(), } } + fn function_existing_in_v2(&self); } #[derive(Default)] @@ -79,6 +84,9 @@ mod v2 { fn sums(&self, a: ArgArgument, b: ArgArgument) -> u32 { a.data3 + a.data2 + b.data2 + b.data3 } + + fn function_existing_in_v2(&self) { + } } } @@ -181,3 +189,43 @@ pub fn test_caller_has_newer_version_and_uses_enum_that_callee_doesnt_have() { assert_eq!(conn1.enum_arg(v2::EnumArgument::Variant3), "Variant3".to_string()); } + +#[test] +#[should_panic(expected = "'function_existing_in_v2' does not exist in implementation.")] +pub fn test_caller_has_newer_version_calling_non_existing_function() { + let iface1: Box = Box::new(Implementation1 {}); + let conn1 = unsafe { + AbiConnection::::from_boxed_trait_for_test( + ::ABI_ENTRY, + iface1, + ) + } + .unwrap(); + conn1.function_existing_in_v2(); +} + +#[test] +#[should_panic(expected = "'function_existing_in_v1' does not exist in implementation.")] +pub fn test_caller_has_older_version_calling_non_existing_function() { + let iface2: Box = Box::new(Implementation2 {}); + let conn = unsafe { + AbiConnection::::from_boxed_trait_for_test( + ::ABI_ENTRY, + iface2, + ) + } + .unwrap(); + conn.function_existing_in_v1(); +} +#[test] +fn test_calling_function_that_is_later_removed() { + let boxed: Box = Box::new(Implementation1{}); + let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); + conn.function_existing_in_v1(); +} +#[test] +fn test_calling_function_that_is_added_in_later_version() { + let boxed: Box = Box::new(Implementation2{}); + let conn = AbiConnection::from_boxed_trait(boxed).unwrap(); + conn.function_existing_in_v2(); +} diff --git a/savefile-test/src/savefile_abi_test/snapshots/savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap b/savefile-test/src/savefile_abi_test/snapshots/savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap index 9d5f9c2..974fc69 100644 --- a/savefile-test/src/savefile_abi_test/snapshots/savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap +++ b/savefile-test/src/savefile_abi_test/snapshots/savefile_test__savefile_abi_test__argument_backward_compatibility__abi_schemas_get_def.snap @@ -57,3 +57,7 @@ methods: has_explicit_repr: false size: 1 alignment: 1 + - name: function_existing_in_v2 + info: + return_value: ZeroSize + arguments: [] From bc7e81d1db79bdb203be8c32bc9d871f5f814705 Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Wed, 1 May 2024 00:47:28 +0200 Subject: [PATCH 14/15] fix: AbiConnection cache could become corrupt if AbiConnection was used incorrectly --- savefile-abi/src/lib.rs | 15 +++++--- savefile-derive/src/serialize.rs | 4 --- savefile-test/src/ext_benchmark.rs | 55 ++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index 6b56581..21702d7 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -314,6 +314,7 @@ use std::path::Path; use std::ptr::null; use std::sync::{Mutex, MutexGuard}; use std::{ptr, slice}; +use std::any::TypeId; use byteorder::ReadBytesExt; use libloading::{Library, Symbol}; @@ -852,7 +853,7 @@ pub fn parse_return_value_impl( /// Parse an RawAbiCallResult instance into a Result, SavefileError> . /// This is used on the caller side, and the type T will always be statically known. /// TODO: There's some duplicated code here, compare parse_return_value -pub fn parse_return_boxed_trait(outcome: &RawAbiCallResult) -> Result>, SavefileError> +pub fn parse_return_boxed_trait(outcome: &RawAbiCallResult) -> Result>, SavefileError> where T: AbiExportable + ?Sized, { @@ -874,7 +875,7 @@ static ENTRY_CACHE: Mutex< > = Mutex::new(None); static ABI_CONNECTION_TEMPLATES: Mutex< - Option>, + Option>, > = Mutex::new(None); struct Guard<'a, K: Hash + Eq, V> { @@ -971,7 +972,7 @@ pub unsafe extern "C" fn abi_result_receiver( /// Raw entry point for receiving return values from other shared libraries #[doc(hidden)] -pub unsafe extern "C" fn abi_boxed_trait_receiver(outcome: *const RawAbiCallResult, result_receiver: *mut ()) +pub unsafe extern "C" fn abi_boxed_trait_receiver(outcome: *const RawAbiCallResult, result_receiver: *mut ()) where T: AbiExportable + ?Sized, { @@ -1075,7 +1076,7 @@ fn arg_layout_compatible( } } -impl AbiConnection { +impl AbiConnection { /// Analyse the difference in definitions between the two sides, /// and create an AbiConnection #[allow(clippy::too_many_arguments)] @@ -1397,7 +1398,11 @@ impl AbiConnection { ) -> Result, SavefileError> { let mut templates = Guard::lock(&ABI_CONNECTION_TEMPLATES); - let template = match templates.entry(remote_entry) { + let typeid = TypeId::of::(); + // In principle, it would be enough to key 'templates' based on 'remote_entry'. + // However, if we do, and the user ever uses AbiConnection with the _wrong_ entry point, + // we risk poisoning the cache with erroneous data. + let template = match templates.entry((typeid,remote_entry)) { Entry::Occupied(template) => template.get().clone(), Entry::Vacant(vacant) => { let own_version = T::get_latest_version(); diff --git a/savefile-derive/src/serialize.rs b/savefile-derive/src/serialize.rs index e7cde57..3541b49 100644 --- a/savefile-derive/src/serialize.rs +++ b/savefile-derive/src/serialize.rs @@ -41,10 +41,6 @@ pub(super) fn savefile_derive_crate_serialize(input: DeriveInput) -> TokenStream let expanded = match &input.data { &syn::Data::Enum(ref enum1) => { let mut output = Vec::new(); - //let variant_count = enum1.variants.len(); - /*if variant_count >= 256 { - panic!("This library is not capable of serializing enums with 256 variants or more. Our deepest apologies, we thought no-one would ever create such an enum!"); - }*/ let enum_size = get_enum_size(&input.attrs, enum1.variants.len()); for (var_idx_usize, variant) in enum1.variants.iter().enumerate() { diff --git a/savefile-test/src/ext_benchmark.rs b/savefile-test/src/ext_benchmark.rs index 6ae1256..b739644 100644 --- a/savefile-test/src/ext_benchmark.rs +++ b/savefile-test/src/ext_benchmark.rs @@ -1,3 +1,7 @@ +use std::hint::black_box; +use test::Bencher; +use rand::Rng; + mod savefile_test_bad_schema { use savefile::prelude::*; @@ -169,3 +173,54 @@ mod savefile_benchmark_no_reprc { }) } } + +#[derive(Savefile,PartialEq,Default)] +pub struct Vector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} +#[derive(Savefile,PartialEq,Default)] +pub struct Triangle { + pub v0: Vector3, + pub v1: Vector3, + pub v2: Vector3, + pub normal: Vector3, +} + +#[derive(Savefile,PartialEq)] +pub struct Mesh { + pub triangles: Vec, +} +#[cfg(test)] +pub fn generate_mesh() -> Mesh { + + let mut mesh = Mesh { + triangles: vec![] + }; + const TRIANGLES: usize = 125_000; + for _ in 0..TRIANGLES { + mesh.triangles.push(Triangle::default()) + } + + mesh +} +#[cfg(feature = "nightly")] +#[bench] +fn bench_ext_triangle(b: &mut Bencher) { + let mesh = generate_mesh(); + let mut encoded: Vec = Vec::new(); + b.iter(move || { + savefile::save_noschema(black_box(&mut encoded), 0, black_box(&mesh)).unwrap(); + }) +} +#[test] +fn test_triangle() { + use savefile::ReprC; + assert!( unsafe { Triangle::repr_c_optimization_safe(0).is_yes() } ); + let mesh = generate_mesh(); + + let mut encoded = Vec::new(); + encoded.clear(); + savefile::save_noschema(black_box(&mut encoded), 0, black_box(&mesh)).unwrap(); +} \ No newline at end of file From 9fd1d5617d4c0136353259df3e50672dbea627dc Mon Sep 17 00:00:00 2001 From: Anders Musikka Date: Wed, 1 May 2024 00:48:36 +0200 Subject: [PATCH 15/15] chore: release (#47) --- Cargo.lock | 6 +++--- savefile-abi/CHANGELOG.md | 6 ++++++ savefile-abi/Cargo.toml | 6 +++--- savefile-derive/CHANGELOG.md | 5 +++++ savefile-derive/Cargo.toml | 2 +- savefile-test/Cargo.toml | 2 +- savefile/CHANGELOG.md | 8 ++++++++ savefile/Cargo.toml | 6 +++--- 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91a4366..effadde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "savefile" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" dependencies = [ "arrayvec", "bit-set", @@ -471,7 +471,7 @@ dependencies = [ [[package]] name = "savefile-abi" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" dependencies = [ "byteorder", "libloading", @@ -511,7 +511,7 @@ dependencies = [ [[package]] name = "savefile-derive" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/savefile-abi/CHANGELOG.md b/savefile-abi/CHANGELOG.md index c9018c7..45fe1b2 100644 --- a/savefile-abi/CHANGELOG.md +++ b/savefile-abi/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0-beta.15](https://github.com/avl/savefile/compare/savefile-abi-v0.17.0-beta.14...savefile-abi-v0.17.0-beta.15) - 2024-04-30 + +### Fixed +- AbiConnection cache could become corrupt if AbiConnection was used incorrectly +- Improve documentation + ## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-abi-v0.17.0-beta.13...savefile-abi-v0.17.0-beta.14) - 2024-04-30 ### Other diff --git a/savefile-abi/Cargo.toml b/savefile-abi/Cargo.toml index c7100c9..6771379 100644 --- a/savefile-abi/Cargo.toml +++ b/savefile-abi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile-abi" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" edition = "2021" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile-abi/" @@ -16,7 +16,7 @@ keywords = ["dylib", "dlopen", "ffi"] license = "MIT/Apache-2.0" [dependencies] -savefile = { path="../savefile", version = "=0.17.0-beta.14" } -savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.14" } +savefile = { path="../savefile", version = "=0.17.0-beta.15" } +savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.15" } byteorder = "1.4" libloading = "0.8" diff --git a/savefile-derive/CHANGELOG.md b/savefile-derive/CHANGELOG.md index 118a115..649c2bd 100644 --- a/savefile-derive/CHANGELOG.md +++ b/savefile-derive/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0-beta.15](https://github.com/avl/savefile/compare/savefile-derive-v0.17.0-beta.14...savefile-derive-v0.17.0-beta.15) - 2024-04-30 + +### Fixed +- AbiConnection cache could become corrupt if AbiConnection was used incorrectly + ## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-derive-v0.17.0-beta.13...savefile-derive-v0.17.0-beta.14) - 2024-04-30 ### Fixed diff --git a/savefile-derive/Cargo.toml b/savefile-derive/Cargo.toml index 0100607..87819ca 100644 --- a/savefile-derive/Cargo.toml +++ b/savefile-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile-derive" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" authors = ["Anders Musikka "] description = "Custom derive macros for savefile crate - simple, convenient, fast, versioned, binary serialization/deserialization library." diff --git a/savefile-test/Cargo.toml b/savefile-test/Cargo.toml index f2d9279..15344b3 100644 --- a/savefile-test/Cargo.toml +++ b/savefile-test/Cargo.toml @@ -12,7 +12,7 @@ nightly=["savefile/nightly"] [dependencies] savefile = { path = "../savefile", features = ["size_sanity_checks", "encryption", "compression","bit-set","bit-vec","rustc-hash","serde_derive", "quickcheck"]} -savefile-derive = { path = "../savefile-derive", version = "=0.17.0-beta.14" } +savefile-derive = { path = "../savefile-derive", version = "=0.17.0-beta.15" } savefile-abi = { path = "../savefile-abi" } bit-vec = "0.6" arrayvec="0.7" diff --git a/savefile/CHANGELOG.md b/savefile/CHANGELOG.md index a96abde..88a3240 100644 --- a/savefile/CHANGELOG.md +++ b/savefile/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.0-beta.15](https://github.com/avl/savefile/compare/savefile-v0.17.0-beta.14...savefile-v0.17.0-beta.15) - 2024-04-30 + +### Added +- Support for serializing 'str' + +### Fixed +- Fix bug in deserializing old schemas + ## [0.17.0-beta.14](https://github.com/avl/savefile/compare/savefile-v0.17.0-beta.13...savefile-v0.17.0-beta.14) - 2024-04-30 ### Fixed diff --git a/savefile/Cargo.toml b/savefile/Cargo.toml index fc2b2fe..b5f4121 100644 --- a/savefile/Cargo.toml +++ b/savefile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile" -version = "0.17.0-beta.14" +version = "0.17.0-beta.15" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile/" homepage = "https://github.com/avl/savefile/" @@ -54,13 +54,13 @@ bit-set = {version = "0.5", optional = true} rustc-hash = {version = "1.1", optional = true} memoffset = "0.9" byteorder = "1.4" -savefile-derive = {path="../savefile-derive", version = "=0.17.0-beta.14", optional = true } +savefile-derive = {path="../savefile-derive", version = "=0.17.0-beta.15", optional = true } serde_derive = {version= "1.0", optional = true} serde = {version= "1.0", optional = true} quickcheck = {version= "1.0", optional = true} [dev-dependencies] -savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.14" } +savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.15" } [build-dependencies] rustc_version="0.2"