summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs
blob: 092b635255efb41dd270366c6a8a65a752b8ffd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Callback interfaces are traits specified in UDL which can be implemented by foreign languages.
//!
//! # Using callback interfaces
//!
//! 1. Define a Rust trait.
//!
//! This toy example defines a way of Rust accessing a key-value store exposed
//! by the host operating system (e.g. the key chain).
//!
//! ```
//! trait Keychain: Send {
//!   fn get(&self, key: String) -> Option<String>;
//!   fn put(&self, key: String, value: String);
//! }
//! ```
//!
//! 2. Define a callback interface in the UDL
//!
//! ```idl
//! callback interface Keychain {
//!     string? get(string key);
//!     void put(string key, string data);
//! };
//! ```
//!
//! 3. And allow it to be passed into Rust.
//!
//! Here, we define a constructor to pass the keychain to rust, and then another method
//! which may use it.
//!
//! In UDL:
//! ```idl
//! object Authenticator {
//!     constructor(Keychain keychain);
//!     void login();
//! }
//! ```
//!
//! In Rust:
//!
//! ```
//!# trait Keychain: Send {
//!#  fn get(&self, key: String) -> Option<String>;
//!#  fn put(&self, key: String, value: String);
//!# }
//! struct Authenticator {
//!   keychain: Box<dyn Keychain>,
//! }
//!
//! impl Authenticator {
//!   pub fn new(keychain: Box<dyn Keychain>) -> Self {
//!     Self { keychain }
//!   }
//!   pub fn login(&self) {
//!     let username = self.keychain.get("username".into());
//!     let password = self.keychain.get("password".into());
//!   }
//! }
//! ```
//! 4. Create an foreign language implementation of the callback interface.
//!
//! In this example, here's a Kotlin implementation.
//!
//! ```kotlin
//! class AndroidKeychain: Keychain {
//!     override fun get(key: String): String? {
//!         // … elide the implementation.
//!         return value
//!     }
//!     override fun put(key: String) {
//!         // … elide the implementation.
//!     }
//! }
//! ```
//! 5. Pass the implementation to Rust.
//!
//! Again, in Kotlin
//!
//! ```kotlin
//! val authenticator = Authenticator(AndroidKeychain())
//! authenticator.login()
//! ```
//!
//! # How it works.
//!
//! ## High level
//!
//! Uniffi generates a protocol or interface in client code in the foreign language must implement.
//!
//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals`
//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface.
//!
//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the
//! `KeychainCallbackInternals` to store the instance in a handlemap.
//!
//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements
//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to
//! client code as `Box<dyn Keychain>`.
//!
//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`.
//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the
//! object handle, and the method selector.
//!
//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap,
//! and calls the actual implementation of the method.
//!
//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for
//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct
//! type and then returns to client code.
//!

use super::RustBuffer;
use std::fmt;
use std::os::raw::c_int;
use std::sync::atomic::{AtomicUsize, Ordering};

/// ForeignCallback is the Rust representation of a foreign language function.
/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface,
/// at library start up time.
/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language.
///
/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object
///   that implements the callback interface/trait.
/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from
///   the IDL. The index is 1 indexed. Note that the list of methods is generated by at uniffi from the IDL and used in all
///   bindings: so we can rely on the method list being stable within the same run of uniffi.
/// * `args` is a serialized buffer of arguments to the function. UniFFI will deserialize it before
///   passing individual arguments to the user's callback.
/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a
///   buffer to write the result into.
/// * A callback returns:
///    - `-2` An error occured that was serialized to buf_ptr
///    - `-1` An unexpected error ocurred
///    - `0` is a deprecated way to signal that if the call succeeded, but did not modify buf_ptr
///    - `1` If the call succeeded.  For non-void functions the return value should be serialized
///      to buf_ptr.
///   Note: The output buffer might still contain 0 bytes of data.
pub type ForeignCallback = unsafe extern "C" fn(
    handle: u64,
    method: u32,
    args: RustBuffer,
    buf_ptr: *mut RustBuffer,
) -> c_int;

/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it,
/// and it can be deleted from the handle map.
pub const IDX_CALLBACK_FREE: u32 = 0;

// Overly-paranoid sanity checking to ensure that these types are
// convertible between each-other. `transmute` actually should check this for
// us too, but this helps document the invariants we rely on in this code.
//
// Note that these are guaranteed by
// https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
// and thus this is a little paranoid.
static_assertions::assert_eq_size!(usize, ForeignCallback);
static_assertions::assert_eq_size!(usize, Option<ForeignCallback>);

/// Struct to hold a foreign callback.
pub struct ForeignCallbackInternals {
    callback_ptr: AtomicUsize,
}

const EMPTY_PTR: usize = 0;

impl ForeignCallbackInternals {
    pub const fn new() -> Self {
        ForeignCallbackInternals {
            callback_ptr: AtomicUsize::new(EMPTY_PTR),
        }
    }

    pub fn set_callback(&self, callback: ForeignCallback) {
        let as_usize = callback as usize;
        let old_ptr = self.callback_ptr.compare_exchange(
            EMPTY_PTR,
            as_usize,
            Ordering::SeqCst,
            Ordering::SeqCst,
        );
        match old_ptr {
            // We get the previous value back. If this is anything except EMPTY_PTR,
            // then this has been set before we get here.
            Ok(EMPTY_PTR) => (),
            _ =>
            // This is an internal bug, the other side of the FFI should ensure
            // it sets this only once.
            {
                panic!("Bug: call set_callback multiple times. This is likely a uniffi bug")
            }
        };
    }

    pub fn get_callback(&self) -> Option<ForeignCallback> {
        let ptr_value = self.callback_ptr.load(Ordering::SeqCst);
        unsafe { std::mem::transmute::<usize, Option<ForeignCallback>>(ptr_value) }
    }
}

/// Used when internal/unexpected error happened when calling a foreign callback, for example when
/// a unknown exception is raised
///
/// User callback error types must implement a From impl from this type to their own error type.
#[derive(Debug)]
pub struct UnexpectedUniFFICallbackError {
    pub reason: String,
}

impl UnexpectedUniFFICallbackError {
    pub fn from_reason(reason: String) -> Self {
        Self { reason }
    }
}

impl fmt::Display for UnexpectedUniFFICallbackError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "UnexpectedUniFFICallbackError(reason: {:?})",
            self.reason
        )
    }
}

impl std::error::Error for UnexpectedUniFFICallbackError {}