summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
blob: c82e9389bb61489a54072b9f5df100ceadaaae52 (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
/* 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/. */

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Result;

use crate::util::mod_path;
use uniffi_meta::UNIFFI_CONTRACT_VERSION;

pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
    let module_path = mod_path()?;
    let ffi_contract_version_ident = format_ident!("ffi_{module_path}_uniffi_contract_version");
    let namespace_upper = namespace.to_ascii_uppercase();
    let namespace_const_ident = format_ident!("UNIFFI_META_CONST_NAMESPACE_{namespace_upper}");
    let namespace_static_ident = format_ident!("UNIFFI_META_NAMESPACE_{namespace_upper}");
    let ffi_rustbuffer_alloc_ident = format_ident!("ffi_{module_path}_rustbuffer_alloc");
    let ffi_rustbuffer_from_bytes_ident = format_ident!("ffi_{module_path}_rustbuffer_from_bytes");
    let ffi_rustbuffer_free_ident = format_ident!("ffi_{module_path}_rustbuffer_free");
    let ffi_rustbuffer_reserve_ident = format_ident!("ffi_{module_path}_rustbuffer_reserve");
    let reexport_hack_ident = format_ident!("{module_path}_uniffi_reexport_hack");
    let ffi_rust_future_scaffolding_fns = rust_future_scaffolding_fns(&module_path);

    Ok(quote! {
        // Unit struct to parameterize the FfiConverter trait.
        //
        // We use FfiConverter<UniFfiTag> to handle lowering/lifting/serializing types for this crate.  See
        // https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait
        // for details.
        //
        // This is pub, since we need to access it to support external types
        #[doc(hidden)]
        pub struct UniFfiTag;

        #[allow(clippy::missing_safety_doc, missing_docs)]
        #[doc(hidden)]
        #[no_mangle]
        pub extern "C" fn #ffi_contract_version_ident() -> u32 {
            #UNIFFI_CONTRACT_VERSION
        }


        /// Export namespace metadata.
        ///
        /// See `uniffi_bindgen::macro_metadata` for how this is used.

        const #namespace_const_ident: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::NAMESPACE)
            .concat_str(#module_path)
            .concat_str(#namespace);

        #[doc(hidden)]
        #[no_mangle]
        pub static #namespace_static_ident: [u8; #namespace_const_ident.size] = #namespace_const_ident.into_array();

        // Everybody gets basic buffer support, since it's needed for passing complex types over the FFI.
        //
        // See `uniffi/src/ffi/rustbuffer.rs` for documentation on these functions

        #[allow(clippy::missing_safety_doc, missing_docs)]
        #[doc(hidden)]
        #[no_mangle]
        pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
            uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status)
        }

        #[allow(clippy::missing_safety_doc, missing_docs)]
        #[doc(hidden)]
        #[no_mangle]
        pub unsafe extern "C" fn #ffi_rustbuffer_from_bytes_ident(bytes: uniffi::ForeignBytes, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
            uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status)
        }

        #[allow(clippy::missing_safety_doc, missing_docs)]
        #[doc(hidden)]
        #[no_mangle]
        pub unsafe extern "C" fn #ffi_rustbuffer_free_ident(buf: uniffi::RustBuffer, call_status: &mut uniffi::RustCallStatus) {
            uniffi::ffi::uniffi_rustbuffer_free(buf, call_status);
        }

        #[allow(clippy::missing_safety_doc, missing_docs)]
        #[doc(hidden)]
        #[no_mangle]
        pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
            uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status)
        }

        #ffi_rust_future_scaffolding_fns

        // Code to re-export the UniFFI scaffolding functions.
        //
        // Rust won't always re-export the functions from dependencies
        // ([rust-lang#50007](https://github.com/rust-lang/rust/issues/50007))
        //
        // A workaround for this is to have the dependent crate reference a function from its dependency in
        // an extern "C" function. This is clearly hacky and brittle, but at least we have some unittests
        // that check if this works (fixtures/reexport-scaffolding-macro).
        //
        // The main way we use this macro is for that contain multiple UniFFI components (libxul,
        // megazord).  The combined library has a cargo dependency for each component and calls
        // uniffi_reexport_scaffolding!() for each one.

        #[allow(missing_docs)]
        #[doc(hidden)]
        pub const fn uniffi_reexport_hack() {}

        #[doc(hidden)]
        #[macro_export]
        macro_rules! uniffi_reexport_scaffolding {
            () => {
                #[doc(hidden)]
                #[no_mangle]
                pub extern "C" fn #reexport_hack_ident() {
                    $crate::uniffi_reexport_hack()
                }
            };
        }

        // A trait that's in our crate for our external wrapped types to implement.
        #[allow(unused)]
        #[doc(hidden)]
        pub trait UniffiCustomTypeConverter {
            type Builtin;
            fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> where Self: Sized;
            fn from_custom(obj: Self) -> Self::Builtin;
        }
    })
}

/// Generates the rust_future_* functions
///
/// The foreign side uses a type-erased `Handle` to interact with futures, which presents
/// a problem when creating scaffolding functions.  What is the `ReturnType` parameter of `RustFutureFfi`?
///
/// Handle this by using some brute-force monomorphization.  For each possible ffi type, we
/// generate a set of scaffolding functions.  The bindings code is responsible for calling the one
/// corresponds the scaffolding function that created the `Handle`.
///
/// This introduces safety issues, but we do get some type checking.  If the bindings code calls
/// the wrong rust_future_complete function, they should get an unexpected return type, which
/// hopefully will result in a compile-time error.
fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream {
    let fn_info = [
        (quote! { u8 }, "u8"),
        (quote! { i8 }, "i8"),
        (quote! { u16 }, "u16"),
        (quote! { i16 }, "i16"),
        (quote! { u32 }, "u32"),
        (quote! { i32 }, "i32"),
        (quote! { u64 }, "u64"),
        (quote! { i64 }, "i64"),
        (quote! { f32 }, "f32"),
        (quote! { f64 }, "f64"),
        (quote! { *const ::std::ffi::c_void }, "pointer"),
        (quote! { ::uniffi::RustBuffer }, "rust_buffer"),
        (quote! { () }, "void"),
    ];
    fn_info.iter()
    .map(|(return_type, fn_suffix)| {
        let ffi_rust_future_poll = format_ident!("ffi_{module_path}_rust_future_poll_{fn_suffix}");
        let ffi_rust_future_cancel = format_ident!("ffi_{module_path}_rust_future_cancel_{fn_suffix}");
        let ffi_rust_future_complete = format_ident!("ffi_{module_path}_rust_future_complete_{fn_suffix}");
        let ffi_rust_future_free = format_ident!("ffi_{module_path}_rust_future_free_{fn_suffix}");

        quote! {
            #[allow(clippy::missing_safety_doc, missing_docs)]
            #[doc(hidden)]
            #[no_mangle]
            pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) {
                ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data);
            }

            #[allow(clippy::missing_safety_doc, missing_docs)]
            #[doc(hidden)]
            #[no_mangle]
            pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) {
                ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle)
            }

            #[allow(clippy::missing_safety_doc, missing_docs)]
            #[doc(hidden)]
            #[no_mangle]
            pub unsafe extern "C" fn #ffi_rust_future_complete(
                handle: ::uniffi::Handle,
                out_status: &mut ::uniffi::RustCallStatus
            ) -> #return_type {
                ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status)
            }

            #[allow(clippy::missing_safety_doc, missing_docs)]
            #[doc(hidden)]
            #[no_mangle]
            pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) {
                ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle)
            }
        }
    })
    .collect()
}