#![allow(clippy::len_zero)]
#![deny(warnings)]
#![deny(missing_docs)]
#![allow(clippy::needless_late_init)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::unnecessary_cast)]
extern crate core;
extern crate savefile;
extern crate savefile_derive;
use byteorder::ReadBytesExt;
use libloading::{Library, Symbol};
use savefile::{
diff_schema, load_file_noschema, load_noschema, save_file_noschema, AbiMethodInfo, AbiTraitDefinition, Deserialize,
Deserializer, LittleEndian, SavefileError, Schema, Serializer, CURRENT_SAVEFILE_LIB_VERSION,
};
use std::any::TypeId;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::Hash;
use std::io::{Cursor, Read, Write};
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::panic::catch_unwind;
use std::path::Path;
use std::ptr::null;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::Wake;
use std::{ptr, slice};
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be used across an ABI-boundary. Try adding a `#[savefile_abi_exportable(version=X)]` attribute to the declaration of the relevant trait.",
label = "`{Self}` cannot be called across an ABI-boundary",
note = "This error probably occurred because `{Self}` occurred as a return-value or argument to a method in a trait marked with `#[savefile_abi_exportable(version=X)]`, or because savefile_abi_export!-macro was used to export `{Self}`."
)]
pub unsafe trait AbiExportable {
const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol);
fn get_definition(version: u32) -> AbiTraitDefinition;
fn get_latest_version() -> u32;
fn call(
trait_object: TraitObject,
method_number: u16,
effective_version: u32,
compatibility_mask: u64,
data: &[u8],
abi_result: *mut (),
receiver: unsafe extern "C" fn(
outcome: *const RawAbiCallResult,
result_receiver: *mut (), /* actual type: Result<T,SaveFileError>>*/
),
) -> Result<(), SavefileError>;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be the concrete type of an AbiExportable dyn trait.",
label = "Does not implement `AbiExportableImplementation`",
note = "You should not be using this trait directly, and should never see this error."
)]
pub unsafe trait AbiExportableImplementation {
const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol);
type AbiInterface: ?Sized + AbiExportable;
fn new() -> Box<Self::AbiInterface>;
}
unsafe fn destroy_trait_obj<T: AbiExportable + ?Sized>(trait_object: TraitObject) {
let mut raw_ptr: MaybeUninit<*mut T> = MaybeUninit::uninit();
ptr::copy(
&trait_object as *const TraitObject as *const MaybeUninit<*mut T>,
&mut raw_ptr as *mut MaybeUninit<*mut T>,
1,
);
let _ = Box::from_raw(raw_ptr.assume_init());
}
unsafe fn call_trait_obj<T: AbiExportable + ?Sized>(
trait_object: TraitObject,
method_number: u16,
effective_version: u32,
compatibility_mask: u64,
data: &[u8],
abi_result: *mut (),
receiver: unsafe extern "C" fn(
outcome: *const RawAbiCallResult,
result_receiver: *mut (),
),
) -> Result<(), SavefileError> {
<T>::call(
trait_object,
method_number,
effective_version,
compatibility_mask,
data,
abi_result,
receiver,
)
}
#[derive(Debug)]
pub struct AbiConnectionMethod {
pub method_name: String,
pub caller_info: AbiMethodInfo,
pub callee_method_number: Option<u16>,
pub compatibility_mask: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TraitObject {
ptr: *const (),
vtable: *const (),
}
unsafe impl Sync for TraitObject {}
unsafe impl Send for TraitObject {}
impl TraitObject {
pub fn zero() -> TraitObject {
TraitObject {
ptr: null(),
vtable: null(),
}
}
pub fn as_mut_ptr<T: ?Sized>(self) -> *mut T {
assert_eq!(
std::mem::size_of::<*mut T>(),
16,
"TraitObject must only be used with dyn trait, not any other kind of trait"
);
let mut target: MaybeUninit<*mut T> = MaybeUninit::zeroed();
unsafe {
ptr::copy(
&self as *const TraitObject as *const MaybeUninit<*mut T>,
&mut target as *mut MaybeUninit<*mut T>,
1,
);
target.assume_init()
}
}
pub fn as_const_ptr<T: ?Sized>(self) -> *const T {
assert_eq!(
std::mem::size_of::<*const T>(),
16,
"TraitObject must only be used with dyn trait, not any other kind of trait"
);
let mut target: MaybeUninit<*const T> = MaybeUninit::zeroed();
unsafe {
ptr::copy(
&self as *const TraitObject as *const MaybeUninit<*const T>,
&mut target as *mut MaybeUninit<*const T>,
1,
);
target.assume_init()
}
}
#[inline]
pub fn new_from_ptr<T: ?Sized>(raw: *const T) -> TraitObject {
debug_assert_eq!(
std::mem::size_of::<*const T>(),
16,
"TraitObject::new_from_ptr() must only be used with dyn trait, not any other kind of trait"
);
debug_assert_eq!(std::mem::size_of::<TraitObject>(), 16);
let mut trait_object = TraitObject::zero();
unsafe {
ptr::copy(
&raw as *const *const T,
&mut trait_object as *mut TraitObject as *mut *const T,
1,
)
};
trait_object
}
#[inline]
pub fn new<T: ?Sized>(input: Box<T>) -> TraitObject {
let raw = Box::into_raw(input);
debug_assert_eq!(
std::mem::size_of::<*mut T>(),
16,
"TraitObject::new() must only be used with Boxed dyn trait, not any other kind of Box"
);
debug_assert_eq!(std::mem::size_of::<TraitObject>(), 16);
let mut trait_object = TraitObject::zero();
unsafe {
ptr::copy(
&raw as *const *mut T,
&mut trait_object as *mut TraitObject as *mut *mut T,
1,
)
};
trait_object
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct AbiConnectionTemplate {
#[doc(hidden)]
pub effective_version: u32,
#[doc(hidden)]
pub methods: &'static [AbiConnectionMethod],
#[doc(hidden)]
pub entry: unsafe extern "C" fn(flag: AbiProtocol),
}
#[repr(C)]
#[derive(Debug)]
pub struct AbiConnection<T: ?Sized> {
#[doc(hidden)]
pub template: AbiConnectionTemplate,
#[doc(hidden)]
pub owning: Owning,
#[doc(hidden)]
pub trait_object: TraitObject,
#[doc(hidden)]
pub phantom: PhantomData<*const T>,
}
unsafe impl<T: ?Sized> Sync for AbiConnection<T> where T: Sync {}
unsafe impl<T: ?Sized> Send for AbiConnection<T> where T: Send {}
#[repr(C)]
#[derive(Debug)]
pub struct PackagedTraitObject {
pub trait_object: TraitObject,
pub entry: unsafe extern "C" fn(flag: AbiProtocol),
}
impl PackagedTraitObject {
pub fn new<T: AbiExportable + ?Sized>(boxed: Box<T>) -> PackagedTraitObject {
let trait_object = TraitObject::new(boxed);
let entry = T::ABI_ENTRY;
PackagedTraitObject { trait_object, entry }
}
#[inline]
pub fn new_from_ptr<T>(r: *const T) -> PackagedTraitObject
where
T: AbiExportable + ?Sized,
{
assert_eq!(std::mem::size_of::<*const T>(), 16);
let trait_object = TraitObject::new_from_ptr(r);
let entry = T::ABI_ENTRY;
PackagedTraitObject { trait_object, entry }
}
pub fn serialize(self, serializer: &mut Serializer<impl Write>) -> Result<(), SavefileError> {
serializer.write_ptr(self.trait_object.ptr)?;
serializer.write_ptr(self.trait_object.vtable)?;
serializer.write_ptr(self.entry as *const ())?;
Ok(())
}
pub unsafe fn deserialize(
deserializer: &mut Deserializer<impl Read>,
) -> Result<PackagedTraitObject, SavefileError> {
let mut trait_object = TraitObject::zero();
trait_object.ptr = deserializer.read_ptr()? as *mut ();
trait_object.vtable = deserializer.read_ptr()? as *mut ();
let entry = deserializer.read_ptr()? as *mut ();
assert_eq!(std::mem::size_of::<unsafe extern "C" fn(flag: AbiProtocol)>(), 8);
Ok(PackagedTraitObject {
trait_object,
entry: unsafe { std::mem::transmute::<*mut (), unsafe extern "C" fn(AbiProtocol)>(entry) },
})
}
}
impl<T: ?Sized> Drop for AbiConnection<T> {
fn drop(&mut self) {
match &self.owning {
Owning::Owned => unsafe {
(self.template.entry)(AbiProtocol::DropInstance {
trait_object: self.trait_object,
});
},
Owning::NotOwned => {}
}
}
}
#[repr(C)]
pub struct AbiErrorMsg {
pub error_msg_utf8: *const u8,
pub len: usize,
}
impl AbiErrorMsg {
pub fn convert_to_string(&self) -> String {
if self.len == 0 {
return "".to_string();
}
let data = unsafe { slice::from_raw_parts(self.error_msg_utf8, self.len) };
String::from_utf8_lossy(data).into()
}
}
#[repr(C, u8)]
pub enum RawAbiCallResult {
Success {
data: *const u8,
len: usize,
},
Panic(AbiErrorMsg),
AbiError(AbiErrorMsg),
}
#[repr(C, u8)]
pub enum AbiProtocol {
RegularCall {
trait_object: TraitObject,
compatibility_mask: u64,
data: *const u8,
data_length: usize,
abi_result: *mut (),
receiver: unsafe extern "C" fn(
outcome: *const RawAbiCallResult,
result_receiver: *mut (),
),
effective_version: u32,
method_number: u16,
},
InterrogateVersion {
schema_version_receiver: *mut u16,
abi_version_receiver: *mut u32,
},
InterrogateMethods {
schema_version_required: u16,
callee_schema_version_interrogated: u32,
result_receiver: *mut (),
callback: unsafe extern "C" fn(
receiver: *mut (),
callee_schema_version: u16,
data: *const u8,
len: usize,
),
},
CreateInstance {
trait_object_receiver: *mut TraitObject,
error_receiver: *mut (),
error_callback: unsafe extern "C" fn(error_receiver: *mut (), error: *const AbiErrorMsg),
},
DropInstance {
trait_object: TraitObject,
},
}
pub fn parse_return_value_impl<T>(
outcome: &RawAbiCallResult,
deserialize_action: impl FnOnce(&mut Deserializer<Cursor<&[u8]>>) -> Result<T, SavefileError>,
) -> Result<T, SavefileError> {
match outcome {
RawAbiCallResult::Success { data, len } => {
let data = unsafe { std::slice::from_raw_parts(*data, *len) };
let mut reader = Cursor::new(data);
let file_version = reader.read_u32::<LittleEndian>()?;
let mut deserializer = Deserializer {
reader: &mut reader,
file_version,
ephemeral_state: HashMap::new(),
};
deserialize_action(&mut deserializer)
}
RawAbiCallResult::Panic(AbiErrorMsg { error_msg_utf8, len }) => {
let errdata = unsafe { std::slice::from_raw_parts(*error_msg_utf8, *len) };
Err(SavefileError::CalleePanic {
msg: String::from_utf8_lossy(errdata).into(),
})
}
RawAbiCallResult::AbiError(AbiErrorMsg { error_msg_utf8, len }) => {
let errdata = unsafe { std::slice::from_raw_parts(*error_msg_utf8, *len) };
Err(SavefileError::GeneralError {
msg: String::from_utf8_lossy(errdata).into(),
})
}
}
}
pub fn parse_return_boxed_trait<T>(outcome: &RawAbiCallResult) -> Result<Box<AbiConnection<T>>, SavefileError>
where
T: AbiExportable + ?Sized + 'static,
{
parse_return_value_impl(outcome, |deserializer| {
let packaged = unsafe { PackagedTraitObject::deserialize(deserializer)? };
unsafe {
Ok(Box::new(AbiConnection::<T>::from_raw_packaged(
packaged,
Owning::Owned,
)?))
}
})
}
static LIBRARY_CACHE: Mutex<Option<HashMap<String , Library>>> = Mutex::new(None);
static ENTRY_CACHE: Mutex<
Option<HashMap<(String /*filename*/, String /*trait name*/), unsafe extern "C" fn(flag: AbiProtocol)>>,
> = Mutex::new(None);
static ABI_CONNECTION_TEMPLATES: Mutex<
Option<HashMap<(TypeId, unsafe extern "C" fn(flag: AbiProtocol)), AbiConnectionTemplate>>,
> = Mutex::new(None);
struct Guard<'a, K: Hash + Eq, V> {
guard: MutexGuard<'a, Option<HashMap<K, V>>>,
}
impl<K: Hash + Eq, V> std::ops::Deref for Guard<'_, K, V> {
type Target = HashMap<K, V>;
fn deref(&self) -> &HashMap<K, V> {
self.guard.as_ref().unwrap()
}
}
impl<K: Hash + Eq, V> std::ops::DerefMut for Guard<'_, K, V> {
fn deref_mut(&mut self) -> &mut HashMap<K, V> {
&mut *self.guard.as_mut().unwrap()
}
}
impl<'a, K: Hash + Eq, V> Guard<'a, K, V> {
pub fn lock(map: &'a Mutex<Option<HashMap<K , V>>>) -> Guard<'a, K, V> {
let mut guard = map.lock().unwrap();
if guard.is_none() {
*guard = Some(HashMap::new());
}
Guard { guard }
}
}
#[derive(Debug, Clone, Copy)]
pub enum Owning {
Owned,
NotOwned,
}
const FLEX_BUFFER_SIZE: usize = 64;
#[doc(hidden)]
pub enum FlexBuffer {
Stack {
position: usize,
data: MaybeUninit<[u8; FLEX_BUFFER_SIZE]>,
},
Spill(Vec<u8>),
}
impl Write for FlexBuffer {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
FlexBuffer::Stack { position, data } => {
if *position + buf.len() <= FLEX_BUFFER_SIZE {
let rawdata = data as *mut MaybeUninit<_> as *mut u8;
unsafe { ptr::copy(buf.as_ptr(), rawdata.add(*position), buf.len()) };
*position += buf.len();
} else {
let mut spill = Vec::with_capacity(2 * FLEX_BUFFER_SIZE + buf.len());
let rawdata = data as *mut MaybeUninit<_> as *mut u8;
let dataslice = unsafe { slice::from_raw_parts(rawdata, *position) };
spill.extend(dataslice);
spill.extend(buf);
*self = FlexBuffer::Spill(spill);
}
}
FlexBuffer::Spill(v) => v.extend(buf),
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[doc(hidden)]
pub unsafe extern "C" fn abi_result_receiver<T: Deserialize>(
outcome: *const RawAbiCallResult,
result_receiver: *mut (),
) {
let outcome = unsafe { &*outcome };
let result_receiver = unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit<Result<T, SavefileError>>) };
result_receiver.write(parse_return_value_impl(outcome, |deserializer| {
T::deserialize(deserializer)
}));
}
#[doc(hidden)]
pub unsafe extern "C" fn abi_boxed_trait_receiver<T>(outcome: *const RawAbiCallResult, result_receiver: *mut ())
where
T: AbiExportable + ?Sized + 'static,
{
let outcome = unsafe { &*outcome };
let result_receiver =
unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit<Result<Box<AbiConnection<T>>, SavefileError>>) };
result_receiver.write(parse_return_value_impl(outcome, |deserializer| {
let packaged = unsafe { PackagedTraitObject::deserialize(deserializer)? };
unsafe {
Ok(Box::new(AbiConnection::<T>::from_raw_packaged(
packaged,
Owning::Owned,
)?))
}
}));
}
#[allow(clippy::new_without_default)]
#[allow(clippy::len_without_is_empty)]
impl FlexBuffer {
pub fn new() -> FlexBuffer {
FlexBuffer::Stack {
position: 0,
data: MaybeUninit::uninit(),
}
}
pub fn as_ptr(&self) -> *const u8 {
match self {
FlexBuffer::Stack { data, .. } => data as *const MaybeUninit<_> as *const u8,
FlexBuffer::Spill(v) => v.as_ptr(),
}
}
pub fn len(&self) -> usize {
match self {
FlexBuffer::Stack { position, .. } => *position,
FlexBuffer::Spill(v) => v.len(),
}
}
}
fn arg_layout_compatible(
a_native: &Schema,
b_native: &Schema,
a_effective: &Schema,
b_effective: &Schema,
effective_version: u32,
is_return_position: bool,
) -> Result<bool, SavefileError> {
match (a_native, b_native) {
(Schema::Future(_a, _, _, _), Schema::Future(_b, _, _, _)) => {
let (
Schema::Future(effective_a2, a_send, a_sync, a_unpin),
Schema::Future(effective_b2, b_send, b_sync, b_unpin),
) = (a_effective, b_effective)
else {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
};
for (a, b, bound) in [
(*a_send, *b_send, "Send"),
(*a_sync, *b_sync, "Sync"),
(*a_unpin, *b_unpin, "Unpin"),
] {
if a && !b {
return Err(SavefileError::IncompatibleSchema{message: format!(
"Caller expects a future with an {}-bound, but implementation provides one without. This is an incompatible difference.",
bound)
});
}
}
effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
Ok(true)
}
(Schema::FnClosure(a1, _a2), Schema::FnClosure(b1, _b2)) => {
let (Schema::FnClosure(effective_a1, effective_a2), Schema::FnClosure(effective_b1, effective_b2)) =
(a_effective, b_effective)
else {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
};
effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
Ok(a1 == b1 && a1 == effective_a1 && a1 == effective_b1)
}
(Schema::Boxed(native_a), Schema::Boxed(native_b)) => {
let (Schema::Boxed(effective_a2), Schema::Boxed(effective_b2)) = (a_effective, b_effective) else {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
};
arg_layout_compatible(
native_a,
native_b,
effective_a2,
effective_b2,
effective_version,
is_return_position,
)
}
(Schema::Trait(s_a, _), Schema::Trait(s_b, _)) => {
if s_a != s_b {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
}
let (Schema::Trait(e_a2, effective_a2), Schema::Trait(e_b2, effective_b2)) = (a_effective, b_effective)
else {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
};
if e_a2 != e_b2 {
return Err(SavefileError::IncompatibleSchema {
message: "Type has changed".to_string(),
});
}
effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
Ok(true)
}
(a, b) => Ok(a.layout_compatible(b)),
}
}
impl<T: AbiExportable + ?Sized + 'static> AbiConnection<T> {
#[allow(clippy::too_many_arguments)]
fn analyze_and_create(
trait_name: &str,
remote_entry: unsafe extern "C" fn(flag: AbiProtocol),
effective_version: u32,
caller_effective_definition: AbiTraitDefinition,
callee_effective_definition: AbiTraitDefinition,
caller_native_definition: AbiTraitDefinition,
callee_native_definition: AbiTraitDefinition,
) -> Result<AbiConnectionTemplate, SavefileError> {
let mut methods = Vec::with_capacity(caller_native_definition.methods.len());
if caller_native_definition.methods.len() > 64 {
panic!("Too many method arguments, max 64 are supported!");
}
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 {
methods.push(AbiConnectionMethod {
method_name: caller_native_method.name,
caller_info: caller_native_method.info,
callee_method_number: None,
compatibility_mask: 0,
});
continue;
};
let Some(callee_effective_method) = callee_effective_definition
.methods
.iter()
.find(|x| x.name == caller_native_method.name)
else {
return Err(SavefileError::GeneralError {msg: format!("Internal error - missing method definition {} in signature when calculating serializable version of call (1).", caller_native_method.name)});
};
let Some(caller_effective_method) = caller_effective_definition
.methods
.iter()
.find(|x| x.name == caller_native_method.name)
else {
return Err(SavefileError::GeneralError {msg: format!("Internal error - missing method definition {} in signature when calculating serializable version of call (2).", caller_native_method.name)});
};
if caller_native_method.info.arguments.len() != callee_native_method.info.arguments.len() {
return Err(SavefileError::GeneralError {msg: format!("Number of arguments for method {} was expected by caller to be {} but was {} in implementation.", caller_native_method.name, caller_native_method.info.arguments.len(), callee_native_method.info.arguments.len())});
}
if caller_native_method.info.arguments.len() != caller_effective_method.info.arguments.len() {
return Err(SavefileError::GeneralError {
msg: format!(
"Internal error - number of arguments for method {} has differs between {} to {} (1).",
caller_native_method.name,
caller_native_method.info.arguments.len(),
caller_effective_method.info.arguments.len()
),
});
}
if caller_native_method.info.arguments.len() != callee_effective_method.info.arguments.len() {
return Err(SavefileError::GeneralError {
msg: format!(
"Internal error - number of arguments for method {} has differs between {} to {} (2).",
caller_native_method.name,
caller_native_method.info.arguments.len(),
callee_effective_method.info.arguments.len()
),
});
}
if caller_native_method.info.arguments.len() > 64 {
return Err(SavefileError::TooManyArguments);
}
let retval_effective_schema_diff = diff_schema(
&caller_effective_method.info.return_value,
&callee_effective_method.info.return_value,
"".to_string(),
true,
);
if let Some(diff) = retval_effective_schema_diff {
return Err(SavefileError::IncompatibleSchema {
message: format!(
"Incompatible ABI detected. Trait: {}, method: {}, return value error: {}",
trait_name, &caller_native_method.name, diff
),
});
}
let mut mask = 0;
let mut verify_compatibility = |effective1, effective2, native1, native2, index: Option<usize>| {
let effective_schema_diff = diff_schema(effective1, effective2, "".to_string(), index.is_none());
if let Some(diff) = effective_schema_diff {
return Err(SavefileError::IncompatibleSchema {
message: if let Some(index) = index {
format!(
"Incompatible ABI detected. Trait: {}, method: {}, argument: #{}: {}",
trait_name, &caller_native_method.name, index, diff
)
} else {
format!(
"Incompatible ABI detected. Trait: {}, method: {}, return value differs: {}",
trait_name, &caller_native_method.name, diff
)
},
});
}
let comp = arg_layout_compatible(
native1,
native2,
effective1,
effective2,
effective_version,
index.is_none(),
)?;
if comp {
if let Some(index) = index {
mask |= 1 << index;
}
}
Ok(())
};
for index in 0..caller_native_method.info.arguments.len() {
let effective1 = &caller_effective_method.info.arguments[index].schema;
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;
verify_compatibility(effective1, effective2, native1, native2, Some(index))?;
}
verify_compatibility(
&caller_effective_method.info.return_value,
&callee_effective_method.info.return_value,
&caller_native_method.info.return_value,
&callee_native_method.info.return_value,
None,
)?;
methods.push(AbiConnectionMethod {
method_name: caller_native_method.name,
caller_info: caller_native_method.info,
callee_method_number: Some(callee_native_method_number as u16),
compatibility_mask: mask,
})
}
Ok(AbiConnectionTemplate {
effective_version,
methods: Box::leak(methods.into_boxed_slice()),
entry: remote_entry,
})
}
fn get_symbol_for(
shared_library_path: &str,
trait_name: &str,
) -> Result<unsafe extern "C" fn(flag: AbiProtocol), SavefileError> {
let mut entry_guard = Guard::lock(&ENTRY_CACHE);
let mut lib_guard = Guard::lock(&LIBRARY_CACHE);
if let Some(item) = entry_guard.get(&(shared_library_path.to_string(), trait_name.to_string())) {
return Ok(*item);
}
let filename = shared_library_path.to_string();
let trait_name = trait_name.to_string();
let library;
match lib_guard.entry(filename.clone()) {
Entry::Occupied(item) => {
library = item.into_mut();
}
Entry::Vacant(vacant) => unsafe {
library = vacant.insert(Library::new(&filename).map_err(|x| SavefileError::LoadLibraryFailed {
libname: filename.to_string(),
msg: x.to_string(),
})?);
},
}
match entry_guard.entry((filename.clone(), trait_name.clone())) {
Entry::Occupied(item) => Ok(*item.get()),
Entry::Vacant(vacant) => {
let symbol_name = format!("abi_entry_{}\0", trait_name);
let symbol: Symbol<unsafe extern "C" fn(flag: AbiProtocol)> = unsafe {
library
.get(symbol_name.as_bytes())
.map_err(|x| SavefileError::LoadSymbolFailed {
libname: filename.to_string(),
symbol: symbol_name,
msg: x.to_string(),
})?
};
let func: unsafe extern "C" fn(flag: AbiProtocol) =
unsafe { std::mem::transmute(symbol.into_raw().into_raw()) };
vacant.insert(func);
Ok(func)
}
}
}
fn trait_name() -> &'static str {
let n = std::any::type_name::<T>();
let n = n.split("::").last().unwrap();
n
}
pub fn load_shared_library(filename: &str) -> Result<AbiConnection<T>, SavefileError> {
let remote_entry = Self::get_symbol_for(filename, Self::trait_name())?;
Self::new_internal(remote_entry, None, Owning::Owned)
}
#[doc(hidden)]
pub unsafe fn from_raw_packaged(
packed: PackagedTraitObject,
owning: Owning,
) -> Result<AbiConnection<T>, SavefileError> {
Self::from_raw(packed.entry, packed.trait_object, owning)
}
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(", ")
);
}
}
#[doc(hidden)]
pub unsafe fn from_raw(
entry_point: unsafe extern "C" fn(AbiProtocol),
trait_object: TraitObject,
owning: Owning,
) -> Result<AbiConnection<T>, SavefileError> {
Self::new_internal(entry_point, Some(trait_object), owning)
}
#[doc(hidden)]
pub fn from_boxed_trait(trait_object: Box<T>) -> Result<AbiConnection<T>, SavefileError> {
let trait_object = TraitObject::new(trait_object);
Self::new_internal(T::ABI_ENTRY, Some(trait_object), Owning::Owned)
}
#[doc(hidden)]
pub unsafe fn from_boxed_trait_for_test<O: AbiExportable + ?Sized>(
entry_point: unsafe extern "C" fn(AbiProtocol),
trait_object: Box<O>,
) -> Result<AbiConnection<T>, SavefileError> {
let trait_object = TraitObject::new(trait_object);
Self::new_internal(entry_point, Some(trait_object), Owning::Owned)
}
fn new_internal(
remote_entry: unsafe extern "C" fn(AbiProtocol),
trait_object: Option<TraitObject>,
owning: Owning,
) -> Result<AbiConnection<T>, SavefileError> {
let mut templates = Guard::lock(&ABI_CONNECTION_TEMPLATES);
let typeid = TypeId::of::<T>();
let template = match templates.entry((typeid, remote_entry)) {
Entry::Occupied(template) => template.get().clone(),
Entry::Vacant(vacant) => {
let own_version = T::get_latest_version();
let own_native_definition = T::get_definition(own_version);
let mut callee_abi_version = 0u32;
let mut callee_schema_version = 0u16;
unsafe {
(remote_entry)(AbiProtocol::InterrogateVersion {
schema_version_receiver: &mut callee_schema_version as *mut _,
abi_version_receiver: &mut callee_abi_version as *mut _,
});
}
let effective_schema_version = callee_schema_version.min(CURRENT_SAVEFILE_LIB_VERSION);
let effective_version = own_version.min(callee_abi_version);
let mut callee_abi_native_definition = Err(SavefileError::ShortRead); let mut callee_abi_effective_definition = Err(SavefileError::ShortRead);
unsafe extern "C" fn definition_receiver(
receiver: *mut (), schema_version: u16,
data: *const u8,
len: usize,
) {
let receiver = unsafe { &mut *(receiver as *mut Result<AbiTraitDefinition, SavefileError>) };
let slice = unsafe { slice::from_raw_parts(data, len) };
let mut cursor = Cursor::new(slice);
*receiver = load_noschema(&mut cursor, schema_version.into());
}
unsafe {
(remote_entry)(AbiProtocol::InterrogateMethods {
schema_version_required: effective_schema_version,
callee_schema_version_interrogated: callee_abi_version,
result_receiver: &mut callee_abi_native_definition as *mut _ as *mut _,
callback: definition_receiver,
});
}
unsafe {
(remote_entry)(AbiProtocol::InterrogateMethods {
schema_version_required: effective_schema_version,
callee_schema_version_interrogated: effective_version,
result_receiver: &mut callee_abi_effective_definition as *mut _ as *mut _,
callback: definition_receiver,
});
}
let callee_abi_native_definition = callee_abi_native_definition?;
let callee_abi_effective_definition = callee_abi_effective_definition?;
let own_effective_definition = T::get_definition(effective_version);
let trait_name = Self::trait_name();
let template = Self::analyze_and_create(
trait_name,
remote_entry,
effective_version,
own_effective_definition,
callee_abi_effective_definition,
own_native_definition,
callee_abi_native_definition,
)?;
vacant.insert(template).clone()
}
};
let trait_object = if let Some(obj) = trait_object {
obj
} else {
let mut trait_object = TraitObject::zero();
let mut error_msg: String = Default::default();
unsafe extern "C" fn error_callback(error_receiver: *mut (), error: *const AbiErrorMsg) {
let error_msg = unsafe { &mut *(error_receiver as *mut String) };
*error_msg = unsafe { &*error }.convert_to_string();
}
unsafe {
(remote_entry)(AbiProtocol::CreateInstance {
trait_object_receiver: &mut trait_object as *mut _,
error_receiver: &mut error_msg as *mut String as *mut _,
error_callback,
});
}
if error_msg.len() > 0 {
return Err(SavefileError::CalleePanic { msg: error_msg });
}
trait_object
};
Ok(AbiConnection {
template,
owning,
trait_object,
phantom: PhantomData,
})
}
}
pub unsafe fn abi_entry_light<T: AbiExportable + ?Sized>(flag: AbiProtocol) {
match flag {
AbiProtocol::RegularCall {
trait_object,
method_number,
effective_version,
compatibility_mask,
data,
data_length,
abi_result,
receiver,
} => {
let result = catch_unwind(|| {
let data = unsafe { slice::from_raw_parts(data, data_length) };
match unsafe {
call_trait_obj::<T>(
trait_object,
method_number,
effective_version,
compatibility_mask,
data,
abi_result,
receiver,
)
} {
Ok(_) => {}
Err(err) => {
let msg = format!("{:?}", err);
let err = RawAbiCallResult::AbiError(AbiErrorMsg {
error_msg_utf8: msg.as_ptr(),
len: msg.len(),
});
receiver(&err, abi_result)
}
}
});
match result {
Ok(()) => {}
Err(err) => {
let msg: &str;
let temp;
if let Some(err) = err.downcast_ref::<&str>() {
msg = err;
} else {
temp = format!("{:?}", err);
msg = &temp;
}
let err = RawAbiCallResult::Panic(AbiErrorMsg {
error_msg_utf8: msg.as_ptr(),
len: msg.len(),
});
receiver(&err, abi_result)
}
}
}
AbiProtocol::InterrogateVersion {
schema_version_receiver,
abi_version_receiver,
} => {
unsafe {
*schema_version_receiver = CURRENT_SAVEFILE_LIB_VERSION;
*abi_version_receiver = <T as AbiExportable>::get_latest_version();
}
}
AbiProtocol::InterrogateMethods {
schema_version_required,
callee_schema_version_interrogated,
result_receiver,
callback,
} => {
let abi = <T as AbiExportable>::get_definition(callee_schema_version_interrogated);
let mut temp = vec![];
let Ok(_) = Serializer::save_noschema_internal(
&mut temp,
schema_version_required as u32,
&abi,
schema_version_required.min(CURRENT_SAVEFILE_LIB_VERSION),
) else {
return;
};
callback(result_receiver, schema_version_required, temp.as_ptr(), temp.len());
}
AbiProtocol::CreateInstance {
trait_object_receiver: _,
error_receiver,
error_callback,
} => {
let msg = format!("Internal error - attempt to create an instance of {} using the interface crate, not an implementation crate", std::any::type_name::<T>());
let err = AbiErrorMsg {
error_msg_utf8: msg.as_ptr(),
len: msg.len(),
};
error_callback(error_receiver, &err as *const _)
}
AbiProtocol::DropInstance { trait_object } => unsafe {
destroy_trait_obj::<T>(trait_object);
},
}
}
pub unsafe fn abi_entry<T: AbiExportableImplementation>(flag: AbiProtocol) {
match flag {
AbiProtocol::CreateInstance {
trait_object_receiver,
error_receiver,
error_callback,
} => {
let result = catch_unwind(|| {
let obj: Box<T::AbiInterface> = T::new();
let raw = Box::into_raw(obj);
assert_eq!(std::mem::size_of::<*mut T::AbiInterface>(), 16);
assert_eq!(std::mem::size_of::<TraitObject>(), 16);
let mut trait_object = TraitObject::zero();
unsafe {
ptr::copy(
&raw as *const *mut T::AbiInterface,
&mut trait_object as *mut TraitObject as *mut *mut T::AbiInterface,
1,
)
};
unsafe {
*trait_object_receiver = trait_object;
}
});
match result {
Ok(_) => {}
Err(err) => {
let msg: &str;
let temp;
if let Some(err) = err.downcast_ref::<&str>() {
msg = err;
} else {
temp = format!("{:?}", err);
msg = &temp;
}
let err = AbiErrorMsg {
error_msg_utf8: msg.as_ptr(),
len: msg.len(),
};
error_callback(error_receiver, &err as *const _)
}
}
}
flag => {
abi_entry_light::<T::AbiInterface>(flag);
}
}
}
pub fn verify_compatiblity<T: AbiExportable + ?Sized>(path: &str) -> Result<(), SavefileError> {
std::fs::create_dir_all(path)?;
for version in 0..=T::get_latest_version() {
let def = T::get_definition(version);
let schema_file_name = Path::join(Path::new(path), format!("savefile_{}_{}.schema", def.name, version));
if std::fs::metadata(&schema_file_name).is_ok() {
let previous_schema = load_file_noschema(&schema_file_name, 1)?;
def.verify_backward_compatible(version, &previous_schema, false)?;
} else {
save_file_noschema(&schema_file_name, 1, &def)?;
}
}
Ok(())
}
#[doc(hidden)]
pub struct AbiWaker {
#[doc(hidden)]
waker: Box<dyn Fn() + Send + Sync>,
}
impl AbiWaker {
pub fn new(waker: Box<dyn Fn() + Send + Sync>) -> Self {
Self { waker }
}
}
impl Wake for AbiWaker {
fn wake(self: Arc<Self>) {
(self.waker)();
}
fn wake_by_ref(self: &Arc<Self>) {
(self.waker)();
}
}
#[cfg(feature = "bytes")]
mod bytes;