diff --git a/Cargo.lock b/Cargo.lock index 04b609e9932..5bd80a305e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1911,6 +1911,7 @@ dependencies = [ name = "iroha_ffi" version = "2.0.0-pre-rc.4" dependencies = [ + "getset", "iroha_ffi_derive", ] diff --git a/client/src/http_default.rs b/client/src/http_default.rs index 6ae54235c35..8a8cc5e2e9a 100644 --- a/client/src/http_default.rs +++ b/client/src/http_default.rs @@ -18,7 +18,6 @@ trait SetSingleHeader { } impl SetSingleHeader for AttoHttpRequestBuilderWithBytes { - #[allow(clippy::only_used_in_recursion)] // False-positive fn header(self, key: HeaderName, value: String) -> Self { self.header(key, value) } diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 5ff6181a896..0e5258c339b 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -172,12 +172,12 @@ impl<'wrld, W: WorldTrait> State<'wrld, W> { } } -struct IrohaModule<'a, W: WorldTrait> { - linker: Linker>, +struct IrohaModule<'wrld, W: WorldTrait> { + linker: Linker>, module: Module, } -impl<'a, W: WorldTrait> IrohaModule<'a, W> { +impl<'wrld, W: WorldTrait> IrohaModule<'wrld, W> { fn new(engine: &Engine, module_path: &str) -> Result { let linker = Self::create_linker(engine).map_err(Error::Initialization)?; let module = Module::from_file(engine, module_path).map_err(Error::Initialization)?; @@ -185,7 +185,7 @@ impl<'a, W: WorldTrait> IrohaModule<'a, W> { Ok(Self { linker, module }) } - fn create_linker(engine: &Engine) -> Result>, anyhow::Error> { + fn create_linker(engine: &Engine) -> Result>, anyhow::Error> { let mut linker = Linker::new(engine); linker @@ -224,7 +224,7 @@ impl<'a, W: WorldTrait> IrohaModule<'a, W> { .ok_or_else(|| Trap::new(format!("{}: not a memory", WASM_MEMORY_NAME))) } - fn instantiate(&self, store: &mut Store>) -> Result { + fn instantiate(&self, store: &mut Store>) -> Result { self.linker .instantiate(store, &self.module) .map_err(Error::Instantiation) diff --git a/data_model/src/account.rs b/data_model/src/account.rs index 5b00f15264c..3fff95b706d 100644 --- a/data_model/src/account.rs +++ b/data_model/src/account.rs @@ -107,6 +107,7 @@ impl Default for SignatureCheckCondition { } /// Builder which can be submitted in a transaction to create a new [`Account`] +#[allow(clippy::multiple_inherent_impl)] #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct NewAccount { id: Id, @@ -140,13 +141,6 @@ impl NewAccount { } } - /// Add [`Metadata`] to the account replacing previously defined - #[must_use] - pub fn with_metadata(mut self, metadata: Metadata) -> Self { - self.metadata = metadata; - self - } - /// Construct [`Account`] #[must_use] #[cfg(feature = "mutable_api")] @@ -163,6 +157,16 @@ impl NewAccount { } } +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] +impl NewAccount { + /// Add [`Metadata`] to the account replacing previously defined + #[must_use] + pub fn with_metadata(mut self, metadata: Metadata) -> Self { + self.metadata = metadata; + self + } +} + /// Account entity is an authority which is used to execute `Iroha Special Instructions`. #[derive( Debug, @@ -180,6 +184,7 @@ impl NewAccount { )] #[getset(get = "pub")] #[allow(clippy::multiple_inherent_impl)] +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] pub struct Account { /// An Identification of the [`Account`]. id: ::Id, @@ -222,6 +227,7 @@ impl Ord for Account { } } +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] impl Account { /// Construct builder for [`Account`] identifiable by [`Id`] containing the given signatories. #[must_use] diff --git a/data_model/src/asset.rs b/data_model/src/asset.rs index d407c4ff2d0..a5b8f2d47aa 100644 --- a/data_model/src/asset.rs +++ b/data_model/src/asset.rs @@ -46,6 +46,8 @@ pub type AssetDefinitionsMap = IntoSchema, )] #[getset(get = "pub")] +#[allow(clippy::multiple_inherent_impl)] +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] pub struct AssetDefinitionEntry { /// Asset definition. #[cfg_attr(feature = "mutable_api", getset(get_mut = "pub"))] @@ -68,6 +70,7 @@ impl Ord for AssetDefinitionEntry { } } +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] impl AssetDefinitionEntry { /// Constructor. pub const fn new( @@ -79,12 +82,14 @@ impl AssetDefinitionEntry { registered_by, } } +} +#[cfg(feature = "mutable_api")] +impl AssetDefinitionEntry { /// Turn off minting for this asset. /// /// # Errors /// If the asset was declared as `Mintable::Infinitely` - #[cfg(feature = "mutable_api")] pub fn forbid_minting(&mut self) -> Result<(), super::MintabilityError> { self.definition.forbid_minting() } @@ -106,6 +111,8 @@ impl AssetDefinitionEntry { IntoSchema, )] #[getset(get = "pub")] +#[allow(clippy::multiple_inherent_impl)] +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] pub struct AssetDefinition { /// An Identification of the [`AssetDefinition`]. id: ::Id, @@ -165,6 +172,7 @@ pub enum Mintable { Debug, Clone, PartialEq, Eq, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] #[getset(get = "pub")] +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] pub struct Asset { /// Component Identification. id: ::Id, @@ -361,6 +369,7 @@ pub struct Id { } /// Builder which can be submitted in a transaction to create a new [`AssetDefinition`] +#[allow(clippy::multiple_inherent_impl)] #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub struct NewAssetDefinition { id: ::Id, @@ -394,6 +403,22 @@ impl NewAssetDefinition { } } + /// Construct [`AssetDefinition`] + #[inline] + #[must_use] + #[cfg(feature = "mutable_api")] + pub fn build(self) -> AssetDefinition { + AssetDefinition { + id: self.id, + value_type: self.value_type, + mintable: self.mintable, + metadata: self.metadata, + } + } +} + +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] +impl NewAssetDefinition { /// Set mintability to [`Mintable::Once`] #[inline] #[must_use] @@ -409,21 +434,9 @@ impl NewAssetDefinition { self.metadata = metadata; self } - - /// Construct [`AssetDefinition`] - #[inline] - #[must_use] - #[cfg(feature = "mutable_api")] - pub fn build(self) -> AssetDefinition { - AssetDefinition { - id: self.id, - value_type: self.value_type, - mintable: self.mintable, - metadata: self.metadata, - } - } } +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] impl AssetDefinition { /// Construct builder for [`AssetDefinition`] identifiable by [`Id`]. #[must_use] @@ -452,13 +465,15 @@ impl AssetDefinition { pub fn store(id: ::Id) -> ::RegisteredWith { ::RegisteredWith::new(id, AssetValueType::Store) } +} +#[cfg(feature = "mutable_api")] +impl AssetDefinition { /// Stop minting on the [`AssetDefinition`] globally. /// /// # Errors /// If the [`AssetDefinition`] is not `Mintable::Once`. #[inline] - #[cfg(feature = "mutable_api")] pub fn forbid_minting(&mut self) -> Result<(), super::MintabilityError> { if self.mintable == Mintable::Once { self.mintable = Mintable::Not; @@ -469,11 +484,12 @@ impl AssetDefinition { } } +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] impl Asset { /// Constructor - pub fn new>( + pub fn new( id: ::Id, - value: V, + value: impl Into, ) -> ::RegisteredWith { Self { id, diff --git a/data_model/src/domain.rs b/data_model/src/domain.rs index a39a101911a..f9ff7b95416 100644 --- a/data_model/src/domain.rs +++ b/data_model/src/domain.rs @@ -11,6 +11,8 @@ use core::{cmp::Ordering, fmt, str::FromStr}; use getset::{Getters, MutGetters}; use iroha_crypto::PublicKey; +#[cfg(feature = "ffi")] +use iroha_ffi::ffi_bindgen; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode, Input}; use serde::{Deserialize, Serialize}; @@ -68,6 +70,7 @@ impl From for Domain { /// Builder which can be submitted in a transaction to create a new [`Domain`] #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] +#[allow(clippy::multiple_inherent_impl)] pub struct NewDomain { /// The identification associated to the domain builder. id: ::Id, @@ -102,6 +105,22 @@ impl NewDomain { } } + /// Construct [`Domain`] + #[must_use] + #[cfg(feature = "mutable_api")] + pub fn build(self) -> Domain { + Domain { + id: self.id, + accounts: AccountsMap::default(), + asset_definitions: AssetDefinitionsMap::default(), + metadata: self.metadata, + logo: self.logo, + } + } +} + +#[cfg_attr(feature = "ffi", ffi_bindgen)] +impl NewDomain { /// Add [`logo`](IpfsPath) to the domain replacing previously defined value #[must_use] pub fn with_logo(mut self, logo: IpfsPath) -> Self { @@ -115,19 +134,6 @@ impl NewDomain { self.metadata = metadata; self } - - /// Construct [`Domain`] - #[must_use] - #[cfg(feature = "mutable_api")] - pub fn build(self) -> Domain { - Domain { - id: self.id, - accounts: AccountsMap::default(), - asset_definitions: AssetDefinitionsMap::default(), - metadata: self.metadata, - logo: self.logo, - } - } } /// Named group of [`Account`] and [`Asset`](`crate::asset::Asset`) entities. @@ -146,6 +152,7 @@ impl NewDomain { )] #[getset(get = "pub")] #[allow(clippy::multiple_inherent_impl)] +#[cfg_attr(feature = "ffi", ffi_bindgen)] pub struct Domain { /// Identification of this [`Domain`]. id: ::Id, @@ -181,6 +188,7 @@ impl Ord for Domain { } } +#[cfg_attr(feature = "ffi", ffi_bindgen)] impl Domain { /// Construct builder for [`Domain`] identifiable by [`Id`]. pub fn new(id: ::Id) -> ::RegisteredWith { diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index 3221ab1f48e..5b42ed9ba97 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -91,6 +91,7 @@ impl Limits { IntoSchema, )] #[serde(transparent)] +#[allow(clippy::multiple_inherent_impl)] pub struct Metadata { map: btree_map::BTreeMap, } @@ -98,6 +99,7 @@ pub struct Metadata { /// A path slice, composed of [`Name`]s. pub type Path = [Name]; +#[cfg_attr(feature = "ffi", iroha_ffi::ffi_bindgen)] impl Metadata { /// Constructor. #[inline] @@ -129,20 +131,21 @@ impl Metadata { map.get(key) } - /// Remove leaf node in metadata, given path. If the path is - /// malformed, or incorrect (if e.g. any of interior path segments - /// are not [`Metadata`] instances) return `None`. Else return the - /// owned value corresponding to that path. - pub fn nested_remove(&mut self, path: &Path) -> Option { - let key = path.last()?; - let mut map = &mut self.map; - for k in path.iter().take(path.len() - 1) { - map = match map.get_mut(k)? { - Value::LimitedMetadata(data) => &mut data.map, - _ => return None, - }; - } - map.remove(key) + /// Returns iterator over key - value pairs stored in [`Metadata`] + pub fn iter(&self) -> impl ExactSizeIterator { + self.map.iter() + } +} + +impl Metadata { + /// Returns a `Some(reference)` to the value corresponding to + /// the key, and `None` if not found. + #[inline] + pub fn get(&self, key: &K) -> Option<&Value> + where + Name: Borrow, + { + self.map.get(key) } /// Insert the given [`Value`] into the given path. If the path is @@ -200,17 +203,10 @@ impl Metadata { check_size_limits(&key, value.clone(), limits)?; Ok(self.map.insert(key, value)) } +} - /// Returns a `Some(reference)` to the value corresponding to - /// the key, and `None` if not found. - #[inline] - pub fn get(&self, key: &K) -> Option<&Value> - where - Name: Borrow, - { - self.map.get(key) - } - +#[cfg(feature = "mutable_api")] +impl Metadata { /// Removes a key from the map, returning the owned /// `Some(value)` at the key if the key was previously in the /// map, else `None`. @@ -222,9 +218,20 @@ impl Metadata { self.map.remove(key) } - /// Returns iterator over key - value pairs stored in [`Metadata`] - pub fn iter(&self) -> btree_map::Iter { - self.map.iter() + /// Remove leaf node in metadata, given path. If the path is + /// malformed, or incorrect (if e.g. any of interior path segments + /// are not [`Metadata`] instances) return `None`. Else return the + /// owned value corresponding to that path. + pub fn nested_remove(&mut self, path: &Path) -> Option { + let key = path.last()?; + let mut map = &mut self.map; + for k in path.iter().take(path.len() - 1) { + map = match map.get_mut(k)? { + Value::LimitedMetadata(data) => &mut data.map, + _ => return None, + }; + } + map.remove(key) } } @@ -266,6 +273,7 @@ mod tests { } #[test] + #[cfg(feature = "mutable_api")] fn nested_fns_ignore_empty_path() { let mut metadata = Metadata::new(); let empty_path = vec![]; @@ -278,6 +286,7 @@ mod tests { #[test] #[allow(clippy::unwrap_used)] + #[cfg(feature = "mutable_api")] fn nesting_inserts_removes() -> Result<(), TestError> { let mut metadata = Metadata::new(); let limits = Limits::new(1024, 1024); @@ -311,6 +320,7 @@ mod tests { } #[test] + #[cfg(feature = "mutable_api")] fn non_existent_path_segment_fails() -> Result<(), TestError> { let mut metadata = Metadata::new(); let limits = Limits::new(10, 15); diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 1dd941fda39..c04b3e28582 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [dependencies] iroha_ffi_derive = { version = "=2.0.0-pre-rc.4", path = "derive" } + +[dev-dependencies] +getset = "0.1.2" diff --git a/ffi/derive/src/ffi.rs b/ffi/derive/src/ffi.rs index e67b05b9a07..c42a67c3973 100644 --- a/ffi/derive/src/ffi.rs +++ b/ffi/derive/src/ffi.rs @@ -60,6 +60,7 @@ impl quote::ToTokens for FfiFnDescriptor { let fn_body = self.get_fn_body(); tokens.extend(quote! { + #[doc = "Generated FFI function equivalent of [`#self_ty::#method_name`]"] pub unsafe extern "C" fn #ffi_fn_name(#(#self_arg,)* #(#fn_args,)* #ret_arg) -> iroha_ffi::FfiResult { #fn_body } @@ -73,35 +74,52 @@ impl quote::ToTokens for FfiFnArgDescriptor { let src_type = &self.src_type; let ffi_type = &self.ffi_type; - tokens.extend(if self.is_slice_ref() { - quote! { mut #ffi_name: #ffi_type } - } else { - quote! { #ffi_name: #ffi_type} - }); - - let mut slice_len_to_tokens = |mutability| { - let ffi_slice_len_arg_name = self.get_slice_len_arg_name(); + if self.is_slice_ref() || self.is_slice_ref_mut() { + tokens.extend(quote! { mut #ffi_name: #ffi_type, }); - if mutability { - tokens.extend(quote! {, #ffi_slice_len_arg_name: *mut usize }); - } else { - tokens.extend(quote! {, #ffi_slice_len_arg_name: usize }); - } + let slice_len_arg_name = &self.get_slice_len_arg_name(); + slice_len_arg_to_tokens(src_type, slice_len_arg_name, tokens); + } else { + tokens.extend(quote! { #ffi_name: #ffi_type }); }; + } +} + +fn slice_len_arg_to_tokens( + src_type: &Type, + slice_len_arg_name: &Ident, + tokens: &mut proc_macro2::TokenStream, +) { + let mut slice_len_to_tokens = |mutability| { + if mutability { + tokens.extend(quote! { #slice_len_arg_name: *mut usize }); + } else { + tokens.extend(quote! { #slice_len_arg_name: usize }); + } + }; - // TODO: Use newly defined functions - if let Type::Reference(type_) = &src_type { + match &src_type { + Type::Reference(type_) => { if matches!(*type_.elem, Type::Slice(_)) { slice_len_to_tokens(type_.mutability.is_some()); } - } else if matches!(src_type, Type::ImplTrait(_)) { - match ffi_type { - Type::Ptr(ptr_ty) => { - slice_len_to_tokens(ptr_ty.mutability.is_some()); + } + Type::ImplTrait(type_) => { + assert_eq!(type_.bounds.len(), 1); + + if let syn::TypeParamBound::Trait(trait_) = &type_.bounds[0] { + let last_seg = &trait_.path.segments.last().expect_or_abort("Defined"); + + if last_seg.ident == "IntoIterator" { + slice_len_to_tokens(false); + } else if last_seg.ident == "ExactSizeIterator" { + slice_len_to_tokens(true); + } else { + abort!(src_type, "Unsupported impl trait slice type") } - _ => unreachable!("Incorrectly transcribed type -> {:?}", ffi_type), } } + _ => {} } } @@ -422,6 +440,81 @@ impl FfiFnArgDescriptor { Ident::new(&format!("{}_len", self.ffi_name), Span::call_site()) } + fn get_src_to_ffi_impl_iterator_conversion_stmts( + &self, + ffi_type: &syn::TypePtr, + ) -> Vec { + let mut stmts = vec![]; + + match &*ffi_type.elem { + Type::Path(type_) => { + let last_seg = type_.path.segments.last().expect_or_abort("Defined"); + + if last_seg.ident == "Pair" { + stmts.push(parse_quote! { + let method_res = method_res.map(|(key, val)| { + iroha_ffi::Pair(key as *const _, val as *const _) + }); + }); + } else { + abort!(self, "Unsupported FFI type conversion"); + } + } + Type::Ptr(type_) => { + stmts.push(if type_.mutability.is_some() { + parse_quote! { let method_res = method_res.map(|arg| arg as *mut _); } + } else { + parse_quote! { let method_res = method_res.map(|arg| arg as *const _); } + }); + } + _ => abort!(self, "Unsupported FFI type conversion"), + } + + stmts.push(parse_quote! { + // TODO: Seems that the implementation reallocates even for `ExactSizeIterator` + // Optimize collecting to avoid reallocation in case of `ExactSizeIterator` + let mut method_res = core::mem::ManuallyDrop::new(method_res.collect::>()); + }); + + stmts + } + + fn get_ffi_to_src_impl_into_iterator_conversion_stmts( + &self, + ffi_type: &syn::TypePtr, + ) -> Vec { + let slice_len_arg_name = self.get_slice_len_arg_name(); + + let arg_name = &self.ffi_name; + let mut stmts = vec![parse_quote! { + let #arg_name = core::slice::from_raw_parts(#arg_name, #slice_len_arg_name).into_iter(); + }]; + + match &*ffi_type.elem { + Type::Path(type_) => { + let last_seg = type_.path.segments.last().expect_or_abort("Defined"); + + if last_seg.ident == "Pair" { + stmts.push(parse_quote! { + let #arg_name = #arg_name.map(|iroha_ffi::Pair(key, val)| { + (key.read(), val.read()) + }); + }); + } else { + abort!(last_seg, "Collection item not supported in FFI") + } + } + Type::Ptr(_) => { + stmts.push(parse_quote! { + let #arg_name = #arg_name.map(|ptr| ptr.read()); + }); + } + _ => abort!(self, "Unsupported FFI type conversion"), + } + + stmts + } + fn get_ffi_to_src_conversion_stmts(&self) -> Vec { let mut stmts = vec![]; @@ -435,41 +528,18 @@ impl FfiFnArgDescriptor { } } (Type::ImplTrait(src_ty), Type::Ptr(ffi_ty)) => { - let slice_len_arg_name = self.get_slice_len_arg_name(); - - // TODO: Should be just a temporary sanity check if let syn::TypeParamBound::Trait(trait_) = &src_ty.bounds[0] { - let last_seg = &trait_.path.segments.last().expect_or_abort("Defined").ident; - - if last_seg != "IntoIterator" { - abort!(last_seg, "impl Trait type not supported"); - } - } - - stmts.push(parse_quote! { - let #arg_name = core::slice::from_raw_parts(#arg_name, #slice_len_arg_name).into_iter(); - }); - - match &*ffi_ty.elem { - Type::Path(type_) => { - let last_seg = type_.path.segments.last().expect_or_abort("Defined"); - - if last_seg.ident == "Pair" { - stmts.push(parse_quote! { - let #arg_name = #arg_name.map(|iroha_ffi::Pair(key, val)| { - (key.read(), val.read()) - }); - }); - } else { - abort!(last_seg, "Collection item not supported in FFI") - } - } - Type::Ptr(_) => { - stmts.push(parse_quote! { - let #arg_name = #arg_name.map(|ptr| ptr.read()); - }); + let last_seg = &trait_.path.segments.last().expect_or_abort("Defined"); + + match last_seg.ident.to_string().as_ref() { + "IntoIterator" => stmts.extend( + self.get_ffi_to_src_impl_into_iterator_conversion_stmts(ffi_ty), + ), + "Into" => stmts.push(parse_quote! { + let #arg_name = #arg_name.read(); + }), + _ => abort!(last_seg, "impl Trait type not supported"), } - _ => abort!(self, "Unsupported FFI type conversion"), } } (Type::Path(_), Type::Ptr(_)) => { @@ -501,46 +571,17 @@ impl FfiFnArgDescriptor { }); } (Type::ImplTrait(src_ty), Type::Ptr(ffi_ty)) => { - stmts.push(parse_quote! { let method_res = method_res.into_iter(); }); - - // TODO: Should be just a temporary sanity check if let syn::TypeParamBound::Trait(trait_) = &src_ty.bounds[0] { - let last_seg = &trait_.path.segments.last().expect_or_abort("Defined").ident; - - if last_seg != "ExactSizeIterator" { - abort!(last_seg, "impl Trait type not supported"); - } - } - - match &*ffi_ty.elem { - Type::Path(type_) => { - let last_seg = type_.path.segments.last().expect_or_abort("Defined"); + let last_seg = &trait_.path.segments.last().expect_or_abort("Defined"); - if last_seg.ident == "Pair" { - stmts.push(parse_quote! { - let method_res = method_res.map(|(key, val)| { - iroha_ffi::Pair(key as *const _, val as *const _) - }); - }); - } else { - abort!(self, "Unsupported FFI type conversion"); + match last_seg.ident.to_string().as_ref() { + "ExactSizeIterator" => { + stmts.push(parse_quote! { let method_res = method_res.into_iter(); }); + stmts.extend(self.get_src_to_ffi_impl_iterator_conversion_stmts(ffi_ty)) } + _ => abort!(last_seg, "impl Trait type not supported"), } - Type::Ptr(type_) => { - stmts.push(if type_.mutability.is_some() { - parse_quote! { let method_res = method_res.map(|arg| arg as *mut _); } - } else { - parse_quote! { let method_res = method_res.map(|arg| arg as *const _); } - }); - } - _ => abort!(self, "Unsupported FFI type conversion"), } - - stmts.push(parse_quote! { - // TODO: Seems that the implementation reallocates even for `ExactSizeIterator` - // Optimize collecting to avoid reallocation in case of `ExactSizeIterator` - let mut method_res = core::mem::ManuallyDrop::new(method_res.collect::>()); - }); } (Type::Path(src_ty), Type::Ptr(ffi_ty)) => { let is_option_type = is_option_type(src_ty); @@ -773,7 +814,6 @@ impl<'ast> syn::visit::Visit<'ast> for FfiFnDescriptor { abort_call_site!("Lifetime bound not supported for the `impl trait` argument"); } - let is_output_arg = self.curr_arg_name.is_none(); match last_seg.ident.to_string().as_str() { "IntoIterator" | "ExactSizeIterator" => { let item = if let AngleBracketed(arguments) = &last_seg.arguments { @@ -802,6 +842,7 @@ impl<'ast> syn::visit::Visit<'ast> for FfiFnDescriptor { self.visit_type(item); self.curr_arg_ty = { let ffi_subty = self.curr_arg_ty.as_ref().map(|ty| &ty.1); + let is_output_arg = self.curr_arg_name.is_none(); Some(( Type::ImplTrait(node.clone()), @@ -825,15 +866,9 @@ impl<'ast> syn::visit::Visit<'ast> for FfiFnDescriptor { self.visit_type(item); self.curr_arg_ty = { - let ffi_subty = self.curr_arg_ty.as_ref().map(|ty| &ty.1); - Some(( Type::ImplTrait(node.clone()), - if is_output_arg { - parse_quote! { *mut #ffi_subty } - } else { - parse_quote! { *const #ffi_subty } - }, + self.curr_arg_ty.take().expect_or_abort("Defined").1, )) }; } @@ -961,12 +996,12 @@ impl<'ast> syn::visit::Visit<'ast> for FfiFnDescriptor { /// Visitor for path types which replaces all occurrences of `Self` with a fully qualified type /// Additionally, visitor expands the integers to fit the size of `WebAssembly` defined types -struct FfiTypePath { +pub struct FfiTypePath { self_ty: syn::Path, } impl FfiTypePath { - fn new(self_ty: syn::Path) -> Self { + pub fn new(self_ty: syn::Path) -> Self { Self { self_ty } } } diff --git a/ffi/derive/src/lib.rs b/ffi/derive/src/lib.rs index 8fadb358990..03ea8886d4f 100644 --- a/ffi/derive/src/lib.rs +++ b/ffi/derive/src/lib.rs @@ -1,11 +1,12 @@ #![allow(clippy::str_to_string, missing_docs)] +use heck::ToSnakeCase; use proc_macro::TokenStream; -use proc_macro_error::abort; +use proc_macro_error::{abort, OptionExt}; use quote::quote; -use syn::{parse_macro_input, visit::Visit, Item}; +use syn::{parse_macro_input, parse_quote, visit::Visit, visit_mut::VisitMut, Item, ItemStruct}; -use crate::ffi::ImplDescriptor; +use crate::ffi::{FfiTypePath, ImplDescriptor}; mod ffi; @@ -24,18 +25,6 @@ pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { } } Item::Struct(item) => { - use heck::ToSnakeCase; - - let struct_name = &item.ident; - let drop_ffi_fn_name = syn::Ident::new( - &format!("{}_drop", struct_name.to_string().to_snake_case()), - proc_macro2::Span::call_site(), - ); - - for __attr in &item.attrs { - // TODO: Generate from getset. - // Also check for repr(C)? - } if !matches!(item.vis, syn::Visibility::Public(_)) { abort!(item.vis, "Only public structs allowed in FFI"); } @@ -43,9 +32,19 @@ pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { abort!(item.generics, "Generic structs not supported"); } + let struct_name = &item.ident; + let ffi_fns = get_ffi_getters(&item); + let drop_ffi_fn_name = syn::Ident::new( + &format!("{}_drop", snake_case_ident(struct_name)), + proc_macro2::Span::call_site(), + ); + quote! { #item + #( #ffi_fns )* + + #[doc = "Drop function for [`#struct_name`]"] // TODO: This fn could be made generic? Which pointer type to take if so? pub unsafe extern "C" fn #drop_ffi_fn_name(handle: *mut #struct_name) -> iroha_ffi::FfiResult { core::mem::drop(Box::from_raw(handle)); @@ -57,3 +56,65 @@ pub fn ffi_bindgen(_attr: TokenStream, item: TokenStream) -> TokenStream { } .into() } + +/// Look for a `skip` of the attribute identified by `attr_ident`. +pub(crate) fn should_skip(attrs: &[syn::Attribute], attr_ident: &str) -> bool { + attrs.iter().any(|attr| { + if attr.path.is_ident(attr_ident) { + if let Ok(path) = attr.parse_args::() { + return path.is_ident("skip"); + } + } + + false + }) +} + +fn get_ffi_getters(item: &ItemStruct) -> Vec { + match &item.fields { + syn::Fields::Unnamed(_) | syn::Fields::Unit => unreachable!("Only named structs supported"), + syn::Fields::Named(fields) => { + let mut ffi_fns = vec![]; + + for field in &fields.named { + if let Some(ffi_fn) = generate_ffi_getter(&item.ident, field) { + ffi_fns.push(ffi_fn); + } + } + + ffi_fns + } + } +} + +fn generate_ffi_getter(struct_name: &syn::Ident, field: &syn::Field) -> Option { + let field_name = field.ident.as_ref().expect_or_abort("Defined"); + + if should_skip(&field.attrs, "getset") { + return None; + } + + if let syn::Type::Path(mut field_ty) = field.ty.clone() { + FfiTypePath::new(parse_quote! { #struct_name }).visit_type_path_mut(&mut field_ty); + + let ffi_fn_name = syn::Ident::new( + &format!("{}_{}", snake_case_ident(struct_name), field_name), + proc_macro2::Span::call_site(), + ); + + return Some(parse_quote! { + /// Generated FFI function equivalent of [`#field_name`] + pub unsafe extern "C" fn #ffi_fn_name(handle: *const #struct_name, output: *mut *const #field_ty) -> iroha_ffi::FfiResult { + let handle = &*handle; + output.write(handle.#field_name() as *const _); + iroha_ffi::FfiResult::Ok + } + }); + } + + None +} + +fn snake_case_ident(ident: &syn::Ident) -> String { + ident.to_string().to_snake_case() +} diff --git a/ffi/tests/ffi_bindgen.rs b/ffi/tests/ffi_bindgen.rs index c9b354e305c..1039b65b661 100644 --- a/ffi/tests/ffi_bindgen.rs +++ b/ffi/tests/ffi_bindgen.rs @@ -1,28 +1,43 @@ +#![allow(unsafe_code)] +#![allow(clippy::restriction)] + use std::{collections::BTreeMap, mem::MaybeUninit}; use iroha_ffi::{ffi_bindgen, FfiResult, Pair}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Name(&'static str); -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Value(&'static str); const DEFAULT_PARAMS: [(Name, Value); 1] = [(Name("Nomen"), Value("Omen"))]; #[ffi_bindgen] +#[derive(getset::Getters)] +#[getset(get = "pub")] pub struct FfiStruct { name: Name, + #[getset(skip)] + tokens: Vec, + #[getset(skip)] params: BTreeMap, } #[ffi_bindgen] impl FfiStruct { - pub fn new(name: Name) -> Self { + pub fn new(name: impl Into) -> Self { Self { - name, + name: name.into(), + tokens: Vec::new(), params: BTreeMap::default(), } } + #[must_use] + pub fn with_tokens(mut self, tokens: impl IntoIterator>) -> Self { + self.tokens = tokens.into_iter().map(Into::into).collect(); + self + } + #[must_use] pub fn with_params(mut self, params: impl IntoIterator) -> Self { self.params = params.into_iter().collect(); self @@ -50,6 +65,7 @@ fn get_new_struct() -> *mut FfiStruct { } } +#[allow(trivial_casts)] fn get_new_struct_with_params() -> *mut FfiStruct { let ffi_struct = get_new_struct(); let params_ffi: Vec<_> = DEFAULT_PARAMS @@ -68,8 +84,30 @@ fn constructor() { let ffi_struct = get_new_struct(); unsafe { - assert_eq!(Name("X"), (&*ffi_struct).name); - assert!((&*ffi_struct).params.is_empty()); + assert_eq!(Name("X"), (*ffi_struct).name); + assert!((*ffi_struct).params.is_empty()); + assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); + } +} + +#[test] +#[allow(trivial_casts)] +fn into_iter_item_impl_into() { + let ffi_struct = get_new_struct(); + + let tokens = vec![Value("My omen"), Value("Your omen")]; + let tokens_ffi: Vec<_> = tokens.iter().map(|t| t as *const _).collect(); + + unsafe { + assert_eq!( + FfiResult::Ok, + ffi_struct_with_tokens(ffi_struct, tokens_ffi.as_ptr(), tokens_ffi.len()) + ); + + assert_eq!(2, (*ffi_struct).tokens.len()); + assert_eq!((*ffi_struct).tokens, tokens); + + assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); } } @@ -78,8 +116,9 @@ fn builder_method() { let ffi_struct = get_new_struct_with_params(); unsafe { - assert_eq!(1, (&*ffi_struct).params.len()); - assert_eq!((&*ffi_struct).params, DEFAULT_PARAMS.into_iter().collect()); + assert_eq!(1, (*ffi_struct).params.len()); + assert_eq!((*ffi_struct).params, DEFAULT_PARAMS.into_iter().collect()); + assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); } } @@ -104,6 +143,7 @@ fn return_option() { unsafe { assert!(!param2.assume_init().is_null()); assert_eq!(&Value("Omen"), &*param2.assume_init()); + assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); } } @@ -129,15 +169,17 @@ fn return_iterator() { .map(|&Pair(key, val)| (&*key, &*val)) .eq(DEFAULT_PARAMS.iter().map(|pair| (&pair.0, &pair.1)))); + assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); // TODO: Call FFI destructor for the received params vector } } #[test] -fn drop_ffi_struct() { +fn getset_getter() { let ffi_struct = get_new_struct_with_params(); unsafe { + assert_eq!(Name("X"), *(*ffi_struct).name()); assert_eq!(FfiResult::Ok, ffi_struct_drop(ffi_struct)); } }