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/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/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/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-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-abi/src/lib.rs b/savefile-abi/src/lib.rs index 35a15eb..21702d7 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, @@ -312,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}; @@ -850,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, { @@ -872,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> { @@ -969,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, { @@ -1073,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)] @@ -1395,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/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-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/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-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 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/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/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-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: [] 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" diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index 74cbf02..cbaeafd 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 @@ -3661,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 { @@ -4035,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)?), @@ -4075,13 +4087,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 +4121,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()