Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
avl committed Apr 10, 2024
1 parent 6999b13 commit 0d4ca5a
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 121 deletions.
6 changes: 0 additions & 6 deletions savefile-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,8 @@ keywords = ["dylib", "dlopen", "ffi"]

license = "MIT/Apache-2.0"


[features]
serde_derive = ["dep:serde_derive","serde"]

[dependencies]
savefile = { path="../savefile", version = "=0.17.0-beta.7"}
savefile-derive = { path="../savefile-derive", version = "=0.17.0-beta.7"}
byteorder = "1.4"
libloading = "0.8"
serde_derive = {version= "1.0", optional = true}
serde = {version= "1.0", optional = true}
107 changes: 4 additions & 103 deletions savefile-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,115 +312,14 @@ use std::collections::HashMap;
use std::hash::Hash;
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
use savefile::{CURRENT_SAVEFILE_LIB_VERSION, Deserialize, Deserializer, diff_schema, LittleEndian, load_file_noschema, load_noschema, save_file_noschema, SavefileError, Schema, Serializer};
use savefile_derive::Savefile;
use savefile::{AbiMethodInfo, AbiTraitDefinition, CURRENT_SAVEFILE_LIB_VERSION, Deserialize, Deserializer, diff_schema, LittleEndian, load_file_noschema, load_noschema, save_file_noschema, SavefileError, Serializer};

use byteorder::ReadBytesExt;
use libloading::{Library, Symbol};

#[cfg(feature = "serde_derive")]
use serde_derive::{Serialize, Deserialize};



/// The definition of an argument to a method
#[derive(Savefile, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct AbiMethodArgument {
/// The schema (type) of the argument. This contains
/// primarily the on-disk serialized format, but also
/// contains information that can allow savefile-abi to determine
/// if memory layouts are the same.
pub schema: Schema,
/// False if this type cannot be sent as a reference
pub can_be_sent_as_ref: bool,
}


/// Return value and argument types for a method
#[derive(Savefile, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct AbiMethodInfo {
/// The return value type of the method
pub return_value: Schema,
/// The arguments of the method.
pub arguments: Vec<AbiMethodArgument>,
}

/// A method exposed through savefile-abi.
/// Contains a name, and a signature.
#[derive(Savefile, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct AbiMethod {
/// The name of the method
pub name: String,
/// The function signature
pub info: AbiMethodInfo
}

/// Defines a dyn trait, basically
#[derive(Savefile, Default, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
pub struct AbiTraitDefinition {
/// The name of the trait
pub name: String,
/// The set of methods available on the trait
pub methods: Vec<AbiMethod>
}

impl AbiTraitDefinition {
/// Verify that the 'self' trait definition is compatible with the 'old' definition.
/// Note, this routine ignores methods which only exist in 'self'.
/// The motivation is that older clients won't call them. Of course, a newer client
/// might call such a method, but this will be detected at runtime, and will panic.
/// However, it is hard to do very much better than this.
///
/// This routine will flag an error if a method that used to exist, no longer does.
/// Note that the _existence_ of methods is not itself versioned with a version number.
///
/// The version number is only for the data types of the arguments.
///
fn verify_compatible_with_old_impl(&self, old_version: u32, old: &AbiTraitDefinition) -> Result<(), String> {

for old_method in old.methods.iter() {
let Some(new_method) = self.methods.iter().find(|x|x.name == old_method.name) else {
return Err(format!("In trait {}, the method {} existed in version {}, but has been removed. This is not a backward-compatible change.",
self.name, old_method.name, old_version,
));
};
if new_method.info.arguments.len() != old_method.info.arguments.len() {
return Err(format!("In trait {}, method {}, the number of arguments has changed from {} in version {} to {}. This is not a backward-compatible change.",
self.name, old_method.name, old_method.info.arguments.len(), old_version, new_method.info.arguments.len()
));
}
if let Some(diff) = diff_schema(&new_method.info.return_value, &old_method.info.return_value, "".into()) {
return Err(format!("In trait {}, method {}, the return value type has changed from version {}: {}. This is not a backward-compatible change.",
self.name, old_method.name, old_version, diff
));
}
for (arg_index, (new_arg, old_arg)) in new_method.info.arguments.iter().zip(old_method.info.arguments.iter()).enumerate() {
if let Some(diff) = diff_schema(&new_arg.schema, &old_arg.schema, "".into()) {
return Err(format!("In trait {}, method {}, argument {}, the type has changed from version {}: {}. This is not a backward-compatible change.",
self.name, old_method.name, arg_index , old_version, diff
));
}
}
}

if self.name != old.name {
return Err(format!("Schema name has changed from {} to {}. This is not actually a compatibility problem, but still indicative of some sort of fault somewhere.", old.name, self.name));
}
Ok(())
}

/// Verify that 'self' represents a newer version of a trait, that is backward compatible
/// with 'old'. 'old_version' is the version number of the old version being inspected.
/// To guarantee compatibility, all versions must be checked
pub fn verify_backward_compatible(&self, old_version: u32, old: &AbiTraitDefinition) -> Result<(), SavefileError> {
self.verify_compatible_with_old_impl(old_version, old).map_err(|x|{
SavefileError::IncompatibleSchema {message: x}
})
}
}

/// This trait is meant to be exported for a 'dyn SomeTrait'.
/// It can be automatically implemented by using the
Expand Down Expand Up @@ -1016,6 +915,8 @@ impl<T:AbiExportable+?Sized> AbiConnection<T> {
for caller_native_method in caller_native_definition.methods.into_iter() {
let Some((callee_native_method_number, callee_native_method)) = callee_native_definition.methods.iter().enumerate().find(|x|x.1.name == caller_native_method.name) else {

println!("Method not found: {:?}", caller_native_method.name);
println!("CAlle: {:#?}", callee_native_definition);
methods.push(AbiConnectionMethod{
method_name: caller_native_method.name,
caller_info: caller_native_method.info,
Expand Down
8 changes: 4 additions & 4 deletions savefile-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1373,10 +1373,10 @@ fn generate_method_definitions(
} else {
caller_fn_arg_list.push(quote!{#arg_name : &dyn #fndef });
}
let temp_trait_name_str = temp_trait_name.to_string();
//let temp_trait_name_str = temp_trait_name.to_string();
metadata_arguments.push(quote!{
AbiMethodArgument {
schema: Schema::BoxedTrait(#temp_trait_name_str.to_string()),
schema: Schema::FnClosure(#ismut, <dyn #temp_trait_name as AbiExportable >::get_definition(version)),
can_be_sent_as_ref: true,
}
})
Expand Down Expand Up @@ -1508,8 +1508,8 @@ pub fn savefile_abi_exportable(attr: proc_macro::TokenStream, input: proc_macro:
let uses = quote_spanned! { defspan =>
extern crate savefile;
extern crate savefile_abi;
use savefile::prelude::{Schema, SchemaPrimitive, WithSchema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian};
use savefile_abi::{abi_result_receiver, FlexBuffer, AbiExportable, TraitObject, AbiTraitDefinition, PackagedTraitObject, Owning, AbiMethodArgument, AbiMethod, AbiMethodInfo, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, parse_return_value, AbiProtocol, abi_entry_light};
use savefile::prelude::{Schema, SchemaPrimitive, WithSchema, Serializer, Serialize, Deserializer, Deserialize, SavefileError, deserialize_slice_as_vec, ReadBytesExt,LittleEndian,AbiMethodArgument, AbiMethod, AbiMethodInfo,AbiTraitDefinition};
use savefile_abi::{abi_result_receiver, FlexBuffer, AbiExportable, TraitObject, PackagedTraitObject, Owning, AbiErrorMsg, RawAbiCallResult, AbiConnection, AbiConnectionMethod, parse_return_value, AbiProtocol, abi_entry_light};
use std::collections::HashMap;
use std::mem::MaybeUninit;
use std::io::Cursor;
Expand Down
2 changes: 1 addition & 1 deletion savefile-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,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.7" }
savefile-abi = { path = "../savefile-abi" , features = [ "serde_derive" ] }
savefile-abi = { path = "../savefile-abi" }
bit-vec = "0.6"
arrayvec="0.7"
smallvec="*"
Expand Down
53 changes: 53 additions & 0 deletions savefile-test/src/savefile_abi_test/closure_tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
#![allow(non_camel_case_types)]
use savefile_abi::{AbiConnection, AbiExportable};
use savefile_abi_test::basic_abi_tests::{CallbackImpl, TestInterface, TestInterfaceImpl};
use savefile_abi_test::closure_tests::new_version::ExampleNewer;

#[derive(Savefile)]
pub struct CustomArg {
pub x: u32,
}

#[savefile_abi_exportable(version=0)]
pub trait Example {
fn call_mut_closure(&self, simple: &mut dyn FnMut(u32, &u32) -> u32);
fn call_closure(&self, simple: &dyn Fn(u32, &u32) -> u32);
fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32;
}

pub mod new_version {

#[derive(Savefile)]
pub struct CustomArg {
pub x: u32,
#[savefile_versions="1.."]
pub y: String
}
#[savefile_abi_exportable(version=1)]
pub trait ExampleNewer {
fn call_mut_closure(&self, simple: &mut dyn FnMut(u32, &u32) -> u32);
fn call_closure(&self, simple: &dyn Fn(u32, &u32) -> u32);
fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32;
}

}

struct ExampleImplementation {
Expand All @@ -19,6 +42,12 @@ impl Example for ExampleImplementation {
fn call_closure(&self, simple: & dyn Fn(u32, &u32) -> u32) {
println!("Output: {}", simple(43,&42));
}

fn call_closure_with_custom_arg(&self, simple: &dyn Fn(&CustomArg) -> u32) -> u32 {
let t = simple(&CustomArg {x: 42});
println!("Output: {}", t);
t
}
}

#[test]
Expand All @@ -44,3 +73,27 @@ fn test_mut_closure() {
assert_eq!(num_calls, 1);
}

#[test]
fn test_closure_with_custom_arg() {
let boxed: Box<dyn Example> = Box::new(ExampleImplementation{});
let conn = unsafe { AbiConnection::from_boxed_trait(<dyn Example as AbiExportable>::ABI_ENTRY, boxed).unwrap() };

let result = conn.call_closure_with_custom_arg(&|arg|{
arg.x
});
assert_eq!(result, 42);
}
#[test]
fn test_closure_with_custom_arg_call_older() {
//TODO: Figure out why the following _crashes_!
//let boxed: Box<dyn Example> = Box::new(ExampleImplementation{});
//let conn = unsafe { AbiConnection::<dyn ExampleNewer>::from_boxed_trait(<dyn Example as AbiExportable>::ABI_ENTRY, boxed).unwrap() };
let iface1 : Box<dyn Example> = Box::new(ExampleImplementation {});
let conn = unsafe { AbiConnection::<dyn ExampleNewer>::from_boxed_trait_for_test(<dyn Example>::ABI_ENTRY, iface1 ) }.unwrap();


let result = conn.call_closure_with_custom_arg(&|arg|{
arg.x
});
assert_eq!(result, 42);
}
Loading

0 comments on commit 0d4ca5a

Please sign in to comment.