savefile_abi/lib.rs
1#![allow(clippy::len_zero)]
2#![deny(warnings)]
3#![deny(missing_docs)]
4#![allow(clippy::needless_late_init)]
5#![allow(clippy::uninlined_format_args)]
6#![allow(clippy::unnecessary_cast)]
7
8/*!
9This is the documentation for `savefile-abi`
10
11# Welcome to savefile-abi!
12
13Savefile-abi is a crate that is primarily meant to help building binary plugins using Rust.
14
15Note! Savefile-abi now supports methods returning boxed futures! See the chapter 'async' below.
16
17# Example
18
19Let's say we have a crate that defines this trait for adding u32s:
20
21*InterfaceCrate*
22```
23extern crate savefile_derive;
24use savefile_derive::savefile_abi_exportable;
25
26#[savefile_abi_exportable(version=0)]
27pub trait AdderInterface {
28 fn add(&self, x: u32, y: u32) -> u32;
29}
30
31```
32
33Now, we want to implement addition in a different crate, compile it to a shared library
34(.dll or .so), and use it in the first crate (or some other crate):
35
36*ImplementationCrate*
37```
38 # extern crate savefile_derive;
39 # use savefile_derive::{savefile_abi_exportable};
40 # #[savefile_abi_exportable(version=0)]
41 # pub trait AdderInterface {
42 # fn add(&self, x: u32, y: u32) -> u32;
43 # }
44 #
45use savefile_derive::{savefile_abi_export};
46#[derive(Default)]
47struct MyAdder { }
48
49impl AdderInterface for MyAdder {
50 fn add(&self, x: u32, y: u32) -> u32 {
51 x + y
52 }
53}
54
55/// Export this implementation as the default-implementation for
56/// the interface 'AdderInterface', for the current library.
57savefile_abi_export!(MyAdder, AdderInterface);
58
59```
60
61We add the following to Cargo.toml in our implementation crate:
62
63```toml
64[lib]
65crate-type = ["cdylib"]
66```
67
68Now, in our application, we add a dependency to *InterfaceCrate*, but not
69to *ImplementationCrate*.
70
71We then load the implementation dynamically at runtime:
72
73*ApplicationCrate*
74
75```rust,no_run
76 # extern crate savefile_derive;
77 # mod adder_interface {
78 # use savefile_derive::savefile_abi_exportable;
79 # #[savefile_abi_exportable(version=0)]
80 # pub trait AdderInterface {
81 # fn add(&self, x: u32, y: u32) -> u32;
82 # }
83 # }
84 #
85use adder_interface::AdderInterface;
86use savefile_abi::AbiConnection;
87
88
89// Load the implementation of `dyn AdderInterface` that was published
90// using the `savefile_abi_export!` above.
91let connection = AbiConnection::<dyn AdderInterface>
92 ::load_shared_library("ImplementationCrate.so").unwrap();
93
94// The type `AbiConnection::<dyn AdderInterface>` implements
95// the `AdderInterface`-trait, so we can use it to call its methods.
96assert_eq!(connection.add(1, 2), 3);
97
98```
99
100# More advanced examples
101
102Interface containing closure arguments:
103```
104 # extern crate savefile_derive;
105 # use savefile_derive::savefile_abi_exportable;
106#[savefile_abi_exportable(version=0)]
107pub trait CallMeBack {
108 fn call_me(&self, x: &dyn Fn(u32) -> u32) -> u32;
109 fn call_me_mut(&self, x: &mut dyn FnMut(u32) -> u32) -> u32;
110}
111
112```
113
114Interface containing more complex types:
115```
116 # extern crate savefile_derive;
117 # use savefile_derive::savefile_abi_exportable;
118 # use std::collections::{HashMap, BinaryHeap};
119#[savefile_abi_exportable(version=0)]
120pub trait Processor {
121 fn process(&self, x: &HashMap<String,String>, parameters: f32) -> BinaryHeap<u32>;
122}
123
124```
125
126Interface containing user defined types:
127```
128 # extern crate savefile_derive;
129 # use savefile_derive::{Savefile,savefile_abi_exportable};
130 # use std::collections::{HashMap, BinaryHeap};
131
132#[derive(Savefile)]
133pub struct MyCustomType {
134 pub name: String,
135 pub age: u8,
136 pub length: f32,
137}
138
139#[savefile_abi_exportable(version=0)]
140pub trait Processor {
141 fn insert(&self, x: &MyCustomType) -> Result<u32, String>;
142}
143
144```
145
146# Versioning
147
148Let's say the last example from the previous chapter needed to be evolved.
149The type now needs a 'city' field.
150
151We can add this while retaining compatibility with clients expecting the old API:
152
153```
154extern crate savefile_derive;
155
156 # use savefile::prelude::SavefileError;
157 # use savefile_derive::{Savefile,savefile_abi_exportable};
158 # use savefile_abi::verify_compatiblity;
159 # use std::collections::{HashMap, BinaryHeap};
160
161#[derive(Savefile)]
162pub struct MyCustomType {
163 pub name: String,
164 pub age: u8,
165 pub length: f32,
166 #[savefile_versions="1.."]
167 pub city: String,
168}
169
170#[savefile_abi_exportable(version=1)]
171pub trait Processor {
172 fn insert(&self, x: &MyCustomType) -> Result<u32, String>;
173}
174
175#[cfg(test)]
176{
177 #[test]
178 pub fn test_backward_compatibility() {
179 // Automatically verify backward compatibility isn't broken.
180 // Schemas for each version are stored in directory 'schemas',
181 // and consulted on next run to ensure no change.
182 // You should check the schemas in to source control.
183 // If check fails for an unreleased version, just remove the schema file from
184 // within 'schemas' directory.
185 verify_compatiblity::<dyn Processor>("schemas").unwrap()
186 }
187}
188
189
190```
191
192Older clients, not aware of the 'city' field, can still call newer implementations. The 'city'
193field will receive an empty string (Default::default()). Newer clients, calling older implementations,
194will simply, automatically, omit the 'city' field.
195
196
197# Background
198
199Savefile-abi is a crate that is primarily meant to help building binary plugins using Rust.
200
201The primary usecase is that a binary rust program is to be shipped to some customer,
202who should then be able to load various binary modules into the program at runtime.
203Savefile-abi defines ABI-stable rust-to-rust FFI for calling between a program and
204a runtime-loaded shared library.
205
206For now, both the main program and the plugins need to be written in rust. They can,
207however, be written using different versions of the rust compiler, and the API may
208be allowed to evolve. That is, data structures can be modified, and methods can be added
209(or removed).
210
211The reason savefile-abi is needed, is that rust does not have a stable 'ABI'. This means that
212if shared libraries are built using rust, all libraries must be compiled by the same version of
213rust, using the exact same source code. This means that rust cannot, 'out of the box', support
214a binary plugin system, without something like savefile-abi. This restriction may be lifted
215in the future, which would make this crate (savefile-abi) mostly redundant.
216
217Savefile-abi does not solve the general 'stable ABI'-problem. Rather, it defines a limited
218set of features, which allows useful calls between shared libraries, without allowing any
219and all rust construct.
220
221# Why another stable ABI-crate for Rust?
222
223There are other crates also providing ABI-stability. Savefile-abi has the following properties:
224
225 * It is able to completely specify the protocol used over the FFI-boundary. I.e, it can
226 isolate two shared libraries completely, making minimal assumptions about data type
227 memory layouts.
228
229 * When it cannot prove that memory layouts are identical, it falls back to (fast) serialization.
230 This has a performance penalty, and may require heap allocation.
231
232 * It tries to require a minimum of configuration needed by the user, while still being safe.
233
234 * It supports versioning of data structures (with a performance penalty).
235
236 * It supports trait objects as arguments, including FnMut() and Fn().
237
238 * Boxed trait objects, including Fn-traits, can be transferred across FFI-boundaries, passing
239 ownership, safely. No unsafe code is needed by the user.
240
241 * It requires enums to be `#[repr(uX)]` in order to pass them by reference. Other enums
242 will still work correctly, but will be serialized under the hood at a performance penalty.
243
244 * It places severe restrictions on types of arguments, since they must be serializable
245 using the Savefile-crate for serialization. Basically, arguments must be 'simple', in that
246 they must own all their contents, and be free of cycles. I.e, the type of the arguments must
247 have lifetime `&'static`. Note, arguments may still be references, and the contents of the
248 argument types may include Box, Vec etc, so this does not mean that only primitive types are
249 supported.
250
251Arguments cannot be mutable, since if serialization is needed, it would be impractical to detect and
252handle updates to arguments made by the callee. This said, arguments can still have types such as
253HashMap, IndexMap, Vec, String and custom defined structs or enums.
254
255# How it all works
256
257The basic principle is that savefile-abi makes sure to send function parameters in a way
258that is certain to be understood by the code on the other end of the FFI-boundary.
259It analyses if memory layouts of reference-parameters are the same on both sides of the
260FFI-boundary, and if they are, the references are simply copied. In all other cases, including
261all non-reference parameters, the data is simply serialized and sent as a binary buffer.
262
263The callee cannot rely on any particular lifetimes of arguments, since if the arguments
264were serialized, the arguments the callee sees will only have a lifetime of a single call,
265regardless of the original lifetime. Savefile-abi inspects all lifetimes and ensures
266that reference parameters don't have non-default lifetimes. Argument types must have static
267lifetimes (otherwise they can't be serialized). The only exception is that the argument
268can be reference types, but the type referenced must itself be `&'static`.
269
270# About Safety
271
272Savefile-Abi uses copious amounts of unsafe code. It has a test suite, and the
273test suite passes with miri.
274
275One thing to be aware of is that, at present, the AbiConnection::load_shared_library-method
276is not marked as unsafe. However, if the .so-file given as argument is corrupt, using this
277method can cause any amount of UB. Thus, it could be argued that it should be marked unsafe.
278
279However, the same is true for _any_ shared library used by a rust program, including the
280system C-library. It is also true that rust programs rely on the rust
281compiler being implemented correctly. Thus, it has been
282judged that the issue of corrupt binary files is beyond the scope of safety for Savefile-Abi.
283
284As long as the shared library is a real Savefile-Abi shared library, it should be sound to use,
285even if it contains code that is completely incompatible. This will be detected at runtime,
286and either AbiConnection::load_shared_library will panic, or any calls made after will panic.
287
288# About Vec and String references
289
290Savefile-Abi allows passing references containing Vec and/or String across the FFI-boundary.
291This is not normally guaranteed to be sound. However, Savefile-Abi uses heuristics to determine
292the actual memory layout of both Vec and String, and verifies that the two libraries agree
293on the layout. If they do not, the data is serialized instead. Also, since
294parameters can never be mutable in Savefile-abi (except for closures), we know
295the callee is not going to be freeing something allocated by the caller. Parameters
296called by value are always serialized.
297
298# Async
299
300Savefile-abi now supports methods returning futures:
301
302```rust
303
304use savefile_derive::savefile_abi_exportable;
305use std::pin::Pin;
306use std::future::Future;
307use std::time::Duration;
308
309#[savefile_abi_exportable(version = 0)]
310pub trait BoxedAsyncInterface {
311 fn add_async(&mut self, x: u32, y: u32) -> Pin<Box<dyn Future<Output=String>>>;
312
313}
314
315struct SimpleImpl;
316
317impl BoxedAsyncInterface for SimpleImpl {
318 fn add_async(&mut self, x: u32, y: u32) -> Pin<Box<dyn Future<Output=String>>> {
319 Box::pin(
320 async move {
321 /* any async code, using .await */
322 format!("{}",x+y)
323 }
324 )
325 }
326}
327
328
329```
330
331It also supports the `#[async_trait]` proc macro crate. Use it like this:
332
333```rust
334use async_trait::async_trait;
335use savefile_derive::savefile_abi_exportable;
336use std::time::Duration;
337
338#[async_trait]
339#[savefile_abi_exportable(version = 0)]
340pub trait SimpleAsyncInterface {
341 async fn add_async(&mut self, x: u32, y: u32) -> u32;
342}
343
344struct SimpleImpl;
345
346#[async_trait]
347impl SimpleAsyncInterface for SimpleImpl {
348 async fn add_async(&mut self, x: u32, y: u32) -> u32 {
349 /* any async code, using .await */
350 x + y
351 }
352}
353
354```
355
356
357
358*/
359
360extern crate core;
361extern crate savefile;
362extern crate savefile_derive;
363
364use byteorder::ReadBytesExt;
365use libloading::{Library, Symbol};
366use savefile::{
367 diff_schema, load_file_noschema, load_noschema, save_file_noschema, AbiMethodInfo, AbiTraitDefinition, Deserialize,
368 Deserializer, LittleEndian, SavefileError, Schema, Serializer, CURRENT_SAVEFILE_LIB_VERSION,
369};
370use std::any::TypeId;
371use std::collections::hash_map::Entry;
372use std::collections::HashMap;
373use std::hash::Hash;
374use std::io::{Cursor, Read, Write};
375use std::marker::PhantomData;
376use std::mem::MaybeUninit;
377use std::panic::catch_unwind;
378use std::path::Path;
379use std::ptr::null;
380use std::sync::{Arc, Mutex, MutexGuard};
381use std::task::Wake;
382use std::{ptr, slice};
383
384/// This trait is meant to be exported for a 'dyn SomeTrait'.
385/// It can be automatically implemented by using the
386/// macro `#[savefile_abi_exportable(version=0)]` on
387/// a trait that is to be exportable.
388///
389/// NOTE!
390/// If trait `MyExampleTrait` is to be exportable, the trait `AbiExportable` must
391/// be implemented for `dyn MyExampleTrait`.
392///
393/// NOTE!
394/// This trait is not meant to be implemented manually. It is mostly an implementation
395/// detail of SavefileAbi, it is only ever meant to be implemented by the savefile-derive
396/// proc macro.
397///
398/// # Safety
399/// The implementor must:
400/// * Make sure that the ABI_ENTRY function implements all parts of AbiProtocol
401/// in a correct manner
402/// * Has a correct 'get_definition' function, which must return a AbiTraitDefinition instance
403/// that is truthful.
404/// * Implement 'call' correctly
405#[diagnostic::on_unimplemented(
406 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.",
407 label = "`{Self}` cannot be called across an ABI-boundary",
408 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}`."
409)]
410pub unsafe trait AbiExportable {
411 /// A function which implements the savefile-abi contract.
412 const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol);
413 /// Must return a truthful description about all the methods in the
414 /// `dyn trait` that AbiExportable is implemented for (i.e, `Self`).
415 fn get_definition(version: u32) -> AbiTraitDefinition;
416 /// Must return the current latest version of the interface. I.e,
417 /// the version which Self represents. Of course, there may be future higher versions,
418 /// but none such are known by the code.
419 fn get_latest_version() -> u32;
420 /// Implement method calling. Must deserialize data from 'data', and
421 /// must return an outcome (result) by calling `receiver`.
422 ///
423 /// The return value is either Ok, or an error if the method to be called could
424 /// not be found or for some reason not called (mismatched actual ABI, for example).
425 ///
426 /// `receiver` must be given 'abi_result' as its 'result_receiver' parameter, so that
427 /// the receiver may set the result. The receiver executes at the caller-side of the ABI-divide,
428 /// but it receives as first argument an RawAbiCallResult that has been created by the callee.
429 fn call(
430 trait_object: TraitObject,
431 method_number: u16,
432 effective_version: u32,
433 compatibility_mask: u64,
434 data: &[u8],
435 abi_result: *mut (),
436 receiver: unsafe extern "C" fn(
437 outcome: *const RawAbiCallResult,
438 result_receiver: *mut (), /* actual type: Result<T,SaveFileError>>*/
439 ),
440 ) -> Result<(), SavefileError>;
441}
442
443/// Trait that is to be implemented for the implementation of a trait whose `dyn trait` type
444/// implements AbiExportable.
445///
446/// If `MyExampleTrait` is an ABI-exportable trait, and `MyExampleImplementation` is an
447/// implementation of `MyExampleTrait`, then:
448/// * The `AbiInterface` associated type must be `dyn MyExampleTrait`
449/// * `AbiExportableImplementation` must be implemented for `MyExampleImplementation`
450///
451/// # Safety
452/// The following must be fulfilled:
453/// * ABI_ENTRY must be a valid function, implementing the AbiProtocol-protocol.
454/// * AbiInterface must be 'dyn SomeTrait', where 'SomeTrait' is an exported trait.
455///
456#[diagnostic::on_unimplemented(
457 message = "`{Self}` cannot be the concrete type of an AbiExportable dyn trait.",
458 label = "Does not implement `AbiExportableImplementation`",
459 note = "You should not be using this trait directly, and should never see this error."
460)]
461pub unsafe trait AbiExportableImplementation {
462 /// An entry point which implements the AbiProtocol protocol
463 const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol);
464 /// The type 'dyn SomeTrait'.
465 type AbiInterface: ?Sized + AbiExportable;
466 /// A method which must be able to return a default-implementation of `dyn SomeTrait`.
467 /// I.e, the returned box is a boxed dyn trait, not 'Self' (the actual implementation type).
468 fn new() -> Box<Self::AbiInterface>;
469}
470
471/// Given a boxed trait object pointer, expressed as a data ptr and a vtable pointer,
472/// of type T (which must be a `dyn SomeTrait` type), drop the boxed trait object.
473/// I.e, `trait_object` is a type erased instance of Box<T> , where T is for example
474/// `dyn MyTrait`.
475/// # Safety
476/// The given `trait_object` must be a boxed trait object.
477unsafe fn destroy_trait_obj<T: AbiExportable + ?Sized>(trait_object: TraitObject) {
478 let mut raw_ptr: MaybeUninit<*mut T> = MaybeUninit::uninit();
479 ptr::copy(
480 &trait_object as *const TraitObject as *const MaybeUninit<*mut T>,
481 &mut raw_ptr as *mut MaybeUninit<*mut T>,
482 1,
483 );
484
485 let _ = Box::from_raw(raw_ptr.assume_init());
486}
487
488/// Call the given method, on the trait object.
489///
490/// trait_object - Type erased version of Box<dyn SomeTrait>
491/// method_number - The method to be called. This is an ordinal number, with 0 being the first method in definition order in the trait.
492/// effective_version - The version number in the serialized format, negotiated previously.
493/// compatibility_mask - For each method, one bit which says if the argument can be sent as just a reference, without having to use serialization to do a deep copy
494/// data - All the arguments, in a slice
495/// abi_result - Pointer to a place which will receiver the return value. This points to a Result<T, SaveFileError>, but since that type may have a different layout in callee and caller, we can't just use that type.
496/// receiver - A function which will receiver the actual serialized return value, and an error code.
497///
498/// If the callee panics, this will be encoded into the RawAbiCallResult given to the `receiver`. The `reveiver` will always be called with the return value/return status.
499///
500/// # Safety
501/// Every detail of all the arguments must be correct. Any little error is overwhelmingly likely to cause
502/// a segfault or worse.
503unsafe fn call_trait_obj<T: AbiExportable + ?Sized>(
504 trait_object: TraitObject,
505 method_number: u16,
506 effective_version: u32,
507 compatibility_mask: u64,
508 data: &[u8],
509 abi_result: *mut (),
510 receiver: unsafe extern "C" fn(
511 outcome: *const RawAbiCallResult,
512 result_receiver: *mut (), /*Result<T,SaveFileError>>*/
513 ),
514) -> Result<(), SavefileError> {
515 <T>::call(
516 trait_object,
517 method_number,
518 effective_version,
519 compatibility_mask,
520 data,
521 abi_result,
522 receiver,
523 )
524}
525
526/// Describes a method in a trait
527#[derive(Debug)]
528pub struct AbiConnectionMethod {
529 /// The name of the method
530 pub method_name: String,
531 /// This is mostly for debugging, it's not actually used
532 pub caller_info: AbiMethodInfo,
533 /// The ordinal number of this method at the callee, or None if callee doesn't have
534 /// method.
535 pub callee_method_number: Option<u16>,
536 /// For each of the up to 64 different arguments,
537 /// a bit value of 1 means layout is identical, and in such a way that
538 /// references can be just binary copied (owned arguments must still be cloned, and
539 /// we can just as well do that using serialization, it will be approx as fast).
540 pub compatibility_mask: u64,
541}
542
543/// Type erased carrier of a dyn trait fat pointer
544#[repr(C)]
545#[derive(Clone, Copy, Debug)]
546pub struct TraitObject {
547 ptr: *const (),
548 vtable: *const (),
549}
550
551unsafe impl Sync for TraitObject {}
552unsafe impl Send for TraitObject {}
553
554impl TraitObject {
555 /// Returns a TraitObject with two null ptrs. This value must never be used,
556 /// but can serve as a default before the real value is written.
557 pub fn zero() -> TraitObject {
558 TraitObject {
559 ptr: null(),
560 vtable: null(),
561 }
562 }
563
564 /// Interpret this TraitObject as *mut T.
565 /// *mut T *MUST* be a fat pointer of the same type as was used to create this TraitObject
566 /// instance.
567 pub fn as_mut_ptr<T: ?Sized>(self) -> *mut T {
568 assert_eq!(
569 std::mem::size_of::<*mut T>(),
570 16,
571 "TraitObject must only be used with dyn trait, not any other kind of trait"
572 );
573
574 let mut target: MaybeUninit<*mut T> = MaybeUninit::zeroed();
575 unsafe {
576 ptr::copy(
577 &self as *const TraitObject as *const MaybeUninit<*mut T>,
578 &mut target as *mut MaybeUninit<*mut T>,
579 1,
580 );
581 target.assume_init()
582 }
583 }
584 /// Interpret this TraitObject as *const T.
585 /// *const T *MUST* be a fat pointer of the same type as was used to create this TraitObject
586 /// instance.
587 pub fn as_const_ptr<T: ?Sized>(self) -> *const T {
588 assert_eq!(
589 std::mem::size_of::<*const T>(),
590 16,
591 "TraitObject must only be used with dyn trait, not any other kind of trait"
592 );
593
594 let mut target: MaybeUninit<*const T> = MaybeUninit::zeroed();
595 unsafe {
596 ptr::copy(
597 &self as *const TraitObject as *const MaybeUninit<*const T>,
598 &mut target as *mut MaybeUninit<*const T>,
599 1,
600 );
601 target.assume_init()
602 }
603 }
604 /// Convert the given fat pointer to a TraitObject instance.
605 #[inline]
606 pub fn new_from_ptr<T: ?Sized>(raw: *const T) -> TraitObject {
607 debug_assert_eq!(
608 std::mem::size_of::<*const T>(),
609 16,
610 "TraitObject::new_from_ptr() must only be used with dyn trait, not any other kind of trait"
611 );
612 debug_assert_eq!(std::mem::size_of::<TraitObject>(), 16);
613
614 let mut trait_object = TraitObject::zero();
615
616 unsafe {
617 ptr::copy(
618 &raw as *const *const T,
619 &mut trait_object as *mut TraitObject as *mut *const T,
620 1,
621 )
622 };
623 trait_object
624 }
625 /// Note: This only works for boxed dyn Trait.
626 /// T must be `dyn SomeTrait`.
627 #[inline]
628 pub fn new<T: ?Sized>(input: Box<T>) -> TraitObject {
629 let raw = Box::into_raw(input);
630 debug_assert_eq!(
631 std::mem::size_of::<*mut T>(),
632 16,
633 "TraitObject::new() must only be used with Boxed dyn trait, not any other kind of Box"
634 );
635 debug_assert_eq!(std::mem::size_of::<TraitObject>(), 16);
636
637 let mut trait_object = TraitObject::zero();
638
639 unsafe {
640 ptr::copy(
641 &raw as *const *mut T,
642 &mut trait_object as *mut TraitObject as *mut *mut T,
643 1,
644 )
645 };
646 trait_object
647 }
648}
649
650/// Information about an entry point and the trait
651/// it corresponds to.
652#[derive(Debug, Clone)]
653#[repr(C)]
654pub struct AbiConnectionTemplate {
655 /// The negotiated effective serialization version.
656 /// See 'savefile' crate for more information about version handling.
657 #[doc(hidden)]
658 pub effective_version: u32,
659 /// All the methods of the trait.
660 #[doc(hidden)]
661 pub methods: &'static [AbiConnectionMethod],
662 /// The entry point which will actually be used for calls. Typically,
663 /// this entry point points into a different shared object/dll compared to
664 /// the caller.
665 #[doc(hidden)]
666 pub entry: unsafe extern "C" fn(flag: AbiProtocol),
667}
668
669/// Information about an ABI-connection.
670///
671/// I.e,
672/// a caller and callee. The caller is in one
673/// particular shared object, the callee in another.
674/// Any modifiable state is stored in this object,
675/// and the actual callee is stateless (allowing multiple
676/// different incoming 'connections').
677///
678/// The fields are public, so that they can be easily written by the
679/// proc macros. But the user should never interact with them directly,
680/// so they are marked as doc(hidden).
681#[repr(C)]
682#[derive(Debug)]
683pub struct AbiConnection<T: ?Sized> {
684 /// Cachable information about the interface
685 #[doc(hidden)]
686 pub template: AbiConnectionTemplate,
687 /// Information on whether we *own* the trait object.
688 /// If we do, we must arrange for the foreign library code to drop it when we're done.
689 /// Otherwise, we must not drop it.
690 #[doc(hidden)]
691 pub owning: Owning,
692 /// The concrete trait object for this instance.
693 /// I.e, type erased trait object in the foreign library
694 #[doc(hidden)]
695 pub trait_object: TraitObject,
696 /// Phantom, to make this valid rust (since we don't otherwise carry a T).
697 #[doc(hidden)]
698 pub phantom: PhantomData<*const T>,
699}
700unsafe impl<T: ?Sized> Sync for AbiConnection<T> where T: Sync {}
701unsafe impl<T: ?Sized> Send for AbiConnection<T> where T: Send {}
702
703/// A trait object together with its entry point
704#[repr(C)]
705#[derive(Debug)]
706pub struct PackagedTraitObject {
707 /// Type erased trait object for an ABI-exported trait
708 pub trait_object: TraitObject,
709 /// The low level entry point
710 pub entry: unsafe extern "C" fn(flag: AbiProtocol),
711}
712
713impl PackagedTraitObject {
714 /// Create a PackagedTraitObject from a `Box<T>` . T must be a trait object.
715 /// T must implement AbiExportable, which means it has an ::ABI_ENTRY associated
716 /// type that gives the entry point.
717 pub fn new<T: AbiExportable + ?Sized>(boxed: Box<T>) -> PackagedTraitObject {
718 let trait_object = TraitObject::new(boxed);
719 let entry = T::ABI_ENTRY;
720 PackagedTraitObject { trait_object, entry }
721 }
722
723 /// Create a PackagedTraitObject from a &T. T must be a trait object.
724 /// T must implement AbiExportable, which means it has an ::ABI_ENTRY associated
725 /// type that gives the entry point.
726 /// Note, we use `*const T` here even for mutable cases, but it doesn't matter
727 /// since it's never dereferenced, it's just cast to other stuff and then finally
728 /// back to the right type.
729 #[inline]
730 pub fn new_from_ptr<T>(r: *const T) -> PackagedTraitObject
731 where
732 T: AbiExportable + ?Sized,
733 {
734 assert_eq!(std::mem::size_of::<*const T>(), 16);
735 let trait_object = TraitObject::new_from_ptr(r);
736 let entry = T::ABI_ENTRY;
737 PackagedTraitObject { trait_object, entry }
738 }
739
740 /// 'Serialize' this object. I.e, write it to a binary buffer, so that we can send it
741 /// to a foreign library.
742 pub fn serialize(self, serializer: &mut Serializer<impl Write>) -> Result<(), SavefileError> {
743 serializer.write_ptr(self.trait_object.ptr)?;
744 serializer.write_ptr(self.trait_object.vtable)?;
745 serializer.write_ptr(self.entry as *const ())?;
746
747 Ok(())
748 }
749 /// 'Deserialize' this object. I.e, read it from a binary buffer, so that we can receive it
750 /// from a foreign library.
751 ///
752 /// # Safety
753 /// The data available to read from Deserializer must be correct, and contain
754 /// a valid serialized PackagedTraitObject.
755 pub unsafe fn deserialize(
756 deserializer: &mut Deserializer<impl Read>,
757 ) -> Result<PackagedTraitObject, SavefileError> {
758 let mut trait_object = TraitObject::zero();
759 trait_object.ptr = deserializer.read_ptr()? as *mut ();
760 trait_object.vtable = deserializer.read_ptr()? as *mut ();
761 let entry = deserializer.read_ptr()? as *mut ();
762 assert_eq!(std::mem::size_of::<unsafe extern "C" fn(flag: AbiProtocol)>(), 8);
763 Ok(PackagedTraitObject {
764 trait_object,
765 entry: unsafe { std::mem::transmute::<*mut (), unsafe extern "C" fn(AbiProtocol)>(entry) },
766 })
767 }
768}
769
770impl<T: ?Sized> Drop for AbiConnection<T> {
771 fn drop(&mut self) {
772 match &self.owning {
773 Owning::Owned => unsafe {
774 (self.template.entry)(AbiProtocol::DropInstance {
775 trait_object: self.trait_object,
776 });
777 },
778 Owning::NotOwned => {}
779 }
780 }
781}
782
783/// Helper struct carrying a pointer and length to an utf8 message.
784/// We use this instead of &str, to guard against the hypothetical possibility
785/// that the layout of &str would ever change.
786#[repr(C)]
787pub struct AbiErrorMsg {
788 /// Pointer to utf8 error message
789 pub error_msg_utf8: *const u8,
790 /// The length of the message, in bytes
791 pub len: usize,
792}
793impl AbiErrorMsg {
794 /// Attempt to convert the given data to a String.
795 /// Any invalid UTF8-chars are replaced.
796 pub fn convert_to_string(&self) -> String {
797 if self.len == 0 {
798 return "".to_string();
799 }
800 let data = unsafe { slice::from_raw_parts(self.error_msg_utf8, self.len) };
801 String::from_utf8_lossy(data).into()
802 }
803}
804
805/// The result of calling a method in a foreign library.
806#[repr(C, u8)]
807pub enum RawAbiCallResult {
808 /// Successful operation
809 Success {
810 /// A pointer to the return value, serialized
811 data: *const u8,
812 /// The size of the serialized return value, in bytes
813 len: usize,
814 },
815 /// The method that was called, panicked. Since the way panic unwinding in Rust
816 /// could change between rust-versions, we can't allow any panics to unwind
817 /// across the boundary between two different libraries.
818 Panic(AbiErrorMsg),
819 /// There was an error in the ABI-framework. This will happen if code tries
820 /// to call a method that is not actually available on the target, or if method
821 /// signatures change in non ABI-compatible ways.
822 AbiError(AbiErrorMsg),
823}
824
825/// This struct carries all information between different libraries.
826/// I.e, it is the sole carrier of information accross an FFI-boundary.
827#[repr(C, u8)]
828pub enum AbiProtocol {
829 /// Call a method on a trait object.
830 RegularCall {
831 /// Type-erased actual trait object. This is the 16 bytes o trait fat pointer.
832 trait_object: TraitObject,
833 /// For every argument, a bit '1' if said argument is a reference that can just
834 /// be binary copied, as a pointer
835 compatibility_mask: u64,
836 /// Data for parameters, possibly serialized
837 data: *const u8,
838 /// Length of parameters-data
839 data_length: usize,
840 /// Instance of type `AbiCallResult<T>`, to which the return-value callback will
841 /// write deserialized results or panic-message.
842 abi_result: *mut (),
843 /// Callback which will be called by callee in order to supply the return value
844 /// (without having to allocate heap-memory)
845 receiver: unsafe extern "C" fn(
846 outcome: *const RawAbiCallResult,
847 result_receiver: *mut (), /*Result<T,SaveFileError>>*/
848 ),
849 /// The negotiated protocol version
850 effective_version: u32,
851 /// The method to call. This is the method number using the
852 /// numbering of the callee.
853 method_number: u16,
854 },
855 /// Get callee version
856 InterrogateVersion {
857 /// The version of the callee savefile schema. This can only change if the savefile library
858 /// is upgraded.
859 schema_version_receiver: *mut u16,
860 /// The version of the data schema, on the callee.
861 abi_version_receiver: *mut u32,
862 },
863 /// Get schema
864 InterrogateMethods {
865 /// The version of the schema that the caller expects.
866 schema_version_required: u16,
867 /// The schema version that the caller expects the callee to communicate using.
868 /// I.e, if callee has a later version of the 'savefile' library, this can be used
869 /// to arrange for it to speak an older dialect. In theory, but savefile is still
870 /// involving and there is always a risk that ABI-breaks will be necessary.
871 callee_schema_version_interrogated: u32,
872 /// A pointer pointing at the location that that caller will expect the return value to be written.
873 /// Note, callee does not actually write to this, it just calls `callback`, which allows caller
874 /// to write to the result_receiver. The field is still needed here, since the `callback` is a bare function,
875 /// and cannot capture any data.
876 result_receiver: *mut (), /*Result<AbiTraitDefinition, SavefileError>*/
877 /// Called by callee to convey information back to caller.
878 /// `receiver` is place the caller will want to write the result.
879 callback: unsafe extern "C" fn(
880 receiver: *mut (), /*Result<AbiTraitDefinition, SavefileError>*/
881 callee_schema_version: u16,
882 data: *const u8,
883 len: usize,
884 ),
885 },
886 /// Create a new trait object.
887 CreateInstance {
888 /// Pointer which will receive the fat pointer to the dyn trait object, allocated on heap using Box.
889 trait_object_receiver: *mut TraitObject,
890 /// Opaque pointer to callers representation of error (String)
891 error_receiver: *mut (), /*String*/
892 /// Called by callee if instance creation fails (by panic)
893 error_callback: unsafe extern "C" fn(error_receiver: *mut (), error: *const AbiErrorMsg),
894 },
895 /// Drop a trait object.
896 DropInstance {
897 /// dyn trait fat pointer
898 trait_object: TraitObject,
899 },
900}
901
902/// Parse the given RawAbiCallResult. If it concerns a success, then deserialize a return value using the given closure.
903pub fn parse_return_value_impl<T>(
904 outcome: &RawAbiCallResult,
905 deserialize_action: impl FnOnce(&mut Deserializer<Cursor<&[u8]>>) -> Result<T, SavefileError>,
906) -> Result<T, SavefileError> {
907 match outcome {
908 RawAbiCallResult::Success { data, len } => {
909 let data = unsafe { std::slice::from_raw_parts(*data, *len) };
910 let mut reader = Cursor::new(data);
911 let file_version = reader.read_u32::<LittleEndian>()?;
912 let mut deserializer = Deserializer {
913 reader: &mut reader,
914 file_version,
915 ephemeral_state: HashMap::new(),
916 };
917 deserialize_action(&mut deserializer)
918 }
919 RawAbiCallResult::Panic(AbiErrorMsg { error_msg_utf8, len }) => {
920 let errdata = unsafe { std::slice::from_raw_parts(*error_msg_utf8, *len) };
921 Err(SavefileError::CalleePanic {
922 msg: String::from_utf8_lossy(errdata).into(),
923 })
924 }
925 RawAbiCallResult::AbiError(AbiErrorMsg { error_msg_utf8, len }) => {
926 let errdata = unsafe { std::slice::from_raw_parts(*error_msg_utf8, *len) };
927 Err(SavefileError::GeneralError {
928 msg: String::from_utf8_lossy(errdata).into(),
929 })
930 }
931 }
932}
933
934/// Parse an RawAbiCallResult instance into a `Result<Box<dyn T>, SavefileError>` .
935///
936/// This is used on the caller side, and the type T will always be statically known.
937/// TODO: There's some duplicated code here, compare parse_return_value
938pub fn parse_return_boxed_trait<T>(outcome: &RawAbiCallResult) -> Result<Box<AbiConnection<T>>, SavefileError>
939where
940 T: AbiExportable + ?Sized + 'static,
941{
942 parse_return_value_impl(outcome, |deserializer| {
943 let packaged = unsafe { PackagedTraitObject::deserialize(deserializer)? };
944 unsafe {
945 Ok(Box::new(AbiConnection::<T>::from_raw_packaged(
946 packaged,
947 Owning::Owned,
948 )?))
949 }
950 })
951}
952/// We never unload libraries which have been dynamically loaded, because of all the problems with
953/// doing so.
954static LIBRARY_CACHE: Mutex<Option<HashMap<String /*filename*/, Library>>> = Mutex::new(None);
955static ENTRY_CACHE: Mutex<
956 Option<HashMap<(String /*filename*/, String /*trait name*/), unsafe extern "C" fn(flag: AbiProtocol)>>,
957> = Mutex::new(None);
958
959static ABI_CONNECTION_TEMPLATES: Mutex<
960 Option<HashMap<(TypeId, unsafe extern "C" fn(flag: AbiProtocol)), AbiConnectionTemplate>>,
961> = Mutex::new(None);
962
963struct Guard<'a, K: Hash + Eq, V> {
964 guard: MutexGuard<'a, Option<HashMap<K, V>>>,
965}
966
967impl<K: Hash + Eq, V> std::ops::Deref for Guard<'_, K, V> {
968 type Target = HashMap<K, V>;
969 fn deref(&self) -> &HashMap<K, V> {
970 self.guard.as_ref().unwrap()
971 }
972}
973
974impl<K: Hash + Eq, V> std::ops::DerefMut for Guard<'_, K, V> {
975 fn deref_mut(&mut self) -> &mut HashMap<K, V> {
976 &mut *self.guard.as_mut().unwrap()
977 }
978}
979
980// Avoid taking a dependency on OnceCell or lazy_static or something, just for this little thing
981impl<'a, K: Hash + Eq, V> Guard<'a, K, V> {
982 pub fn lock(map: &'a Mutex<Option<HashMap<K /*filename*/, V>>>) -> Guard<'a, K, V> {
983 let mut guard = map.lock().unwrap();
984 if guard.is_none() {
985 *guard = Some(HashMap::new());
986 }
987 Guard { guard }
988 }
989}
990
991/// Helper to determine if something is owned, or not
992#[derive(Debug, Clone, Copy)]
993pub enum Owning {
994 /// The object is owned
995 Owned,
996 /// The object is not owned
997 NotOwned,
998}
999
1000const FLEX_BUFFER_SIZE: usize = 64;
1001/// Stack allocated buffer that overflows on heap if needed
1002#[doc(hidden)]
1003pub enum FlexBuffer {
1004 /// Allocated on stack>
1005 Stack {
1006 /// The current write position. This is the same as
1007 /// the logical size of the buffer, since we can only write at the end.
1008 position: usize,
1009 /// The data backing this buffer, on the stack
1010 data: MaybeUninit<[u8; FLEX_BUFFER_SIZE]>,
1011 },
1012 /// Allocated on heap
1013 Spill(Vec<u8>),
1014}
1015impl Write for FlexBuffer {
1016 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1017 match self {
1018 FlexBuffer::Stack { position, data } => {
1019 if *position + buf.len() <= FLEX_BUFFER_SIZE {
1020 let rawdata = data as *mut MaybeUninit<_> as *mut u8;
1021 unsafe { ptr::copy(buf.as_ptr(), rawdata.add(*position), buf.len()) };
1022 *position += buf.len();
1023 } else {
1024 let mut spill = Vec::with_capacity(2 * FLEX_BUFFER_SIZE + buf.len());
1025 let rawdata = data as *mut MaybeUninit<_> as *mut u8;
1026 let dataslice = unsafe { slice::from_raw_parts(rawdata, *position) };
1027 spill.extend(dataslice);
1028 spill.extend(buf);
1029 *self = FlexBuffer::Spill(spill);
1030 }
1031 }
1032 FlexBuffer::Spill(v) => v.extend(buf),
1033 }
1034 Ok(buf.len())
1035 }
1036
1037 fn flush(&mut self) -> std::io::Result<()> {
1038 Ok(())
1039 }
1040}
1041
1042/// Raw entry point for receiving return values from other shared libraries
1043#[doc(hidden)]
1044pub unsafe extern "C" fn abi_result_receiver<T: Deserialize>(
1045 outcome: *const RawAbiCallResult,
1046 result_receiver: *mut (),
1047) {
1048 let outcome = unsafe { &*outcome };
1049 let result_receiver = unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit<Result<T, SavefileError>>) };
1050 result_receiver.write(parse_return_value_impl(outcome, |deserializer| {
1051 T::deserialize(deserializer)
1052 }));
1053}
1054
1055/// Raw entry point for receiving return values from other shared libraries
1056#[doc(hidden)]
1057pub unsafe extern "C" fn abi_boxed_trait_receiver<T>(outcome: *const RawAbiCallResult, result_receiver: *mut ())
1058where
1059 T: AbiExportable + ?Sized + 'static,
1060{
1061 let outcome = unsafe { &*outcome };
1062 let result_receiver =
1063 unsafe { &mut *(result_receiver as *mut std::mem::MaybeUninit<Result<Box<AbiConnection<T>>, SavefileError>>) };
1064 result_receiver.write(parse_return_value_impl(outcome, |deserializer| {
1065 let packaged = unsafe { PackagedTraitObject::deserialize(deserializer)? };
1066 unsafe {
1067 Ok(Box::new(AbiConnection::<T>::from_raw_packaged(
1068 packaged,
1069 Owning::Owned,
1070 )?))
1071 }
1072 }));
1073}
1074
1075// Flex buffer is only used internally, and we don't need to provide
1076// any of the regular convenience.
1077#[allow(clippy::new_without_default)]
1078#[allow(clippy::len_without_is_empty)]
1079impl FlexBuffer {
1080 /// Create a new buffer instance, allocated from the stack
1081 pub fn new() -> FlexBuffer {
1082 FlexBuffer::Stack {
1083 position: 0,
1084 data: MaybeUninit::uninit(),
1085 }
1086 }
1087 /// Get a pointer to the buffer contents
1088 pub fn as_ptr(&self) -> *const u8 {
1089 match self {
1090 FlexBuffer::Stack { data, .. } => data as *const MaybeUninit<_> as *const u8,
1091 FlexBuffer::Spill(v) => v.as_ptr(),
1092 }
1093 }
1094 /// Get the number of bytes in the buffer
1095 pub fn len(&self) -> usize {
1096 match self {
1097 FlexBuffer::Stack { position, .. } => *position,
1098 FlexBuffer::Spill(v) => v.len(),
1099 }
1100 }
1101}
1102
1103/// Arguments are layout compatible if their native versions are layout_compatible,
1104/// or if they are traits and the effective version of the traits are compatible.
1105/// For traits, the actual fat pointer is always compatible, so can always be used.
1106/// The trait-objects themselves can never be serialized, so they can only be used as references.
1107///
1108/// b is the callee, a is the caller
1109fn arg_layout_compatible(
1110 a_native: &Schema,
1111 b_native: &Schema,
1112 a_effective: &Schema,
1113 b_effective: &Schema,
1114 effective_version: u32,
1115 is_return_position: bool,
1116) -> Result<bool, SavefileError> {
1117 match (a_native, b_native) {
1118 (Schema::Future(_a, _, _, _), Schema::Future(_b, _, _, _)) => {
1119 let (
1120 Schema::Future(effective_a2, a_send, a_sync, a_unpin),
1121 Schema::Future(effective_b2, b_send, b_sync, b_unpin),
1122 ) = (a_effective, b_effective)
1123 else {
1124 return Err(SavefileError::IncompatibleSchema {
1125 message: "Type has changed".to_string(),
1126 });
1127 };
1128 for (a, b, bound) in [
1129 (*a_send, *b_send, "Send"),
1130 (*a_sync, *b_sync, "Sync"),
1131 (*a_unpin, *b_unpin, "Unpin"),
1132 ] {
1133 if a && !b {
1134 return Err(SavefileError::IncompatibleSchema{message: format!(
1135 "Caller expects a future with an {}-bound, but implementation provides one without. This is an incompatible difference.",
1136 bound)
1137 });
1138 }
1139 }
1140
1141 effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
1142 Ok(true)
1143 }
1144 (Schema::FnClosure(a1, _a2), Schema::FnClosure(b1, _b2)) => {
1145 let (Schema::FnClosure(effective_a1, effective_a2), Schema::FnClosure(effective_b1, effective_b2)) =
1146 (a_effective, b_effective)
1147 else {
1148 return Err(SavefileError::IncompatibleSchema {
1149 message: "Type has changed".to_string(),
1150 });
1151 };
1152
1153 effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
1154 Ok(a1 == b1 && a1 == effective_a1 && a1 == effective_b1)
1155 }
1156 (Schema::Boxed(native_a), Schema::Boxed(native_b)) => {
1157 let (Schema::Boxed(effective_a2), Schema::Boxed(effective_b2)) = (a_effective, b_effective) else {
1158 return Err(SavefileError::IncompatibleSchema {
1159 message: "Type has changed".to_string(),
1160 });
1161 };
1162 arg_layout_compatible(
1163 native_a,
1164 native_b,
1165 effective_a2,
1166 effective_b2,
1167 effective_version,
1168 is_return_position,
1169 )
1170 }
1171 (Schema::Trait(s_a, _), Schema::Trait(s_b, _)) => {
1172 if s_a != s_b {
1173 return Err(SavefileError::IncompatibleSchema {
1174 message: "Type has changed".to_string(),
1175 });
1176 }
1177 let (Schema::Trait(e_a2, effective_a2), Schema::Trait(e_b2, effective_b2)) = (a_effective, b_effective)
1178 else {
1179 return Err(SavefileError::IncompatibleSchema {
1180 message: "Type has changed".to_string(),
1181 });
1182 };
1183 if e_a2 != e_b2 {
1184 return Err(SavefileError::IncompatibleSchema {
1185 message: "Type has changed".to_string(),
1186 });
1187 }
1188
1189 effective_a2.verify_backward_compatible(effective_version, effective_b2, is_return_position)?;
1190 Ok(true)
1191 }
1192 (a, b) => Ok(a.layout_compatible(b)),
1193 }
1194}
1195
1196impl<T: AbiExportable + ?Sized + 'static> AbiConnection<T> {
1197 /// Analyse the difference in definitions between the two sides,
1198 /// and create an AbiConnection
1199 #[allow(clippy::too_many_arguments)]
1200 fn analyze_and_create(
1201 trait_name: &str,
1202 remote_entry: unsafe extern "C" fn(flag: AbiProtocol),
1203 effective_version: u32,
1204 caller_effective_definition: AbiTraitDefinition,
1205 callee_effective_definition: AbiTraitDefinition,
1206 caller_native_definition: AbiTraitDefinition,
1207 callee_native_definition: AbiTraitDefinition,
1208 ) -> Result<AbiConnectionTemplate, SavefileError> {
1209 let mut methods = Vec::with_capacity(caller_native_definition.methods.len());
1210 if caller_native_definition.methods.len() > 64 {
1211 panic!("Too many method arguments, max 64 are supported!");
1212 }
1213 for caller_native_method in caller_native_definition.methods.into_iter() {
1214 let Some((callee_native_method_number, callee_native_method)) = callee_native_definition
1215 .methods
1216 .iter()
1217 .enumerate()
1218 .find(|x| x.1.name == caller_native_method.name)
1219 else {
1220 methods.push(AbiConnectionMethod {
1221 method_name: caller_native_method.name,
1222 caller_info: caller_native_method.info,
1223 callee_method_number: None,
1224 compatibility_mask: 0,
1225 });
1226 continue;
1227 };
1228
1229 let Some(callee_effective_method) = callee_effective_definition
1230 .methods
1231 .iter()
1232 .find(|x| x.name == caller_native_method.name)
1233 else {
1234 return Err(SavefileError::GeneralError {msg: format!("Internal error - missing method definition {} in signature when calculating serializable version of call (1).", caller_native_method.name)});
1235 };
1236
1237 let Some(caller_effective_method) = caller_effective_definition
1238 .methods
1239 .iter()
1240 .find(|x| x.name == caller_native_method.name)
1241 else {
1242 return Err(SavefileError::GeneralError {msg: format!("Internal error - missing method definition {} in signature when calculating serializable version of call (2).", caller_native_method.name)});
1243 };
1244
1245 if caller_native_method.info.arguments.len() != callee_native_method.info.arguments.len() {
1246 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())});
1247 }
1248
1249 if caller_native_method.info.arguments.len() != caller_effective_method.info.arguments.len() {
1250 return Err(SavefileError::GeneralError {
1251 msg: format!(
1252 "Internal error - number of arguments for method {} has differs between {} to {} (1).",
1253 caller_native_method.name,
1254 caller_native_method.info.arguments.len(),
1255 caller_effective_method.info.arguments.len()
1256 ),
1257 });
1258 }
1259
1260 if caller_native_method.info.arguments.len() != callee_effective_method.info.arguments.len() {
1261 return Err(SavefileError::GeneralError {
1262 msg: format!(
1263 "Internal error - number of arguments for method {} has differs between {} to {} (2).",
1264 caller_native_method.name,
1265 caller_native_method.info.arguments.len(),
1266 callee_effective_method.info.arguments.len()
1267 ),
1268 });
1269 }
1270
1271 if caller_native_method.info.arguments.len() > 64 {
1272 return Err(SavefileError::TooManyArguments);
1273 }
1274
1275 let retval_effective_schema_diff = diff_schema(
1276 &caller_effective_method.info.return_value,
1277 &callee_effective_method.info.return_value,
1278 "".to_string(),
1279 true,
1280 );
1281 if let Some(diff) = retval_effective_schema_diff {
1282 return Err(SavefileError::IncompatibleSchema {
1283 message: format!(
1284 "Incompatible ABI detected. Trait: {}, method: {}, return value error: {}",
1285 trait_name, &caller_native_method.name, diff
1286 ),
1287 });
1288 }
1289 let mut mask = 0;
1290 let mut verify_compatibility = |effective1, effective2, native1, native2, index: Option<usize>| {
1291 let effective_schema_diff = diff_schema(effective1, effective2, "".to_string(), index.is_none());
1292 if let Some(diff) = effective_schema_diff {
1293 return Err(SavefileError::IncompatibleSchema {
1294 message: if let Some(index) = index {
1295 format!(
1296 "Incompatible ABI detected. Trait: {}, method: {}, argument: #{}: {}",
1297 trait_name, &caller_native_method.name, index, diff
1298 )
1299 } else {
1300 format!(
1301 "Incompatible ABI detected. Trait: {}, method: {}, return value differs: {}",
1302 trait_name, &caller_native_method.name, diff
1303 )
1304 },
1305 });
1306 }
1307
1308 let comp = arg_layout_compatible(
1309 native1,
1310 native2,
1311 effective1,
1312 effective2,
1313 effective_version,
1314 index.is_none(),
1315 )?;
1316
1317 if comp {
1318 if let Some(index) = index {
1319 mask |= 1 << index;
1320 }
1321 }
1322 Ok(())
1323 };
1324
1325 for index in 0..caller_native_method.info.arguments.len() {
1326 let effective1 = &caller_effective_method.info.arguments[index].schema;
1327 let effective2 = &callee_effective_method.info.arguments[index].schema;
1328 let native1 = &caller_native_method.info.arguments[index].schema;
1329 let native2 = &callee_native_method.info.arguments[index].schema;
1330 verify_compatibility(effective1, effective2, native1, native2, Some(index))?;
1331 }
1332
1333 verify_compatibility(
1334 &caller_effective_method.info.return_value,
1335 &callee_effective_method.info.return_value,
1336 &caller_native_method.info.return_value,
1337 &callee_native_method.info.return_value,
1338 None, /*return value*/
1339 )?;
1340
1341 methods.push(AbiConnectionMethod {
1342 method_name: caller_native_method.name,
1343 caller_info: caller_native_method.info,
1344 callee_method_number: Some(callee_native_method_number as u16),
1345 compatibility_mask: mask,
1346 })
1347 }
1348
1349 Ok(AbiConnectionTemplate {
1350 effective_version,
1351 methods: Box::leak(methods.into_boxed_slice()),
1352 entry: remote_entry,
1353 })
1354 }
1355
1356 /// Gets the function pointer for the entry point of the given interface, in the given
1357 /// shared library.
1358 fn get_symbol_for(
1359 shared_library_path: &str,
1360 trait_name: &str,
1361 ) -> Result<unsafe extern "C" fn(flag: AbiProtocol), SavefileError> {
1362 let mut entry_guard = Guard::lock(&ENTRY_CACHE);
1363 let mut lib_guard = Guard::lock(&LIBRARY_CACHE);
1364
1365 if let Some(item) = entry_guard.get(&(shared_library_path.to_string(), trait_name.to_string())) {
1366 return Ok(*item);
1367 }
1368
1369 let filename = shared_library_path.to_string();
1370 let trait_name = trait_name.to_string();
1371 let library;
1372 match lib_guard.entry(filename.clone()) {
1373 Entry::Occupied(item) => {
1374 library = item.into_mut();
1375 }
1376 Entry::Vacant(vacant) => unsafe {
1377 library = vacant.insert(Library::new(&filename).map_err(|x| SavefileError::LoadLibraryFailed {
1378 libname: filename.to_string(),
1379 msg: x.to_string(),
1380 })?);
1381 },
1382 }
1383
1384 match entry_guard.entry((filename.clone(), trait_name.clone())) {
1385 Entry::Occupied(item) => Ok(*item.get()),
1386 Entry::Vacant(vacant) => {
1387 let symbol_name = format!("abi_entry_{}\0", trait_name);
1388 let symbol: Symbol<unsafe extern "C" fn(flag: AbiProtocol)> = unsafe {
1389 library
1390 .get(symbol_name.as_bytes())
1391 .map_err(|x| SavefileError::LoadSymbolFailed {
1392 libname: filename.to_string(),
1393 symbol: symbol_name,
1394 msg: x.to_string(),
1395 })?
1396 };
1397 let func: unsafe extern "C" fn(flag: AbiProtocol) =
1398 unsafe { std::mem::transmute(symbol.into_raw().into_raw()) };
1399 vacant.insert(func);
1400 Ok(func)
1401 }
1402 }
1403 }
1404
1405 /// Determines the name, without namespace, of the implemented
1406 /// trait.
1407 fn trait_name() -> &'static str {
1408 let n = std::any::type_name::<T>();
1409 let n = n.split("::").last().unwrap();
1410 n
1411 }
1412 /// Load the shared library given by 'filename', and find a savefile-abi-implementation of
1413 /// the trait 'T'. Returns an object that implements the
1414 ///
1415 /// # Safety
1416 /// The shared library referenced by 'filename' must be safely implemented,
1417 /// and must contain an ABI-exported implementation of T, which must be a dyn trait.
1418 /// However, this kind of guarantee is really needed for all execution of any rust code,
1419 /// so we don't mark this as unsafe. Symbols are unlikely to match by mistake.
1420 pub fn load_shared_library(filename: &str) -> Result<AbiConnection<T>, SavefileError> {
1421 let remote_entry = Self::get_symbol_for(filename, Self::trait_name())?;
1422 Self::new_internal(remote_entry, None, Owning::Owned)
1423 }
1424
1425 /// Creates an AbiConnection from a PackagedTraitObject
1426 /// This is the way the derive macro crates AbiConnection instances.
1427 ///
1428 /// # Safety
1429 /// * entry_point of `packed` must implement AbiProtocol
1430 /// * trait_object of `packed` must be a type erased trait object reference
1431 /// * owning must be correct
1432 #[doc(hidden)]
1433 pub unsafe fn from_raw_packaged(
1434 packed: PackagedTraitObject,
1435 owning: Owning,
1436 ) -> Result<AbiConnection<T>, SavefileError> {
1437 Self::from_raw(packed.entry, packed.trait_object, owning)
1438 }
1439
1440 /// Check if the given argument 'arg' in method 'method' is memory compatible such that
1441 /// it will be sent as a reference, not copied. This will depend on the memory layout
1442 /// of the code being called into. It will not change during the lifetime of an
1443 /// AbiConnector, but it may change if the target library is recompiled.
1444 pub fn get_arg_passable_by_ref(&self, method: &str, arg: usize) -> bool {
1445 if let Some(found) = self.template.methods.iter().find(|var| var.method_name == method) {
1446 let abi_method: &AbiConnectionMethod = found;
1447 if arg >= abi_method.caller_info.arguments.len() {
1448 panic!(
1449 "Method '{}' has only {} arguments, so there is no argument #{}",
1450 method,
1451 abi_method.caller_info.arguments.len(),
1452 arg
1453 );
1454 }
1455 (abi_method.compatibility_mask & (1 << (arg as u64))) != 0
1456 } else {
1457 let arg_names: Vec<_> = self.template.methods.iter().map(|x| x.method_name.as_str()).collect();
1458 panic!(
1459 "Trait has no method with name '{}'. Available methods: {}",
1460 method,
1461 arg_names.join(", ")
1462 );
1463 }
1464 }
1465
1466 /// This routine is mostly for tests.
1467 /// It allows using a raw external API entry point directly.
1468 /// This is mostly useful for internal testing of the savefile-abi-library.
1469 /// 'miri' does not support loading dynamic libraries. Using this function
1470 /// from within the same image as the implementation, can be a workaround for this.
1471 ///
1472 /// # Safety
1473 /// * entry_point must implement AbiProtocol
1474 /// * trait_object must be a type erased trait object reference
1475 /// * owning must be correct
1476 #[doc(hidden)]
1477 pub unsafe fn from_raw(
1478 entry_point: unsafe extern "C" fn(AbiProtocol),
1479 trait_object: TraitObject,
1480 owning: Owning,
1481 ) -> Result<AbiConnection<T>, SavefileError> {
1482 Self::new_internal(entry_point, Some(trait_object), owning)
1483 }
1484
1485 /// Crate a AbiConnection from an entry point and a boxed trait object.
1486 /// This is undocumented, since it's basically useless except for tests.
1487 /// If you have a Box<dyn Example>, you'd want to just use it directly,
1488 /// not make an AbiConnection wrapping it.
1489 ///
1490 /// This method is still useful during testing.
1491 ///
1492 /// # Safety
1493 /// * The entry point must contain a correct implementation matching the type T.
1494 /// * T must be a dyn trait object
1495 #[doc(hidden)]
1496 pub fn from_boxed_trait(trait_object: Box<T>) -> Result<AbiConnection<T>, SavefileError> {
1497 let trait_object = TraitObject::new(trait_object);
1498 Self::new_internal(T::ABI_ENTRY, Some(trait_object), Owning::Owned)
1499 }
1500
1501 /// Crate a AbiConnection from an entry point and a boxed trait object.
1502 /// This allows using a different interface trait for the backing implementation, for
1503 /// test cases which want to test version evolution.
1504 ///
1505 /// # Safety
1506 /// * The entry point must contain a correct implementation matching the type T.
1507 /// * T must be a dyn trait object
1508 #[doc(hidden)]
1509 pub unsafe fn from_boxed_trait_for_test<O: AbiExportable + ?Sized>(
1510 entry_point: unsafe extern "C" fn(AbiProtocol),
1511 trait_object: Box<O>,
1512 ) -> Result<AbiConnection<T>, SavefileError> {
1513 let trait_object = TraitObject::new(trait_object);
1514 Self::new_internal(entry_point, Some(trait_object), Owning::Owned)
1515 }
1516
1517 fn new_internal(
1518 remote_entry: unsafe extern "C" fn(AbiProtocol),
1519 trait_object: Option<TraitObject>,
1520 owning: Owning,
1521 ) -> Result<AbiConnection<T>, SavefileError> {
1522 let mut templates = Guard::lock(&ABI_CONNECTION_TEMPLATES);
1523
1524 let typeid = TypeId::of::<T>();
1525 // In principle, it would be enough to key 'templates' based on 'remote_entry'.
1526 // However, if we do, and the user ever uses AbiConnection<T> with the _wrong_ entry point,
1527 // we risk poisoning the cache with erroneous data.
1528 let template = match templates.entry((typeid, remote_entry)) {
1529 Entry::Occupied(template) => template.get().clone(),
1530 Entry::Vacant(vacant) => {
1531 let own_version = T::get_latest_version();
1532 let own_native_definition = T::get_definition(own_version);
1533
1534 let mut callee_abi_version = 0u32;
1535 let mut callee_schema_version = 0u16;
1536 unsafe {
1537 (remote_entry)(AbiProtocol::InterrogateVersion {
1538 schema_version_receiver: &mut callee_schema_version as *mut _,
1539 abi_version_receiver: &mut callee_abi_version as *mut _,
1540 });
1541 }
1542
1543 let effective_schema_version = callee_schema_version.min(CURRENT_SAVEFILE_LIB_VERSION);
1544 let effective_version = own_version.min(callee_abi_version);
1545
1546 let mut callee_abi_native_definition = Err(SavefileError::ShortRead); //Uust dummy-values
1547 let mut callee_abi_effective_definition = Err(SavefileError::ShortRead);
1548
1549 unsafe extern "C" fn definition_receiver(
1550 receiver: *mut (), //Result<AbiTraitDefinition, SavefileError>,
1551 schema_version: u16,
1552 data: *const u8,
1553 len: usize,
1554 ) {
1555 let receiver = unsafe { &mut *(receiver as *mut Result<AbiTraitDefinition, SavefileError>) };
1556 let slice = unsafe { slice::from_raw_parts(data, len) };
1557 let mut cursor = Cursor::new(slice);
1558
1559 *receiver = load_noschema(&mut cursor, schema_version.into());
1560 }
1561
1562 unsafe {
1563 (remote_entry)(AbiProtocol::InterrogateMethods {
1564 schema_version_required: effective_schema_version,
1565 callee_schema_version_interrogated: callee_abi_version,
1566 result_receiver: &mut callee_abi_native_definition as *mut _ as *mut _,
1567 callback: definition_receiver,
1568 });
1569 }
1570
1571 unsafe {
1572 (remote_entry)(AbiProtocol::InterrogateMethods {
1573 schema_version_required: effective_schema_version,
1574 callee_schema_version_interrogated: effective_version,
1575 result_receiver: &mut callee_abi_effective_definition as *mut _ as *mut _,
1576 callback: definition_receiver,
1577 });
1578 }
1579
1580 let callee_abi_native_definition = callee_abi_native_definition?;
1581 let callee_abi_effective_definition = callee_abi_effective_definition?;
1582
1583 let own_effective_definition = T::get_definition(effective_version);
1584 let trait_name = Self::trait_name();
1585 let template = Self::analyze_and_create(
1586 trait_name,
1587 remote_entry,
1588 effective_version,
1589 own_effective_definition,
1590 callee_abi_effective_definition,
1591 own_native_definition,
1592 callee_abi_native_definition,
1593 )?;
1594 vacant.insert(template).clone()
1595 }
1596 };
1597
1598 let trait_object = if let Some(obj) = trait_object {
1599 obj
1600 } else {
1601 let mut trait_object = TraitObject::zero();
1602 let mut error_msg: String = Default::default();
1603 unsafe extern "C" fn error_callback(error_receiver: *mut (), error: *const AbiErrorMsg) {
1604 let error_msg = unsafe { &mut *(error_receiver as *mut String) };
1605 *error_msg = unsafe { &*error }.convert_to_string();
1606 }
1607 unsafe {
1608 (remote_entry)(AbiProtocol::CreateInstance {
1609 trait_object_receiver: &mut trait_object as *mut _,
1610 error_receiver: &mut error_msg as *mut String as *mut _,
1611 error_callback,
1612 });
1613 }
1614
1615 if error_msg.len() > 0 {
1616 return Err(SavefileError::CalleePanic { msg: error_msg });
1617 }
1618 trait_object
1619 };
1620
1621 Ok(AbiConnection {
1622 template,
1623 owning,
1624 trait_object,
1625 phantom: PhantomData,
1626 })
1627 }
1628}
1629
1630/// Helper implementation of ABI entry point.
1631/// The actual low level `extern "C"` functions call into this.
1632/// This is an entry point meant to be used by the derive macro.
1633///
1634/// This version, the 'light version', does not support instance
1635/// creation.
1636///
1637/// # Safety
1638/// The 'AbiProtocol' protocol must only contain valid data.
1639pub unsafe fn abi_entry_light<T: AbiExportable + ?Sized>(flag: AbiProtocol) {
1640 match flag {
1641 AbiProtocol::RegularCall {
1642 trait_object,
1643 method_number,
1644 effective_version,
1645 compatibility_mask,
1646 data,
1647 data_length,
1648 abi_result,
1649 receiver,
1650 } => {
1651 let result = catch_unwind(|| {
1652 let data = unsafe { slice::from_raw_parts(data, data_length) };
1653
1654 match unsafe {
1655 call_trait_obj::<T>(
1656 trait_object,
1657 method_number,
1658 effective_version,
1659 compatibility_mask,
1660 data,
1661 abi_result,
1662 receiver,
1663 )
1664 } {
1665 Ok(_) => {}
1666 Err(err) => {
1667 let msg = format!("{:?}", err);
1668 let err = RawAbiCallResult::AbiError(AbiErrorMsg {
1669 error_msg_utf8: msg.as_ptr(),
1670 len: msg.len(),
1671 });
1672 receiver(&err, abi_result)
1673 }
1674 }
1675 });
1676 match result {
1677 Ok(()) => {}
1678 Err(err) => {
1679 let msg: &str;
1680 let temp;
1681 if let Some(err) = err.downcast_ref::<&str>() {
1682 msg = err;
1683 } else {
1684 temp = format!("{:?}", err);
1685 msg = &temp;
1686 }
1687 let err = RawAbiCallResult::Panic(AbiErrorMsg {
1688 error_msg_utf8: msg.as_ptr(),
1689 len: msg.len(),
1690 });
1691 receiver(&err, abi_result)
1692 }
1693 }
1694 }
1695 AbiProtocol::InterrogateVersion {
1696 schema_version_receiver,
1697 abi_version_receiver,
1698 } => {
1699 // # SAFETY
1700 // The pointers come from another savefile-implementation, and are known to be valid
1701 unsafe {
1702 *schema_version_receiver = CURRENT_SAVEFILE_LIB_VERSION;
1703 *abi_version_receiver = <T as AbiExportable>::get_latest_version();
1704 }
1705 }
1706 AbiProtocol::InterrogateMethods {
1707 schema_version_required,
1708 callee_schema_version_interrogated,
1709 result_receiver,
1710 callback,
1711 } => {
1712 // Note! Any conforming implementation must send a 'schema_version_required' number that is
1713 // within the ability of the receiving implementation. It can interrogate this using 'AbiProtocol::InterrogateVersion'.
1714 let abi = <T as AbiExportable>::get_definition(callee_schema_version_interrogated);
1715 let mut temp = vec![];
1716 let Ok(_) = Serializer::save_noschema_internal(
1717 &mut temp,
1718 schema_version_required as u32,
1719 &abi,
1720 schema_version_required.min(CURRENT_SAVEFILE_LIB_VERSION),
1721 ) else {
1722 return;
1723 };
1724 callback(result_receiver, schema_version_required, temp.as_ptr(), temp.len());
1725 }
1726 AbiProtocol::CreateInstance {
1727 trait_object_receiver: _,
1728 error_receiver,
1729 error_callback,
1730 } => {
1731 let msg = format!("Internal error - attempt to create an instance of {} using the interface crate, not an implementation crate", std::any::type_name::<T>());
1732 let err = AbiErrorMsg {
1733 error_msg_utf8: msg.as_ptr(),
1734 len: msg.len(),
1735 };
1736 error_callback(error_receiver, &err as *const _)
1737 }
1738 AbiProtocol::DropInstance { trait_object } => unsafe {
1739 destroy_trait_obj::<T>(trait_object);
1740 },
1741 }
1742}
1743/// Helper implementation of ABI entry point.
1744/// The actual low level `extern "C"` functions call into this.
1745/// This is an entry point meant to be used by the derive macro.
1746///
1747/// This version, the 'full version', does support instance
1748/// creation.
1749///
1750/// # Safety
1751/// The 'AbiProtocol' protocol must only contain valid data.
1752pub unsafe fn abi_entry<T: AbiExportableImplementation>(flag: AbiProtocol) {
1753 match flag {
1754 AbiProtocol::CreateInstance {
1755 trait_object_receiver,
1756 error_receiver,
1757 error_callback,
1758 } => {
1759 let result = catch_unwind(|| {
1760 let obj: Box<T::AbiInterface> = T::new();
1761 let raw = Box::into_raw(obj);
1762 assert_eq!(std::mem::size_of::<*mut T::AbiInterface>(), 16);
1763 assert_eq!(std::mem::size_of::<TraitObject>(), 16);
1764
1765 let mut trait_object = TraitObject::zero();
1766
1767 unsafe {
1768 ptr::copy(
1769 &raw as *const *mut T::AbiInterface,
1770 &mut trait_object as *mut TraitObject as *mut *mut T::AbiInterface,
1771 1,
1772 )
1773 };
1774
1775 unsafe {
1776 *trait_object_receiver = trait_object;
1777 }
1778 });
1779 match result {
1780 Ok(_) => {}
1781 Err(err) => {
1782 let msg: &str;
1783 let temp;
1784 if let Some(err) = err.downcast_ref::<&str>() {
1785 msg = err;
1786 } else {
1787 temp = format!("{:?}", err);
1788 msg = &temp;
1789 }
1790 let err = AbiErrorMsg {
1791 error_msg_utf8: msg.as_ptr(),
1792 len: msg.len(),
1793 };
1794 error_callback(error_receiver, &err as *const _)
1795 }
1796 }
1797 }
1798 flag => {
1799 abi_entry_light::<T::AbiInterface>(flag);
1800 }
1801 }
1802}
1803
1804/// Verify compatibility with old versions.
1805///
1806/// If files representing the given AbiExportable definition is not already present,
1807/// create one file per supported version, with the definition of the ABI.
1808/// If files are present, verify that the definition is the same as that in the files.
1809///
1810/// This allows us to detect if the data structure as we've declared it is modified
1811/// in a non-backward compatible way.
1812///
1813/// 'path' is a path where files defining the Abi schema are stored. These files
1814/// should be checked in to version control.
1815pub fn verify_compatiblity<T: AbiExportable + ?Sized>(path: &str) -> Result<(), SavefileError> {
1816 std::fs::create_dir_all(path)?;
1817 for version in 0..=T::get_latest_version() {
1818 let def = T::get_definition(version);
1819 let schema_file_name = Path::join(Path::new(path), format!("savefile_{}_{}.schema", def.name, version));
1820 if std::fs::metadata(&schema_file_name).is_ok() {
1821 let previous_schema = load_file_noschema(&schema_file_name, 1)?;
1822
1823 def.verify_backward_compatible(version, &previous_schema, false)?;
1824 } else {
1825 save_file_noschema(&schema_file_name, 1, &def)?;
1826 }
1827 }
1828 Ok(())
1829}
1830
1831#[doc(hidden)]
1832pub struct AbiWaker {
1833 #[doc(hidden)]
1834 waker: Box<dyn Fn() + Send + Sync>,
1835}
1836impl AbiWaker {
1837 pub fn new(waker: Box<dyn Fn() + Send + Sync>) -> Self {
1838 Self { waker }
1839 }
1840}
1841impl Wake for AbiWaker {
1842 fn wake(self: Arc<Self>) {
1843 (self.waker)();
1844 }
1845 fn wake_by_ref(self: &Arc<Self>) {
1846 (self.waker)();
1847 }
1848}
1849
1850#[cfg(feature = "bytes")]
1851mod bytes;